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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # CDX Manager
2
2
 
3
- [![License](https://img.shields.io/badge/license-MIT-4C8BF5)](LICENSE) ![Version](https://img.shields.io/badge/version-v0.9.1-4C8BF5) ![Python](https://img.shields.io/badge/python-3.9%2B-3776AB?logo=python&logoColor=white)
3
+ [![License](https://img.shields.io/badge/license-MIT-4C8BF5)](LICENSE) ![Version](https://img.shields.io/badge/version-v0.9.4-4C8BF5) ![Python](https://img.shields.io/badge/python-3.9%2B-3776AB?logo=python&logoColor=white)
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` clears the stored power setting and uses low effort; setting `--power` again disables fast mode. `--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.
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\|max] [--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
+ | `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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cdx-manager",
3
- "version": "0.9.2",
3
+ "version": "0.9.4",
4
4
  "description": "Terminal session manager for Codex and Claude accounts.",
5
5
  "license": "MIT",
6
6
  "author": "Alexandre Agostini",
package/pyproject.toml CHANGED
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "cdx-manager"
7
- version = "0.9.2"
7
+ version = "0.9.4"
8
8
  description = "Terminal session manager for Codex and Claude accounts."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
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.2"
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|max] [--permission review|default|auto|full] [--fast on|off] [--rtk on|off] [--logics on|off] [--model MODEL] [--priority 0..100] [--json]', '36', use_color)}",
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 not rest or rest == ["--json"]:
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
 
@@ -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|max] [--permission review|default|auto|full] [--fast on|off] [--rtk on|off] [--logics on|off] [--model MODEL] [--priority 0..100] [--json]"
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
- SELECT_USAGE = "Usage: cdx select --provider PROVIDER [--min-reasoning-effort low|medium|high] [--min-power low|medium|high] [--require-ready] [--refresh] --json"
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 = {"low": 0, "medium": 1, "high": 2, "xhigh": 2, "max": 2}
1687
- return order.get(str(value or "low").lower(), 0)
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 handle_launch(command, ctx, initial_prompt=None):
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
- _write_json(ctx, _json_success("launch", message, warnings=warnings, session=ctx["service"]["get_session"](session["name"])))
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)
@@ -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.get("fast") is True:
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
- spec = (
775
- _build_launch_spec(session, cwd=cwd, env_override=env_override, initial_prompt=initial_prompt)
776
- if action == "launch"
777
- else _build_auth_action_spec(session, action, cwd=cwd, env_override=env_override)
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:
@@ -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
  )
@@ -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", "max"}
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
- if "fast" in updates and not explicit_power:
836
- if updates["fast"] is True:
837
- launch.pop("power", None)
838
- launch.pop("reasoning_effort", None)
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: {