@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,486 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * Tool Output Formatter Test Suite
4
- * Tests for formatToolOutput, extractToolCommand, and shell box formatting
5
- */
6
-
7
- const assert = require('assert');
8
- const { formatToolOutput, extractToolCommand, TOOL_COLORS, BOX } = require('../lib/tool-output-formatter');
9
-
10
- class TestRunner {
11
- constructor() {
12
- this.tests = [];
13
- this.passed = 0;
14
- this.failed = 0;
15
- }
16
-
17
- test(name, fn) {
18
- this.tests.push({ name, fn });
19
- }
20
-
21
- async run() {
22
- console.log('Testing tool-output-formatter.js\n');
23
-
24
- for (const { name, fn } of this.tests) {
25
- try {
26
- await fn();
27
- console.log(` [PASS] ${name}`);
28
- this.passed++;
29
- } catch (error) {
30
- console.log(` [FAIL] ${name}`);
31
- console.log(` ${error.message}`);
32
- this.failed++;
33
- }
34
- }
35
-
36
- console.log(`\nResults: ${this.passed} passed, ${this.failed} failed`);
37
- return this.failed === 0;
38
- }
39
- }
40
-
41
- const runner = new TestRunner();
42
-
43
- // ========================================
44
- // TOOL_COLORS tests
45
- // ========================================
46
-
47
- runner.test('TOOL_COLORS: has all required color codes', () => {
48
- assert.ok(TOOL_COLORS.reset, 'reset color should exist');
49
- assert.ok(TOOL_COLORS.bold, 'bold color should exist');
50
- assert.ok(TOOL_COLORS.dim, 'dim color should exist');
51
- assert.ok(TOOL_COLORS.red, 'red color should exist');
52
- assert.ok(TOOL_COLORS.green, 'green color should exist');
53
- assert.ok(TOOL_COLORS.yellow, 'yellow color should exist');
54
- assert.ok(TOOL_COLORS.cyan, 'cyan color should exist');
55
- assert.ok(TOOL_COLORS.gray, 'gray color should exist');
56
- });
57
-
58
- runner.test('TOOL_COLORS: all values are ANSI escape codes', () => {
59
- for (const [name, value] of Object.entries(TOOL_COLORS)) {
60
- assert.ok(value.startsWith('\x1b['), `${name} should be an ANSI escape code`);
61
- }
62
- });
63
-
64
- // ========================================
65
- // BOX characters tests
66
- // ========================================
67
-
68
- runner.test('BOX: has all required box drawing characters', () => {
69
- assert.ok(BOX.topLeft, 'topLeft should exist');
70
- assert.ok(BOX.topRight, 'topRight should exist');
71
- assert.ok(BOX.bottomLeft, 'bottomLeft should exist');
72
- assert.ok(BOX.bottomRight, 'bottomRight should exist');
73
- assert.ok(BOX.horizontal, 'horizontal should exist');
74
- assert.ok(BOX.vertical, 'vertical should exist');
75
- });
76
-
77
- runner.test('BOX: uses correct Unicode box drawing characters', () => {
78
- assert.strictEqual(BOX.topLeft, '╭', 'topLeft should be ╭');
79
- assert.strictEqual(BOX.topRight, '╮', 'topRight should be ╮');
80
- assert.strictEqual(BOX.bottomLeft, '╰', 'bottomLeft should be ╰');
81
- assert.strictEqual(BOX.bottomRight, '╯', 'bottomRight should be ╯');
82
- assert.strictEqual(BOX.horizontal, '─', 'horizontal should be ─');
83
- assert.strictEqual(BOX.vertical, '│', 'vertical should be │');
84
- });
85
-
86
- // ========================================
87
- // extractToolCommand tests
88
- // ========================================
89
-
90
- runner.test('extractToolCommand: returns tool name when no input', () => {
91
- assert.strictEqual(extractToolCommand('bash', null), 'bash');
92
- assert.strictEqual(extractToolCommand('read', undefined), 'read');
93
- });
94
-
95
- runner.test('extractToolCommand: extracts bash command from object', () => {
96
- const input = { command: 'ls -la' };
97
- assert.strictEqual(extractToolCommand('bash', input), 'ls -la');
98
- });
99
-
100
- runner.test('extractToolCommand: extracts bash command from JSON string', () => {
101
- const input = JSON.stringify({ command: 'git status' });
102
- assert.strictEqual(extractToolCommand('bash', input), 'git status');
103
- });
104
-
105
- runner.test('extractToolCommand: formats read tool', () => {
106
- const input = { filePath: '/path/to/file.js' };
107
- assert.strictEqual(extractToolCommand('read', input), 'read /path/to/file.js');
108
- });
109
-
110
- runner.test('extractToolCommand: formats grep tool with pattern and path', () => {
111
- const input = { pattern: 'TODO', path: 'src/' };
112
- assert.strictEqual(extractToolCommand('grep', input), 'grep "TODO" src/');
113
- });
114
-
115
- runner.test('extractToolCommand: formats grep tool with pattern and include', () => {
116
- const input = { pattern: 'function', include: '*.js' };
117
- assert.strictEqual(extractToolCommand('grep', input), 'grep "function" *.js');
118
- });
119
-
120
- runner.test('extractToolCommand: formats glob tool', () => {
121
- const input = { pattern: '**/*.ts' };
122
- assert.strictEqual(extractToolCommand('glob', input), 'glob "**/*.ts"');
123
- });
124
-
125
- runner.test('extractToolCommand: formats edit tool', () => {
126
- const input = { filePath: '/path/to/edit.js' };
127
- assert.strictEqual(extractToolCommand('edit', input), 'edit /path/to/edit.js');
128
- });
129
-
130
- runner.test('extractToolCommand: formats write tool', () => {
131
- const input = { filePath: '/path/to/new-file.js' };
132
- assert.strictEqual(extractToolCommand('write', input), 'write /path/to/new-file.js');
133
- });
134
-
135
- runner.test('extractToolCommand: formats task tool', () => {
136
- const input = { description: 'Find all React components' };
137
- assert.strictEqual(extractToolCommand('task', input), 'task "Find all React components"');
138
- });
139
-
140
- runner.test('extractToolCommand: formats webfetch tool', () => {
141
- const input = { url: 'https://example.com/api' };
142
- assert.strictEqual(extractToolCommand('webfetch', input), 'fetch https://example.com/api');
143
- });
144
-
145
- runner.test('extractToolCommand: returns tool name for invalid JSON', () => {
146
- assert.strictEqual(extractToolCommand('bash', 'not valid json'), 'bash');
147
- });
148
-
149
- runner.test('extractToolCommand: returns tool name for unknown tool', () => {
150
- const input = { someField: 'someValue' };
151
- assert.strictEqual(extractToolCommand('unknown', input), 'unknown');
152
- });
153
-
154
- // ========================================
155
- // formatToolOutput basic tests
156
- // ========================================
157
-
158
- runner.test('formatToolOutput: returns string for basic invocation', () => {
159
- const result = formatToolOutput('bash', 'completed', null, 'Hello', 100);
160
- assert.strictEqual(typeof result, 'string', 'should return a string');
161
- assert.ok(result.length > 0, 'should not be empty');
162
- });
163
-
164
- runner.test('formatToolOutput: includes tool name in header', () => {
165
- const result = formatToolOutput('read', 'completed', null, 'file content', 50);
166
- assert.ok(result.includes('read'), 'should include tool name');
167
- });
168
-
169
- runner.test('formatToolOutput: includes duration when provided', () => {
170
- const result = formatToolOutput('bash', 'completed', null, 'output', 123);
171
- assert.ok(result.includes('123ms'), 'should include duration');
172
- });
173
-
174
- runner.test('formatToolOutput: handles missing duration', () => {
175
- const result = formatToolOutput('bash', 'completed', null, 'output');
176
- assert.strictEqual(typeof result, 'string', 'should return a string');
177
- assert.ok(!result.includes('undefinedms'), 'should not include undefined duration');
178
- assert.ok(!result.includes('ms)'), 'should not include ms when no duration');
179
- });
180
-
181
- // ========================================
182
- // Shell box structure tests
183
- // ========================================
184
-
185
- runner.test('formatToolOutput: uses box drawing characters', () => {
186
- const result = formatToolOutput('bash', 'completed', null, 'output', 100);
187
- assert.ok(result.includes('╭'), 'should include top-left corner');
188
- assert.ok(result.includes('╮'), 'should include top-right corner');
189
- assert.ok(result.includes('╰'), 'should include bottom-left corner');
190
- assert.ok(result.includes('╯'), 'should include bottom-right corner');
191
- // Note: left border vertical lines removed per issue #47
192
- assert.ok(result.includes('─'), 'should include horizontal line');
193
- });
194
-
195
- runner.test('formatToolOutput: command line directly follows top border (no empty line gap)', () => {
196
- const input = { command: 'echo hello' };
197
- const result = formatToolOutput('bash', 'completed', input, 'output', 100);
198
- const lines = result.split('\n');
199
- // Find the top border line (starts with colored ╭)
200
- const topBorderIndex = lines.findIndex((l) => l.includes('╭') && l.includes('╮'));
201
- assert.ok(topBorderIndex >= 0, 'should have top border line');
202
- // The line immediately following the top border should be the command line, not empty
203
- // Strip ANSI codes for a clean assertion
204
- const lineAfterTopBorder = lines[topBorderIndex + 1].replace(/\x1b\[[0-9;]*m/g, '').trim();
205
- assert.ok(lineAfterTopBorder.startsWith('$'), 'line after top border should start with $ prompt, got: "' + lineAfterTopBorder + '"');
206
- });
207
-
208
- runner.test('formatToolOutput: trims trailing empty lines from output', () => {
209
- const input = { command: 'echo hello' };
210
- // Output ending with multiple newlines should not produce blank lines before bottom border
211
- const result = formatToolOutput('bash', 'completed', input, 'hello\n\n\n', 100);
212
- const lines = result.split('\n');
213
- // Find the bottom border line
214
- const bottomBorderIndex = lines.findIndex((l) => l.includes('╰') && l.includes('╯'));
215
- assert.ok(bottomBorderIndex >= 0, 'should have bottom border line');
216
- // The line immediately before the bottom border should NOT be empty
217
- const lineBeforeBottom = lines[bottomBorderIndex - 1].replace(/\x1b\[[0-9;]*m/g, '').trim();
218
- assert.ok(lineBeforeBottom !== '', 'line before bottom border should not be empty (trailing lines trimmed)');
219
- });
220
-
221
- runner.test('formatToolOutput: trims trailing empty lines in error output', () => {
222
- const input = { command: 'fail' };
223
- // Error output with trailing newlines should also trim (ANSI color codes should not prevent trimming)
224
- const result = formatToolOutput('bash', 'error', input, 'error occurred\n\n', 100);
225
- const lines = result.split('\n');
226
- const bottomBorderIndex = lines.findIndex((l) => l.includes('╰') && l.includes('╯'));
227
- assert.ok(bottomBorderIndex >= 0, 'should have bottom border line');
228
- const lineBeforeBottom = lines[bottomBorderIndex - 1].replace(/\x1b\[[0-9;]*m/g, '').trim();
229
- assert.ok(lineBeforeBottom !== '', 'line before bottom border in error output should not be empty');
230
- });
231
-
232
- runner.test('formatToolOutput: shows $ prompt for command', () => {
233
- const input = { command: 'ls -la' };
234
- const result = formatToolOutput('bash', 'completed', input, 'output', 100);
235
- // $ is wrapped in color codes, so check for $ and command separately
236
- assert.ok(result.includes('$'), 'should include $ prompt');
237
- assert.ok(result.includes('ls -la'), 'should include command');
238
- });
239
-
240
- runner.test('formatToolOutput: shows extracted command for non-bash tools', () => {
241
- const input = { filePath: '/path/to/file.js' };
242
- const result = formatToolOutput('read', 'completed', input, 'file contents', 50);
243
- // $ is wrapped in color codes, so check for $ and command separately
244
- assert.ok(result.includes('$'), 'should include $ prompt');
245
- assert.ok(result.includes('read /path/to/file.js'), 'should include formatted read command');
246
- });
247
-
248
- runner.test('formatToolOutput: includes output lines without vertical bar prefix', () => {
249
- const result = formatToolOutput('bash', 'completed', null, 'line1\nline2', 100);
250
- // Lines should NOT be prefixed with │ (per issue #47 - remove left border)
251
- const lines = result.split('\n');
252
- const outputLines = lines.filter((l) => l.includes('line1') || l.includes('line2'));
253
- assert.strictEqual(outputLines.length, 2, 'should have 2 output lines');
254
- // Verify that each output line doesn't start with vertical bar prefix
255
- outputLines.forEach((line) => {
256
- assert.ok(!line.startsWith('│'), `output line "${line}" should not have vertical bar prefix`);
257
- });
258
- });
259
-
260
- // ========================================
261
- // Status icon tests
262
- // ========================================
263
-
264
- runner.test('formatToolOutput: shows checkmark for completed status', () => {
265
- const result = formatToolOutput('bash', 'completed', null, 'output', 100);
266
- assert.ok(result.includes('✓'), 'should include checkmark');
267
- });
268
-
269
- runner.test('formatToolOutput: shows X for error status', () => {
270
- const result = formatToolOutput('bash', 'error', null, 'error output', 100);
271
- assert.ok(result.includes('✗'), 'should include X mark');
272
- });
273
-
274
- runner.test('formatToolOutput: shows spinner for running status', () => {
275
- const result = formatToolOutput('bash', 'running', null, '', 0);
276
- assert.ok(result.includes('⟳'), 'should include spinner');
277
- });
278
-
279
- runner.test('formatToolOutput: shows cursor for running status', () => {
280
- const result = formatToolOutput('bash', 'running', { command: 'ls' }, '', 0);
281
- assert.ok(result.includes('█'), 'should include block cursor for running state');
282
- });
283
-
284
- runner.test('formatToolOutput: no cursor for completed status', () => {
285
- const result = formatToolOutput('bash', 'completed', { command: 'ls' }, 'output', 100);
286
- assert.ok(!result.includes('█'), 'should not include block cursor for completed state');
287
- });
288
-
289
- // ========================================
290
- // Output handling tests (NO TRUNCATION)
291
- // ========================================
292
-
293
- runner.test('formatToolOutput: does not truncate long output', () => {
294
- const longOutput = 'x'.repeat(10000);
295
- const result = formatToolOutput('bash', 'completed', null, longOutput, 100);
296
- assert.ok(result.includes(longOutput), 'should include full long output without truncation');
297
- });
298
-
299
- runner.test('formatToolOutput: includes full output for very long strings', () => {
300
- const veryLongOutput = 'line\n'.repeat(500);
301
- const result = formatToolOutput('bash', 'completed', null, veryLongOutput, 100);
302
- const matches = result.match(/line/g) || [];
303
- assert.strictEqual(matches.length, 500, 'should include all 500 lines');
304
- });
305
-
306
- // ========================================
307
- // Error handling tests
308
- // ========================================
309
-
310
- runner.test('formatToolOutput: error output is colored red', () => {
311
- const result = formatToolOutput('bash', 'error', null, 'Something went wrong', 100);
312
- assert.ok(result.includes(TOOL_COLORS.red), 'should include red color for error output');
313
- });
314
-
315
- runner.test('formatToolOutput: error status uses red header color', () => {
316
- const result = formatToolOutput('bash', 'error', null, 'error', 100);
317
- // The X mark should be red
318
- assert.ok(result.includes(TOOL_COLORS.red + '✗'), 'should have red X mark');
319
- });
320
-
321
- // ========================================
322
- // noColor option tests
323
- // ========================================
324
-
325
- runner.test('formatToolOutput: includes colors by default', () => {
326
- const result = formatToolOutput('bash', 'completed', null, 'output', 100);
327
- assert.ok(result.includes('\x1b['), 'should include ANSI escape codes by default');
328
- });
329
-
330
- runner.test('formatToolOutput: removes colors when noColor is true', () => {
331
- const result = formatToolOutput('bash', 'completed', null, 'output', 100, { noColor: true });
332
- assert.ok(!result.includes('\x1b['), 'should not include ANSI escape codes when noColor is true');
333
- });
334
-
335
- runner.test('formatToolOutput: still shows checkmark without colors', () => {
336
- const result = formatToolOutput('bash', 'completed', null, 'output', 100, { noColor: true });
337
- assert.ok(result.includes('✓'), 'should include checkmark even without colors');
338
- });
339
-
340
- runner.test('formatToolOutput: still uses box characters without colors', () => {
341
- const result = formatToolOutput('bash', 'completed', null, 'output', 100, { noColor: true });
342
- assert.ok(result.includes('╭'), 'should include box characters even without colors');
343
- assert.ok(result.includes('╯'), 'should include box characters even without colors');
344
- });
345
-
346
- // ========================================
347
- // Width option tests
348
- // ========================================
349
-
350
- runner.test('formatToolOutput: respects custom width option', () => {
351
- const result = formatToolOutput('bash', 'completed', null, 'output', 100, { width: 40 });
352
- // The bottom border should be shorter
353
- const bottomBorderMatch = result.match(/╰─+╯/);
354
- assert.ok(bottomBorderMatch, 'should have bottom border');
355
- // Width of 40 means 39 dashes in bottom border
356
- assert.ok(bottomBorderMatch[0].length < 50, 'should have shorter border with smaller width');
357
- });
358
-
359
- // ========================================
360
- // Edge cases
361
- // ========================================
362
-
363
- runner.test('formatToolOutput: handles empty output', () => {
364
- const result = formatToolOutput('bash', 'completed', null, '', 100);
365
- assert.strictEqual(typeof result, 'string', 'should return a string');
366
- // Should still have box structure
367
- assert.ok(result.includes('╭'), 'should include top border');
368
- assert.ok(result.includes('╰'), 'should include bottom border');
369
- });
370
-
371
- runner.test('formatToolOutput: handles null output', () => {
372
- const result = formatToolOutput('bash', 'completed', null, null, 100);
373
- assert.strictEqual(typeof result, 'string', 'should return a string');
374
- });
375
-
376
- runner.test('formatToolOutput: handles undefined output', () => {
377
- const result = formatToolOutput('bash', 'completed', null, undefined, 100);
378
- assert.strictEqual(typeof result, 'string', 'should return a string');
379
- });
380
-
381
- runner.test('formatToolOutput: handles multiline output', () => {
382
- const output = 'line1\nline2\nline3';
383
- const result = formatToolOutput('bash', 'completed', null, output, 100);
384
- assert.ok(result.includes('line1'), 'should include line1');
385
- assert.ok(result.includes('line2'), 'should include line2');
386
- assert.ok(result.includes('line3'), 'should include line3');
387
- });
388
-
389
- // ========================================
390
- // Color scheme tests (matching TUI)
391
- // ========================================
392
-
393
- runner.test('formatToolOutput: uses green color for $ prompt', () => {
394
- const input = { command: 'ls' };
395
- const result = formatToolOutput('bash', 'completed', input, 'output', 100);
396
- // $ prompt should be green
397
- assert.ok(result.includes(TOOL_COLORS.green + '$'), 'should use green color for $ prompt');
398
- });
399
-
400
- runner.test('formatToolOutput: uses gray color for box borders', () => {
401
- const result = formatToolOutput('bash', 'completed', null, 'output', 100);
402
- // Box borders should be gray
403
- assert.ok(result.includes(TOOL_COLORS.gray + '╭'), 'should use gray for top border');
404
- assert.ok(result.includes(TOOL_COLORS.gray + '╰'), 'should use gray for bottom border');
405
- });
406
-
407
- runner.test('formatToolOutput: uses cyan header color for completed status', () => {
408
- const result = formatToolOutput('bash', 'completed', null, 'output', 100);
409
- // Tool name should have cyan color for completed
410
- assert.ok(result.includes(TOOL_COLORS.cyan), 'should use cyan for completed status header');
411
- });
412
-
413
- runner.test('formatToolOutput: uses red header color for error status', () => {
414
- const result = formatToolOutput('bash', 'error', null, 'error', 100);
415
- // Tool name should have red color for error (header area)
416
- const headerPart = result.split('\n')[1]; // Second line is header
417
- assert.ok(headerPart.includes(TOOL_COLORS.red), 'should use red color in error status header');
418
- });
419
-
420
- runner.test('formatToolOutput: uses yellow header color for running status', () => {
421
- const result = formatToolOutput('bash', 'running', { command: 'ls' }, '', 0);
422
- // Tool name should have yellow color for running
423
- assert.ok(result.includes(TOOL_COLORS.yellow), 'should use yellow for running status header');
424
- });
425
-
426
- // ========================================
427
- // Default width tests (matching TUI)
428
- // ========================================
429
-
430
- runner.test('formatToolOutput: uses default width of 60 (matching TUI)', () => {
431
- const result = formatToolOutput('bash', 'completed', null, 'output', 100);
432
- // Bottom border should be 60 chars wide (╰ + 59 dashes + ╯)
433
- const bottomBorderMatch = result.match(/╰─+╯/);
434
- assert.ok(bottomBorderMatch, 'should have bottom border');
435
- // Width should be approximately 60 (╰ + ─s + ╯ = 60)
436
- assert.strictEqual(bottomBorderMatch[0].length, 60, 'default width should be 60');
437
- });
438
-
439
- // ========================================
440
- // JSON output handling
441
- // ========================================
442
-
443
- runner.test('formatToolOutput: stringifies object output', () => {
444
- const output = { key: 'value', nested: { a: 1 } };
445
- const result = formatToolOutput('bash', 'completed', null, output, 100);
446
- assert.ok(result.includes('key'), 'should include JSON key');
447
- assert.ok(result.includes('value'), 'should include JSON value');
448
- });
449
-
450
- // ========================================
451
- // Consistency with TUI format
452
- // ========================================
453
-
454
- runner.test('formatToolOutput: header format matches TUI (tool + status + duration)', () => {
455
- const result = formatToolOutput('bash', 'completed', null, 'output', 100);
456
- // Header should contain: ╭─ bash ✓ (100ms) ─...─╮
457
- assert.ok(result.includes('bash'), 'should include tool name');
458
- assert.ok(result.includes('✓'), 'should include status icon');
459
- assert.ok(result.includes('100ms'), 'should include duration');
460
- });
461
-
462
- runner.test('formatToolOutput: command line format ($ command without left border)', () => {
463
- const input = { command: 'npm test' };
464
- const result = formatToolOutput('bash', 'completed', input, 'passed', 50);
465
- // Should have $ prompt and command (no vertical bar per issue #47)
466
- assert.ok(result.includes('$'), 'should have $ prompt');
467
- assert.ok(result.includes('npm test'), 'should have command');
468
- });
469
-
470
- runner.test('formatToolOutput: output lines have no vertical bar prefix', () => {
471
- const result = formatToolOutput('bash', 'completed', null, 'test output', 100);
472
- const lines = result.split('\n');
473
- const outputLine = lines.find((l) => l.includes('test output'));
474
- assert.ok(outputLine, 'should have output line');
475
- // Per issue #47: output lines should NOT have vertical bar prefix
476
- assert.ok(!outputLine.startsWith('│'), 'output line should not have vertical bar prefix');
477
- });
478
-
479
- // Run tests if executed directly
480
- if (require.main === module) {
481
- runner.run().then((success) => {
482
- process.exit(success ? 0 : 1);
483
- });
484
- }
485
-
486
- module.exports = { runner };
@@ -1,143 +0,0 @@
1
- /**
2
- * Tests for tool output gating based on showTools flag
3
- * Tool output is shown by default (showTools: true)
4
- * Use --no-tools to hide tool output (showTools: false)
5
- */
6
-
7
- describe('Tool Output Gating', () => {
8
- let mockStdout;
9
-
10
- beforeEach(() => {
11
- mockStdout = jest.spyOn(process.stdout, 'write').mockImplementation();
12
- });
13
-
14
- afterEach(() => {
15
- mockStdout.mockRestore();
16
- });
17
-
18
- test('should not show tool output when showTools is false (--no-tools)', () => {
19
- const _toolPart = {
20
- type: 'tool',
21
- id: 'tool-123',
22
- tool: 'bash',
23
- state: {
24
- status: 'completed',
25
- input: '{"command": "ls"}',
26
- output: 'file1.txt\\nfile2.txt',
27
- },
28
- };
29
-
30
- // Simulate processing tool part without showTools
31
- const showTools = false;
32
- const json = false;
33
- const _noStream = false;
34
-
35
- // Tool output should NOT be displayed
36
- if (showTools && !json) {
37
- process.stdout.write('TOOL OUTPUT');
38
- }
39
-
40
- expect(mockStdout).not.toHaveBeenCalledWith(expect.stringContaining('TOOL OUTPUT'));
41
- });
42
-
43
- test('should show tool output when showTools is true (default)', () => {
44
- const _toolPart = {
45
- type: 'tool',
46
- id: 'tool-123',
47
- tool: 'bash',
48
- state: {
49
- status: 'completed',
50
- input: '{"command": "ls"}',
51
- output: 'file1.txt\\nfile2.txt',
52
- },
53
- };
54
-
55
- // Simulate processing tool part with showTools
56
- const showTools = true;
57
- const json = false;
58
- const _noStream = false;
59
-
60
- if (showTools && !json) {
61
- process.stdout.write('TOOL OUTPUT SHOWN');
62
- }
63
-
64
- expect(mockStdout).toHaveBeenCalledWith('TOOL OUTPUT SHOWN');
65
- });
66
-
67
- test('should buffer tool output in no-stream mode', () => {
68
- let bufferedToolOutput = '';
69
- const toolOutput = 'Buffered tool output\\n';
70
-
71
- // Simulate --tools + --no-stream
72
- const showTools = true;
73
- const json = false;
74
- const noStream = true;
75
-
76
- if (showTools && !json) {
77
- if (noStream) {
78
- bufferedToolOutput += toolOutput;
79
- } else {
80
- process.stdout.write(toolOutput);
81
- }
82
- }
83
-
84
- // Output should be buffered, not written immediately
85
- expect(mockStdout).not.toHaveBeenCalledWith(toolOutput);
86
- expect(bufferedToolOutput).toBe(toolOutput);
87
- });
88
- });
89
-
90
- describe('JSON Schema Consistency', () => {
91
- test('should always include tools array when showTools is true (default)', () => {
92
- const showTools = true;
93
- const toolOutputs = [];
94
-
95
- const output = {
96
- sessionId: 'test-session',
97
- messageId: 'test-message',
98
- response: 'Test response',
99
- };
100
-
101
- if (showTools) {
102
- output.tools = Array.isArray(toolOutputs) ? toolOutputs : [];
103
- }
104
-
105
- expect(output).toHaveProperty('tools');
106
- expect(Array.isArray(output.tools)).toBe(true);
107
- });
108
-
109
- test('should not include tools array when showTools is false (--no-tools)', () => {
110
- const showTools = false;
111
- const toolOutputs = [{ id: 'tool-1', name: 'bash' }];
112
-
113
- const output = {
114
- sessionId: 'test-session',
115
- messageId: 'test-message',
116
- response: 'Test response',
117
- };
118
-
119
- if (showTools) {
120
- output.tools = Array.isArray(toolOutputs) ? toolOutputs : [];
121
- }
122
-
123
- expect(output).not.toHaveProperty('tools');
124
- });
125
-
126
- test('should include tools even when empty with showTools true (default)', () => {
127
- const showTools = true;
128
- const toolOutputs = []; // Empty but --tools was used
129
-
130
- const output = {
131
- sessionId: 'test-session',
132
- messageId: 'test-message',
133
- response: 'Test response',
134
- };
135
-
136
- if (showTools) {
137
- output.tools = Array.isArray(toolOutputs) ? toolOutputs : [];
138
- }
139
-
140
- expect(output.tools).toEqual([]);
141
- expect(Array.isArray(output.tools)).toBe(true);
142
- });
143
- });