@pushpalsdev/cli 1.1.40 → 1.1.42
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
|
@@ -119,6 +119,7 @@ _DEFAULT_NO_EDIT_RECHECK_S = 120
|
|
|
119
119
|
_NO_EDIT_RECOVERY_RECHECK_S = 30
|
|
120
120
|
_DEFAULT_NO_EDIT_COMMAND_GRACE_S = 240
|
|
121
121
|
_DEFAULT_NO_EDIT_COMMAND_PROGRESS_CAP_S = 360
|
|
122
|
+
_BACKGROUND_NO_EDIT_COMMAND_PROGRESS_CAP_S = 120
|
|
122
123
|
_NO_EDIT_RECOVERY_COMMAND_PROGRESS_CAP_S = 120
|
|
123
124
|
_DEFAULT_STARTUP_STALL_WATCHDOG_S = 210
|
|
124
125
|
_RECOVERY_STARTUP_STALL_WATCHDOG_S = 150
|
|
@@ -817,6 +818,7 @@ def _resolve_no_edit_command_progress_cap_seconds(
|
|
|
817
818
|
communicate_timeout_s: Optional[int],
|
|
818
819
|
no_edit_command_grace_s: Optional[int],
|
|
819
820
|
recovery_attempt: int = 0,
|
|
821
|
+
prompt: str = "",
|
|
820
822
|
) -> Optional[int]:
|
|
821
823
|
if not communicate_timeout_s or no_edit_command_grace_s is None:
|
|
822
824
|
return None
|
|
@@ -834,11 +836,12 @@ def _resolve_no_edit_command_progress_cap_seconds(
|
|
|
834
836
|
else:
|
|
835
837
|
return max(1, min(parsed, max(1, communicate_timeout_s - 1)))
|
|
836
838
|
|
|
837
|
-
|
|
838
|
-
_NO_EDIT_RECOVERY_COMMAND_PROGRESS_CAP_S
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
839
|
+
if recovery_attempt > 0:
|
|
840
|
+
default_s = _NO_EDIT_RECOVERY_COMMAND_PROGRESS_CAP_S
|
|
841
|
+
elif _looks_like_background_autonomy_prompt(prompt):
|
|
842
|
+
default_s = _BACKGROUND_NO_EDIT_COMMAND_PROGRESS_CAP_S
|
|
843
|
+
else:
|
|
844
|
+
default_s = _DEFAULT_NO_EDIT_COMMAND_PROGRESS_CAP_S
|
|
842
845
|
upper = max(1, communicate_timeout_s - 1)
|
|
843
846
|
return max(1, min(default_s, upper))
|
|
844
847
|
|
|
@@ -2713,10 +2716,11 @@ def _run_codex_task(
|
|
|
2713
2716
|
communicate_timeout_s,
|
|
2714
2717
|
no_edit_command_grace_s,
|
|
2715
2718
|
recovery_attempt=recovery_depth,
|
|
2719
|
+
prompt=prompt,
|
|
2716
2720
|
)
|
|
2717
2721
|
startup_stall_watchdog_s = _resolve_startup_stall_watchdog_seconds(
|
|
2718
2722
|
communicate_timeout_s,
|
|
2719
|
-
recovery_attempt=
|
|
2723
|
+
recovery_attempt=recovery_depth,
|
|
2720
2724
|
)
|
|
2721
2725
|
startup_stall_deadline = (
|
|
2722
2726
|
started_at + float(startup_stall_watchdog_s)
|
|
@@ -2849,6 +2853,11 @@ def _run_codex_task(
|
|
|
2849
2853
|
first_no_edit_command_progress_at
|
|
2850
2854
|
+ float(no_edit_command_progress_cap_s),
|
|
2851
2855
|
)
|
|
2856
|
+
if deadline is not None and command_grace_deadline > 0:
|
|
2857
|
+
command_grace_deadline = min(
|
|
2858
|
+
command_grace_deadline,
|
|
2859
|
+
max(now, deadline - 1.0),
|
|
2860
|
+
)
|
|
2852
2861
|
if command_progress_cap_reached:
|
|
2853
2862
|
log.info(
|
|
2854
2863
|
"No-edit watchdog observed Codex tool progress for "
|
|
@@ -45,6 +45,7 @@ from openai_codex_executor import (
|
|
|
45
45
|
_has_credible_shell_wrapper_progress,
|
|
46
46
|
_load_prompt_template,
|
|
47
47
|
_mask_repo_local_codex_files,
|
|
48
|
+
_minimum_recovery_attempt_seconds,
|
|
48
49
|
_repo_root_for_prompt_loading,
|
|
49
50
|
_restore_repo_local_codex_files,
|
|
50
51
|
_resolve_codex_command_prefix,
|
|
@@ -249,6 +250,41 @@ class OpenAICodexRuntimeConfigTests(unittest.TestCase):
|
|
|
249
250
|
)
|
|
250
251
|
self.assertEqual(_resolve_rollout_watchdog_seconds(prompt, 1200, no_edit), 90)
|
|
251
252
|
|
|
253
|
+
def test_background_autonomy_caps_patchless_command_progress_before_recovery_reserve(self) -> None:
|
|
254
|
+
prompt = (
|
|
255
|
+
"Task planning contract from PushPals:\n"
|
|
256
|
+
"- Planning summary: intent=code_change, risk=low, priority=background\n"
|
|
257
|
+
"- Origin=autonomy targetPaths=[app/__tests__/opportunity-graph.contract.test.ts]\n"
|
|
258
|
+
"Add focused contract coverage without broad discovery.\n"
|
|
259
|
+
)
|
|
260
|
+
child_budget_s = 570
|
|
261
|
+
|
|
262
|
+
with mock.patch.dict(
|
|
263
|
+
os.environ,
|
|
264
|
+
{
|
|
265
|
+
"WORKERPALS_OPENAI_CODEX_NO_EDIT_COMMAND_GRACE_S": "",
|
|
266
|
+
"WORKERPALS_OPENAI_CODEX_NO_EDIT_COMMAND_PROGRESS_CAP_S": "",
|
|
267
|
+
"WORKERPALS_OPENAI_CODEX_STARTUP_STALL_WATCHDOG_S": "",
|
|
268
|
+
},
|
|
269
|
+
clear=False,
|
|
270
|
+
):
|
|
271
|
+
command_grace_s = _resolve_no_edit_command_grace_seconds(child_budget_s)
|
|
272
|
+
command_cap_s = _resolve_no_edit_command_progress_cap_seconds(
|
|
273
|
+
child_budget_s,
|
|
274
|
+
command_grace_s,
|
|
275
|
+
prompt=prompt,
|
|
276
|
+
)
|
|
277
|
+
startup_stall_s = _resolve_startup_stall_watchdog_seconds(child_budget_s)
|
|
278
|
+
|
|
279
|
+
self.assertEqual(command_grace_s, 240)
|
|
280
|
+
self.assertEqual(command_cap_s, 120)
|
|
281
|
+
self.assertEqual(startup_stall_s, 210)
|
|
282
|
+
first_attempt_patchless_ceiling_s = startup_stall_s + command_cap_s
|
|
283
|
+
self.assertGreaterEqual(
|
|
284
|
+
child_budget_s - first_attempt_patchless_ceiling_s,
|
|
285
|
+
2 * _minimum_recovery_attempt_seconds(child_budget_s),
|
|
286
|
+
)
|
|
287
|
+
|
|
252
288
|
def test_runtime_config_prefers_explicit_config_dir_override(self) -> None:
|
|
253
289
|
import executor_base
|
|
254
290
|
|
|
@@ -1758,6 +1794,86 @@ class OpenAICodexRuntimeConfigTests(unittest.TestCase):
|
|
|
1758
1794
|
self.assertIn("no publishable changes", str(result.get("summary") or ""))
|
|
1759
1795
|
self.assertEqual(result.get("cooldownMs"), 600000)
|
|
1760
1796
|
|
|
1797
|
+
def test_run_codex_task_recovery_command_grace_stops_before_child_timeout(self) -> None:
|
|
1798
|
+
with tempfile.TemporaryDirectory(prefix="pushpals-codex-no-edit-before-timeout-") as temp_dir:
|
|
1799
|
+
repo = Path(temp_dir) / "repo"
|
|
1800
|
+
repo.mkdir(parents=True, exist_ok=True)
|
|
1801
|
+
(repo / "README.md").write_text("# no edit before timeout repo\n", encoding="utf-8")
|
|
1802
|
+
subprocess.run(["git", "init"], cwd=repo, check=True, capture_output=True, text=True)
|
|
1803
|
+
subprocess.run(
|
|
1804
|
+
["git", "config", "user.name", "PushPals Test"],
|
|
1805
|
+
cwd=repo,
|
|
1806
|
+
check=True,
|
|
1807
|
+
capture_output=True,
|
|
1808
|
+
text=True,
|
|
1809
|
+
)
|
|
1810
|
+
subprocess.run(
|
|
1811
|
+
["git", "config", "user.email", "pushpals-tests@example.com"],
|
|
1812
|
+
cwd=repo,
|
|
1813
|
+
check=True,
|
|
1814
|
+
capture_output=True,
|
|
1815
|
+
text=True,
|
|
1816
|
+
)
|
|
1817
|
+
subprocess.run(["git", "add", "README.md"], cwd=repo, check=True, capture_output=True, text=True)
|
|
1818
|
+
subprocess.run(
|
|
1819
|
+
["git", "commit", "-m", "chore: seed no-edit before timeout repo"],
|
|
1820
|
+
cwd=repo,
|
|
1821
|
+
check=True,
|
|
1822
|
+
capture_output=True,
|
|
1823
|
+
text=True,
|
|
1824
|
+
)
|
|
1825
|
+
|
|
1826
|
+
stub_path = Path(temp_dir) / "fake_codex_no_edit_before_timeout.py"
|
|
1827
|
+
stub_path.write_text(
|
|
1828
|
+
"\n".join(
|
|
1829
|
+
[
|
|
1830
|
+
"import json",
|
|
1831
|
+
"import sys",
|
|
1832
|
+
"import time",
|
|
1833
|
+
"",
|
|
1834
|
+
"prompt = sys.stdin.read()",
|
|
1835
|
+
"print(json.dumps({'type': 'thread.started'}), flush=True)",
|
|
1836
|
+
"print(json.dumps({'type': 'turn.started'}), flush=True)",
|
|
1837
|
+
"if 'No-edit watchdog recovery' in prompt:",
|
|
1838
|
+
" print(json.dumps({'type': 'item.started', 'item': {'id': 'cmd-read', 'type': 'command_execution', 'command': 'cat README.md', 'status': 'in_progress'}}), flush=True)",
|
|
1839
|
+
" time.sleep(0.1)",
|
|
1840
|
+
" print(json.dumps({'type': 'item.completed', 'item': {'id': 'cmd-read', 'type': 'command_execution', 'command': 'cat README.md', 'status': 'completed', 'exit_code': 0, 'aggregated_output': '# no edit before timeout repo'}}), flush=True)",
|
|
1841
|
+
" time.sleep(6)",
|
|
1842
|
+
" raise SystemExit(0)",
|
|
1843
|
+
"",
|
|
1844
|
+
"print(json.dumps({'type': 'item.completed', 'item': {'type': 'message', 'text': 'Still inspecting without a patch.'}}), flush=True)",
|
|
1845
|
+
"time.sleep(6)",
|
|
1846
|
+
]
|
|
1847
|
+
),
|
|
1848
|
+
encoding="utf-8",
|
|
1849
|
+
)
|
|
1850
|
+
|
|
1851
|
+
env_overrides = {
|
|
1852
|
+
"PUSHPALS_OPENAI_CODEX_BIN_JSON": json.dumps([sys.executable, str(stub_path)]),
|
|
1853
|
+
"PUSHPALS_OPENAI_CODEX_AUTH_MODE": "api_key",
|
|
1854
|
+
"OPENAI_API_KEY": "pushpals-no-edit-before-timeout-test-key",
|
|
1855
|
+
"WORKERPALS_OPENAI_CODEX_JSON": "true",
|
|
1856
|
+
"WORKERPALS_OPENAI_CODEX_TIMEOUT_S": "6",
|
|
1857
|
+
"WORKERPALS_OPENAI_CODEX_NO_EDIT_WATCHDOG_S": "1",
|
|
1858
|
+
"WORKERPALS_OPENAI_CODEX_NO_EDIT_COMMAND_GRACE_S": "5",
|
|
1859
|
+
"WORKERPALS_OPENAI_CODEX_PROGRESS_LOG_INTERVAL_S": "1",
|
|
1860
|
+
}
|
|
1861
|
+
with mock.patch.dict(os.environ, env_overrides, clear=False):
|
|
1862
|
+
result = _run_codex_task(
|
|
1863
|
+
str(repo),
|
|
1864
|
+
"Make one focused test edit after the hinted file read.",
|
|
1865
|
+
[],
|
|
1866
|
+
)
|
|
1867
|
+
|
|
1868
|
+
self.assertFalse(result.get("ok"), result)
|
|
1869
|
+
self.assertEqual(result.get("exitCode"), 124)
|
|
1870
|
+
self.assertEqual(
|
|
1871
|
+
result.get("summary"),
|
|
1872
|
+
"openai_codex made no publishable changes before the no-edit watchdog",
|
|
1873
|
+
)
|
|
1874
|
+
self.assertNotIn("execution timed out", str(result.get("summary") or ""))
|
|
1875
|
+
self.assertEqual(result.get("cooldownMs"), 600000)
|
|
1876
|
+
|
|
1761
1877
|
def test_run_codex_task_no_edit_watchdog_rechecks_transient_publishable_progress(self) -> None:
|
|
1762
1878
|
with tempfile.TemporaryDirectory(prefix="pushpals-codex-no-edit-recheck-") as temp_dir:
|
|
1763
1879
|
repo = Path(temp_dir) / "repo"
|