nexo-brain 7.27.0 → 7.27.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +5 -1
- package/package.json +1 -1
- package/src/cli.py +22 -5
- package/src/client_preferences.py +82 -36
- package/src/plugins/lifecycle_events.py +9 -1
- package/src/runtime_power.py +40 -7
- package/src/scripts/nexo-cron-wrapper.sh +27 -5
- package/templates/nexo_helper.py +18 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nexo-brain",
|
|
3
|
-
"version": "7.27.
|
|
3
|
+
"version": "7.27.2",
|
|
4
4
|
"description": "Local cognitive runtime for Claude Code \u2014 persistent memory, overnight learning, doctor diagnostics, personal scripts, recovery-aware jobs, startup preflight, and optional dashboard/power helper.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "NEXO Brain",
|
package/README.md
CHANGED
|
@@ -18,7 +18,11 @@
|
|
|
18
18
|
|
|
19
19
|
[Watch the overview video](https://nexo-brain.com/watch/) · [Watch on YouTube](https://www.youtube.com/watch?v=i2lkGhKyVqI) · [Open the infographic](https://nexo-brain.com/assets/nexo-brain-infographic-v5.png)
|
|
20
20
|
|
|
21
|
-
Version `7.27.
|
|
21
|
+
Version `7.27.2` is the current packaged-runtime line. Patch release over v7.27.1 - legacy Codex preferences now migrate to OpenAI correctly, and the F0.6 config-folder transition keeps reading existing `config/schedule.json` before it is moved.
|
|
22
|
+
|
|
23
|
+
Previously in `7.27.1`: patch release over v7.27.0 - lifecycle stop calls skip external provider session UUIDs safely, and provider runtime selection keeps chat plus automation aligned to the same Anthropic/OpenAI account.
|
|
24
|
+
|
|
25
|
+
Previously in `7.27.0`: minor release over v7.26.0 - Codex-side defaults move to verified `gpt-5.5` resonance tiers, managed config healing follows that model family, and Local Memory file-type actions tolerate transient SQLite locks.
|
|
22
26
|
|
|
23
27
|
Previously in `7.26.0`: minor release over v7.25.6 - provider runtime parity lets Desktop-managed Brain choose Anthropic or OpenAI, keep provider metadata on sessions/automation/crons, and provision the managed OpenAI runtime from bundled Desktop resources.
|
|
24
28
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nexo-brain",
|
|
3
|
-
"version": "7.27.
|
|
3
|
+
"version": "7.27.2",
|
|
4
4
|
"mcpName": "io.github.wazionapps/nexo",
|
|
5
5
|
"description": "NEXO Brain — Shared brain for AI agents. Persistent memory, semantic RAG, natural forgetting, metacognitive guard, trust scoring, 150+ MCP tools. Works with Claude Code, Codex, Claude Desktop & any MCP client. 100% local, free.",
|
|
6
6
|
"homepage": "https://nexo-brain.com",
|
package/src/cli.py
CHANGED
|
@@ -2519,6 +2519,7 @@ def _preferences(args):
|
|
|
2519
2519
|
PROVIDER_KEYS,
|
|
2520
2520
|
PROVIDER_NONE,
|
|
2521
2521
|
load_client_preferences,
|
|
2522
|
+
normalize_provider_key,
|
|
2522
2523
|
normalize_default_terminal_client,
|
|
2523
2524
|
save_client_preferences,
|
|
2524
2525
|
)
|
|
@@ -2554,6 +2555,18 @@ def _preferences(args):
|
|
|
2554
2555
|
file=sys.stderr,
|
|
2555
2556
|
)
|
|
2556
2557
|
return 2
|
|
2558
|
+
normalized_chat_provider = normalize_provider_key(chat_provider)
|
|
2559
|
+
normalized_automation_provider = normalize_provider_key(automation_provider)
|
|
2560
|
+
if (
|
|
2561
|
+
normalized_chat_provider in PROVIDER_KEYS
|
|
2562
|
+
and normalized_automation_provider in PROVIDER_KEYS
|
|
2563
|
+
and normalized_chat_provider != normalized_automation_provider
|
|
2564
|
+
):
|
|
2565
|
+
print(
|
|
2566
|
+
"[NEXO] Chat and background automation must use the same provider.",
|
|
2567
|
+
file=sys.stderr,
|
|
2568
|
+
)
|
|
2569
|
+
return 2
|
|
2557
2570
|
|
|
2558
2571
|
if args.resonance:
|
|
2559
2572
|
tier = args.resonance.lower()
|
|
@@ -2668,12 +2681,16 @@ def _provider(args):
|
|
|
2668
2681
|
if target not in PROVIDER_KEYS:
|
|
2669
2682
|
print(f"[NEXO] Unknown provider '{target}'. Valid values: {', '.join(PROVIDER_KEYS)}.", file=sys.stderr)
|
|
2670
2683
|
return 2
|
|
2671
|
-
|
|
2672
|
-
|
|
2684
|
+
if getattr(args, "automation_only", False) or getattr(args, "chat_only", False):
|
|
2685
|
+
print(
|
|
2686
|
+
"[NEXO] Provider selection is unified: chat and background automation use the same provider.",
|
|
2687
|
+
file=sys.stderr,
|
|
2688
|
+
)
|
|
2689
|
+
return 2
|
|
2673
2690
|
save_client_preferences(
|
|
2674
|
-
selected_chat_provider=
|
|
2675
|
-
automation_provider=
|
|
2676
|
-
automation_user_override=True
|
|
2691
|
+
selected_chat_provider=target,
|
|
2692
|
+
automation_provider=target,
|
|
2693
|
+
automation_user_override=True,
|
|
2677
2694
|
)
|
|
2678
2695
|
prefs = load_client_preferences()
|
|
2679
2696
|
provider_runtime = prefs.get("provider_runtime") if isinstance(prefs.get("provider_runtime"), dict) else {}
|
|
@@ -373,6 +373,21 @@ def _provider_from_runtime_payload(value, key: str, fallback_client: str) -> str
|
|
|
373
373
|
return client_to_provider(fallback_client) or PROVIDER_ANTHROPIC
|
|
374
374
|
|
|
375
375
|
|
|
376
|
+
def _provider_runtime_is_unselected_default(value) -> bool:
|
|
377
|
+
if not isinstance(value, dict):
|
|
378
|
+
return False
|
|
379
|
+
selected = normalize_provider_key(value.get("selected_chat_provider"))
|
|
380
|
+
automation_provider = normalize_provider_key(value.get("automation_provider"))
|
|
381
|
+
automation_backend = normalize_backend_key(value.get("automation_backend"))
|
|
382
|
+
last_change = value.get("last_provider_change") if isinstance(value.get("last_provider_change"), dict) else {}
|
|
383
|
+
return (
|
|
384
|
+
selected == PROVIDER_ANTHROPIC
|
|
385
|
+
and automation_provider in {"", PROVIDER_ANTHROPIC}
|
|
386
|
+
and automation_backend in {"", CLIENT_CLAUDE_CODE}
|
|
387
|
+
and not last_change.get("changed_at")
|
|
388
|
+
)
|
|
389
|
+
|
|
390
|
+
|
|
376
391
|
def normalize_provider_runtime(
|
|
377
392
|
value,
|
|
378
393
|
*,
|
|
@@ -388,19 +403,17 @@ def normalize_provider_runtime(
|
|
|
388
403
|
default_terminal_client,
|
|
389
404
|
)
|
|
390
405
|
raw_automation_provider = normalize_provider_key(raw.get("automation_provider")) if isinstance(raw, dict) else ""
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
)
|
|
400
|
-
if not automation_enabled or automation_backend == BACKEND_NONE:
|
|
406
|
+
raw_has_automation_backend = isinstance(raw, dict) and "automation_backend" in raw
|
|
407
|
+
raw_automation_backend = normalize_backend_key(raw.get("automation_backend")) if raw_has_automation_backend else ""
|
|
408
|
+
if (
|
|
409
|
+
not automation_enabled
|
|
410
|
+
or automation_backend == BACKEND_NONE
|
|
411
|
+
or raw_automation_provider == PROVIDER_NONE
|
|
412
|
+
or (raw_has_automation_backend and raw_automation_backend == BACKEND_NONE)
|
|
413
|
+
):
|
|
401
414
|
automation_provider = PROVIDER_NONE
|
|
402
|
-
|
|
403
|
-
automation_provider =
|
|
415
|
+
else:
|
|
416
|
+
automation_provider = selected_provider
|
|
404
417
|
|
|
405
418
|
providers = defaults["providers"]
|
|
406
419
|
raw_providers = raw.get("providers") if isinstance(raw.get("providers"), dict) else {}
|
|
@@ -692,8 +705,19 @@ def normalize_client_preferences(
|
|
|
692
705
|
runtime_profiles = normalize_client_runtime_profiles(
|
|
693
706
|
schedule.get("client_runtime_profiles")
|
|
694
707
|
)
|
|
708
|
+
raw_provider_runtime = schedule.get("provider_runtime")
|
|
709
|
+
legacy_terminal_provider = client_to_provider(default_terminal_client)
|
|
710
|
+
if (
|
|
711
|
+
legacy_terminal_provider in PROVIDER_KEYS
|
|
712
|
+
and legacy_terminal_provider != PROVIDER_ANTHROPIC
|
|
713
|
+
and _provider_runtime_is_unselected_default(raw_provider_runtime)
|
|
714
|
+
):
|
|
715
|
+
raw_provider_runtime = dict(raw_provider_runtime or {})
|
|
716
|
+
raw_provider_runtime["selected_chat_provider"] = legacy_terminal_provider
|
|
717
|
+
raw_provider_runtime["automation_provider"] = legacy_terminal_provider
|
|
718
|
+
raw_provider_runtime["automation_backend"] = provider_to_client(legacy_terminal_provider)
|
|
695
719
|
provider_runtime = normalize_provider_runtime(
|
|
696
|
-
|
|
720
|
+
raw_provider_runtime,
|
|
697
721
|
default_terminal_client=default_terminal_client,
|
|
698
722
|
automation_backend=automation_backend,
|
|
699
723
|
automation_enabled=automation_enabled,
|
|
@@ -786,28 +810,53 @@ def apply_client_preferences(
|
|
|
786
810
|
else current.get("provider_runtime")
|
|
787
811
|
)
|
|
788
812
|
raw_provider_runtime = dict(raw_provider_runtime or {})
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
813
|
+
explicit_selected_provider = normalize_provider_key(selected_chat_provider)
|
|
814
|
+
explicit_automation_provider = normalize_provider_key(automation_provider)
|
|
815
|
+
backend_provider = (
|
|
816
|
+
client_to_provider(merged["automation_backend"])
|
|
817
|
+
if merged["automation_backend"] != BACKEND_NONE
|
|
818
|
+
else ""
|
|
819
|
+
)
|
|
820
|
+
terminal_provider = (
|
|
821
|
+
client_to_provider(merged["default_terminal_client"])
|
|
822
|
+
if default_terminal_client is not None
|
|
823
|
+
else ""
|
|
824
|
+
)
|
|
825
|
+
single_provider = (
|
|
826
|
+
(explicit_selected_provider if explicit_selected_provider != PROVIDER_NONE else "")
|
|
827
|
+
or (explicit_automation_provider if explicit_automation_provider != PROVIDER_NONE else "")
|
|
828
|
+
or (backend_provider if automation_backend is not None else "")
|
|
829
|
+
or terminal_provider
|
|
830
|
+
)
|
|
831
|
+
if single_provider in PROVIDER_KEYS:
|
|
832
|
+
raw_provider_runtime["selected_chat_provider"] = single_provider
|
|
833
|
+
selected_client = provider_to_client(single_provider)
|
|
834
|
+
if selected_client:
|
|
835
|
+
merged["interactive_clients"][selected_client] = True
|
|
836
|
+
merged["default_terminal_client"] = selected_client
|
|
795
837
|
elif default_terminal_client is not None:
|
|
796
838
|
raw_provider_runtime["selected_chat_provider"] = client_to_provider(
|
|
797
839
|
merged["default_terminal_client"]
|
|
798
840
|
)
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
841
|
+
|
|
842
|
+
automation_off = (
|
|
843
|
+
merged["automation_enabled"] is False
|
|
844
|
+
or merged["automation_backend"] == BACKEND_NONE
|
|
845
|
+
or explicit_automation_provider == PROVIDER_NONE
|
|
846
|
+
)
|
|
847
|
+
if automation_off:
|
|
848
|
+
raw_provider_runtime["automation_provider"] = PROVIDER_NONE
|
|
849
|
+
raw_provider_runtime["automation_backend"] = BACKEND_NONE
|
|
850
|
+
merged["automation_backend"] = BACKEND_NONE
|
|
851
|
+
elif single_provider in PROVIDER_KEYS:
|
|
852
|
+
raw_provider_runtime["automation_provider"] = single_provider
|
|
853
|
+
merged["automation_backend"] = provider_to_client(single_provider)
|
|
854
|
+
raw_provider_runtime["automation_backend"] = merged["automation_backend"]
|
|
807
855
|
elif automation_backend is not None or automation_enabled is not None:
|
|
808
856
|
raw_provider_runtime["automation_provider"] = client_to_provider(
|
|
809
857
|
merged["automation_backend"]
|
|
810
858
|
) if merged["automation_backend"] != BACKEND_NONE else PROVIDER_NONE
|
|
859
|
+
raw_provider_runtime["automation_backend"] = merged["automation_backend"]
|
|
811
860
|
merged["provider_runtime"] = normalize_provider_runtime(
|
|
812
861
|
raw_provider_runtime,
|
|
813
862
|
default_terminal_client=merged["default_terminal_client"],
|
|
@@ -977,7 +1026,7 @@ def detect_installed_clients(user_home: str | os.PathLike[str] | None = None) ->
|
|
|
977
1026
|
|
|
978
1027
|
|
|
979
1028
|
def resolve_terminal_client(requested: str | None = None, *, preferences: dict | None = None) -> str:
|
|
980
|
-
normalized = preferences
|
|
1029
|
+
normalized = normalize_client_preferences(preferences) if preferences is not None else load_client_preferences()
|
|
981
1030
|
if requested is not None:
|
|
982
1031
|
interactive_clients = normalized["interactive_clients"]
|
|
983
1032
|
return normalize_default_terminal_client(requested, interactive_clients=interactive_clients)
|
|
@@ -988,7 +1037,7 @@ def resolve_terminal_client(requested: str | None = None, *, preferences: dict |
|
|
|
988
1037
|
|
|
989
1038
|
|
|
990
1039
|
def resolve_selected_chat_provider(preferences: dict | None = None) -> str:
|
|
991
|
-
normalized = preferences
|
|
1040
|
+
normalized = normalize_client_preferences(preferences) if preferences is not None else load_client_preferences()
|
|
992
1041
|
provider_runtime = normalize_provider_runtime(
|
|
993
1042
|
normalized.get("provider_runtime"),
|
|
994
1043
|
default_terminal_client=normalized.get("default_terminal_client", CLIENT_CLAUDE_CODE),
|
|
@@ -999,7 +1048,7 @@ def resolve_selected_chat_provider(preferences: dict | None = None) -> str:
|
|
|
999
1048
|
|
|
1000
1049
|
|
|
1001
1050
|
def resolve_automation_provider(preferences: dict | None = None) -> str:
|
|
1002
|
-
normalized = preferences
|
|
1051
|
+
normalized = normalize_client_preferences(preferences) if preferences is not None else load_client_preferences()
|
|
1003
1052
|
provider_runtime = normalize_provider_runtime(
|
|
1004
1053
|
normalized.get("provider_runtime"),
|
|
1005
1054
|
default_terminal_client=normalized.get("default_terminal_client", CLIENT_CLAUDE_CODE),
|
|
@@ -1016,7 +1065,7 @@ def resolve_user_model(preferences: dict | None = None) -> str:
|
|
|
1016
1065
|
string. The value comes from the user's ``client_runtime_profiles`` for
|
|
1017
1066
|
their ``default_terminal_client`` (usually ``claude_code``).
|
|
1018
1067
|
"""
|
|
1019
|
-
normalized = preferences
|
|
1068
|
+
normalized = normalize_client_preferences(preferences) if preferences is not None else load_client_preferences()
|
|
1020
1069
|
client = normalize_default_terminal_client(
|
|
1021
1070
|
normalized["default_terminal_client"],
|
|
1022
1071
|
interactive_clients=normalized["interactive_clients"],
|
|
@@ -1026,11 +1075,8 @@ def resolve_user_model(preferences: dict | None = None) -> str:
|
|
|
1026
1075
|
|
|
1027
1076
|
|
|
1028
1077
|
def resolve_automation_backend(preferences: dict | None = None) -> str:
|
|
1029
|
-
normalized = preferences
|
|
1030
|
-
return
|
|
1031
|
-
normalized["automation_backend"],
|
|
1032
|
-
automation_enabled=normalized["automation_enabled"],
|
|
1033
|
-
)
|
|
1078
|
+
normalized = normalize_client_preferences(preferences) if preferences is not None else load_client_preferences()
|
|
1079
|
+
return normalized["automation_backend"]
|
|
1034
1080
|
|
|
1035
1081
|
|
|
1036
1082
|
def resolve_client_runtime_profile(
|
|
@@ -218,7 +218,15 @@ def handle_nexo_lifecycle_stop_nexo_session(
|
|
|
218
218
|
from tools_sessions import handle_stop
|
|
219
219
|
|
|
220
220
|
raw_sid = str(sid or "").strip()
|
|
221
|
-
stop_sids = lifecycle_events.registered_stop_session_ids(raw_sid)
|
|
221
|
+
stop_sids = lifecycle_events.registered_stop_session_ids(raw_sid)
|
|
222
|
+
if not stop_sids:
|
|
223
|
+
return json.dumps({
|
|
224
|
+
"status": "ok",
|
|
225
|
+
"sid": raw_sid,
|
|
226
|
+
"stopped_session_ids": [],
|
|
227
|
+
"skipped": True,
|
|
228
|
+
"reason": "no-registered-nexo-session",
|
|
229
|
+
}, ensure_ascii=False)
|
|
222
230
|
messages = [handle_stop(stop_sid) for stop_sid in stop_sids]
|
|
223
231
|
return json.dumps({
|
|
224
232
|
"status": "ok",
|
package/src/runtime_power.py
CHANGED
|
@@ -337,6 +337,23 @@ def _schedule_defaults() -> dict:
|
|
|
337
337
|
"default_terminal_client": "claude_code",
|
|
338
338
|
"automation_enabled": True,
|
|
339
339
|
"automation_backend": "claude_code",
|
|
340
|
+
"provider_runtime": {
|
|
341
|
+
"schema_version": 1,
|
|
342
|
+
"selected_chat_provider": "anthropic",
|
|
343
|
+
"automation_provider": "anthropic",
|
|
344
|
+
"automation_backend": "claude_code",
|
|
345
|
+
"providers": {
|
|
346
|
+
"anthropic": {"client": "claude_code"},
|
|
347
|
+
"openai": {"client": "codex"},
|
|
348
|
+
},
|
|
349
|
+
"fallback_policy": {"chat": "ask", "automation": "fail_closed"},
|
|
350
|
+
"last_provider_change": {
|
|
351
|
+
"changed_at": None,
|
|
352
|
+
"from_provider": None,
|
|
353
|
+
"to_provider": None,
|
|
354
|
+
"source": None,
|
|
355
|
+
},
|
|
356
|
+
},
|
|
340
357
|
"client_runtime_profiles": {
|
|
341
358
|
"claude_code": {
|
|
342
359
|
"model": DEFAULT_CLAUDE_CODE_MODEL,
|
|
@@ -361,16 +378,32 @@ def _schedule_defaults() -> dict:
|
|
|
361
378
|
}
|
|
362
379
|
|
|
363
380
|
|
|
364
|
-
def
|
|
365
|
-
if not SCHEDULE_FILE.is_file():
|
|
366
|
-
return _schedule_defaults()
|
|
381
|
+
def _load_schedule_payload(path: Path) -> dict | None:
|
|
367
382
|
try:
|
|
368
|
-
data = json.loads(
|
|
383
|
+
data = json.loads(path.read_text())
|
|
369
384
|
except Exception:
|
|
385
|
+
return None
|
|
386
|
+
return data if isinstance(data, dict) else None
|
|
387
|
+
|
|
388
|
+
|
|
389
|
+
def _legacy_schedule_file() -> Path:
|
|
390
|
+
return NEXO_HOME / "config" / "schedule.json"
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
def load_schedule_config() -> dict:
|
|
394
|
+
data: dict | None = None
|
|
395
|
+
if SCHEDULE_FILE.is_file():
|
|
396
|
+
data = _load_schedule_payload(SCHEDULE_FILE)
|
|
397
|
+
else:
|
|
398
|
+
legacy_file = _legacy_schedule_file()
|
|
399
|
+
if legacy_file != SCHEDULE_FILE and legacy_file.is_file():
|
|
400
|
+
data = _load_schedule_payload(legacy_file)
|
|
401
|
+
if data is None:
|
|
370
402
|
return _schedule_defaults()
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
403
|
+
defaults = _schedule_defaults()
|
|
404
|
+
if "provider_runtime" not in data:
|
|
405
|
+
defaults.pop("provider_runtime", None)
|
|
406
|
+
merged = defaults
|
|
374
407
|
merged.update(data)
|
|
375
408
|
merged.setdefault("processes", {})
|
|
376
409
|
return merged
|
|
@@ -63,11 +63,33 @@ for candidate in (
|
|
|
63
63
|
except Exception:
|
|
64
64
|
continue
|
|
65
65
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
66
|
+
for import_root in (
|
|
67
|
+
nexo_home / "core",
|
|
68
|
+
nexo_home / "core" / "src",
|
|
69
|
+
nexo_home / "src",
|
|
70
|
+
):
|
|
71
|
+
if import_root.exists():
|
|
72
|
+
sys.path.insert(0, str(import_root))
|
|
73
|
+
try:
|
|
74
|
+
from client_preferences import normalize_client_preferences # type: ignore
|
|
75
|
+
prefs = normalize_client_preferences(schedule)
|
|
76
|
+
provider_runtime = prefs.get("provider_runtime") if isinstance(prefs.get("provider_runtime"), dict) else {}
|
|
77
|
+
provider = str(provider_runtime.get("automation_provider") or "none").strip().lower()
|
|
78
|
+
backend = str(prefs.get("automation_backend") or provider_runtime.get("automation_backend") or "none").strip().lower()
|
|
79
|
+
except Exception:
|
|
80
|
+
provider_runtime = schedule.get("provider_runtime") if isinstance(schedule.get("provider_runtime"), dict) else {}
|
|
81
|
+
selected = str(provider_runtime.get("selected_chat_provider") or "").strip().lower()
|
|
82
|
+
backend_raw = str(provider_runtime.get("automation_backend") or schedule.get("automation_backend") or "claude_code").strip().lower()
|
|
83
|
+
provider_raw = str(provider_runtime.get("automation_provider") or "").strip().lower()
|
|
84
|
+
automation_enabled = schedule.get("automation_enabled", True) is not False
|
|
85
|
+
backend_map = {"claude_code": "anthropic", "codex": "openai", "none": "none"}
|
|
86
|
+
provider_map = {"anthropic": "anthropic", "openai": "openai", "none": "none"}
|
|
87
|
+
if (not automation_enabled) or backend_raw in {"none", "off", "disabled", "false", "0"} or provider_raw in {"none", "off", "disabled", "false", "0"}:
|
|
88
|
+
provider = "none"
|
|
89
|
+
backend = "none"
|
|
90
|
+
else:
|
|
91
|
+
provider = provider_map.get(selected) or provider_map.get(provider_raw) or backend_map.get(backend_raw, "anthropic")
|
|
92
|
+
backend = {"anthropic": "claude_code", "openai": "codex", "none": "none"}.get(provider, "claude_code")
|
|
71
93
|
snapshot = {
|
|
72
94
|
"selected_chat_provider": provider_runtime.get("selected_chat_provider") or "",
|
|
73
95
|
"automation_provider": provider,
|
package/templates/nexo_helper.py
CHANGED
|
@@ -76,7 +76,24 @@ def _resolve_core_runner(script_name: str) -> Path:
|
|
|
76
76
|
|
|
77
77
|
def _resolve_automation_backend() -> str:
|
|
78
78
|
data = _load_schedule()
|
|
79
|
-
|
|
79
|
+
provider_runtime = data.get("provider_runtime") if isinstance(data.get("provider_runtime"), dict) else {}
|
|
80
|
+
selected = str(provider_runtime.get("selected_chat_provider") or "").strip().lower()
|
|
81
|
+
backend_raw = str(provider_runtime.get("automation_backend") or data.get("automation_backend") or "claude_code").strip().lower()
|
|
82
|
+
provider_raw = str(provider_runtime.get("automation_provider") or "").strip().lower()
|
|
83
|
+
automation_enabled = data.get("automation_enabled", True) is not False
|
|
84
|
+
if (
|
|
85
|
+
not automation_enabled
|
|
86
|
+
or backend_raw in {"none", "off", "disabled", "false", "0"}
|
|
87
|
+
or provider_raw in {"none", "off", "disabled", "false", "0"}
|
|
88
|
+
):
|
|
89
|
+
return "none"
|
|
90
|
+
provider = (
|
|
91
|
+
{"anthropic": "anthropic", "openai": "openai"}.get(selected)
|
|
92
|
+
or {"anthropic": "anthropic", "openai": "openai"}.get(provider_raw)
|
|
93
|
+
or {"claude_code": "anthropic", "codex": "openai"}.get(backend_raw)
|
|
94
|
+
or "anthropic"
|
|
95
|
+
)
|
|
96
|
+
return {"anthropic": "claude_code", "openai": "codex"}.get(provider, "claude_code")
|
|
80
97
|
|
|
81
98
|
|
|
82
99
|
def _load_bootstrap_prompt() -> str:
|