@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.
Files changed (83) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +21 -218
  3. package/package.json +32 -35
  4. package/pave.js +3 -0
  5. package/sandbox/SandboxRunner.js +1 -0
  6. package/sandbox/pave-run.js +2 -0
  7. package/sandbox/permission.js +1 -0
  8. package/sandbox/utils/yaml.js +1 -0
  9. package/MARKETPLACE.md +0 -406
  10. package/build-binary.js +0 -591
  11. package/build-npm.js +0 -537
  12. package/build.js +0 -230
  13. package/check-binary.js +0 -26
  14. package/deploy.sh +0 -95
  15. package/index.js +0 -5776
  16. package/lib/agent-registry.js +0 -1037
  17. package/lib/args-parser.js +0 -837
  18. package/lib/blessed-widget-patched.js +0 -93
  19. package/lib/cli-markdown.js +0 -590
  20. package/lib/compaction.js +0 -153
  21. package/lib/duration.js +0 -94
  22. package/lib/hash.js +0 -22
  23. package/lib/marketplace.js +0 -866
  24. package/lib/memory-config.js +0 -166
  25. package/lib/skill-manager.js +0 -891
  26. package/lib/soul.js +0 -31
  27. package/lib/tool-output-formatter.js +0 -180
  28. package/start-pave.sh +0 -149
  29. package/status.js +0 -271
  30. package/test/abort-stream.test.js +0 -445
  31. package/test/agent-auto-compaction.test.js +0 -552
  32. package/test/agent-comm-abort.test.js +0 -95
  33. package/test/agent-comm.test.js +0 -598
  34. package/test/agent-inbox.test.js +0 -576
  35. package/test/agent-init.test.js +0 -264
  36. package/test/agent-interrupt.test.js +0 -314
  37. package/test/agent-lifecycle.test.js +0 -520
  38. package/test/agent-log-files.test.js +0 -349
  39. package/test/agent-mode.manual-test.js +0 -392
  40. package/test/agent-parsing.test.js +0 -228
  41. package/test/agent-post-stream-idle.test.js +0 -762
  42. package/test/agent-registry.test.js +0 -359
  43. package/test/agent-rm.test.js +0 -442
  44. package/test/agent-spawn.test.js +0 -933
  45. package/test/agent-status-api.test.js +0 -624
  46. package/test/agent-update.test.js +0 -435
  47. package/test/args-parser.test.js +0 -391
  48. package/test/auto-compaction-chat.manual-test.js +0 -227
  49. package/test/auto-compaction.test.js +0 -941
  50. package/test/build-config.test.js +0 -120
  51. package/test/build-npm.test.js +0 -388
  52. package/test/chat-command.test.js +0 -137
  53. package/test/chat-leading-lines.test.js +0 -159
  54. package/test/config-flag.test.js +0 -272
  55. package/test/cursor-drift.test.js +0 -135
  56. package/test/debug-require.js +0 -23
  57. package/test/dir-migration.test.js +0 -323
  58. package/test/duration.test.js +0 -229
  59. package/test/ghostty-term.test.js +0 -202
  60. package/test/http500-backoff.test.js +0 -854
  61. package/test/integration.test.js +0 -86
  62. package/test/memory-guard-env.test.js +0 -220
  63. package/test/pr233-fixes.test.js +0 -259
  64. package/test/run-agent-init.js +0 -297
  65. package/test/run-all.js +0 -64
  66. package/test/run-config-flag.js +0 -159
  67. package/test/run-cursor-drift.js +0 -82
  68. package/test/run-session-path.js +0 -154
  69. package/test/run-tests.js +0 -643
  70. package/test/sandbox-redirect.test.js +0 -202
  71. package/test/session-path.test.js +0 -132
  72. package/test/shebang-strip.test.js +0 -241
  73. package/test/soul-reinject.test.js +0 -1027
  74. package/test/soul-reread.test.js +0 -281
  75. package/test/tool-output-formatter.test.js +0 -486
  76. package/test/tool-output-gating.test.js +0 -143
  77. package/test/tool-states.test.js +0 -167
  78. package/test/tools-flag.test.js +0 -65
  79. package/test/tui-attach.test.js +0 -1255
  80. package/test/tui-compaction.test.js +0 -354
  81. package/test/tui-wrap.test.js +0 -568
  82. package/test-binary.js +0 -52
  83. package/test-binary2.js +0 -36
@@ -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;