@pushpalsdev/cli 1.0.99 → 1.1.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/package.json
CHANGED
|
@@ -16,6 +16,7 @@ Execution rules:
|
|
|
16
16
|
- If the hinted file is a thin wrapper or the behavior lives elsewhere, edit the behavior-owning file(s) needed to solve the task and explain the scope expansion in your final response.
|
|
17
17
|
- Avoid irrelevant sprawl; the review agent will judge whether changed files are necessary for the requested outcome.
|
|
18
18
|
- Read relevant files before editing, then run focused validation.
|
|
19
|
+
- PushPals runs the deterministic ValidationGate after your edit, including any repo-required `vision.md` commands. During the editing turn, prefer focused/fast validation. Do not spend the main Codex execution budget repeatedly running long browser/e2e smoke commands such as `bun run web:e2e`; run them only when the task is specifically about the browser harness or when you need a final targeted confirmation and can stop promptly on a clear failure.
|
|
19
20
|
- Use direct commands without shell wrappers. Prefer plain commands like `git diff -- path`, `git add <path>`, `git status --porcelain`, and `pwd`.
|
|
20
21
|
- Do not wrap commands in `/bin/bash -lc`, `sh -lc`, `cmd /c`, or `powershell -Command`, and avoid pipelines, `awk`, heredocs, or multi-command shell snippets unless they are truly unavoidable.
|
|
21
22
|
- If the command router rejects a command, simplify it to a single direct command instead of retrying more shell wrappers.
|
|
@@ -647,7 +647,10 @@ async function terminateValidationProcessTree(
|
|
|
647
647
|
}
|
|
648
648
|
}
|
|
649
649
|
|
|
650
|
-
function captureValidationStream(
|
|
650
|
+
function captureValidationStream(
|
|
651
|
+
stream: ReadableStream<Uint8Array> | null,
|
|
652
|
+
onChunk?: (chunk: string) => void,
|
|
653
|
+
) {
|
|
651
654
|
let text = "";
|
|
652
655
|
let done = false;
|
|
653
656
|
const reader = stream?.getReader();
|
|
@@ -657,7 +660,9 @@ function captureValidationStream(stream: ReadableStream<Uint8Array> | null) {
|
|
|
657
660
|
while (true) {
|
|
658
661
|
const result = await reader.read();
|
|
659
662
|
if (result.done) break;
|
|
660
|
-
|
|
663
|
+
const chunk = Buffer.from(result.value).toString("utf8");
|
|
664
|
+
text += chunk;
|
|
665
|
+
onChunk?.(chunk);
|
|
661
666
|
}
|
|
662
667
|
} catch {
|
|
663
668
|
// Stream cancellation after process exit is expected when descendants
|
|
@@ -689,6 +694,41 @@ function captureValidationStream(stream: ReadableStream<Uint8Array> | null) {
|
|
|
689
694
|
};
|
|
690
695
|
}
|
|
691
696
|
|
|
697
|
+
const DEFAULT_BROWSER_VALIDATION_FAILURE_IDLE_MS = 15_000;
|
|
698
|
+
|
|
699
|
+
function browserValidationFailureIdleMs(env: Record<string, string>): number {
|
|
700
|
+
const configured = Number(env.PUSHPALS_VALIDATION_FAILURE_IDLE_MS ?? "");
|
|
701
|
+
if (Number.isFinite(configured) && configured >= 250) {
|
|
702
|
+
return Math.min(120_000, Math.trunc(configured));
|
|
703
|
+
}
|
|
704
|
+
return DEFAULT_BROWSER_VALIDATION_FAILURE_IDLE_MS;
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
function hasBrowserValidationFailureSignal(output: string): boolean {
|
|
708
|
+
const text = String(output ?? "");
|
|
709
|
+
if (!text.trim()) return false;
|
|
710
|
+
const patterns = [
|
|
711
|
+
/\bAssertionError\b/i,
|
|
712
|
+
/\bTimeoutError\b/i,
|
|
713
|
+
/\bWeb end-to-end smoke test failed:/i,
|
|
714
|
+
/\bexpect\([^)]*\)\.[a-z0-9_]+\([^)]*\)\s+failed/i,
|
|
715
|
+
/\bError:\s+expect\(/i,
|
|
716
|
+
/\blocator\.[a-z0-9_]+:\s+Timeout\s+\d+ms\s+exceeded\b/i,
|
|
717
|
+
/\bpage\.[a-z0-9_]+:\s+Timeout\s+\d+ms\s+exceeded\b/i,
|
|
718
|
+
/\bTimeout\s+\d+ms\s+exceeded\b/i,
|
|
719
|
+
/\bTest timeout of \d+ms exceeded\b/i,
|
|
720
|
+
/\bCall log:\s*(?:\r?\n|$)/i,
|
|
721
|
+
/\bwaiting for getBy(?:TestId|Role|Text|Label|Placeholder|Title)\([^)]*\)/i,
|
|
722
|
+
/\bpage\.[a-z0-9_]+:\s+net::ERR_[A-Z0-9_]+/i,
|
|
723
|
+
/\bbrowserType\.launch:/i,
|
|
724
|
+
/\bERR_SOCKET_BAD_PORT\b/i,
|
|
725
|
+
/\blisten\s+EPERM\b/i,
|
|
726
|
+
/\bEADDRINUSE\b/i,
|
|
727
|
+
/\berror:\s+script\s+"[^"]+"\s+exited with code\s+\d+/i,
|
|
728
|
+
];
|
|
729
|
+
return patterns.some((pattern) => pattern.test(text));
|
|
730
|
+
}
|
|
731
|
+
|
|
692
732
|
export async function runValidationArgv(
|
|
693
733
|
repo: string,
|
|
694
734
|
command: string,
|
|
@@ -707,26 +747,62 @@ export async function runValidationArgv(
|
|
|
707
747
|
stderr: "pipe",
|
|
708
748
|
detached: process.platform !== "win32",
|
|
709
749
|
});
|
|
710
|
-
|
|
711
|
-
const
|
|
750
|
+
let lastOutputAt = Date.now();
|
|
751
|
+
const noteOutput = () => {
|
|
752
|
+
lastOutputAt = Date.now();
|
|
753
|
+
};
|
|
754
|
+
const stdoutCapture = captureValidationStream(proc.stdout, noteOutput);
|
|
755
|
+
const stderrCapture = captureValidationStream(proc.stderr, noteOutput);
|
|
712
756
|
let timedOut = false;
|
|
757
|
+
let stoppedAfterFailureSignal = false;
|
|
713
758
|
const timeout = Math.max(1_000, timeoutMs);
|
|
714
759
|
let timeoutTimer: ReturnType<typeof setTimeout> | null = null;
|
|
715
|
-
const timeoutPromise = new Promise<"timeout">((resolveTimeout) => {
|
|
760
|
+
const timeoutPromise = new Promise<{ type: "timeout" }>((resolveTimeout) => {
|
|
716
761
|
timeoutTimer = setTimeout(() => {
|
|
717
762
|
timedOut = true;
|
|
718
|
-
|
|
719
|
-
resolveTimeout("timeout");
|
|
763
|
+
resolveTimeout({ type: "timeout" });
|
|
720
764
|
}, timeout);
|
|
721
765
|
});
|
|
766
|
+
|
|
767
|
+
let failureSignalTimer: ReturnType<typeof setInterval> | null = null;
|
|
768
|
+
const failureSignalPromise = isLongRunningBrowserValidationCommand(command)
|
|
769
|
+
? new Promise<{ type: "failure-signal" }>((resolveFailureSignal) => {
|
|
770
|
+
const idleMs = browserValidationFailureIdleMs(env);
|
|
771
|
+
failureSignalTimer = setInterval(() => {
|
|
772
|
+
const combinedOutput = `${stdoutCapture.text()}\n${stderrCapture.text()}`;
|
|
773
|
+
if (
|
|
774
|
+
hasBrowserValidationFailureSignal(combinedOutput) &&
|
|
775
|
+
Date.now() - lastOutputAt >= idleMs
|
|
776
|
+
) {
|
|
777
|
+
stoppedAfterFailureSignal = true;
|
|
778
|
+
resolveFailureSignal({ type: "failure-signal" });
|
|
779
|
+
}
|
|
780
|
+
}, 250);
|
|
781
|
+
})
|
|
782
|
+
: new Promise<never>(() => {
|
|
783
|
+
// Non-browser validations should only end on process exit or timeout.
|
|
784
|
+
});
|
|
785
|
+
|
|
722
786
|
const exitOrTimeout = await Promise.race([
|
|
723
787
|
proc.exited.then((code) => ({ type: "exit" as const, code })),
|
|
724
788
|
timeoutPromise,
|
|
789
|
+
failureSignalPromise,
|
|
725
790
|
]);
|
|
726
791
|
if (timeoutTimer) clearTimeout(timeoutTimer);
|
|
727
|
-
|
|
792
|
+
if (failureSignalTimer) clearInterval(failureSignalTimer);
|
|
793
|
+
|
|
794
|
+
if (timedOut || stoppedAfterFailureSignal) {
|
|
795
|
+
await terminateValidationProcessTree(proc);
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
const exitCode =
|
|
799
|
+
exitOrTimeout.type === "timeout"
|
|
800
|
+
? 124
|
|
801
|
+
: exitOrTimeout.type === "failure-signal"
|
|
802
|
+
? 1
|
|
803
|
+
: exitOrTimeout.code;
|
|
728
804
|
|
|
729
|
-
if (!timedOut) {
|
|
805
|
+
if (!timedOut && !stoppedAfterFailureSignal) {
|
|
730
806
|
await Promise.race([
|
|
731
807
|
Promise.all([stdoutCapture.promise, stderrCapture.promise]),
|
|
732
808
|
Bun.sleep(1_000),
|
|
@@ -754,6 +830,9 @@ export async function runValidationArgv(
|
|
|
754
830
|
[
|
|
755
831
|
stderrCapture.text().trim(),
|
|
756
832
|
timedOut ? timeoutMessage : "",
|
|
833
|
+
stoppedAfterFailureSignal
|
|
834
|
+
? `Validation command emitted a browser/e2e failure signal and then produced no output for ${browserValidationFailureIdleMs(env)}ms. PushPals terminated the leaked process tree and preserved the captured failure output for repair.`
|
|
835
|
+
: "",
|
|
757
836
|
]
|
|
758
837
|
.filter(Boolean)
|
|
759
838
|
.join("\n"),
|
|
@@ -1474,6 +1553,12 @@ export function extractValidationFailureDigest(run: {
|
|
|
1474
1553
|
/\bCould not resolve\s+['"`]?[^'"`\r\n]+['"`]?[^\r\n]*/i,
|
|
1475
1554
|
/\bModule not found[^\r\n]*/i,
|
|
1476
1555
|
/\bbrowserType\.launch:[^\r\n]*/i,
|
|
1556
|
+
/\bWeb end-to-end smoke test failed:[^\r\n]*/i,
|
|
1557
|
+
/\blocator\.[a-z0-9_]+:\s+Timeout\s+\d+ms\s+exceeded[^\r\n]*/i,
|
|
1558
|
+
/\bpage\.[a-z0-9_]+:\s+Timeout\s+\d+ms\s+exceeded[^\r\n]*/i,
|
|
1559
|
+
/\bTimeout\s+\d+ms\s+exceeded[^\r\n]*/i,
|
|
1560
|
+
/\bwaiting for getBy(?:TestId|Role|Text|Label|Placeholder|Title)\([^)]*\)[^\r\n]*/i,
|
|
1561
|
+
/\bpage\.[a-z0-9_]+:\s+net::ERR_[A-Z0-9_]+[^\r\n]*/i,
|
|
1477
1562
|
/\bExecutable doesn't exist[^\r\n]*/i,
|
|
1478
1563
|
/\bPlease run the following command to download new browsers:[^\r\n]*(?:\r?\n\s+[^\r\n]+)?/i,
|
|
1479
1564
|
/\bRun ["`]?npx playwright install[^'"`\r\n]*["`]?[^\r\n]*/i,
|
|
@@ -16,6 +16,7 @@ Execution rules:
|
|
|
16
16
|
- If the hinted file is a thin wrapper or the behavior lives elsewhere, edit the behavior-owning file(s) needed to solve the task and explain the scope expansion in your final response.
|
|
17
17
|
- Avoid irrelevant sprawl; the review agent will judge whether changed files are necessary for the requested outcome.
|
|
18
18
|
- Read relevant files before editing, then run focused validation.
|
|
19
|
+
- PushPals runs the deterministic ValidationGate after your edit, including any repo-required `vision.md` commands. During the editing turn, prefer focused/fast validation. Do not spend the main Codex execution budget repeatedly running long browser/e2e smoke commands such as `bun run web:e2e`; run them only when the task is specifically about the browser harness or when you need a final targeted confirmation and can stop promptly on a clear failure.
|
|
19
20
|
- Use direct commands without shell wrappers. Prefer plain commands like `git diff -- path`, `git add <path>`, `git status --porcelain`, and `pwd`.
|
|
20
21
|
- Do not wrap commands in `/bin/bash -lc`, `sh -lc`, `cmd /c`, or `powershell -Command`, and avoid pipelines, `awk`, heredocs, or multi-command shell snippets unless they are truly unavoidable.
|
|
21
22
|
- If the command router rejects a command, simplify it to a single direct command instead of retrying more shell wrappers.
|