cdx-manager 0.9.2 → 0.9.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -4
- package/changelogs/CHANGELOGS_0_9_3.md +35 -0
- package/checksums/release-archives.json +4 -0
- package/package.json +1 -1
- package/pyproject.toml +1 -1
- package/src/cli.py +4 -4
- package/src/cli_commands.py +10 -11
- package/src/health.py +40 -1
- package/src/provider_runtime.py +77 -2
- package/src/run_command.py +1 -1
- package/src/run_usage.py +1 -0
- package/src/session_service.py +15 -11
package/README.md
CHANGED
|
@@ -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
|
|
|
@@ -339,7 +339,7 @@ cdx history --summary --from 2026-05-01 --to 2026-05-28
|
|
|
339
339
|
| `cdx config <name> [--json]` | Show persistent launch settings for a session |
|
|
340
340
|
| `cdx configs [--json]` | Show persistent launch settings for all sessions in one table |
|
|
341
341
|
| `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
|
|
342
|
+
| `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
343
|
| `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
344
|
| `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
345
|
| `cdx last [--json]` | Launch the most recent existing session from launch history |
|
|
@@ -358,8 +358,8 @@ cdx history --summary --from 2026-05-01 --to 2026-05-28
|
|
|
358
358
|
| `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
359
|
| `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
360
|
| `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 |
|
|
361
|
+
| `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 |
|
|
362
|
+
| `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
363
|
| `cdx stats [name] [--since 7d\|today\|DATE] [--from DATE] [--to DATE] [--json]` | Aggregate launch counts, duration, and known headless token usage by session |
|
|
364
364
|
| `cdx status [--json] [--refresh]` | Show token usage table for all sessions; JSON returns a versioned payload with structured warnings |
|
|
365
365
|
| `cdx status --small [--refresh]` / `cdx status -s [--refresh]` | Show compact token usage table without provider, blocking quota, credits, and updated columns |
|
|
@@ -597,6 +597,7 @@ Session names are URL-encoded when used as directory or file names. CLI command
|
|
|
597
597
|
## Troubleshooting
|
|
598
598
|
|
|
599
599
|
- **`cdx <name>` fails with "not authenticated"** — run `cdx login <name>` first.
|
|
600
|
+
- **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
601
|
- **`cdx` says no compatible Python 3 interpreter was found** — install Python 3 and make `py -3`, `python`, or `python3` available on PATH.
|
|
601
602
|
- **`cdx add` succeeds but the session does not appear** — check that `CDX_HOME` is consistent between calls; a mismatch creates two separate registries.
|
|
602
603
|
- **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`
|
|
@@ -72,6 +72,10 @@
|
|
|
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"
|
|
75
79
|
}
|
|
76
80
|
}
|
|
77
81
|
}
|
package/package.json
CHANGED
package/pyproject.toml
CHANGED
package/src/cli.py
CHANGED
|
@@ -64,7 +64,7 @@ from .status_view import (
|
|
|
64
64
|
)
|
|
65
65
|
from .update_check import check_for_update, check_logics_manager_for_update
|
|
66
66
|
|
|
67
|
-
VERSION = "0.9.
|
|
67
|
+
VERSION = "0.9.3"
|
|
68
68
|
|
|
69
69
|
|
|
70
70
|
# ---------------------------------------------------------------------------
|
|
@@ -82,8 +82,8 @@ def _print_help(use_color=False):
|
|
|
82
82
|
f" {_style('cdx status --small|-s [--refresh]', '36', use_color)}",
|
|
83
83
|
f" {_style('cdx status <name> [--json] [--refresh]', '36', use_color)}",
|
|
84
84
|
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)}",
|
|
85
|
+
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)}",
|
|
86
|
+
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
87
|
f" {_style('cdx runs [--limit N] --json', '36', use_color)}",
|
|
88
88
|
f" {_style('cdx run-status <run_id> --json', '36', use_color)}",
|
|
89
89
|
f" {_style('cdx run-report <run_id> --json', '36', use_color)}",
|
|
@@ -91,7 +91,7 @@ def _print_help(use_color=False):
|
|
|
91
91
|
f" {_style('cdx config <name> [--json]', '36', use_color)}",
|
|
92
92
|
f" {_style('cdx configs [--json]', '36', use_color)}",
|
|
93
93
|
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
|
|
94
|
+
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
95
|
f" {_style('cdx unset <name>|--sessions all|a,b|--provider PROVIDER (--power|--permission|--fast|--rtk|--logics|--model|--priority|--all) [--json]', '36', use_color)}",
|
|
96
96
|
f" {_style('cdx history [name] [--limit N] [--summary] [--since 7d|today|DATE] [--from DATE] [--to DATE] [--json]', '36', use_color)}",
|
|
97
97
|
f" {_style('cdx stats [name] [--since 7d|today|DATE] [--from DATE] [--to DATE] [--json]', '36', use_color)}",
|
package/src/cli_commands.py
CHANGED
|
@@ -58,7 +58,7 @@ EXPORT_USAGE = "Usage: cdx export <file> [--include-auth] [--force] [--json] [--
|
|
|
58
58
|
IMPORT_USAGE = "Usage: cdx import <file> [--force|--merge] [--json] [--sessions name1,name2] [--passphrase-env VAR]"
|
|
59
59
|
CONTEXT_USAGE = "Usage: cdx context show|path|init|edit|clear|set [text...] [--json]"
|
|
60
60
|
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
|
|
61
|
+
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
62
|
UNSET_USAGE = "Usage: cdx unset <name>|--sessions all|a,b|--provider PROVIDER (--power|--permission|--fast|--rtk|--logics|--model|--priority|--all) [--json]"
|
|
63
63
|
SETTING_ALIAS_USAGE = "Usage: cdx power|perm|fast|model <name|all|provider:PROVIDER|a,b> <value|default> [--json]"
|
|
64
64
|
CONFIG_USAGE = "Usage: cdx config <name> [--json]"
|
|
@@ -66,9 +66,9 @@ CONFIGS_USAGE = "Usage: cdx configs [--json]"
|
|
|
66
66
|
HISTORY_USAGE = "Usage: cdx history [name] [--limit N] [--summary] [--since 7d|today|DATE] [--from DATE] [--to DATE] [--json]"
|
|
67
67
|
STATS_USAGE = "Usage: cdx stats [name] [--since 7d|today|DATE] [--from DATE] [--to DATE] [--json]"
|
|
68
68
|
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"
|
|
69
|
+
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
70
|
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"
|
|
71
|
+
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
72
|
RUNS_USAGE = "Usage: cdx runs [--limit N] --json"
|
|
73
73
|
RUN_STATUS_USAGE = "Usage: cdx run-status <run_id> --json"
|
|
74
74
|
RUN_REPORT_USAGE = "Usage: cdx run-report <run_id> --json"
|
|
@@ -1683,8 +1683,8 @@ def _resolve_bulk_launch_targets(parsed, service):
|
|
|
1683
1683
|
|
|
1684
1684
|
|
|
1685
1685
|
def _reasoning_rank(value):
|
|
1686
|
-
order = {"
|
|
1687
|
-
return order.get(str(value or "low").lower(),
|
|
1686
|
+
order = {"minimal": 0, "low": 1, "medium": 2, "high": 3, "xhigh": 4}
|
|
1687
|
+
return order.get(str(value or "low").lower(), -1)
|
|
1688
1688
|
|
|
1689
1689
|
|
|
1690
1690
|
def _session_reasoning_effort(session):
|
|
@@ -1693,7 +1693,7 @@ def _session_reasoning_effort(session):
|
|
|
1693
1693
|
launch.get("reasoning_effort")
|
|
1694
1694
|
or launch.get("reasoningEffort")
|
|
1695
1695
|
or launch.get("power")
|
|
1696
|
-
or ("low" if launch.get("fast") is True else None)
|
|
1696
|
+
or ("low" if launch.get("fast") is True and launch.get("fastMode") != "service_tier" else None)
|
|
1697
1697
|
or "low"
|
|
1698
1698
|
)
|
|
1699
1699
|
|
|
@@ -2290,6 +2290,7 @@ def handle_doctor(rest, ctx):
|
|
|
2290
2290
|
ctx["service"],
|
|
2291
2291
|
ctx["service"]["base_dir"],
|
|
2292
2292
|
env=ctx.get("env"),
|
|
2293
|
+
spawn_sync=ctx.get("spawn_sync"),
|
|
2293
2294
|
)
|
|
2294
2295
|
if json_flag:
|
|
2295
2296
|
_write_json(ctx, _json_success("doctor", "Collected health report", report=report))
|
|
@@ -2672,11 +2673,6 @@ def handle_login(rest, ctx):
|
|
|
2672
2673
|
session = ctx["service"]["get_session"](args[0])
|
|
2673
2674
|
if not session:
|
|
2674
2675
|
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
2676
|
_run_interactive_provider_command(
|
|
2681
2677
|
session, "login", spawn=ctx.get("spawn"), env_override=ctx.get("env"),
|
|
2682
2678
|
signal_emitter=ctx.get("signal_emitter")
|
|
@@ -2687,6 +2683,7 @@ def handle_login(rest, ctx):
|
|
|
2687
2683
|
spawn_sync=ctx.get("spawn_sync"),
|
|
2688
2684
|
env_override=ctx.get("env"),
|
|
2689
2685
|
behavior="probe-only",
|
|
2686
|
+
trust_local_credentials=False,
|
|
2690
2687
|
)
|
|
2691
2688
|
if not auth_probe.get("authenticated") and session["provider"] == PROVIDER_CLAUDE:
|
|
2692
2689
|
_bootstrap_claude_setup_token(session, ctx)
|
|
@@ -2696,6 +2693,7 @@ def handle_login(rest, ctx):
|
|
|
2696
2693
|
spawn_sync=ctx.get("spawn_sync"),
|
|
2697
2694
|
env_override=ctx.get("env"),
|
|
2698
2695
|
behavior="probe-only",
|
|
2696
|
+
trust_local_credentials=False,
|
|
2699
2697
|
)
|
|
2700
2698
|
if not auth_probe.get("authenticated"):
|
|
2701
2699
|
raise CdxError(
|
|
@@ -2859,6 +2857,7 @@ def handle_launch(command, ctx, initial_prompt=None):
|
|
|
2859
2857
|
stdin_is_tty=ctx["stdin_is_tty"],
|
|
2860
2858
|
behavior="launch",
|
|
2861
2859
|
signal_emitter=ctx.get("signal_emitter"),
|
|
2860
|
+
trust_local_credentials=False,
|
|
2862
2861
|
)
|
|
2863
2862
|
message = f"Launching {session['provider']} session {session['name']}"
|
|
2864
2863
|
if not json_flag:
|
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
|
|
@@ -482,6 +556,7 @@ def _build_headless_launch_spec(session, cwd=None, env_override=None, initial_pr
|
|
|
482
556
|
args += ["--model", model]
|
|
483
557
|
if power:
|
|
484
558
|
args += ["-c", f'model_reasoning_effort="{power}"']
|
|
559
|
+
args += _codex_fast_config_args(launch)
|
|
485
560
|
if permission:
|
|
486
561
|
args += HEADLESS_CODEX_PERMISSION_ARGS.get(permission, [])
|
|
487
562
|
if initial_prompt:
|
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
|
@@ -66,8 +66,8 @@ RESERVED_SESSION_NAMES = {
|
|
|
66
66
|
STATUS_CACHE_TTL_SECONDS = 60
|
|
67
67
|
CLAUDE_STATUS_CACHE_TTL_SECONDS = 10 * 60
|
|
68
68
|
MAX_STATUS_WORKERS = 8
|
|
69
|
-
LAUNCH_POWER_VALUES = {"low", "medium", "high", "xhigh"
|
|
70
|
-
LAUNCH_REASONING_EFFORT_VALUES = {"low", "medium", "high"}
|
|
69
|
+
LAUNCH_POWER_VALUES = {"minimal", "low", "medium", "high", "xhigh"}
|
|
70
|
+
LAUNCH_REASONING_EFFORT_VALUES = {"minimal", "low", "medium", "high", "xhigh"}
|
|
71
71
|
LAUNCH_PERMISSION_VALUES = {"review", "default", "auto", "full"}
|
|
72
72
|
MAX_LAUNCH_MODEL_LENGTH = 128
|
|
73
73
|
MIN_LAUNCH_PRIORITY = 0
|
|
@@ -107,7 +107,7 @@ def _seed_codex_auth_from_global(auth_home, env=None):
|
|
|
107
107
|
return True
|
|
108
108
|
|
|
109
109
|
|
|
110
|
-
def _normalize_launch_settings(settings):
|
|
110
|
+
def _normalize_launch_settings(settings, mark_fast_service_tier=True):
|
|
111
111
|
normalized = {}
|
|
112
112
|
if not settings:
|
|
113
113
|
return normalized
|
|
@@ -138,6 +138,12 @@ def _normalize_launch_settings(settings):
|
|
|
138
138
|
normalized["fast"] = False
|
|
139
139
|
else:
|
|
140
140
|
raise CdxError(f"Unsupported fast value: {settings['fast']}")
|
|
141
|
+
if normalized["fast"] is True and mark_fast_service_tier:
|
|
142
|
+
normalized["fastMode"] = "service_tier"
|
|
143
|
+
else:
|
|
144
|
+
normalized.pop("fastMode", None)
|
|
145
|
+
if settings.get("fastMode") == "service_tier" and normalized["fast"] is True:
|
|
146
|
+
normalized["fastMode"] = "service_tier"
|
|
141
147
|
if "rtk" in settings and settings["rtk"] is not None:
|
|
142
148
|
value = settings["rtk"]
|
|
143
149
|
if isinstance(value, bool):
|
|
@@ -827,17 +833,15 @@ def create_session_service(options=None):
|
|
|
827
833
|
updates = _normalize_launch_settings(settings)
|
|
828
834
|
if not updates:
|
|
829
835
|
raise CdxError("At least one launch setting is required.")
|
|
830
|
-
current = _normalize_launch_settings(session.get("launch") or {})
|
|
836
|
+
current = _normalize_launch_settings(session.get("launch") or {}, mark_fast_service_tier=False)
|
|
831
837
|
launch = {**current, **updates}
|
|
832
838
|
explicit_power = "power" in updates or "reasoning_effort" in updates
|
|
833
|
-
if explicit_power and "fast" not in updates:
|
|
839
|
+
if explicit_power and "fast" not in updates and launch.get("fastMode") != "service_tier":
|
|
834
840
|
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")):
|
|
841
|
+
launch.pop("fastMode", None)
|
|
842
|
+
if updates.get("fast") is False:
|
|
843
|
+
launch.pop("fastMode", None)
|
|
844
|
+
if not any(key in launch for key in ("power", "reasoning_effort", "reasoningEffort")):
|
|
841
845
|
launch["power"] = DEFAULT_LAUNCH_SETTINGS["power"]
|
|
842
846
|
now = _local_now_iso()
|
|
843
847
|
return store["update_session"](name, lambda s: {
|