@cnrai/pave 0.3.32 → 0.3.34
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/MARKETPLACE.md +406 -0
- package/README.md +218 -21
- package/build-binary.js +591 -0
- package/build-npm.js +537 -0
- package/build.js +230 -0
- package/check-binary.js +26 -0
- package/deploy.sh +95 -0
- package/index.js +5775 -0
- package/lib/agent-registry.js +1037 -0
- package/lib/args-parser.js +837 -0
- package/lib/blessed-widget-patched.js +93 -0
- package/lib/cli-markdown.js +590 -0
- package/lib/compaction.js +153 -0
- package/lib/duration.js +94 -0
- package/lib/hash.js +22 -0
- package/lib/marketplace.js +866 -0
- package/lib/memory-config.js +166 -0
- package/lib/skill-manager.js +891 -0
- package/lib/soul.js +31 -0
- package/lib/tool-output-formatter.js +180 -0
- package/package.json +35 -33
- package/start-pave.sh +149 -0
- package/status.js +271 -0
- package/test/abort-stream.test.js +445 -0
- package/test/agent-auto-compaction.test.js +552 -0
- package/test/agent-comm-abort.test.js +95 -0
- package/test/agent-comm.test.js +598 -0
- package/test/agent-inbox.test.js +576 -0
- package/test/agent-init.test.js +264 -0
- package/test/agent-interrupt.test.js +314 -0
- package/test/agent-lifecycle.test.js +520 -0
- package/test/agent-log-files.test.js +349 -0
- package/test/agent-mode.manual-test.js +392 -0
- package/test/agent-parsing.test.js +228 -0
- package/test/agent-post-stream-idle.test.js +762 -0
- package/test/agent-registry.test.js +359 -0
- package/test/agent-rm.test.js +442 -0
- package/test/agent-spawn.test.js +933 -0
- package/test/agent-status-api.test.js +624 -0
- package/test/agent-update.test.js +435 -0
- package/test/args-parser.test.js +391 -0
- package/test/auto-compaction-chat.manual-test.js +227 -0
- package/test/auto-compaction.test.js +941 -0
- package/test/build-config.test.js +120 -0
- package/test/build-npm.test.js +388 -0
- package/test/chat-command.test.js +137 -0
- package/test/chat-leading-lines.test.js +159 -0
- package/test/config-flag.test.js +272 -0
- package/test/cursor-drift.test.js +135 -0
- package/test/debug-require.js +23 -0
- package/test/dir-migration.test.js +323 -0
- package/test/duration.test.js +229 -0
- package/test/ghostty-term.test.js +202 -0
- package/test/http500-backoff.test.js +854 -0
- package/test/integration.test.js +86 -0
- package/test/memory-guard-env.test.js +220 -0
- package/test/pr233-fixes.test.js +259 -0
- package/test/run-agent-init.js +297 -0
- package/test/run-all.js +64 -0
- package/test/run-config-flag.js +159 -0
- package/test/run-cursor-drift.js +82 -0
- package/test/run-session-path.js +154 -0
- package/test/run-tests.js +643 -0
- package/test/sandbox-redirect.test.js +202 -0
- package/test/session-path.test.js +132 -0
- package/test/shebang-strip.test.js +241 -0
- package/test/soul-reinject.test.js +1027 -0
- package/test/soul-reread.test.js +281 -0
- package/test/tool-output-formatter.test.js +486 -0
- package/test/tool-output-gating.test.js +143 -0
- package/test/tool-states.test.js +167 -0
- package/test/tools-flag.test.js +65 -0
- package/test/tui-attach.test.js +1255 -0
- package/test/tui-compaction.test.js +354 -0
- package/test/tui-wrap.test.js +568 -0
- package/test-binary.js +52 -0
- package/test-binary2.js +36 -0
- package/LICENSE +0 -21
- package/pave.js +0 -2
- package/sandbox/SandboxRunner.js +0 -1
- package/sandbox/pave-run.js +0 -2
- package/sandbox/permission.js +0 -1
- package/sandbox/utils/yaml.js +0 -1
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Test suite for args-parser.js
|
|
4
|
+
*
|
|
5
|
+
* NOTE: This test file directly imports the real parseArgs implementation
|
|
6
|
+
* to test actual parsing behavior. The mock in run-tests.js is for other
|
|
7
|
+
* test files that need consistent args behavior.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
// Directly import the real parseArgs to bypass mock
|
|
11
|
+
const { parseArgs } = require('../lib/args-parser');
|
|
12
|
+
|
|
13
|
+
// Detect whether spawnSync actually works in this environment.
|
|
14
|
+
let canSpawn = false;
|
|
15
|
+
try {
|
|
16
|
+
const { spawnSync: _sp } = require('child_process');
|
|
17
|
+
if (typeof _sp === 'function') {
|
|
18
|
+
const probe = _sp(process.execPath || 'node', ['-e', 'process.stdout.write("OK")'], {
|
|
19
|
+
encoding: 'utf-8', timeout: 5000,
|
|
20
|
+
});
|
|
21
|
+
canSpawn = (probe && probe.stdout === 'OK');
|
|
22
|
+
}
|
|
23
|
+
} catch (_) {}
|
|
24
|
+
|
|
25
|
+
function runTest(name, testFn) {
|
|
26
|
+
try {
|
|
27
|
+
testFn();
|
|
28
|
+
console.log(`✅ ${name}`);
|
|
29
|
+
} catch (error) {
|
|
30
|
+
console.log(`❌ ${name}: ${error.message}`);
|
|
31
|
+
process.exitCode = 1;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function assertEqual(actual, expected, message) {
|
|
36
|
+
if (actual !== expected) {
|
|
37
|
+
throw new Error(`${message}: expected ${expected}, got ${actual}`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Test default showTools behavior (defaults to true)
|
|
42
|
+
runTest('Default showTools should be true for chat command', () => {
|
|
43
|
+
const args = parseArgs(['chat', 'hello world']);
|
|
44
|
+
assertEqual(args.command, 'chat', 'Command should be chat');
|
|
45
|
+
assertEqual(args.showTools, true, 'showTools should default to true');
|
|
46
|
+
assertEqual(args.commandArgs.join(' '), 'hello world', 'Command args should be preserved');
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
// Test --no-tools flag (explicit disable)
|
|
50
|
+
runTest('--no-tools flag should set showTools to false', () => {
|
|
51
|
+
const args = parseArgs(['chat', '--no-tools', 'summarize files']);
|
|
52
|
+
assertEqual(args.command, 'chat', 'Command should be chat');
|
|
53
|
+
assertEqual(args.showTools, false, 'showTools should be false with --no-tools');
|
|
54
|
+
assertEqual(args.commandArgs.join(' '), 'summarize files', 'Command args should exclude --no-tools flag');
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
// Test --no-tools with other flags
|
|
58
|
+
runTest('--no-tools should work with other chat flags', () => {
|
|
59
|
+
const args = parseArgs(['chat', '--no-tools', '--verbose', 'hello world']);
|
|
60
|
+
assertEqual(args.command, 'chat', 'Command should be chat');
|
|
61
|
+
assertEqual(args.showTools, false, 'showTools should be false');
|
|
62
|
+
assertEqual(args.verbose, true, 'Verbose should be true');
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
// Test no command (TUI mode) - default should be true
|
|
66
|
+
runTest('No command should have default showTools true', () => {
|
|
67
|
+
const args = parseArgs([]);
|
|
68
|
+
assertEqual(args.showTools, true, 'showTools should default to true');
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
// Test flag parsing in chat command args
|
|
72
|
+
runTest('Chat command should properly exclude --no-tools from args', () => {
|
|
73
|
+
const args = parseArgs(['chat', 'list', '--no-tools', 'all', 'files']);
|
|
74
|
+
assertEqual(args.commandArgs.join(' '), 'list all files', 'All non-flag words should be in commandArgs');
|
|
75
|
+
assertEqual(args.showTools, false, '--no-tools flag should disable showTools');
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
// Test --no-tools flag position doesn't matter
|
|
79
|
+
runTest('--no-tools at end should still work', () => {
|
|
80
|
+
const args = parseArgs(['chat', 'hello', 'world', '--no-tools']);
|
|
81
|
+
assertEqual(args.showTools, false, 'showTools should be false');
|
|
82
|
+
assertEqual(args.commandArgs.join(' '), 'hello world', 'Command args should exclude --no-tools');
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
// Test non-chat commands (should have default true)
|
|
86
|
+
runTest('Non-chat commands should have default showTools true', () => {
|
|
87
|
+
const args = parseArgs(['list']);
|
|
88
|
+
assertEqual(args.command, 'list', 'Command should be list');
|
|
89
|
+
assertEqual(args.showTools, true, 'showTools should default to true');
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
// ============================================
|
|
93
|
+
// Agent command tests
|
|
94
|
+
// The parser uses:
|
|
95
|
+
// - args.soul for --soul <path> flag
|
|
96
|
+
// - args.commandArgs for positional SOUL file (e.g., 'pave agent SOUL.md')
|
|
97
|
+
// - args.config for --config <path> flag (defaults to './.pave')
|
|
98
|
+
// - args.sleep for --sleep <duration> flag (defaults to '1m')
|
|
99
|
+
// ============================================
|
|
100
|
+
|
|
101
|
+
// Test agent command with no args should apply defaults
|
|
102
|
+
runTest('Agent command with no args should apply defaults', () => {
|
|
103
|
+
const args = parseArgs(['agent']);
|
|
104
|
+
assertEqual(args.command, 'agent', 'Command should be agent');
|
|
105
|
+
assertEqual(args.config, './.pave', 'Config should default to ./.pave');
|
|
106
|
+
assertEqual(args.sleep, '1m', 'Sleep should default to 1m');
|
|
107
|
+
assertEqual(args.commandArgs.length, 1, 'Should have one command arg');
|
|
108
|
+
assertEqual(args.commandArgs[0], 'AGENTS.md', 'Command arg should default to AGENTS.md');
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
// Test agent command with explicit values should override defaults
|
|
112
|
+
runTest('Agent command with explicit values should override defaults', () => {
|
|
113
|
+
const args = parseArgs(['agent', '--config', '/custom', '--sleep', '5m', '--soul', 'custom.md']);
|
|
114
|
+
assertEqual(args.command, 'agent', 'Command should be agent');
|
|
115
|
+
assertEqual(args.config, '/custom', 'Config should be overridden');
|
|
116
|
+
assertEqual(args.sleep, '5m', 'Sleep should be overridden');
|
|
117
|
+
assertEqual(args.soul, 'custom.md', 'Soul should be overridden');
|
|
118
|
+
assertEqual(args.commandArgs.length, 0, 'Command args should be empty when --soul is used');
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
// Test agent command with positional SOUL file should not add default
|
|
122
|
+
runTest('Agent command with positional SOUL file should not add default AGENTS.md', () => {
|
|
123
|
+
const args = parseArgs(['agent', 'MY_SOUL.md']);
|
|
124
|
+
assertEqual(args.command, 'agent', 'Command should be agent');
|
|
125
|
+
assertEqual(args.config, './.pave', 'Config should still be default');
|
|
126
|
+
assertEqual(args.sleep, '1m', 'Sleep should still be default');
|
|
127
|
+
assertEqual(args.commandArgs.length, 1, 'Should have one command arg');
|
|
128
|
+
assertEqual(args.commandArgs[0], 'MY_SOUL.md', 'Command arg should be the provided file');
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
// Test agent command with partial overrides
|
|
132
|
+
runTest('Agent command with partial overrides should apply remaining defaults', () => {
|
|
133
|
+
const args = parseArgs(['agent', '--config', '/tmp/.pave']);
|
|
134
|
+
assertEqual(args.command, 'agent', 'Command should be agent');
|
|
135
|
+
assertEqual(args.config, '/tmp/.pave', 'Config should be overridden');
|
|
136
|
+
assertEqual(args.sleep, '1m', 'Sleep should still be default');
|
|
137
|
+
assertEqual(args.commandArgs.length, 1, 'Should have default command arg');
|
|
138
|
+
assertEqual(args.commandArgs[0], 'AGENTS.md', 'Command arg should be default AGENTS.md');
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
// Test agent command with --soul should set soul field
|
|
142
|
+
runTest('Agent command with --soul should set soul field', () => {
|
|
143
|
+
const args = parseArgs(['agent', '--soul', 'SOUL.md']);
|
|
144
|
+
assertEqual(args.command, 'agent', 'Command should be agent');
|
|
145
|
+
assertEqual(args.soul, 'SOUL.md', 'Soul should be SOUL.md');
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
// Test agent command with --sleep
|
|
149
|
+
runTest('Agent command with --sleep should override default', () => {
|
|
150
|
+
const args = parseArgs(['agent', '--sleep', '5m']);
|
|
151
|
+
assertEqual(args.command, 'agent', 'Command should be agent');
|
|
152
|
+
assertEqual(args.sleep, '5m', 'Sleep should be 5m');
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
// Test agent command with --config
|
|
156
|
+
runTest('Agent command with --config should override default', () => {
|
|
157
|
+
const args = parseArgs(['agent', '--config', '/home/user/.pave']);
|
|
158
|
+
assertEqual(args.command, 'agent', 'Command should be agent');
|
|
159
|
+
assertEqual(args.config, '/home/user/.pave', 'Config should match provided path');
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
// Test agent command with --verbose
|
|
163
|
+
runTest('Agent command with --verbose should set verbose flag', () => {
|
|
164
|
+
const args = parseArgs(['agent', '--verbose']);
|
|
165
|
+
assertEqual(args.command, 'agent', 'Command should be agent');
|
|
166
|
+
assertEqual(args.verbose, true, 'Verbose should be true');
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
// Test agent command with --debug
|
|
170
|
+
runTest('Agent command with --debug should set debug flag', () => {
|
|
171
|
+
const args = parseArgs(['agent', '--debug']);
|
|
172
|
+
assertEqual(args.command, 'agent', 'Command should be agent');
|
|
173
|
+
assertEqual(args.debug, true, 'Debug should be true');
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
// Test agent command with --help should NOT apply defaults (to avoid side effects like directory creation)
|
|
177
|
+
runTest('Agent command with --help should not apply defaults', () => {
|
|
178
|
+
const args = parseArgs(['agent', '--help']);
|
|
179
|
+
assertEqual(args.command, 'agent', 'Command should be agent');
|
|
180
|
+
assertEqual(args.help, true, 'Help should be true');
|
|
181
|
+
// Defaults should NOT be applied when --help is requested to avoid side effects
|
|
182
|
+
// Note: config and sleep are initialized to null in parseArgs, not undefined
|
|
183
|
+
assertEqual(args.config, null, 'Config should be null with --help');
|
|
184
|
+
assertEqual(args.sleep, null, 'Sleep should be null with --help');
|
|
185
|
+
assertEqual(args.commandArgs.length, 0, 'Should have no command args with --help');
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
// Test agent command with --version should not apply defaults (avoid side effects)
|
|
189
|
+
runTest('Agent command with --version should not apply defaults', () => {
|
|
190
|
+
const args = parseArgs(['agent', '--version']);
|
|
191
|
+
assertEqual(args.command, 'agent', 'Command should be agent');
|
|
192
|
+
assertEqual(args.version, true, 'Version should be true');
|
|
193
|
+
// Defaults should NOT be applied when --version is requested to avoid side effects
|
|
194
|
+
// Note: config and sleep are initialized to null in parseArgs, not undefined
|
|
195
|
+
assertEqual(args.config, null, 'Config should be null with --version');
|
|
196
|
+
assertEqual(args.sleep, null, 'Sleep should be null with --version');
|
|
197
|
+
assertEqual(args.commandArgs.length, 0, 'Should have no command args with --version');
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
// Test agent command with -v shorthand should not apply defaults
|
|
201
|
+
runTest('Agent command with -v shorthand should not apply defaults', () => {
|
|
202
|
+
const args = parseArgs(['agent', '-v']);
|
|
203
|
+
assertEqual(args.command, 'agent', 'Command should be agent');
|
|
204
|
+
assertEqual(args.version, true, 'Version should be true');
|
|
205
|
+
// Defaults should NOT be applied when -v is requested to avoid side effects
|
|
206
|
+
// Note: config and sleep are initialized to null in parseArgs, not undefined
|
|
207
|
+
assertEqual(args.config, null, 'Config should be null with -v');
|
|
208
|
+
assertEqual(args.sleep, null, 'Sleep should be null with -v');
|
|
209
|
+
assertEqual(args.commandArgs.length, 0, 'Should have no command args with -v');
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
// Test agent command argument order doesn't matter
|
|
213
|
+
runTest('Agent command args order should not matter', () => {
|
|
214
|
+
const args = parseArgs(['agent', '--sleep', '2m', '--config', '/custom']);
|
|
215
|
+
assertEqual(args.command, 'agent', 'Command should be agent');
|
|
216
|
+
assertEqual(args.config, '/custom', 'Config should be /custom');
|
|
217
|
+
assertEqual(args.sleep, '2m', 'Sleep should be 2m');
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
// Test --config without value exits with error (using subprocess)
|
|
221
|
+
runTest('Agent command with --config without value should error', () => {
|
|
222
|
+
if (!canSpawn) return; // skip in sandbox
|
|
223
|
+
const { spawnSync } = require('child_process');
|
|
224
|
+
const path = require('path');
|
|
225
|
+
|
|
226
|
+
// Create a small test script that just calls parseArgs
|
|
227
|
+
const testScript = `
|
|
228
|
+
const { parseArgs } = require('./lib/args-parser');
|
|
229
|
+
parseArgs(['agent', '--config']);
|
|
230
|
+
`;
|
|
231
|
+
|
|
232
|
+
const result = spawnSync('node', ['-e', testScript], {
|
|
233
|
+
cwd: path.join(__dirname, '..'),
|
|
234
|
+
encoding: 'utf8',
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
assertEqual(result.status, 1, 'Should exit with status 1');
|
|
238
|
+
assertEqual(result.stderr.includes('--config requires a path argument'), true, 'Should print correct error message');
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
// Test --sleep without value exits with error (using subprocess)
|
|
242
|
+
runTest('Agent command with --sleep without value should error', () => {
|
|
243
|
+
if (!canSpawn) return; // skip in sandbox
|
|
244
|
+
const { spawnSync } = require('child_process');
|
|
245
|
+
const path = require('path');
|
|
246
|
+
|
|
247
|
+
const testScript = `
|
|
248
|
+
const { parseArgs } = require('./lib/args-parser');
|
|
249
|
+
parseArgs(['agent', '--sleep']);
|
|
250
|
+
`;
|
|
251
|
+
|
|
252
|
+
const result = spawnSync('node', ['-e', testScript], {
|
|
253
|
+
cwd: path.join(__dirname, '..'),
|
|
254
|
+
encoding: 'utf8',
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
assertEqual(result.status, 1, 'Should exit with status 1');
|
|
258
|
+
assertEqual(result.stderr.includes('--sleep requires a value'), true, 'Should print error message');
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
// Test --soul without value exits with error (using subprocess)
|
|
262
|
+
runTest('Agent command with --soul without value should error', () => {
|
|
263
|
+
if (!canSpawn) return; // skip in sandbox
|
|
264
|
+
const { spawnSync } = require('child_process');
|
|
265
|
+
const path = require('path');
|
|
266
|
+
|
|
267
|
+
const testScript = `
|
|
268
|
+
const { parseArgs } = require('./lib/args-parser');
|
|
269
|
+
parseArgs(['agent', '--soul']);
|
|
270
|
+
`;
|
|
271
|
+
|
|
272
|
+
const result = spawnSync('node', ['-e', testScript], {
|
|
273
|
+
cwd: path.join(__dirname, '..'),
|
|
274
|
+
encoding: 'utf8',
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
assertEqual(result.status, 1, 'Should exit with status 1');
|
|
278
|
+
assertEqual(result.stderr.includes('--soul requires a value'), true, 'Should print error message');
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
// Test --name without value exits with error (using subprocess)
|
|
282
|
+
runTest('Agent command with --name without value should error', () => {
|
|
283
|
+
if (!canSpawn) return; // skip in sandbox
|
|
284
|
+
const { spawnSync } = require('child_process');
|
|
285
|
+
const path = require('path');
|
|
286
|
+
|
|
287
|
+
const testScript = `
|
|
288
|
+
const { parseArgs } = require('./lib/args-parser');
|
|
289
|
+
parseArgs(['agent', '--name']);
|
|
290
|
+
`;
|
|
291
|
+
|
|
292
|
+
const result = spawnSync('node', ['-e', testScript], {
|
|
293
|
+
cwd: path.join(__dirname, '..'),
|
|
294
|
+
encoding: 'utf8',
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
assertEqual(result.status, 1, 'Should exit with status 1');
|
|
298
|
+
assertEqual(result.stderr.includes('--name requires a value'), true, 'Should print error message');
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
// Test --agent without valid value exits with error (using subprocess) (#231)
|
|
302
|
+
runTest('Chat command with --agent missing value should error', () => {
|
|
303
|
+
if (!canSpawn) return; // skip in sandbox
|
|
304
|
+
const { spawnSync } = require('child_process');
|
|
305
|
+
const path = require('path');
|
|
306
|
+
|
|
307
|
+
const testScript = `
|
|
308
|
+
const { parseArgs } = require('./lib/args-parser');
|
|
309
|
+
parseArgs(['chat', '--agent']);
|
|
310
|
+
`;
|
|
311
|
+
|
|
312
|
+
const result = spawnSync('node', ['-e', testScript], {
|
|
313
|
+
cwd: path.join(__dirname, '..'),
|
|
314
|
+
encoding: 'utf8',
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
assertEqual(result.status, 1, 'Should exit with status 1');
|
|
318
|
+
assertEqual(result.stderr.includes('--agent requires an agent name'), true, 'Should print error message');
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
// Test --agent followed by flag-like value exits with error (#231)
|
|
322
|
+
runTest('Chat command with --agent followed by flag should error', () => {
|
|
323
|
+
if (!canSpawn) return; // skip in sandbox
|
|
324
|
+
const { spawnSync } = require('child_process');
|
|
325
|
+
const path = require('path');
|
|
326
|
+
|
|
327
|
+
const testScript = `
|
|
328
|
+
const { parseArgs } = require('./lib/args-parser');
|
|
329
|
+
parseArgs(['chat', '--agent', '--no-stream', 'hello']);
|
|
330
|
+
`;
|
|
331
|
+
|
|
332
|
+
const result = spawnSync('node', ['-e', testScript], {
|
|
333
|
+
cwd: path.join(__dirname, '..'),
|
|
334
|
+
encoding: 'utf8',
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
assertEqual(result.status, 1, 'Should exit with status 1');
|
|
338
|
+
assertEqual(result.stderr.includes('--agent requires an agent name'), true, 'Should print error message');
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
// =============================================================
|
|
342
|
+
// 'pave agents' command tests (Issue #198)
|
|
343
|
+
// =============================================================
|
|
344
|
+
|
|
345
|
+
runTest('Agents command should set command to agents', () => {
|
|
346
|
+
const args = parseArgs(['agents']);
|
|
347
|
+
assertEqual(args.command, 'agents', 'Command should be agents');
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
runTest('Agents command with --json should set json flag', () => {
|
|
351
|
+
const args = parseArgs(['agents', '--json']);
|
|
352
|
+
assertEqual(args.command, 'agents', 'Command should be agents');
|
|
353
|
+
assertEqual(args.json, true, 'Should have json flag set');
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
// =============================================================
|
|
357
|
+
// 'pave agent ps' subcommand tests (Issue #278)
|
|
358
|
+
// =============================================================
|
|
359
|
+
|
|
360
|
+
runTest('Agent ps subcommand should map to agents command', () => {
|
|
361
|
+
// 'pave agent ps' is mapped to 'pave agents' to avoid .pave directory creation
|
|
362
|
+
const args = parseArgs(['agent', 'ps']);
|
|
363
|
+
assertEqual(args.command, 'agents', 'Command should be agents (mapped from agent ps)');
|
|
364
|
+
assertEqual(args.agentSubcommand, null, 'agentSubcommand should be null');
|
|
365
|
+
assertEqual(args.commandArgs.length, 0, 'Should have no command args');
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
runTest('Agent ps subcommand with extra args should error', () => {
|
|
369
|
+
// Test that 'pave agent ps extra' is rejected - parseArgs calls process.exit(1)
|
|
370
|
+
// when validation fails. We mock process.exit to verify it's called with code 1.
|
|
371
|
+
const origExit = process.exit;
|
|
372
|
+
let exitCalledWith = null;
|
|
373
|
+
process.exit = (code) => { exitCalledWith = code; throw new Error('exit called'); };
|
|
374
|
+
|
|
375
|
+
try {
|
|
376
|
+
parseArgs(['agent', 'ps', 'extra']);
|
|
377
|
+
} catch (e) {
|
|
378
|
+
// Expected to throw
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
process.exit = origExit;
|
|
382
|
+
assertEqual(exitCalledWith, 1, 'Should call process.exit(1)');
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
console.log('\n📊 Test Summary:');
|
|
386
|
+
if (process.exitCode === 1) {
|
|
387
|
+
console.log('Some tests failed. Please fix the issues above.');
|
|
388
|
+
throw new Error('args-parser tests failed');
|
|
389
|
+
} else {
|
|
390
|
+
console.log('All tests passed! 🎉');
|
|
391
|
+
}
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Auto-compaction test for chat command
|
|
3
|
+
// Tests that 'pave chat' automatically triggers compaction when context exceeds threshold
|
|
4
|
+
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
const path = require('path');
|
|
7
|
+
const { execSync } = require('child_process');
|
|
8
|
+
|
|
9
|
+
// Test configuration
|
|
10
|
+
const TEST_CONFIG = {
|
|
11
|
+
timeout: 120000, // 2 minutes
|
|
12
|
+
tempDir: null,
|
|
13
|
+
sessionFile: null,
|
|
14
|
+
originalCwd: process.cwd(),
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
// ANSI colors for output
|
|
18
|
+
const COLORS = {
|
|
19
|
+
green: '\x1b[32m',
|
|
20
|
+
red: '\x1b[31m',
|
|
21
|
+
yellow: '\x1b[33m',
|
|
22
|
+
blue: '\x1b[34m',
|
|
23
|
+
reset: '\x1b[0m',
|
|
24
|
+
dim: '\x1b[2m',
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
function log(message, color = 'reset') {
|
|
28
|
+
console.log(`${COLORS[color]}${message}${COLORS.reset}`);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function createTempWorkspace() {
|
|
32
|
+
const tmpDir = fs.mkdtempSync(path.join(__dirname, 'tmp-auto-compaction-'));
|
|
33
|
+
const sessionFile = path.join(tmpDir, '.pave-session.json');
|
|
34
|
+
|
|
35
|
+
TEST_CONFIG.tempDir = tmpDir;
|
|
36
|
+
TEST_CONFIG.sessionFile = sessionFile;
|
|
37
|
+
|
|
38
|
+
process.chdir(tmpDir);
|
|
39
|
+
log(`Created temp workspace: ${tmpDir}`, 'dim');
|
|
40
|
+
return { tmpDir, sessionFile };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function cleanup() {
|
|
44
|
+
process.chdir(TEST_CONFIG.originalCwd);
|
|
45
|
+
|
|
46
|
+
if (TEST_CONFIG.tempDir && fs.existsSync(TEST_CONFIG.tempDir)) {
|
|
47
|
+
fs.rmSync(TEST_CONFIG.tempDir, { recursive: true, force: true });
|
|
48
|
+
log(`Cleaned up: ${TEST_CONFIG.tempDir}`, 'dim');
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function runPaveCommand(args, options = {}) {
|
|
53
|
+
const { expectError = false, timeout = 30000 } = options;
|
|
54
|
+
const pavePath = path.join(__dirname, '../index.js');
|
|
55
|
+
const cmd = `node "${pavePath}" ${args}`;
|
|
56
|
+
|
|
57
|
+
try {
|
|
58
|
+
const result = execSync(cmd, {
|
|
59
|
+
encoding: 'utf8',
|
|
60
|
+
timeout,
|
|
61
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
62
|
+
cwd: TEST_CONFIG.tempDir,
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
if (expectError) {
|
|
66
|
+
throw new Error(`Expected command to fail but it succeeded: ${cmd}`);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return { success: true, stdout: result, stderr: '' };
|
|
70
|
+
} catch (error) {
|
|
71
|
+
if (expectError) {
|
|
72
|
+
return { success: false, stdout: '', stderr: error.message };
|
|
73
|
+
}
|
|
74
|
+
throw error;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async function testAutoCompaction() {
|
|
79
|
+
log('Testing auto-compaction in chat command...', 'blue');
|
|
80
|
+
|
|
81
|
+
try {
|
|
82
|
+
// Create temporary workspace
|
|
83
|
+
createTempWorkspace();
|
|
84
|
+
|
|
85
|
+
// Test 1: Create a session and fill it with many messages to exceed threshold
|
|
86
|
+
log('Step 1: Creating session with initial message...', 'yellow');
|
|
87
|
+
const result1 = runPaveCommand('chat --verbose "Hello, please introduce yourself."');
|
|
88
|
+
|
|
89
|
+
if (!result1.success) {
|
|
90
|
+
throw new Error('Failed to create initial session');
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Check session file was created
|
|
94
|
+
if (!fs.existsSync(TEST_CONFIG.sessionFile)) {
|
|
95
|
+
throw new Error('Session file was not created');
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const sessionData = JSON.parse(fs.readFileSync(TEST_CONFIG.sessionFile, 'utf8'));
|
|
99
|
+
const originalSessionId = sessionData.sessionId;
|
|
100
|
+
log(`Initial session ID: ${originalSessionId}`, 'dim');
|
|
101
|
+
|
|
102
|
+
// Test 2: Send multiple large messages to trigger compaction
|
|
103
|
+
log('Step 2: Sending large messages to trigger compaction...', 'yellow');
|
|
104
|
+
|
|
105
|
+
// Create a large message that will push us over the token limit
|
|
106
|
+
const largeMessage = 'Please analyze the following text and provide detailed insights: ' +
|
|
107
|
+
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. '.repeat(200) +
|
|
108
|
+
'Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. '.repeat(200) +
|
|
109
|
+
'Ut enim ad minim veniam, quis nostrud exercitation ullamco. '.repeat(200);
|
|
110
|
+
|
|
111
|
+
// Send several large messages
|
|
112
|
+
for (let i = 0; i < 3; i++) {
|
|
113
|
+
log(` Sending message ${i + 1}/3...`, 'dim');
|
|
114
|
+
const result = runPaveCommand(`chat --verbose "${largeMessage} Message ${i + 1}"`);
|
|
115
|
+
|
|
116
|
+
if (!result.success) {
|
|
117
|
+
log(`Message ${i + 1} failed`, 'red');
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Check if session ID changed (indicating compaction occurred)
|
|
122
|
+
const currentData = JSON.parse(fs.readFileSync(TEST_CONFIG.sessionFile, 'utf8'));
|
|
123
|
+
if (currentData.sessionId !== originalSessionId) {
|
|
124
|
+
log(` Auto-compaction triggered! Session changed: ${originalSessionId} � ${currentData.sessionId}`, 'green');
|
|
125
|
+
return true;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Add delay between messages
|
|
129
|
+
// await new Promise(resolve => setTimeout(resolve, 1000));
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Test 3: Try one more very large message to force compaction
|
|
133
|
+
log('Step 3: Sending extra large message to force compaction...', 'yellow');
|
|
134
|
+
const extraLargeMessage = 'Please provide a comprehensive analysis: ' +
|
|
135
|
+
'The quick brown fox jumps over the lazy dog. '.repeat(500) +
|
|
136
|
+
'This is a test message to exceed token limits. '.repeat(500);
|
|
137
|
+
|
|
138
|
+
const _result3 = runPaveCommand(`chat --verbose "${extraLargeMessage}"`);
|
|
139
|
+
|
|
140
|
+
// Check final session state
|
|
141
|
+
const finalData = JSON.parse(fs.readFileSync(TEST_CONFIG.sessionFile, 'utf8'));
|
|
142
|
+
if (finalData.sessionId !== originalSessionId) {
|
|
143
|
+
log(` Auto-compaction triggered on large message! ${originalSessionId} � ${finalData.sessionId}`, 'green');
|
|
144
|
+
return true;
|
|
145
|
+
}
|
|
146
|
+
log(`� Compaction may not have been needed or may be disabled in test environment`, 'yellow');
|
|
147
|
+
log(`Session remained: ${finalData.sessionId}`, 'dim');
|
|
148
|
+
// This is not necessarily a failure - compaction might not be needed
|
|
149
|
+
return true;
|
|
150
|
+
} catch (error) {
|
|
151
|
+
log(` Test failed: ${error.message}`, 'red');
|
|
152
|
+
return false;
|
|
153
|
+
} finally {
|
|
154
|
+
cleanup();
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Test helper: Check if auto-compaction feature is working
|
|
159
|
+
async function testCompactionAPI() {
|
|
160
|
+
log('Testing compaction API availability...', 'blue');
|
|
161
|
+
|
|
162
|
+
try {
|
|
163
|
+
createTempWorkspace();
|
|
164
|
+
|
|
165
|
+
// Start a session
|
|
166
|
+
const result = runPaveCommand('chat "Test message"');
|
|
167
|
+
if (!result.success) {
|
|
168
|
+
log(' Failed to create test session', 'red');
|
|
169
|
+
return false;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
log(' Compaction feature appears to be available', 'green');
|
|
173
|
+
return true;
|
|
174
|
+
} catch (error) {
|
|
175
|
+
log(` Compaction API test failed: ${error.message}`, 'red');
|
|
176
|
+
return false;
|
|
177
|
+
} finally {
|
|
178
|
+
cleanup();
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Main test runner
|
|
183
|
+
async function runTests() {
|
|
184
|
+
log('='.repeat(60), 'blue');
|
|
185
|
+
log('Auto-Compaction Chat Command Test', 'blue');
|
|
186
|
+
log('='.repeat(60), 'blue');
|
|
187
|
+
|
|
188
|
+
const tests = [
|
|
189
|
+
{ name: 'Compaction API', fn: testCompactionAPI },
|
|
190
|
+
{ name: 'Auto-compaction trigger', fn: testAutoCompaction },
|
|
191
|
+
];
|
|
192
|
+
|
|
193
|
+
let passed = 0;
|
|
194
|
+
const total = tests.length;
|
|
195
|
+
|
|
196
|
+
for (const test of tests) {
|
|
197
|
+
log(`\nRunning test: ${test.name}`, 'yellow');
|
|
198
|
+
const success = await test.fn();
|
|
199
|
+
if (success) {
|
|
200
|
+
passed++;
|
|
201
|
+
log(` ${test.name} passed`, 'green');
|
|
202
|
+
} else {
|
|
203
|
+
log(` ${test.name} failed`, 'red');
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
log('\n' + '='.repeat(60), 'blue');
|
|
208
|
+
log(`Test Results: ${passed}/${total} passed`, passed === total ? 'green' : 'red');
|
|
209
|
+
log('='.repeat(60), 'blue');
|
|
210
|
+
|
|
211
|
+
process.exit(passed === total ? 0 : 1);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Run tests if this file is executed directly
|
|
215
|
+
if (require.main === module) {
|
|
216
|
+
runTests().catch((error) => {
|
|
217
|
+
log(` Test suite crashed: ${error.message}`, 'red');
|
|
218
|
+
cleanup();
|
|
219
|
+
process.exit(1);
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
module.exports = {
|
|
224
|
+
testAutoCompaction,
|
|
225
|
+
testCompactionAPI,
|
|
226
|
+
runTests,
|
|
227
|
+
};
|