cdx-manager 0.6.5 → 0.7.1
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 +47 -7
- package/changelogs/CHANGELOGS_0_7_0.md +49 -0
- package/changelogs/CHANGELOGS_0_7_1.md +41 -0
- package/checksums/release-archives.json +8 -0
- package/package.json +1 -1
- package/pyproject.toml +1 -1
- package/src/claude_usage.py +11 -2
- package/src/cli.py +15 -4
- package/src/cli_commands.py +429 -5
- package/src/cli_render.py +3 -1
- package/src/provider_runtime.py +223 -11
- package/src/run_usage.py +133 -0
- package/src/session_service.py +35 -8
package/src/cli_commands.py
CHANGED
|
@@ -33,11 +33,14 @@ from .notify import (
|
|
|
33
33
|
from .provider_runtime import (
|
|
34
34
|
_ensure_session_authentication,
|
|
35
35
|
_list_launch_transcript_paths,
|
|
36
|
+
_normalize_reasoning_effort,
|
|
36
37
|
_probe_provider_auth,
|
|
38
|
+
_run_headless_provider_command,
|
|
37
39
|
_run_interactive_provider_command,
|
|
38
40
|
)
|
|
39
41
|
from .repair import format_repair_report, repair_health
|
|
40
42
|
from .backup_bundle import read_bundle_meta
|
|
43
|
+
from .run_usage import empty_usage, extract_run_usage
|
|
41
44
|
from .status_view import _format_status_detail, _format_status_rows
|
|
42
45
|
from .update_check import LatestReleaseCheckError, fetch_latest_release, fetch_latest_release_or_raise, is_newer_version
|
|
43
46
|
from .update_manager import build_update_plan, format_update_failure, run_update_plan
|
|
@@ -51,12 +54,14 @@ EXPORT_USAGE = "Usage: cdx export <file> [--include-auth] [--force] [--json] [--
|
|
|
51
54
|
IMPORT_USAGE = "Usage: cdx import <file> [--force] [--json] [--sessions name1,name2] [--passphrase-env VAR]"
|
|
52
55
|
CONTEXT_USAGE = "Usage: cdx context show|path|init|edit|clear|set [text...] [--json]"
|
|
53
56
|
HANDOFF_USAGE = "Usage: cdx handoff <name> [--json] | cdx handoff <source> <target> [--json]"
|
|
54
|
-
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] [--model MODEL] [--json]"
|
|
55
|
-
UNSET_USAGE = "Usage: cdx unset <name>|--sessions all|a,b|--provider PROVIDER (--power|--permission|--fast|--model|--all) [--json]"
|
|
57
|
+
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] [--model MODEL] [--priority 0..100] [--json]"
|
|
58
|
+
UNSET_USAGE = "Usage: cdx unset <name>|--sessions all|a,b|--provider PROVIDER (--power|--permission|--fast|--model|--priority|--all) [--json]"
|
|
56
59
|
SETTING_ALIAS_USAGE = "Usage: cdx power|perm|fast|model <name|all|provider:PROVIDER|a,b> <value|default> [--json]"
|
|
57
60
|
CONFIG_USAGE = "Usage: cdx config <name> [--json]"
|
|
58
61
|
HISTORY_USAGE = "Usage: cdx history [name] [--limit N] [--summary] [--since 7d|today|DATE] [--from DATE] [--to DATE] [--json]"
|
|
59
62
|
LAST_USAGE = "Usage: cdx last [--json]"
|
|
63
|
+
SELECT_USAGE = "Usage: cdx select --provider PROVIDER [--min-reasoning-effort low|medium|high] [--min-power low|medium|high] [--require-ready] [--refresh] --json"
|
|
64
|
+
RUN_USAGE = "Usage: 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"
|
|
60
65
|
API_SCHEMA_VERSION = 1
|
|
61
66
|
HANDOFF_TRANSCRIPT_CHARS = 120000
|
|
62
67
|
HANDOFF_NATIVE_TRANSCRIPT_CANDIDATES = 64
|
|
@@ -78,6 +83,23 @@ def _json_success(action, message, warnings=None, **extra):
|
|
|
78
83
|
return payload
|
|
79
84
|
|
|
80
85
|
|
|
86
|
+
def _json_failure(action, code, message, source="cdx", exit_code=1, warnings=None, **extra):
|
|
87
|
+
payload = {
|
|
88
|
+
"schema_version": API_SCHEMA_VERSION,
|
|
89
|
+
"ok": False,
|
|
90
|
+
"action": action,
|
|
91
|
+
"warnings": warnings or [],
|
|
92
|
+
"error": {
|
|
93
|
+
"source": source,
|
|
94
|
+
"code": code,
|
|
95
|
+
"message": message,
|
|
96
|
+
"exit_code": exit_code,
|
|
97
|
+
},
|
|
98
|
+
}
|
|
99
|
+
payload.update(extra)
|
|
100
|
+
return payload
|
|
101
|
+
|
|
102
|
+
|
|
81
103
|
def _write_json(ctx, payload):
|
|
82
104
|
ctx["out"](f"{json.dumps(payload, indent=2)}\n")
|
|
83
105
|
|
|
@@ -467,12 +489,23 @@ def _parse_fast_value(value):
|
|
|
467
489
|
raise CdxError(SET_USAGE)
|
|
468
490
|
|
|
469
491
|
|
|
492
|
+
def _parse_priority_value(value):
|
|
493
|
+
try:
|
|
494
|
+
priority = int(value)
|
|
495
|
+
except (TypeError, ValueError) as error:
|
|
496
|
+
raise CdxError(SET_USAGE) from error
|
|
497
|
+
if priority < 0 or priority > 100:
|
|
498
|
+
raise CdxError(SET_USAGE)
|
|
499
|
+
return priority
|
|
500
|
+
|
|
501
|
+
|
|
470
502
|
def _parse_set_args(args):
|
|
471
503
|
parsed = _parse_flag_args(args, {
|
|
472
504
|
"--power": {"key": "power", "type": "str", "default": None},
|
|
473
505
|
"--permission": {"key": "permission", "type": "str", "default": None},
|
|
474
506
|
"--fast": {"key": "fast", "type": "str", "default": None, "transform": _parse_fast_value},
|
|
475
507
|
"--model": {"key": "model", "type": "str", "default": None},
|
|
508
|
+
"--priority": {"key": "priority", "type": "str", "default": None, "transform": _parse_priority_value},
|
|
476
509
|
"--sessions": {"key": "sessions", "type": "str", "default": None, "transform": _parse_set_unset_sessions},
|
|
477
510
|
"--provider": {"key": "provider", "type": "str", "default": None, "transform": lambda value: _parse_provider_filter(value, SET_USAGE)},
|
|
478
511
|
"--json": {"key": "json", "type": "bool", "default": False},
|
|
@@ -487,7 +520,7 @@ def _parse_set_args(args):
|
|
|
487
520
|
raise CdxError(SET_USAGE)
|
|
488
521
|
settings = {
|
|
489
522
|
key: parsed[key]
|
|
490
|
-
for key in ("power", "permission", "fast", "model")
|
|
523
|
+
for key in ("power", "permission", "fast", "model", "priority")
|
|
491
524
|
if parsed[key] is not None
|
|
492
525
|
}
|
|
493
526
|
if not settings:
|
|
@@ -507,6 +540,7 @@ def _parse_unset_args(args):
|
|
|
507
540
|
"--permission": {"key": "permission", "type": "bool", "default": False},
|
|
508
541
|
"--fast": {"key": "fast", "type": "bool", "default": False},
|
|
509
542
|
"--model": {"key": "model", "type": "bool", "default": False},
|
|
543
|
+
"--priority": {"key": "priority", "type": "bool", "default": False},
|
|
510
544
|
"--all": {"key": "all", "type": "bool", "default": False},
|
|
511
545
|
"--sessions": {"key": "sessions", "type": "str", "default": None, "transform": _parse_set_unset_sessions},
|
|
512
546
|
"--provider": {"key": "provider", "type": "str", "default": None, "transform": lambda value: _parse_provider_filter(value, UNSET_USAGE)},
|
|
@@ -520,8 +554,8 @@ def _parse_unset_args(args):
|
|
|
520
554
|
raise CdxError(UNSET_USAGE)
|
|
521
555
|
if not parsed["names"] and not parsed["sessions"] and not parsed["provider"]:
|
|
522
556
|
raise CdxError(UNSET_USAGE)
|
|
523
|
-
keys = ["power", "permission", "fast", "model"] if parsed["all"] else [
|
|
524
|
-
key for key in ("power", "permission", "fast", "model") if parsed[key]
|
|
557
|
+
keys = ["power", "permission", "fast", "model", "priority"] if parsed["all"] else [
|
|
558
|
+
key for key in ("power", "permission", "fast", "model", "priority") if parsed[key]
|
|
525
559
|
]
|
|
526
560
|
if not keys:
|
|
527
561
|
raise CdxError(UNSET_USAGE)
|
|
@@ -543,6 +577,99 @@ def _parse_config_args(args):
|
|
|
543
577
|
return {"name": parsed["names"][0], "json": parsed["json"]}
|
|
544
578
|
|
|
545
579
|
|
|
580
|
+
def _parse_select_args(args):
|
|
581
|
+
parsed = _parse_flag_args(args, {
|
|
582
|
+
"--provider": {"key": "provider", "type": "str", "default": None, "transform": lambda value: _parse_provider_filter(value, SELECT_USAGE)},
|
|
583
|
+
"--min-reasoning-effort": {"key": "min_reasoning_effort", "type": "str", "default": None},
|
|
584
|
+
"--min-power": {"key": "min_power", "type": "str", "default": None},
|
|
585
|
+
"--require-ready": {"key": "require_ready", "type": "bool", "default": False},
|
|
586
|
+
"--refresh": {"key": "refresh", "type": "bool", "default": False},
|
|
587
|
+
"--json": {"key": "json", "type": "bool", "default": False},
|
|
588
|
+
}, SELECT_USAGE)
|
|
589
|
+
if not parsed["provider"] or not parsed["json"]:
|
|
590
|
+
raise CdxError(SELECT_USAGE)
|
|
591
|
+
effort = _normalize_reasoning_effort(
|
|
592
|
+
reasoning_effort=parsed["min_reasoning_effort"],
|
|
593
|
+
power=parsed["min_power"],
|
|
594
|
+
usage=SELECT_USAGE,
|
|
595
|
+
).get("reasoning_effort")
|
|
596
|
+
return {
|
|
597
|
+
"provider": parsed["provider"],
|
|
598
|
+
"min_reasoning_effort": effort,
|
|
599
|
+
"require_ready": parsed["require_ready"],
|
|
600
|
+
"refresh": parsed["refresh"],
|
|
601
|
+
"json": parsed["json"],
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
|
|
605
|
+
def _parse_timeout_seconds(value):
|
|
606
|
+
try:
|
|
607
|
+
parsed = float(value)
|
|
608
|
+
except (TypeError, ValueError) as error:
|
|
609
|
+
raise CdxError(RUN_USAGE) from error
|
|
610
|
+
if parsed <= 0:
|
|
611
|
+
raise CdxError(RUN_USAGE)
|
|
612
|
+
return parsed
|
|
613
|
+
|
|
614
|
+
|
|
615
|
+
def _normalize_run_permission(value):
|
|
616
|
+
if value is None:
|
|
617
|
+
return None
|
|
618
|
+
text = str(value).strip().lower()
|
|
619
|
+
aliases = {
|
|
620
|
+
"workspace-write": "default",
|
|
621
|
+
"read-only": "review",
|
|
622
|
+
"danger-full-access": "full",
|
|
623
|
+
}
|
|
624
|
+
text = aliases.get(text, text)
|
|
625
|
+
if text not in ("review", "default", "auto", "full"):
|
|
626
|
+
raise CdxError(RUN_USAGE)
|
|
627
|
+
return text
|
|
628
|
+
|
|
629
|
+
|
|
630
|
+
def _parse_run_args(args):
|
|
631
|
+
parsed = _parse_flag_args(args, {
|
|
632
|
+
"--cwd": {"key": "cwd", "type": "str", "default": None},
|
|
633
|
+
"--prompt-file": {"key": "prompt_file", "type": "str", "default": None},
|
|
634
|
+
"--prompt": {"key": "prompt", "type": "str", "default": None},
|
|
635
|
+
"--provider": {"key": "provider", "type": "str", "default": None, "transform": lambda value: _parse_provider_filter(value, RUN_USAGE)},
|
|
636
|
+
"--model": {"key": "model", "type": "str", "default": None},
|
|
637
|
+
"--reasoning-effort": {"key": "reasoning_effort", "type": "str", "default": None},
|
|
638
|
+
"--power": {"key": "power", "type": "str", "default": None},
|
|
639
|
+
"--permission": {"key": "permission", "type": "str", "default": None, "transform": _normalize_run_permission},
|
|
640
|
+
"--timeout-seconds": {"key": "timeout_seconds", "type": "str", "default": None, "transform": _parse_timeout_seconds},
|
|
641
|
+
"--json": {"key": "json", "type": "bool", "default": False},
|
|
642
|
+
}, RUN_USAGE, positionals_key="names", max_positionals=1)
|
|
643
|
+
if not parsed["json"]:
|
|
644
|
+
raise CdxError(RUN_USAGE)
|
|
645
|
+
name = parsed["names"][0] if parsed["names"] else None
|
|
646
|
+
if name and parsed["provider"]:
|
|
647
|
+
raise CdxError(RUN_USAGE)
|
|
648
|
+
if not name and not parsed["provider"]:
|
|
649
|
+
raise CdxError(RUN_USAGE)
|
|
650
|
+
if not parsed["cwd"]:
|
|
651
|
+
raise CdxError(RUN_USAGE)
|
|
652
|
+
if bool(parsed["prompt_file"]) == bool(parsed["prompt"]):
|
|
653
|
+
raise CdxError(RUN_USAGE)
|
|
654
|
+
effort = _normalize_reasoning_effort(
|
|
655
|
+
reasoning_effort=parsed["reasoning_effort"],
|
|
656
|
+
power=parsed["power"],
|
|
657
|
+
usage=RUN_USAGE,
|
|
658
|
+
)
|
|
659
|
+
return {
|
|
660
|
+
"name": name,
|
|
661
|
+
"provider": parsed["provider"],
|
|
662
|
+
"cwd": parsed["cwd"],
|
|
663
|
+
"prompt_file": parsed["prompt_file"],
|
|
664
|
+
"prompt": parsed["prompt"],
|
|
665
|
+
"model": parsed["model"],
|
|
666
|
+
"permission": parsed["permission"],
|
|
667
|
+
"timeout_seconds": parsed["timeout_seconds"],
|
|
668
|
+
"reasoning_effort": effort.get("reasoning_effort"),
|
|
669
|
+
"power": effort.get("power"),
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
|
|
546
673
|
def _parse_positive_int(value):
|
|
547
674
|
try:
|
|
548
675
|
parsed = int(value)
|
|
@@ -1025,6 +1152,221 @@ def handle_enable(rest, ctx):
|
|
|
1025
1152
|
return 0
|
|
1026
1153
|
|
|
1027
1154
|
|
|
1155
|
+
def handle_select(rest, ctx):
|
|
1156
|
+
parsed = _parse_select_args(rest)
|
|
1157
|
+
selected = _select_headless_session(
|
|
1158
|
+
ctx,
|
|
1159
|
+
parsed["provider"],
|
|
1160
|
+
min_reasoning_effort=parsed["min_reasoning_effort"],
|
|
1161
|
+
require_ready=parsed["require_ready"],
|
|
1162
|
+
force_refresh=parsed["refresh"],
|
|
1163
|
+
)
|
|
1164
|
+
policy = "ready_then_cooldown_then_health_then_priority_then_name"
|
|
1165
|
+
if not selected:
|
|
1166
|
+
message = "No suitable session found."
|
|
1167
|
+
_write_json(ctx, _json_failure(
|
|
1168
|
+
"select",
|
|
1169
|
+
"no_suitable_session",
|
|
1170
|
+
message,
|
|
1171
|
+
provider=parsed["provider"],
|
|
1172
|
+
min_reasoning_effort=parsed["min_reasoning_effort"],
|
|
1173
|
+
require_ready=parsed["require_ready"],
|
|
1174
|
+
selection_policy=policy,
|
|
1175
|
+
))
|
|
1176
|
+
return 1
|
|
1177
|
+
session = selected["session"]
|
|
1178
|
+
row = selected.get("row") or {}
|
|
1179
|
+
reason = "highest availability suitable session"
|
|
1180
|
+
_write_json(ctx, _json_success(
|
|
1181
|
+
"select",
|
|
1182
|
+
f"Selected session {session['name']}",
|
|
1183
|
+
session=session["name"],
|
|
1184
|
+
provider=session["provider"],
|
|
1185
|
+
reason=reason,
|
|
1186
|
+
selection_policy=policy,
|
|
1187
|
+
min_reasoning_effort=parsed["min_reasoning_effort"],
|
|
1188
|
+
reasoning_effort=_session_reasoning_effort(session),
|
|
1189
|
+
available_pct=row.get("available_pct"),
|
|
1190
|
+
auth_status=row.get("auth_status"),
|
|
1191
|
+
))
|
|
1192
|
+
return 0
|
|
1193
|
+
|
|
1194
|
+
|
|
1195
|
+
def _read_run_prompt(parsed):
|
|
1196
|
+
if parsed.get("prompt") is not None:
|
|
1197
|
+
return parsed["prompt"]
|
|
1198
|
+
try:
|
|
1199
|
+
with open(parsed["prompt_file"], "r", encoding="utf-8") as handle:
|
|
1200
|
+
return handle.read()
|
|
1201
|
+
except OSError as error:
|
|
1202
|
+
raise CdxError(f"Unable to read prompt file: {parsed['prompt_file']}") from error
|
|
1203
|
+
|
|
1204
|
+
|
|
1205
|
+
def _run_cdx_error_code(error):
|
|
1206
|
+
message = str(error)
|
|
1207
|
+
if message.startswith("Usage:"):
|
|
1208
|
+
return "invalid_request"
|
|
1209
|
+
if message.startswith("Invalid cwd:"):
|
|
1210
|
+
return "invalid_cwd"
|
|
1211
|
+
if message.startswith("Session is disabled:"):
|
|
1212
|
+
return "session_disabled"
|
|
1213
|
+
if "CLI not found on PATH" in message:
|
|
1214
|
+
return "provider_cli_not_found"
|
|
1215
|
+
if message.startswith("Failed to start "):
|
|
1216
|
+
return "provider_start_failed"
|
|
1217
|
+
if (
|
|
1218
|
+
message.startswith("Unsupported reasoning effort:")
|
|
1219
|
+
or message.startswith("Unsupported power:")
|
|
1220
|
+
or "--reasoning-effort and --power must match" in message
|
|
1221
|
+
):
|
|
1222
|
+
return "invalid_reasoning_effort"
|
|
1223
|
+
return "cdx_error"
|
|
1224
|
+
|
|
1225
|
+
|
|
1226
|
+
def _run_result_payload(ok, parsed, session, run_info=None, error=None, error_source=None, error_code=None):
|
|
1227
|
+
run_info = run_info or {}
|
|
1228
|
+
usage = (
|
|
1229
|
+
extract_run_usage(session.get("provider"), run_info.get("stdout_path"))
|
|
1230
|
+
if ok and session else
|
|
1231
|
+
empty_usage()
|
|
1232
|
+
)
|
|
1233
|
+
return {
|
|
1234
|
+
"schema_version": API_SCHEMA_VERSION,
|
|
1235
|
+
"ok": bool(ok),
|
|
1236
|
+
"action": "run",
|
|
1237
|
+
"launcher": "cdx",
|
|
1238
|
+
"session": session.get("name") if session else None,
|
|
1239
|
+
"provider": session.get("provider") if session else parsed.get("provider"),
|
|
1240
|
+
"model": parsed.get("model") or ((session.get("launch") or {}).get("model") if session else None),
|
|
1241
|
+
"reasoning_effort": parsed.get("reasoning_effort") or ((session.get("launch") or {}).get("reasoning_effort") if session else None),
|
|
1242
|
+
"power": parsed.get("power") or ((session.get("launch") or {}).get("power") if session else None),
|
|
1243
|
+
"cwd": os.path.abspath(parsed.get("cwd") or os.getcwd()),
|
|
1244
|
+
"run_id": run_info.get("run_id"),
|
|
1245
|
+
"exit_code": run_info.get("returncode"),
|
|
1246
|
+
"duration_seconds": (run_info.get("duration_ms") / 1000.0) if run_info.get("duration_ms") is not None else None,
|
|
1247
|
+
"transcript_path": run_info.get("transcript_path"),
|
|
1248
|
+
"stdout_path": run_info.get("stdout_path"),
|
|
1249
|
+
"stderr_path": run_info.get("stderr_path"),
|
|
1250
|
+
"usage": usage,
|
|
1251
|
+
"warnings": [],
|
|
1252
|
+
"error": None if ok else {
|
|
1253
|
+
"source": error_source or "cdx",
|
|
1254
|
+
"code": error_code or "cdx_error",
|
|
1255
|
+
"message": str(error) if error else "Run failed.",
|
|
1256
|
+
"provider_code": run_info.get("returncode") if error_source == "provider" else None,
|
|
1257
|
+
},
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
|
|
1261
|
+
def handle_run(rest, ctx):
|
|
1262
|
+
try:
|
|
1263
|
+
parsed = _parse_run_args(rest)
|
|
1264
|
+
session = None
|
|
1265
|
+
if parsed["name"]:
|
|
1266
|
+
session = ctx["service"]["get_session"](parsed["name"])
|
|
1267
|
+
if not session:
|
|
1268
|
+
raise CdxError(f"Unknown session: {parsed['name']}")
|
|
1269
|
+
if session.get("enabled", True) is False:
|
|
1270
|
+
raise CdxError(f"Session is disabled: {parsed['name']}")
|
|
1271
|
+
else:
|
|
1272
|
+
selected = _select_headless_session(
|
|
1273
|
+
ctx,
|
|
1274
|
+
parsed["provider"],
|
|
1275
|
+
min_reasoning_effort=parsed["reasoning_effort"],
|
|
1276
|
+
require_ready=True,
|
|
1277
|
+
force_refresh=False,
|
|
1278
|
+
)
|
|
1279
|
+
if not selected:
|
|
1280
|
+
_write_json(ctx, _json_failure(
|
|
1281
|
+
"run",
|
|
1282
|
+
"no_suitable_session",
|
|
1283
|
+
"No suitable session found.",
|
|
1284
|
+
launcher="cdx",
|
|
1285
|
+
provider=parsed["provider"],
|
|
1286
|
+
))
|
|
1287
|
+
return 1
|
|
1288
|
+
session = selected["session"]
|
|
1289
|
+
|
|
1290
|
+
cwd = os.path.abspath(parsed["cwd"])
|
|
1291
|
+
if not os.path.isdir(cwd):
|
|
1292
|
+
raise CdxError(f"Invalid cwd: {parsed['cwd']}")
|
|
1293
|
+
prompt = _read_run_prompt(parsed)
|
|
1294
|
+
launch_updates = {}
|
|
1295
|
+
if parsed.get("model"):
|
|
1296
|
+
launch_updates["model"] = parsed["model"]
|
|
1297
|
+
if parsed.get("permission"):
|
|
1298
|
+
launch_updates["permission"] = parsed["permission"]
|
|
1299
|
+
if parsed.get("reasoning_effort"):
|
|
1300
|
+
launch_updates["reasoning_effort"] = parsed["reasoning_effort"]
|
|
1301
|
+
launch_updates["power"] = parsed["reasoning_effort"]
|
|
1302
|
+
run_session = {
|
|
1303
|
+
**session,
|
|
1304
|
+
"launch": {
|
|
1305
|
+
**(session.get("launch") or {}),
|
|
1306
|
+
**launch_updates,
|
|
1307
|
+
},
|
|
1308
|
+
}
|
|
1309
|
+
_ensure_session_authentication(
|
|
1310
|
+
run_session,
|
|
1311
|
+
ctx["service"],
|
|
1312
|
+
spawn=ctx.get("spawn"),
|
|
1313
|
+
spawn_sync=ctx.get("spawn_sync"),
|
|
1314
|
+
env_override=ctx.get("env"),
|
|
1315
|
+
stdin_is_tty=False,
|
|
1316
|
+
behavior="launch",
|
|
1317
|
+
signal_emitter=ctx.get("signal_emitter"),
|
|
1318
|
+
)
|
|
1319
|
+
run_info = _run_headless_provider_command(
|
|
1320
|
+
run_session,
|
|
1321
|
+
cwd=cwd,
|
|
1322
|
+
env_override=ctx.get("env"),
|
|
1323
|
+
initial_prompt=prompt,
|
|
1324
|
+
timeout_seconds=parsed.get("timeout_seconds"),
|
|
1325
|
+
spawn=ctx.get("spawn_headless") or ctx.get("spawn"),
|
|
1326
|
+
)
|
|
1327
|
+
ok = run_info.get("returncode") == 0
|
|
1328
|
+
if ok:
|
|
1329
|
+
ctx["service"]["record_launch_history"](session["name"], {
|
|
1330
|
+
"status": "success",
|
|
1331
|
+
"cwd": cwd,
|
|
1332
|
+
"exit_code": 0,
|
|
1333
|
+
**run_info,
|
|
1334
|
+
})
|
|
1335
|
+
_write_json(ctx, _run_result_payload(True, parsed, run_session, run_info=run_info))
|
|
1336
|
+
return 0
|
|
1337
|
+
message = "Provider process timed out." if run_info.get("timed_out") else "Provider process exited with a non-zero status."
|
|
1338
|
+
error = CdxError(message, run_info.get("returncode") or 1)
|
|
1339
|
+
ctx["service"]["record_launch_history"](session["name"], {
|
|
1340
|
+
"status": "failed",
|
|
1341
|
+
"cwd": cwd,
|
|
1342
|
+
"error": str(error),
|
|
1343
|
+
"exit_code": error.exit_code,
|
|
1344
|
+
**run_info,
|
|
1345
|
+
})
|
|
1346
|
+
_write_json(ctx, _run_result_payload(
|
|
1347
|
+
False,
|
|
1348
|
+
parsed,
|
|
1349
|
+
run_session,
|
|
1350
|
+
run_info=run_info,
|
|
1351
|
+
error=error,
|
|
1352
|
+
error_source="provider",
|
|
1353
|
+
error_code="provider_timeout" if run_info.get("timed_out") else "provider_failed",
|
|
1354
|
+
))
|
|
1355
|
+
return error.exit_code or 1
|
|
1356
|
+
except CdxError as error:
|
|
1357
|
+
run_info = getattr(error, "run_info", None)
|
|
1358
|
+
_write_json(ctx, _run_result_payload(
|
|
1359
|
+
False,
|
|
1360
|
+
locals().get("parsed", {}) or {},
|
|
1361
|
+
locals().get("session"),
|
|
1362
|
+
run_info=run_info,
|
|
1363
|
+
error=error,
|
|
1364
|
+
error_source="cdx",
|
|
1365
|
+
error_code=_run_cdx_error_code(error),
|
|
1366
|
+
))
|
|
1367
|
+
return error.exit_code
|
|
1368
|
+
|
|
1369
|
+
|
|
1028
1370
|
def _format_launch_config(session):
|
|
1029
1371
|
launch = session.get("launch") or {}
|
|
1030
1372
|
return "\n".join([
|
|
@@ -1033,6 +1375,7 @@ def _format_launch_config(session):
|
|
|
1033
1375
|
f"permission: {launch.get('permission') or 'default'}",
|
|
1034
1376
|
f"fast: {'on' if launch.get('fast') is True else 'off' if launch.get('fast') is False else 'default'}",
|
|
1035
1377
|
f"model: {launch.get('model') or 'default'}",
|
|
1378
|
+
f"priority: {launch.get('priority') if launch.get('priority') is not None else 'default'}",
|
|
1036
1379
|
])
|
|
1037
1380
|
|
|
1038
1381
|
|
|
@@ -1062,6 +1405,87 @@ def _resolve_bulk_launch_targets(parsed, service):
|
|
|
1062
1405
|
return targets
|
|
1063
1406
|
|
|
1064
1407
|
|
|
1408
|
+
def _reasoning_rank(value):
|
|
1409
|
+
order = {"low": 0, "medium": 1, "high": 2, "xhigh": 2, "max": 2}
|
|
1410
|
+
return order.get(str(value or "low").lower(), 0)
|
|
1411
|
+
|
|
1412
|
+
|
|
1413
|
+
def _session_reasoning_effort(session):
|
|
1414
|
+
launch = session.get("launch") or {}
|
|
1415
|
+
return (
|
|
1416
|
+
launch.get("reasoning_effort")
|
|
1417
|
+
or launch.get("reasoningEffort")
|
|
1418
|
+
or launch.get("power")
|
|
1419
|
+
or ("low" if launch.get("fast") is True else None)
|
|
1420
|
+
or "low"
|
|
1421
|
+
)
|
|
1422
|
+
|
|
1423
|
+
|
|
1424
|
+
def _session_selection_priority(session):
|
|
1425
|
+
launch = session.get("launch") or {}
|
|
1426
|
+
try:
|
|
1427
|
+
return int(launch.get("priority") or 0)
|
|
1428
|
+
except (TypeError, ValueError):
|
|
1429
|
+
return 0
|
|
1430
|
+
|
|
1431
|
+
|
|
1432
|
+
def _row_blocks_ready(row):
|
|
1433
|
+
if row.get("provider") in (PROVIDER_ANTIGRAVITY, PROVIDER_OLLAMA):
|
|
1434
|
+
auth = "n/a"
|
|
1435
|
+
else:
|
|
1436
|
+
auth = row.get("auth_status") or "unknown"
|
|
1437
|
+
if auth not in ("authenticated", "n/a"):
|
|
1438
|
+
return True
|
|
1439
|
+
available = row.get("available_pct")
|
|
1440
|
+
if available is not None:
|
|
1441
|
+
try:
|
|
1442
|
+
if float(available) <= 0:
|
|
1443
|
+
return True
|
|
1444
|
+
except (TypeError, ValueError):
|
|
1445
|
+
return True
|
|
1446
|
+
return False
|
|
1447
|
+
|
|
1448
|
+
|
|
1449
|
+
def _select_headless_session(ctx, provider, min_reasoning_effort=None, require_ready=False, force_refresh=False):
|
|
1450
|
+
sessions = [
|
|
1451
|
+
session for session in ctx["service"]["list_sessions"]()
|
|
1452
|
+
if session.get("provider") == provider and session.get("enabled", True) is not False
|
|
1453
|
+
]
|
|
1454
|
+
minimum = _reasoning_rank(min_reasoning_effort)
|
|
1455
|
+
sessions = [
|
|
1456
|
+
session for session in sessions
|
|
1457
|
+
if _reasoning_rank(_session_reasoning_effort(session)) >= minimum
|
|
1458
|
+
]
|
|
1459
|
+
rows = ctx["service"]["get_status_rows"](force_refresh=force_refresh)
|
|
1460
|
+
row_by_name = {row.get("session_name"): row for row in rows}
|
|
1461
|
+
candidates = []
|
|
1462
|
+
for session in sessions:
|
|
1463
|
+
row = row_by_name.get(session["name"], {})
|
|
1464
|
+
if require_ready and _row_blocks_ready(row):
|
|
1465
|
+
continue
|
|
1466
|
+
available = row.get("available_pct")
|
|
1467
|
+
try:
|
|
1468
|
+
available_sort = float(available) if available is not None else -1.0
|
|
1469
|
+
except (TypeError, ValueError):
|
|
1470
|
+
available_sort = -1.0
|
|
1471
|
+
cooldown_sort = 1 if available_sort > 0 else 0
|
|
1472
|
+
candidates.append({
|
|
1473
|
+
"session": session,
|
|
1474
|
+
"row": row,
|
|
1475
|
+
"sort_key": (
|
|
1476
|
+
-cooldown_sort,
|
|
1477
|
+
-available_sort,
|
|
1478
|
+
-_session_selection_priority(session),
|
|
1479
|
+
_reasoning_rank(_session_reasoning_effort(session)),
|
|
1480
|
+
session["name"],
|
|
1481
|
+
),
|
|
1482
|
+
})
|
|
1483
|
+
if not candidates:
|
|
1484
|
+
return None
|
|
1485
|
+
candidates.sort(key=lambda item: item["sort_key"])
|
|
1486
|
+
return candidates[0]
|
|
1487
|
+
|
|
1488
|
+
|
|
1065
1489
|
def _format_bulk_launch_summary(sessions):
|
|
1066
1490
|
names = [session["name"] for session in sessions]
|
|
1067
1491
|
if len(names) <= 8:
|
package/src/cli_render.py
CHANGED
|
@@ -75,10 +75,12 @@ def _style_pct(value, use_color=False):
|
|
|
75
75
|
text = _format_pct(value)
|
|
76
76
|
if value is None:
|
|
77
77
|
return _style(text, "2", use_color)
|
|
78
|
-
if value
|
|
78
|
+
if value <= 0:
|
|
79
79
|
return _style(text, "31", use_color)
|
|
80
80
|
if value <= 10:
|
|
81
81
|
return _style(text, "33", use_color)
|
|
82
|
+
if value > 80:
|
|
83
|
+
return _style(text, "96", use_color)
|
|
82
84
|
return _style(text, "32", use_color)
|
|
83
85
|
|
|
84
86
|
|