@pushpalsdev/cli 1.0.32 → 1.0.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/dist/pushpals-cli.js +4 -4
- package/monitor-ui/+not-found.html +1 -1
- package/monitor-ui/_expo/static/js/web/{entry-275c5f7972e2d2f4f0422fe2213a7f89.js → entry-5e6db7139bc13703a24f952bd64faf4c.js} +2 -2
- package/monitor-ui/_sitemap.html +1 -1
- package/monitor-ui/index.html +1 -1
- package/monitor-ui/modal.html +1 -1
- package/package.json +1 -1
- package/runtime/prompts/localbuddy/localbuddy_planner_git_diff_section.md +0 -1
- package/runtime/prompts/localbuddy/localbuddy_planner_git_status_section.md +0 -1
- package/runtime/prompts/localbuddy/localbuddy_planner_output_contract.md +1 -0
- package/runtime/prompts/remotebuddy/autonomy_ideation_system_prompt.md +31 -30
- package/runtime/prompts/remotebuddy/autonomy_scoring_system_prompt.md +2 -2
- package/runtime/prompts/remotebuddy/context_packer_user_prompt.md +1 -0
- package/runtime/prompts/remotebuddy/remotebuddy_system_prompt.md +1 -0
- package/runtime/prompts/review_agent/review_prompt_template.md +1 -0
- package/runtime/prompts/review_agent/reviewer.md +6 -4
- package/runtime/prompts/workerpals/commit_message_prompt.md +3 -0
- package/runtime/prompts/workerpals/miniswe_broker_system_prompt.md +10 -9
- package/runtime/prompts/workerpals/miniswe_strict_tool_use_guidance.md +1 -0
- package/runtime/prompts/workerpals/openai_codex_runtime_policy_appendix.md +1 -0
- package/runtime/prompts/workerpals/openai_codex_task_execute_system_prompt.md +2 -0
- package/runtime/prompts/workerpals/task_quality_critic_system_prompt.md +3 -2
- package/runtime/sandbox/apps/workerpals/src/backends/openai_codex_backend.ts +4 -4
- package/runtime/sandbox/apps/workerpals/src/backends/openhands_task_execute.ts +1 -4
- package/runtime/sandbox/apps/workerpals/src/common/execution_utils.ts +5 -3
- package/runtime/sandbox/apps/workerpals/src/common/generic_python_executor.ts +1 -4
- package/runtime/sandbox/apps/workerpals/src/common/worktree_cleanup.ts +3 -2
- package/runtime/sandbox/apps/workerpals/src/docker_executor.ts +132 -32
- package/runtime/sandbox/apps/workerpals/src/execute_job.ts +7 -7
- package/runtime/sandbox/apps/workerpals/src/job_runner.ts +7 -4
- package/runtime/sandbox/apps/workerpals/src/workerpals_main.ts +46 -27
- package/runtime/sandbox/packages/shared/src/autonomy_policy.ts +8 -3
- package/runtime/sandbox/packages/shared/src/communication.ts +19 -8
- package/runtime/sandbox/packages/shared/src/config.ts +9 -24
- package/runtime/sandbox/packages/shared/src/config_template_parity.ts +5 -6
- package/runtime/sandbox/packages/shared/src/git_backend.ts +5 -9
- package/runtime/sandbox/packages/shared/src/local_network.ts +3 -1
- package/runtime/sandbox/packages/shared/src/localbuddy_runtime.ts +4 -5
- package/runtime/sandbox/packages/shared/src/vision.ts +6 -2
- package/runtime/sandbox/prompts/workerpals/commit_message_prompt.md +3 -0
- package/runtime/sandbox/prompts/workerpals/miniswe_broker_system_prompt.md +10 -9
- package/runtime/sandbox/prompts/workerpals/miniswe_strict_tool_use_guidance.md +1 -0
- package/runtime/sandbox/prompts/workerpals/openai_codex_runtime_policy_appendix.md +1 -0
- package/runtime/sandbox/prompts/workerpals/openai_codex_task_execute_system_prompt.md +2 -0
- package/runtime/sandbox/prompts/workerpals/task_quality_critic_system_prompt.md +3 -2
- package/runtime/vision.example.md +24 -5
|
@@ -23,6 +23,7 @@ Intent taxonomy (choose the single best fit):
|
|
|
23
23
|
- `other` — do NOT use `other` with `requires_worker=false`. `other` must always have `requires_worker=true`. When in doubt, prefer `code_change`.
|
|
24
24
|
|
|
25
25
|
Classification rules (applied in order):
|
|
26
|
+
|
|
26
27
|
1. Action verb present (add, fix, update, implement, create, remove, test, run, build, configure, refactor, improve, etc.) → `code_change` + `requires_worker=true`
|
|
27
28
|
2. File/test/config/component reference + no explicit read-only ask → `code_change` + `requires_worker=true`
|
|
28
29
|
3. Read-only analysis explicitly requested → `analysis` + `requires_worker=false`
|
|
@@ -7,6 +7,7 @@ Return an objective quality score and specific, actionable feedback. The ReviewA
|
|
|
7
7
|
## Rating Criteria
|
|
8
8
|
|
|
9
9
|
### 9.0-10.0: Distinguished Engineer quality
|
|
10
|
+
|
|
10
11
|
- Code is correct, complete, and production-ready with zero known defects
|
|
11
12
|
- All edge cases and error paths are handled explicitly
|
|
12
13
|
- Tests cover both positive (happy path) and negative (failure/edge) cases with meaningful assertions
|
|
@@ -21,6 +22,7 @@ Return an objective quality score and specific, actionable feedback. The ReviewA
|
|
|
21
22
|
### 1.0-6.9: Not production-ready - list specific issues and remediation steps
|
|
22
23
|
|
|
23
24
|
Common rejection reasons:
|
|
25
|
+
|
|
24
26
|
- Missing negative test assertions
|
|
25
27
|
- Incomplete error handling
|
|
26
28
|
- Tests that pass trivially (no real assertions)
|
|
@@ -32,8 +34,8 @@ Common rejection reasons:
|
|
|
32
34
|
|
|
33
35
|
Respond with a JSON object only (no markdown wrapper):
|
|
34
36
|
{
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
37
|
+
"score": <number 1.0-10.0>,
|
|
38
|
+
"summary": "<one sentence verdict>",
|
|
39
|
+
"issues": ["<issue 1>", "<issue 2>", ...],
|
|
40
|
+
"fix_instruction": "<precise instruction for the worker to fix all issues - this will be sent directly to the WorkerPal as its task>"
|
|
39
41
|
}
|
|
@@ -10,6 +10,7 @@ Output only the raw commit message text — no markdown fences, no explanation,
|
|
|
10
10
|
- <specific implementation detail>
|
|
11
11
|
|
|
12
12
|
Tests:
|
|
13
|
+
|
|
13
14
|
- <test runner command>
|
|
14
15
|
|
|
15
16
|
## Writing rules
|
|
@@ -25,12 +26,14 @@ Background context: "can you add one more unit test for localbuddy"
|
|
|
25
26
|
|
|
26
27
|
Bad (copies instruction / uses planning language):
|
|
27
28
|
{{type}}({{area}}): lets add one more unit test for localbuddy
|
|
29
|
+
|
|
28
30
|
- At least one new unit test is added validating a meaningful LocalBuddy behavior.
|
|
29
31
|
- All existing and new tests pass.
|
|
30
32
|
- No unrelated files are modified.
|
|
31
33
|
|
|
32
34
|
Good (reads the diff):
|
|
33
35
|
{{type}}({{area}}): add unit test for LocalBuddy request routing and error response handling
|
|
36
|
+
|
|
34
37
|
- add test case in localbuddy.test.ts asserting router returns 404 for unknown tool calls
|
|
35
38
|
- add negative test for malformed request payload returning 400 with error message
|
|
36
39
|
- extract shared test fixtures into testHelpers.ts to reduce duplication
|
|
@@ -5,18 +5,19 @@ Repository root: {{repo}}
|
|
|
5
5
|
|
|
6
6
|
Output format (STRICT JSON, no markdown, no extra keys unless specified):
|
|
7
7
|
{
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
8
|
+
"actions": [
|
|
9
|
+
{"type":"read_file","path":"README.md"},
|
|
10
|
+
{"type":"append_line","path":"README.md","line":"..."},
|
|
11
|
+
{"type":"replace_text_once","path":"x","old":"a","new":"b"},
|
|
12
|
+
{"type":"write_file","path":"x","content":"..."},
|
|
13
|
+
{"type":"run_shell","command":"git status --porcelain"}
|
|
14
|
+
],
|
|
15
|
+
"done": false,
|
|
16
|
+
"note": "short explanation"
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
Rules:
|
|
20
|
+
|
|
20
21
|
- Keep actions minimal and directly relevant.
|
|
21
22
|
- JSON syntax must be exact: use ":" between keys and values, never ",".
|
|
22
23
|
- Use double quotes for all keys and string values.
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
CRITICAL: You must use tools to make progress.
|
|
2
|
+
|
|
2
3
|
- Use the environment's tools (file read/list/search, and file edit/write/patch) to inspect and modify the repo.
|
|
3
4
|
- Do NOT only describe what you would do; actually do it.
|
|
4
5
|
- Avoid broad scans; choose one target file quickly.
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
You are PushPals WorkerPal running via the OpenAI Codex CLI backend.
|
|
2
2
|
|
|
3
3
|
Non-negotiable runtime invariants:
|
|
4
|
+
|
|
4
5
|
- Codex CLI is required infrastructure in this environment.
|
|
5
6
|
- Do not modify tests or production code to bypass, stub, or remove Codex CLI usage due to assumed environment limitations.
|
|
6
7
|
- Do not "adapt around" missing Codex access by rewriting coverage or behavior expectations.
|
|
7
8
|
- If Codex CLI authentication/execution is unavailable, fail loudly with a clear error and stop.
|
|
8
9
|
|
|
9
10
|
Execution rules:
|
|
11
|
+
|
|
10
12
|
- Keep edits minimal, correct, and scoped to the requested task.
|
|
11
13
|
- Read relevant files before editing, then run focused validation.
|
|
12
14
|
- Report blockers explicitly; do not hide platform/runtime issues with workaround edits.
|
|
@@ -2,8 +2,9 @@ You are a strict code-review critic for worker-generated patches.
|
|
|
2
2
|
Return exactly one JSON object with keys:
|
|
3
3
|
{"score": <0-10 number>, "findings": [string], "must_fix": [string], "revision_guidance": string}
|
|
4
4
|
Scoring rubric:
|
|
5
|
+
|
|
5
6
|
- 10: complete, correct, and robust with strong validation coverage.
|
|
6
7
|
- 8-9: good quality with minor non-blocking issues.
|
|
7
8
|
- <=7: requires revision before commit.
|
|
8
|
-
must_fix must list blocking issues only.
|
|
9
|
-
Do not include markdown or prose outside JSON.
|
|
9
|
+
must_fix must list blocking issues only.
|
|
10
|
+
Do not include markdown or prose outside JSON.
|
|
@@ -27,14 +27,14 @@ function warmupProbeCommand(sharedVenvPython: string): string {
|
|
|
27
27
|
'AUTH_MODE="$(printf %s "$AUTH_MODE_RAW" | tr "[:upper:]" "[:lower:]")"; ' +
|
|
28
28
|
'if [ ! -x "$PY" ]; then PY="$(command -v python3 || command -v python || true)"; fi; ' +
|
|
29
29
|
'[ -n "$PY" ] || { echo "python runtime not found" >&2; exit 1; }; ' +
|
|
30
|
-
|
|
30
|
+
"if command -v bunx >/dev/null 2>&1; then " +
|
|
31
31
|
' CODEX_CMD="bunx --yes @openai/codex"; ' +
|
|
32
|
-
|
|
32
|
+
"elif command -v codex >/dev/null 2>&1; then " +
|
|
33
33
|
' CODEX_CMD="codex"; ' +
|
|
34
|
-
|
|
34
|
+
"else " +
|
|
35
35
|
' echo "Neither bunx nor codex was found in PATH" >&2; ' +
|
|
36
36
|
" exit 1; " +
|
|
37
|
-
|
|
37
|
+
"fi; " +
|
|
38
38
|
'sh -lc "$CODEX_CMD --version"; ' +
|
|
39
39
|
'NEED_LOGIN="0"; ' +
|
|
40
40
|
'if [ "$AUTH_MODE" = "chatgpt" ] || [ "$AUTH_MODE" = "chatgpt_login" ] || [ "$AUTH_MODE" = "subscription" ]; then NEED_LOGIN="1"; fi; ' +
|
|
@@ -55,10 +55,7 @@ function estimateJobTokenUsage(
|
|
|
55
55
|
};
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
function coerceJobTokenUsage(
|
|
59
|
-
value: unknown,
|
|
60
|
-
fallback: JobTokenUsage,
|
|
61
|
-
): JobTokenUsage {
|
|
58
|
+
function coerceJobTokenUsage(value: unknown, fallback: JobTokenUsage): JobTokenUsage {
|
|
62
59
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
63
60
|
return fallback;
|
|
64
61
|
}
|
|
@@ -16,8 +16,7 @@ export function resolveOutputCompactionPolicy(
|
|
|
16
16
|
const maxOutputChars = Number(overrides.maxOutputChars ?? worker.outputMaxChars);
|
|
17
17
|
const maxOutputLines = Number(overrides.maxOutputLines ?? worker.outputMaxLines);
|
|
18
18
|
const maxOutputHeadLines = Number(overrides.maxOutputHeadLines ?? worker.outputMaxHeadLines);
|
|
19
|
-
const executorResultPrefixRaw =
|
|
20
|
-
overrides.executorResultPrefix ?? worker.executorResultPrefix;
|
|
19
|
+
const executorResultPrefixRaw = overrides.executorResultPrefix ?? worker.executorResultPrefix;
|
|
21
20
|
const executorResultPrefix =
|
|
22
21
|
typeof executorResultPrefixRaw === "string" && executorResultPrefixRaw.length > 0
|
|
23
22
|
? executorResultPrefixRaw
|
|
@@ -42,7 +41,10 @@ export function resolveOutputCompactionPolicy(
|
|
|
42
41
|
|
|
43
42
|
// ---- Output truncation -------------------------------------------------------
|
|
44
43
|
|
|
45
|
-
export function compactJobOutput(
|
|
44
|
+
export function compactJobOutput(
|
|
45
|
+
text: string,
|
|
46
|
+
policyOverrides: Partial<OutputCompactionPolicy> = {},
|
|
47
|
+
): string {
|
|
46
48
|
if (!text) return "";
|
|
47
49
|
const policy = resolveOutputCompactionPolicy(policyOverrides);
|
|
48
50
|
const maxOutputChars = policy.maxOutputChars;
|
|
@@ -58,10 +58,7 @@ function estimateJobTokenUsage(
|
|
|
58
58
|
};
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
-
function coerceJobTokenUsage(
|
|
62
|
-
value: unknown,
|
|
63
|
-
fallback: JobTokenUsage,
|
|
64
|
-
): JobTokenUsage {
|
|
61
|
+
function coerceJobTokenUsage(value: unknown, fallback: JobTokenUsage): JobTokenUsage {
|
|
65
62
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
66
63
|
return fallback;
|
|
67
64
|
}
|
|
@@ -39,7 +39,9 @@ export async function forceDeleteWorktreePath(
|
|
|
39
39
|
const retries = Math.max(1, Math.floor(options.retries ?? 5));
|
|
40
40
|
const delayMs = Math.max(0, Math.floor(options.delayMs ?? 120));
|
|
41
41
|
const sleep = options.sleepFn ?? defaultSleep;
|
|
42
|
-
const removePath =
|
|
42
|
+
const removePath =
|
|
43
|
+
options.removeFn ??
|
|
44
|
+
((targetPath: string) => rmSync(targetPath, { recursive: true, force: true }));
|
|
43
45
|
const pathExists = options.existsFn ?? ((targetPath: string) => existsSync(targetPath));
|
|
44
46
|
let lastError = "";
|
|
45
47
|
|
|
@@ -63,4 +65,3 @@ export async function forceDeleteWorktreePath(
|
|
|
63
65
|
...(lastError ? { lastError } : {}),
|
|
64
66
|
};
|
|
65
67
|
}
|
|
66
|
-
|
|
@@ -75,7 +75,10 @@ function resolveDockerExecutable(): string {
|
|
|
75
75
|
return process.platform === "win32" ? "docker.exe" : "docker";
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
-
function resolveWorkerpalSandboxBuildContext(repoRoot: string): {
|
|
78
|
+
function resolveWorkerpalSandboxBuildContext(repoRoot: string): {
|
|
79
|
+
root: string;
|
|
80
|
+
dockerfilePath: string;
|
|
81
|
+
} {
|
|
79
82
|
const configuredRoot = String(process.env.PUSHPALS_WORKERPALS_SANDBOX_ROOT ?? "").trim();
|
|
80
83
|
const sandboxRoot = configuredRoot || repoRoot;
|
|
81
84
|
const dockerfilePath = configuredRoot
|
|
@@ -233,6 +236,7 @@ export class DockerExecutor {
|
|
|
233
236
|
private readonly jobRetryMaxAttempts: number;
|
|
234
237
|
private readonly jobRetryBackoffMs: number;
|
|
235
238
|
private readonly failureCooldownMs: number;
|
|
239
|
+
private readonly worktreeVisibilityTimeoutMs: number;
|
|
236
240
|
private lastLoggedExecutionConfig = "";
|
|
237
241
|
private lastLoggedEndpointRewrite = "";
|
|
238
242
|
private warmedBackends = new Set<string>();
|
|
@@ -291,6 +295,7 @@ export class DockerExecutor {
|
|
|
291
295
|
20_000,
|
|
292
296
|
300_000,
|
|
293
297
|
);
|
|
298
|
+
this.worktreeVisibilityTimeoutMs = process.platform === "win32" ? 15_000 : 5_000;
|
|
294
299
|
|
|
295
300
|
// Ensure worktrees directory exists
|
|
296
301
|
try {
|
|
@@ -413,7 +418,10 @@ export class DockerExecutor {
|
|
|
413
418
|
try {
|
|
414
419
|
await this.createWorktree(worktreePath, this.options.baseRef);
|
|
415
420
|
await this.runGitSelfCheckContainer(worktreePath);
|
|
416
|
-
|
|
421
|
+
await this.ensureWorktreeAccessibleInWarmContainer(worktreePath);
|
|
422
|
+
console.log(
|
|
423
|
+
`[DockerExecutor] Startup self-check passed (git/worktree in container and warm container).`,
|
|
424
|
+
);
|
|
417
425
|
} finally {
|
|
418
426
|
await this.removeWorktree(worktreePath).catch(() => {
|
|
419
427
|
// Ignore cleanup failures for startup self-check artifacts.
|
|
@@ -446,14 +454,11 @@ export class DockerExecutor {
|
|
|
446
454
|
});
|
|
447
455
|
await prune.exited;
|
|
448
456
|
|
|
449
|
-
proc = Bun.spawn(
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
stderr: "pipe",
|
|
455
|
-
},
|
|
456
|
-
);
|
|
457
|
+
proc = Bun.spawn(["git", "worktree", "add", "--force", "--detach", worktreePath, baseRef], {
|
|
458
|
+
cwd: this.options.repo,
|
|
459
|
+
stdout: "pipe",
|
|
460
|
+
stderr: "pipe",
|
|
461
|
+
});
|
|
457
462
|
exitCode = await proc.exited;
|
|
458
463
|
stdout = await new Response(proc.stdout).text();
|
|
459
464
|
stderr = await new Response(proc.stderr).text();
|
|
@@ -713,7 +718,10 @@ export class DockerExecutor {
|
|
|
713
718
|
|
|
714
719
|
args.push("--entrypoint", "/bin/sh", this.options.imageName, "-lc", startupCmd);
|
|
715
720
|
|
|
716
|
-
const proc = Bun.spawn([resolveDockerExecutable(), ...args], {
|
|
721
|
+
const proc = Bun.spawn([resolveDockerExecutable(), ...args], {
|
|
722
|
+
stdout: "pipe",
|
|
723
|
+
stderr: "pipe",
|
|
724
|
+
});
|
|
717
725
|
const [exitCode, stdout, stderr] = await Promise.all([
|
|
718
726
|
proc.exited,
|
|
719
727
|
new Response(proc.stdout).text(),
|
|
@@ -814,10 +822,48 @@ export class DockerExecutor {
|
|
|
814
822
|
stderr: string;
|
|
815
823
|
exitCode: number;
|
|
816
824
|
}> {
|
|
817
|
-
const proc = Bun.spawn(
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
825
|
+
const proc = Bun.spawn(
|
|
826
|
+
[resolveDockerExecutable(), "exec", this.warmContainerName, "/bin/sh", "-lc", command],
|
|
827
|
+
{
|
|
828
|
+
stdout: "pipe",
|
|
829
|
+
stderr: "pipe",
|
|
830
|
+
},
|
|
831
|
+
);
|
|
832
|
+
const [stdout, stderr, exitCode] = await Promise.all([
|
|
833
|
+
new Response(proc.stdout).text(),
|
|
834
|
+
new Response(proc.stderr).text(),
|
|
835
|
+
proc.exited,
|
|
836
|
+
]);
|
|
837
|
+
return {
|
|
838
|
+
ok: exitCode === 0,
|
|
839
|
+
stdout: stdout.trim(),
|
|
840
|
+
stderr: stderr.trim(),
|
|
841
|
+
exitCode,
|
|
842
|
+
};
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
private async runWarmWorktreeProbe(containerWorktreePath: string): Promise<{
|
|
846
|
+
ok: boolean;
|
|
847
|
+
stdout: string;
|
|
848
|
+
stderr: string;
|
|
849
|
+
exitCode: number;
|
|
850
|
+
}> {
|
|
851
|
+
const proc = Bun.spawn(
|
|
852
|
+
[
|
|
853
|
+
resolveDockerExecutable(),
|
|
854
|
+
"exec",
|
|
855
|
+
"-w",
|
|
856
|
+
containerWorktreePath,
|
|
857
|
+
this.warmContainerName,
|
|
858
|
+
"/bin/sh",
|
|
859
|
+
"-lc",
|
|
860
|
+
"git rev-parse --is-inside-work-tree && git rev-parse --git-dir",
|
|
861
|
+
],
|
|
862
|
+
{
|
|
863
|
+
stdout: "pipe",
|
|
864
|
+
stderr: "pipe",
|
|
865
|
+
},
|
|
866
|
+
);
|
|
821
867
|
const [stdout, stderr, exitCode] = await Promise.all([
|
|
822
868
|
new Response(proc.stdout).text(),
|
|
823
869
|
new Response(proc.stderr).text(),
|
|
@@ -854,10 +900,13 @@ export class DockerExecutor {
|
|
|
854
900
|
}
|
|
855
901
|
|
|
856
902
|
private async readWarmContainerLogs(tail = 160): Promise<string> {
|
|
857
|
-
const proc = Bun.spawn(
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
903
|
+
const proc = Bun.spawn(
|
|
904
|
+
[resolveDockerExecutable(), "logs", "--tail", String(tail), this.warmContainerName],
|
|
905
|
+
{
|
|
906
|
+
stdout: "pipe",
|
|
907
|
+
stderr: "pipe",
|
|
908
|
+
},
|
|
909
|
+
);
|
|
861
910
|
const [stdout, stderr, exitCode] = await Promise.all([
|
|
862
911
|
new Response(proc.stdout).text(),
|
|
863
912
|
new Response(proc.stderr).text(),
|
|
@@ -1045,10 +1094,10 @@ export class DockerExecutor {
|
|
|
1045
1094
|
): Promise<DockerJobResult> {
|
|
1046
1095
|
await this.ensureWarmRuntimeReady(job, onLog);
|
|
1047
1096
|
const startedAtMs = Date.now();
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1097
|
+
const containerWorktreePath = await this.ensureWorktreeAccessibleInWarmContainer(
|
|
1098
|
+
worktreePath,
|
|
1099
|
+
onLog,
|
|
1100
|
+
);
|
|
1052
1101
|
|
|
1053
1102
|
const args: string[] = [
|
|
1054
1103
|
"exec",
|
|
@@ -1145,6 +1194,51 @@ export class DockerExecutor {
|
|
|
1145
1194
|
);
|
|
1146
1195
|
}
|
|
1147
1196
|
|
|
1197
|
+
private async ensureWorktreeAccessibleInWarmContainer(
|
|
1198
|
+
worktreePath: string,
|
|
1199
|
+
onLog?: (stream: "stdout" | "stderr", line: string) => void,
|
|
1200
|
+
): Promise<string> {
|
|
1201
|
+
const worktreeRelPath = relative(this.options.repo, worktreePath).replace(/\\/g, "/");
|
|
1202
|
+
const containerWorktreePath = `/repo/${worktreeRelPath}`;
|
|
1203
|
+
let lastError: unknown = null;
|
|
1204
|
+
|
|
1205
|
+
for (let attempt = 1; attempt <= 2; attempt++) {
|
|
1206
|
+
try {
|
|
1207
|
+
await this.ensureWarmContainer();
|
|
1208
|
+
await this.waitForWorktreePathInWarmContainer(
|
|
1209
|
+
containerWorktreePath,
|
|
1210
|
+
this.worktreeVisibilityTimeoutMs,
|
|
1211
|
+
);
|
|
1212
|
+
const probe = await this.runWarmWorktreeProbe(containerWorktreePath);
|
|
1213
|
+
if (probe.ok) {
|
|
1214
|
+
return containerWorktreePath;
|
|
1215
|
+
}
|
|
1216
|
+
const detail = [probe.stderr, probe.stdout].filter(Boolean).join("\n").trim();
|
|
1217
|
+
throw new Error(
|
|
1218
|
+
`warm container git probe failed (exit ${probe.exitCode})${detail ? `: ${detail}` : ""}`,
|
|
1219
|
+
);
|
|
1220
|
+
} catch (err) {
|
|
1221
|
+
lastError = err;
|
|
1222
|
+
if (attempt >= 2) {
|
|
1223
|
+
const diagnostics = await this.inspectWarmContainerState().catch(() => "");
|
|
1224
|
+
throw new Error(
|
|
1225
|
+
`worktree not accessible inside warm container after ${attempt} attempts: ${containerWorktreePath}${
|
|
1226
|
+
lastError ? ` (${this.compactError(lastError)})` : ""
|
|
1227
|
+
}${diagnostics ? ` | container=${diagnostics}` : ""}`,
|
|
1228
|
+
);
|
|
1229
|
+
}
|
|
1230
|
+
const note =
|
|
1231
|
+
`[DockerExecutor] Warm container could not access worktree ${containerWorktreePath}; ` +
|
|
1232
|
+
`recycling container and retrying once (${this.compactError(err)}).`;
|
|
1233
|
+
console.warn(note);
|
|
1234
|
+
onLog?.("stderr", note);
|
|
1235
|
+
await this.stopWarmContainer("worktree visibility retry", true);
|
|
1236
|
+
}
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
return containerWorktreePath;
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1148
1242
|
private normalizeProvider(raw: string): string {
|
|
1149
1243
|
const value = raw.trim().toLowerCase();
|
|
1150
1244
|
if (!value) return "auto";
|
|
@@ -1555,11 +1649,14 @@ export class DockerExecutor {
|
|
|
1555
1649
|
`[DockerExecutor] Worktree path already exists; forcing cleanup before create: ${worktreePath}`,
|
|
1556
1650
|
);
|
|
1557
1651
|
|
|
1558
|
-
const unregister = Bun.spawn(
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1652
|
+
const unregister = Bun.spawn(
|
|
1653
|
+
["git", "worktree", "remove", "--force", "--force", worktreePath],
|
|
1654
|
+
{
|
|
1655
|
+
cwd: this.options.repo,
|
|
1656
|
+
stdout: "pipe",
|
|
1657
|
+
stderr: "pipe",
|
|
1658
|
+
},
|
|
1659
|
+
);
|
|
1563
1660
|
await unregister.exited;
|
|
1564
1661
|
|
|
1565
1662
|
const prune = Bun.spawn(["git", "worktree", "prune"], {
|
|
@@ -1777,10 +1874,13 @@ export class DockerExecutor {
|
|
|
1777
1874
|
* Check if the Docker image exists locally
|
|
1778
1875
|
*/
|
|
1779
1876
|
private async imageExists(): Promise<boolean> {
|
|
1780
|
-
const proc = Bun.spawn(
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1877
|
+
const proc = Bun.spawn(
|
|
1878
|
+
[resolveDockerExecutable(), "image", "inspect", this.options.imageName],
|
|
1879
|
+
{
|
|
1880
|
+
stdout: "pipe",
|
|
1881
|
+
stderr: "pipe",
|
|
1882
|
+
},
|
|
1883
|
+
);
|
|
1784
1884
|
const exitCode = await proc.exited;
|
|
1785
1885
|
return exitCode === 0;
|
|
1786
1886
|
}
|
|
@@ -173,7 +173,10 @@ export function buildQualityGateRevisionIssues(
|
|
|
173
173
|
if (!critic || critic.score >= qualityCriticMinScore) {
|
|
174
174
|
return [...normalizedQualityIssues];
|
|
175
175
|
}
|
|
176
|
-
const merged = [
|
|
176
|
+
const merged = [
|
|
177
|
+
...normalizedQualityIssues,
|
|
178
|
+
...buildCriticRevisionIssues(critic, qualityCriticMinScore),
|
|
179
|
+
];
|
|
177
180
|
return [...new Set(merged)];
|
|
178
181
|
}
|
|
179
182
|
|
|
@@ -2569,7 +2572,8 @@ function validateTaskExecutePlanning(
|
|
|
2569
2572
|
if (!componentArea) {
|
|
2570
2573
|
return {
|
|
2571
2574
|
ok: false,
|
|
2572
|
-
message:
|
|
2575
|
+
message:
|
|
2576
|
+
"task.execute planning.targetPaths must resolve to a repo-relative componentArea",
|
|
2573
2577
|
};
|
|
2574
2578
|
}
|
|
2575
2579
|
if (
|
|
@@ -3056,11 +3060,7 @@ export async function executeJob(
|
|
|
3056
3060
|
return result;
|
|
3057
3061
|
}
|
|
3058
3062
|
|
|
3059
|
-
const issues = buildQualityGateRevisionIssues(
|
|
3060
|
-
quality.issues,
|
|
3061
|
-
critic,
|
|
3062
|
-
qualityCriticMinScore,
|
|
3063
|
-
);
|
|
3063
|
+
const issues = buildQualityGateRevisionIssues(quality.issues, critic, qualityCriticMinScore);
|
|
3064
3064
|
const issueSummary = issues.map((entry) => toSingleLine(entry, 180)).join(" | ");
|
|
3065
3065
|
if (revisionAttempt >= qualityMaxAutoRevisions) {
|
|
3066
3066
|
if (qualitySoftPassOnExhausted) {
|
|
@@ -67,10 +67,13 @@ echo "password=${token}"
|
|
|
67
67
|
writeFileSync(helperPath, helperScript, { mode: 0o755 });
|
|
68
68
|
|
|
69
69
|
// Remove any legacy URL rewrite rules that may have embedded token credentials.
|
|
70
|
-
const urlRules = Bun.spawnSync(
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
70
|
+
const urlRules = Bun.spawnSync(
|
|
71
|
+
["git", "config", "--global", "--get-regexp", "^url\\..*\\.insteadOf$"],
|
|
72
|
+
{
|
|
73
|
+
stdout: "pipe",
|
|
74
|
+
stderr: "pipe",
|
|
75
|
+
},
|
|
76
|
+
);
|
|
74
77
|
if (urlRules.exitCode === 0) {
|
|
75
78
|
const lines = String(urlRules.stdout ?? "")
|
|
76
79
|
.split(/\r?\n/)
|