@lnilluv/pi-ralph-loop 1.0.0 → 1.2.0
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/README.md +3 -0
- package/package.json +1 -1
- package/src/index.ts +336 -1
- package/src/ralph.ts +8 -1
- package/src/runner-rpc.ts +36 -1
- package/src/runner-state.ts +20 -3
- package/src/runner.ts +109 -24
- package/tests/index.test.ts +460 -4
- package/tests/ralph.test.ts +24 -0
- package/tests/runner-event-contract.test.ts +1 -1
- package/tests/runner-rpc.test.ts +89 -1
- package/tests/runner-state.test.ts +28 -0
- package/tests/runner.test.ts +206 -1
package/tests/ralph.test.ts
CHANGED
|
@@ -121,6 +121,7 @@ test("parseRalphMarkdown parses frontmatter and normalizes line endings", () =>
|
|
|
121
121
|
timeout: 12.5,
|
|
122
122
|
completionPromise: "done",
|
|
123
123
|
requiredOutputs: ["docs/ARCHITECTURE.md"],
|
|
124
|
+
stopOnError: true,
|
|
124
125
|
guardrails: { blockCommands: ["rm .*"], protectedFiles: ["src/**"] },
|
|
125
126
|
invalidCommandEntries: undefined,
|
|
126
127
|
});
|
|
@@ -136,6 +137,21 @@ test("parseRalphMarkdown parses declared args as runtime parameters", () => {
|
|
|
136
137
|
assert.equal(validateFrontmatter(parsed.frontmatter), null);
|
|
137
138
|
});
|
|
138
139
|
|
|
140
|
+
test("parseRalphMarkdown parses stop_on_error from frontmatter", () => {
|
|
141
|
+
const parsed = parseRalphMarkdown("---\nstop_on_error: false\nmax_iterations: 5\ntimeout: 60\ncommands: []\nguardrails: { block_commands: [], protected_files: [] }\n---\nTask\n");
|
|
142
|
+
assert.equal(parsed.frontmatter.stopOnError, false);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
test("parseRalphMarkdown defaults stop_on_error to true", () => {
|
|
146
|
+
const parsed = parseRalphMarkdown("---\nmax_iterations: 5\ntimeout: 60\ncommands: []\nguardrails: { block_commands: [], protected_files: [] }\n---\nTask\n");
|
|
147
|
+
assert.equal(parsed.frontmatter.stopOnError, true);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
test("parseRalphMarkdown treats non-false stop_on_error as true (safe default)", () => {
|
|
151
|
+
const parsed = parseRalphMarkdown("---\nstop_on_error: yes\nmax_iterations: 5\ntimeout: 60\ncommands: []\nguardrails: { block_commands: [], protected_files: [] }\n---\nTask\n");
|
|
152
|
+
assert.equal(parsed.frontmatter.stopOnError, true);
|
|
153
|
+
});
|
|
154
|
+
|
|
139
155
|
test("validateFrontmatter accepts valid input and rejects invalid bounds, names, args, and globs", () => {
|
|
140
156
|
assert.equal(validateFrontmatter(defaultFrontmatter()), null);
|
|
141
157
|
assert.equal(
|
|
@@ -244,6 +260,13 @@ test("validateFrontmatter accepts valid input and rejects invalid bounds, names,
|
|
|
244
260
|
);
|
|
245
261
|
});
|
|
246
262
|
|
|
263
|
+
test("validateFrontmatter accepts stop_on_error true and false", () => {
|
|
264
|
+
const fmTrue = { ...defaultFrontmatter(), stopOnError: true };
|
|
265
|
+
const fmFalse = { ...defaultFrontmatter(), stopOnError: false };
|
|
266
|
+
assert.equal(validateFrontmatter(fmTrue), null);
|
|
267
|
+
assert.equal(validateFrontmatter(fmFalse), null);
|
|
268
|
+
});
|
|
269
|
+
|
|
247
270
|
test("validateFrontmatter rejects unsafe completion_promise values and Mission Brief fails closed", () => {
|
|
248
271
|
assert.equal(
|
|
249
272
|
validateFrontmatter({ ...defaultFrontmatter(), completionPromise: "ready\nnow" }),
|
|
@@ -1010,6 +1033,7 @@ test("generated drafts reparse as valid RALPH files", () => {
|
|
|
1010
1033
|
timeout: 300,
|
|
1011
1034
|
completionPromise: undefined,
|
|
1012
1035
|
requiredOutputs: [],
|
|
1036
|
+
stopOnError: true,
|
|
1013
1037
|
guardrails: { blockCommands: ["git\\s+push"], protectedFiles: [] },
|
|
1014
1038
|
invalidCommandEntries: undefined,
|
|
1015
1039
|
});
|
|
@@ -36,7 +36,7 @@ type ExpectedRunnerEvent =
|
|
|
36
36
|
timestamp: string;
|
|
37
37
|
iteration: number;
|
|
38
38
|
loopToken: string;
|
|
39
|
-
status: "complete" | "timeout" | "error";
|
|
39
|
+
status: "complete" | "timeout" | "error" | "cancelled";
|
|
40
40
|
progress: ProgressState;
|
|
41
41
|
changedFiles: string[];
|
|
42
42
|
noProgressStreak: number;
|
package/tests/runner-rpc.test.ts
CHANGED
|
@@ -355,4 +355,92 @@ echo '{"type":"agent_end","messages":[{"role":"assistant","content":[{"type":"te
|
|
|
355
355
|
} finally {
|
|
356
356
|
rmSync(cwd, { recursive: true, force: true });
|
|
357
357
|
}
|
|
358
|
-
});
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
test("runRpcIteration cancels on AbortSignal and returns cancelled=true", async () => {
|
|
362
|
+
const taskDir = mkdtempSync(join(tmpdir(), "pi-ralph-rpc-"));
|
|
363
|
+
try {
|
|
364
|
+
const scriptPath = join(taskDir, "slow-pi.sh");
|
|
365
|
+
writeFileSync(
|
|
366
|
+
scriptPath,
|
|
367
|
+
`#!/bin/bash
|
|
368
|
+
read line
|
|
369
|
+
echo '{"type":"response","command":"prompt","success":true}'
|
|
370
|
+
sleep 30
|
|
371
|
+
echo '{"type":"agent_end","messages":[{"role":"assistant","content":[{"type":"text","text":"done"}]}]}'
|
|
372
|
+
`,
|
|
373
|
+
{ mode: 0o755 },
|
|
374
|
+
);
|
|
375
|
+
|
|
376
|
+
const controller = new AbortController();
|
|
377
|
+
setTimeout(() => controller.abort(), 500);
|
|
378
|
+
|
|
379
|
+
const result = await runRpcIteration({
|
|
380
|
+
prompt: "do something",
|
|
381
|
+
cwd: taskDir,
|
|
382
|
+
timeoutMs: 60_000,
|
|
383
|
+
spawnCommand: "bash",
|
|
384
|
+
spawnArgs: [scriptPath],
|
|
385
|
+
signal: controller.signal,
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
assert.equal(result.cancelled, true);
|
|
389
|
+
assert.equal(result.success, false);
|
|
390
|
+
assert.equal(result.timedOut, false);
|
|
391
|
+
assert.equal(result.error, "cancelled");
|
|
392
|
+
} finally {
|
|
393
|
+
rmSync(taskDir, { recursive: true, force: true });
|
|
394
|
+
}
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
test("runRpcIteration returns immediately if AbortSignal is already aborted", async () => {
|
|
398
|
+
const taskDir = mkdtempSync(join(tmpdir(), "pi-ralph-rpc-"));
|
|
399
|
+
try {
|
|
400
|
+
const controller = new AbortController();
|
|
401
|
+
controller.abort();
|
|
402
|
+
|
|
403
|
+
const result = await runRpcIteration({
|
|
404
|
+
prompt: "do something",
|
|
405
|
+
cwd: taskDir,
|
|
406
|
+
timeoutMs: 5_000,
|
|
407
|
+
spawnCommand: "echo",
|
|
408
|
+
spawnArgs: ["mock"],
|
|
409
|
+
signal: controller.signal,
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
assert.equal(result.cancelled, true);
|
|
413
|
+
assert.equal(result.success, false);
|
|
414
|
+
} finally {
|
|
415
|
+
rmSync(taskDir, { recursive: true, force: true });
|
|
416
|
+
}
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
test("runRpcIteration completes normally without AbortSignal", async () => {
|
|
420
|
+
const taskDir = mkdtempSync(join(tmpdir(), "pi-ralph-rpc-"));
|
|
421
|
+
try {
|
|
422
|
+
const scriptPath = join(taskDir, "fast-pi.sh");
|
|
423
|
+
writeFileSync(
|
|
424
|
+
scriptPath,
|
|
425
|
+
`#!/bin/bash
|
|
426
|
+
read line
|
|
427
|
+
echo '{"type":"response","command":"prompt","success":true}'
|
|
428
|
+
echo '{"type":"agent_end","messages":[{"role":"assistant","content":[{"type":"text","text":"done"}]}]}'
|
|
429
|
+
`,
|
|
430
|
+
{ mode: 0o755 },
|
|
431
|
+
);
|
|
432
|
+
|
|
433
|
+
const result = await runRpcIteration({
|
|
434
|
+
prompt: "do something",
|
|
435
|
+
cwd: taskDir,
|
|
436
|
+
timeoutMs: 5_000,
|
|
437
|
+
spawnCommand: "bash",
|
|
438
|
+
spawnArgs: [scriptPath],
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
assert.equal(result.success, true);
|
|
442
|
+
assert.equal(result.cancelled, undefined);
|
|
443
|
+
} finally {
|
|
444
|
+
rmSync(taskDir, { recursive: true, force: true });
|
|
445
|
+
}
|
|
446
|
+
});
|
|
@@ -11,9 +11,12 @@ import {
|
|
|
11
11
|
type RunnerStatusFile,
|
|
12
12
|
appendIterationRecord,
|
|
13
13
|
appendRunnerEvent,
|
|
14
|
+
checkCancelSignal,
|
|
14
15
|
checkStopSignal,
|
|
16
|
+
clearCancelSignal,
|
|
15
17
|
clearRunnerDir,
|
|
16
18
|
clearStopSignal,
|
|
19
|
+
createCancelSignal,
|
|
17
20
|
createStopSignal,
|
|
18
21
|
ensureRunnerDir,
|
|
19
22
|
listActiveLoopRegistryEntries,
|
|
@@ -288,6 +291,31 @@ test("clearStopSignal is idempotent when no signal exists", () => {
|
|
|
288
291
|
}
|
|
289
292
|
});
|
|
290
293
|
|
|
294
|
+
test("createCancelSignal writes cancel.flag and checkCancelSignal detects it", () => {
|
|
295
|
+
const taskDir = createTempDir();
|
|
296
|
+
try {
|
|
297
|
+
ensureRunnerDir(taskDir);
|
|
298
|
+
assert.equal(checkCancelSignal(taskDir), false);
|
|
299
|
+
createCancelSignal(taskDir);
|
|
300
|
+
assert.equal(checkCancelSignal(taskDir), true);
|
|
301
|
+
clearCancelSignal(taskDir);
|
|
302
|
+
assert.equal(checkCancelSignal(taskDir), false);
|
|
303
|
+
} finally {
|
|
304
|
+
rmSync(taskDir, { recursive: true, force: true });
|
|
305
|
+
}
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
test("clearCancelSignal is safe when cancel.flag does not exist", () => {
|
|
309
|
+
const taskDir = createTempDir();
|
|
310
|
+
try {
|
|
311
|
+
ensureRunnerDir(taskDir);
|
|
312
|
+
clearCancelSignal(taskDir); // should not throw
|
|
313
|
+
assert.equal(checkCancelSignal(taskDir), false);
|
|
314
|
+
} finally {
|
|
315
|
+
rmSync(taskDir, { recursive: true, force: true });
|
|
316
|
+
}
|
|
317
|
+
});
|
|
318
|
+
|
|
291
319
|
// --- clearRunnerDir ---
|
|
292
320
|
|
|
293
321
|
test("clearRunnerDir removes .ralph-runner directory", () => {
|
package/tests/runner.test.ts
CHANGED
|
@@ -5,7 +5,7 @@ import { tmpdir } from "node:os";
|
|
|
5
5
|
import test from "node:test";
|
|
6
6
|
|
|
7
7
|
import { assessTaskDirectoryProgress, captureTaskDirectorySnapshot, runRalphLoop, validateCompletionReadiness } from "../src/runner.ts";
|
|
8
|
-
import { readStatusFile, readIterationRecords, readRunnerEvents, checkStopSignal, createStopSignal as createStopSignalFn, type RunnerEvent } from "../src/runner-state.ts";
|
|
8
|
+
import { readStatusFile, readIterationRecords, readRunnerEvents, checkStopSignal, createCancelSignal, createStopSignal as createStopSignalFn, type RunnerEvent } from "../src/runner-state.ts";
|
|
9
9
|
import { generateDraft } from "../src/ralph.ts";
|
|
10
10
|
import type { DraftTarget, CommandOutput, CommandDef } from "../src/ralph.ts";
|
|
11
11
|
|
|
@@ -339,6 +339,86 @@ echo '{"type":"agent_end","messages":[{"role":"assistant","content":[{"type":"te
|
|
|
339
339
|
}
|
|
340
340
|
});
|
|
341
341
|
|
|
342
|
+
test("runRalphLoop cancels mid-iteration when cancel flag is written", async () => {
|
|
343
|
+
const taskDir = createTempDir();
|
|
344
|
+
try {
|
|
345
|
+
const ralphPath = writeRalphMd(taskDir, minimalRalphMd({ max_iterations: 3 }));
|
|
346
|
+
|
|
347
|
+
const scriptPath = join(taskDir, "slow-pi.sh");
|
|
348
|
+
writeFileSync(
|
|
349
|
+
scriptPath,
|
|
350
|
+
`#!/bin/bash
|
|
351
|
+
read line
|
|
352
|
+
echo '{"type":"response","command":"prompt","success":true}'
|
|
353
|
+
sleep 10
|
|
354
|
+
echo '{"type":"agent_end","messages":[{"role":"assistant","content":[{"type":"text","text":"done"}]}]}'
|
|
355
|
+
`,
|
|
356
|
+
{ mode: 0o755 },
|
|
357
|
+
);
|
|
358
|
+
|
|
359
|
+
setTimeout(() => createCancelSignal(taskDir), 1000);
|
|
360
|
+
|
|
361
|
+
const result = await runRalphLoop({
|
|
362
|
+
ralphPath,
|
|
363
|
+
cwd: taskDir,
|
|
364
|
+
timeout: 30,
|
|
365
|
+
maxIterations: 3,
|
|
366
|
+
guardrails: { blockCommands: [], protectedFiles: [] },
|
|
367
|
+
spawnCommand: "bash",
|
|
368
|
+
spawnArgs: [scriptPath],
|
|
369
|
+
runCommandsFn: async () => [],
|
|
370
|
+
pi: makeMockPi(),
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
assert.equal(result.status, "cancelled");
|
|
374
|
+
assert.ok(result.iterations.length >= 1);
|
|
375
|
+
assert.equal(result.iterations[result.iterations.length - 1].status, "cancelled");
|
|
376
|
+
} finally {
|
|
377
|
+
rmSync(taskDir, { recursive: true, force: true });
|
|
378
|
+
}
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
test("runRalphLoop checks cancel flag at iteration boundary", async () => {
|
|
382
|
+
const taskDir = createTempDir();
|
|
383
|
+
try {
|
|
384
|
+
const ralphPath = writeRalphMd(taskDir, minimalRalphMd({ max_iterations: 3 }));
|
|
385
|
+
|
|
386
|
+
const scriptPath = join(taskDir, "mock-pi.sh");
|
|
387
|
+
writeFileSync(
|
|
388
|
+
scriptPath,
|
|
389
|
+
`#!/bin/bash
|
|
390
|
+
read line
|
|
391
|
+
echo '{"type":"response","command":"prompt","success":true}'
|
|
392
|
+
echo '{"type":"agent_end","messages":[{"role":"assistant","content":[{"type":"text","text":"done"}]}]}'
|
|
393
|
+
`,
|
|
394
|
+
{ mode: 0o755 },
|
|
395
|
+
);
|
|
396
|
+
|
|
397
|
+
let iterationCount = 0;
|
|
398
|
+
const result = await runRalphLoop({
|
|
399
|
+
ralphPath,
|
|
400
|
+
cwd: taskDir,
|
|
401
|
+
timeout: 5,
|
|
402
|
+
maxIterations: 3,
|
|
403
|
+
guardrails: { blockCommands: [], protectedFiles: [] },
|
|
404
|
+
spawnCommand: "bash",
|
|
405
|
+
spawnArgs: [scriptPath],
|
|
406
|
+
onIterationComplete() {
|
|
407
|
+
iterationCount++;
|
|
408
|
+
if (iterationCount >= 1) {
|
|
409
|
+
createCancelSignal(taskDir);
|
|
410
|
+
}
|
|
411
|
+
},
|
|
412
|
+
runCommandsFn: async () => [],
|
|
413
|
+
pi: makeMockPi(),
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
assert.equal(result.status, "cancelled");
|
|
417
|
+
} finally {
|
|
418
|
+
rmSync(taskDir, { recursive: true, force: true });
|
|
419
|
+
}
|
|
420
|
+
});
|
|
421
|
+
|
|
342
422
|
test("runRalphLoop waits between iterations when inter_iteration_delay is set", async () => {
|
|
343
423
|
const taskDir = createTempDir();
|
|
344
424
|
try {
|
|
@@ -1345,3 +1425,128 @@ echo '{"type":"agent_end","messages":[{"role":"assistant","content":[{"type":"te
|
|
|
1345
1425
|
rmSync(taskDir, { recursive: true, force: true });
|
|
1346
1426
|
}
|
|
1347
1427
|
});
|
|
1428
|
+
|
|
1429
|
+
test("runRalphLoop stops on error when stopOnError is true (default)", async () => {
|
|
1430
|
+
const taskDir = createTempDir();
|
|
1431
|
+
try {
|
|
1432
|
+
const ralphPath = writeRalphMd(taskDir, minimalRalphMd({ max_iterations: 3 }));
|
|
1433
|
+
|
|
1434
|
+
const scriptPath = join(taskDir, "failing-pi.sh");
|
|
1435
|
+
writeFileSync(
|
|
1436
|
+
scriptPath,
|
|
1437
|
+
`#!/bin/bash
|
|
1438
|
+
read line
|
|
1439
|
+
echo '{"type":"response","command":"prompt","success":true}'
|
|
1440
|
+
exit 1
|
|
1441
|
+
`,
|
|
1442
|
+
{ mode: 0o755 },
|
|
1443
|
+
);
|
|
1444
|
+
|
|
1445
|
+
const result = await runRalphLoop({
|
|
1446
|
+
ralphPath,
|
|
1447
|
+
cwd: taskDir,
|
|
1448
|
+
timeout: 5,
|
|
1449
|
+
maxIterations: 3,
|
|
1450
|
+
stopOnError: true,
|
|
1451
|
+
guardrails: { blockCommands: [], protectedFiles: [] },
|
|
1452
|
+
spawnCommand: "bash",
|
|
1453
|
+
spawnArgs: [scriptPath],
|
|
1454
|
+
runCommandsFn: async () => [],
|
|
1455
|
+
pi: makeMockPi(),
|
|
1456
|
+
});
|
|
1457
|
+
|
|
1458
|
+
assert.equal(result.status, "error");
|
|
1459
|
+
assert.equal(result.iterations.length, 1);
|
|
1460
|
+
} finally {
|
|
1461
|
+
rmSync(taskDir, { recursive: true, force: true });
|
|
1462
|
+
}
|
|
1463
|
+
});
|
|
1464
|
+
|
|
1465
|
+
test("runRalphLoop continues past error when stopOnError is false", async () => {
|
|
1466
|
+
const taskDir = createTempDir();
|
|
1467
|
+
try {
|
|
1468
|
+
const ralphPath = writeRalphMd(taskDir, minimalRalphMd({ max_iterations: 3 }));
|
|
1469
|
+
|
|
1470
|
+
const scriptPath = join(taskDir, "maybe-fail-pi.sh");
|
|
1471
|
+
writeFileSync(
|
|
1472
|
+
scriptPath,
|
|
1473
|
+
`#!/bin/bash
|
|
1474
|
+
read line
|
|
1475
|
+
COUNTER_FILE="${taskDir}/.call-counter"
|
|
1476
|
+
COUNT=0
|
|
1477
|
+
if [ -f "$COUNTER_FILE" ]; then
|
|
1478
|
+
COUNT=$(cat "$COUNTER_FILE")
|
|
1479
|
+
fi
|
|
1480
|
+
COUNT=$((COUNT + 1))
|
|
1481
|
+
echo "$COUNT" > "$COUNTER_FILE"
|
|
1482
|
+
echo '{"type":"response","command":"prompt","success":true}'
|
|
1483
|
+
if [ "$COUNT" -le 1 ]; then
|
|
1484
|
+
exit 1
|
|
1485
|
+
fi
|
|
1486
|
+
echo '{"type":"agent_end","messages":[{"role":"assistant","content":[{"type":"text","text":"done"}]}]}'
|
|
1487
|
+
`,
|
|
1488
|
+
{ mode: 0o755 },
|
|
1489
|
+
);
|
|
1490
|
+
|
|
1491
|
+
const result = await runRalphLoop({
|
|
1492
|
+
ralphPath,
|
|
1493
|
+
cwd: taskDir,
|
|
1494
|
+
timeout: 5,
|
|
1495
|
+
maxIterations: 3,
|
|
1496
|
+
stopOnError: false,
|
|
1497
|
+
guardrails: { blockCommands: [], protectedFiles: [] },
|
|
1498
|
+
spawnCommand: "bash",
|
|
1499
|
+
spawnArgs: [scriptPath],
|
|
1500
|
+
runCommandsFn: async () => [],
|
|
1501
|
+
pi: makeMockPi(),
|
|
1502
|
+
});
|
|
1503
|
+
|
|
1504
|
+
assert.ok(result.iterations.length > 1, `Expected >1 iteration, got ${result.iterations.length}`);
|
|
1505
|
+
assert.equal(result.iterations[0].status, "error");
|
|
1506
|
+
} finally {
|
|
1507
|
+
rmSync(taskDir, { recursive: true, force: true });
|
|
1508
|
+
}
|
|
1509
|
+
});
|
|
1510
|
+
|
|
1511
|
+
test("runRalphLoop breaks on structural failure even with stopOnError false", async () => {
|
|
1512
|
+
const taskDir = createTempDir();
|
|
1513
|
+
try {
|
|
1514
|
+
const ralphPath = writeRalphMd(taskDir, minimalRalphMd({ max_iterations: 3, stop_on_error: false }));
|
|
1515
|
+
|
|
1516
|
+
const scriptPath = join(taskDir, "mock-pi.sh");
|
|
1517
|
+
writeFileSync(
|
|
1518
|
+
scriptPath,
|
|
1519
|
+
`#!/bin/bash
|
|
1520
|
+
read line
|
|
1521
|
+
echo '{"type":"response","command":"prompt","success":true}'
|
|
1522
|
+
echo '{"type":"agent_end","messages":[{"role":"assistant","content":[{"type":"text","text":"delete ralph"}]}]}'
|
|
1523
|
+
`,
|
|
1524
|
+
{ mode: 0o755 },
|
|
1525
|
+
);
|
|
1526
|
+
|
|
1527
|
+
let iterationCount = 0;
|
|
1528
|
+
const result = await runRalphLoop({
|
|
1529
|
+
ralphPath,
|
|
1530
|
+
cwd: taskDir,
|
|
1531
|
+
timeout: 5,
|
|
1532
|
+
maxIterations: 3,
|
|
1533
|
+
stopOnError: false,
|
|
1534
|
+
guardrails: { blockCommands: [], protectedFiles: [] },
|
|
1535
|
+
spawnCommand: "bash",
|
|
1536
|
+
spawnArgs: [scriptPath],
|
|
1537
|
+
onIterationComplete() {
|
|
1538
|
+
iterationCount++;
|
|
1539
|
+
if (iterationCount >= 1) {
|
|
1540
|
+
rmSync(ralphPath, { force: true });
|
|
1541
|
+
}
|
|
1542
|
+
},
|
|
1543
|
+
runCommandsFn: async () => [],
|
|
1544
|
+
pi: makeMockPi(),
|
|
1545
|
+
});
|
|
1546
|
+
|
|
1547
|
+
assert.equal(result.status, "error");
|
|
1548
|
+
} finally {
|
|
1549
|
+
rmSync(taskDir, { recursive: true, force: true });
|
|
1550
|
+
}
|
|
1551
|
+
});
|
|
1552
|
+
|