claude-dev-env 8.43.5 → 8.43.6
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/advisor-protocol.md +2 -2
- package/_shared/advisor/reference/cli-chain.md +9 -12
- package/_shared/advisor/reference/lifecycle.md +1 -1
- package/_shared/advisor/reference/third-party-bind.md +3 -3
- package/bin/install.test.mjs +1 -1
- package/docs/second-claude-account.md +11 -13
- package/package.json +1 -1
- package/scripts/_code_review_test_support.py +29 -39
- package/scripts/claude_account_worker.py +46 -297
- package/scripts/claude_account_worker_process.py +18 -154
- package/scripts/claude_account_worker_report.py +8 -4
- package/scripts/claude_chain_usage.py +7 -271
- package/scripts/codec_forwarding_test_support.py +11 -65
- package/scripts/dev_env_scripts_constants/claude_account_worker_constants.py +6 -24
- package/scripts/invoke_code_review.py +34 -56
- package/scripts/resolve_worker_spawn.py +40 -72
- package/scripts/test_account_broker_guard.py +0 -4
- package/scripts/test_claude_account_worker.py +105 -350
- package/scripts/test_claude_chain_usage.py +50 -510
- package/scripts/test_dispatcher_profile_import.py +5 -9
- package/scripts/test_invoke_code_review.py +7 -6
- package/scripts/test_invoke_code_review_chain.py +26 -0
- package/scripts/test_invoke_code_review_cli.py +8 -8
- package/scripts/test_invoke_code_review_codec.py +3 -11
- package/scripts/test_invoke_code_review_contract.py +5 -1
- package/scripts/test_resolve_worker_spawn.py +118 -265
- package/scripts/test_resolve_worker_spawn_codec.py +9 -21
- package/scripts/tests/test_code_review_constants.py +2 -2
- package/scripts/claude_account_choice.py +0 -422
- package/scripts/claude_chain_runner.py +0 -1289
- package/scripts/test_claude_account_choice.py +0 -355
- package/scripts/test_claude_chain_runner.py +0 -1839
|
@@ -138,8 +138,8 @@ Drift signals and the per-host re-spawn / re-bind steps: [`reference/lifecycle.m
|
|
|
138
138
|
|
|
139
139
|
## CLI chain
|
|
140
140
|
|
|
141
|
-
The shared runner is `python "$HOME/.claude/scripts/
|
|
142
|
-
|
|
141
|
+
The shared runner is `python "$HOME/.claude/scripts/account_broker.py" run --product claude --report <path> -- claude <args...>`.
|
|
142
|
+
Account choice, failover, the tier-to-alias table, brief piping, and `--resume` session handling: [`reference/cli-chain.md`](reference/cli-chain.md).
|
|
143
143
|
|
|
144
144
|
**Third-party host:** the primary bind and consult path; the walk order and fail-closed rule live in [`reference/third-party-bind.md`](reference/third-party-bind.md).
|
|
145
145
|
|
|
@@ -1,18 +1,15 @@
|
|
|
1
1
|
# CLI Claude-chain
|
|
2
2
|
|
|
3
3
|
Detail behind the `## CLI chain` section of [`advisor-protocol.md`](../advisor-protocol.md).
|
|
4
|
-
The shared runner is `python "$HOME/.claude/scripts/
|
|
4
|
+
The shared runner is `python "$HOME/.claude/scripts/account_broker.py" run --product claude --report <path> -- claude <args...>`.
|
|
5
5
|
|
|
6
|
-
##
|
|
6
|
+
## Account choice
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
**Root advisor bind and consult** (the third-party host path): ordered-account mode. A non-usage failure terminates with `terminal_status=advisor_blocked`, exit code 4 on the CLI.
|
|
14
|
-
|
|
15
|
-
**General chain calls** (non-root automation): keep the default usage-ranked mode.
|
|
8
|
+
The broker applies the main account guard and chooses an eligible extra account
|
|
9
|
+
by remaining usage. On a usage-limit response, it tries another account. If no
|
|
10
|
+
account has room, it reports a reset time and exits with code 3. A non-usage
|
|
11
|
+
Claude failure has `JobOutcome.status == "advisor_blocked"`; the CLI exits with
|
|
12
|
+
code 4. The report path receives the broker's decision and attempt events.
|
|
16
13
|
|
|
17
14
|
## Tier-to-alias map
|
|
18
15
|
|
|
@@ -38,8 +35,8 @@ Drop the file once the consult completes.
|
|
|
38
35
|
|
|
39
36
|
## Session resume
|
|
40
37
|
|
|
41
|
-
Read the `session_id` out of the first call's JSON events.
|
|
38
|
+
Read the `session_id` out of the first call's JSON events or `JobOutcome.session_id`.
|
|
42
39
|
Pass it to `-p --resume <session_id> --output-format json` on every later consult — `-p` stays on the resume call too, since it is still a non-interactive invocation.
|
|
43
|
-
A session store belongs to the
|
|
40
|
+
A session store belongs to the account that minted it. The broker keeps account affinity when that account has room. If the account reaches a usage limit, a `--resume` on another account can fail.
|
|
44
41
|
Treat that failure as starting over.
|
|
45
42
|
Resend the charter plus a compact recap of the consults since the last one, capture the new `session_id` the fresh call returns, and continue from there.
|
|
@@ -22,6 +22,6 @@ Every other consumer reaches it by message alone; spawn, respawn, and shutdown b
|
|
|
22
22
|
|
|
23
23
|
The orchestrating session owns the CLI advisor bind for the whole run: first bind, re-bind on drift or lost `session_id`, and fail-closed report when the bound path cannot serve.
|
|
24
24
|
|
|
25
|
-
**Re-bind on drift.** If a reply shows a stale picture, the task pivots, or `--resume` fails after a usage-limit failover
|
|
25
|
+
**Re-bind on drift.** If a reply shows a stale picture, the task pivots, or `--resume` fails after a usage-limit failover, re-bind Opus through `python "$HOME/.claude/scripts/account_broker.py" run --product claude --report <path> -- claude <args...>` with the charter plus a compact recap of consults so far.
|
|
26
26
|
Capture the new `session_id`, and log a fresh Opus walk with `result: "cli"` on success, then Astra when that rung is open. An Astra re-bind uses `codex_astra_advisor.py --bind --enable-astra` with the same charter and recap, and records `result: "codex"` on success.
|
|
27
27
|
Executors keep reporting to the orchestrating session; advisor binding stays with that session alone.
|
|
@@ -22,15 +22,15 @@ When the Astra flag is off, follow the Claude-chain steps below.
|
|
|
22
22
|
3. **CLI bind (primary path):** for Opus, pipe a charter file into:
|
|
23
23
|
|
|
24
24
|
```
|
|
25
|
-
python "$HOME/.claude/scripts/
|
|
25
|
+
python "$HOME/.claude/scripts/account_broker.py" run --product claude --report <path> -- claude -p --model <alias> --effort <effort> --output-format json
|
|
26
26
|
```
|
|
27
27
|
|
|
28
28
|
Use `--model opus --effort` with the value of `ADVISOR_EFFORT` (default `xhigh`) on Opus.
|
|
29
29
|
User-facing wording follows [`rules/asd-ste100-language.md`](../../../rules/asd-ste100-language.md).
|
|
30
|
-
|
|
30
|
+
Account choice, failover, and the `advisor_blocked` status are in [`cli-chain.md`](cli-chain.md).
|
|
31
31
|
4. Stop at the first successful bind.
|
|
32
32
|
Record `{tier, result: "cli"}` for Opus or `{tier: "Astra", result: "codex"}` for the Astra helper, and set `selected_tier` to that tier.
|
|
33
|
-
Persist `session_id` from the JSON events
|
|
33
|
+
Persist `session_id` from the JSON events or `JobOutcome.session_id`; reply text is the `type == "result"` event's `.result` field.
|
|
34
34
|
Run every bind and every later consult with cwd set to the repo root the work is for. Claude sessions are project-scoped by working directory.
|
|
35
35
|
5. **Fail closed:** when every candidate fails (chain exhausted, `advisor_blocked`, or model unavailable), set `selected_tier = null` and a `fallback_reason`, report that the advisor is unreachable, and **stop**. ENDORSE / CORRECTION / PLAN / STOP come only from a bound advisor.
|
|
36
36
|
6. Assemble and paste each executor's Advisor block from [`advisor-block.md`](advisor-block.md). Executors report to the orchestrating session; that session consults the bound advisor and relays the four-signal reply.
|
package/bin/install.test.mjs
CHANGED
|
@@ -216,7 +216,7 @@ test('core includeDirectories ships _shared and scripts for advisor protocol and
|
|
|
216
216
|
);
|
|
217
217
|
assert.ok(
|
|
218
218
|
CORE_INCLUDE_DIRECTORIES.includes('scripts'),
|
|
219
|
-
'scripts must ship with --only core so
|
|
219
|
+
'scripts must ship with --only core so account_broker.py is available for advisor CLI fallback',
|
|
220
220
|
);
|
|
221
221
|
});
|
|
222
222
|
|
|
@@ -9,12 +9,12 @@ keeps its own sign-in and history.
|
|
|
9
9
|
| File | What it does |
|
|
10
10
|
|---|---|
|
|
11
11
|
| `scripts/claude_account_profile.py` | Links each shared entry of `~/.claude` into the profile `~/.claude-profiles/ev`, moves stale copies into `.replaced/<time>/`, and writes the `claude-ev.cmd` launcher into `~/.local/bin` |
|
|
12
|
-
| `scripts/
|
|
13
|
-
| `scripts/claude_account_worker.py` | Runs one headless Claude worker
|
|
14
|
-
| `scripts/claude_account_worker_process.py` |
|
|
12
|
+
| `scripts/account_broker.py` | Reads the account roster and usage meters, chooses an account, and runs Claude with that account's home |
|
|
13
|
+
| `scripts/claude_account_worker.py` | Runs one headless Claude worker through the broker |
|
|
14
|
+
| `scripts/claude_account_worker_process.py` | Sends the brief to the broker and measures the run |
|
|
15
15
|
| `scripts/claude_account_worker_report.py` | Builds and writes the JSON report a worker leaves behind |
|
|
16
16
|
| `scripts/dev_env_scripts_constants/claude_account_worker_constants.py` | The worker's flag names, defaults, exit codes, and report keys |
|
|
17
|
-
| `scripts/dev_env_scripts_constants/claude_account_constants.py` | The profile name, the entries that stay per account, and the
|
|
17
|
+
| `scripts/dev_env_scripts_constants/claude_account_constants.py` | The profile name, the entries that stay per account, and the account thresholds |
|
|
18
18
|
|
|
19
19
|
The launcher sets `CLAUDE_CONFIG_DIR` to the profile and passes every argument to
|
|
20
20
|
`claude`, so `claude-ev -p "..."` runs like `claude -p "..."` on the second
|
|
@@ -43,19 +43,17 @@ to spend leftover usage that expires soon.
|
|
|
43
43
|
|---|---|
|
|
44
44
|
| Main week resets within 24 hours, main under 90% of its week, main under 50% of its 5-hour window | main |
|
|
45
45
|
| Otherwise, second under 95% of its week and under 90% of its 5-hour window | second |
|
|
46
|
-
| Second meter unreadable |
|
|
46
|
+
| Second meter unreadable | wait until the meter can be read |
|
|
47
47
|
| Otherwise | wait, with the next reset time |
|
|
48
48
|
|
|
49
49
|
An unreadable main meter never picks main.
|
|
50
50
|
|
|
51
51
|
```
|
|
52
|
-
python packages/claude-dev-env/scripts/
|
|
53
|
-
{"account": "second", "config_dir": "C:\\Users\\me\\.claude-profiles\\ev", "reason": "...",
|
|
54
|
-
"meters": {"main": {"session_used_percent": 12.0, "session_resets_at": "...",
|
|
55
|
-
"weekly_used_percent": 34.0, "weekly_resets_at": "..."},
|
|
56
|
-
"second": null}}
|
|
52
|
+
python packages/claude-dev-env/scripts/account_broker.py choose --product claude
|
|
57
53
|
```
|
|
58
54
|
|
|
59
|
-
The
|
|
60
|
-
|
|
61
|
-
|
|
55
|
+
The JSON output has a `decision` object and an `accounts` list. Each account's
|
|
56
|
+
`meters` holds its remaining percent and reset time for the 5-hour window and
|
|
57
|
+
the week. An unreadable meter appears as `null`. A wait decision has
|
|
58
|
+
`action: "wait"` and a `resets_at` timestamp. The broker exits with code 3 on
|
|
59
|
+
wait. The worker writes the same reset time to its report.
|
package/package.json
CHANGED
|
@@ -24,13 +24,8 @@ _SCRIPTS_DIRECTORY = str(Path(__file__).resolve().parent)
|
|
|
24
24
|
if _SCRIPTS_DIRECTORY not in sys.path:
|
|
25
25
|
sys.path.insert(0, _SCRIPTS_DIRECTORY)
|
|
26
26
|
|
|
27
|
-
import claude_chain_runner as chain_runner # noqa: E402
|
|
28
27
|
import invoke_code_review as invoker # noqa: E402
|
|
29
|
-
from
|
|
30
|
-
from dev_env_scripts_constants.claude_chain_constants import ( # noqa: E402
|
|
31
|
-
TERMINAL_STATUS_CHAIN_EXHAUSTED,
|
|
32
|
-
TERMINAL_STATUS_SERVED,
|
|
33
|
-
)
|
|
28
|
+
from dev_env_scripts_constants.account_broker_constants import JobOutcome, Product, WAIT_EXIT_CODE
|
|
34
29
|
from dev_env_scripts_constants.code_review_constants import ( # noqa: E402
|
|
35
30
|
CLI_SESSION_MODEL_FLAG,
|
|
36
31
|
CODE_REVIEW_MODEL_ALIAS,
|
|
@@ -111,27 +106,29 @@ def claude_served(
|
|
|
111
106
|
*,
|
|
112
107
|
returncode: int = FIXTURE_CHAIN_RETURNCODE,
|
|
113
108
|
stdout: str = FIXTURE_CHAIN_STDOUT,
|
|
114
|
-
) ->
|
|
115
|
-
return
|
|
116
|
-
served_command=FIXTURE_SERVED_COMMAND,
|
|
109
|
+
) -> JobOutcome:
|
|
110
|
+
return JobOutcome(
|
|
117
111
|
returncode=returncode,
|
|
118
112
|
stdout=stdout,
|
|
119
113
|
stderr="",
|
|
120
|
-
|
|
121
|
-
|
|
114
|
+
account_name=FIXTURE_SERVED_COMMAND,
|
|
115
|
+
attempts=((FIXTURE_SERVED_COMMAND, "served"),),
|
|
116
|
+
status="served" if returncode == 0 else "advisor_blocked",
|
|
117
|
+
session_id=None,
|
|
118
|
+
wait_reset_at=None,
|
|
122
119
|
)
|
|
123
120
|
|
|
124
121
|
|
|
125
|
-
def claude_failed() ->
|
|
126
|
-
return
|
|
127
|
-
|
|
128
|
-
returncode=FIXTURE_FAILED_RETURNCODE,
|
|
122
|
+
def claude_failed() -> JobOutcome:
|
|
123
|
+
return JobOutcome(
|
|
124
|
+
returncode=WAIT_EXIT_CODE,
|
|
129
125
|
stdout="",
|
|
130
|
-
stderr="
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
126
|
+
stderr="",
|
|
127
|
+
account_name=None,
|
|
128
|
+
attempts=((FIXTURE_SERVED_COMMAND, "usage_limited"),),
|
|
129
|
+
status="exhausted",
|
|
130
|
+
session_id=None,
|
|
131
|
+
wait_reset_at=None,
|
|
135
132
|
)
|
|
136
133
|
|
|
137
134
|
|
|
@@ -179,7 +176,7 @@ class SeamCallLog:
|
|
|
179
176
|
@dataclass
|
|
180
177
|
class _SeamConfiguration:
|
|
181
178
|
host_profile: str
|
|
182
|
-
claude_outcome:
|
|
179
|
+
claude_outcome: JobOutcome | BaseException | None
|
|
183
180
|
should_dirty_tree_on_chain: bool
|
|
184
181
|
working_directory: Path | None
|
|
185
182
|
|
|
@@ -220,23 +217,21 @@ def _build_host_profile_seam(
|
|
|
220
217
|
|
|
221
218
|
def _build_claude_seam(
|
|
222
219
|
call_log: SeamCallLog, configuration: _SeamConfiguration
|
|
223
|
-
) -> Callable[...,
|
|
220
|
+
) -> Callable[..., JobOutcome]:
|
|
224
221
|
def fake_claude(
|
|
225
|
-
all_claude_arguments: list[str],
|
|
226
|
-
) ->
|
|
222
|
+
product: Product, all_claude_arguments: list[str], **options: object
|
|
223
|
+
) -> JobOutcome:
|
|
224
|
+
assert product is Product.CLAUDE
|
|
227
225
|
call_log.claude_calls += 1
|
|
228
|
-
call_log.claude_arguments = list(all_claude_arguments)
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
timeout=timeout_seconds,
|
|
234
|
-
check=False,
|
|
235
|
-
)
|
|
226
|
+
call_log.claude_arguments = list(all_claude_arguments[1:])
|
|
227
|
+
call_log.is_stdin_empty = options.get("stdin_text") == ""
|
|
228
|
+
if options.get("cwd") is not None:
|
|
229
|
+
call_log.claude_working_directory = Path(options["cwd"])
|
|
230
|
+
call_log.all_observed_working_directories.append(Path(options["cwd"]))
|
|
236
231
|
_record_dirty_tree(configuration)
|
|
237
232
|
if isinstance(configuration.claude_outcome, BaseException):
|
|
238
233
|
raise configuration.claude_outcome
|
|
239
|
-
assert isinstance(configuration.claude_outcome,
|
|
234
|
+
assert isinstance(configuration.claude_outcome, JobOutcome)
|
|
240
235
|
return configuration.claude_outcome
|
|
241
236
|
|
|
242
237
|
return fake_claude
|
|
@@ -284,18 +279,13 @@ def _apply_seams(
|
|
|
284
279
|
"review_claude_runner",
|
|
285
280
|
_build_claude_seam(call_log, configuration),
|
|
286
281
|
)
|
|
287
|
-
monkeypatch.setattr(
|
|
288
|
-
chain_runner,
|
|
289
|
-
"chain_subprocess_runner",
|
|
290
|
-
_build_subprocess_seam(call_log),
|
|
291
|
-
)
|
|
292
282
|
|
|
293
283
|
|
|
294
284
|
def install_seams(
|
|
295
285
|
monkeypatch: pytest.MonkeyPatch,
|
|
296
286
|
*,
|
|
297
287
|
host_profile: str = HOST_PROFILE_CLAUDE,
|
|
298
|
-
claude_outcome:
|
|
288
|
+
claude_outcome: JobOutcome | BaseException | None = None,
|
|
299
289
|
should_dirty_tree_on_chain: bool = False,
|
|
300
290
|
working_directory: Path | None = None,
|
|
301
291
|
) -> SeamCallLog:
|