cdx-manager 0.9.2 → 0.9.4
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 +9 -5
- package/changelogs/CHANGELOGS_0_9_3.md +35 -0
- package/changelogs/CHANGELOGS_0_9_4.md +43 -0
- package/checksums/release-archives.json +8 -0
- package/package.json +1 -1
- package/pyproject.toml +1 -1
- package/src/cli.py +17 -7
- package/src/cli_commands.py +99 -15
- package/src/health.py +40 -1
- package/src/provider_runtime.py +169 -7
- package/src/run_command.py +1 -1
- package/src/run_usage.py +1 -0
- package/src/session_service.py +17 -11
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# CDX Manager
|
|
2
2
|
|
|
3
|
-
[](LICENSE) ](LICENSE)  
|
|
4
4
|
|
|
5
5
|
**Run multiple Codex, Claude, Antigravity, and Ollama sessions from one terminal. Switch between accounts instantly.**
|
|
6
6
|
|
|
@@ -301,7 +301,7 @@ cdx power all default
|
|
|
301
301
|
cdx model provider:ollama default
|
|
302
302
|
```
|
|
303
303
|
|
|
304
|
-
`--model` maps to Codex `--model`, Claude `--model`, and Ollama `ollama run <model>`. `--power` maps to Codex `model_reasoning_effort` and Claude `--effort`. `--permission` maps to provider-native permission flags. `--fast on`
|
|
304
|
+
`--model` maps to Codex `--model`, Claude `--model`, and Ollama `ollama run <model>`. `--power` maps to Codex `model_reasoning_effort` and Claude `--effort`; supported values are `minimal`, `low`, `medium`, `high`, and `xhigh`. `--permission` maps to provider-native permission flags. Codex Fast mode is separate from reasoning effort: `--fast on` opts a Codex session into the Codex Fast service tier, while new sessions, `--fast off`, and default launches force the non-Fast `flex` tier. Use `--power low` when you want low reasoning effort without enabling Codex Fast credits. Existing legacy sessions that stored `fast=on` before this split continue to behave as low effort unless the user explicitly sets `--fast on` again. `--priority` is a 0..100 selector preference used as a tie-breaker after readiness and availability. `--rtk on` injects a launch instruction that encourages assistants to use RTK (`rtk <command>`) for noisy terminal commands when RTK is available, while keeping raw commands for exact output. Logics guidance is auto-enabled when `logics-manager` is available; use `--logics off` to disable that guidance for a session, or `--logics on` to pin it explicitly.
|
|
305
305
|
|
|
306
306
|
### Launch History
|
|
307
307
|
|
|
@@ -329,6 +329,7 @@ cdx history --summary --from 2026-05-01 --to 2026-05-28
|
|
|
329
329
|
| `cdx --json` | List all sessions as a machine-readable JSON payload |
|
|
330
330
|
| `cdx <name>` | Launch a session (checks auth first) |
|
|
331
331
|
| `cdx <name> [--json]` | Launch a session; `--json` returns a structured success payload after the interactive run ends |
|
|
332
|
+
| `cdx <name> -r` / `cdx <name> --resume` | Resume the provider-native conversation for a session when supported |
|
|
332
333
|
| `cdx add [provider] <name> [--model MODEL] [--json]` | Register a new session (`provider`: `codex`, `claude`, `antigravity`, or `ollama`; Ollama requires `--model`) |
|
|
333
334
|
| `cdx cp <source> <dest> [--json]` | Copy a session into another session name, overwriting the destination if it exists |
|
|
334
335
|
| `cdx ren <source> <dest> [--json]` | Rename a session and move its auth data |
|
|
@@ -339,10 +340,12 @@ cdx history --summary --from 2026-05-01 --to 2026-05-28
|
|
|
339
340
|
| `cdx config <name> [--json]` | Show persistent launch settings for a session |
|
|
340
341
|
| `cdx configs [--json]` | Show persistent launch settings for all sessions in one table |
|
|
341
342
|
| `cdx power\|perm\|fast\|model <name\|all\|provider:PROVIDER\|a,b> <value\|default> [--json]` | Shortcut commands for setting or clearing one launch setting |
|
|
342
|
-
| `cdx set <name>\|--sessions all\|a,b\|--provider PROVIDER [--power low\|medium\|high\|xhigh
|
|
343
|
+
| `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] [--model MODEL] [--priority 0..100] [--json]` | Persist launch settings for one or more sessions |
|
|
343
344
|
| `cdx unset <name>\|--sessions all\|a,b\|--provider PROVIDER (--power\|--permission\|--fast\|--rtk\|--logics\|--model\|--priority\|--all) [--json]` | Remove persisted launch settings and fall back to provider defaults |
|
|
344
345
|
| `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 |
|
|
345
346
|
| `cdx last [--json]` | Launch the most recent existing session from launch history |
|
|
347
|
+
| `cdx resume <name> [--json]` | Resume the provider-native conversation for a session using the named command form |
|
|
348
|
+
| `cdx can-resume <name> [--json]` | Check whether a session supports native resume without launching the provider |
|
|
346
349
|
| `cdx context show\|path\|init\|edit\|clear\|set [text...] [--json]` | Manage the shared Markdown context for the current workspace |
|
|
347
350
|
| `cdx handoff <name> [--json]` | Install the current workspace context into a target session and launch it unless `--json` is used |
|
|
348
351
|
| `cdx handoff <source> <target> [--json]` | Build shared context from the source session's latest launch transcript, install it into the target session, and launch the target unless `--json` is used; supports cross-provider handoff |
|
|
@@ -358,8 +361,8 @@ cdx history --summary --from 2026-05-01 --to 2026-05-28
|
|
|
358
361
|
| `cdx notify <name> --at-reset [--poll seconds] [--once] [--schedule] [--refresh] [--json]` | Wait for a session reset time or schedule an OS wake-up notification when due |
|
|
359
362
|
| `cdx notify --next-ready [--poll seconds] [--once] [--schedule] [--refresh] [--json]` | Wait until the recommended session is usable, or schedule the next known reset notification |
|
|
360
363
|
| `cdx next [--json] [--refresh]` | Select the best next assistant using the same priority logic as `cdx status` |
|
|
361
|
-
| `cdx select --provider PROVIDER [--min-reasoning-effort low\|medium\|high] [--min-power low\|medium\|high] [--require-ready] [--refresh] --json` | Select a suitable session for headless automation |
|
|
362
|
-
| `cdx run [session] --cwd PATH (--prompt-file PATH\|--prompt TEXT) [--provider PROVIDER] [--model MODEL] [--reasoning-effort low\|medium\|high] [--power low\|medium\|high] [--permission MODE] [--timeout-seconds N] --json` | Run one headless task and return a stable JSON result |
|
|
364
|
+
| `cdx select --provider PROVIDER [--min-reasoning-effort minimal\|low\|medium\|high\|xhigh] [--min-power minimal\|low\|medium\|high\|xhigh] [--require-ready] [--refresh] --json` | Select a suitable session for headless automation |
|
|
365
|
+
| `cdx run [session] --cwd PATH (--prompt-file PATH\|--prompt TEXT) [--provider PROVIDER] [--model MODEL] [--reasoning-effort minimal\|low\|medium\|high\|xhigh] [--power minimal\|low\|medium\|high\|xhigh] [--permission MODE] [--timeout-seconds N] --json` | Run one headless task and return a stable JSON result |
|
|
363
366
|
| `cdx stats [name] [--since 7d\|today\|DATE] [--from DATE] [--to DATE] [--json]` | Aggregate launch counts, duration, and known headless token usage by session |
|
|
364
367
|
| `cdx status [--json] [--refresh]` | Show token usage table for all sessions; JSON returns a versioned payload with structured warnings |
|
|
365
368
|
| `cdx status --small [--refresh]` / `cdx status -s [--refresh]` | Show compact token usage table without provider, blocking quota, credits, and updated columns |
|
|
@@ -597,6 +600,7 @@ Session names are URL-encoded when used as directory or file names. CLI command
|
|
|
597
600
|
## Troubleshooting
|
|
598
601
|
|
|
599
602
|
- **`cdx <name>` fails with "not authenticated"** — run `cdx login <name>` first.
|
|
603
|
+
- **One of two Codex accounts keeps asking for login** — run `cdx doctor --json` and inspect each Codex session's `codex_auth_file` and `codex_live_auth` checks. Codex sessions use isolated `CODEX_HOME` directories, but newly created sessions seed from the current global `~/.codex/auth.json` when one exists. For two separate Codex accounts, create or repair each session by running `cdx login <name>` for that session; `cdx login` does not log out first, so use `cdx logout <name>` explicitly only when you want to clear that isolated profile.
|
|
600
604
|
- **`cdx` says no compatible Python 3 interpreter was found** — install Python 3 and make `py -3`, `python`, or `python3` available on PATH.
|
|
601
605
|
- **`cdx add` succeeds but the session does not appear** — check that `CDX_HOME` is consistent between calls; a mismatch creates two separate registries.
|
|
602
606
|
- **Status shows `n/a` for all fields** — the Codex app-server rate-limit probe may be unavailable, the session may not be authenticated, and no legacy transcript/history status has been captured yet.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# CDX Manager 0.9.3
|
|
2
|
+
|
|
3
|
+
## Highlights
|
|
4
|
+
|
|
5
|
+
- Codex Fast now stays opt-in and no longer silently lowers normal Codex session reasoning effort.
|
|
6
|
+
- Codex authentication diagnostics now detect when local credentials belong to a different account than the live CLI session.
|
|
7
|
+
- `cdx run` usage parsing now covers the Codex 0.141 JSONL `turn.completed` usage shape, including `reasoning_output_tokens`.
|
|
8
|
+
|
|
9
|
+
## Changes
|
|
10
|
+
|
|
11
|
+
### Codex Fast launch behavior
|
|
12
|
+
|
|
13
|
+
`cdx set <session> --fast on` now marks Fast as a service-tier choice instead of treating it as a reason to lower power to `low`. Normal Codex sessions continue to launch with their configured reasoning effort and an explicit flex service tier when Fast is off.
|
|
14
|
+
|
|
15
|
+
### Codex auth isolation diagnostics
|
|
16
|
+
|
|
17
|
+
`cdx doctor` now reads the email from local Codex tokens and compares it with the live `codex login status` account when both are available. If a managed session has local tokens for one account but the CLI is authenticated as another, the report surfaces a repairable `codex_auth_mismatch` issue instead of reporting a vague live-auth failure.
|
|
18
|
+
|
|
19
|
+
The auth probe also avoids trusting local credentials alone for launch and run readiness, which keeps multi-account sessions from accidentally using the wrong global Codex login after a CLI update or account switch.
|
|
20
|
+
|
|
21
|
+
### Codex 0.141 run usage parsing
|
|
22
|
+
|
|
23
|
+
Codex CLI 0.141 emits headless usage in JSONL `turn.completed` events. `cdx run` now maps the current `reasoning_output_tokens` field into the stable `reasoning_tokens` output and keeps computing total tokens from input plus output when the provider omits `total_tokens`.
|
|
24
|
+
|
|
25
|
+
## Validation
|
|
26
|
+
|
|
27
|
+
- `cdx doctor --json`
|
|
28
|
+
- `cdx status --json --refresh`
|
|
29
|
+
- `cdx run --provider codex --cwd /Users/alexandreagostini/Documents/cdx-manager --prompt 'Reply exactly: CDX_CODEX_OK' --timeout-seconds 90 --json`
|
|
30
|
+
- `cdx run --provider claude --cwd /Users/alexandreagostini/Documents/cdx-manager --prompt 'Reply exactly: CDX_CLAUDE_OK' --timeout-seconds 90 --json`
|
|
31
|
+
- `npm run lint`
|
|
32
|
+
- `npm test`
|
|
33
|
+
- `logics-manager lint --require-status`
|
|
34
|
+
- `logics-manager health`
|
|
35
|
+
- `git diff --check`
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# CDX Manager 0.9.4
|
|
2
|
+
|
|
3
|
+
## Highlights
|
|
4
|
+
|
|
5
|
+
- Added provider-native resume commands for named sessions.
|
|
6
|
+
- Added a non-launching capability check for resume support.
|
|
7
|
+
- Closed the Logics workflow for the resume command delivery.
|
|
8
|
+
|
|
9
|
+
## Changes
|
|
10
|
+
|
|
11
|
+
### Provider-native resume commands
|
|
12
|
+
|
|
13
|
+
`cdx` can now resume supported provider conversations without requiring users to remember provider-specific commands:
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
cdx main -r
|
|
17
|
+
cdx main --resume
|
|
18
|
+
cdx resume main
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Codex sessions resume with `codex resume --last --cd <cwd>` inside the named session's isolated `CODEX_HOME`. Claude sessions resume with `claude --continue --name <name>` inside the named session's isolated `HOME`.
|
|
22
|
+
|
|
23
|
+
Providers without a verified native resume mode, currently Antigravity and Ollama, return a clear unsupported result instead of falling back to a normal launch.
|
|
24
|
+
|
|
25
|
+
### Resume capability checks
|
|
26
|
+
|
|
27
|
+
`cdx can-resume <name>` reports whether a session can resume without launching an interactive provider. JSON mode exposes a provider-neutral payload with the session name, provider, resumable state, strategy, reason, and command preview.
|
|
28
|
+
|
|
29
|
+
### Workflow traceability
|
|
30
|
+
|
|
31
|
+
The Logics request, backlog item, task, and ADR for provider-native resume are complete, with validation evidence attached to the delivery task.
|
|
32
|
+
|
|
33
|
+
## Validation
|
|
34
|
+
|
|
35
|
+
- `python -m unittest discover -s test -p 'test_runtime_py.py' -k resume`
|
|
36
|
+
- `python -m unittest discover -s test -p 'test_cli_py.py' -k resume`
|
|
37
|
+
- `python -m unittest discover -s test -p 'test_cli_py.py' -k help`
|
|
38
|
+
- `python -m unittest discover -s test -p 'test_*_py.py' -k resume`
|
|
39
|
+
- `npm run lint`
|
|
40
|
+
- `npm test`
|
|
41
|
+
- `logics-manager lint --require-status`
|
|
42
|
+
- `logics-manager audit`
|
|
43
|
+
- `git diff --check`
|
|
@@ -72,6 +72,14 @@
|
|
|
72
72
|
"v0.9.1": {
|
|
73
73
|
"github_tarball_sha256": "8cbab620c823aeaf56e72c1a4d20e251a1c7142bfeeb3d516321d59c2c0a621d",
|
|
74
74
|
"github_zip_sha256": "253909ede6dca61937835bd4ee29145ddacaa08fbdfba1b39810da73ae2ccc84"
|
|
75
|
+
},
|
|
76
|
+
"v0.9.2": {
|
|
77
|
+
"github_tarball_sha256": "3e3ae4e4efc63a97dc6623ae8ddff36f04f4b7a0b531878a28c041298223d0b8",
|
|
78
|
+
"github_zip_sha256": "09acd2866770f4dc7f9aaba6aff8308766bb211dabddd7f28bdfde9ace427e78"
|
|
79
|
+
},
|
|
80
|
+
"v0.9.3": {
|
|
81
|
+
"github_tarball_sha256": "cb3cfd9134447d1049b510836014001fb6c38a89de588adc8cdbb1fe5deb4d6d",
|
|
82
|
+
"github_zip_sha256": "056eb2d1a3f0fa721a7a0e597cd0409e496618cfa35791a78d4e184397596754"
|
|
75
83
|
}
|
|
76
84
|
}
|
|
77
85
|
}
|
package/package.json
CHANGED
package/pyproject.toml
CHANGED
package/src/cli.py
CHANGED
|
@@ -11,6 +11,7 @@ from .cli_commands import (
|
|
|
11
11
|
handle_clean,
|
|
12
12
|
handle_config,
|
|
13
13
|
handle_configs,
|
|
14
|
+
handle_can_resume,
|
|
14
15
|
handle_context,
|
|
15
16
|
handle_copy,
|
|
16
17
|
handle_doctor,
|
|
@@ -30,6 +31,7 @@ from .cli_commands import (
|
|
|
30
31
|
handle_remove,
|
|
31
32
|
handle_repair,
|
|
32
33
|
handle_rename,
|
|
34
|
+
handle_resume,
|
|
33
35
|
handle_run,
|
|
34
36
|
handle_run_report,
|
|
35
37
|
handle_run_status,
|
|
@@ -64,7 +66,7 @@ from .status_view import (
|
|
|
64
66
|
)
|
|
65
67
|
from .update_check import check_for_update, check_logics_manager_for_update
|
|
66
68
|
|
|
67
|
-
VERSION = "0.9.
|
|
69
|
+
VERSION = "0.9.4"
|
|
68
70
|
|
|
69
71
|
|
|
70
72
|
# ---------------------------------------------------------------------------
|
|
@@ -82,8 +84,8 @@ def _print_help(use_color=False):
|
|
|
82
84
|
f" {_style('cdx status --small|-s [--refresh]', '36', use_color)}",
|
|
83
85
|
f" {_style('cdx status <name> [--json] [--refresh]', '36', use_color)}",
|
|
84
86
|
f" {_style('cdx next [--json] [--refresh]', '36', use_color)}",
|
|
85
|
-
f" {_style('cdx select --provider PROVIDER [--min-reasoning-effort low|medium|high] [--min-power low|medium|high] [--require-ready] [--refresh] --json', '36', use_color)}",
|
|
86
|
-
f" {_style('cdx run [session] --cwd PATH (--prompt-file PATH|--prompt TEXT) [--provider PROVIDER] [--model MODEL] [--reasoning-effort low|medium|high] [--power low|medium|high] [--permission review|default|auto|full|workspace-write|read-only|danger-full-access] [--timeout-seconds N] --json', '36', use_color)}",
|
|
87
|
+
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)}",
|
|
88
|
+
f" {_style('cdx run [session] --cwd PATH (--prompt-file PATH|--prompt TEXT) [--provider PROVIDER] [--model MODEL] [--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] --json', '36', use_color)}",
|
|
87
89
|
f" {_style('cdx runs [--limit N] --json', '36', use_color)}",
|
|
88
90
|
f" {_style('cdx run-status <run_id> --json', '36', use_color)}",
|
|
89
91
|
f" {_style('cdx run-report <run_id> --json', '36', use_color)}",
|
|
@@ -91,11 +93,13 @@ def _print_help(use_color=False):
|
|
|
91
93
|
f" {_style('cdx config <name> [--json]', '36', use_color)}",
|
|
92
94
|
f" {_style('cdx configs [--json]', '36', use_color)}",
|
|
93
95
|
f" {_style('cdx power|perm|fast|model <name|all|provider:PROVIDER|a,b> <value|default> [--json]', '36', use_color)}",
|
|
94
|
-
f" {_style('cdx set <name>|--sessions all|a,b|--provider PROVIDER [--power low|medium|high|xhigh
|
|
96
|
+
f" {_style('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] [--model MODEL] [--priority 0..100] [--json]', '36', use_color)}",
|
|
95
97
|
f" {_style('cdx unset <name>|--sessions all|a,b|--provider PROVIDER (--power|--permission|--fast|--rtk|--logics|--model|--priority|--all) [--json]', '36', use_color)}",
|
|
96
98
|
f" {_style('cdx history [name] [--limit N] [--summary] [--since 7d|today|DATE] [--from DATE] [--to DATE] [--json]', '36', use_color)}",
|
|
97
99
|
f" {_style('cdx stats [name] [--since 7d|today|DATE] [--from DATE] [--to DATE] [--json]', '36', use_color)}",
|
|
98
100
|
f" {_style('cdx last [--json]', '36', use_color)}",
|
|
101
|
+
f" {_style('cdx resume <name> [--json]', '36', use_color)}",
|
|
102
|
+
f" {_style('cdx can-resume <name> [--json]', '36', use_color)}",
|
|
99
103
|
f" {_style('cdx handoff <name> [--json]', '36', use_color)}",
|
|
100
104
|
f" {_style('cdx handoff <source> <target> [--json]', '36', use_color)}",
|
|
101
105
|
f" {_style('cdx add [provider] <name> [--model MODEL] [--json]', '36', use_color)}",
|
|
@@ -283,7 +287,7 @@ def main(argv, options=None):
|
|
|
283
287
|
"version": VERSION,
|
|
284
288
|
"cwd": options.get("cwd") or os.getcwd(),
|
|
285
289
|
"update_notices": _get_update_notices(service, env, options) if command not in (
|
|
286
|
-
"add", "cp", "ren", "rename", "mv", "rmv", "clean", "doctor", "repair", "view", "update", "ready", "notify", "next", "context", "config", "configs", "set", "unset", "power", "perm", "fast", "model", "history", "stats", "handoff", "login", "logout", "disable", "enable", "export", "import", "select", "run", "help", "version"
|
|
290
|
+
"add", "cp", "ren", "rename", "mv", "rmv", "clean", "doctor", "repair", "view", "update", "ready", "notify", "next", "context", "config", "configs", "set", "unset", "power", "perm", "fast", "model", "history", "stats", "resume", "can-resume", "handoff", "login", "logout", "disable", "enable", "export", "import", "select", "run", "help", "version"
|
|
287
291
|
) else None,
|
|
288
292
|
"use_color": use_color,
|
|
289
293
|
}
|
|
@@ -365,6 +369,12 @@ def main(argv, options=None):
|
|
|
365
369
|
if command == "last":
|
|
366
370
|
return handle_last(rest, ctx)
|
|
367
371
|
|
|
372
|
+
if command == "resume":
|
|
373
|
+
return handle_resume(rest, ctx)
|
|
374
|
+
|
|
375
|
+
if command == "can-resume":
|
|
376
|
+
return handle_can_resume(rest, ctx)
|
|
377
|
+
|
|
368
378
|
if command == "handoff":
|
|
369
379
|
return handle_handoff(rest, ctx)
|
|
370
380
|
|
|
@@ -400,8 +410,8 @@ def main(argv, options=None):
|
|
|
400
410
|
out(f"{_print_version()}\n")
|
|
401
411
|
return 0
|
|
402
412
|
|
|
403
|
-
if
|
|
404
|
-
return handle_launch(command, ctx)
|
|
413
|
+
if all(arg in ("--json", "-r", "--resume") for arg in rest):
|
|
414
|
+
return handle_launch(command, ctx, resume=("-r" in rest or "--resume" in rest))
|
|
405
415
|
|
|
406
416
|
raise CdxError(f"Unknown command: {command}. Use cdx --help.")
|
|
407
417
|
|
package/src/cli_commands.py
CHANGED
|
@@ -3,6 +3,7 @@ import getpass
|
|
|
3
3
|
import json
|
|
4
4
|
import os
|
|
5
5
|
import re
|
|
6
|
+
import shlex
|
|
6
7
|
import sys
|
|
7
8
|
import time
|
|
8
9
|
from datetime import datetime, timedelta
|
|
@@ -37,6 +38,7 @@ from .provider_runtime import (
|
|
|
37
38
|
_list_launch_transcript_paths,
|
|
38
39
|
_normalize_reasoning_effort,
|
|
39
40
|
_probe_provider_auth,
|
|
41
|
+
get_resume_capability,
|
|
40
42
|
_run_headless_provider_command,
|
|
41
43
|
_run_interactive_provider_command,
|
|
42
44
|
)
|
|
@@ -58,7 +60,7 @@ EXPORT_USAGE = "Usage: cdx export <file> [--include-auth] [--force] [--json] [--
|
|
|
58
60
|
IMPORT_USAGE = "Usage: cdx import <file> [--force|--merge] [--json] [--sessions name1,name2] [--passphrase-env VAR]"
|
|
59
61
|
CONTEXT_USAGE = "Usage: cdx context show|path|init|edit|clear|set [text...] [--json]"
|
|
60
62
|
HANDOFF_USAGE = "Usage: cdx handoff <name> [--json] | cdx handoff <source> <target> [--json]"
|
|
61
|
-
SET_USAGE = "Usage: cdx set <name>|--sessions all|a,b|--provider PROVIDER [--power low|medium|high|xhigh
|
|
63
|
+
SET_USAGE = "Usage: 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] [--model MODEL] [--priority 0..100] [--json]"
|
|
62
64
|
UNSET_USAGE = "Usage: cdx unset <name>|--sessions all|a,b|--provider PROVIDER (--power|--permission|--fast|--rtk|--logics|--model|--priority|--all) [--json]"
|
|
63
65
|
SETTING_ALIAS_USAGE = "Usage: cdx power|perm|fast|model <name|all|provider:PROVIDER|a,b> <value|default> [--json]"
|
|
64
66
|
CONFIG_USAGE = "Usage: cdx config <name> [--json]"
|
|
@@ -66,9 +68,11 @@ CONFIGS_USAGE = "Usage: cdx configs [--json]"
|
|
|
66
68
|
HISTORY_USAGE = "Usage: cdx history [name] [--limit N] [--summary] [--since 7d|today|DATE] [--from DATE] [--to DATE] [--json]"
|
|
67
69
|
STATS_USAGE = "Usage: cdx stats [name] [--since 7d|today|DATE] [--from DATE] [--to DATE] [--json]"
|
|
68
70
|
LAST_USAGE = "Usage: cdx last [--json]"
|
|
69
|
-
|
|
71
|
+
RESUME_USAGE = "Usage: cdx resume <name> [--json]"
|
|
72
|
+
CAN_RESUME_USAGE = "Usage: cdx can-resume <name> [--json]"
|
|
73
|
+
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"
|
|
70
74
|
NEXT_USAGE = "Usage: cdx next [--json] [--refresh]"
|
|
71
|
-
RUN_USAGE = "Usage: cdx run [session] --cwd PATH (--prompt-file PATH|--prompt TEXT) [--provider PROVIDER] [--model MODEL] [--kind assistant|code-review] [--reasoning-effort low|medium|high] [--power low|medium|high] [--permission review|default|auto|full|workspace-write|read-only|danger-full-access] [--timeout-seconds N] --json"
|
|
75
|
+
RUN_USAGE = "Usage: cdx run [session] --cwd PATH (--prompt-file PATH|--prompt TEXT) [--provider PROVIDER] [--model MODEL] [--kind assistant|code-review] [--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] --json"
|
|
72
76
|
RUNS_USAGE = "Usage: cdx runs [--limit N] --json"
|
|
73
77
|
RUN_STATUS_USAGE = "Usage: cdx run-status <run_id> --json"
|
|
74
78
|
RUN_REPORT_USAGE = "Usage: cdx run-report <run_id> --json"
|
|
@@ -1683,8 +1687,8 @@ def _resolve_bulk_launch_targets(parsed, service):
|
|
|
1683
1687
|
|
|
1684
1688
|
|
|
1685
1689
|
def _reasoning_rank(value):
|
|
1686
|
-
order = {"
|
|
1687
|
-
return order.get(str(value or "low").lower(),
|
|
1690
|
+
order = {"minimal": 0, "low": 1, "medium": 2, "high": 3, "xhigh": 4}
|
|
1691
|
+
return order.get(str(value or "low").lower(), -1)
|
|
1688
1692
|
|
|
1689
1693
|
|
|
1690
1694
|
def _session_reasoning_effort(session):
|
|
@@ -1693,7 +1697,7 @@ def _session_reasoning_effort(session):
|
|
|
1693
1697
|
launch.get("reasoning_effort")
|
|
1694
1698
|
or launch.get("reasoningEffort")
|
|
1695
1699
|
or launch.get("power")
|
|
1696
|
-
or ("low" if launch.get("fast") is True else None)
|
|
1700
|
+
or ("low" if launch.get("fast") is True and launch.get("fastMode") != "service_tier" else None)
|
|
1697
1701
|
or "low"
|
|
1698
1702
|
)
|
|
1699
1703
|
|
|
@@ -2290,6 +2294,7 @@ def handle_doctor(rest, ctx):
|
|
|
2290
2294
|
ctx["service"],
|
|
2291
2295
|
ctx["service"]["base_dir"],
|
|
2292
2296
|
env=ctx.get("env"),
|
|
2297
|
+
spawn_sync=ctx.get("spawn_sync"),
|
|
2293
2298
|
)
|
|
2294
2299
|
if json_flag:
|
|
2295
2300
|
_write_json(ctx, _json_success("doctor", "Collected health report", report=report))
|
|
@@ -2672,11 +2677,6 @@ def handle_login(rest, ctx):
|
|
|
2672
2677
|
session = ctx["service"]["get_session"](args[0])
|
|
2673
2678
|
if not session:
|
|
2674
2679
|
raise CdxError(f"Unknown session: {args[0]}")
|
|
2675
|
-
if session["provider"] == PROVIDER_CODEX:
|
|
2676
|
-
_run_interactive_provider_command(
|
|
2677
|
-
session, "logout", spawn=ctx.get("spawn"), env_override=ctx.get("env"),
|
|
2678
|
-
signal_emitter=ctx.get("signal_emitter")
|
|
2679
|
-
)
|
|
2680
2680
|
_run_interactive_provider_command(
|
|
2681
2681
|
session, "login", spawn=ctx.get("spawn"), env_override=ctx.get("env"),
|
|
2682
2682
|
signal_emitter=ctx.get("signal_emitter")
|
|
@@ -2687,6 +2687,7 @@ def handle_login(rest, ctx):
|
|
|
2687
2687
|
spawn_sync=ctx.get("spawn_sync"),
|
|
2688
2688
|
env_override=ctx.get("env"),
|
|
2689
2689
|
behavior="probe-only",
|
|
2690
|
+
trust_local_credentials=False,
|
|
2690
2691
|
)
|
|
2691
2692
|
if not auth_probe.get("authenticated") and session["provider"] == PROVIDER_CLAUDE:
|
|
2692
2693
|
_bootstrap_claude_setup_token(session, ctx)
|
|
@@ -2696,6 +2697,7 @@ def handle_login(rest, ctx):
|
|
|
2696
2697
|
spawn_sync=ctx.get("spawn_sync"),
|
|
2697
2698
|
env_override=ctx.get("env"),
|
|
2698
2699
|
behavior="probe-only",
|
|
2700
|
+
trust_local_credentials=False,
|
|
2699
2701
|
)
|
|
2700
2702
|
if not auth_probe.get("authenticated"):
|
|
2701
2703
|
raise CdxError(
|
|
@@ -2846,10 +2848,83 @@ def handle_update(rest, ctx):
|
|
|
2846
2848
|
return 0
|
|
2847
2849
|
|
|
2848
2850
|
|
|
2849
|
-
def
|
|
2851
|
+
def _resume_capability_for_session(session, ctx):
|
|
2852
|
+
capability = get_resume_capability(session, cwd=ctx.get("cwd") or os.getcwd())
|
|
2853
|
+
return {
|
|
2854
|
+
"session": session["name"],
|
|
2855
|
+
"provider": session["provider"],
|
|
2856
|
+
"resumable": bool(capability.get("resumable")),
|
|
2857
|
+
"strategy": capability.get("strategy"),
|
|
2858
|
+
"reason": capability.get("reason"),
|
|
2859
|
+
"command_preview": capability.get("command_preview") or [],
|
|
2860
|
+
}
|
|
2861
|
+
|
|
2862
|
+
|
|
2863
|
+
def _format_resume_capability(capability, use_color=False):
|
|
2864
|
+
name = capability["session"]
|
|
2865
|
+
provider = capability["provider"]
|
|
2866
|
+
if capability["resumable"]:
|
|
2867
|
+
preview = shlex.join(capability.get("command_preview") or [])
|
|
2868
|
+
detail = f"({provider}, {capability['strategy']})"
|
|
2869
|
+
return (
|
|
2870
|
+
f"{_success(f'{name} can resume', use_color)} "
|
|
2871
|
+
f"{_dim(detail, use_color)}\n"
|
|
2872
|
+
f"{_dim(preview, use_color)}"
|
|
2873
|
+
)
|
|
2874
|
+
reason = capability.get("reason") or "not_supported"
|
|
2875
|
+
return (
|
|
2876
|
+
f"{_warn(f'{name} cannot resume', use_color)} "
|
|
2877
|
+
f"{_dim(f'({provider}, {reason})', use_color)}"
|
|
2878
|
+
)
|
|
2879
|
+
|
|
2880
|
+
|
|
2881
|
+
def handle_can_resume(rest, ctx):
|
|
2882
|
+
json_flag, args = _parse_json_flag(rest)
|
|
2883
|
+
if len(args) != 1:
|
|
2884
|
+
raise CdxError(CAN_RESUME_USAGE)
|
|
2885
|
+
session = ctx["service"]["get_session"](args[0])
|
|
2886
|
+
if not session:
|
|
2887
|
+
raise CdxError(f"Unknown session: {args[0]}")
|
|
2888
|
+
if session.get("enabled", True) is False:
|
|
2889
|
+
capability = {
|
|
2890
|
+
"session": session["name"],
|
|
2891
|
+
"provider": session["provider"],
|
|
2892
|
+
"resumable": False,
|
|
2893
|
+
"strategy": "session_disabled",
|
|
2894
|
+
"reason": "session_disabled",
|
|
2895
|
+
"command_preview": [],
|
|
2896
|
+
}
|
|
2897
|
+
else:
|
|
2898
|
+
capability = _resume_capability_for_session(session, ctx)
|
|
2899
|
+
if json_flag:
|
|
2900
|
+
_write_json(ctx, {
|
|
2901
|
+
"schema_version": API_SCHEMA_VERSION,
|
|
2902
|
+
"ok": True,
|
|
2903
|
+
**capability,
|
|
2904
|
+
})
|
|
2905
|
+
return 0
|
|
2906
|
+
ctx["out"](f"{_format_resume_capability(capability, ctx['use_color'])}\n")
|
|
2907
|
+
return 0
|
|
2908
|
+
|
|
2909
|
+
|
|
2910
|
+
def handle_resume(rest, ctx):
|
|
2911
|
+
json_flag, args = _parse_json_flag(rest)
|
|
2912
|
+
if len(args) != 1:
|
|
2913
|
+
raise CdxError(RESUME_USAGE)
|
|
2914
|
+
return handle_launch(args[0], ctx, resume=True, force_json=json_flag)
|
|
2915
|
+
|
|
2916
|
+
|
|
2917
|
+
def handle_launch(command, ctx, initial_prompt=None, resume=False, force_json=None):
|
|
2850
2918
|
json_flag = "--json" in ctx.get("raw_args", ctx["options"].get("raw_args", []))
|
|
2919
|
+
if force_json is not None:
|
|
2920
|
+
json_flag = force_json
|
|
2851
2921
|
warnings = _update_notice_warnings(ctx)
|
|
2852
2922
|
session = ctx["service"]["launch_session"](command)
|
|
2923
|
+
capability = _resume_capability_for_session(session, ctx) if resume else None
|
|
2924
|
+
if capability and not capability["resumable"]:
|
|
2925
|
+
raise CdxError(
|
|
2926
|
+
f"Provider {session['provider']} does not support native resume through cdx."
|
|
2927
|
+
)
|
|
2853
2928
|
_ensure_session_authentication(
|
|
2854
2929
|
session,
|
|
2855
2930
|
ctx["service"],
|
|
@@ -2859,8 +2934,13 @@ def handle_launch(command, ctx, initial_prompt=None):
|
|
|
2859
2934
|
stdin_is_tty=ctx["stdin_is_tty"],
|
|
2860
2935
|
behavior="launch",
|
|
2861
2936
|
signal_emitter=ctx.get("signal_emitter"),
|
|
2937
|
+
trust_local_credentials=False,
|
|
2938
|
+
)
|
|
2939
|
+
message = (
|
|
2940
|
+
f"Resuming {session['provider']} session {session['name']}"
|
|
2941
|
+
if resume else
|
|
2942
|
+
f"Launching {session['provider']} session {session['name']}"
|
|
2862
2943
|
)
|
|
2863
|
-
message = f"Launching {session['provider']} session {session['name']}"
|
|
2864
2944
|
if not json_flag:
|
|
2865
2945
|
ctx["out"](f"{_info(message, ctx['use_color'])}\n")
|
|
2866
2946
|
_write_update_notice(ctx)
|
|
@@ -2881,7 +2961,7 @@ def handle_launch(command, ctx, initial_prompt=None):
|
|
|
2881
2961
|
|
|
2882
2962
|
try:
|
|
2883
2963
|
run_info = _run_interactive_provider_command(
|
|
2884
|
-
session, "launch", spawn=ctx.get("spawn"), cwd=cwd, env_override=ctx.get("env"),
|
|
2964
|
+
session, "resume" if resume else "launch", spawn=ctx.get("spawn"), cwd=cwd, env_override=ctx.get("env"),
|
|
2885
2965
|
signal_emitter=ctx.get("signal_emitter"), initial_prompt=initial_prompt,
|
|
2886
2966
|
lifecycle_callback=runtime_lifecycle,
|
|
2887
2967
|
)
|
|
@@ -2904,12 +2984,16 @@ def handle_launch(command, ctx, initial_prompt=None):
|
|
|
2904
2984
|
raise
|
|
2905
2985
|
ctx["service"]["record_launch_history"](session["name"], {
|
|
2906
2986
|
"status": "success",
|
|
2987
|
+
"action": "resume" if resume else "launch",
|
|
2907
2988
|
"cwd": cwd,
|
|
2908
2989
|
"exit_code": 0,
|
|
2909
2990
|
**run_info,
|
|
2910
2991
|
})
|
|
2911
2992
|
if json_flag:
|
|
2912
|
-
|
|
2993
|
+
extra = {"session": ctx["service"]["get_session"](session["name"])}
|
|
2994
|
+
if capability:
|
|
2995
|
+
extra["resume"] = capability
|
|
2996
|
+
_write_json(ctx, _json_success("resume" if resume else "launch", message, warnings=warnings, **extra))
|
|
2913
2997
|
return 0
|
|
2914
2998
|
|
|
2915
2999
|
|
package/src/health.py
CHANGED
|
@@ -6,6 +6,8 @@ import tempfile
|
|
|
6
6
|
from urllib.parse import quote, unquote
|
|
7
7
|
|
|
8
8
|
from .cli_render import _pad_table, _style
|
|
9
|
+
from .config import PROVIDER_CODEX
|
|
10
|
+
from .provider_runtime import codex_auth_diagnostic
|
|
9
11
|
|
|
10
12
|
|
|
11
13
|
def _encode(name):
|
|
@@ -30,7 +32,7 @@ def _issue(status, code, message, detail=None, repairable=False):
|
|
|
30
32
|
}
|
|
31
33
|
|
|
32
34
|
|
|
33
|
-
def collect_health_report(service, base_dir, env=None):
|
|
35
|
+
def collect_health_report(service, base_dir, env=None, spawn_sync=None):
|
|
34
36
|
env = env or os.environ
|
|
35
37
|
issues = []
|
|
36
38
|
|
|
@@ -79,11 +81,48 @@ def collect_health_report(service, base_dir, env=None):
|
|
|
79
81
|
state_path = _state_file_path(base_dir, name)
|
|
80
82
|
if not os.path.isfile(state_path):
|
|
81
83
|
issues.append(_issue("FAIL", "missing_state", f"session {name} state file is missing", state_path, True))
|
|
84
|
+
if session.get("provider") == PROVIDER_CODEX:
|
|
85
|
+
issues.extend(_codex_auth_issues(session, spawn_sync=spawn_sync, env=env))
|
|
82
86
|
|
|
83
87
|
issues.extend(_collect_profile_issues(base_dir, session_names))
|
|
84
88
|
return {"base_dir": base_dir, "issues": issues, "summary": summarize_health(issues)}
|
|
85
89
|
|
|
86
90
|
|
|
91
|
+
def _codex_auth_issues(session, spawn_sync=None, env=None):
|
|
92
|
+
name = session["name"]
|
|
93
|
+
diag = codex_auth_diagnostic(session, spawn_sync=spawn_sync, env_override=env)
|
|
94
|
+
identity = diag.get("account_email") or "unknown account"
|
|
95
|
+
issues = [
|
|
96
|
+
_issue(
|
|
97
|
+
"OK" if diag.get("auth_json_exists") else "WARN",
|
|
98
|
+
"codex_auth_file",
|
|
99
|
+
f"session {name} Codex auth file {'found' if diag.get('auth_json_exists') else 'missing'} ({identity})",
|
|
100
|
+
{
|
|
101
|
+
"session": name,
|
|
102
|
+
"auth_home": diag.get("auth_home"),
|
|
103
|
+
"auth_json_exists": diag.get("auth_json_exists"),
|
|
104
|
+
"local_tokens_present": diag.get("local_tokens_present"),
|
|
105
|
+
"account_email": diag.get("account_email"),
|
|
106
|
+
},
|
|
107
|
+
)
|
|
108
|
+
]
|
|
109
|
+
live_status = diag.get("live_status")
|
|
110
|
+
live_ok = live_status == "authenticated"
|
|
111
|
+
live_warn = live_status in ("logged_out", "error")
|
|
112
|
+
issues.append(_issue(
|
|
113
|
+
"OK" if live_ok else "WARN" if live_warn else "WARN",
|
|
114
|
+
"codex_live_auth",
|
|
115
|
+
f"session {name} live Codex auth status: {live_status}",
|
|
116
|
+
{
|
|
117
|
+
"session": name,
|
|
118
|
+
"auth_home": diag.get("auth_home"),
|
|
119
|
+
"live_status": live_status,
|
|
120
|
+
"live_error": diag.get("live_error"),
|
|
121
|
+
},
|
|
122
|
+
))
|
|
123
|
+
return issues
|
|
124
|
+
|
|
125
|
+
|
|
87
126
|
def _check_cdx_home(base_dir):
|
|
88
127
|
try:
|
|
89
128
|
os.makedirs(base_dir, exist_ok=True)
|
package/src/provider_runtime.py
CHANGED
|
@@ -7,6 +7,7 @@ import shutil
|
|
|
7
7
|
import subprocess
|
|
8
8
|
import sys
|
|
9
9
|
import uuid
|
|
10
|
+
import base64
|
|
10
11
|
from datetime import datetime, timezone
|
|
11
12
|
|
|
12
13
|
from .config import PROVIDER_ANTIGRAVITY, PROVIDER_CLAUDE, PROVIDER_CODEX, PROVIDER_OLLAMA
|
|
@@ -14,7 +15,7 @@ from .errors import CdxError
|
|
|
14
15
|
|
|
15
16
|
|
|
16
17
|
LOG_ROTATE_BYTES = 10 * 1024 * 1024 # 10 MB
|
|
17
|
-
REASONING_EFFORT_VALUES = {"low", "medium", "high"}
|
|
18
|
+
REASONING_EFFORT_VALUES = {"minimal", "low", "medium", "high", "xhigh"}
|
|
18
19
|
RTK_PROMPT = (
|
|
19
20
|
"When running noisy shell commands, prefer RTK wrappers (`rtk <command>`) if `rtk` is available. "
|
|
20
21
|
"Use raw commands when exact, unfiltered output is required."
|
|
@@ -148,6 +149,64 @@ def _has_local_codex_auth(auth_home):
|
|
|
148
149
|
)
|
|
149
150
|
|
|
150
151
|
|
|
152
|
+
def _decode_jwt_claims(token):
|
|
153
|
+
if not token or "." not in str(token):
|
|
154
|
+
return {}
|
|
155
|
+
parts = str(token).split(".")
|
|
156
|
+
if len(parts) < 2:
|
|
157
|
+
return {}
|
|
158
|
+
padding = "=" * (-len(parts[1]) % 4)
|
|
159
|
+
try:
|
|
160
|
+
decoded = base64.urlsafe_b64decode(parts[1] + padding)
|
|
161
|
+
return json.loads(decoded.decode("utf-8"))
|
|
162
|
+
except (ValueError, json.JSONDecodeError, UnicodeDecodeError):
|
|
163
|
+
return {}
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def _read_codex_account_email(auth_home):
|
|
167
|
+
try:
|
|
168
|
+
with open(os.path.join(auth_home, "auth.json"), "r", encoding="utf-8") as handle:
|
|
169
|
+
auth = json.load(handle)
|
|
170
|
+
except (FileNotFoundError, OSError, json.JSONDecodeError):
|
|
171
|
+
return None
|
|
172
|
+
tokens = auth.get("tokens") if isinstance(auth, dict) else {}
|
|
173
|
+
if not isinstance(tokens, dict):
|
|
174
|
+
return None
|
|
175
|
+
for token_name in ("id_token", "access_token"):
|
|
176
|
+
claims = _decode_jwt_claims(tokens.get(token_name))
|
|
177
|
+
email = claims.get("email")
|
|
178
|
+
if not email and token_name == "access_token":
|
|
179
|
+
profile = claims.get("https://api.openai.com/profile") or {}
|
|
180
|
+
email = profile.get("email") if isinstance(profile, dict) else None
|
|
181
|
+
if email:
|
|
182
|
+
return str(email).strip().lower()
|
|
183
|
+
return None
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def codex_auth_diagnostic(session, spawn_sync=None, env_override=None):
|
|
187
|
+
auth_home = _get_auth_home(session)
|
|
188
|
+
auth_path = os.path.join(auth_home, "auth.json")
|
|
189
|
+
result = {
|
|
190
|
+
"auth_home": auth_home,
|
|
191
|
+
"auth_json_exists": os.path.isfile(auth_path),
|
|
192
|
+
"local_tokens_present": _has_local_codex_auth(auth_home),
|
|
193
|
+
"account_email": _read_codex_account_email(auth_home),
|
|
194
|
+
"live_status": "unknown",
|
|
195
|
+
"live_error": None,
|
|
196
|
+
}
|
|
197
|
+
try:
|
|
198
|
+
result["live_status"] = "authenticated" if _probe_provider_auth(
|
|
199
|
+
session,
|
|
200
|
+
spawn_sync=spawn_sync,
|
|
201
|
+
env_override=env_override,
|
|
202
|
+
trust_local_credentials=False,
|
|
203
|
+
) else "logged_out"
|
|
204
|
+
except CdxError as error:
|
|
205
|
+
result["live_status"] = "error"
|
|
206
|
+
result["live_error"] = str(error)
|
|
207
|
+
return result
|
|
208
|
+
|
|
209
|
+
|
|
151
210
|
def _read_claude_account_email(auth_home):
|
|
152
211
|
config_path = os.path.join(auth_home, ".claude.json")
|
|
153
212
|
try:
|
|
@@ -196,11 +255,25 @@ def _launch_power(session):
|
|
|
196
255
|
power = launch.get("reasoning_effort") or launch.get("reasoningEffort") or launch.get("power")
|
|
197
256
|
if power:
|
|
198
257
|
return power
|
|
199
|
-
if launch
|
|
258
|
+
if _legacy_fast_low_effort(launch):
|
|
200
259
|
return "low"
|
|
201
260
|
return None
|
|
202
261
|
|
|
203
262
|
|
|
263
|
+
def _legacy_fast_low_effort(launch):
|
|
264
|
+
return launch.get("fast") is True and launch.get("fastMode") != "service_tier"
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
def _codex_fast_enabled(launch):
|
|
268
|
+
return launch.get("fast") is True and launch.get("fastMode") == "service_tier"
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
def _codex_fast_config_args(launch):
|
|
272
|
+
if _codex_fast_enabled(launch):
|
|
273
|
+
return ["-c", 'service_tier="fast"', "-c", "features.fast_mode=true"]
|
|
274
|
+
return ["-c", 'service_tier="flex"', "-c", "features.fast_mode=false"]
|
|
275
|
+
|
|
276
|
+
|
|
204
277
|
def _normalize_reasoning_effort(reasoning_effort=None, power=None, usage="Unsupported reasoning effort."):
|
|
205
278
|
effort = str(reasoning_effort).strip().lower() if reasoning_effort is not None else None
|
|
206
279
|
alias = str(power).strip().lower() if power is not None else None
|
|
@@ -269,6 +342,7 @@ def _launch_config_args(session):
|
|
|
269
342
|
return args
|
|
270
343
|
if power:
|
|
271
344
|
args += ["-c", f'model_reasoning_effort="{power}"']
|
|
345
|
+
args += _codex_fast_config_args(launch)
|
|
272
346
|
if permission:
|
|
273
347
|
args += LAUNCH_PERMISSION_ARGS[PROVIDER_CODEX].get(permission, [])
|
|
274
348
|
return args
|
|
@@ -437,6 +511,90 @@ def _build_launch_spec(session, cwd=None, env_override=None, initial_prompt=None
|
|
|
437
511
|
}, capture_transcript=capture_transcript, env=env)
|
|
438
512
|
|
|
439
513
|
|
|
514
|
+
def _redacted_resume_command_preview(session, cwd=None):
|
|
515
|
+
capability = get_resume_capability(session, cwd=cwd)
|
|
516
|
+
return capability.get("command_preview")
|
|
517
|
+
|
|
518
|
+
|
|
519
|
+
def get_resume_capability(session, cwd=None):
|
|
520
|
+
provider = session.get("provider")
|
|
521
|
+
cwd = cwd or os.getcwd()
|
|
522
|
+
if provider == PROVIDER_CODEX:
|
|
523
|
+
return {
|
|
524
|
+
"resumable": True,
|
|
525
|
+
"provider": provider,
|
|
526
|
+
"strategy": "provider_last",
|
|
527
|
+
"reason": "supported",
|
|
528
|
+
"command_preview": ["codex", "resume", "--last", "--cd", cwd],
|
|
529
|
+
}
|
|
530
|
+
if provider == PROVIDER_CLAUDE:
|
|
531
|
+
return {
|
|
532
|
+
"resumable": True,
|
|
533
|
+
"provider": provider,
|
|
534
|
+
"strategy": "provider_continue",
|
|
535
|
+
"reason": "supported",
|
|
536
|
+
"command_preview": ["claude", "--continue"],
|
|
537
|
+
}
|
|
538
|
+
return {
|
|
539
|
+
"resumable": False,
|
|
540
|
+
"provider": provider,
|
|
541
|
+
"strategy": "not_supported",
|
|
542
|
+
"reason": "not_supported",
|
|
543
|
+
"command_preview": [],
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
|
|
547
|
+
def _build_resume_spec(session, cwd=None, env_override=None, capture_transcript=True):
|
|
548
|
+
cwd = cwd or os.getcwd()
|
|
549
|
+
env_override = env_override or {}
|
|
550
|
+
env = {**os.environ, **env_override}
|
|
551
|
+
capability = get_resume_capability(session, cwd=cwd)
|
|
552
|
+
if not capability["resumable"]:
|
|
553
|
+
raise CdxError(f"Provider {session.get('provider')} does not support native resume through cdx.")
|
|
554
|
+
|
|
555
|
+
resume_prompt = _with_launch_preferences(session, env=env)
|
|
556
|
+
_validate_initial_prompt(resume_prompt)
|
|
557
|
+
|
|
558
|
+
if session["provider"] == PROVIDER_CLAUDE:
|
|
559
|
+
launch = session.get("launch") or {}
|
|
560
|
+
args = ["--continue", "--name", session["name"]]
|
|
561
|
+
if launch.get("model"):
|
|
562
|
+
args += ["--model", _claude_cli_model(launch["model"])]
|
|
563
|
+
args += _launch_config_args(session)
|
|
564
|
+
if resume_prompt:
|
|
565
|
+
args.append(resume_prompt)
|
|
566
|
+
auth_home = _get_auth_home(session)
|
|
567
|
+
claude_env = _claude_env(env, auth_home)
|
|
568
|
+
oauth_token = _read_claude_launch_oauth_token(auth_home)
|
|
569
|
+
if oauth_token:
|
|
570
|
+
claude_env["CLAUDE_CODE_OAUTH_TOKEN"] = oauth_token
|
|
571
|
+
return _wrap_launch_with_transcript(session, {
|
|
572
|
+
"command": "claude",
|
|
573
|
+
"args": args,
|
|
574
|
+
"options": {
|
|
575
|
+
"cwd": cwd,
|
|
576
|
+
"env": claude_env,
|
|
577
|
+
},
|
|
578
|
+
"label": "claude resume",
|
|
579
|
+
}, capture_transcript=capture_transcript, env=env)
|
|
580
|
+
|
|
581
|
+
launch = session.get("launch") or {}
|
|
582
|
+
args = ["resume", "--last", "--cd", cwd]
|
|
583
|
+
if launch.get("model"):
|
|
584
|
+
args += ["--model", launch["model"]]
|
|
585
|
+
args += _launch_config_args(session)
|
|
586
|
+
if resume_prompt:
|
|
587
|
+
args.append(resume_prompt)
|
|
588
|
+
return _wrap_launch_with_transcript(session, {
|
|
589
|
+
"command": "codex",
|
|
590
|
+
"args": args,
|
|
591
|
+
"options": {
|
|
592
|
+
"env": {**env, "CODEX_HOME": _get_auth_home(session)},
|
|
593
|
+
},
|
|
594
|
+
"label": "codex resume",
|
|
595
|
+
}, capture_transcript=capture_transcript, env=env)
|
|
596
|
+
|
|
597
|
+
|
|
440
598
|
def _validate_initial_prompt(initial_prompt):
|
|
441
599
|
if initial_prompt is not None:
|
|
442
600
|
if not isinstance(initial_prompt, str):
|
|
@@ -482,6 +640,7 @@ def _build_headless_launch_spec(session, cwd=None, env_override=None, initial_pr
|
|
|
482
640
|
args += ["--model", model]
|
|
483
641
|
if power:
|
|
484
642
|
args += ["-c", f'model_reasoning_effort="{power}"']
|
|
643
|
+
args += _codex_fast_config_args(launch)
|
|
485
644
|
if permission:
|
|
486
645
|
args += HEADLESS_CODEX_PERMISSION_ARGS.get(permission, [])
|
|
487
646
|
if initial_prompt:
|
|
@@ -771,11 +930,14 @@ def _run_interactive_provider_command(session, action, spawn=None, cwd=None,
|
|
|
771
930
|
env_override=None, signal_emitter=None,
|
|
772
931
|
initial_prompt=None, lifecycle_callback=None):
|
|
773
932
|
spawn = spawn or subprocess.Popen
|
|
774
|
-
|
|
775
|
-
_build_launch_spec(session, cwd=cwd, env_override=env_override, initial_prompt=initial_prompt)
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
933
|
+
if action == "launch":
|
|
934
|
+
spec = _build_launch_spec(session, cwd=cwd, env_override=env_override, initial_prompt=initial_prompt)
|
|
935
|
+
elif action == "resume":
|
|
936
|
+
if initial_prompt is not None:
|
|
937
|
+
raise CdxError("initial_prompt is not supported for resume.")
|
|
938
|
+
spec = _build_resume_spec(session, cwd=cwd, env_override=env_override)
|
|
939
|
+
else:
|
|
940
|
+
spec = _build_auth_action_spec(session, action, cwd=cwd, env_override=env_override)
|
|
779
941
|
def start_child(current_spec):
|
|
780
942
|
command = current_spec["command"]
|
|
781
943
|
if spawn is subprocess.Popen:
|
package/src/run_command.py
CHANGED
|
@@ -43,7 +43,7 @@ def run_payload_reasoning_effort(parsed, session):
|
|
|
43
43
|
or launch.get("reasoning_effort")
|
|
44
44
|
or launch.get("reasoningEffort")
|
|
45
45
|
or launch.get("power")
|
|
46
|
-
or ("low" if launch.get("fast") is True else None)
|
|
46
|
+
or ("low" if launch.get("fast") is True and launch.get("fastMode") != "service_tier" else None)
|
|
47
47
|
)
|
|
48
48
|
|
|
49
49
|
|
package/src/run_usage.py
CHANGED
|
@@ -91,6 +91,7 @@ def _usage_from_dict(value):
|
|
|
91
91
|
output_tokens = _int_value(usage.get("output_tokens"), usage.get("completion_tokens"))
|
|
92
92
|
reasoning_tokens = _int_value(
|
|
93
93
|
usage.get("reasoning_tokens"),
|
|
94
|
+
usage.get("reasoning_output_tokens"),
|
|
94
95
|
_nested_int(usage, "output_tokens_details", "reasoning_tokens"),
|
|
95
96
|
_nested_int(usage, "completion_tokens_details", "reasoning_tokens"),
|
|
96
97
|
)
|
package/src/session_service.py
CHANGED
|
@@ -21,6 +21,7 @@ ALLOWED_PROVIDERS = set(PROVIDERS)
|
|
|
21
21
|
MAX_SESSION_NAME_LENGTH = 64
|
|
22
22
|
RESERVED_SESSION_NAMES = {
|
|
23
23
|
"add",
|
|
24
|
+
"can-resume",
|
|
24
25
|
"clean",
|
|
25
26
|
"context",
|
|
26
27
|
"configs",
|
|
@@ -45,6 +46,7 @@ RESERVED_SESSION_NAMES = {
|
|
|
45
46
|
"power",
|
|
46
47
|
"ready",
|
|
47
48
|
"repair",
|
|
49
|
+
"resume",
|
|
48
50
|
"ren",
|
|
49
51
|
"rename",
|
|
50
52
|
"rmv",
|
|
@@ -66,8 +68,8 @@ RESERVED_SESSION_NAMES = {
|
|
|
66
68
|
STATUS_CACHE_TTL_SECONDS = 60
|
|
67
69
|
CLAUDE_STATUS_CACHE_TTL_SECONDS = 10 * 60
|
|
68
70
|
MAX_STATUS_WORKERS = 8
|
|
69
|
-
LAUNCH_POWER_VALUES = {"low", "medium", "high", "xhigh"
|
|
70
|
-
LAUNCH_REASONING_EFFORT_VALUES = {"low", "medium", "high"}
|
|
71
|
+
LAUNCH_POWER_VALUES = {"minimal", "low", "medium", "high", "xhigh"}
|
|
72
|
+
LAUNCH_REASONING_EFFORT_VALUES = {"minimal", "low", "medium", "high", "xhigh"}
|
|
71
73
|
LAUNCH_PERMISSION_VALUES = {"review", "default", "auto", "full"}
|
|
72
74
|
MAX_LAUNCH_MODEL_LENGTH = 128
|
|
73
75
|
MIN_LAUNCH_PRIORITY = 0
|
|
@@ -107,7 +109,7 @@ def _seed_codex_auth_from_global(auth_home, env=None):
|
|
|
107
109
|
return True
|
|
108
110
|
|
|
109
111
|
|
|
110
|
-
def _normalize_launch_settings(settings):
|
|
112
|
+
def _normalize_launch_settings(settings, mark_fast_service_tier=True):
|
|
111
113
|
normalized = {}
|
|
112
114
|
if not settings:
|
|
113
115
|
return normalized
|
|
@@ -138,6 +140,12 @@ def _normalize_launch_settings(settings):
|
|
|
138
140
|
normalized["fast"] = False
|
|
139
141
|
else:
|
|
140
142
|
raise CdxError(f"Unsupported fast value: {settings['fast']}")
|
|
143
|
+
if normalized["fast"] is True and mark_fast_service_tier:
|
|
144
|
+
normalized["fastMode"] = "service_tier"
|
|
145
|
+
else:
|
|
146
|
+
normalized.pop("fastMode", None)
|
|
147
|
+
if settings.get("fastMode") == "service_tier" and normalized["fast"] is True:
|
|
148
|
+
normalized["fastMode"] = "service_tier"
|
|
141
149
|
if "rtk" in settings and settings["rtk"] is not None:
|
|
142
150
|
value = settings["rtk"]
|
|
143
151
|
if isinstance(value, bool):
|
|
@@ -827,17 +835,15 @@ def create_session_service(options=None):
|
|
|
827
835
|
updates = _normalize_launch_settings(settings)
|
|
828
836
|
if not updates:
|
|
829
837
|
raise CdxError("At least one launch setting is required.")
|
|
830
|
-
current = _normalize_launch_settings(session.get("launch") or {})
|
|
838
|
+
current = _normalize_launch_settings(session.get("launch") or {}, mark_fast_service_tier=False)
|
|
831
839
|
launch = {**current, **updates}
|
|
832
840
|
explicit_power = "power" in updates or "reasoning_effort" in updates
|
|
833
|
-
if explicit_power and "fast" not in updates:
|
|
841
|
+
if explicit_power and "fast" not in updates and launch.get("fastMode") != "service_tier":
|
|
834
842
|
launch["fast"] = False
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
launch.pop("reasoningEffort", None)
|
|
840
|
-
elif not any(key in launch for key in ("power", "reasoning_effort", "reasoningEffort")):
|
|
843
|
+
launch.pop("fastMode", None)
|
|
844
|
+
if updates.get("fast") is False:
|
|
845
|
+
launch.pop("fastMode", None)
|
|
846
|
+
if not any(key in launch for key in ("power", "reasoning_effort", "reasoningEffort")):
|
|
841
847
|
launch["power"] = DEFAULT_LAUNCH_SETTINGS["power"]
|
|
842
848
|
now = _local_now_iso()
|
|
843
849
|
return store["update_session"](name, lambda s: {
|