@cnrai/pave 0.3.35 → 0.3.51
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/LICENSE +21 -0
- package/README.md +21 -218
- package/package.json +32 -35
- package/pave.js +3 -0
- package/sandbox/SandboxRunner.js +1 -0
- package/sandbox/pave-run.js +2 -0
- package/sandbox/permission.js +1 -0
- package/sandbox/utils/yaml.js +1 -0
- package/MARKETPLACE.md +0 -406
- package/build-binary.js +0 -591
- package/build-npm.js +0 -537
- package/build.js +0 -230
- package/check-binary.js +0 -26
- package/deploy.sh +0 -95
- package/index.js +0 -5776
- package/lib/agent-registry.js +0 -1037
- package/lib/args-parser.js +0 -837
- package/lib/blessed-widget-patched.js +0 -93
- package/lib/cli-markdown.js +0 -590
- package/lib/compaction.js +0 -153
- package/lib/duration.js +0 -94
- package/lib/hash.js +0 -22
- package/lib/marketplace.js +0 -866
- package/lib/memory-config.js +0 -166
- package/lib/skill-manager.js +0 -891
- package/lib/soul.js +0 -31
- package/lib/tool-output-formatter.js +0 -180
- package/start-pave.sh +0 -149
- package/status.js +0 -271
- package/test/abort-stream.test.js +0 -445
- package/test/agent-auto-compaction.test.js +0 -552
- package/test/agent-comm-abort.test.js +0 -95
- package/test/agent-comm.test.js +0 -598
- package/test/agent-inbox.test.js +0 -576
- package/test/agent-init.test.js +0 -264
- package/test/agent-interrupt.test.js +0 -314
- package/test/agent-lifecycle.test.js +0 -520
- package/test/agent-log-files.test.js +0 -349
- package/test/agent-mode.manual-test.js +0 -392
- package/test/agent-parsing.test.js +0 -228
- package/test/agent-post-stream-idle.test.js +0 -762
- package/test/agent-registry.test.js +0 -359
- package/test/agent-rm.test.js +0 -442
- package/test/agent-spawn.test.js +0 -933
- package/test/agent-status-api.test.js +0 -624
- package/test/agent-update.test.js +0 -435
- package/test/args-parser.test.js +0 -391
- package/test/auto-compaction-chat.manual-test.js +0 -227
- package/test/auto-compaction.test.js +0 -941
- package/test/build-config.test.js +0 -120
- package/test/build-npm.test.js +0 -388
- package/test/chat-command.test.js +0 -137
- package/test/chat-leading-lines.test.js +0 -159
- package/test/config-flag.test.js +0 -272
- package/test/cursor-drift.test.js +0 -135
- package/test/debug-require.js +0 -23
- package/test/dir-migration.test.js +0 -323
- package/test/duration.test.js +0 -229
- package/test/ghostty-term.test.js +0 -202
- package/test/http500-backoff.test.js +0 -854
- package/test/integration.test.js +0 -86
- package/test/memory-guard-env.test.js +0 -220
- package/test/pr233-fixes.test.js +0 -259
- package/test/run-agent-init.js +0 -297
- package/test/run-all.js +0 -64
- package/test/run-config-flag.js +0 -159
- package/test/run-cursor-drift.js +0 -82
- package/test/run-session-path.js +0 -154
- package/test/run-tests.js +0 -643
- package/test/sandbox-redirect.test.js +0 -202
- package/test/session-path.test.js +0 -132
- package/test/shebang-strip.test.js +0 -241
- package/test/soul-reinject.test.js +0 -1027
- package/test/soul-reread.test.js +0 -281
- package/test/tool-output-formatter.test.js +0 -486
- package/test/tool-output-gating.test.js +0 -143
- package/test/tool-states.test.js +0 -167
- package/test/tools-flag.test.js +0 -65
- package/test/tui-attach.test.js +0 -1255
- package/test/tui-compaction.test.js +0 -354
- package/test/tui-wrap.test.js +0 -568
- package/test-binary.js +0 -52
- package/test-binary2.js +0 -36
package/test/agent-comm.test.js
DELETED
|
@@ -1,598 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* Tests for agent communication tools (Issue #203)
|
|
4
|
-
* Phase 6: agent_send and agent_list tools
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
const path = require('path');
|
|
8
|
-
const fs = require('fs');
|
|
9
|
-
const os = require('os');
|
|
10
|
-
|
|
11
|
-
const registryPath = path.join(__dirname, '..', 'lib', 'agent-registry.js');
|
|
12
|
-
const registry = require(registryPath);
|
|
13
|
-
|
|
14
|
-
const agentCommPath = path.join(__dirname, '..', '..', 'opencode-lite', 'src', 'tools', 'agent-comm.js');
|
|
15
|
-
const agentComm = require(agentCommPath);
|
|
16
|
-
|
|
17
|
-
const testDir = path.join(os.tmpdir(), 'pave-test-agent-comm-' + (process.pid || Date.now()));
|
|
18
|
-
|
|
19
|
-
// Detect whether filesystem operations work (sandbox may lack mkdirSync)
|
|
20
|
-
let canMkdir = false;
|
|
21
|
-
try {
|
|
22
|
-
if (typeof fs.mkdirSync === 'function') {
|
|
23
|
-
fs.mkdirSync(testDir, { recursive: true });
|
|
24
|
-
canMkdir = true;
|
|
25
|
-
try { fs.rmSync(testDir, { recursive: true }); } catch (_) {}
|
|
26
|
-
}
|
|
27
|
-
} catch (_) {}
|
|
28
|
-
|
|
29
|
-
let passed = 0;
|
|
30
|
-
let failed = 0;
|
|
31
|
-
|
|
32
|
-
function assert(condition, msg) {
|
|
33
|
-
if (!condition) throw new Error('Assertion failed: ' + msg);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function runTest(name, fn) {
|
|
37
|
-
try {
|
|
38
|
-
fn();
|
|
39
|
-
console.log('\u2705 ' + name);
|
|
40
|
-
passed++;
|
|
41
|
-
} catch (e) {
|
|
42
|
-
console.log('\u274C ' + name + ': ' + e.message);
|
|
43
|
-
failed++;
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
function resetTestDir() {
|
|
48
|
-
if (!canMkdir) return false;
|
|
49
|
-
try { fs.rmSync(testDir, { recursive: true }); } catch (e) {}
|
|
50
|
-
try { fs.mkdirSync(testDir, { recursive: true }); } catch (e) {}
|
|
51
|
-
registry.setAgentsDir(testDir);
|
|
52
|
-
return true;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
function cleanup() {
|
|
56
|
-
try { fs.rmSync(testDir, { recursive: true }); } catch (e) {}
|
|
57
|
-
registry.resetAgentsDir();
|
|
58
|
-
// Clear rate limit map between tests
|
|
59
|
-
agentComm._rateLimitMap.clear();
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// ============================================================
|
|
63
|
-
// Registration
|
|
64
|
-
// ============================================================
|
|
65
|
-
|
|
66
|
-
runTest('isAgentCommRegistered: false before registration', () => {
|
|
67
|
-
// Clear any prior registration
|
|
68
|
-
agentComm.registerAgentComm(null, null);
|
|
69
|
-
assert(agentComm.isAgentCommRegistered() === false, 'should be false');
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
runTest('registerAgentComm: sets registry and agent name', () => {
|
|
73
|
-
agentComm.registerAgentComm(registry, 'test-sender');
|
|
74
|
-
assert(agentComm.isAgentCommRegistered() === true, 'should be true after registration');
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
runTest('getAgentCommTools: returns 3 tools when registered', () => {
|
|
78
|
-
agentComm.registerAgentComm({ listAgents() { return []; }, isAgentAlive() { return { alive: false, status: null }; }, resolveAgentName() { return { error: 'not found' }; }, writeInboxMessage() {}, writeInterrupt() {}, readStatus() { return null; } }, 'test-agent');
|
|
79
|
-
const tools = agentComm.getAgentCommTools();
|
|
80
|
-
assert(tools.length === 3, 'should have 3 tools, got ' + tools.length);
|
|
81
|
-
assert(tools[0].function.name === 'agent_send', 'first tool should be agent_send');
|
|
82
|
-
assert(tools[1].function.name === 'agent_list', 'second tool should be agent_list');
|
|
83
|
-
assert(tools[2].function.name === 'agent_spawn', 'third tool should be agent_spawn');
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
runTest('getAgentCommTools: returns empty when not registered', () => {
|
|
87
|
-
agentComm.registerAgentComm(null, null);
|
|
88
|
-
const tools = agentComm.getAgentCommTools();
|
|
89
|
-
assert(tools.length === 0, 'should have 0 tools when not registered');
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
// ============================================================
|
|
93
|
-
// agent_send: validation
|
|
94
|
-
// ============================================================
|
|
95
|
-
|
|
96
|
-
resetTestDir();
|
|
97
|
-
agentComm.registerAgentComm(registry, 'sender-agent');
|
|
98
|
-
|
|
99
|
-
runTest('agent_send: rejects missing agent', () => {
|
|
100
|
-
const result = agentComm.executeAgentSend({ message: 'hello' }, 'test1');
|
|
101
|
-
assert(result.success === false, 'should fail');
|
|
102
|
-
assert(result.error.indexOf('agent') !== -1, 'error should mention agent');
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
runTest('agent_send: rejects missing message', () => {
|
|
106
|
-
const result = agentComm.executeAgentSend({ agent: 'target' }, 'test2');
|
|
107
|
-
assert(result.success === false, 'should fail');
|
|
108
|
-
assert(result.error.indexOf('message') !== -1, 'error should mention message');
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
runTest('agent_send: rejects sending to self (exact name)', () => {
|
|
112
|
-
if (!canMkdir) return;
|
|
113
|
-
resetTestDir();
|
|
114
|
-
registry.claimAgent('sender-agent'); registry.writeStatus('sender-agent', { state: 'working', pid: process.pid, iteration: 1, startedAt: Date.now() });
|
|
115
|
-
const result = agentComm.executeAgentSend({ agent: 'sender-agent', message: 'hello' }, 'test3');
|
|
116
|
-
assert(result.success === false, 'should fail');
|
|
117
|
-
assert(result.error.indexOf('yourself') !== -1, 'error should mention self-send');
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
runTest('agent_send: rejects sending to self via prefix', () => {
|
|
121
|
-
if (!canMkdir) return;
|
|
122
|
-
resetTestDir();
|
|
123
|
-
agentComm._rateLimitMap.clear();
|
|
124
|
-
registry.claimAgent('sender-agent'); registry.writeStatus('sender-agent', { state: 'working', pid: process.pid, iteration: 1, startedAt: Date.now() });
|
|
125
|
-
// Use prefix 'sender' which should resolve to 'sender-agent' (self)
|
|
126
|
-
const result = agentComm.executeAgentSend({ agent: 'sender', message: 'hello' }, 'test3b');
|
|
127
|
-
assert(result.success === false, 'should fail when prefix resolves to self');
|
|
128
|
-
assert(result.error.indexOf('yourself') !== -1, 'error should mention self-send');
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
runTest('agent_send: rejects invalid priority', () => {
|
|
132
|
-
if (!canMkdir) return;
|
|
133
|
-
resetTestDir();
|
|
134
|
-
registry.claimAgent('target-agent'); registry.writeStatus('target-agent', { state: 'working', pid: process.pid, iteration: 1, startedAt: Date.now() });
|
|
135
|
-
const result = agentComm.executeAgentSend({
|
|
136
|
-
agent: 'target-agent',
|
|
137
|
-
message: 'hello',
|
|
138
|
-
priority: 'critical',
|
|
139
|
-
}, 'test4');
|
|
140
|
-
assert(result.success === false, 'should fail');
|
|
141
|
-
assert(result.error.indexOf('priority') !== -1, 'error should mention priority');
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
runTest('agent_send: rejects nonexistent agent', () => {
|
|
145
|
-
if (!canMkdir) return;
|
|
146
|
-
resetTestDir();
|
|
147
|
-
const result = agentComm.executeAgentSend({
|
|
148
|
-
agent: 'nonexistent-agent-xyz',
|
|
149
|
-
message: 'hello',
|
|
150
|
-
}, 'test5');
|
|
151
|
-
assert(result.success === false, 'should fail');
|
|
152
|
-
assert(result.error.indexOf('not found') !== -1, 'error should mention not found');
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
// ============================================================
|
|
156
|
-
// agent_send: normal message
|
|
157
|
-
// ============================================================
|
|
158
|
-
|
|
159
|
-
runTest('agent_send: sends normal message to inbox', () => {
|
|
160
|
-
if (!canMkdir) return;
|
|
161
|
-
resetTestDir();
|
|
162
|
-
agentComm._rateLimitMap.clear();
|
|
163
|
-
registry.claimAgent('target-agent'); registry.writeStatus('target-agent', { state: 'working', pid: process.pid, iteration: 1, startedAt: Date.now() });
|
|
164
|
-
const result = agentComm.executeAgentSend({
|
|
165
|
-
agent: 'target-agent',
|
|
166
|
-
message: 'please review PR #42',
|
|
167
|
-
}, 'test6');
|
|
168
|
-
assert(result.success === true, 'should succeed: ' + (result.error || ''));
|
|
169
|
-
assert(result.message.indexOf('target-agent') !== -1, 'should mention target');
|
|
170
|
-
|
|
171
|
-
// Verify the message was written to inbox
|
|
172
|
-
assert(registry.inboxHasMessages('target-agent'), 'inbox should have messages');
|
|
173
|
-
const inbox = registry.drainInbox('target-agent');
|
|
174
|
-
assert(inbox.messages.length === 1, 'should have 1 message');
|
|
175
|
-
assert(inbox.messages[0].message === 'please review PR #42', 'message content should match');
|
|
176
|
-
assert(inbox.messages[0].from === 'sender-agent', 'from should be sender agent');
|
|
177
|
-
});
|
|
178
|
-
|
|
179
|
-
runTest('agent_send: sends high priority message', () => {
|
|
180
|
-
if (!canMkdir) return;
|
|
181
|
-
resetTestDir();
|
|
182
|
-
agentComm._rateLimitMap.clear();
|
|
183
|
-
registry.claimAgent('target-agent'); registry.writeStatus('target-agent', { state: 'working', pid: process.pid, iteration: 1, startedAt: Date.now() });
|
|
184
|
-
agentComm.executeAgentSend({
|
|
185
|
-
agent: 'target-agent',
|
|
186
|
-
message: 'urgent task',
|
|
187
|
-
priority: 'high',
|
|
188
|
-
}, 'test7');
|
|
189
|
-
const inbox = registry.drainInbox('target-agent');
|
|
190
|
-
assert(inbox.messages[0].priority === 'high', 'priority should be high');
|
|
191
|
-
});
|
|
192
|
-
|
|
193
|
-
// ============================================================
|
|
194
|
-
// agent_send: urgent interrupt
|
|
195
|
-
// ============================================================
|
|
196
|
-
|
|
197
|
-
runTest('agent_send: sends urgent interrupt', () => {
|
|
198
|
-
if (!canMkdir) return;
|
|
199
|
-
resetTestDir();
|
|
200
|
-
agentComm._rateLimitMap.clear();
|
|
201
|
-
registry.claimAgent('target-agent'); registry.writeStatus('target-agent', { state: 'working', pid: process.pid, iteration: 1, startedAt: Date.now() });
|
|
202
|
-
const result = agentComm.executeAgentSend({
|
|
203
|
-
agent: 'target-agent',
|
|
204
|
-
message: 'production is down!',
|
|
205
|
-
urgent: true,
|
|
206
|
-
}, 'test8');
|
|
207
|
-
assert(result.success === true, 'should succeed');
|
|
208
|
-
assert(result.message.indexOf('Urgent') !== -1 || result.message.indexOf('urgent') !== -1,
|
|
209
|
-
'should mention urgent');
|
|
210
|
-
|
|
211
|
-
// Verify interrupt was written
|
|
212
|
-
assert(registry.hasInterrupt('target-agent'), 'should have interrupt');
|
|
213
|
-
const interrupt = registry.readInterrupt('target-agent');
|
|
214
|
-
assert(interrupt.message === 'production is down!', 'interrupt message should match');
|
|
215
|
-
assert(interrupt.from === 'sender-agent', 'from should be sender agent');
|
|
216
|
-
});
|
|
217
|
-
|
|
218
|
-
// ============================================================
|
|
219
|
-
// agent_send: rate limiting
|
|
220
|
-
// ============================================================
|
|
221
|
-
|
|
222
|
-
runTest('agent_send: rate limits after 10 messages', () => {
|
|
223
|
-
if (!canMkdir) return;
|
|
224
|
-
resetTestDir();
|
|
225
|
-
agentComm._rateLimitMap.clear();
|
|
226
|
-
registry.claimAgent('target-agent'); registry.writeStatus('target-agent', { state: 'working', pid: process.pid, iteration: 1, startedAt: Date.now() });
|
|
227
|
-
|
|
228
|
-
// Send 10 messages (should all succeed)
|
|
229
|
-
for (let i = 0; i < 10; i++) {
|
|
230
|
-
const result = agentComm.executeAgentSend({
|
|
231
|
-
agent: 'target-agent',
|
|
232
|
-
message: 'msg ' + i,
|
|
233
|
-
}, 'rate-' + i);
|
|
234
|
-
assert(result.success === true, 'message ' + i + ' should succeed');
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
// 11th should be rate limited
|
|
238
|
-
const result = agentComm.executeAgentSend({
|
|
239
|
-
agent: 'target-agent',
|
|
240
|
-
message: 'msg 10',
|
|
241
|
-
}, 'rate-10');
|
|
242
|
-
assert(result.success === false, 'should be rate limited');
|
|
243
|
-
assert(result.error.indexOf('Rate limited') !== -1, 'error should mention rate limit');
|
|
244
|
-
});
|
|
245
|
-
|
|
246
|
-
runTest('_checkRateLimit: resets after window expires', () => {
|
|
247
|
-
agentComm._rateLimitMap.clear();
|
|
248
|
-
// Manually set an expired window
|
|
249
|
-
agentComm._rateLimitMap.set('old-agent', {
|
|
250
|
-
count: 10,
|
|
251
|
-
windowStart: Date.now() - (agentComm.RATE_LIMIT_WINDOW_MS + 1000),
|
|
252
|
-
});
|
|
253
|
-
const check = agentComm._checkRateLimit('old-agent');
|
|
254
|
-
assert(check.allowed === true, 'should be allowed after window expires');
|
|
255
|
-
});
|
|
256
|
-
|
|
257
|
-
// ============================================================
|
|
258
|
-
// agent_list
|
|
259
|
-
// ============================================================
|
|
260
|
-
|
|
261
|
-
runTest('agent_list: returns empty array when no agents', () => {
|
|
262
|
-
if (!canMkdir) return;
|
|
263
|
-
resetTestDir();
|
|
264
|
-
const result = agentComm.executeAgentList({}, 'list1');
|
|
265
|
-
assert(result.success === true, 'should succeed');
|
|
266
|
-
assert(Array.isArray(result.agents), 'agents should be array');
|
|
267
|
-
assert(result.count === 0, 'should have 0 agents');
|
|
268
|
-
});
|
|
269
|
-
|
|
270
|
-
runTest('agent_list: returns registered agents with status', () => {
|
|
271
|
-
if (!canMkdir) return;
|
|
272
|
-
resetTestDir();
|
|
273
|
-
registry.claimAgent('agent-a');
|
|
274
|
-
registry.writeStatus('agent-a', {
|
|
275
|
-
state: 'working',
|
|
276
|
-
pid: process.pid,
|
|
277
|
-
iteration: 5,
|
|
278
|
-
startedAt: Date.now() - 60000,
|
|
279
|
-
});
|
|
280
|
-
registry.claimAgent('agent-b');
|
|
281
|
-
registry.writeStatus('agent-b', {
|
|
282
|
-
state: 'sleeping',
|
|
283
|
-
pid: process.pid,
|
|
284
|
-
iteration: 3,
|
|
285
|
-
startedAt: Date.now() - 30000,
|
|
286
|
-
});
|
|
287
|
-
|
|
288
|
-
const result = agentComm.executeAgentList({}, 'list2');
|
|
289
|
-
assert(result.success === true, 'should succeed');
|
|
290
|
-
assert(result.count >= 2, 'should have at least 2 agents');
|
|
291
|
-
|
|
292
|
-
// Find agent-a
|
|
293
|
-
const agentA = result.agents.find((a) => { return a.name === 'agent-a'; });
|
|
294
|
-
assert(agentA, 'should find agent-a');
|
|
295
|
-
assert(agentA.state === 'working', 'agent-a state should be working');
|
|
296
|
-
assert(agentA.iteration === 5, 'agent-a iteration should be 5');
|
|
297
|
-
assert(agentA.uptime !== null, 'agent-a should have uptime');
|
|
298
|
-
});
|
|
299
|
-
|
|
300
|
-
runTest('agent_list: marks self agent', () => {
|
|
301
|
-
if (!canMkdir) return;
|
|
302
|
-
resetTestDir();
|
|
303
|
-
registry.claimAgent('sender-agent'); registry.writeStatus('sender-agent', { state: 'working', pid: process.pid, iteration: 1, startedAt: Date.now() });
|
|
304
|
-
registry.writeStatus('sender-agent', {
|
|
305
|
-
state: 'working',
|
|
306
|
-
pid: process.pid,
|
|
307
|
-
iteration: 1,
|
|
308
|
-
startedAt: Date.now(),
|
|
309
|
-
});
|
|
310
|
-
|
|
311
|
-
const result = agentComm.executeAgentList({}, 'list3');
|
|
312
|
-
const self = result.agents.find((a) => { return a.name === 'sender-agent'; });
|
|
313
|
-
assert(self, 'should find sender-agent');
|
|
314
|
-
assert(self.isSelf === true, 'sender-agent should have isSelf=true');
|
|
315
|
-
assert(result.self === 'sender-agent', 'result.self should be sender-agent');
|
|
316
|
-
});
|
|
317
|
-
|
|
318
|
-
runTest('agent_list: fails when not registered', () => {
|
|
319
|
-
agentComm.registerAgentComm(null, null);
|
|
320
|
-
const result = agentComm.executeAgentList({}, 'list4');
|
|
321
|
-
assert(result.success === false, 'should fail');
|
|
322
|
-
assert(result.error.indexOf('not available') !== -1, 'error should mention not available');
|
|
323
|
-
// Re-register for remaining tests
|
|
324
|
-
agentComm.registerAgentComm(registry, 'sender-agent');
|
|
325
|
-
});
|
|
326
|
-
|
|
327
|
-
// ============================================================
|
|
328
|
-
// Tool registration in tools/index.js (source inspection)
|
|
329
|
-
// ============================================================
|
|
330
|
-
|
|
331
|
-
runTest('tools/index.js imports agent-comm module', () => {
|
|
332
|
-
const source = fs.readFileSync(path.join(__dirname, '..', '..', 'opencode-lite', 'src', 'tools', 'index.js'), 'utf8');
|
|
333
|
-
assert(source.indexOf("require(\"./agent-comm\")") !== -1 || source.indexOf("require('./agent-comm')") !== -1,
|
|
334
|
-
'should import agent-comm');
|
|
335
|
-
});
|
|
336
|
-
|
|
337
|
-
runTest('tools/index.js includes agent comm tools in getTools()', () => {
|
|
338
|
-
const source = fs.readFileSync(path.join(__dirname, '..', '..', 'opencode-lite', 'src', 'tools', 'index.js'), 'utf8');
|
|
339
|
-
assert(source.indexOf('getAgentCommTools') !== -1, 'should call getAgentCommTools in getTools()');
|
|
340
|
-
assert(source.indexOf('agentCommTools') !== -1, 'should spread agentCommTools into allTools');
|
|
341
|
-
});
|
|
342
|
-
|
|
343
|
-
runTest('tools/index.js dispatches agent_send in executeTool()', () => {
|
|
344
|
-
const source = fs.readFileSync(path.join(__dirname, '..', '..', 'opencode-lite', 'src', 'tools', 'index.js'), 'utf8');
|
|
345
|
-
assert(source.indexOf('"agent_send"') !== -1, 'should have agent_send case');
|
|
346
|
-
assert(source.indexOf('executeAgentSend') !== -1, 'should call executeAgentSend');
|
|
347
|
-
});
|
|
348
|
-
|
|
349
|
-
runTest('tools/index.js dispatches agent_list in executeTool()', () => {
|
|
350
|
-
const source = fs.readFileSync(path.join(__dirname, '..', '..', 'opencode-lite', 'src', 'tools', 'index.js'), 'utf8');
|
|
351
|
-
assert(source.indexOf('"agent_list"') !== -1, 'should have agent_list case');
|
|
352
|
-
assert(source.indexOf('executeAgentList') !== -1, 'should call executeAgentList');
|
|
353
|
-
});
|
|
354
|
-
|
|
355
|
-
runTest('tools/index.js exports registerAgentComm', () => {
|
|
356
|
-
const source = fs.readFileSync(path.join(__dirname, '..', '..', 'opencode-lite', 'src', 'tools', 'index.js'), 'utf8');
|
|
357
|
-
assert(source.indexOf('registerAgentComm') !== -1, 'should export registerAgentComm');
|
|
358
|
-
});
|
|
359
|
-
|
|
360
|
-
// ============================================================
|
|
361
|
-
// Agent loop registration (source inspection)
|
|
362
|
-
// ============================================================
|
|
363
|
-
|
|
364
|
-
runTest('pave/index.js registers agent comm tools after server start', () => {
|
|
365
|
-
const source = fs.readFileSync(path.join(__dirname, '..', 'index.js'), 'utf8');
|
|
366
|
-
assert(source.indexOf('registerAgentComm') !== -1, 'should call registerAgentComm');
|
|
367
|
-
assert(source.indexOf('registerAgentComm(registry, agentName)') !== -1,
|
|
368
|
-
'should pass registry and agentName');
|
|
369
|
-
});
|
|
370
|
-
|
|
371
|
-
runTest('pave/index.js surfaces registration failure as warning', () => {
|
|
372
|
-
const source = fs.readFileSync(path.join(__dirname, '..', 'index.js'), 'utf8');
|
|
373
|
-
assert(source.indexOf('agent_send/agent_list will be unavailable') !== -1,
|
|
374
|
-
'should warn about unavailable tools on failure');
|
|
375
|
-
});
|
|
376
|
-
|
|
377
|
-
// ============================================================
|
|
378
|
-
// agent-comm.js tool definitions
|
|
379
|
-
// ============================================================
|
|
380
|
-
|
|
381
|
-
runTest('agent_send tool has correct parameters', () => {
|
|
382
|
-
agentComm.registerAgentComm(registry, 'test');
|
|
383
|
-
const tools = agentComm.getAgentCommTools();
|
|
384
|
-
const sendTool = tools.find((t) => { return t.function.name === 'agent_send'; });
|
|
385
|
-
assert(sendTool, 'should find agent_send');
|
|
386
|
-
const params = sendTool.function.parameters.properties;
|
|
387
|
-
assert(params.agent, 'should have agent param');
|
|
388
|
-
assert(params.message, 'should have message param');
|
|
389
|
-
assert(params.priority, 'should have priority param');
|
|
390
|
-
assert(params.urgent, 'should have urgent param');
|
|
391
|
-
const required = sendTool.function.parameters.required;
|
|
392
|
-
assert(required.indexOf('agent') !== -1, 'agent should be required');
|
|
393
|
-
assert(required.indexOf('message') !== -1, 'message should be required');
|
|
394
|
-
});
|
|
395
|
-
|
|
396
|
-
runTest('agent_list tool has no required parameters', () => {
|
|
397
|
-
const tools = agentComm.getAgentCommTools();
|
|
398
|
-
const listTool = tools.find((t) => { return t.function.name === 'agent_list'; });
|
|
399
|
-
assert(listTool, 'should find agent_list');
|
|
400
|
-
assert(!listTool.function.parameters.required || listTool.function.parameters.required.length === 0,
|
|
401
|
-
'should have no required params');
|
|
402
|
-
});
|
|
403
|
-
|
|
404
|
-
// ============================================================
|
|
405
|
-
// Fix: _senderAgentName normalization (Issue #240)
|
|
406
|
-
// ============================================================
|
|
407
|
-
|
|
408
|
-
runTest('registerAgentComm: normalizes sender name via sanitizeName', () => {
|
|
409
|
-
if (!canMkdir) return;
|
|
410
|
-
// Register with unsanitized name (e.g. "PAVE Agent" with spaces and uppercase)
|
|
411
|
-
agentComm.registerAgentComm(registry, 'PAVE Agent');
|
|
412
|
-
assert(agentComm.isAgentCommRegistered(), 'should be registered');
|
|
413
|
-
|
|
414
|
-
// The internal _senderAgentName should now be sanitized
|
|
415
|
-
// We can verify by checking self-send rejection
|
|
416
|
-
resetTestDir();
|
|
417
|
-
registry.claimAgent('pave-agent');
|
|
418
|
-
registry.writeStatus('pave-agent', { state: 'working', pid: process.pid, iteration: 1, startedAt: Date.now() });
|
|
419
|
-
|
|
420
|
-
const result = agentComm.executeAgentSend({ agent: 'pave-agent', message: 'test' }, 'norm1');
|
|
421
|
-
assert(result.success === false, 'should reject self-send with sanitized name');
|
|
422
|
-
assert(result.error.indexOf('yourself') !== -1, 'error should mention self-send');
|
|
423
|
-
|
|
424
|
-
// Restore
|
|
425
|
-
agentComm.registerAgentComm(registry, 'sender-agent');
|
|
426
|
-
});
|
|
427
|
-
|
|
428
|
-
runTest('agent_send: self-send works with raw name like "My Bot 123"', () => {
|
|
429
|
-
if (!canMkdir) return;
|
|
430
|
-
agentComm.registerAgentComm(registry, 'My Bot 123');
|
|
431
|
-
resetTestDir();
|
|
432
|
-
agentComm._rateLimitMap.clear();
|
|
433
|
-
|
|
434
|
-
// sanitizeName('My Bot 123') should produce 'my-bot-123'
|
|
435
|
-
registry.claimAgent('my-bot-123');
|
|
436
|
-
registry.writeStatus('my-bot-123', { state: 'working', pid: process.pid, iteration: 1, startedAt: Date.now() });
|
|
437
|
-
|
|
438
|
-
const result = agentComm.executeAgentSend({ agent: 'my-bot-123', message: 'hello' }, 'norm2');
|
|
439
|
-
assert(result.success === false, 'should reject self-send');
|
|
440
|
-
assert(result.error.indexOf('yourself') !== -1, 'error should mention self-send');
|
|
441
|
-
|
|
442
|
-
agentComm.registerAgentComm(registry, 'sender-agent');
|
|
443
|
-
});
|
|
444
|
-
|
|
445
|
-
runTest('agent_list: isSelf works with unsanitized registration name', () => {
|
|
446
|
-
if (!canMkdir) return;
|
|
447
|
-
agentComm.registerAgentComm(registry, 'PAVE Agent');
|
|
448
|
-
resetTestDir();
|
|
449
|
-
|
|
450
|
-
registry.claimAgent('pave-agent');
|
|
451
|
-
registry.writeStatus('pave-agent', { state: 'working', pid: process.pid, iteration: 1, startedAt: Date.now() });
|
|
452
|
-
|
|
453
|
-
const result = agentComm.executeAgentList({}, 'norm3');
|
|
454
|
-
const self = result.agents.find((a) => { return a.name === 'pave-agent'; });
|
|
455
|
-
assert(self, 'should find pave-agent');
|
|
456
|
-
assert(self.isSelf === true, 'pave-agent should have isSelf=true when registered as "PAVE Agent"');
|
|
457
|
-
assert(result.self === 'pave-agent', 'result.self should be sanitized name');
|
|
458
|
-
|
|
459
|
-
agentComm.registerAgentComm(registry, 'sender-agent');
|
|
460
|
-
});
|
|
461
|
-
|
|
462
|
-
runTest('agent_send: from field uses sanitized name', () => {
|
|
463
|
-
if (!canMkdir) return;
|
|
464
|
-
agentComm.registerAgentComm(registry, 'My Agent');
|
|
465
|
-
resetTestDir();
|
|
466
|
-
agentComm._rateLimitMap.clear();
|
|
467
|
-
|
|
468
|
-
registry.claimAgent('target-bot');
|
|
469
|
-
registry.writeStatus('target-bot', { state: 'working', pid: process.pid, iteration: 1, startedAt: Date.now() });
|
|
470
|
-
|
|
471
|
-
const result = agentComm.executeAgentSend({ agent: 'target-bot', message: 'hello' }, 'norm4');
|
|
472
|
-
assert(result.success === true, 'should succeed sending to other agent');
|
|
473
|
-
|
|
474
|
-
// Check the inbox message has sanitized from field
|
|
475
|
-
const inbox = registry.drainInbox('target-bot');
|
|
476
|
-
assert(inbox.messages.length > 0, 'should have inbox message');
|
|
477
|
-
assert(inbox.messages[0].from === 'my-agent', 'from field should be sanitized: ' + inbox.messages[0].from);
|
|
478
|
-
|
|
479
|
-
agentComm.registerAgentComm(registry, 'sender-agent');
|
|
480
|
-
});
|
|
481
|
-
|
|
482
|
-
// ============================================================
|
|
483
|
-
// Issue #246: agent_list .message and handler.js fallback
|
|
484
|
-
// ============================================================
|
|
485
|
-
|
|
486
|
-
runTest('agent_list: returns .message with human-readable summary', () => {
|
|
487
|
-
if (!canMkdir) return;
|
|
488
|
-
resetTestDir();
|
|
489
|
-
agentComm.registerAgentComm(registry, 'sender-agent');
|
|
490
|
-
registry.claimAgent('agent-a');
|
|
491
|
-
registry.writeStatus('agent-a', { state: 'working', pid: process.pid, iteration: 5, startedAt: Date.now() - 60000 });
|
|
492
|
-
registry.claimAgent('agent-b');
|
|
493
|
-
registry.writeStatus('agent-b', { state: 'sleeping', pid: process.pid, iteration: 3, startedAt: Date.now() - 30000 });
|
|
494
|
-
|
|
495
|
-
const result = agentComm.executeAgentList({}, 'list-msg1');
|
|
496
|
-
assert(result.message, 'should have .message property');
|
|
497
|
-
assert(typeof result.message === 'string', '.message should be a string');
|
|
498
|
-
assert(result.message.indexOf('agent(s) found') !== -1, 'message should contain "agent(s) found"');
|
|
499
|
-
assert(result.message.indexOf('agent-a') !== -1, 'message should mention agent-a');
|
|
500
|
-
assert(result.message.indexOf('agent-b') !== -1, 'message should mention agent-b');
|
|
501
|
-
assert(result.message.indexOf('working') !== -1, 'message should include state');
|
|
502
|
-
assert(result.message.indexOf('iter=') !== -1, 'message should include iteration');
|
|
503
|
-
assert(result.message.indexOf('uptime=') !== -1, 'message should include uptime');
|
|
504
|
-
});
|
|
505
|
-
|
|
506
|
-
runTest('agent_list: .message with 0 agents', () => {
|
|
507
|
-
if (!canMkdir) return;
|
|
508
|
-
resetTestDir();
|
|
509
|
-
agentComm.registerAgentComm(registry, 'sender-agent');
|
|
510
|
-
const result = agentComm.executeAgentList({}, 'list-msg2');
|
|
511
|
-
assert(result.message, 'should have .message even with 0 agents');
|
|
512
|
-
assert(result.message.indexOf('0 agent(s) found') !== -1, 'message should say 0 agents');
|
|
513
|
-
});
|
|
514
|
-
|
|
515
|
-
runTest('agent_list: .message marks self agent with [you]', () => {
|
|
516
|
-
if (!canMkdir) return;
|
|
517
|
-
resetTestDir();
|
|
518
|
-
agentComm.registerAgentComm(registry, 'sender-agent');
|
|
519
|
-
registry.claimAgent('sender-agent');
|
|
520
|
-
registry.writeStatus('sender-agent', { state: 'working', pid: process.pid, iteration: 1, startedAt: Date.now() });
|
|
521
|
-
|
|
522
|
-
const result = agentComm.executeAgentList({}, 'list-msg3');
|
|
523
|
-
assert(result.message.indexOf('[you]') !== -1, 'message should mark self with [you]');
|
|
524
|
-
});
|
|
525
|
-
|
|
526
|
-
runTest('agent_list: .message shows alive/dead status', () => {
|
|
527
|
-
if (!canMkdir) return;
|
|
528
|
-
resetTestDir();
|
|
529
|
-
agentComm.registerAgentComm(registry, 'sender-agent');
|
|
530
|
-
registry.claimAgent('agent-x');
|
|
531
|
-
registry.writeStatus('agent-x', { state: 'working', pid: process.pid, iteration: 1, startedAt: Date.now() });
|
|
532
|
-
|
|
533
|
-
const result = agentComm.executeAgentList({}, 'list-msg4');
|
|
534
|
-
const agentX = result.agents.find((a) => { return a.name === 'agent-x'; });
|
|
535
|
-
if (agentX && agentX.alive) {
|
|
536
|
-
assert(result.message.indexOf('alive') !== -1, 'message should show alive when PID is running');
|
|
537
|
-
} else {
|
|
538
|
-
assert(result.message.indexOf('dead') !== -1, 'message should show dead when PID is not running');
|
|
539
|
-
}
|
|
540
|
-
});
|
|
541
|
-
|
|
542
|
-
runTest('agent_send urgent: message mentions inbox bypass', () => {
|
|
543
|
-
if (!canMkdir) return;
|
|
544
|
-
resetTestDir();
|
|
545
|
-
agentComm.registerAgentComm(registry, 'sender-agent');
|
|
546
|
-
agentComm._rateLimitMap.clear();
|
|
547
|
-
registry.claimAgent('target-agent');
|
|
548
|
-
registry.writeStatus('target-agent', { state: 'working', pid: process.pid, iteration: 1, startedAt: Date.now() });
|
|
549
|
-
|
|
550
|
-
const result = agentComm.executeAgentSend({
|
|
551
|
-
agent: 'target-agent',
|
|
552
|
-
message: 'urgent task!',
|
|
553
|
-
urgent: true,
|
|
554
|
-
}, 'urgent-msg1');
|
|
555
|
-
assert(result.success === true, 'should succeed');
|
|
556
|
-
assert(result.message.indexOf('bypass') !== -1 || result.message.indexOf('Bypass') !== -1,
|
|
557
|
-
'urgent success message should mention bypassing inbox');
|
|
558
|
-
});
|
|
559
|
-
|
|
560
|
-
runTest('handler.js: generic JSON.stringify fallback before "(no output)"', () => {
|
|
561
|
-
const handlerSource = fs.readFileSync(
|
|
562
|
-
path.join(__dirname, '..', '..', 'opencode-lite', 'src', 'prompt', 'handler.js'), 'utf8');
|
|
563
|
-
// Search whole file with regex for resilience against formatting changes
|
|
564
|
-
const stringifyPattern = /JSON\.stringify\s*\(\s*toolResult\s*\)/;
|
|
565
|
-
assert(stringifyPattern.test(handlerSource), 'should have JSON.stringify(toolResult) fallback');
|
|
566
|
-
assert(handlerSource.indexOf('"(no output)"') !== -1, 'should still have (no output) as last resort');
|
|
567
|
-
// Verify ordering: stringify comes before "(no output)"
|
|
568
|
-
const stringifyMatch = handlerSource.match(stringifyPattern);
|
|
569
|
-
const noOutputIdx = handlerSource.indexOf('"(no output)"');
|
|
570
|
-
assert(stringifyMatch.index < noOutputIdx, 'JSON.stringify fallback should come before (no output)');
|
|
571
|
-
});
|
|
572
|
-
|
|
573
|
-
runTest('handler.js: fallback guards with typeof and try/catch', () => {
|
|
574
|
-
const handlerSource = fs.readFileSync(
|
|
575
|
-
path.join(__dirname, '..', '..', 'opencode-lite', 'src', 'prompt', 'handler.js'), 'utf8');
|
|
576
|
-
// Use regex to tolerate whitespace/formatting variations
|
|
577
|
-
const typeofObjectGuard = /typeof\s+toolResult\s*===\s*['"]object['"]/;
|
|
578
|
-
assert(typeofObjectGuard.test(handlerSource), 'should guard JSON.stringify with typeof object check');
|
|
579
|
-
// Verify try/catch wraps the stringify to handle circular refs/BigInt
|
|
580
|
-
const tryCatchPattern = /try\s*\{[^}]*JSON\.stringify\s*\(\s*toolResult\s*\)[^}]*\}\s*catch/;
|
|
581
|
-
assert(tryCatchPattern.test(handlerSource), 'should wrap JSON.stringify in try/catch for safety');
|
|
582
|
-
// Catch branch should use lightweight valid JSON (keys only), not String(toolResult)
|
|
583
|
-
assert(handlerSource.indexOf('String(toolResult)') === -1 ||
|
|
584
|
-
handlerSource.indexOf('String(toolResult)') > handlerSource.indexOf('"(no output)"'),
|
|
585
|
-
'catch branch should not use String(toolResult) before (no output)');
|
|
586
|
-
// Should produce valid JSON in catch branch via JSON.stringify({keys: keys})
|
|
587
|
-
assert(/JSON\.stringify\s*\(\s*\{\s*keys[\s:,}]/.test(handlerSource),
|
|
588
|
-
'catch branch should emit valid JSON with top-level keys');
|
|
589
|
-
});
|
|
590
|
-
|
|
591
|
-
// ============================================================
|
|
592
|
-
// Cleanup & Summary
|
|
593
|
-
// ============================================================
|
|
594
|
-
|
|
595
|
-
cleanup();
|
|
596
|
-
|
|
597
|
-
console.log('\nTotal: ' + (passed + failed) + ', Passed: ' + passed + ', Failed: ' + failed);
|
|
598
|
-
if (failed > 0) process.exitCode = 1;
|