claude-dev-env 2.10.0 → 2.12.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/CLAUDE.md +1 -1
- package/_shared/advisor/CLAUDE.md +3 -2
- package/_shared/advisor/advisor-protocol.md +74 -108
- package/_shared/advisor/reference/advisor-block.md +37 -0
- package/_shared/advisor/reference/cli-chain.md +45 -0
- package/_shared/advisor/reference/consult-format.md +41 -0
- package/_shared/advisor/reference/lifecycle.md +21 -0
- package/_shared/advisor/reference/sol-rung.md +34 -0
- package/_shared/advisor/reference/spawn-walk-log.md +31 -0
- package/_shared/advisor/reference/third-party-bind.md +30 -0
- package/_shared/advisor/reference/warm-up.md +34 -0
- package/_shared/advisor/scripts/codex_sol_advisor.py +514 -0
- package/_shared/advisor/scripts/config/advisor_scripts_constants/advisor_route_constants.py +21 -0
- package/_shared/advisor/scripts/config/advisor_scripts_constants/model_tier_run_validator_constants.py +19 -17
- package/_shared/advisor/scripts/config/advisor_scripts_constants/sol_advisor_constants.py +33 -0
- package/_shared/advisor/scripts/model_tier_run_validator.py +32 -9
- package/_shared/advisor/scripts/tests/test_codex_sol_advisor.py +636 -0
- package/_shared/advisor/scripts/tests/test_model_tier_run_validator.py +79 -0
- package/_shared/advisor/scripts/tests/test_tier_model_ids.py +39 -17
- package/_shared/advisor/scripts/tier_model_ids.py +24 -0
- package/commands/CLAUDE.md +1 -0
- package/commands/sr-loop.md +48 -0
- package/docs/references/CLAUDE.md +2 -1
- package/docs/references/advisor-tool.md +26 -8
- package/docs/references/team-advisor-skill.md +3 -3
- package/docs/references/weak-executor-advisor.md +91 -0
- package/hooks/blocking/test_fable_spawn_gate.py +18 -11
- package/package.json +1 -1
- package/skills/_shared/advisor/CLAUDE.md +1 -1
- package/skills/_shared/advisor/scripts/README.md +2 -0
- package/skills/grokify/SKILL.md +1 -1
- package/skills/grokify/templates/handoff-template.md +2 -2
- package/skills/orchestrator/SKILL.md +5 -4
- package/skills/team-advisor/SKILL.md +7 -4
- package/skills/team-advisor/reference/advisor-docs-review.md +207 -0
- package/skills/usage-pause/SKILL.md +1 -1
- package/skills/usage-pause/scripts/resolve_usage_window.py +32 -4
- package/skills/usage-pause/scripts/test_resolve_usage_window.py +26 -0
- package/skills/usage-pause/scripts/usage_pause_constants/resolve_usage_window_constants.py +3 -1
|
@@ -0,0 +1,636 @@
|
|
|
1
|
+
"""Behavioral tests for the executable Codex Sol advisor path."""
|
|
2
|
+
|
|
3
|
+
import io
|
|
4
|
+
import importlib.util
|
|
5
|
+
import json
|
|
6
|
+
import subprocess
|
|
7
|
+
import sys
|
|
8
|
+
from collections.abc import Callable
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from types import ModuleType
|
|
11
|
+
|
|
12
|
+
import pytest
|
|
13
|
+
|
|
14
|
+
_ProcessRunner = Callable[..., subprocess.CompletedProcess[str]]
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _load_sol_module() -> ModuleType:
|
|
18
|
+
scripts_root = Path(__file__).parent.parent
|
|
19
|
+
config_root = scripts_root / "config"
|
|
20
|
+
sys.path.insert(0, str(config_root))
|
|
21
|
+
specification = importlib.util.spec_from_file_location(
|
|
22
|
+
"codex_sol_advisor", scripts_root / "codex_sol_advisor.py"
|
|
23
|
+
)
|
|
24
|
+
assert specification is not None and specification.loader is not None
|
|
25
|
+
module = importlib.util.module_from_spec(specification)
|
|
26
|
+
sys.modules[specification.name] = module
|
|
27
|
+
specification.loader.exec_module(module)
|
|
28
|
+
return module
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
sol_advisor = _load_sol_module()
|
|
32
|
+
SCRIPTS_ROOT = Path(__file__).parent.parent
|
|
33
|
+
USAGE_PROBE_PATH = (
|
|
34
|
+
SCRIPTS_ROOT.parents[2]
|
|
35
|
+
/ "skills"
|
|
36
|
+
/ "codex-review"
|
|
37
|
+
/ "scripts"
|
|
38
|
+
/ "codex_usage_probe.py"
|
|
39
|
+
)
|
|
40
|
+
WINDOWS_SHIM_PATH = r"C:\Users\me\AppData\Roaming\npm\codex.cmd"
|
|
41
|
+
ENABLED_SETTING_BY_NAME = {sol_advisor.SOL_ENV_VAR: "1"}
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@pytest.fixture(autouse=True)
|
|
45
|
+
def _codex_on_search_path(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
46
|
+
monkeypatch.setattr(
|
|
47
|
+
sol_advisor.shutil,
|
|
48
|
+
"which",
|
|
49
|
+
lambda name: "codex" if name == sol_advisor.CODEX_EXECUTABLE else None,
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _probe_process(
|
|
54
|
+
payload: object,
|
|
55
|
+
returncode: int = 0,
|
|
56
|
+
) -> subprocess.CompletedProcess[str]:
|
|
57
|
+
return subprocess.CompletedProcess(
|
|
58
|
+
[sys.executable, str(USAGE_PROBE_PATH)],
|
|
59
|
+
returncode,
|
|
60
|
+
json.dumps(payload),
|
|
61
|
+
"",
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _event_stream(
|
|
66
|
+
session_id: str = "thread-1",
|
|
67
|
+
guidance: str = "PLAN\ninspect the change",
|
|
68
|
+
) -> str:
|
|
69
|
+
return "\n".join(
|
|
70
|
+
[
|
|
71
|
+
json.dumps({"type": "thread.started", "thread_id": session_id}),
|
|
72
|
+
json.dumps(
|
|
73
|
+
{
|
|
74
|
+
"type": "item.completed",
|
|
75
|
+
"item": {
|
|
76
|
+
"type": "agent_message",
|
|
77
|
+
"text": guidance,
|
|
78
|
+
},
|
|
79
|
+
}
|
|
80
|
+
),
|
|
81
|
+
]
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _two_step_process_runner(calls: list[list[str]], guidance: str) -> _ProcessRunner:
|
|
86
|
+
def process_runner(
|
|
87
|
+
arguments: list[str], **kwargs: object
|
|
88
|
+
) -> subprocess.CompletedProcess[str]:
|
|
89
|
+
calls.append(arguments)
|
|
90
|
+
if len(calls) == 1:
|
|
91
|
+
return _probe_process({"percent_left": 90})
|
|
92
|
+
return subprocess.CompletedProcess(
|
|
93
|
+
arguments, 0, _event_stream(guidance=guidance), ""
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
return process_runner
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def test_sol_flag_accepts_documented_truthy_values() -> None:
|
|
100
|
+
assert sol_advisor.is_sol_advisor_enabled({"ADVISOR_SOL_XHIGH": "yes"})
|
|
101
|
+
assert not sol_advisor.is_sol_advisor_enabled({"ADVISOR_SOL_XHIGH": "0"})
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def test_resolve_usage_probe_path_uses_supplied_home_directory(tmp_path: Path) -> None:
|
|
105
|
+
probe_path = sol_advisor.resolve_usage_probe_path(tmp_path)
|
|
106
|
+
|
|
107
|
+
assert probe_path == (
|
|
108
|
+
tmp_path
|
|
109
|
+
/ ".claude"
|
|
110
|
+
/ "skills"
|
|
111
|
+
/ "codex-review"
|
|
112
|
+
/ "scripts"
|
|
113
|
+
/ "codex_usage_probe.py"
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def test_argument_parser_accepts_bind_and_resume_modes() -> None:
|
|
118
|
+
parser = sol_advisor.build_argument_parser()
|
|
119
|
+
|
|
120
|
+
bind_arguments = parser.parse_args(["--bind", "--cwd", "."])
|
|
121
|
+
resume_arguments = parser.parse_args(
|
|
122
|
+
["--resume", "thread-1", "--cwd", "."]
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
assert bind_arguments.bind
|
|
126
|
+
assert bind_arguments.resume is None
|
|
127
|
+
assert resume_arguments.resume == "thread-1"
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def test_main_serializes_stable_result_field(monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]) -> None:
|
|
131
|
+
monkeypatch.setattr(
|
|
132
|
+
sol_advisor,
|
|
133
|
+
"run_codex_sol_advisor",
|
|
134
|
+
lambda **kwargs: sol_advisor.CodexSolAdvisorReply(
|
|
135
|
+
session_id="thread-1",
|
|
136
|
+
guidance="PLAN\ninspect",
|
|
137
|
+
successful=True,
|
|
138
|
+
reason=None,
|
|
139
|
+
is_fallback=False,
|
|
140
|
+
signal="PLAN",
|
|
141
|
+
sol_enabled=True,
|
|
142
|
+
selected_tier=sol_advisor.ADVISOR_MODEL_TIER,
|
|
143
|
+
outcome=sol_advisor.CODEX_BIND_SUCCESS_TOKEN,
|
|
144
|
+
fallback_kind=None,
|
|
145
|
+
),
|
|
146
|
+
)
|
|
147
|
+
monkeypatch.setattr(sys, "stdin", io.StringIO("first consult"))
|
|
148
|
+
|
|
149
|
+
exit_code = sol_advisor.main(["--bind", "--cwd", "."])
|
|
150
|
+
payload = json.loads(capsys.readouterr().out)
|
|
151
|
+
|
|
152
|
+
assert exit_code == 0
|
|
153
|
+
assert payload["result"] == "codex"
|
|
154
|
+
assert "outcome" not in payload
|
|
155
|
+
assert payload["sol_enabled"] is True
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def test_bind_and_resume_arguments_match_installed_codex_interface() -> None:
|
|
159
|
+
expected_common_arguments = [
|
|
160
|
+
"codex",
|
|
161
|
+
"exec",
|
|
162
|
+
"--model",
|
|
163
|
+
sol_advisor.ADVISOR_CODEX_MODEL_ID,
|
|
164
|
+
"--config",
|
|
165
|
+
'model_reasoning_effort="xhigh"',
|
|
166
|
+
"--sandbox",
|
|
167
|
+
"read-only",
|
|
168
|
+
"--json",
|
|
169
|
+
]
|
|
170
|
+
assert sol_advisor.build_codex_arguments("codex") == [
|
|
171
|
+
*expected_common_arguments,
|
|
172
|
+
"-",
|
|
173
|
+
]
|
|
174
|
+
assert sol_advisor.build_codex_arguments(
|
|
175
|
+
WINDOWS_SHIM_PATH, session_id="thread-1"
|
|
176
|
+
) == [
|
|
177
|
+
WINDOWS_SHIM_PATH,
|
|
178
|
+
*expected_common_arguments[1:],
|
|
179
|
+
"resume",
|
|
180
|
+
"thread-1",
|
|
181
|
+
"-",
|
|
182
|
+
]
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def test_resolve_codex_executable_prefers_the_env_var_override(
|
|
186
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
187
|
+
) -> None:
|
|
188
|
+
monkeypatch.setattr(sol_advisor.shutil, "which", lambda name: None)
|
|
189
|
+
|
|
190
|
+
resolved_executable = sol_advisor.resolve_codex_executable(
|
|
191
|
+
{sol_advisor.ADVISOR_CODEX_EXECUTABLE_ENV_VAR: WINDOWS_SHIM_PATH}
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
assert resolved_executable == WINDOWS_SHIM_PATH
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def test_resolve_codex_executable_falls_back_to_which_search(
|
|
198
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
199
|
+
) -> None:
|
|
200
|
+
monkeypatch.setattr(
|
|
201
|
+
sol_advisor.shutil,
|
|
202
|
+
"which",
|
|
203
|
+
lambda name: (
|
|
204
|
+
WINDOWS_SHIM_PATH if name == sol_advisor.CODEX_EXECUTABLE else None
|
|
205
|
+
),
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
resolved_executable = sol_advisor.resolve_codex_executable({})
|
|
209
|
+
|
|
210
|
+
assert resolved_executable == WINDOWS_SHIM_PATH
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def test_resolve_codex_executable_returns_none_when_unresolved(
|
|
214
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
215
|
+
) -> None:
|
|
216
|
+
monkeypatch.setattr(sol_advisor.shutil, "which", lambda name: None)
|
|
217
|
+
|
|
218
|
+
resolved_executable = sol_advisor.resolve_codex_executable({})
|
|
219
|
+
|
|
220
|
+
assert resolved_executable is None
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def test_bind_falls_back_with_a_clear_reason_when_executable_is_missing(
|
|
224
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
225
|
+
) -> None:
|
|
226
|
+
monkeypatch.setattr(sol_advisor.shutil, "which", lambda name: None)
|
|
227
|
+
calls: list[list[str]] = []
|
|
228
|
+
|
|
229
|
+
def process_runner(
|
|
230
|
+
arguments: list[str], **kwargs: object
|
|
231
|
+
) -> subprocess.CompletedProcess[str]:
|
|
232
|
+
calls.append(arguments)
|
|
233
|
+
return _probe_process({"percent_left": 90})
|
|
234
|
+
|
|
235
|
+
reply = sol_advisor.run_codex_sol_advisor(
|
|
236
|
+
prompt="first consult",
|
|
237
|
+
working_directory=Path("."),
|
|
238
|
+
preflight=None,
|
|
239
|
+
probe_path=USAGE_PROBE_PATH,
|
|
240
|
+
session_id=None,
|
|
241
|
+
process_runner=process_runner,
|
|
242
|
+
setting_by_name={sol_advisor.SOL_ENV_VAR: "1"},
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
assert not reply.successful
|
|
246
|
+
assert reply.is_fallback
|
|
247
|
+
assert reply.reason is not None
|
|
248
|
+
assert "codex" in reply.reason
|
|
249
|
+
assert reply.fallback_kind == sol_advisor.SOL_FALLBACK_KIND_BROKEN
|
|
250
|
+
assert calls == []
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
def test_policy_fallbacks_are_marked_declined() -> None:
|
|
254
|
+
def gate_closed_runner(
|
|
255
|
+
arguments: list[str], **kwargs: object
|
|
256
|
+
) -> subprocess.CompletedProcess[str]:
|
|
257
|
+
return _probe_process({"percent_left": 5})
|
|
258
|
+
|
|
259
|
+
disabled_reply = sol_advisor.run_codex_sol_advisor(
|
|
260
|
+
prompt="first consult",
|
|
261
|
+
working_directory=Path("."),
|
|
262
|
+
preflight=None,
|
|
263
|
+
probe_path=USAGE_PROBE_PATH,
|
|
264
|
+
session_id=None,
|
|
265
|
+
process_runner=gate_closed_runner,
|
|
266
|
+
setting_by_name={},
|
|
267
|
+
)
|
|
268
|
+
gate_closed_reply = sol_advisor.run_codex_sol_advisor(
|
|
269
|
+
prompt="first consult",
|
|
270
|
+
working_directory=Path("."),
|
|
271
|
+
preflight=None,
|
|
272
|
+
probe_path=USAGE_PROBE_PATH,
|
|
273
|
+
session_id=None,
|
|
274
|
+
process_runner=gate_closed_runner,
|
|
275
|
+
setting_by_name=ENABLED_SETTING_BY_NAME,
|
|
276
|
+
)
|
|
277
|
+
|
|
278
|
+
assert disabled_reply.fallback_kind == sol_advisor.SOL_FALLBACK_KIND_DECLINED
|
|
279
|
+
assert gate_closed_reply.fallback_kind == sol_advisor.SOL_FALLBACK_KIND_DECLINED
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
def test_probe_failure_fallback_is_marked_broken() -> None:
|
|
283
|
+
def failing_probe_runner(
|
|
284
|
+
arguments: list[str], **kwargs: object
|
|
285
|
+
) -> subprocess.CompletedProcess[str]:
|
|
286
|
+
return _probe_process({}, returncode=3)
|
|
287
|
+
|
|
288
|
+
reply = sol_advisor.run_codex_sol_advisor(
|
|
289
|
+
prompt="first consult",
|
|
290
|
+
working_directory=Path("."),
|
|
291
|
+
preflight=None,
|
|
292
|
+
probe_path=USAGE_PROBE_PATH,
|
|
293
|
+
session_id=None,
|
|
294
|
+
process_runner=failing_probe_runner,
|
|
295
|
+
setting_by_name=ENABLED_SETTING_BY_NAME,
|
|
296
|
+
)
|
|
297
|
+
|
|
298
|
+
assert reply.is_fallback
|
|
299
|
+
assert reply.fallback_kind == sol_advisor.SOL_FALLBACK_KIND_BROKEN
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
def test_enable_sol_flag_opens_the_rung_without_an_environment_flag(
|
|
303
|
+
monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
|
|
304
|
+
) -> None:
|
|
305
|
+
captured_settings: dict[str, str] = {}
|
|
306
|
+
|
|
307
|
+
def fake_advisor(**kwargs: object) -> object:
|
|
308
|
+
captured_settings.update(dict(kwargs["setting_by_name"])) # type: ignore[arg-type]
|
|
309
|
+
return sol_advisor._reply_fallback(
|
|
310
|
+
"probe declined",
|
|
311
|
+
True,
|
|
312
|
+
fallback_kind=sol_advisor.SOL_FALLBACK_KIND_DECLINED,
|
|
313
|
+
)
|
|
314
|
+
|
|
315
|
+
monkeypatch.setattr(sol_advisor, "run_codex_sol_advisor", fake_advisor)
|
|
316
|
+
monkeypatch.delenv(sol_advisor.SOL_ENV_VAR, raising=False)
|
|
317
|
+
monkeypatch.setattr(sys, "stdin", io.StringIO("first consult"))
|
|
318
|
+
|
|
319
|
+
exit_code = sol_advisor.main(
|
|
320
|
+
["--bind", "--cwd", ".", sol_advisor.SOL_ENABLE_FLAG]
|
|
321
|
+
)
|
|
322
|
+
payload = json.loads(capsys.readouterr().out)
|
|
323
|
+
|
|
324
|
+
assert exit_code == 1
|
|
325
|
+
assert captured_settings[sol_advisor.SOL_ENV_VAR] == "1"
|
|
326
|
+
assert payload["fallback_kind"] == sol_advisor.SOL_FALLBACK_KIND_DECLINED
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
def test_successful_probe_requires_finite_meter_above_configured_gate() -> None:
|
|
330
|
+
def probe_runner(arguments: list[str], **kwargs: object) -> subprocess.CompletedProcess[str]:
|
|
331
|
+
return _probe_process({"percent_left": 90})
|
|
332
|
+
|
|
333
|
+
preflight = sol_advisor.run_sol_preflight(USAGE_PROBE_PATH, probe_runner)
|
|
334
|
+
|
|
335
|
+
assert preflight.eligible
|
|
336
|
+
assert preflight.percent_left == 90
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
@pytest.mark.parametrize("percent_left", [10, 10.0, 10.000])
|
|
340
|
+
def test_meter_at_configured_threshold_falls_back(
|
|
341
|
+
percent_left: float,
|
|
342
|
+
) -> None:
|
|
343
|
+
def probe_runner(arguments: list[str], **kwargs: object) -> subprocess.CompletedProcess[str]:
|
|
344
|
+
return _probe_process({"percent_left": percent_left})
|
|
345
|
+
|
|
346
|
+
preflight = sol_advisor.run_sol_preflight(USAGE_PROBE_PATH, probe_runner)
|
|
347
|
+
|
|
348
|
+
assert not preflight.eligible
|
|
349
|
+
assert preflight.percent_left == percent_left
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
@pytest.mark.parametrize(
|
|
353
|
+
"payload",
|
|
354
|
+
[
|
|
355
|
+
{"percent_left": None},
|
|
356
|
+
{"percent_left": "90"},
|
|
357
|
+
{"percent_left": True},
|
|
358
|
+
{"percent_left": float("nan")},
|
|
359
|
+
["percent_left", 90],
|
|
360
|
+
],
|
|
361
|
+
)
|
|
362
|
+
def test_unknown_and_malformed_meter_falls_back(payload: object) -> None:
|
|
363
|
+
def probe_runner(arguments: list[str], **kwargs: object) -> subprocess.CompletedProcess[str]:
|
|
364
|
+
return _probe_process(payload)
|
|
365
|
+
|
|
366
|
+
preflight = sol_advisor.run_sol_preflight(USAGE_PROBE_PATH, probe_runner)
|
|
367
|
+
|
|
368
|
+
assert not preflight.eligible
|
|
369
|
+
assert preflight.percent_left is None
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
def test_malformed_probe_json_falls_back() -> None:
|
|
373
|
+
def probe_runner(arguments: list[str], **kwargs: object) -> subprocess.CompletedProcess[str]:
|
|
374
|
+
return subprocess.CompletedProcess(arguments, 0, "{broken", "")
|
|
375
|
+
|
|
376
|
+
preflight = sol_advisor.run_sol_preflight(USAGE_PROBE_PATH, probe_runner)
|
|
377
|
+
|
|
378
|
+
assert not preflight.eligible
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
def test_probe_nonzero_exit_falls_back() -> None:
|
|
382
|
+
def probe_runner(arguments: list[str], **kwargs: object) -> subprocess.CompletedProcess[str]:
|
|
383
|
+
return _probe_process({"percent_left": 90}, returncode=2)
|
|
384
|
+
|
|
385
|
+
preflight = sol_advisor.run_sol_preflight(USAGE_PROBE_PATH, probe_runner)
|
|
386
|
+
|
|
387
|
+
assert not preflight.eligible
|
|
388
|
+
|
|
389
|
+
|
|
390
|
+
def test_probe_timeout_falls_back() -> None:
|
|
391
|
+
def probe_runner(arguments: list[str], **kwargs: object) -> subprocess.CompletedProcess[str]:
|
|
392
|
+
raise subprocess.TimeoutExpired(arguments, 30)
|
|
393
|
+
|
|
394
|
+
preflight = sol_advisor.run_sol_preflight(USAGE_PROBE_PATH, probe_runner)
|
|
395
|
+
|
|
396
|
+
assert not preflight.eligible
|
|
397
|
+
assert "timed out" in preflight.reason
|
|
398
|
+
|
|
399
|
+
|
|
400
|
+
@pytest.mark.parametrize("signal", ["ENDORSE", "CORRECTION", "PLAN", "STOP"])
|
|
401
|
+
def test_jsonl_parser_accepts_each_exact_guidance_signal(signal: str) -> None:
|
|
402
|
+
reply = sol_advisor.parse_codex_jsonl_reply(
|
|
403
|
+
_event_stream(guidance=f"{signal}\nadditional guidance"),
|
|
404
|
+
existing_session_id=None,
|
|
405
|
+
is_sol_enabled=True,
|
|
406
|
+
)
|
|
407
|
+
|
|
408
|
+
assert reply.successful
|
|
409
|
+
assert not reply.is_fallback
|
|
410
|
+
assert reply.session_id == "thread-1"
|
|
411
|
+
assert reply.guidance == f"{signal}\nadditional guidance"
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
@pytest.mark.parametrize(
|
|
415
|
+
"jsonl_text",
|
|
416
|
+
[
|
|
417
|
+
"{broken",
|
|
418
|
+
json.dumps({"type": "thread.started", "thread_id": "thread-1"}),
|
|
419
|
+
_event_stream(session_id="", guidance="PLAN\ninspect"),
|
|
420
|
+
_event_stream(guidance="ENDORSE: ready"),
|
|
421
|
+
_event_stream(guidance="\n unknown signal\nmore"),
|
|
422
|
+
json.dumps(
|
|
423
|
+
{
|
|
424
|
+
"type": "item.completed",
|
|
425
|
+
"item": {"type": "agent_message", "text": "PLAN"},
|
|
426
|
+
}
|
|
427
|
+
),
|
|
428
|
+
],
|
|
429
|
+
)
|
|
430
|
+
def test_jsonl_parser_returns_typed_fallback_for_invalid_reply(
|
|
431
|
+
jsonl_text: str,
|
|
432
|
+
) -> None:
|
|
433
|
+
reply = sol_advisor.parse_codex_jsonl_reply(
|
|
434
|
+
jsonl_text,
|
|
435
|
+
existing_session_id=None,
|
|
436
|
+
is_sol_enabled=True,
|
|
437
|
+
)
|
|
438
|
+
|
|
439
|
+
assert not reply.successful
|
|
440
|
+
assert reply.is_fallback
|
|
441
|
+
assert reply.session_id is None
|
|
442
|
+
assert reply.guidance is None
|
|
443
|
+
assert reply.reason
|
|
444
|
+
|
|
445
|
+
|
|
446
|
+
def test_bind_runs_probe_then_codex_with_read_only_xhigh_settings() -> None:
|
|
447
|
+
calls: list[tuple[list[str], dict[str, object]]] = []
|
|
448
|
+
|
|
449
|
+
def process_runner(
|
|
450
|
+
arguments: list[str], **kwargs: object
|
|
451
|
+
) -> subprocess.CompletedProcess[str]:
|
|
452
|
+
calls.append((arguments, kwargs))
|
|
453
|
+
if len(calls) == 1:
|
|
454
|
+
return _probe_process({"percent_left": 90})
|
|
455
|
+
return subprocess.CompletedProcess(
|
|
456
|
+
arguments, 0, _event_stream(guidance="PLAN\ninspect"), ""
|
|
457
|
+
)
|
|
458
|
+
|
|
459
|
+
reply = sol_advisor.run_codex_sol_advisor(
|
|
460
|
+
prompt="reply only",
|
|
461
|
+
working_directory=Path("."),
|
|
462
|
+
preflight=None,
|
|
463
|
+
probe_path=USAGE_PROBE_PATH,
|
|
464
|
+
session_id=None,
|
|
465
|
+
process_runner=process_runner,
|
|
466
|
+
setting_by_name=ENABLED_SETTING_BY_NAME,
|
|
467
|
+
)
|
|
468
|
+
|
|
469
|
+
assert reply.successful
|
|
470
|
+
assert len(calls) == 2
|
|
471
|
+
assert calls[1][0] == sol_advisor.build_codex_arguments("codex")
|
|
472
|
+
assert calls[1][1]["cwd"] == "."
|
|
473
|
+
assert calls[1][1]["shell"] is False
|
|
474
|
+
assert calls[1][1]["timeout"]
|
|
475
|
+
|
|
476
|
+
|
|
477
|
+
def test_team_advisor_path_preserves_sol_routing_fields() -> None:
|
|
478
|
+
team_advisor_path = SCRIPTS_ROOT.parents[2] / "skills" / "team-advisor" / "SKILL.md"
|
|
479
|
+
sol_rung_path = SCRIPTS_ROOT.parent / "reference" / "sol-rung.md"
|
|
480
|
+
assert "advisor-protocol.md" in team_advisor_path.read_text(encoding="utf-8")
|
|
481
|
+
sol_rung = sol_rung_path.read_text(encoding="utf-8")
|
|
482
|
+
assert "codex_sol_advisor.py" in sol_rung
|
|
483
|
+
assert "--resume <session_id>" in sol_rung
|
|
484
|
+
calls: list[list[str]] = []
|
|
485
|
+
|
|
486
|
+
reply = sol_advisor.run_codex_sol_advisor(
|
|
487
|
+
prompt="first consult",
|
|
488
|
+
working_directory=Path("."),
|
|
489
|
+
preflight=None,
|
|
490
|
+
probe_path=USAGE_PROBE_PATH,
|
|
491
|
+
session_id=None,
|
|
492
|
+
process_runner=_two_step_process_runner(calls, guidance="PLAN\ninspect"),
|
|
493
|
+
setting_by_name=ENABLED_SETTING_BY_NAME,
|
|
494
|
+
)
|
|
495
|
+
|
|
496
|
+
assert reply.successful
|
|
497
|
+
assert reply.sol_enabled
|
|
498
|
+
assert reply.selected_tier == sol_advisor.ADVISOR_MODEL_TIER
|
|
499
|
+
assert reply.outcome == sol_advisor.CODEX_BIND_SUCCESS_TOKEN
|
|
500
|
+
assert reply.signal == "PLAN"
|
|
501
|
+
assert reply.guidance == "PLAN\ninspect"
|
|
502
|
+
|
|
503
|
+
|
|
504
|
+
def test_team_advisor_path_uses_fable_result_when_sol_gate_is_closed() -> None:
|
|
505
|
+
calls: list[list[str]] = []
|
|
506
|
+
|
|
507
|
+
def process_runner(
|
|
508
|
+
arguments: list[str], **kwargs: object
|
|
509
|
+
) -> subprocess.CompletedProcess[str]:
|
|
510
|
+
calls.append(arguments)
|
|
511
|
+
return _probe_process({"percent_left": 10})
|
|
512
|
+
|
|
513
|
+
reply = sol_advisor.run_codex_sol_advisor(
|
|
514
|
+
prompt="first consult",
|
|
515
|
+
working_directory=Path("."),
|
|
516
|
+
preflight=None,
|
|
517
|
+
probe_path=USAGE_PROBE_PATH,
|
|
518
|
+
session_id=None,
|
|
519
|
+
process_runner=process_runner,
|
|
520
|
+
setting_by_name=ENABLED_SETTING_BY_NAME,
|
|
521
|
+
)
|
|
522
|
+
|
|
523
|
+
assert not reply.successful
|
|
524
|
+
assert reply.is_fallback
|
|
525
|
+
assert reply.sol_enabled
|
|
526
|
+
assert reply.selected_tier == sol_advisor.ADVISOR_FALLBACK_TIER
|
|
527
|
+
assert reply.outcome == sol_advisor.ADVISOR_FALLBACK_RESULT
|
|
528
|
+
assert len(calls) == 1
|
|
529
|
+
|
|
530
|
+
|
|
531
|
+
def test_disabled_flag_uses_default_optional_routing_inputs(
|
|
532
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
533
|
+
) -> None:
|
|
534
|
+
monkeypatch.delenv(sol_advisor.SOL_ENV_VAR, raising=False)
|
|
535
|
+
reply = sol_advisor.run_codex_sol_advisor(
|
|
536
|
+
prompt="first consult",
|
|
537
|
+
working_directory=Path("."),
|
|
538
|
+
preflight=sol_advisor.SolPreflight(
|
|
539
|
+
eligible=True,
|
|
540
|
+
percent_left=90,
|
|
541
|
+
reason="test preflight",
|
|
542
|
+
),
|
|
543
|
+
probe_path=None,
|
|
544
|
+
setting_by_name=None,
|
|
545
|
+
session_id=None,
|
|
546
|
+
process_runner=subprocess.run,
|
|
547
|
+
)
|
|
548
|
+
|
|
549
|
+
assert reply.is_fallback
|
|
550
|
+
assert reply.reason == "Sol advisor flag is disabled"
|
|
551
|
+
|
|
552
|
+
|
|
553
|
+
def test_resume_runs_the_usage_gate_before_codex() -> None:
|
|
554
|
+
calls: list[list[str]] = []
|
|
555
|
+
|
|
556
|
+
reply = sol_advisor.run_codex_sol_advisor(
|
|
557
|
+
prompt="resume",
|
|
558
|
+
working_directory=Path("."),
|
|
559
|
+
preflight=None,
|
|
560
|
+
session_id="thread-1",
|
|
561
|
+
probe_path=USAGE_PROBE_PATH,
|
|
562
|
+
process_runner=_two_step_process_runner(calls, guidance="ENDORSE\nready"),
|
|
563
|
+
setting_by_name=ENABLED_SETTING_BY_NAME,
|
|
564
|
+
)
|
|
565
|
+
|
|
566
|
+
assert reply.successful
|
|
567
|
+
assert calls == [
|
|
568
|
+
[sys.executable, str(USAGE_PROBE_PATH)],
|
|
569
|
+
sol_advisor.build_codex_arguments("codex", session_id="thread-1"),
|
|
570
|
+
]
|
|
571
|
+
|
|
572
|
+
|
|
573
|
+
@pytest.mark.parametrize(
|
|
574
|
+
"codex_failure",
|
|
575
|
+
[
|
|
576
|
+
"nonzero",
|
|
577
|
+
"timeout",
|
|
578
|
+
"malformed_jsonl",
|
|
579
|
+
"missing_session",
|
|
580
|
+
"missing_guidance",
|
|
581
|
+
"invalid_signal",
|
|
582
|
+
],
|
|
583
|
+
)
|
|
584
|
+
def test_codex_failure_modes_always_return_fallback(
|
|
585
|
+
codex_failure: str,
|
|
586
|
+
) -> None:
|
|
587
|
+
def process_runner(
|
|
588
|
+
arguments: list[str], **kwargs: object
|
|
589
|
+
) -> subprocess.CompletedProcess[str]:
|
|
590
|
+
if len(arguments) == 2:
|
|
591
|
+
return _probe_process({"percent_left": 90})
|
|
592
|
+
if codex_failure == "timeout":
|
|
593
|
+
raise subprocess.TimeoutExpired(arguments, 30)
|
|
594
|
+
if codex_failure == "nonzero":
|
|
595
|
+
return subprocess.CompletedProcess(
|
|
596
|
+
arguments, 3, _event_stream(guidance="PLAN"), ""
|
|
597
|
+
)
|
|
598
|
+
if codex_failure == "malformed_jsonl":
|
|
599
|
+
return subprocess.CompletedProcess(arguments, 0, "{broken", "")
|
|
600
|
+
if codex_failure == "missing_session":
|
|
601
|
+
return subprocess.CompletedProcess(
|
|
602
|
+
arguments,
|
|
603
|
+
0,
|
|
604
|
+
json.dumps(
|
|
605
|
+
{
|
|
606
|
+
"type": "item.completed",
|
|
607
|
+
"item": {"type": "agent_message", "text": "PLAN"},
|
|
608
|
+
}
|
|
609
|
+
),
|
|
610
|
+
"",
|
|
611
|
+
)
|
|
612
|
+
if codex_failure == "missing_guidance":
|
|
613
|
+
return subprocess.CompletedProcess(
|
|
614
|
+
arguments,
|
|
615
|
+
0,
|
|
616
|
+
json.dumps({"type": "thread.started", "thread_id": "thread-1"}),
|
|
617
|
+
"",
|
|
618
|
+
)
|
|
619
|
+
return subprocess.CompletedProcess(
|
|
620
|
+
arguments, 0, _event_stream(guidance="PLAN: inspect"), ""
|
|
621
|
+
)
|
|
622
|
+
|
|
623
|
+
reply = sol_advisor.run_codex_sol_advisor(
|
|
624
|
+
prompt="bind",
|
|
625
|
+
working_directory=Path("."),
|
|
626
|
+
preflight=None,
|
|
627
|
+
probe_path=USAGE_PROBE_PATH,
|
|
628
|
+
session_id=None,
|
|
629
|
+
process_runner=process_runner,
|
|
630
|
+
setting_by_name=ENABLED_SETTING_BY_NAME,
|
|
631
|
+
)
|
|
632
|
+
|
|
633
|
+
assert not reply.successful
|
|
634
|
+
assert reply.is_fallback
|
|
635
|
+
assert reply.session_id is None
|
|
636
|
+
assert reply.guidance is None
|