@pushpalsdev/cli 1.0.61 → 1.0.62
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 +1 -1
- package/runtime/prompts/workerpals/openai_codex_command_router_policy.md +10 -0
- package/runtime/sandbox/apps/workerpals/src/backends/openai_codex/openai_codex_executor.py +62 -14
- package/runtime/sandbox/apps/workerpals/src/backends/openai_codex/test_openai_codex_runtime_config.py +11 -1
- package/runtime/sandbox/apps/workerpals/src/docker_executor.ts +10 -4
- package/runtime/sandbox/apps/workerpals/src/execute_job.ts +40 -1
- package/runtime/sandbox/prompts/workerpals/openai_codex_command_router_policy.md +10 -0
package/package.json
CHANGED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
## Base Guidance
|
|
2
|
+
Command-router policy: shell commands are allowed, but invoke the actual command directly instead of wrapping it with `/bin/bash -lc`, `bash -c`, `sh -lc`, `cmd /c`, `powershell -Command`, or `pwsh -Command`. If a wrapper command is rejected, rerun its inner command directly through the command tool.
|
|
3
|
+
|
|
4
|
+
## Recovery Guidance
|
|
5
|
+
Command-router recovery: the previous attempt retried disallowed shell wrappers.
|
|
6
|
+
Retry once using shell commands normally, but invoke the inner command directly instead of wrapping it in `/bin/bash -lc`, `bash -c`, `sh -lc`, `cmd /c`, `powershell -Command`, or `pwsh -Command`.
|
|
7
|
+
You are not limited to a fixed allowlist of commands. The constraint is only that command execution must target the actual program/argv directly rather than a wrapper shell.
|
|
8
|
+
|
|
9
|
+
## Rejection Detail
|
|
10
|
+
Codex repeatedly attempted disallowed shell-wrapper commands that the command router rejected. Shell commands are allowed, but wrapper shells are not; invoke the inner command directly and avoid wrapper retries.
|
|
@@ -55,6 +55,7 @@ _DEFAULT_TASK_SYSTEM_PROMPT_PATH = "workerpals/openai_codex_default_system_promp
|
|
|
55
55
|
_MANDATORY_RUNTIME_POLICY_APPENDIX_PATH = "workerpals/openai_codex_runtime_policy_appendix.md"
|
|
56
56
|
_INSTRUCTION_WRAPPER_PROMPT_PATH = "workerpals/openai_codex_instruction_wrapper.md"
|
|
57
57
|
_SUPPLEMENTAL_GUIDANCE_SECTION_PATH = "workerpals/openai_codex_supplemental_guidance_section.md"
|
|
58
|
+
_COMMAND_ROUTER_POLICY_PATH = "workerpals/openai_codex_command_router_policy.md"
|
|
58
59
|
_CODEX_WORKAROUND_PATTERNS = (
|
|
59
60
|
re.compile(
|
|
60
61
|
r"\bcodex cli\b.{0,120}\b(isn't|is not|not)\b.{0,120}\bavailable\b.{0,120}\b(so|therefore|instead|fallback|workaround|without|using)\b",
|
|
@@ -94,12 +95,6 @@ _VALID_SANDBOX_POLICIES = {"read-only", "workspace-write", "danger-full-access"}
|
|
|
94
95
|
_VALID_COLORS = {"always", "never", "auto"}
|
|
95
96
|
_VALID_AUTH_MODES = {"auto", "api_key", "chatgpt"}
|
|
96
97
|
_VALID_REASONING_EFFORTS = {"low", "medium", "high", "xhigh"}
|
|
97
|
-
_DIRECT_COMMAND_POLICY_GUIDANCE = (
|
|
98
|
-
"Command-router policy: use direct commands only. Do not invoke `/bin/bash -lc`, `bash -c`, "
|
|
99
|
-
"`sh -lc`, `cmd /c`, `powershell -Command`, or `pwsh -Command`. Run the direct command "
|
|
100
|
-
"instead, such as `pwd`, `git status --porcelain`, `git diff -- path`, `ls dir`, "
|
|
101
|
-
"`cat file`, `sed -n '1,160p' file`, or `bun test <path>`."
|
|
102
|
-
)
|
|
103
98
|
|
|
104
99
|
|
|
105
100
|
def _model_supports_xhigh_reasoning(model: str) -> bool:
|
|
@@ -254,6 +249,64 @@ def _load_prompt_template(
|
|
|
254
249
|
return _PROMPT_TOKEN_REGEX.sub(_replace, template)
|
|
255
250
|
|
|
256
251
|
|
|
252
|
+
def _load_markdown_h2_section(relative_path: str, heading: str) -> str:
|
|
253
|
+
document = _load_prompt_template(relative_path)
|
|
254
|
+
if not document:
|
|
255
|
+
return ""
|
|
256
|
+
lines = document.splitlines()
|
|
257
|
+
needle = f"## {heading}".strip().lower()
|
|
258
|
+
start: Optional[int] = None
|
|
259
|
+
for idx, line in enumerate(lines):
|
|
260
|
+
if line.strip().lower() == needle:
|
|
261
|
+
start = idx + 1
|
|
262
|
+
break
|
|
263
|
+
if start is None:
|
|
264
|
+
return ""
|
|
265
|
+
collected: List[str] = []
|
|
266
|
+
for line in lines[start:]:
|
|
267
|
+
if line.startswith("## "):
|
|
268
|
+
break
|
|
269
|
+
collected.append(line)
|
|
270
|
+
return "\n".join(collected).strip()
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
def _command_router_policy_guidance() -> str:
|
|
274
|
+
guidance = _load_markdown_h2_section(_COMMAND_ROUTER_POLICY_PATH, "Base Guidance")
|
|
275
|
+
if guidance:
|
|
276
|
+
return guidance
|
|
277
|
+
return (
|
|
278
|
+
"Command-router policy: shell commands are allowed, but invoke the actual command directly "
|
|
279
|
+
"instead of wrapping it with `/bin/bash -lc`, `bash -c`, `sh -lc`, `cmd /c`, "
|
|
280
|
+
"`powershell -Command`, or `pwsh -Command`. If a wrapper command is rejected, rerun its "
|
|
281
|
+
"inner command directly through the command tool."
|
|
282
|
+
)
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
def _command_router_recovery_guidance() -> str:
|
|
286
|
+
guidance = _load_markdown_h2_section(_COMMAND_ROUTER_POLICY_PATH, "Recovery Guidance")
|
|
287
|
+
if guidance:
|
|
288
|
+
return guidance
|
|
289
|
+
return (
|
|
290
|
+
"Command-router recovery: the previous attempt retried disallowed shell wrappers.\n"
|
|
291
|
+
"Retry once using shell commands normally, but invoke the inner command directly instead of "
|
|
292
|
+
"wrapping it in `/bin/bash -lc`, `bash -c`, `sh -lc`, `cmd /c`, `powershell -Command`, or "
|
|
293
|
+
"`pwsh -Command`.\n"
|
|
294
|
+
"You are not limited to a fixed allowlist of commands. The constraint is only that command "
|
|
295
|
+
"execution must target the actual program/argv directly rather than a wrapper shell."
|
|
296
|
+
)
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
def _command_router_rejection_detail_intro() -> str:
|
|
300
|
+
guidance = _load_markdown_h2_section(_COMMAND_ROUTER_POLICY_PATH, "Rejection Detail")
|
|
301
|
+
if guidance:
|
|
302
|
+
return guidance
|
|
303
|
+
return (
|
|
304
|
+
"Codex repeatedly attempted disallowed shell-wrapper commands that the command router "
|
|
305
|
+
"rejected. Shell commands are allowed, but wrapper shells are not; invoke the inner "
|
|
306
|
+
"command directly and avoid wrapper retries."
|
|
307
|
+
)
|
|
308
|
+
|
|
309
|
+
|
|
257
310
|
def _to_positive_int(raw: str) -> Optional[int]:
|
|
258
311
|
try:
|
|
259
312
|
parsed = int(raw)
|
|
@@ -1023,11 +1076,7 @@ def _build_wrapper_recovery_guidance(rejected_commands: List[str]) -> str:
|
|
|
1023
1076
|
continue
|
|
1024
1077
|
seen.add(lowered)
|
|
1025
1078
|
direct_equivalents.append(f"- `{command}` -> `{direct}`")
|
|
1026
|
-
guidance_lines = [
|
|
1027
|
-
"Command-router recovery: the previous attempt retried disallowed shell wrappers.",
|
|
1028
|
-
"Retry once using direct commands only. Do not use `/bin/bash -lc`, `bash -c`, `sh -lc`, `cmd /c`, `powershell -Command`, `pwsh -Command`, pipelines, or chained shell snippets.",
|
|
1029
|
-
"If you need to inspect files or git state, run the direct command itself (for example `git diff --name-only`, `git status --porcelain`, `ls path`, `cat file`, or `sed -n '1,120p' file`).",
|
|
1030
|
-
]
|
|
1079
|
+
guidance_lines = [_command_router_recovery_guidance()]
|
|
1031
1080
|
if direct_equivalents:
|
|
1032
1081
|
guidance_lines.append("Use these direct replacements for the rejected commands:")
|
|
1033
1082
|
guidance_lines.extend(direct_equivalents[:6])
|
|
@@ -1064,7 +1113,7 @@ def _augment_supplemental_guidance(supplemental_guidance: List[str]) -> List[str
|
|
|
1064
1113
|
joined = "\n".join(normalized).lower()
|
|
1065
1114
|
if "direct commands only" in joined or "shell-wrapper" in joined or "/bin/bash -lc" in joined:
|
|
1066
1115
|
return normalized
|
|
1067
|
-
return [
|
|
1116
|
+
return [_command_router_policy_guidance(), *normalized]
|
|
1068
1117
|
|
|
1069
1118
|
|
|
1070
1119
|
def _read_text_if_exists(path: Path) -> str:
|
|
@@ -1503,8 +1552,7 @@ def _run_codex_task(
|
|
|
1503
1552
|
else "- (no command details captured)"
|
|
1504
1553
|
)
|
|
1505
1554
|
detail = (
|
|
1506
|
-
"
|
|
1507
|
-
"router rejected. Switch to direct commands only and avoid wrapper retries.\n"
|
|
1555
|
+
f"{_command_router_rejection_detail_intro()}\n"
|
|
1508
1556
|
f"Rejected commands:\n{command_lines}"
|
|
1509
1557
|
)
|
|
1510
1558
|
if last_message:
|
|
@@ -21,6 +21,7 @@ from executor_base import (
|
|
|
21
21
|
from openai_codex_executor import (
|
|
22
22
|
OpenAICodexRuntimeConfig,
|
|
23
23
|
_augment_supplemental_guidance,
|
|
24
|
+
_build_wrapper_recovery_guidance,
|
|
24
25
|
_resolve_reasoning_effort,
|
|
25
26
|
_build_instruction,
|
|
26
27
|
_collect_disallowed_shell_wrapper_rejections,
|
|
@@ -266,10 +267,19 @@ class OpenAICodexRuntimeConfigTests(unittest.TestCase):
|
|
|
266
267
|
def test_augments_guidance_with_direct_command_policy_once(self) -> None:
|
|
267
268
|
guidance = _augment_supplemental_guidance(["Run bun test tests/example.test.ts"])
|
|
268
269
|
self.assertGreaterEqual(len(guidance), 2)
|
|
269
|
-
self.assertIn("
|
|
270
|
+
self.assertIn("shell commands are allowed", guidance[0].lower())
|
|
270
271
|
guidance_again = _augment_supplemental_guidance(guidance)
|
|
271
272
|
self.assertEqual(guidance_again, guidance)
|
|
272
273
|
|
|
274
|
+
def test_wrapper_recovery_guidance_allows_arbitrary_shell_commands_without_wrappers(self) -> None:
|
|
275
|
+
guidance = _build_wrapper_recovery_guidance(
|
|
276
|
+
["/bin/bash -lc 'git status --porcelain'", "/bin/bash -lc pwd"]
|
|
277
|
+
)
|
|
278
|
+
lowered = guidance.lower()
|
|
279
|
+
self.assertIn("shell commands normally", lowered)
|
|
280
|
+
self.assertIn("not limited to a fixed allowlist", lowered)
|
|
281
|
+
self.assertIn("`/bin/bash -lc 'git status --porcelain'` -> `git status --porcelain`", guidance)
|
|
282
|
+
|
|
273
283
|
def test_usage_falls_back_to_estimate_when_trace_has_no_usage(self) -> None:
|
|
274
284
|
usage = _usage_from_trace_or_estimate({}, "abc" * 30, "done", model="gpt-5.4")
|
|
275
285
|
self.assertTrue(usage["estimated"])
|
|
@@ -757,11 +757,17 @@ export class DockerExecutor {
|
|
|
757
757
|
if (backend !== "openai_codex") return [];
|
|
758
758
|
|
|
759
759
|
const hostCodexHomeRaw = (process.env.PUSHPALS_OPENAI_CODEX_HOST_CODEX_HOME || "").trim();
|
|
760
|
+
if (hostCodexHomeRaw && !isAbsolute(hostCodexHomeRaw)) {
|
|
761
|
+
console.warn(
|
|
762
|
+
`[DockerExecutor] Ignoring relative PUSHPALS_OPENAI_CODEX_HOST_CODEX_HOME=${hostCodexHomeRaw}; using ${resolve(
|
|
763
|
+
homedir(),
|
|
764
|
+
".codex",
|
|
765
|
+
)} so Codex state stays outside the repo worktree.`,
|
|
766
|
+
);
|
|
767
|
+
}
|
|
760
768
|
const hostCodexHome = (
|
|
761
|
-
hostCodexHomeRaw
|
|
762
|
-
?
|
|
763
|
-
? hostCodexHomeRaw
|
|
764
|
-
: resolve(this.options.repo, hostCodexHomeRaw)
|
|
769
|
+
hostCodexHomeRaw && isAbsolute(hostCodexHomeRaw)
|
|
770
|
+
? hostCodexHomeRaw
|
|
765
771
|
: resolve(homedir(), ".codex")
|
|
766
772
|
).trim();
|
|
767
773
|
if (!hostCodexHome) return [];
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Used by both the host Worker (direct mode) and the Docker job runner.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { existsSync, readFileSync, unlinkSync } from "fs";
|
|
6
|
+
import { existsSync, readFileSync, rmSync, unlinkSync } from "fs";
|
|
7
7
|
import { resolve } from "path";
|
|
8
8
|
import {
|
|
9
9
|
deriveAutonomyComponentArea,
|
|
@@ -2606,6 +2606,41 @@ export async function syncHiddenRefWithRemoteBranchByRebase(
|
|
|
2606
2606
|
publicBranchName: string,
|
|
2607
2607
|
jobId: string,
|
|
2608
2608
|
): Promise<{ ok: true; sha: string } | { ok: false; error: string }> {
|
|
2609
|
+
const scrubKnownPreSyncArtifacts = async (): Promise<
|
|
2610
|
+
{ ok: true } | { ok: false; error: string }
|
|
2611
|
+
> => {
|
|
2612
|
+
const codexPath = resolve(repo, ".codex");
|
|
2613
|
+
if (!existsSync(codexPath)) return { ok: true };
|
|
2614
|
+
|
|
2615
|
+
const trackedCodex = await git(repo, ["ls-files", "--error-unmatch", "--", ".codex"]);
|
|
2616
|
+
if (trackedCodex.ok) {
|
|
2617
|
+
return {
|
|
2618
|
+
ok: false,
|
|
2619
|
+
error:
|
|
2620
|
+
"Tracked .codex path blocks branch sync. Move Codex state outside the repo worktree before retrying.",
|
|
2621
|
+
};
|
|
2622
|
+
}
|
|
2623
|
+
|
|
2624
|
+
try {
|
|
2625
|
+
rmSync(codexPath, { recursive: true, force: true });
|
|
2626
|
+
} catch (error) {
|
|
2627
|
+
return {
|
|
2628
|
+
ok: false,
|
|
2629
|
+
error: `Failed to scrub transient .codex artifact before branch sync: ${String(error)}`,
|
|
2630
|
+
};
|
|
2631
|
+
}
|
|
2632
|
+
|
|
2633
|
+
if (existsSync(codexPath)) {
|
|
2634
|
+
return {
|
|
2635
|
+
ok: false,
|
|
2636
|
+
error: "Failed to scrub transient .codex artifact before branch sync: path still exists.",
|
|
2637
|
+
};
|
|
2638
|
+
}
|
|
2639
|
+
|
|
2640
|
+
console.warn("[WorkerPals] Removed transient .codex artifact before branch sync.");
|
|
2641
|
+
return { ok: true };
|
|
2642
|
+
};
|
|
2643
|
+
|
|
2609
2644
|
const pullRebaseNonInteractive = () =>
|
|
2610
2645
|
git(repo, [
|
|
2611
2646
|
"-c",
|
|
@@ -2652,6 +2687,10 @@ export async function syncHiddenRefWithRemoteBranchByRebase(
|
|
|
2652
2687
|
const maxPullRebaseAttempts = 5;
|
|
2653
2688
|
let syncedWithRemote = false;
|
|
2654
2689
|
for (let attempt = 1; attempt <= maxPullRebaseAttempts; attempt++) {
|
|
2690
|
+
const preSyncGuard = await scrubKnownPreSyncArtifacts();
|
|
2691
|
+
if (!preSyncGuard.ok) {
|
|
2692
|
+
return { ok: false, error: preSyncGuard.error };
|
|
2693
|
+
}
|
|
2655
2694
|
let pullRebase = await pullRebaseNonInteractive();
|
|
2656
2695
|
if (!pullRebase.ok && isPullRebaseDirtyWorkingTreeOutput(combinedGitOutput(pullRebase))) {
|
|
2657
2696
|
// Recover from dirty index/worktree left by previous attempts and retry non-interactively.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
## Base Guidance
|
|
2
|
+
Command-router policy: shell commands are allowed, but invoke the actual command directly instead of wrapping it with `/bin/bash -lc`, `bash -c`, `sh -lc`, `cmd /c`, `powershell -Command`, or `pwsh -Command`. If a wrapper command is rejected, rerun its inner command directly through the command tool.
|
|
3
|
+
|
|
4
|
+
## Recovery Guidance
|
|
5
|
+
Command-router recovery: the previous attempt retried disallowed shell wrappers.
|
|
6
|
+
Retry once using shell commands normally, but invoke the inner command directly instead of wrapping it in `/bin/bash -lc`, `bash -c`, `sh -lc`, `cmd /c`, `powershell -Command`, or `pwsh -Command`.
|
|
7
|
+
You are not limited to a fixed allowlist of commands. The constraint is only that command execution must target the actual program/argv directly rather than a wrapper shell.
|
|
8
|
+
|
|
9
|
+
## Rejection Detail
|
|
10
|
+
Codex repeatedly attempted disallowed shell-wrapper commands that the command router rejected. Shell commands are allowed, but wrapper shells are not; invoke the inner command directly and avoid wrapper retries.
|