cdx-manager 0.25.0 → 0.25.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/README.md +1 -1
- package/VERSION +1 -1
- package/changelogs/CHANGELOGS_0_25_1.md +14 -0
- package/changelogs/CHANGELOGS_0_25_2.md +15 -0
- package/checksums/release-archives.json +22 -0
- package/docs/cli.md +90 -4
- package/docs/operations.md +9 -0
- package/package.json +1 -1
- package/pyproject.toml +1 -1
- package/src/cli.py +7 -5
- package/src/cli_args.py +5 -2
- package/src/commands/launch.py +13 -5
- package/src/commands/runs.py +88 -24
- package/src/commands/status.py +32 -4
- package/src/history_models.py +10 -1
- package/src/project_identity.py +71 -0
- package/src/provider_runtime.py +35 -5
- package/src/run_command.py +14 -1
- package/src/run_observation.py +93 -0
- package/src/run_registry.py +17 -2
- package/src/run_usage.py +38 -4
- package/src/session_service.py +11 -0
- package/src/session_store.py +62 -0
- package/src/workspace_sandbox.py +6 -2
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
<br clear="left"/>
|
|
6
6
|
|
|
7
|
-
[](LICENSE) ](LICENSE)  
|
|
8
8
|
|
|
9
9
|
**The right AI coding account, when you need it.** CDX Manager keeps your Codex and Claude accounts in separate profiles, shows which ones have quota, and helps you pick the next usable session from your terminal.
|
|
10
10
|
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.25.
|
|
1
|
+
0.25.2
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# CDX Manager 0.25.1
|
|
2
|
+
|
|
3
|
+
Release date: 2026-10-31
|
|
4
|
+
|
|
5
|
+
This patch release preserves shared Playwright browsers in isolated workspaces. (#73)
|
|
6
|
+
|
|
7
|
+
## Fixes
|
|
8
|
+
|
|
9
|
+
- Force `PLAYWRIGHT_SKIP_BROWSER_GC=1` in isolated preparation and provider environments so Playwright does not delete shared browser revisions whose package references are only visible on the host. Browser installation remains available; cache cleanup remains an explicit operator operation. (#73)
|
|
10
|
+
|
|
11
|
+
## Maintenance
|
|
12
|
+
|
|
13
|
+
- Record tray companion checksums for v0.25.0. (`c766e752cbf79c74bcc2a80ab39dc5f7364d7fd7`)
|
|
14
|
+
- Record release archive checksums for v0.25.0. (`feb0fbc276b0456d238cf64fada6aa34a96890fa`)
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# CDX Manager 0.25.2
|
|
2
|
+
|
|
3
|
+
Release date: 2026-10-31
|
|
4
|
+
|
|
5
|
+
This patch release improves usage attribution and makes unverifiable run budgets fail explicitly.
|
|
6
|
+
|
|
7
|
+
## Fixes
|
|
8
|
+
|
|
9
|
+
- Preserve observed Codex model evidence for usage pricing. Keep costs unknown when model evidence is missing or mixed without per-model counters, and fail unverifiable budgets with `budget_unverifiable` and exit code 125. (#76)
|
|
10
|
+
- Attribute CDX usage to canonical source repositories so project statistics group runs by their source repository rather than isolated execution directories. (#75)
|
|
11
|
+
|
|
12
|
+
## Maintenance
|
|
13
|
+
|
|
14
|
+
- Record tray companion checksums for v0.25.1. (`e3530977b2b95cd3ec39bbe962725f275fef4370`)
|
|
15
|
+
- Record release archive checksums for v0.25.1. (`59b5d276abc5877554d9ffc44b468b53b12dd1d4`)
|
|
@@ -683,6 +683,28 @@
|
|
|
683
683
|
"x86_64-pc-windows-msvc": "21dfebd0265bd8705d381517357cd8166cb0e2da37b27ed3e3485c892d9289ee",
|
|
684
684
|
"x86_64-unknown-linux-musl": "8477262e55f31ee2217382048b967846478a34f222beeb4242fa9c4c2942f747"
|
|
685
685
|
}
|
|
686
|
+
},
|
|
687
|
+
"v0.25.0": {
|
|
688
|
+
"github_tarball_sha256": "5372027a03afb12763993816b8421ca9387ca5d6d4eb5508e9b915fe08344957",
|
|
689
|
+
"github_zip_sha256": "2ca9873cff13f5fcf526fa3275bf9735f6a19fff2dbf1d98b95f891de2bbf448",
|
|
690
|
+
"tray_assets": {
|
|
691
|
+
"aarch64-apple-darwin": "7138e93a8ec02ed3435b012be22ddc58cf758eb692ca930769be4b8493ee6ca3",
|
|
692
|
+
"aarch64-unknown-linux-musl": "40ffad4815394fddc9e410cef2ba5c12af05bc364bbb3f510c630f59bd1e8559",
|
|
693
|
+
"x86_64-apple-darwin": "e8587030436e7134e9889c7a73e86ac62632b0b79331531c1e5e475f932f3ad5",
|
|
694
|
+
"x86_64-pc-windows-msvc": "2493b0c8212ccdbf5e69756af07afe47c73474407f79604d671142d5192aa642",
|
|
695
|
+
"x86_64-unknown-linux-musl": "53024f4908847452ce6ac399ca03725b95b628df721856da6e2ed9399691455f"
|
|
696
|
+
}
|
|
697
|
+
},
|
|
698
|
+
"v0.25.1": {
|
|
699
|
+
"github_tarball_sha256": "7899a8cb3f3eb2d83d10b921a4c22c16f1d7eba4681c564b7b09812699892ad6",
|
|
700
|
+
"github_zip_sha256": "19f9646e1e74761f588b9ef195a2a90914a48ad2621a67f9d85057417db8cd19",
|
|
701
|
+
"tray_assets": {
|
|
702
|
+
"aarch64-apple-darwin": "d5d5fd19ad3a2e8bd3bcd72a959a217bbfa2fea351bcd739b94265ceb1fdd2c4",
|
|
703
|
+
"aarch64-unknown-linux-musl": "e8824cd7ec91e81bf7be1f2af53689de0aa8bbb9f7037d9e2e51fed35837a8a3",
|
|
704
|
+
"x86_64-apple-darwin": "d507a6f35894a7125cef7dc67f98e485e08774221d9dc92698c11b0f9c5354c3",
|
|
705
|
+
"x86_64-pc-windows-msvc": "183ef3664489888a356ad9a6096b68c913d433c513d6d10e9890e65667e1e417",
|
|
706
|
+
"x86_64-unknown-linux-musl": "5660cfd08c0580db43dee5664fe95fc4cd528a6495c40167e34c122d3c4fcd4a"
|
|
707
|
+
}
|
|
686
708
|
}
|
|
687
709
|
}
|
|
688
710
|
}
|
package/docs/cli.md
CHANGED
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
| `cdx set <name>\|--sessions all\|a,b\|--provider PROVIDER [--power minimal\|low\|medium\|high\|xhigh] [--permission review\|default\|auto\|full] [--fast on\|off] [--rtk on\|off] [--logics on\|off] [--notify on\|off] [--agent-state on\|off] [--notify-preview on\|off] [--scrub-env on\|off] [--shared-memory on\|off] [--retro on\|off] [--policy use\|ask-first\|never] [--model MODEL] [--fallback-model MODEL[,MODEL...]] [--budget USD] [--extra-args 'ARGS'] [--priority 0..100] [--json]` | Persist launch settings for one or more sessions |
|
|
29
29
|
| `cdx unset <name>\|--sessions all\|a,b\|--provider PROVIDER (--power\|--reasoning-effort\|--permission\|--fast\|--rtk\|--logics\|--notify\|--agent-state\|--notify-preview\|--scrub-env\|--shared-memory\|--retro\|--policy\|--model\|--fallback-model\|--budget\|--extra-args\|--priority\|--all) [--json]` | Remove persisted launch settings and fall back to provider defaults |
|
|
30
30
|
| `cdx history [name] [--limit N] [--summary] [--since 7d\|today\|DATE] [--from DATE] [--to DATE] [--json]` | Show recent launch history or aggregate total launch time per assistant, optionally filtered by period |
|
|
31
|
+
| `cdx history backfill-projects [--mapping FILE] [--dry-run \| --apply] [--json]` | Recover source repository attribution from exact recorded run evidence or an operator mapping; defaults to a read-only preview |
|
|
31
32
|
| `cdx history backfill-models [--dry-run \| --apply] [--json]` | Recover missing historical usage models from evidence tied to each run; defaults to a read-only preview |
|
|
32
33
|
| `cdx last [--json]` | Launch the most recent existing session from launch history |
|
|
33
34
|
| `cdx resume <name> [--conversation <id>] [--json]` | Resume the provider-native conversation for a session using the named command form; `--conversation` reopens that exact conversation and refuses with `conversation_not_found` if the profile has no such transcript |
|
|
@@ -62,7 +63,7 @@
|
|
|
62
63
|
| `cdx run-tail <run_id> [--lines N] --json` | Last lines of a run's own output, while it is still running or after it finished |
|
|
63
64
|
| `cdx runs [--limit N] [--since 7d\|today\|DATE] [--running] --json` | List recent runs; `--since` returns every run completed after the cursor and ignores `--limit`; `--running` returns every run still running, uncapped. A run's `kind` is one of the values `cdx schema --json` lists under `enums.kind` |
|
|
64
65
|
| `cdx schema --json` | Publish the enums, mutually-exclusive argument groups, and error codes programmatic callers should validate against |
|
|
65
|
-
| `cdx stats [name] [--since 7d\|today\|DATE] [--from DATE] [--to DATE] [--by session\|project\|model] [--json]` | Aggregate launch counts, duration, and known token usage by session, for headless and interactive runs alike. `--by project` groups runs by
|
|
66
|
+
| `cdx stats [name] [--since 7d\|today\|DATE] [--from DATE] [--to DATE] [--by session\|project\|model] [--json]` | Aggregate launch counts, duration, and known token usage by session, for headless and interactive runs alike. `--by project` groups runs by the recorded canonical source repository (labelled by the shortest distinguishing path suffix; `project` in JSON holds the canonical identity, `session_name` the display label, and `-` explicitly means unknown), `--by model` by the model that served them (same totals, same pricing; default `session`). PROV. lists every provider a row saw, such as `claude+codex`. Without a session filter it also prints what rtk saved over the period (`rtk` in JSON), read with `rtk gain` from `RTK_DB_PATH` when set, else the shared history, and the older per-profile ones. |
|
|
66
67
|
| `cdx status [--json] [--refresh\|--cached] [--timeout SECONDS]` | Show token usage table for all sessions; empty credits, block, and reset columns are omitted; `--cached` skips live provider probes and returns only stored status; Claude rows also report the auth type (`setup-token` or `full-login`) in a TYPE column and as `auth_type` in JSON. |
|
|
67
68
|
| `cdx status --small [--refresh\|--cached] [--timeout SECONDS]` / `cdx status -s [--refresh\|--cached] [--timeout SECONDS]` | Show compact token usage table without provider, blocking quota, credits, and updated columns |
|
|
68
69
|
| `cdx status <name> [--json] [--refresh\|--cached] [--timeout SECONDS]` | Show detailed usage breakdown for one session; `--cached` avoids live provider refreshes |
|
|
@@ -253,7 +254,7 @@ At or above the limit, cdx terminates the provider process tree, skips further r
|
|
|
253
254
|
|
|
254
255
|
With `--detach`, the launch still exits successfully and includes `budget_usd`; cdx always uses its own child supervisor for budgeted runs, including when native background delegation is enabled. Inspect `cdx run-status RUN_ID --json` or `cdx run-report RUN_ID --json` for the child's final payload and budget error. `--continue-run` starts a fresh budget for the new run, including in detached mode.
|
|
255
256
|
|
|
256
|
-
Conservative assumption: this is a stop based on observed, priceable usage, not a prepaid billing limit. Provider updates can arrive late or only at completion, and termination can take time, so actual spend can exceed the amount.
|
|
257
|
+
Conservative assumption: this is a stop based on observed, priceable usage, not a prepaid billing limit. Provider updates can arrive late or only at completion, and termination can take time, so actual spend can exceed the amount. Before the first measurement, `budget_status` is `usage_not_measured`; absence is not zero spend. Once usage is measured, missing continuation boundaries, mixed/unobserved models, and unknown prices stop further execution/recovery with exit **125**, `error.source: "cdx"`, `error.code: "budget_unverifiable"`, `budget_status: "unverifiable"`, and `cost_usd: null`. This is a measurement failure, not a claim that the dollar limit was exceeded or enforced. Supply a `CDX_TOKEN_PRICES` override for a model missing from the table. Existing profile-level `cdx set --budget` settings remain the provider-native Claude cap and apply independently.
|
|
257
258
|
|
|
258
259
|
Every usage record, whichever path produced it, uses one set of definitions:
|
|
259
260
|
|
|
@@ -289,6 +290,10 @@ The figure uses standard public list prices, excluding Fast/priority, Batch/Flex
|
|
|
289
290
|
|
|
290
291
|
`cdx stats` counts completed history attempts. A headless failover has one logical run in `cdx runs` but one history row per attempted session, including failed attempts that reported usage. Period filtering includes the entire run in every period it overlaps, so adjacent daily results can both include a run crossing midnight; adding daily totals may exceed a combined period. JSON `accounting` names this policy and the current price-table source. `usage_runs` and `priced_runs` show how much of the token total has a dollar estimate. Live runs are absent until their history row is written, and unknown usage is different from measured zero.
|
|
291
292
|
|
|
293
|
+
Completed results, registry records, and history JSON include `cost_status` (`estimated` or `unknown`) and nullable `cost_reason`: `usage_not_measured`, `model_not_observed`, `model_price_unknown`, `attempt_usage_unavailable`, or `mixed_models_without_counters`. Stats include `unpriced_reasons` counts alongside `priced_runs` and `unpriced_models`. These are list-price estimates, not billing receipts. `budget_status` is `not_requested`, `usage_not_measured`, `estimated`, `exceeded`, or `unverifiable`.
|
|
294
|
+
|
|
295
|
+
New headless attempts have distinct `attempt_id` values and artifact paths, including retries on the same profile. Final payloads and registry reports expose `attempts` with their individual model evidence, counters, and estimates; top-level usage describes the final attempt, while a budget-exceeded spend includes all attempts. New isolated Codex attempts mount only a fresh sessions directory from volatile `/dev/shm` storage. The supervisor reads the exact `thread.started` rollout while it exists and at closeout, requiring matching session identity, turn timestamps within this attempt, and token counters consistent with stdout. It exports `usage_evidence` containing only the stdout attempt identity, thread id, timestamp bounds, turn ids, observed models, and normalized counters; live rollout counters can establish the first measurement before `turn.completed`. No requested model, agent-authored cost report, current profile setting, prompt, message, or tool output is used as model evidence. The raw temporary sessions are removed at closeout, including cancellation and startup failure; they are never copied into persistent run artifacts. Host profiles are not mounted. Mixed serving models stay unpriced without attributable counters. Isolated continuation without comparable launch boundaries stays unmeasured rather than charging an older conversation to the new attempt.
|
|
296
|
+
|
|
292
297
|
Completed run JSON and registry reports expose `usage_model` separately from the requested `model`. Pricing uses the observed model from provider output; Codex can also supply it through the exact rollout identified by `thread.started.thread_id`. Configured defaults alone never establish serving-model identity, and missing or mixed model evidence stays unpriced. `--by model` groups Claude's `[1m]` context variant with its base model while preserving the original model string and per-run pricing. New runs record this evidence automatically. To recover missing models in existing history, run:
|
|
293
298
|
|
|
294
299
|
```bash
|
|
@@ -297,9 +302,9 @@ cdx history backfill-models --dry-run --json # Machine-readable preview
|
|
|
297
302
|
cdx history backfill-models --apply # Save proven models with a backup
|
|
298
303
|
```
|
|
299
304
|
|
|
300
|
-
The command considers only history rows with usage and an empty or placeholder `usage_model`. Evidence is checked in order: the row's preserved JSON/JSONL stdout, a preserved run-registry report with the same run id, profile, provider and stdout path, then the exact Codex thread rollout or Claude session transcript. Thread/session ids must come from the row or its stdout, or from a preserved transcript path recorded as an exact conversation-id match. The current profile is used only to locate an exact transcript under its auth home; its configured model and latest conversation/transcript never establish a historical model. The requested `model` in a report is also ignored.
|
|
305
|
+
The command considers only history rows with usage and an empty or placeholder `usage_model`. Evidence is checked in order: a supervisor-preserved isolated observation tied to the exact stdout attempt and counters, the row's preserved JSON/JSONL stdout, a preserved run-registry report with the same run id, profile, provider and stdout path, then the exact Codex thread rollout or Claude session transcript. Thread/session ids must come from the row or its stdout, or from a preserved transcript path recorded as an exact conversation-id match. The current profile is used only to locate an exact transcript under its auth home; its configured model and latest conversation/transcript never establish a historical model. The requested `model` in a report is also ignored.
|
|
301
306
|
|
|
302
|
-
Only one serving model is accepted. Claude `[1m]` variants count as the same model for this check and for stats grouping; the observed spelling is preserved. Mixed models, incomplete turn models, malformed or oversized evidence, conflicting identities and missing exact transcripts stay unknown. For resumed conversations, the conservative rule is that all recorded turns in the exact transcript must agree: this command does not infer historical turn boundaries. Ambiguous higher-priority evidence stops recovery rather than being overridden by a lower-priority source. Missing files may fall through to the next source.
|
|
307
|
+
Run recovery in dry-run mode first and review every evidence kind and unknown reason before applying. A missing isolated rollout with no preserved observation cannot be reconstructed from a current profile: its historical model and cost remain unknown. No historical recovery is applied automatically. Only one serving model is accepted. Claude `[1m]` variants count as the same model for this check and for stats grouping; the observed spelling is preserved. Mixed models, incomplete turn models, malformed or oversized evidence, conflicting identities and missing exact transcripts stay unknown. For resumed conversations, the conservative rule is that all recorded turns in the exact transcript must agree: this command does not infer historical turn boundaries. Ambiguous higher-priority evidence stops recovery rather than being overridden by a lower-priority source. Missing files may fall through to the next source.
|
|
303
308
|
|
|
304
309
|
Text and JSON output include counts by evidence kind, counts of unknown reasons and up to 20 samples. Dry-run leaves history and run reports unchanged. `--apply` changes only missing models and their evidence annotation, preserving token counts and unrelated history bytes. It holds the history lock, saves the original bytes to a unique `state/launch_history.jsonl.models-<id>.bak` file, then atomically replaces history. The backup path is printed; a repeated apply leaves already recovered rows alone. `--apply` and `--dry-run` cannot be combined. Subsequent `cdx stats` calls price recovered models when the local price table knows them; unknown models remain unpriced.
|
|
305
310
|
|
|
@@ -459,3 +464,84 @@ It returns accepted values for `permission` (with its aliases and canonical form
|
|
|
459
464
|
Codex ties network access to its sandbox, so any permission below `full` leaves the run unable to resolve DNS — `gh`, `curl`, and package installs fail inside an otherwise successful run. Show these warnings to whoever reads your output rather than discarding them; a run that succeeds while quietly doing less is the case they exist for.
|
|
460
465
|
|
|
461
466
|
Claude result records with nonempty `permission_denials` produce a warning with code `permission_denials`, the `provider`, and the structured `permission_denials` list. Exit code 0 still means `ok: true`: a denied tool may have been replaced successfully. Empty or absent denials add no warning. With `--scrub-env on`, Claude forces its default permission mode; allow tools needed by unattended runs explicitly using `--allowedTools` via `--extra-args` (see [operations](operations.md)).
|
|
467
|
+
|
|
468
|
+
### Source repository attribution and external launchers
|
|
469
|
+
|
|
470
|
+
CDX captures `project` (an absolute canonical source repository path) and
|
|
471
|
+
`project_source` before starting a provider or applying workspace isolation.
|
|
472
|
+
Normal Git launches use the repository root, including launches from subfolders.
|
|
473
|
+
Linked Git worktrees use their common Git directory's source repository, so
|
|
474
|
+
normal and worktree launches share an identity even after worktree deletion.
|
|
475
|
+
Unrelated repositories with the same basename retain separate full-path keys.
|
|
476
|
+
Without Git evidence or an explicit source, attribution is unknown (`project:
|
|
477
|
+
null`, `project_source: "unknown"` in history; `project: "-"` in project stats).
|
|
478
|
+
Old cwd-only history also stays unknown: stats never inspect today's filesystem
|
|
479
|
+
or profile settings to guess yesterday's project.
|
|
480
|
+
|
|
481
|
+
Trusted producers such as Jarvis/remote-ops must resolve the source repository
|
|
482
|
+
**before** creating a disposable mission directory, worktree, or temporary clone,
|
|
483
|
+
and pass it to the installed CDX launcher:
|
|
484
|
+
|
|
485
|
+
```bash
|
|
486
|
+
cdx run work --cwd /temporary/mission --project /repos/remote-ops --prompt 'Fix the issue' --isolate-workspace --json
|
|
487
|
+
cdx work --dir /temporary/mission --project /repos/remote-ops
|
|
488
|
+
```
|
|
489
|
+
|
|
490
|
+
`--project` is a trusted operator assertion, not provider output, a profile
|
|
491
|
+
setting, or an environment variable. It must be an absolute normalized path,
|
|
492
|
+
with symlinks already resolved by the producer; it may refer to a deleted source.
|
|
493
|
+
A plain temporary clone has no reliable link to a local source repository: the
|
|
494
|
+
producer must supply `--project` rather than expecting inference from a remote
|
|
495
|
+
URL or directory naming convention. Existing launch-directory validation and
|
|
496
|
+
execution `cwd` are unchanged. CDX records the separate identity in history,
|
|
497
|
+
runtime/registry records, and launch/result JSON. Failover attempts and detached
|
|
498
|
+
children retain the launch identity. `--continue-run` inherits the parent's
|
|
499
|
+
recorded identity (including unknown) and rejects a different `--project`.
|
|
500
|
+
Neither continuation nor backfill reattributes an older run from current profile
|
|
501
|
+
configuration.
|
|
502
|
+
|
|
503
|
+
Consumers should use stats `project` as the stable grouping key and
|
|
504
|
+
`session_name` as the disambiguated display label. In particular, dependent
|
|
505
|
+
remote-ops `scripts/cdx-stats.py` should not reduce `project` to `Path(...).name`:
|
|
506
|
+
that loses disambiguation between unrelated repositories named alike. Token,
|
|
507
|
+
duration, cost, and attempt counts keep the existing accounting rules; the
|
|
508
|
+
existing `totals.sessions` field counts groups for the chosen grouping.
|
|
509
|
+
|
|
510
|
+
### Recover historical project attribution
|
|
511
|
+
|
|
512
|
+
```bash
|
|
513
|
+
cdx history backfill-projects --mapping projects.json # Preview (default)
|
|
514
|
+
cdx history backfill-projects --mapping projects.json --dry-run --json
|
|
515
|
+
cdx history backfill-projects --mapping projects.json --apply
|
|
516
|
+
```
|
|
517
|
+
|
|
518
|
+
The mapping format is deliberately generic, with exact CDX run IDs, not mission
|
|
519
|
+
IDs, profile names, prefixes, or cwd values:
|
|
520
|
+
|
|
521
|
+
```json
|
|
522
|
+
{"version":1,"projects":[{"run_id":"exact CDX run id","project":"/repos/remote-ops"}]}
|
|
523
|
+
```
|
|
524
|
+
|
|
525
|
+
Operators must build this file from attributable external evidence, such as an
|
|
526
|
+
exact launch receipt. CDX never imports Jarvis, scans its private state, guesses
|
|
527
|
+
from disposable path prefixes, or resolves deleted worktrees. The supplied
|
|
528
|
+
Tower observation (84 disposable groups attributed by receipts to remote-ops
|
|
529
|
+
66, cdx-manager 10, logics-manager 6, city-jump 2) is diagnostic evidence, not a
|
|
530
|
+
mapping: actual exact CDX run IDs must be obtained on Tower before repair.
|
|
531
|
+
|
|
532
|
+
Without a mapping, the command can recover only from trusted project fields
|
|
533
|
+
already preserved for the exact run in CDX history or its run registry. A cwd-only
|
|
534
|
+
report is insufficient evidence. All attempts sharing an exact run ID must agree.
|
|
535
|
+
Invalid schema, contradictory duplicate mapping entries, and unknown exact run
|
|
536
|
+
IDs fail validation. An attribution conflict with known history or recorded run
|
|
537
|
+
evidence blocks the entire batch, preserving the original history and returning
|
|
538
|
+
exit status 1 with the conflict counts.
|
|
539
|
+
|
|
540
|
+
Results report `recovered` (recoverable history rows), `unchanged`, `unresolved`,
|
|
541
|
+
and `conflicting` counts, plus `applied` and `backup_path`. Counts are history
|
|
542
|
+
attempts, matching existing history accounting. Dry runs write nothing. Apply
|
|
543
|
+
holds the existing history lock, writes an original-byte `.projects-*.bak`
|
|
544
|
+
backup, and atomically replaces history only when the complete batch has no
|
|
545
|
+
conflict. Unrelated fields, usage, and malformed/unrelated history lines survive.
|
|
546
|
+
Repeating the same mapping is a no-op and creates no additional backup. The
|
|
547
|
+
registry and provider transcripts are never rewritten by backfill.
|
package/docs/operations.md
CHANGED
|
@@ -240,3 +240,12 @@ credential sync.
|
|
|
240
240
|
- **`cdx login` hangs** — the provider's login flow requires a browser or device code. Follow the on-screen instructions in the terminal that opened.
|
|
241
241
|
- **`make install` says `npm link` is not found** — ensure Node.js and npm are installed and in your PATH.
|
|
242
242
|
- **On Windows, `doctor` warns that `script` is missing** — this is expected on many setups. Codex still launches, but transcript capture stays disabled unless you point `CDX_SCRIPT_BIN` to a compatible wrapper.
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
### Playwright in isolated workspaces
|
|
246
|
+
|
|
247
|
+
Isolated preparation and provider environments force `PLAYWRIGHT_SKIP_BROWSER_GC=1`.
|
|
248
|
+
The browser cache is shared with host services, but package paths differ inside
|
|
249
|
+
`/cache` and on the host. Playwright garbage collection cannot see every user's
|
|
250
|
+
package references and must not delete other users' browser revisions. Browser
|
|
251
|
+
installation remains available; cache cleanup is an explicit operator operation.
|
package/package.json
CHANGED
package/pyproject.toml
CHANGED
package/src/cli.py
CHANGED
|
@@ -245,7 +245,7 @@ def _print_help(use_color=False):
|
|
|
245
245
|
f" {_style('cdx reset <name> [--yes] [--json]', '36', use_color)}",
|
|
246
246
|
f" {_style('cdx auth refresh <name|all> [--json]', '36', use_color)}",
|
|
247
247
|
f" {_style('cdx select --provider PROVIDER [--min-reasoning-effort minimal|low|medium|high|xhigh] [--min-power minimal|low|medium|high|xhigh] [--require-ready] [--refresh] --json', '36', use_color)}",
|
|
248
|
-
f" {_style('cdx run [session] --cwd PATH (--prompt-file PATH|--prompt TEXT|--prompt-file -) [--provider PROVIDER] [--model MODEL] [--kind assistant|code-review|retro] [--reasoning-effort minimal|low|medium|high|xhigh] [--power minimal|low|medium|high|xhigh] [--permission review|default|auto|full|workspace-write|read-only|danger-full-access] [--timeout-seconds N] [--label TEXT] [--max-parallel N] [--continue-run RUN_ID] [--detach] [--failover] [--refresh] --json', '36', use_color)}",
|
|
248
|
+
f" {_style('cdx run [session] --cwd PATH [--project PATH] (--prompt-file PATH|--prompt TEXT|--prompt-file -) [--provider PROVIDER] [--model MODEL] [--kind assistant|code-review|retro] [--reasoning-effort minimal|low|medium|high|xhigh] [--power minimal|low|medium|high|xhigh] [--permission review|default|auto|full|workspace-write|read-only|danger-full-access] [--timeout-seconds N] [--label TEXT] [--max-parallel N] [--continue-run RUN_ID] [--detach] [--failover] [--refresh] --json', '36', use_color)}",
|
|
249
249
|
f" {_style('cdx runs [--limit N] [--since 7d|today|DATE] [--running] --json', '36', use_color)}",
|
|
250
250
|
f" {_style('cdx run-status <run_id> --json', '36', use_color)}",
|
|
251
251
|
f" {_style('cdx run-report <run_id> --json', '36', use_color)}",
|
|
@@ -260,10 +260,11 @@ def _print_help(use_color=False):
|
|
|
260
260
|
f" {_style('cdx power|perm|fast|model <name|all|provider:PROVIDER|a,b> <value|default> [--json]', '36', use_color)}",
|
|
261
261
|
f" {_style(SET_USAGE[len('Usage: '):], '36', use_color)}",
|
|
262
262
|
f" {_style('cdx unset <name>|--sessions all|a,b|--provider PROVIDER (--power|--permission|--fast|--rtk|--logics|--notify|--notify-preview|--model|--priority|--all) [--json]', '36', use_color)}",
|
|
263
|
+
f" {_style('cdx history backfill-projects [--mapping FILE] [--dry-run|--apply] [--json]', '36', use_color)}",
|
|
263
264
|
f" {_style('cdx history [name] [--limit N] [--summary] [--since 7d|today|DATE] [--from DATE] [--to DATE] [--json]', '36', use_color)}",
|
|
264
265
|
f" {_style('cdx stats [name] [--since 7d|today|DATE] [--from DATE] [--to DATE] [--by session|project|model] [--json]', '36', use_color)}",
|
|
265
266
|
f" {_style('cdx last [--json]', '36', use_color)}",
|
|
266
|
-
f" {_style('cdx resume <name> [--conversation <id>] [--json]', '36', use_color)}",
|
|
267
|
+
f" {_style('cdx resume <name> [--project PATH] [--conversation <id>] [--json]', '36', use_color)}",
|
|
267
268
|
f" {_style('cdx can-resume <name> [--conversation <id>] [--json]', '36', use_color)}",
|
|
268
269
|
f" {_style('cdx rescue [<session>] [--to <target>] [--yes] [--dry-run] [--json]', '36', use_color)}",
|
|
269
270
|
f" {_style('cdx retro [--limit N] [--json]', '36', use_color)}",
|
|
@@ -295,7 +296,7 @@ def _print_help(use_color=False):
|
|
|
295
296
|
_style("Notifications:", "1", use_color),
|
|
296
297
|
f" {_style('cdx set <name> --notify on', '36', use_color)} # agent finishes or needs attention",
|
|
297
298
|
f" {_style('cdx ready', '36', use_color)} # next session to become ready",
|
|
298
|
-
f" {_style('cdx <name> [--on-busy fail|ask] [--json]', '36', use_color)}",
|
|
299
|
+
f" {_style('cdx <name> [--project PATH] [--on-busy fail|ask] [--json]', '36', use_color)}",
|
|
299
300
|
" --on-busy: fail immediately if already running; ask (default) prompts only with a terminal and without --json",
|
|
300
301
|
f" {_style('cdx <name> --prompt TEXT|--prompt-file PATH', '36', use_color)} # start with a first message",
|
|
301
302
|
f" {_style('cdx <name> --dir PATH --task TEXT --end-state TEXT', '36', use_color)} # start on a task, with what the go-ahead covers",
|
|
@@ -637,7 +638,8 @@ def main(argv, options=None):
|
|
|
637
638
|
if handler:
|
|
638
639
|
return handler(rest, ctx)
|
|
639
640
|
|
|
640
|
-
from .commands.launch import _parse_launch_args, _read_launch_prompt
|
|
641
|
+
from .commands.launch import _parse_launch_args, _pop_value_flag, _read_launch_prompt
|
|
642
|
+
project_path, rest = _pop_value_flag(rest, "--project", "Usage: cdx <name> [--project PATH]")
|
|
641
643
|
directory, launch_args, prompt, on_busy = _parse_launch_args(rest)
|
|
642
644
|
if all(arg in ("--json", "-r", "--resume") for arg in launch_args):
|
|
643
645
|
resume = "-r" in launch_args or "--resume" in launch_args
|
|
@@ -648,7 +650,7 @@ def main(argv, options=None):
|
|
|
648
650
|
if has_prompt and (ctx["service"]["get_session"](command) or {}).get("provider") == PROVIDER_OLLAMA:
|
|
649
651
|
raise CdxError("cdx <name>: --prompt and --prompt-file are not supported for Ollama sessions.")
|
|
650
652
|
return handle_launch(command, ctx, initial_prompt=_read_launch_prompt(prompt), resume=resume,
|
|
651
|
-
directory=directory, on_busy=on_busy)
|
|
653
|
+
directory=directory, on_busy=on_busy, project_path=project_path)
|
|
652
654
|
|
|
653
655
|
raise CdxError(f"Unknown command: {command}. Use cdx --help.")
|
|
654
656
|
|
package/src/cli_args.py
CHANGED
|
@@ -43,12 +43,12 @@ HISTORY_USAGE = "Usage: cdx history [name] [--limit N] [--summary] [--since 7d|t
|
|
|
43
43
|
STATS_USAGE = "Usage: cdx stats [name] [--since 7d|today|DATE] [--from DATE] [--to DATE] [--by session|project|model] [--json]"
|
|
44
44
|
STATS_GROUPS = ("session", "project", "model")
|
|
45
45
|
LAST_USAGE = "Usage: cdx last [--json]"
|
|
46
|
-
RESUME_USAGE = "Usage: cdx resume <name> [--conversation <id>] [--on-busy fail|ask] [--json]"
|
|
46
|
+
RESUME_USAGE = "Usage: cdx resume <name> [--project PATH] [--conversation <id>] [--on-busy fail|ask] [--json]"
|
|
47
47
|
CAN_RESUME_USAGE = "Usage: cdx can-resume <name> [--conversation <id>] [--json]"
|
|
48
48
|
SESSIONS_USAGE = "Usage: cdx sessions [--all] --json"
|
|
49
49
|
SELECT_USAGE = "Usage: cdx select --provider PROVIDER [--min-reasoning-effort minimal|low|medium|high|xhigh] [--min-power minimal|low|medium|high|xhigh] [--require-ready] [--refresh] --json"
|
|
50
50
|
NEXT_USAGE = "Usage: cdx next [--json] [--refresh]"
|
|
51
|
-
RUN_USAGE = "Usage: cdx run [session] (--cwd|--dir) PATH (--prompt-file PATH|--prompt TEXT|--prompt-file -) [--provider PROVIDER] [--model MODEL] [--kind assistant|code-review|retro] [--reasoning-effort minimal|low|medium|high|xhigh] [--power minimal|low|medium|high|xhigh] [--permission review|default|auto|full|workspace-write|read-only|danger-full-access] [--network-access] [--isolate-workspace] [--timeout-seconds N] [--budget USD] [--label TEXT] [--max-parallel N] [--continue-run RUN_ID] [--detach] [--failover] [--refresh] --json"
|
|
51
|
+
RUN_USAGE = "Usage: cdx run [session] (--cwd|--dir) PATH [--project PATH] (--prompt-file PATH|--prompt TEXT|--prompt-file -) [--provider PROVIDER] [--model MODEL] [--kind assistant|code-review|retro] [--reasoning-effort minimal|low|medium|high|xhigh] [--power minimal|low|medium|high|xhigh] [--permission review|default|auto|full|workspace-write|read-only|danger-full-access] [--network-access] [--isolate-workspace] [--timeout-seconds N] [--budget USD] [--label TEXT] [--max-parallel N] [--continue-run RUN_ID] [--detach] [--failover] [--refresh] --json"
|
|
52
52
|
RUN_JSON_REQUIRED = "cdx run: --json is required."
|
|
53
53
|
RUN_TARGET_REQUIRED = "cdx run: specify a session name or --provider PROVIDER."
|
|
54
54
|
RUN_SESSION_PROVIDER_CONFLICT = "cdx run: cannot specify both a session name and --provider."
|
|
@@ -552,6 +552,7 @@ def cdx_schema():
|
|
|
552
552
|
"provider_failed",
|
|
553
553
|
"provider_timeout",
|
|
554
554
|
"budget_exceeded",
|
|
555
|
+
"budget_unverifiable",
|
|
555
556
|
"run_cancelled",
|
|
556
557
|
"too_many_runs",
|
|
557
558
|
"failover_exhausted",
|
|
@@ -672,6 +673,7 @@ def _parse_max_parallel(value):
|
|
|
672
673
|
|
|
673
674
|
def _parse_run_args(args):
|
|
674
675
|
parsed = _parse_run_flag_args(args, {
|
|
676
|
+
"--project": {"key": "project", "type": "str", "default": None},
|
|
675
677
|
"--cwd": {"key": "cwd", "type": "str", "default": None},
|
|
676
678
|
"--dir": {"key": "cwd", "type": "str", "default": None},
|
|
677
679
|
"--prompt-file": {"key": "prompt_file", "type": "str", "default": None},
|
|
@@ -735,6 +737,7 @@ def _parse_run_args(args):
|
|
|
735
737
|
"name": name,
|
|
736
738
|
"provider": parsed["provider"],
|
|
737
739
|
"cwd": parsed["cwd"],
|
|
740
|
+
"project": parsed["project"],
|
|
738
741
|
"prompt_file": parsed["prompt_file"],
|
|
739
742
|
"prompt": parsed["prompt"],
|
|
740
743
|
"continue_run": parsed["continue_run"],
|
package/src/commands/launch.py
CHANGED
|
@@ -36,7 +36,7 @@ from ..provider_runtime import (
|
|
|
36
36
|
from ..run_command import PROMPT_STDIN, read_run_prompt
|
|
37
37
|
from ..run_usage import estimate_cost, format_cache_ratio
|
|
38
38
|
|
|
39
|
-
LAUNCH_USAGE = "Usage: cdx <name> [--dir PATH] [--prompt TEXT|--prompt-file PATH|--task TEXT [--end-state TEXT]] [--resume] [--on-busy fail|ask] [--json]"
|
|
39
|
+
LAUNCH_USAGE = "Usage: cdx <name> [--dir PATH] [--project PATH] [--prompt TEXT|--prompt-file PATH|--task TEXT [--end-state TEXT]] [--resume] [--on-busy fail|ask] [--json]"
|
|
40
40
|
|
|
41
41
|
#: The first message of a session started with --task. The end state is what the
|
|
42
42
|
#: user's go-ahead covers: a bare "show me first" made sessions stop after a commit
|
|
@@ -206,6 +206,7 @@ def handle_can_resume(rest, ctx):
|
|
|
206
206
|
return 0
|
|
207
207
|
|
|
208
208
|
def handle_resume(rest, ctx):
|
|
209
|
+
project_path, rest = _pop_value_flag(rest, "--project", RESUME_USAGE)
|
|
209
210
|
conversation_id, rest = _pop_value_flag(rest, "--conversation", RESUME_USAGE)
|
|
210
211
|
directory, rest, prompt, on_busy = _parse_launch_args(rest)
|
|
211
212
|
if prompt["prompt"] is not None or prompt["prompt_file"] is not None:
|
|
@@ -214,7 +215,7 @@ def handle_resume(rest, ctx):
|
|
|
214
215
|
if len(args) != 1:
|
|
215
216
|
raise CdxError(RESUME_USAGE)
|
|
216
217
|
return handle_launch(args[0], ctx, resume=True, force_json=json_flag, directory=directory,
|
|
217
|
-
conversation_id=conversation_id, on_busy=on_busy)
|
|
218
|
+
conversation_id=conversation_id, on_busy=on_busy, project_path=project_path)
|
|
218
219
|
|
|
219
220
|
|
|
220
221
|
def _notification_provisioning_enabled(session, ctx, json_flag):
|
|
@@ -258,7 +259,7 @@ def _link_shared_memory(session, ctx, cwd, json_flag):
|
|
|
258
259
|
|
|
259
260
|
|
|
260
261
|
def handle_launch(command, ctx, initial_prompt=None, resume=False, force_json=None, directory=None,
|
|
261
|
-
conversation_id=None, on_busy="ask"):
|
|
262
|
+
conversation_id=None, on_busy="ask", project_path=None):
|
|
262
263
|
json_flag = "--json" in ctx.get("raw_args", ctx["options"].get("raw_args", []))
|
|
263
264
|
if force_json is not None:
|
|
264
265
|
json_flag = force_json
|
|
@@ -278,7 +279,10 @@ def handle_launch(command, ctx, initial_prompt=None, resume=False, force_json=No
|
|
|
278
279
|
raise CdxError(
|
|
279
280
|
f"Provider {session['provider']} does not support native resume through cdx."
|
|
280
281
|
)
|
|
282
|
+
from ..project_identity import resolve_project
|
|
283
|
+
|
|
281
284
|
cwd = _launch_directory(session, ctx, directory, json_flag)
|
|
285
|
+
project = resolve_project(cwd, project_path)
|
|
282
286
|
session = ctx["service"]["launch_session"](command, resume=resume)
|
|
283
287
|
if conversation_id:
|
|
284
288
|
session = with_requested_conversation(session, conversation_id)
|
|
@@ -330,7 +334,7 @@ def handle_launch(command, ctx, initial_prompt=None, resume=False, force_json=No
|
|
|
330
334
|
def runtime_lifecycle(event, info):
|
|
331
335
|
nonlocal runtime_run_id
|
|
332
336
|
if event == "started":
|
|
333
|
-
runtime = ctx["service"]["start_session_runtime"](session["name"], {**info, "cwd": cwd})
|
|
337
|
+
runtime = ctx["service"]["start_session_runtime"](session["name"], {**info, "cwd": cwd, **project})
|
|
334
338
|
runtime_run_id = runtime.get("runId")
|
|
335
339
|
elif event == "finished" and runtime_run_id:
|
|
336
340
|
ctx["service"]["finish_session_runtime"](
|
|
@@ -382,6 +386,8 @@ def handle_launch(command, ctx, initial_prompt=None, resume=False, force_json=No
|
|
|
382
386
|
"error": str(error),
|
|
383
387
|
"exit_code": error.exit_code,
|
|
384
388
|
**run_info,
|
|
389
|
+
**project,
|
|
390
|
+
"run_id": runtime_run_id,
|
|
385
391
|
})
|
|
386
392
|
raise
|
|
387
393
|
success_run_info = _attach_interactive_usage(
|
|
@@ -395,13 +401,15 @@ def handle_launch(command, ctx, initial_prompt=None, resume=False, force_json=No
|
|
|
395
401
|
"cwd": cwd,
|
|
396
402
|
"exit_code": 0,
|
|
397
403
|
**success_run_info,
|
|
404
|
+
**project,
|
|
405
|
+
"run_id": runtime_run_id,
|
|
398
406
|
})
|
|
399
407
|
if not json_flag:
|
|
400
408
|
ctx["out"](f"{_goodbye_line(session, success_run_info, ctx['use_color'])}\n")
|
|
401
409
|
if retro.start(session, cwd, success_run_info.get("provider_transcript_path"), env=ctx.get("env")) and not json_flag:
|
|
402
410
|
ctx["out"](f"{_dim('Session retro started; read it later with `cdx retro`', ctx['use_color'])}\n")
|
|
403
411
|
if json_flag:
|
|
404
|
-
extra = {"session": ctx["service"]["get_session"](session["name"]), "cwd": cwd}
|
|
412
|
+
extra = {"session": ctx["service"]["get_session"](session["name"]), "cwd": cwd, **project, "run_id": runtime_run_id}
|
|
405
413
|
if capability:
|
|
406
414
|
extra["resume"] = capability
|
|
407
415
|
_write_json(ctx, _json_success("resume" if resume else "launch", message, warnings=warnings, **extra))
|