@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
|
@@ -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
|
-
});
|