claude-dev-env 8.46.0 → 8.46.2
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/docs/account-broker.md
CHANGED
|
@@ -27,6 +27,7 @@ The broker stores cached meters, spent marks, and Claude session bindings in one
|
|
|
27
27
|
| `0` | `choose` or `check` found an account, or the job exited successfully. |
|
|
28
28
|
| `3` | No account clears its floor. The decision includes `resets_at`. |
|
|
29
29
|
| `4` | A Claude job stopped with `advisor_blocked`. |
|
|
30
|
+
| `127` | A Codex job timed out, or its command could not start while no Codex roster is configured. |
|
|
30
31
|
| Other command code | `run` returns the command's exit code after a served response. |
|
|
31
32
|
|
|
32
33
|
`run` writes a JSON report with `product`, `command`, `events`, and `final_decision`. A wait report has `final_decision.action` equal to `wait` and a UTC `final_decision.resets_at`.
|
package/package.json
CHANGED
|
@@ -263,7 +263,8 @@ def _attempt_once(context: _RunContext, decision: Decision) -> JobOutcome | None
|
|
|
263
263
|
_record_spent_attempt(context, account, status, COMMAND_MISSING_EXIT_CODE)
|
|
264
264
|
if isinstance(error, subprocess.TimeoutExpired) or not context.all_readings:
|
|
265
265
|
context.report.final_decision = decision
|
|
266
|
-
|
|
266
|
+
final_status = "advisor_blocked" if context.product is Product.CLAUDE else status
|
|
267
|
+
return JobOutcome(COMMAND_MISSING_EXIT_CODE, "", str(error), account.name, tuple(context.all_attempts), final_status, None, None)
|
|
267
268
|
return None
|
|
268
269
|
combined = f"{completion.stdout}{completion.stderr}".casefold()
|
|
269
270
|
is_limited = completion.returncode != 0 and any(
|
|
@@ -435,13 +435,44 @@ def test_should_stop_after_timeout_without_running_job_again(
|
|
|
435
435
|
|
|
436
436
|
assert invoked_homes == ["first"]
|
|
437
437
|
assert outcome.attempts == (("first", "timeout"),)
|
|
438
|
-
assert outcome.status == "
|
|
438
|
+
assert outcome.status == "timeout"
|
|
439
439
|
assert outcome.account_name == "first"
|
|
440
440
|
assert report.final_decision.account.name == "first"
|
|
441
441
|
assert account_broker.main(("choose", "--product", "codex")) == 0
|
|
442
442
|
assert json.loads(capsys.readouterr().out)["decision"]["account"] == "first"
|
|
443
443
|
|
|
444
444
|
|
|
445
|
+
@pytest.mark.parametrize(
|
|
446
|
+
("product", "roster_names", "start_error", "expected_code"),
|
|
447
|
+
(
|
|
448
|
+
(Product.CLAUDE, ("first",), subprocess.TimeoutExpired("job", 1), 4),
|
|
449
|
+
(Product.CODEX, ("first",), subprocess.TimeoutExpired("job", 1), 127),
|
|
450
|
+
(Product.CODEX, (), OSError("missing command"), 127),
|
|
451
|
+
),
|
|
452
|
+
)
|
|
453
|
+
def test_should_exit_four_only_for_a_blocked_claude_job(
|
|
454
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
455
|
+
tmp_path: Path,
|
|
456
|
+
product: Product,
|
|
457
|
+
roster_names: tuple[str, ...],
|
|
458
|
+
start_error: Exception,
|
|
459
|
+
expected_code: int,
|
|
460
|
+
) -> None:
|
|
461
|
+
accounts = tuple(_account(each_name, product) for each_name in roster_names)
|
|
462
|
+
monkeypatch.setitem(account_broker.all_product_adapters, product, _adapter(accounts, {
|
|
463
|
+
each_name: _meters(80, 80) for each_name in roster_names
|
|
464
|
+
}))
|
|
465
|
+
monkeypatch.setattr(account_broker.sys, "stdin", io.TextIOWrapper(io.BytesIO(b""), encoding="utf-8"))
|
|
466
|
+
|
|
467
|
+
def runner(argv: object, **options: object) -> subprocess.CompletedProcess[str]:
|
|
468
|
+
raise start_error
|
|
469
|
+
|
|
470
|
+
with account_broker.override_subprocess_runner(runner):
|
|
471
|
+
code = account_broker.main(("run", "--product", product.value, "--report", str(tmp_path / "report.json"), "--", "job"))
|
|
472
|
+
|
|
473
|
+
assert code == expected_code
|
|
474
|
+
|
|
475
|
+
|
|
445
476
|
def test_should_keep_usage_limited_account_spent_for_later_choices(
|
|
446
477
|
monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
|
|
447
478
|
) -> None:
|
|
@@ -3,11 +3,12 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import ast
|
|
6
|
-
import os
|
|
7
6
|
import re
|
|
8
7
|
from collections import Counter
|
|
9
8
|
from pathlib import Path
|
|
10
9
|
|
|
10
|
+
import pytest
|
|
11
|
+
|
|
11
12
|
|
|
12
13
|
_PACKAGE_ROOT = Path(__file__).resolve().parents[2]
|
|
13
14
|
_REPOSITORY_ROOT = _PACKAGE_ROOT.parent.parent
|
|
@@ -118,28 +119,31 @@ def _collect_citation_texts() -> list[tuple[Path, str]]:
|
|
|
118
119
|
|
|
119
120
|
def test_should_resolve_code_rules_anchors_and_sections() -> None:
|
|
120
121
|
index_text = _INDEX_PATH.read_text(encoding="utf-8")
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
122
|
+
_assert_citations_resolve(index_text, _collect_citation_texts())
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def test_should_report_dead_code_rules_anchor() -> None:
|
|
126
|
+
index_text = _INDEX_PATH.read_text(encoding="utf-8")
|
|
127
|
+
dead_citation = (_INDEX_PATH, "CODE_RULES.md" + "#no-such-heading")
|
|
128
|
+
with pytest.raises(AssertionError, match="dead CODE_RULES anchor #no-such-heading"):
|
|
129
|
+
_assert_citations_resolve(index_text, [dead_citation])
|
|
126
130
|
|
|
127
131
|
|
|
128
|
-
def _assert_destination_resolves(
|
|
132
|
+
def _assert_destination_resolves(markdown_path: Path, destination: str) -> None:
|
|
129
133
|
if "://" in destination or destination.startswith("#"):
|
|
130
134
|
return
|
|
131
135
|
path_text, _, anchor_text = destination.partition("#")
|
|
132
|
-
destination_path =
|
|
133
|
-
assert destination_path.is_file(), f"{
|
|
136
|
+
destination_path = markdown_path.parent / path_text
|
|
137
|
+
assert destination_path.is_file(), f"{markdown_path}: {destination}"
|
|
134
138
|
if anchor_text:
|
|
135
139
|
destination_text = destination_path.read_text(encoding="utf-8")
|
|
136
140
|
assert anchor_text in _heading_slugs(destination_text), (
|
|
137
|
-
f"{
|
|
141
|
+
f"{markdown_path}: {destination}"
|
|
138
142
|
)
|
|
139
143
|
|
|
140
144
|
|
|
141
|
-
def
|
|
142
|
-
for
|
|
143
|
-
|
|
144
|
-
for each_destination in _MARKDOWN_LINK.findall(
|
|
145
|
-
_assert_destination_resolves(
|
|
145
|
+
def test_should_resolve_every_index_and_family_relative_link() -> None:
|
|
146
|
+
for each_markdown_path in [_INDEX_PATH, *_FAMILY_DIRECTORY.glob("*.md")]:
|
|
147
|
+
markdown_text = each_markdown_path.read_text(encoding="utf-8")
|
|
148
|
+
for each_destination in _MARKDOWN_LINK.findall(markdown_text):
|
|
149
|
+
_assert_destination_resolves(each_markdown_path, each_destination)
|