claude-dev-env 8.43.1 → 8.43.3
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
|
@@ -244,7 +244,7 @@ def _attempt_once(context: _RunContext, decision: Decision) -> JobOutcome | None
|
|
|
244
244
|
except (OSError, subprocess.TimeoutExpired) as error:
|
|
245
245
|
status = "timeout" if isinstance(error, subprocess.TimeoutExpired) else "start_failed"
|
|
246
246
|
_record_spent_attempt(context, account, status, COMMAND_MISSING_EXIT_CODE)
|
|
247
|
-
if not context.all_readings:
|
|
247
|
+
if isinstance(error, subprocess.TimeoutExpired) or not context.all_readings:
|
|
248
248
|
context.report.final_decision = decision
|
|
249
249
|
return JobOutcome(COMMAND_MISSING_EXIT_CODE, "", str(error), account.name, tuple(context.all_attempts), "advisor_blocked", None, None)
|
|
250
250
|
return None
|
|
@@ -224,9 +224,13 @@ def _account_reset(reading: Reading, is_spent: bool = False) -> datetime | None:
|
|
|
224
224
|
return None
|
|
225
225
|
if is_spent:
|
|
226
226
|
return min((reset for reset in (meters.session_resets_at, meters.weekly_resets_at) if reset is not None), default=None)
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
227
|
+
is_claude_main = reading.account.product is Product.CLAUDE and reading.account.is_main
|
|
228
|
+
if is_claude_main:
|
|
229
|
+
blocking = (
|
|
230
|
+
(meters.session_percent_left, FULL_PERCENT - ALL_CLAUDE_FLOORS["main_session_used_ceiling"], meters.session_resets_at),
|
|
231
|
+
(meters.weekly_percent_left, FULL_PERCENT - ALL_CLAUDE_FLOORS["main_weekly_used_ceiling"], meters.weekly_resets_at),
|
|
232
|
+
)
|
|
233
|
+
elif reading.account.product is Product.CLAUDE:
|
|
230
234
|
blocking = (
|
|
231
235
|
(meters.session_percent_left, FULL_PERCENT - ALL_CLAUDE_FLOORS["extra_session_used_ceiling"], meters.session_resets_at),
|
|
232
236
|
(meters.weekly_percent_left, FULL_PERCENT - ALL_CLAUDE_FLOORS["extra_weekly_used_ceiling"], meters.weekly_resets_at),
|
|
@@ -236,7 +240,10 @@ def _account_reset(reading: Reading, is_spent: bool = False) -> datetime | None:
|
|
|
236
240
|
(meters.session_percent_left, ALL_CODEX_FLOORS["luna_short_minimum_left"], meters.session_resets_at),
|
|
237
241
|
(meters.weekly_percent_left, ALL_CODEX_FLOORS["luna_stop_left"], meters.weekly_resets_at),
|
|
238
242
|
)
|
|
239
|
-
|
|
243
|
+
all_blocking_resets = [reset for room, floor, reset in blocking if room is not None and room <= floor and reset is not None]
|
|
244
|
+
if is_claude_main and meters.weekly_resets_at is not None:
|
|
245
|
+
all_blocking_resets.append(meters.weekly_resets_at - ALL_CLAUDE_FLOORS["main_spend_window"])
|
|
246
|
+
return max(all_blocking_resets, default=None)
|
|
240
247
|
|
|
241
248
|
|
|
242
249
|
def _rank_key(reading: Reading) -> float:
|
|
@@ -127,6 +127,28 @@ def test_should_prioritize_main_when_its_guard_passes() -> None:
|
|
|
127
127
|
assert decision.account == main.account
|
|
128
128
|
|
|
129
129
|
|
|
130
|
+
@pytest.mark.parametrize(
|
|
131
|
+
("short_left", "weekly_left", "weekly_reset", "expected_reset"),
|
|
132
|
+
(
|
|
133
|
+
(40, 20, NOW + timedelta(hours=4), NOW + timedelta(hours=2)),
|
|
134
|
+
(80, 5, NOW + timedelta(hours=4), NOW + timedelta(hours=4)),
|
|
135
|
+
(80, 50, NOW + timedelta(days=3), NOW + timedelta(days=2)),
|
|
136
|
+
),
|
|
137
|
+
)
|
|
138
|
+
def test_should_wait_for_the_meter_or_window_that_blocks_main(
|
|
139
|
+
short_left: float, weekly_left: float, weekly_reset: datetime, expected_reset: datetime
|
|
140
|
+
) -> None:
|
|
141
|
+
main = Reading(
|
|
142
|
+
_account("main", Product.CLAUDE, main=True),
|
|
143
|
+
_meters(short_left, weekly_left, short_reset=NOW + timedelta(hours=2), weekly_reset=weekly_reset),
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
decision = choose_from_readings(Product.CLAUDE, (main,), now=NOW)
|
|
147
|
+
|
|
148
|
+
assert decision.action == "wait"
|
|
149
|
+
assert decision.resets_at == expected_reset
|
|
150
|
+
|
|
151
|
+
|
|
130
152
|
def test_should_try_next_account_after_usage_limit(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
|
131
153
|
accounts = (_account("first"), _account("second"))
|
|
132
154
|
adapter = _adapter(accounts, {"first": _meters(80, 80), "second": _meters(70, 70)})
|
|
@@ -361,12 +383,8 @@ def test_should_keep_spent_mark_until_reset(
|
|
|
361
383
|
assert json.loads(capsys.readouterr().out)["decision"]["account"] == "second"
|
|
362
384
|
|
|
363
385
|
|
|
364
|
-
@pytest.mark.parametrize(
|
|
365
|
-
("start_error", "expected_status"),
|
|
366
|
-
((OSError("missing command"), "start_failed"), (subprocess.TimeoutExpired("job", 1), "timeout")),
|
|
367
|
-
)
|
|
368
386
|
def test_should_leave_later_choices_open_after_start_failure(
|
|
369
|
-
monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
|
|
387
|
+
monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
|
|
370
388
|
) -> None:
|
|
371
389
|
accounts = (_account("first"), _account("second"))
|
|
372
390
|
monkeypatch.setitem(account_broker.all_product_adapters, Product.CODEX, _adapter(accounts, {
|
|
@@ -374,12 +392,37 @@ def test_should_leave_later_choices_open_after_start_failure(
|
|
|
374
392
|
}))
|
|
375
393
|
|
|
376
394
|
def runner(argv: object, **options: object) -> subprocess.CompletedProcess[str]:
|
|
377
|
-
raise
|
|
395
|
+
raise OSError("missing command")
|
|
378
396
|
|
|
379
397
|
with account_broker.override_subprocess_runner(runner):
|
|
380
398
|
outcome, _ = account_broker._execute(Product.CODEX, ("job",), now=datetime.now(timezone.utc))
|
|
381
399
|
|
|
382
|
-
assert outcome.attempts == (("first",
|
|
400
|
+
assert outcome.attempts == (("first", "start_failed"), ("second", "start_failed"))
|
|
401
|
+
assert account_broker.main(("choose", "--product", "codex")) == 0
|
|
402
|
+
assert json.loads(capsys.readouterr().out)["decision"]["account"] == "first"
|
|
403
|
+
|
|
404
|
+
|
|
405
|
+
def test_should_stop_after_timeout_without_running_job_again(
|
|
406
|
+
monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
|
|
407
|
+
) -> None:
|
|
408
|
+
accounts = (_account("first"), _account("second"))
|
|
409
|
+
monkeypatch.setitem(account_broker.all_product_adapters, Product.CODEX, _adapter(accounts, {
|
|
410
|
+
"first": _meters(80, 80), "second": _meters(70, 70)
|
|
411
|
+
}))
|
|
412
|
+
invoked_homes: list[str] = []
|
|
413
|
+
|
|
414
|
+
def runner(argv: object, **options: object) -> subprocess.CompletedProcess[str]:
|
|
415
|
+
invoked_homes.append(Path(options["env"]["CODEX_HOME"]).name)
|
|
416
|
+
raise subprocess.TimeoutExpired("job", 1)
|
|
417
|
+
|
|
418
|
+
with account_broker.override_subprocess_runner(runner):
|
|
419
|
+
outcome, report = account_broker._execute(Product.CODEX, ("job",), now=datetime.now(timezone.utc))
|
|
420
|
+
|
|
421
|
+
assert invoked_homes == ["first"]
|
|
422
|
+
assert outcome.attempts == (("first", "timeout"),)
|
|
423
|
+
assert outcome.status == "advisor_blocked"
|
|
424
|
+
assert outcome.account_name == "first"
|
|
425
|
+
assert report.final_decision.account.name == "first"
|
|
383
426
|
assert account_broker.main(("choose", "--product", "codex")) == 0
|
|
384
427
|
assert json.loads(capsys.readouterr().out)["decision"]["account"] == "first"
|
|
385
428
|
|