claude-dev-env 2.11.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/_shared/advisor/reference/sol-rung.md +5 -2
- package/_shared/advisor/reference/warm-up.md +1 -0
- package/_shared/advisor/scripts/codex_sol_advisor.py +96 -31
- package/_shared/advisor/scripts/config/advisor_scripts_constants/sol_advisor_constants.py +5 -0
- package/_shared/advisor/scripts/tests/test_codex_sol_advisor.py +173 -11
- package/commands/CLAUDE.md +1 -0
- package/commands/sr-loop.md +48 -0
- package/package.json +1 -1
- 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
|
@@ -5,8 +5,11 @@ Open this when `ADVISOR_SOL_XHIGH` is set and a bind is starting.
|
|
|
5
5
|
|
|
6
6
|
## Flag
|
|
7
7
|
|
|
8
|
-
`ADVISOR_SOL_XHIGH=1` (or `true` / `yes` / `on`) opens the rung
|
|
9
|
-
Flag off: the walk starts at the host's Claude ladder, Fable first.
|
|
8
|
+
`ADVISOR_SOL_XHIGH=1` (or `true` / `yes` / `on`) opens the rung. Two channels exist: set the variable in the helper's process environment, or pass `--enable-sol` on the helper invocation — the CLI flag opens the rung for that run without touching the environment.
|
|
9
|
+
Flag off both ways: the walk starts at the host's Claude ladder, Fable first.
|
|
10
|
+
A Windows `setx` write only updates the persisted user environment; only a process started after that write inherits the new value, so an already-running session either sets the flag in its own invoking process environment or passes `--enable-sol`.
|
|
11
|
+
|
|
12
|
+
Every fallback reply carries a `fallback_kind` field: `declined` when policy closed the rung (flag off, usage meter at or below the gate) and `broken` when the Sol path itself failed (missing executable, spawn error, timeout, malformed reply). A `broken` fallback is a defect to report, not a routing outcome.
|
|
10
13
|
|
|
11
14
|
## Preflight
|
|
12
15
|
|
|
@@ -25,6 +25,7 @@ State plainly:
|
|
|
25
25
|
- Reply via SendMessage to whoever sent the consult, by name — each reply goes back to its own sender, and many different consumers may reach this one agent.
|
|
26
26
|
- Treat each consult on its own terms, keyed to the sender's stated assignment. Different consumers' consults will interleave in this one transcript — keep each consumer's context separate, and blend only when a consult explicitly asks for that.
|
|
27
27
|
- If a consult re-raises a question already answered, with nothing new attached, reply by restating the prior answer and naming it as a restatement.
|
|
28
|
+
- Every reply, including this first bind turn, opens its first line with exactly one of the four uppercase signal words — `ENDORSE`, `CORRECTION`, `PLAN`, `STOP` — and nothing else on that line. Standing by after the bind is itself an `ENDORSE` of the charter, stated as that first line.
|
|
28
29
|
|
|
29
30
|
The agent finishes its first turn standing by. `SendMessage` alone resumes it; between consults it waits quietly.
|
|
30
31
|
|
|
@@ -7,6 +7,7 @@ import importlib
|
|
|
7
7
|
import json
|
|
8
8
|
import math
|
|
9
9
|
import os
|
|
10
|
+
import shutil
|
|
10
11
|
import subprocess
|
|
11
12
|
import sys
|
|
12
13
|
from collections.abc import Callable, Mapping, Sequence
|
|
@@ -20,6 +21,7 @@ if _config_directory_text not in sys.path:
|
|
|
20
21
|
sys.path.insert(0, _config_directory_text)
|
|
21
22
|
|
|
22
23
|
from advisor_scripts_constants.sol_advisor_constants import ( # noqa: E402
|
|
24
|
+
ADVISOR_CODEX_EXECUTABLE_ENV_VAR,
|
|
23
25
|
CODEX_CONFIG_FLAG,
|
|
24
26
|
CODEX_EXECUTABLE,
|
|
25
27
|
CODEX_EXEC_SUBCOMMAND,
|
|
@@ -35,6 +37,10 @@ from advisor_scripts_constants.sol_advisor_constants import ( # noqa: E402
|
|
|
35
37
|
SOL_CODEX_TIMEOUT_REASON,
|
|
36
38
|
SOL_CODEX_TIMEOUT_SECONDS,
|
|
37
39
|
SOL_ENV_VAR,
|
|
40
|
+
SOL_ENABLE_FLAG,
|
|
41
|
+
SOL_EXECUTABLE_NOT_FOUND_REASON,
|
|
42
|
+
SOL_FALLBACK_KIND_BROKEN,
|
|
43
|
+
SOL_FALLBACK_KIND_DECLINED,
|
|
38
44
|
SOL_INVALID_SIGNAL_REASON,
|
|
39
45
|
SOL_MALFORMED_JSONL_REASON,
|
|
40
46
|
SOL_MISSING_SESSION_REASON,
|
|
@@ -63,6 +69,7 @@ class SolPreflight:
|
|
|
63
69
|
eligible: bool
|
|
64
70
|
percent_left: float | None
|
|
65
71
|
reason: str
|
|
72
|
+
fallback_kind: str | None = None
|
|
66
73
|
|
|
67
74
|
|
|
68
75
|
@dataclass(frozen=True)
|
|
@@ -78,17 +85,21 @@ class CodexSolAdvisorReply:
|
|
|
78
85
|
sol_enabled: bool
|
|
79
86
|
selected_tier: str
|
|
80
87
|
outcome: str
|
|
88
|
+
fallback_kind: str | None
|
|
81
89
|
|
|
82
90
|
|
|
83
91
|
def _preflight_fallback(
|
|
84
|
-
reason: str,
|
|
92
|
+
reason: str,
|
|
93
|
+
percent_left: float | None,
|
|
94
|
+
fallback_kind: str = SOL_FALLBACK_KIND_BROKEN,
|
|
85
95
|
) -> SolPreflight:
|
|
86
|
-
return SolPreflight(False, percent_left, reason)
|
|
96
|
+
return SolPreflight(False, percent_left, reason, fallback_kind)
|
|
87
97
|
|
|
88
98
|
|
|
89
99
|
def _reply_fallback(
|
|
90
100
|
reason: str,
|
|
91
101
|
is_sol_enabled: bool,
|
|
102
|
+
fallback_kind: str | None = SOL_FALLBACK_KIND_BROKEN,
|
|
92
103
|
) -> CodexSolAdvisorReply:
|
|
93
104
|
return CodexSolAdvisorReply(
|
|
94
105
|
session_id=None,
|
|
@@ -100,6 +111,7 @@ def _reply_fallback(
|
|
|
100
111
|
sol_enabled=is_sol_enabled,
|
|
101
112
|
selected_tier=ADVISOR_FALLBACK_TIER,
|
|
102
113
|
outcome=ADVISOR_FALLBACK_RESULT,
|
|
114
|
+
fallback_kind=fallback_kind,
|
|
103
115
|
)
|
|
104
116
|
|
|
105
117
|
|
|
@@ -118,9 +130,16 @@ def _reply_success(
|
|
|
118
130
|
sol_enabled=True,
|
|
119
131
|
selected_tier=ADVISOR_MODEL_TIER,
|
|
120
132
|
outcome=CODEX_BIND_SUCCESS_TOKEN,
|
|
133
|
+
fallback_kind=None,
|
|
121
134
|
)
|
|
122
135
|
|
|
123
136
|
|
|
137
|
+
def _resolved_setting_by_name(
|
|
138
|
+
setting_by_name: Mapping[str, str] | None,
|
|
139
|
+
) -> Mapping[str, str]:
|
|
140
|
+
return os.environ if setting_by_name is None else setting_by_name
|
|
141
|
+
|
|
142
|
+
|
|
124
143
|
def is_sol_advisor_enabled(
|
|
125
144
|
setting_by_name: Mapping[str, str] | None,
|
|
126
145
|
) -> bool:
|
|
@@ -132,7 +151,7 @@ def is_sol_advisor_enabled(
|
|
|
132
151
|
Returns:
|
|
133
152
|
Whether the Sol feature flag contains a recognized truthy value.
|
|
134
153
|
"""
|
|
135
|
-
resolved_setting_by_name =
|
|
154
|
+
resolved_setting_by_name = _resolved_setting_by_name(setting_by_name)
|
|
136
155
|
return (
|
|
137
156
|
resolved_setting_by_name.get(SOL_ENV_VAR, "").strip().lower()
|
|
138
157
|
in ALL_SOL_TRUTHY_VALUES
|
|
@@ -223,11 +242,11 @@ def run_sol_preflight(
|
|
|
223
242
|
return _preflight_fallback(
|
|
224
243
|
f"{SOL_PREFLIGHT_FAILURE_REASON}: usage meter is unknown", None
|
|
225
244
|
)
|
|
226
|
-
usage_gate = _load_usage_gate(probe_path)
|
|
227
245
|
if not usage_gate(percent_left):
|
|
228
246
|
return _preflight_fallback(
|
|
229
247
|
f"{SOL_PREFLIGHT_FAILURE_REASON}: usage meter is at or below the gate",
|
|
230
248
|
percent_left,
|
|
249
|
+
fallback_kind=SOL_FALLBACK_KIND_DECLINED,
|
|
231
250
|
)
|
|
232
251
|
except subprocess.TimeoutExpired as probe_error:
|
|
233
252
|
return _preflight_fallback(f"{SOL_PROBE_TIMEOUT_REASON}: {probe_error}", None)
|
|
@@ -245,17 +264,44 @@ def run_sol_preflight(
|
|
|
245
264
|
return SolPreflight(True, percent_left, "usage meter is above the Sol gate")
|
|
246
265
|
|
|
247
266
|
|
|
248
|
-
def
|
|
267
|
+
def resolve_codex_executable(
|
|
268
|
+
setting_by_name: Mapping[str, str] | None,
|
|
269
|
+
) -> str | None:
|
|
270
|
+
"""Resolve the Codex CLI executable to an invocable name or path.
|
|
271
|
+
|
|
272
|
+
A bare "codex" name fails Windows `CreateProcess`, since the npm shim
|
|
273
|
+
directory holds only `codex` (a sh script), `codex.cmd`, and `codex.ps1`.
|
|
274
|
+
`shutil.which` finds `codex.cmd` via `PATHEXT`. An explicit override
|
|
275
|
+
always wins and is trusted without a `which` check.
|
|
276
|
+
|
|
277
|
+
Args:
|
|
278
|
+
setting_by_name: Optional environment-like settings mapping.
|
|
279
|
+
|
|
280
|
+
Returns:
|
|
281
|
+
An invocable executable name or path, or None when unresolved.
|
|
282
|
+
"""
|
|
283
|
+
resolved_setting_by_name = _resolved_setting_by_name(setting_by_name)
|
|
284
|
+
executable_override = resolved_setting_by_name.get(ADVISOR_CODEX_EXECUTABLE_ENV_VAR, "").strip()
|
|
285
|
+
if executable_override:
|
|
286
|
+
return executable_override
|
|
287
|
+
return shutil.which(CODEX_EXECUTABLE)
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
def build_codex_arguments(
|
|
291
|
+
codex_executable: str,
|
|
292
|
+
session_id: str | None = None,
|
|
293
|
+
) -> list[str]:
|
|
249
294
|
"""Build the installed CLI's shell-free bind or resume argv.
|
|
250
295
|
|
|
251
296
|
Args:
|
|
297
|
+
codex_executable: Resolved executable name or path to invoke.
|
|
252
298
|
session_id: Optional existing session to resume.
|
|
253
299
|
|
|
254
300
|
Returns:
|
|
255
301
|
The shell-free Codex command argument vector.
|
|
256
302
|
"""
|
|
257
303
|
command_arguments = [
|
|
258
|
-
|
|
304
|
+
codex_executable,
|
|
259
305
|
CODEX_EXEC_SUBCOMMAND,
|
|
260
306
|
CODEX_MODEL_FLAG,
|
|
261
307
|
ADVISOR_CODEX_MODEL_ID,
|
|
@@ -333,6 +379,21 @@ def parse_codex_jsonl_reply(
|
|
|
333
379
|
return _reply_success(discovered_session_id, final_guidance, guidance_signal)
|
|
334
380
|
|
|
335
381
|
|
|
382
|
+
def _resolve_sol_preflight(
|
|
383
|
+
preflight: SolPreflight | None,
|
|
384
|
+
probe_path: Path | None,
|
|
385
|
+
process_runner: Callable[..., subprocess.CompletedProcess[str]],
|
|
386
|
+
) -> SolPreflight:
|
|
387
|
+
if preflight is not None:
|
|
388
|
+
return preflight
|
|
389
|
+
resolved_probe_path = (
|
|
390
|
+
resolve_usage_probe_path(Path.home()) if probe_path is None else probe_path
|
|
391
|
+
)
|
|
392
|
+
return run_sol_preflight(
|
|
393
|
+
probe_path=resolved_probe_path, process_runner=process_runner
|
|
394
|
+
)
|
|
395
|
+
|
|
396
|
+
|
|
336
397
|
def run_codex_sol_advisor(
|
|
337
398
|
prompt: str,
|
|
338
399
|
working_directory: Path,
|
|
@@ -356,26 +417,25 @@ def run_codex_sol_advisor(
|
|
|
356
417
|
Returns:
|
|
357
418
|
The parsed Sol guidance or an explicit Fable fallback reply.
|
|
358
419
|
"""
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
probe_path=(
|
|
365
|
-
resolve_usage_probe_path(Path.home())
|
|
366
|
-
if probe_path is None
|
|
367
|
-
else probe_path
|
|
368
|
-
),
|
|
369
|
-
process_runner=process_runner,
|
|
420
|
+
if not is_sol_advisor_enabled(setting_by_name):
|
|
421
|
+
return _reply_fallback(
|
|
422
|
+
"Sol advisor flag is disabled",
|
|
423
|
+
False,
|
|
424
|
+
fallback_kind=SOL_FALLBACK_KIND_DECLINED,
|
|
370
425
|
)
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
426
|
+
codex_executable = resolve_codex_executable(setting_by_name)
|
|
427
|
+
if codex_executable is None:
|
|
428
|
+
return _reply_fallback(SOL_EXECUTABLE_NOT_FOUND_REASON, True)
|
|
429
|
+
resolved_preflight = _resolve_sol_preflight(preflight, probe_path, process_runner)
|
|
374
430
|
if not resolved_preflight.eligible:
|
|
375
|
-
return _reply_fallback(
|
|
431
|
+
return _reply_fallback(
|
|
432
|
+
resolved_preflight.reason,
|
|
433
|
+
True,
|
|
434
|
+
fallback_kind=resolved_preflight.fallback_kind,
|
|
435
|
+
)
|
|
376
436
|
try:
|
|
377
437
|
completed_process = process_runner(
|
|
378
|
-
build_codex_arguments(session_id=session_id),
|
|
438
|
+
build_codex_arguments(codex_executable, session_id=session_id),
|
|
379
439
|
cwd=str(working_directory),
|
|
380
440
|
input=prompt,
|
|
381
441
|
capture_output=True,
|
|
@@ -385,22 +445,18 @@ def run_codex_sol_advisor(
|
|
|
385
445
|
timeout=SOL_CODEX_TIMEOUT_SECONDS,
|
|
386
446
|
)
|
|
387
447
|
except subprocess.TimeoutExpired as bind_error:
|
|
388
|
-
return _reply_fallback(
|
|
389
|
-
f"{SOL_CODEX_TIMEOUT_REASON}: {bind_error}", is_sol_enabled
|
|
390
|
-
)
|
|
448
|
+
return _reply_fallback(f"{SOL_CODEX_TIMEOUT_REASON}: {bind_error}", True)
|
|
391
449
|
except (OSError, subprocess.SubprocessError) as bind_error:
|
|
392
|
-
return _reply_fallback(
|
|
393
|
-
f"{SOL_BIND_FAILURE_REASON}: {bind_error}", is_sol_enabled
|
|
394
|
-
)
|
|
450
|
+
return _reply_fallback(f"{SOL_BIND_FAILURE_REASON}: {bind_error}", True)
|
|
395
451
|
if completed_process.returncode != 0:
|
|
396
452
|
return _reply_fallback(
|
|
397
453
|
f"{SOL_BIND_FAILURE_REASON}: process exit {completed_process.returncode}",
|
|
398
|
-
|
|
454
|
+
True,
|
|
399
455
|
)
|
|
400
456
|
return parse_codex_jsonl_reply(
|
|
401
457
|
completed_process.stdout,
|
|
402
458
|
existing_session_id=session_id,
|
|
403
|
-
is_sol_enabled=
|
|
459
|
+
is_sol_enabled=True,
|
|
404
460
|
)
|
|
405
461
|
|
|
406
462
|
|
|
@@ -417,6 +473,12 @@ def build_argument_parser() -> argparse.ArgumentParser:
|
|
|
417
473
|
mode_group.add_argument("--bind", action="store_true")
|
|
418
474
|
mode_group.add_argument("--resume", metavar=SOL_SESSION_ID_METAVAR)
|
|
419
475
|
argument_parser.add_argument("--cwd", required=True, type=Path)
|
|
476
|
+
argument_parser.add_argument(
|
|
477
|
+
SOL_ENABLE_FLAG,
|
|
478
|
+
dest="is_sol_requested",
|
|
479
|
+
action="store_true",
|
|
480
|
+
help="Open the Sol rung for this invocation without an environment flag.",
|
|
481
|
+
)
|
|
420
482
|
return argument_parser
|
|
421
483
|
|
|
422
484
|
|
|
@@ -430,12 +492,15 @@ def main(all_cli_arguments: Sequence[str]) -> int:
|
|
|
430
492
|
Zero for a successful Sol response, or one for an explicit fallback.
|
|
431
493
|
"""
|
|
432
494
|
parsed_arguments = build_argument_parser().parse_args(list(all_cli_arguments))
|
|
495
|
+
setting_by_name: Mapping[str, str] = os.environ
|
|
496
|
+
if parsed_arguments.is_sol_requested:
|
|
497
|
+
setting_by_name = {**os.environ, SOL_ENV_VAR: "1"}
|
|
433
498
|
advisor_reply = run_codex_sol_advisor(
|
|
434
499
|
prompt=sys.stdin.read(),
|
|
435
500
|
working_directory=parsed_arguments.cwd,
|
|
436
501
|
preflight=None,
|
|
437
502
|
probe_path=None,
|
|
438
|
-
setting_by_name=
|
|
503
|
+
setting_by_name=setting_by_name,
|
|
439
504
|
session_id=parsed_arguments.resume if not parsed_arguments.bind else None,
|
|
440
505
|
process_runner=subprocess.run,
|
|
441
506
|
)
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
CODEX_EXECUTABLE: str = "codex"
|
|
6
|
+
ADVISOR_CODEX_EXECUTABLE_ENV_VAR: str = "ADVISOR_CODEX_EXECUTABLE"
|
|
6
7
|
CODEX_READ_ONLY_SANDBOX: str = "read-only"
|
|
7
8
|
CODEX_JSON_FLAG: str = "--json"
|
|
8
9
|
CODEX_MODEL_FLAG: str = "--model"
|
|
@@ -26,3 +27,7 @@ SOL_CODEX_TIMEOUT_REASON: str = "Codex Sol xhigh request timed out"
|
|
|
26
27
|
SOL_MALFORMED_JSONL_REASON: str = "Codex Sol xhigh returned malformed JSONL"
|
|
27
28
|
SOL_MISSING_SESSION_REASON: str = "Codex Sol xhigh returned no session id"
|
|
28
29
|
SOL_INVALID_SIGNAL_REASON: str = "Codex Sol xhigh returned an invalid guidance signal"
|
|
30
|
+
SOL_EXECUTABLE_NOT_FOUND_REASON: str = "Codex Sol xhigh could not find the codex executable on PATH"
|
|
31
|
+
SOL_FALLBACK_KIND_DECLINED: str = "declined"
|
|
32
|
+
SOL_FALLBACK_KIND_BROKEN: str = "broken"
|
|
33
|
+
SOL_ENABLE_FLAG: str = "--enable-sol"
|
|
@@ -37,6 +37,17 @@ USAGE_PROBE_PATH = (
|
|
|
37
37
|
/ "scripts"
|
|
38
38
|
/ "codex_usage_probe.py"
|
|
39
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
|
+
)
|
|
40
51
|
|
|
41
52
|
|
|
42
53
|
def _probe_process(
|
|
@@ -130,6 +141,7 @@ def test_main_serializes_stable_result_field(monkeypatch: pytest.MonkeyPatch, ca
|
|
|
130
141
|
sol_enabled=True,
|
|
131
142
|
selected_tier=sol_advisor.ADVISOR_MODEL_TIER,
|
|
132
143
|
outcome=sol_advisor.CODEX_BIND_SUCCESS_TOKEN,
|
|
144
|
+
fallback_kind=None,
|
|
133
145
|
),
|
|
134
146
|
)
|
|
135
147
|
monkeypatch.setattr(sys, "stdin", io.StringIO("first consult"))
|
|
@@ -155,18 +167,165 @@ def test_bind_and_resume_arguments_match_installed_codex_interface() -> None:
|
|
|
155
167
|
"read-only",
|
|
156
168
|
"--json",
|
|
157
169
|
]
|
|
158
|
-
assert sol_advisor.build_codex_arguments() == [
|
|
170
|
+
assert sol_advisor.build_codex_arguments("codex") == [
|
|
159
171
|
*expected_common_arguments,
|
|
160
172
|
"-",
|
|
161
173
|
]
|
|
162
|
-
assert sol_advisor.build_codex_arguments(
|
|
163
|
-
|
|
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:],
|
|
164
179
|
"resume",
|
|
165
180
|
"thread-1",
|
|
166
181
|
"-",
|
|
167
182
|
]
|
|
168
183
|
|
|
169
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
|
+
|
|
170
329
|
def test_successful_probe_requires_finite_meter_above_configured_gate() -> None:
|
|
171
330
|
def probe_runner(arguments: list[str], **kwargs: object) -> subprocess.CompletedProcess[str]:
|
|
172
331
|
return _probe_process({"percent_left": 90})
|
|
@@ -304,12 +463,12 @@ def test_bind_runs_probe_then_codex_with_read_only_xhigh_settings() -> None:
|
|
|
304
463
|
probe_path=USAGE_PROBE_PATH,
|
|
305
464
|
session_id=None,
|
|
306
465
|
process_runner=process_runner,
|
|
307
|
-
setting_by_name=
|
|
466
|
+
setting_by_name=ENABLED_SETTING_BY_NAME,
|
|
308
467
|
)
|
|
309
468
|
|
|
310
469
|
assert reply.successful
|
|
311
470
|
assert len(calls) == 2
|
|
312
|
-
assert calls[1][0] == sol_advisor.build_codex_arguments()
|
|
471
|
+
assert calls[1][0] == sol_advisor.build_codex_arguments("codex")
|
|
313
472
|
assert calls[1][1]["cwd"] == "."
|
|
314
473
|
assert calls[1][1]["shell"] is False
|
|
315
474
|
assert calls[1][1]["timeout"]
|
|
@@ -331,7 +490,7 @@ def test_team_advisor_path_preserves_sol_routing_fields() -> None:
|
|
|
331
490
|
probe_path=USAGE_PROBE_PATH,
|
|
332
491
|
session_id=None,
|
|
333
492
|
process_runner=_two_step_process_runner(calls, guidance="PLAN\ninspect"),
|
|
334
|
-
setting_by_name=
|
|
493
|
+
setting_by_name=ENABLED_SETTING_BY_NAME,
|
|
335
494
|
)
|
|
336
495
|
|
|
337
496
|
assert reply.successful
|
|
@@ -358,7 +517,7 @@ def test_team_advisor_path_uses_fable_result_when_sol_gate_is_closed() -> None:
|
|
|
358
517
|
probe_path=USAGE_PROBE_PATH,
|
|
359
518
|
session_id=None,
|
|
360
519
|
process_runner=process_runner,
|
|
361
|
-
setting_by_name=
|
|
520
|
+
setting_by_name=ENABLED_SETTING_BY_NAME,
|
|
362
521
|
)
|
|
363
522
|
|
|
364
523
|
assert not reply.successful
|
|
@@ -369,7 +528,10 @@ def test_team_advisor_path_uses_fable_result_when_sol_gate_is_closed() -> None:
|
|
|
369
528
|
assert len(calls) == 1
|
|
370
529
|
|
|
371
530
|
|
|
372
|
-
def test_disabled_flag_uses_default_optional_routing_inputs(
|
|
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)
|
|
373
535
|
reply = sol_advisor.run_codex_sol_advisor(
|
|
374
536
|
prompt="first consult",
|
|
375
537
|
working_directory=Path("."),
|
|
@@ -398,13 +560,13 @@ def test_resume_runs_the_usage_gate_before_codex() -> None:
|
|
|
398
560
|
session_id="thread-1",
|
|
399
561
|
probe_path=USAGE_PROBE_PATH,
|
|
400
562
|
process_runner=_two_step_process_runner(calls, guidance="ENDORSE\nready"),
|
|
401
|
-
setting_by_name=
|
|
563
|
+
setting_by_name=ENABLED_SETTING_BY_NAME,
|
|
402
564
|
)
|
|
403
565
|
|
|
404
566
|
assert reply.successful
|
|
405
567
|
assert calls == [
|
|
406
568
|
[sys.executable, str(USAGE_PROBE_PATH)],
|
|
407
|
-
sol_advisor.build_codex_arguments("thread-1"),
|
|
569
|
+
sol_advisor.build_codex_arguments("codex", session_id="thread-1"),
|
|
408
570
|
]
|
|
409
571
|
|
|
410
572
|
|
|
@@ -465,7 +627,7 @@ def test_codex_failure_modes_always_return_fallback(
|
|
|
465
627
|
probe_path=USAGE_PROBE_PATH,
|
|
466
628
|
session_id=None,
|
|
467
629
|
process_runner=process_runner,
|
|
468
|
-
setting_by_name=
|
|
630
|
+
setting_by_name=ENABLED_SETTING_BY_NAME,
|
|
469
631
|
)
|
|
470
632
|
|
|
471
633
|
assert not reply.successful
|
package/commands/CLAUDE.md
CHANGED
|
@@ -15,6 +15,7 @@ Slash-command definitions installed into `~/.claude/commands/` by `bin/install.m
|
|
|
15
15
|
| `pr-comments.md` | `/pr-comments` | Fetches and formats PR review comments for response |
|
|
16
16
|
| `review-plan.md` | `/review-plan` | Reviews the current plan packet against code standards |
|
|
17
17
|
| `right-size.md` | `/right-size` | Checks an implementation against the Right-Sized Engineering rules |
|
|
18
|
+
| `sr-loop.md` | `/sr-loop` | Runs the converging cleanup loop: /simplify passes until clean, then a code-review fix pass |
|
|
18
19
|
| `sum.md` | `/sum` | Generates a formatted session summary for quick pickup in a new session |
|
|
19
20
|
|
|
20
21
|
## Format
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Converging cleanup loop - repeat /simplify until clean, then repeat /code-review --fix until clean
|
|
3
|
+
argument-hint: [PR URL, branch, or blank for the current diff]
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Run the converging cleanup loop on the target: `$ARGUMENTS` (blank means the
|
|
7
|
+
current branch's diff). Each phase invokes an existing skill with the Skill
|
|
8
|
+
tool and repeats it until a pass returns zero new findings.
|
|
9
|
+
|
|
10
|
+
## Phase A — loop /simplify
|
|
11
|
+
|
|
12
|
+
1. Invoke the `simplify` skill on the target (the same review the user gets
|
|
13
|
+
from `/simplify <target>`). Let it run its 4-lens fan-out and apply its
|
|
14
|
+
fixes.
|
|
15
|
+
2. After each pass: run the scoped test suite (test files beside the touched
|
|
16
|
+
code, not the full repo suite), commit once (`git commit -F <file>`, body
|
|
17
|
+
written with the Write tool, Co-Authored-By line, 10-minute timeout — the
|
|
18
|
+
pre-commit gate runs its own tests), and push to the PR head branch. The PR
|
|
19
|
+
stays draft.
|
|
20
|
+
3. Repeat the invocation. **Skips are sticky:** carry every adjudicated skip
|
|
21
|
+
forward into the next pass as "already adjudicated — do not re-report: ..."
|
|
22
|
+
context, or the loop never converges.
|
|
23
|
+
4. Phase A converges when a full pass returns zero new findings. When the
|
|
24
|
+
previous pass changed only a line or two, a single combined-lens
|
|
25
|
+
confirmation agent may serve as the final pass.
|
|
26
|
+
|
|
27
|
+
## Phase B — loop /code-review low --fix
|
|
28
|
+
|
|
29
|
+
1. Invoke the `code-review` skill with arguments `low --fix` on the same
|
|
30
|
+
target. Let it report findings and apply its fixes.
|
|
31
|
+
2. After any pass that changed files: test, commit, push as in Phase A.
|
|
32
|
+
3. Repeat until a pass reports zero findings. Phase B usually converges in one
|
|
33
|
+
pass when Phase A ran first.
|
|
34
|
+
|
|
35
|
+
## Finish
|
|
36
|
+
|
|
37
|
+
Report: passes run per phase, commits pushed with hashes, fixes applied, and
|
|
38
|
+
the standing skip list with reasons.
|
|
39
|
+
|
|
40
|
+
## Constraints (observed under headless runs)
|
|
41
|
+
|
|
42
|
+
- Run this loop in the main session. A wrapper subagent cannot see the
|
|
43
|
+
`code-review` skill, and a nested agent's own subagent reports route to the
|
|
44
|
+
main session instead of back to it.
|
|
45
|
+
- Scope test runs to the touched files; a full-suite run can outlive one
|
|
46
|
+
foreground tool call.
|
|
47
|
+
- A hung or newly slow test suite is a finding to investigate, not an
|
|
48
|
+
inconvenience.
|
package/package.json
CHANGED
|
@@ -44,7 +44,7 @@ On exit 2 the script prints `{"error": ...}`. Ask the user for a manual reset ti
|
|
|
44
44
|
In short: the resolver picks a bearer token, probes the OAuth usage endpoint the interactive `/usage` panel uses, and returns the session and weekly buckets with utilization and reset times. Token sources depend on the host:
|
|
45
45
|
|
|
46
46
|
- **Desktop host** (the `CLAUDE_CODE_ENTRYPOINT` variable is `claude-desktop`): the resolver does not read the CLI credential file, which belongs to a different authentication session than the one the desktop app counts usage against. It uses the session ingress token when one is set, and otherwise takes the manual-override path.
|
|
47
|
-
- **Every other host**: the resolver reads the Claude Code CLI's stored OAuth access token first (honored only while unexpired), then the session ingress bearer token file named by `CLAUDE_SESSION_INGRESS_TOKEN_FILE` (cloud sessions) when the credential token is unavailable.
|
|
47
|
+
- **Every other host**: the resolver reads the Claude Code CLI's stored OAuth access token first (honored only while unexpired), then the session ingress bearer token file named by `CLAUDE_SESSION_INGRESS_TOKEN_FILE` (cloud sessions) when the credential token is unavailable. The credential file lives under the directory `CLAUDE_CONFIG_DIR` names when that variable is set — a profile-isolated session's own account — else the home-directory default; the home file on a multi-profile machine can belong to a different account, whose meter is the wrong one to read.
|
|
48
48
|
|
|
49
49
|
Fallbacks, in order: both token sources unavailable (expired/unreadable credential and no ingress file), a failed request, or a response with no readable session-window reset time all end in exit 2 — the manual-override ask above. The manual path works with no probe at all, so the skill functions even when both token sources are unavailable.
|
|
50
50
|
|
|
@@ -9,8 +9,10 @@
|
|
|
9
9
|
|
|
10
10
|
With no ``--override``, the script resolves a bearer token. On the desktop
|
|
11
11
|
host it uses only the session ingress token. On every other host it reads the
|
|
12
|
-
Claude Code OAuth access token from the CLI credential file
|
|
13
|
-
|
|
12
|
+
Claude Code OAuth access token from the CLI credential file — under the
|
|
13
|
+
directory ``CLAUDE_CONFIG_DIR`` names when set, else the home-directory
|
|
14
|
+
default — then the session ingress token file when that credential is
|
|
15
|
+
unavailable. It asks the OAuth
|
|
14
16
|
usage endpoint for the ``five_hour`` and ``seven_day`` windows. Exit code 2
|
|
15
17
|
means the probe cannot resolve; the caller then asks the user for a manual
|
|
16
18
|
reset time.
|
|
@@ -38,10 +40,12 @@ from usage_pause_constants.resolve_usage_window_constants import (
|
|
|
38
40
|
BARE_MINUTES_PATTERN,
|
|
39
41
|
CLOCK_HOUR_MAXIMUM,
|
|
40
42
|
CLOCK_PATTERN,
|
|
43
|
+
CONFIG_DIR_ENV_VAR,
|
|
41
44
|
CONTENT_TYPE_HEADER_NAME,
|
|
42
45
|
CONTENT_TYPE_JSON,
|
|
43
46
|
CREDENTIALS_ACCESS_TOKEN_KEY,
|
|
44
47
|
CREDENTIALS_EXPIRES_AT_KEY,
|
|
48
|
+
CREDENTIALS_FILE_NAME,
|
|
45
49
|
CREDENTIALS_OAUTH_SECTION_KEY,
|
|
46
50
|
DESKTOP_ENTRYPOINT_VALUE,
|
|
47
51
|
DURATION_PATTERN,
|
|
@@ -203,6 +207,27 @@ def plan_wakeup_stages(seconds_until_reset: int) -> list[int]:
|
|
|
203
207
|
return stages
|
|
204
208
|
|
|
205
209
|
|
|
210
|
+
def default_credentials_path() -> Path:
|
|
211
|
+
"""Locate the CLI credential file for the account this session runs as.
|
|
212
|
+
|
|
213
|
+
::
|
|
214
|
+
|
|
215
|
+
CLAUDE_CONFIG_DIR=C:/profiles/mel -> C:/profiles/mel/.credentials.json
|
|
216
|
+
(variable unset or empty) -> ~/.claude/.credentials.json
|
|
217
|
+
|
|
218
|
+
A profile-isolated session keeps its credential under the config-dir
|
|
219
|
+
directory. On such a machine the home file belongs to a different
|
|
220
|
+
account. Reading the home file reports that other account's meter.
|
|
221
|
+
|
|
222
|
+
Returns:
|
|
223
|
+
The credential file for the session's own account.
|
|
224
|
+
"""
|
|
225
|
+
config_directory = os.environ.get(CONFIG_DIR_ENV_VAR)
|
|
226
|
+
if config_directory:
|
|
227
|
+
return Path(config_directory) / CREDENTIALS_FILE_NAME
|
|
228
|
+
return Path.home().joinpath(*ALL_CREDENTIALS_RELATIVE_PATH_PARTS)
|
|
229
|
+
|
|
230
|
+
|
|
206
231
|
def read_oauth_access_token(credentials_path: Path, now: datetime) -> str | None:
|
|
207
232
|
"""Read the CLI's OAuth access token when it is still valid.
|
|
208
233
|
|
|
@@ -523,7 +548,10 @@ def _parse_arguments() -> argparse.Namespace:
|
|
|
523
548
|
parser.add_argument(
|
|
524
549
|
"--credentials-path",
|
|
525
550
|
default=None,
|
|
526
|
-
help=
|
|
551
|
+
help=(
|
|
552
|
+
"Path to the CLI credential file; defaults to the CLAUDE_CONFIG_DIR "
|
|
553
|
+
"location when that variable is set, else the home-directory location."
|
|
554
|
+
),
|
|
527
555
|
)
|
|
528
556
|
return parser.parse_args()
|
|
529
557
|
|
|
@@ -558,7 +586,7 @@ def main() -> int:
|
|
|
558
586
|
credentials_path = (
|
|
559
587
|
Path(arguments.credentials_path)
|
|
560
588
|
if arguments.credentials_path
|
|
561
|
-
else
|
|
589
|
+
else default_credentials_path()
|
|
562
590
|
)
|
|
563
591
|
access_token = resolve_access_token(credentials_path, now)
|
|
564
592
|
if access_token is None:
|
|
@@ -19,6 +19,7 @@ if str(SCRIPTS_DIRECTORY) not in sys.path:
|
|
|
19
19
|
sys.path.insert(0, str(SCRIPTS_DIRECTORY))
|
|
20
20
|
|
|
21
21
|
from usage_pause_constants.resolve_usage_window_constants import ( # noqa: E402
|
|
22
|
+
CONFIG_DIR_ENV_VAR,
|
|
22
23
|
DESKTOP_ENTRYPOINT_VALUE,
|
|
23
24
|
ENTRYPOINT_ENV_VAR,
|
|
24
25
|
SESSION_INGRESS_TOKEN_FILE_ENV_VAR as INGRESS_TOKEN_FILE_ENV_VAR,
|
|
@@ -195,6 +196,31 @@ class TestReadOauthAccessToken:
|
|
|
195
196
|
assert any("unreadable" in each_message for each_message in caplog.messages)
|
|
196
197
|
|
|
197
198
|
|
|
199
|
+
class TestDefaultCredentialsPath:
|
|
200
|
+
def should_use_the_config_dir_credential_file_when_the_variable_is_set(
|
|
201
|
+
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
|
202
|
+
) -> None:
|
|
203
|
+
resolver = load_resolver_module()
|
|
204
|
+
monkeypatch.setenv(CONFIG_DIR_ENV_VAR, str(tmp_path))
|
|
205
|
+
assert resolver.default_credentials_path() == tmp_path / ".credentials.json"
|
|
206
|
+
|
|
207
|
+
def should_fall_back_to_the_home_credential_file_when_the_variable_is_unset(
|
|
208
|
+
self, monkeypatch: pytest.MonkeyPatch
|
|
209
|
+
) -> None:
|
|
210
|
+
resolver = load_resolver_module()
|
|
211
|
+
monkeypatch.delenv(CONFIG_DIR_ENV_VAR, raising=False)
|
|
212
|
+
expected = Path.home() / ".claude" / ".credentials.json"
|
|
213
|
+
assert resolver.default_credentials_path() == expected
|
|
214
|
+
|
|
215
|
+
def should_fall_back_to_the_home_credential_file_when_the_variable_is_empty(
|
|
216
|
+
self, monkeypatch: pytest.MonkeyPatch
|
|
217
|
+
) -> None:
|
|
218
|
+
resolver = load_resolver_module()
|
|
219
|
+
monkeypatch.setenv(CONFIG_DIR_ENV_VAR, "")
|
|
220
|
+
expected = Path.home() / ".claude" / ".credentials.json"
|
|
221
|
+
assert resolver.default_credentials_path() == expected
|
|
222
|
+
|
|
223
|
+
|
|
198
224
|
class TestReadSessionIngressToken:
|
|
199
225
|
def should_return_stripped_token_from_the_named_file(
|
|
200
226
|
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
|
@@ -19,6 +19,8 @@ CONTENT_TYPE_JSON = "application/json"
|
|
|
19
19
|
PROBE_TIMEOUT_SECONDS = 10
|
|
20
20
|
|
|
21
21
|
ALL_CREDENTIALS_RELATIVE_PATH_PARTS = (".claude", ".credentials.json")
|
|
22
|
+
CONFIG_DIR_ENV_VAR = "CLAUDE_CONFIG_DIR"
|
|
23
|
+
CREDENTIALS_FILE_NAME = ".credentials.json"
|
|
22
24
|
CREDENTIALS_OAUTH_SECTION_KEY = "claudeAiOauth"
|
|
23
25
|
CREDENTIALS_ACCESS_TOKEN_KEY = "accessToken"
|
|
24
26
|
CREDENTIALS_EXPIRES_AT_KEY = "expiresAt"
|
|
@@ -67,4 +69,4 @@ SOURCE_OVERRIDE = "override"
|
|
|
67
69
|
EXIT_CODE_RESOLVED = 0
|
|
68
70
|
EXIT_CODE_PROBE_UNAVAILABLE = 2
|
|
69
71
|
|
|
70
|
-
LOGGING_FORMAT = "%(levelname)s %(name)s: %(message)s"
|
|
72
|
+
LOGGING_FORMAT = "%(levelname)s %(name)s: %(message)s"
|