@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
package/test/soul-reread.test.js
DELETED
|
@@ -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
|
-
});
|