@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,281 +0,0 @@
1
- /**
2
- * Tests for SOUL file re-reading behavior in agent mode
3
- *
4
- * Verifies that the SOUL file is re-read on each iteration so that
5
- * runtime changes to the file are picked up without restarting the agent.
6
- *
7
- * Issue: https://github.com/cnrai/openpave/issues/89
8
- */
9
-
10
- const fs = require('fs');
11
- const path = require('path');
12
- const os = require('os');
13
-
14
- // Import the production validateSoulFile helper directly (no fallback).
15
- // A require failure here means the export path is broken and should fail the test.
16
- const { validateSoulFile } = require('../lib/soul');
17
-
18
- // Helper to create a temporary SOUL file
19
- function createTempSoulFile(content) {
20
- const tmpDir = os.tmpdir();
21
- const soulPath = path.join(tmpDir, `test-soul-${Date.now()}-${Math.random().toString(36).slice(2)}.md`);
22
- fs.writeFileSync(soulPath, content, 'utf8');
23
- return soulPath;
24
- }
25
-
26
- // Helper to clean up temp files
27
- function removeTempFile(filePath) {
28
- try {
29
- if (fs.existsSync(filePath)) {
30
- fs.unlinkSync(filePath);
31
- }
32
- } catch (e) {
33
- // Ignore cleanup errors
34
- }
35
- }
36
-
37
- /**
38
- * Simulate the per-iteration SOUL read + message decision logic from the agent.
39
- * This mirrors the code inside the while(running) loop:
40
- * 1. Re-read SOUL file
41
- * 2. If !soulSent -> message = soulContent (sendingSoul = true)
42
- * 3. Else -> message = 'keep going'
43
- * 4. After successful send, soulSent = true (only on success)
44
- *
45
- * @param {string} soulPath - Path to the SOUL file
46
- * @param {object} state - Mutable state with { soulSent: boolean }
47
- * @param {object} [opts] - Options
48
- * @param {boolean} [opts.sendSuccess=true] - Simulate whether the send succeeds
49
- * @returns {{ message, soulSent, error, sendingSoul, readContent }}
50
- */
51
- function simulateIteration(soulPath, state, opts) {
52
- const sendSuccess = (opts && opts.sendSuccess !== undefined) ? opts.sendSuccess : true;
53
-
54
- let soulContent;
55
- try {
56
- soulContent = fs.readFileSync(soulPath, 'utf8');
57
- } catch (err) {
58
- return { message: null, soulSent: state.soulSent, error: err, readContent: null };
59
- }
60
-
61
- const sendingSoul = !state.soulSent;
62
- let message;
63
- if (sendingSoul) {
64
- message = soulContent;
65
- } else {
66
- message = 'keep going';
67
- }
68
-
69
- // Only mark soulSent after a successful send (mirrors production code)
70
- if (sendSuccess && sendingSoul) {
71
- state.soulSent = true;
72
- }
73
-
74
- return { message, soulSent: state.soulSent, error: null, sendingSoul, readContent: soulContent };
75
- }
76
-
77
- describe('SOUL file re-reading (issue #89)', () => {
78
- test('SOUL content is sent on first iteration of a new session', () => {
79
- const soulPath = createTempSoulFile('# My SOUL\nDo important work');
80
- try {
81
- const state = { soulSent: false }; // new session
82
- const result = simulateIteration(soulPath, state);
83
-
84
- expect(result.error).toBeNull();
85
- expect(result.message).toBe('# My SOUL\nDo important work');
86
- expect(result.soulSent).toBe(true);
87
- expect(result.sendingSoul).toBe(true);
88
- } finally {
89
- removeTempFile(soulPath);
90
- }
91
- });
92
-
93
- test('subsequent iterations send "keep going" after SOUL has been sent', () => {
94
- const soulPath = createTempSoulFile('# SOUL v1');
95
- try {
96
- const state = { soulSent: true }; // already sent
97
- const result = simulateIteration(soulPath, state);
98
-
99
- expect(result.error).toBeNull();
100
- expect(result.message).toBe('keep going');
101
- expect(result.sendingSoul).toBe(false);
102
- } finally {
103
- removeTempFile(soulPath);
104
- }
105
- });
106
-
107
- test('resumed sessions skip SOUL and send "keep going"', () => {
108
- const soulPath = createTempSoulFile('# SOUL');
109
- try {
110
- // Resumed sessions initialize soulSent = true (matching: let soulSent = !isNewSession)
111
- const state = { soulSent: true };
112
- const result = simulateIteration(soulPath, state);
113
-
114
- expect(result.message).toBe('keep going');
115
- expect(result.sendingSoul).toBe(false);
116
- } finally {
117
- removeTempFile(soulPath);
118
- }
119
- });
120
-
121
- test('SOUL file changes are picked up on re-read between iterations', () => {
122
- const soulPath = createTempSoulFile('# SOUL v1\nDo task A');
123
- try {
124
- const state = { soulSent: false };
125
-
126
- // First iteration sends SOUL v1
127
- const r1 = simulateIteration(soulPath, state);
128
- expect(r1.message).toContain('SOUL v1');
129
- expect(r1.readContent).toContain('SOUL v1');
130
-
131
- // User edits SOUL file between iterations
132
- fs.writeFileSync(soulPath, '# SOUL v2\nDo task B instead', 'utf8');
133
-
134
- // Second iteration re-reads the file and gets v2 content
135
- const r2 = simulateIteration(soulPath, state);
136
- expect(r2.message).toBe('keep going');
137
- // Assert the updated content was actually read (not cached from v1)
138
- expect(r2.readContent).toContain('SOUL v2');
139
- expect(r2.readContent).toContain('Do task B instead');
140
- expect(r2.readContent).not.toContain('SOUL v1');
141
- } finally {
142
- removeTempFile(soulPath);
143
- }
144
- });
145
-
146
- test('transient read failure does NOT permanently skip SOUL delivery', () => {
147
- // This is the key bug that was fixed: if the SOUL file is temporarily
148
- // unreadable on the first attempt, soulSent stays false so the next
149
- // successful iteration will still send SOUL content.
150
- const soulPath = createTempSoulFile('# Important SOUL');
151
- try {
152
- const state = { soulSent: false };
153
-
154
- // Simulate transient failure: delete file temporarily
155
- const originalContent = fs.readFileSync(soulPath, 'utf8');
156
- fs.unlinkSync(soulPath);
157
-
158
- // Iteration fails to read SOUL
159
- const r1 = simulateIteration(soulPath, state);
160
- expect(r1.error).not.toBeNull();
161
- expect(r1.soulSent).toBe(false); // Still false! Not permanently skipped
162
-
163
- // File comes back (user recreates it)
164
- fs.writeFileSync(soulPath, originalContent, 'utf8');
165
-
166
- // Next iteration successfully sends SOUL
167
- const r2 = simulateIteration(soulPath, state);
168
- expect(r2.error).toBeNull();
169
- expect(r2.message).toBe('# Important SOUL');
170
- expect(r2.soulSent).toBe(true);
171
- } finally {
172
- removeTempFile(soulPath);
173
- }
174
- });
175
-
176
- test('multiple transient failures followed by recovery still sends SOUL', () => {
177
- const soulPath = createTempSoulFile('# My Instructions');
178
- try {
179
- const state = { soulSent: false };
180
- const content = fs.readFileSync(soulPath, 'utf8');
181
- fs.unlinkSync(soulPath);
182
-
183
- // Fail 3 times
184
- for (let i = 0; i < 3; i++) {
185
- const r = simulateIteration(soulPath, state);
186
- expect(r.error).not.toBeNull();
187
- expect(r.soulSent).toBe(false);
188
- }
189
-
190
- // Recover
191
- fs.writeFileSync(soulPath, content, 'utf8');
192
- const r = simulateIteration(soulPath, state);
193
- expect(r.error).toBeNull();
194
- expect(r.message).toBe('# My Instructions');
195
- expect(r.soulSent).toBe(true);
196
- } finally {
197
- removeTempFile(soulPath);
198
- }
199
- });
200
-
201
- test('compaction guard uses boolean flag, not message string comparison', () => {
202
- // Verify that the iteration logic returns a sendingSoul boolean
203
- // that can be used for compaction guard instead of string comparison
204
- const soulPath = createTempSoulFile('# SOUL');
205
- try {
206
- const state = { soulSent: false };
207
- const r = simulateIteration(soulPath, state);
208
-
209
- // sendingSoul is a boolean derived from control flow
210
- expect(typeof r.sendingSoul).toBe('boolean');
211
- expect(r.sendingSoul).toBe(true); // first iteration sends SOUL
212
-
213
- // After SOUL sent, sendingSoul becomes false
214
- const r2 = simulateIteration(soulPath, state);
215
- expect(r2.sendingSoul).toBe(false);
216
- } finally {
217
- removeTempFile(soulPath);
218
- }
219
- });
220
-
221
- test('soulSent only flips after a successful send, not before', () => {
222
- // If the send fails (HTTP error, session busy), soulSent should remain false
223
- // so the next iteration retries sending SOUL content
224
- const soulPath = createTempSoulFile('# Critical SOUL');
225
- try {
226
- const state = { soulSent: false };
227
-
228
- // Iteration 1: read succeeds but send fails
229
- const r1 = simulateIteration(soulPath, state, { sendSuccess: false });
230
- expect(r1.error).toBeNull(); // File was read OK
231
- expect(r1.message).toBe('# Critical SOUL'); // SOUL content was prepared
232
- expect(r1.sendingSoul).toBe(true); // Was attempting SOUL delivery
233
- expect(r1.soulSent).toBe(false); // But send failed, so NOT marked as sent
234
-
235
- // Iteration 2: retry also fails
236
- const r2 = simulateIteration(soulPath, state, { sendSuccess: false });
237
- expect(r2.message).toBe('# Critical SOUL'); // Still trying to send SOUL
238
- expect(r2.soulSent).toBe(false); // Still not sent
239
-
240
- // Iteration 3: send succeeds
241
- const r3 = simulateIteration(soulPath, state, { sendSuccess: true });
242
- expect(r3.message).toBe('# Critical SOUL');
243
- expect(r3.soulSent).toBe(true); // NOW marked as sent
244
-
245
- // Iteration 4: sends "keep going"
246
- const r4 = simulateIteration(soulPath, state);
247
- expect(r4.message).toBe('keep going');
248
- expect(r4.sendingSoul).toBe(false);
249
- } finally {
250
- removeTempFile(soulPath);
251
- }
252
- });
253
- });
254
-
255
- describe('validateSoulFile (lib/soul.js)', () => {
256
- test('accepts a valid readable file', () => {
257
- const soulPath = createTempSoulFile('# Valid SOUL');
258
- try {
259
- const result = validateSoulFile(soulPath);
260
- expect(result.valid).toBe(true);
261
- expect(result.error).toBeUndefined();
262
- } finally {
263
- removeTempFile(soulPath);
264
- }
265
- });
266
-
267
- test('rejects a non-existent file with ENOENT in error', () => {
268
- const fakePath = path.join(os.tmpdir(), `nonexistent-soul-${Date.now()}.md`);
269
- const result = validateSoulFile(fakePath);
270
- expect(result.valid).toBe(false);
271
- expect(result.error).toContain('ENOENT');
272
- expect(result.error).toContain(fakePath);
273
- });
274
-
275
- test('rejects a directory', () => {
276
- const result = validateSoulFile(os.tmpdir());
277
- expect(result.valid).toBe(false);
278
- expect(result.error).toContain('not a regular file');
279
- expect(result.error).toContain(os.tmpdir());
280
- });
281
- });