nexo-brain 7.26.0 → 7.27.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/.claude-plugin/plugin.json +1 -1
- package/README.md +5 -1
- package/bin/nexo-brain.js +2 -2
- package/package.json +1 -1
- package/src/agent_runner.py +3 -1
- package/src/call_model_raw.py +1 -1
- package/src/cli.py +22 -5
- package/src/client_preferences.py +90 -41
- package/src/client_sync.py +22 -11
- package/src/enforcement_classifier.py +1 -1
- package/src/local_context/api.py +58 -45
- package/src/model_defaults.json +4 -4
- package/src/model_defaults.py +4 -4
- package/src/plugins/lifecycle_events.py +9 -1
- package/src/resonance_tiers.json +5 -5
- package/src/runtime_power.py +40 -7
- package/src/scripts/nexo-cron-wrapper.sh +27 -5
- package/templates/launchagents/README.md +2 -2
- package/templates/launchagents/com.nexo.auto-close-sessions.plist +1 -1
- package/templates/launchagents/com.nexo.catchup.plist +3 -3
- package/templates/launchagents/com.nexo.cognitive-decay.plist +3 -3
- package/templates/launchagents/com.nexo.deep-sleep.plist +3 -3
- package/templates/launchagents/com.nexo.evolution.plist +3 -3
- package/templates/launchagents/com.nexo.followup-hygiene.plist +3 -3
- package/templates/launchagents/com.nexo.immune.plist +1 -1
- package/templates/launchagents/com.nexo.postmortem.plist +3 -3
- package/templates/launchagents/com.nexo.self-audit.plist +3 -3
- package/templates/launchagents/com.nexo.synthesis.plist +1 -1
- package/templates/launchagents/com.nexo.watchdog.plist +3 -3
- package/templates/nexo_helper.py +18 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nexo-brain",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.27.1",
|
|
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.
|
|
21
|
+
Version `7.27.1` is the current packaged-runtime line. Patch release over v7.27.0 - lifecycle stop calls now skip external provider session UUIDs safely, and provider runtime selection keeps chat plus automation aligned to the same Anthropic/OpenAI account.
|
|
22
|
+
|
|
23
|
+
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.
|
|
24
|
+
|
|
25
|
+
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.
|
|
22
26
|
|
|
23
27
|
Previously in `7.25.6`: patch release over v7.25.5 - existing Local Memory sidecar databases repair legacy root/exclusion columns before source-dependent indexes are created, and core background crons prefer the NEXO-managed Python runtime.
|
|
24
28
|
|
package/bin/nexo-brain.js
CHANGED
|
@@ -115,8 +115,8 @@ const PUBLIC_CONTRIBUTION_UPSTREAM = "wazionapps/nexo";
|
|
|
115
115
|
const MODEL_DEFAULTS_PATH = path.join(__dirname, "..", "src", "model_defaults.json");
|
|
116
116
|
function _loadModelDefaults() {
|
|
117
117
|
const fallback = {
|
|
118
|
-
claude_code: { model: "claude-opus-4-
|
|
119
|
-
codex: { model: "gpt-5.
|
|
118
|
+
claude_code: { model: "claude-opus-4-7[1m]", reasoning_effort: "max", display_name: "Opus 4.7 with 1M context" },
|
|
119
|
+
codex: { model: "gpt-5.5", reasoning_effort: "xhigh", display_name: "GPT-5.5 with max reasoning" },
|
|
120
120
|
};
|
|
121
121
|
try {
|
|
122
122
|
const raw = JSON.parse(fs.readFileSync(MODEL_DEFAULTS_PATH, "utf8"));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nexo-brain",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.27.1",
|
|
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/agent_runner.py
CHANGED
|
@@ -44,6 +44,7 @@ CLAUDE_LEGACY_MODEL_HINTS = {"opus", "sonnet"}
|
|
|
44
44
|
MODEL_PRICING_USD_PER_1M = {
|
|
45
45
|
# Pricing snapshot used only when the backend does not return explicit cost.
|
|
46
46
|
# Codex model names map to the current GPT-5 family pricing.
|
|
47
|
+
"gpt-5.5": {"input": 1.25, "cached_input": 0.125, "output": 10.0},
|
|
47
48
|
"gpt-5.4": {"input": 1.25, "cached_input": 0.125, "output": 10.0},
|
|
48
49
|
"gpt-5.4-mini": {"input": 0.25, "cached_input": 0.025, "output": 2.0},
|
|
49
50
|
}
|
|
@@ -66,7 +67,8 @@ def _canonical_pricing_model(model: str) -> str:
|
|
|
66
67
|
lowered = str(model or "").strip().lower()
|
|
67
68
|
lowered = lowered.split("[", 1)[0]
|
|
68
69
|
aliases = {
|
|
69
|
-
"gpt-5": "gpt-5.
|
|
70
|
+
"gpt-5": "gpt-5.5",
|
|
71
|
+
"gpt-5.5": "gpt-5.5",
|
|
70
72
|
"gpt-5.4": "gpt-5.4",
|
|
71
73
|
"gpt-5-mini": "gpt-5.4-mini",
|
|
72
74
|
"gpt-5.4-mini": "gpt-5.4-mini",
|
package/src/call_model_raw.py
CHANGED
|
@@ -260,7 +260,7 @@ def call_model_raw(
|
|
|
260
260
|
Parameters follow the Fase 2 plan doc 1 spec:
|
|
261
261
|
|
|
262
262
|
prompt — the user-role text (English or the model's default).
|
|
263
|
-
tier — resonance tier; default "muy_bajo" → Haiku / gpt-5.
|
|
263
|
+
tier — resonance tier; default "muy_bajo" → Haiku / gpt-5.5 low.
|
|
264
264
|
caller — resonance caller label. Must be registered in
|
|
265
265
|
resonance_map.SYSTEM_OWNED_CALLERS. Default
|
|
266
266
|
"enforcer_classifier".
|
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 {}
|
|
@@ -203,12 +203,15 @@ def _managed_codex_vendor_present(home: Path | None = None) -> bool:
|
|
|
203
203
|
managed_prefix / "node_modules" / "@openai" / "codex",
|
|
204
204
|
)
|
|
205
205
|
for package_root in package_roots:
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
206
|
+
vendor_roots = [package_root / "vendor"]
|
|
207
|
+
optional_root = package_root / "node_modules" / "@openai"
|
|
208
|
+
if optional_root.is_dir():
|
|
209
|
+
vendor_roots.extend(item / "vendor" for item in optional_root.glob("codex-*"))
|
|
210
|
+
existing_vendor_roots = [item for item in vendor_roots if item.exists()]
|
|
209
211
|
try:
|
|
210
|
-
|
|
211
|
-
|
|
212
|
+
for vendor_root in existing_vendor_roots:
|
|
213
|
+
if any(candidate.is_file() for candidate in vendor_root.rglob("bin/codex*")):
|
|
214
|
+
return True
|
|
212
215
|
except Exception:
|
|
213
216
|
continue
|
|
214
217
|
return False
|
|
@@ -370,6 +373,21 @@ def _provider_from_runtime_payload(value, key: str, fallback_client: str) -> str
|
|
|
370
373
|
return client_to_provider(fallback_client) or PROVIDER_ANTHROPIC
|
|
371
374
|
|
|
372
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
|
+
|
|
373
391
|
def normalize_provider_runtime(
|
|
374
392
|
value,
|
|
375
393
|
*,
|
|
@@ -385,19 +403,17 @@ def normalize_provider_runtime(
|
|
|
385
403
|
default_terminal_client,
|
|
386
404
|
)
|
|
387
405
|
raw_automation_provider = normalize_provider_key(raw.get("automation_provider")) if isinstance(raw, dict) else ""
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
)
|
|
397
|
-
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
|
+
):
|
|
398
414
|
automation_provider = PROVIDER_NONE
|
|
399
|
-
|
|
400
|
-
automation_provider =
|
|
415
|
+
else:
|
|
416
|
+
automation_provider = selected_provider
|
|
401
417
|
|
|
402
418
|
providers = defaults["providers"]
|
|
403
419
|
raw_providers = raw.get("providers") if isinstance(raw.get("providers"), dict) else {}
|
|
@@ -689,8 +705,19 @@ def normalize_client_preferences(
|
|
|
689
705
|
runtime_profiles = normalize_client_runtime_profiles(
|
|
690
706
|
schedule.get("client_runtime_profiles")
|
|
691
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)
|
|
692
719
|
provider_runtime = normalize_provider_runtime(
|
|
693
|
-
|
|
720
|
+
raw_provider_runtime,
|
|
694
721
|
default_terminal_client=default_terminal_client,
|
|
695
722
|
automation_backend=automation_backend,
|
|
696
723
|
automation_enabled=automation_enabled,
|
|
@@ -783,28 +810,53 @@ def apply_client_preferences(
|
|
|
783
810
|
else current.get("provider_runtime")
|
|
784
811
|
)
|
|
785
812
|
raw_provider_runtime = dict(raw_provider_runtime or {})
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
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
|
|
792
837
|
elif default_terminal_client is not None:
|
|
793
838
|
raw_provider_runtime["selected_chat_provider"] = client_to_provider(
|
|
794
839
|
merged["default_terminal_client"]
|
|
795
840
|
)
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
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"]
|
|
804
855
|
elif automation_backend is not None or automation_enabled is not None:
|
|
805
856
|
raw_provider_runtime["automation_provider"] = client_to_provider(
|
|
806
857
|
merged["automation_backend"]
|
|
807
858
|
) if merged["automation_backend"] != BACKEND_NONE else PROVIDER_NONE
|
|
859
|
+
raw_provider_runtime["automation_backend"] = merged["automation_backend"]
|
|
808
860
|
merged["provider_runtime"] = normalize_provider_runtime(
|
|
809
861
|
raw_provider_runtime,
|
|
810
862
|
default_terminal_client=merged["default_terminal_client"],
|
|
@@ -974,7 +1026,7 @@ def detect_installed_clients(user_home: str | os.PathLike[str] | None = None) ->
|
|
|
974
1026
|
|
|
975
1027
|
|
|
976
1028
|
def resolve_terminal_client(requested: str | None = None, *, preferences: dict | None = None) -> str:
|
|
977
|
-
normalized = preferences
|
|
1029
|
+
normalized = normalize_client_preferences(preferences) if preferences is not None else load_client_preferences()
|
|
978
1030
|
if requested is not None:
|
|
979
1031
|
interactive_clients = normalized["interactive_clients"]
|
|
980
1032
|
return normalize_default_terminal_client(requested, interactive_clients=interactive_clients)
|
|
@@ -985,7 +1037,7 @@ def resolve_terminal_client(requested: str | None = None, *, preferences: dict |
|
|
|
985
1037
|
|
|
986
1038
|
|
|
987
1039
|
def resolve_selected_chat_provider(preferences: dict | None = None) -> str:
|
|
988
|
-
normalized = preferences
|
|
1040
|
+
normalized = normalize_client_preferences(preferences) if preferences is not None else load_client_preferences()
|
|
989
1041
|
provider_runtime = normalize_provider_runtime(
|
|
990
1042
|
normalized.get("provider_runtime"),
|
|
991
1043
|
default_terminal_client=normalized.get("default_terminal_client", CLIENT_CLAUDE_CODE),
|
|
@@ -996,7 +1048,7 @@ def resolve_selected_chat_provider(preferences: dict | None = None) -> str:
|
|
|
996
1048
|
|
|
997
1049
|
|
|
998
1050
|
def resolve_automation_provider(preferences: dict | None = None) -> str:
|
|
999
|
-
normalized = preferences
|
|
1051
|
+
normalized = normalize_client_preferences(preferences) if preferences is not None else load_client_preferences()
|
|
1000
1052
|
provider_runtime = normalize_provider_runtime(
|
|
1001
1053
|
normalized.get("provider_runtime"),
|
|
1002
1054
|
default_terminal_client=normalized.get("default_terminal_client", CLIENT_CLAUDE_CODE),
|
|
@@ -1013,7 +1065,7 @@ def resolve_user_model(preferences: dict | None = None) -> str:
|
|
|
1013
1065
|
string. The value comes from the user's ``client_runtime_profiles`` for
|
|
1014
1066
|
their ``default_terminal_client`` (usually ``claude_code``).
|
|
1015
1067
|
"""
|
|
1016
|
-
normalized = preferences
|
|
1068
|
+
normalized = normalize_client_preferences(preferences) if preferences is not None else load_client_preferences()
|
|
1017
1069
|
client = normalize_default_terminal_client(
|
|
1018
1070
|
normalized["default_terminal_client"],
|
|
1019
1071
|
interactive_clients=normalized["interactive_clients"],
|
|
@@ -1023,11 +1075,8 @@ def resolve_user_model(preferences: dict | None = None) -> str:
|
|
|
1023
1075
|
|
|
1024
1076
|
|
|
1025
1077
|
def resolve_automation_backend(preferences: dict | None = None) -> str:
|
|
1026
|
-
normalized = preferences
|
|
1027
|
-
return
|
|
1028
|
-
normalized["automation_backend"],
|
|
1029
|
-
automation_enabled=normalized["automation_enabled"],
|
|
1030
|
-
)
|
|
1078
|
+
normalized = normalize_client_preferences(preferences) if preferences is not None else load_client_preferences()
|
|
1079
|
+
return normalized["automation_backend"]
|
|
1031
1080
|
|
|
1032
1081
|
|
|
1033
1082
|
def resolve_client_runtime_profile(
|
package/src/client_sync.py
CHANGED
|
@@ -65,10 +65,9 @@ except Exception:
|
|
|
65
65
|
}
|
|
66
66
|
|
|
67
67
|
def resolve_client_runtime_profile(client: str, preferences: dict | None = None) -> dict:
|
|
68
|
-
_default_model = "claude-opus-4-7[1m]"
|
|
69
68
|
defaults = {
|
|
70
|
-
"claude_code": {"model":
|
|
71
|
-
"codex": {"model":
|
|
69
|
+
"claude_code": {"model": "claude-opus-4-7[1m]", "reasoning_effort": "max"},
|
|
70
|
+
"codex": {"model": "gpt-5.5", "reasoning_effort": "xhigh"},
|
|
72
71
|
}
|
|
73
72
|
return dict(defaults.get(client, {}))
|
|
74
73
|
|
|
@@ -241,6 +240,15 @@ def _brain_bundle_root() -> Path:
|
|
|
241
240
|
return Path(__file__).resolve().parents[1]
|
|
242
241
|
|
|
243
242
|
|
|
243
|
+
def _codex_bundle_dir() -> Path:
|
|
244
|
+
explicit = str(os.environ.get("NEXO_CODEX_BUNDLE_DIR", "")).strip()
|
|
245
|
+
if explicit:
|
|
246
|
+
candidate = Path(explicit).expanduser()
|
|
247
|
+
if candidate.is_dir():
|
|
248
|
+
return candidate
|
|
249
|
+
return _brain_bundle_root() / "codex"
|
|
250
|
+
|
|
251
|
+
|
|
244
252
|
def _platform_slug() -> str:
|
|
245
253
|
if sys.platform.startswith("darwin"):
|
|
246
254
|
os_part = "darwin"
|
|
@@ -379,12 +387,15 @@ def _codex_vendor_present(managed_prefix: Path) -> bool:
|
|
|
379
387
|
managed_prefix / "node_modules" / "@openai" / "codex",
|
|
380
388
|
]
|
|
381
389
|
for package_root in package_roots:
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
390
|
+
vendor_roots = [package_root / "vendor"]
|
|
391
|
+
optional_root = package_root / "node_modules" / "@openai"
|
|
392
|
+
if optional_root.is_dir():
|
|
393
|
+
vendor_roots.extend(item / "vendor" for item in optional_root.glob("codex-*"))
|
|
394
|
+
existing_vendor_roots = [item for item in vendor_roots if item.exists()]
|
|
385
395
|
try:
|
|
386
|
-
|
|
387
|
-
|
|
396
|
+
for vendor_root in existing_vendor_roots:
|
|
397
|
+
if any(candidate.is_file() for candidate in vendor_root.rglob("bin/codex*")):
|
|
398
|
+
return True
|
|
388
399
|
except Exception:
|
|
389
400
|
continue
|
|
390
401
|
return False
|
|
@@ -659,7 +670,7 @@ def ensure_codex_installed(*, user_home: str | os.PathLike[str] | None = None) -
|
|
|
659
670
|
)
|
|
660
671
|
|
|
661
672
|
desktop_node, bundled_npm_cli = _bundled_npm_runtime()
|
|
662
|
-
bundle_dir =
|
|
673
|
+
bundle_dir = _codex_bundle_dir()
|
|
663
674
|
if desktop_managed and not (desktop_node and bundled_npm_cli):
|
|
664
675
|
vendor_installed = _install_codex_vendor_from_bundle(
|
|
665
676
|
bundle_dir=bundle_dir,
|
|
@@ -946,7 +957,7 @@ def _sync_codex_managed_config(
|
|
|
946
957
|
# Heal any pre-existing Claude model written by earlier NEXO versions.
|
|
947
958
|
existing_model = str(payload.get("model") or "").strip()
|
|
948
959
|
if existing_model and _looks_like_claude_model(existing_model):
|
|
949
|
-
payload["model"] = "gpt-5.
|
|
960
|
+
payload["model"] = "gpt-5.5"
|
|
950
961
|
|
|
951
962
|
# Only write a model from the runtime profile into Codex config if it
|
|
952
963
|
# looks like a Codex/OpenAI model. Claude models are invalid for Codex
|
|
@@ -956,7 +967,7 @@ def _sync_codex_managed_config(
|
|
|
956
967
|
payload["model"] = profile_model
|
|
957
968
|
elif profile_model:
|
|
958
969
|
# Fall back to a known-good Codex default to self-heal.
|
|
959
|
-
payload["model"] = "gpt-5.
|
|
970
|
+
payload["model"] = "gpt-5.5"
|
|
960
971
|
if "reasoning_effort" in runtime_profile:
|
|
961
972
|
payload["model_reasoning_effort"] = runtime_profile.get("reasoning_effort") or ""
|
|
962
973
|
|
|
@@ -121,7 +121,7 @@ def classify(
|
|
|
121
121
|
context: Optional extra context appended to the user message.
|
|
122
122
|
call_raw: Injection point for tests — defaults to call_model_raw.
|
|
123
123
|
cache: TTL cache instance. Tests can pass a fresh cache.
|
|
124
|
-
tier: Resonance tier. Default "muy_bajo" (Haiku / gpt-5.
|
|
124
|
+
tier: Resonance tier. Default "muy_bajo" (Haiku / gpt-5.5 low).
|
|
125
125
|
tristate: When True, return "yes" / "no" / "unknown" as strings.
|
|
126
126
|
"unknown" represents the conservative-parse-fallback path
|
|
127
127
|
which existing bool callers cannot distinguish from a real
|
package/src/local_context/api.py
CHANGED
|
@@ -383,18 +383,22 @@ def _effective_file_type_rule(conn, extension: str) -> dict:
|
|
|
383
383
|
|
|
384
384
|
|
|
385
385
|
def list_file_type_rules(*, readonly: bool = True) -> dict:
|
|
386
|
-
|
|
386
|
+
def _list() -> dict:
|
|
387
|
+
if readonly:
|
|
388
|
+
conn = _read_conn()
|
|
389
|
+
try:
|
|
390
|
+
rows = _list_file_type_rules_conn(conn)
|
|
391
|
+
finally:
|
|
392
|
+
_close_read_conn(conn)
|
|
393
|
+
return _shape_file_type_rules(rows)
|
|
394
|
+
|
|
387
395
|
conn = _conn()
|
|
388
396
|
seed_core_file_type_rules(conn)
|
|
389
397
|
conn.commit()
|
|
390
398
|
rows = _list_file_type_rules_conn(conn)
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
rows = _list_file_type_rules_conn(conn)
|
|
395
|
-
finally:
|
|
396
|
-
_close_read_conn(conn)
|
|
397
|
-
return _shape_file_type_rules(rows)
|
|
399
|
+
return _shape_file_type_rules(rows)
|
|
400
|
+
|
|
401
|
+
return _with_sqlite_busy_retry(_list)
|
|
398
402
|
|
|
399
403
|
|
|
400
404
|
def _purge_assets_by_extension(conn, extension: str) -> dict:
|
|
@@ -406,49 +410,58 @@ def _purge_assets_by_extension(conn, extension: str) -> dict:
|
|
|
406
410
|
|
|
407
411
|
|
|
408
412
|
def set_file_type_rule(extension: str, *, action: str = "extract", source: str = "user", priority: int | None = None, reason: str = "user") -> dict:
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
413
|
+
def _set() -> dict:
|
|
414
|
+
conn = _conn()
|
|
415
|
+
ext = _normalize_extension(extension)
|
|
416
|
+
if not ext:
|
|
417
|
+
return {"ok": False, "error": "extension_required"}
|
|
418
|
+
normalized_action = _normalize_file_type_action(action)
|
|
419
|
+
source_value = _normalize_source(source)
|
|
420
|
+
priority_value = int(priority if priority is not None else (82 if normalized_action == "extract" else 20 if normalized_action == "metadata" else 0))
|
|
421
|
+
timestamp = now()
|
|
422
|
+
conn.execute(
|
|
423
|
+
"""
|
|
424
|
+
INSERT INTO local_index_file_type_rules(extension, action, source, priority, reason, created_at, updated_at)
|
|
425
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
426
|
+
ON CONFLICT(extension, source) DO UPDATE SET
|
|
427
|
+
action=excluded.action,
|
|
428
|
+
priority=excluded.priority,
|
|
429
|
+
reason=excluded.reason,
|
|
430
|
+
updated_at=excluded.updated_at
|
|
431
|
+
""",
|
|
432
|
+
(ext, normalized_action, source_value, priority_value, reason, timestamp, timestamp),
|
|
433
|
+
)
|
|
434
|
+
cleanup = _purge_assets_by_extension(conn, ext) if normalized_action == "ignore" and source_value == "user" else {"assets": 0}
|
|
435
|
+
conn.commit()
|
|
436
|
+
log_event("info", "file_type_rule_set", "Local memory file type rule set", extension=ext, action=normalized_action, source=source_value, cleanup=cleanup)
|
|
437
|
+
return {"ok": True, "extension": ext, "action": normalized_action, "source": source_value, "priority": priority_value, "cleanup": cleanup}
|
|
438
|
+
|
|
439
|
+
return _with_sqlite_busy_retry(_set)
|
|
433
440
|
|
|
434
441
|
|
|
435
442
|
def remove_file_type_rule(extension: str, *, source: str = "user") -> dict:
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
+
def _remove() -> dict:
|
|
444
|
+
conn = _conn()
|
|
445
|
+
ext = _normalize_extension(extension)
|
|
446
|
+
source_value = _normalize_source(source)
|
|
447
|
+
conn.execute("DELETE FROM local_index_file_type_rules WHERE extension=? AND source=?", (ext, source_value))
|
|
448
|
+
conn.commit()
|
|
449
|
+
log_event("info", "file_type_rule_removed", "Local memory file type rule removed", extension=ext, source=source_value)
|
|
450
|
+
return {"ok": True, "extension": ext, "source": source_value}
|
|
451
|
+
|
|
452
|
+
return _with_sqlite_busy_retry(_remove)
|
|
443
453
|
|
|
444
454
|
|
|
445
455
|
def reset_file_type_rules() -> dict:
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
456
|
+
def _reset() -> dict:
|
|
457
|
+
conn = _conn()
|
|
458
|
+
deleted = int(conn.execute("DELETE FROM local_index_file_type_rules WHERE source='user'").rowcount or 0)
|
|
459
|
+
seeded = seed_core_file_type_rules(conn)
|
|
460
|
+
conn.commit()
|
|
461
|
+
log_event("info", "file_type_rules_reset", "Local memory user file type overrides reset", deleted=deleted)
|
|
462
|
+
return {"ok": True, "deleted": deleted, "core_rules": int(seeded.get("rules") or 0), "file_types": list_file_type_rules(readonly=False)}
|
|
463
|
+
|
|
464
|
+
return _with_sqlite_busy_retry(_reset)
|
|
452
465
|
|
|
453
466
|
|
|
454
467
|
def _file_type_action(conn, path: str | Path) -> str:
|
package/src/model_defaults.json
CHANGED
|
@@ -8,10 +8,10 @@
|
|
|
8
8
|
"previous_defaults": ["claude-opus-4-6[1m]"]
|
|
9
9
|
},
|
|
10
10
|
"codex": {
|
|
11
|
-
"model": "gpt-5.
|
|
11
|
+
"model": "gpt-5.5",
|
|
12
12
|
"reasoning_effort": "xhigh",
|
|
13
|
-
"display_name": "GPT-5.
|
|
14
|
-
"recommendation_version":
|
|
15
|
-
"previous_defaults": []
|
|
13
|
+
"display_name": "GPT-5.5 with max reasoning",
|
|
14
|
+
"recommendation_version": 2,
|
|
15
|
+
"previous_defaults": ["gpt-5.4"]
|
|
16
16
|
}
|
|
17
17
|
}
|
package/src/model_defaults.py
CHANGED
|
@@ -27,11 +27,11 @@ _FALLBACK: dict[str, Any] = {
|
|
|
27
27
|
"previous_defaults": ["claude-opus-4-6[1m]"],
|
|
28
28
|
},
|
|
29
29
|
"codex": {
|
|
30
|
-
"model": "gpt-5.
|
|
30
|
+
"model": "gpt-5.5",
|
|
31
31
|
"reasoning_effort": "xhigh",
|
|
32
|
-
"display_name": "GPT-5.
|
|
33
|
-
"recommendation_version":
|
|
34
|
-
"previous_defaults": [],
|
|
32
|
+
"display_name": "GPT-5.5 with max reasoning",
|
|
33
|
+
"recommendation_version": 2,
|
|
34
|
+
"previous_defaults": ["gpt-5.4"],
|
|
35
35
|
},
|
|
36
36
|
}
|
|
37
37
|
|
|
@@ -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/resonance_tiers.json
CHANGED
|
@@ -2,23 +2,23 @@
|
|
|
2
2
|
"tiers": {
|
|
3
3
|
"maximo": {
|
|
4
4
|
"claude_code": { "model": "claude-opus-4-7[1m]", "effort": "max" },
|
|
5
|
-
"codex": { "model": "gpt-5.
|
|
5
|
+
"codex": { "model": "gpt-5.5", "effort": "xhigh" }
|
|
6
6
|
},
|
|
7
7
|
"alto": {
|
|
8
8
|
"claude_code": { "model": "claude-opus-4-7[1m]", "effort": "xhigh" },
|
|
9
|
-
"codex": { "model": "gpt-5.
|
|
9
|
+
"codex": { "model": "gpt-5.5", "effort": "high" }
|
|
10
10
|
},
|
|
11
11
|
"medio": {
|
|
12
12
|
"claude_code": { "model": "claude-opus-4-7[1m]", "effort": "high" },
|
|
13
|
-
"codex": { "model": "gpt-5.
|
|
13
|
+
"codex": { "model": "gpt-5.5", "effort": "medium" }
|
|
14
14
|
},
|
|
15
15
|
"bajo": {
|
|
16
16
|
"claude_code": { "model": "claude-opus-4-7[1m]", "effort": "medium" },
|
|
17
|
-
"codex": { "model": "gpt-5.
|
|
17
|
+
"codex": { "model": "gpt-5.5", "effort": "low" }
|
|
18
18
|
},
|
|
19
19
|
"muy_bajo": {
|
|
20
20
|
"claude_code": { "model": "claude-haiku-4-5-20251001", "effort": "" },
|
|
21
|
-
"codex": { "model": "gpt-5.
|
|
21
|
+
"codex": { "model": "gpt-5.5", "effort": "low" }
|
|
22
22
|
}
|
|
23
23
|
},
|
|
24
24
|
"default_tier": "alto"
|
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,
|
|
@@ -37,7 +37,7 @@ Each plist calls `/usr/bin/python3`. If your Python 3 is elsewhere (e.g. Homebre
|
|
|
37
37
|
|
|
38
38
|
### 3. Create required directories
|
|
39
39
|
|
|
40
|
-
The agents write logs to `{{NEXO_HOME}}/logs/` and `{{NEXO_HOME}}/coordination/`. Create them if they do not exist:
|
|
40
|
+
The agents write logs to `{{NEXO_HOME}}/runtime/logs/` and `{{NEXO_HOME}}/coordination/`. Create them if they do not exist:
|
|
41
41
|
|
|
42
42
|
```bash
|
|
43
43
|
mkdir -p "$NEXO_HOME/logs" "$NEXO_HOME/coordination"
|
|
@@ -117,7 +117,7 @@ These agents power NEXO's learning and memory systems. Strongly recommended.
|
|
|
117
117
|
|
|
118
118
|
## Logs
|
|
119
119
|
|
|
120
|
-
All agents write stdout and stderr to files under `{{NEXO_HOME}}/logs/` (or `{{NEXO_HOME}}/coordination/` for the session-related ones). Check these first when debugging:
|
|
120
|
+
All agents write stdout and stderr to files under `{{NEXO_HOME}}/runtime/logs/` (or `{{NEXO_HOME}}/coordination/` for the session-related ones). Check these first when debugging:
|
|
121
121
|
|
|
122
122
|
```bash
|
|
123
123
|
tail -50 "$NEXO_HOME/logs/watchdog-stdout.log"
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
<key>ProgramArguments</key>
|
|
15
15
|
<array>
|
|
16
16
|
<string>/usr/bin/python3</string>
|
|
17
|
-
<string>{{NEXO_HOME}}/auto_close_sessions.py</string>
|
|
17
|
+
<string>{{NEXO_HOME}}/core/auto_close_sessions.py</string>
|
|
18
18
|
</array>
|
|
19
19
|
<key>StartInterval</key>
|
|
20
20
|
<integer>300</integer>
|
|
@@ -14,16 +14,16 @@
|
|
|
14
14
|
<key>ProgramArguments</key>
|
|
15
15
|
<array>
|
|
16
16
|
<string>/usr/bin/python3</string>
|
|
17
|
-
<string>{{NEXO_HOME}}/scripts/nexo-catchup.py</string>
|
|
17
|
+
<string>{{NEXO_HOME}}/core/scripts/nexo-catchup.py</string>
|
|
18
18
|
</array>
|
|
19
19
|
<key>RunAtLoad</key>
|
|
20
20
|
<true/>
|
|
21
21
|
<key>StartInterval</key>
|
|
22
22
|
<integer>900</integer>
|
|
23
23
|
<key>StandardOutPath</key>
|
|
24
|
-
<string>{{NEXO_HOME}}/logs/catchup-stdout.log</string>
|
|
24
|
+
<string>{{NEXO_HOME}}/runtime/logs/catchup-stdout.log</string>
|
|
25
25
|
<key>StandardErrorPath</key>
|
|
26
|
-
<string>{{NEXO_HOME}}/logs/catchup-stderr.log</string>
|
|
26
|
+
<string>{{NEXO_HOME}}/runtime/logs/catchup-stderr.log</string>
|
|
27
27
|
<key>EnvironmentVariables</key>
|
|
28
28
|
<dict>
|
|
29
29
|
<key>HOME</key>
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
<key>ProgramArguments</key>
|
|
15
15
|
<array>
|
|
16
16
|
<string>/usr/bin/python3</string>
|
|
17
|
-
<string>{{NEXO_HOME}}/scripts/nexo-cognitive-decay.py</string>
|
|
17
|
+
<string>{{NEXO_HOME}}/core/scripts/nexo-cognitive-decay.py</string>
|
|
18
18
|
</array>
|
|
19
19
|
<key>StartCalendarInterval</key>
|
|
20
20
|
<dict>
|
|
@@ -24,9 +24,9 @@
|
|
|
24
24
|
<integer>0</integer>
|
|
25
25
|
</dict>
|
|
26
26
|
<key>StandardOutPath</key>
|
|
27
|
-
<string>{{NEXO_HOME}}/logs/cognitive-decay-stdout.log</string>
|
|
27
|
+
<string>{{NEXO_HOME}}/runtime/logs/cognitive-decay-stdout.log</string>
|
|
28
28
|
<key>StandardErrorPath</key>
|
|
29
|
-
<string>{{NEXO_HOME}}/logs/cognitive-decay-stderr.log</string>
|
|
29
|
+
<string>{{NEXO_HOME}}/runtime/logs/cognitive-decay-stderr.log</string>
|
|
30
30
|
<key>RunAtLoad</key>
|
|
31
31
|
<false/>
|
|
32
32
|
<key>EnvironmentVariables</key>
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
<key>ProgramArguments</key>
|
|
16
16
|
<array>
|
|
17
17
|
<string>/bin/bash</string>
|
|
18
|
-
<string>{{NEXO_HOME}}/scripts/nexo-deep-sleep.sh</string>
|
|
18
|
+
<string>{{NEXO_HOME}}/core/scripts/nexo-deep-sleep.sh</string>
|
|
19
19
|
</array>
|
|
20
20
|
<key>StartCalendarInterval</key>
|
|
21
21
|
<dict>
|
|
@@ -25,9 +25,9 @@
|
|
|
25
25
|
<integer>30</integer>
|
|
26
26
|
</dict>
|
|
27
27
|
<key>StandardOutPath</key>
|
|
28
|
-
<string>{{NEXO_HOME}}/logs/deep-sleep-stdout.log</string>
|
|
28
|
+
<string>{{NEXO_HOME}}/runtime/logs/deep-sleep-stdout.log</string>
|
|
29
29
|
<key>StandardErrorPath</key>
|
|
30
|
-
<string>{{NEXO_HOME}}/logs/deep-sleep-stderr.log</string>
|
|
30
|
+
<string>{{NEXO_HOME}}/runtime/logs/deep-sleep-stderr.log</string>
|
|
31
31
|
<key>EnvironmentVariables</key>
|
|
32
32
|
<dict>
|
|
33
33
|
<key>PATH</key>
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
<key>ProgramArguments</key>
|
|
15
15
|
<array>
|
|
16
16
|
<string>/usr/bin/python3</string>
|
|
17
|
-
<string>{{NEXO_HOME}}/scripts/nexo-evolution-run.py</string>
|
|
17
|
+
<string>{{NEXO_HOME}}/core/scripts/nexo-evolution-run.py</string>
|
|
18
18
|
</array>
|
|
19
19
|
<key>StartCalendarInterval</key>
|
|
20
20
|
<dict>
|
|
@@ -26,9 +26,9 @@
|
|
|
26
26
|
<integer>0</integer>
|
|
27
27
|
</dict>
|
|
28
28
|
<key>StandardOutPath</key>
|
|
29
|
-
<string>{{NEXO_HOME}}/logs/evolution-stdout.log</string>
|
|
29
|
+
<string>{{NEXO_HOME}}/runtime/logs/evolution-stdout.log</string>
|
|
30
30
|
<key>StandardErrorPath</key>
|
|
31
|
-
<string>{{NEXO_HOME}}/logs/evolution-stderr.log</string>
|
|
31
|
+
<string>{{NEXO_HOME}}/runtime/logs/evolution-stderr.log</string>
|
|
32
32
|
<key>EnvironmentVariables</key>
|
|
33
33
|
<dict>
|
|
34
34
|
<key>HOME</key>
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
<key>ProgramArguments</key>
|
|
16
16
|
<array>
|
|
17
17
|
<string>/usr/bin/python3</string>
|
|
18
|
-
<string>{{NEXO_HOME}}/scripts/nexo-followup-hygiene.py</string>
|
|
18
|
+
<string>{{NEXO_HOME}}/core/scripts/nexo-followup-hygiene.py</string>
|
|
19
19
|
</array>
|
|
20
20
|
<key>StartCalendarInterval</key>
|
|
21
21
|
<dict>
|
|
@@ -27,9 +27,9 @@
|
|
|
27
27
|
<integer>0</integer>
|
|
28
28
|
</dict>
|
|
29
29
|
<key>StandardOutPath</key>
|
|
30
|
-
<string>{{NEXO_HOME}}/logs/followup-hygiene-stdout.log</string>
|
|
30
|
+
<string>{{NEXO_HOME}}/runtime/logs/followup-hygiene-stdout.log</string>
|
|
31
31
|
<key>StandardErrorPath</key>
|
|
32
|
-
<string>{{NEXO_HOME}}/logs/followup-hygiene-stderr.log</string>
|
|
32
|
+
<string>{{NEXO_HOME}}/runtime/logs/followup-hygiene-stderr.log</string>
|
|
33
33
|
<key>EnvironmentVariables</key>
|
|
34
34
|
<dict>
|
|
35
35
|
<key>HOME</key>
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
<key>ProgramArguments</key>
|
|
16
16
|
<array>
|
|
17
17
|
<string>/usr/bin/python3</string>
|
|
18
|
-
<string>{{NEXO_HOME}}/scripts/nexo-postmortem-consolidator.py</string>
|
|
18
|
+
<string>{{NEXO_HOME}}/core/scripts/nexo-postmortem-consolidator.py</string>
|
|
19
19
|
</array>
|
|
20
20
|
<key>RunAtLoad</key>
|
|
21
21
|
<false/>
|
|
@@ -27,9 +27,9 @@
|
|
|
27
27
|
<integer>30</integer>
|
|
28
28
|
</dict>
|
|
29
29
|
<key>StandardOutPath</key>
|
|
30
|
-
<string>{{NEXO_HOME}}/logs/postmortem-stdout.log</string>
|
|
30
|
+
<string>{{NEXO_HOME}}/runtime/logs/postmortem-stdout.log</string>
|
|
31
31
|
<key>StandardErrorPath</key>
|
|
32
|
-
<string>{{NEXO_HOME}}/logs/postmortem-stderr.log</string>
|
|
32
|
+
<string>{{NEXO_HOME}}/runtime/logs/postmortem-stderr.log</string>
|
|
33
33
|
<key>EnvironmentVariables</key>
|
|
34
34
|
<dict>
|
|
35
35
|
<key>HOME</key>
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
<key>ProgramArguments</key>
|
|
16
16
|
<array>
|
|
17
17
|
<string>/usr/bin/python3</string>
|
|
18
|
-
<string>{{NEXO_HOME}}/scripts/nexo-daily-self-audit.py</string>
|
|
18
|
+
<string>{{NEXO_HOME}}/core/scripts/nexo-daily-self-audit.py</string>
|
|
19
19
|
</array>
|
|
20
20
|
|
|
21
21
|
<key>StartCalendarInterval</key>
|
|
@@ -27,10 +27,10 @@
|
|
|
27
27
|
</dict>
|
|
28
28
|
|
|
29
29
|
<key>StandardOutPath</key>
|
|
30
|
-
<string>{{NEXO_HOME}}/logs/self-audit-stdout.log</string>
|
|
30
|
+
<string>{{NEXO_HOME}}/runtime/logs/self-audit-stdout.log</string>
|
|
31
31
|
|
|
32
32
|
<key>StandardErrorPath</key>
|
|
33
|
-
<string>{{NEXO_HOME}}/logs/self-audit-stderr.log</string>
|
|
33
|
+
<string>{{NEXO_HOME}}/runtime/logs/self-audit-stderr.log</string>
|
|
34
34
|
|
|
35
35
|
<key>EnvironmentVariables</key>
|
|
36
36
|
<dict>
|
|
@@ -14,16 +14,16 @@
|
|
|
14
14
|
<key>ProgramArguments</key>
|
|
15
15
|
<array>
|
|
16
16
|
<string>/bin/bash</string>
|
|
17
|
-
<string>{{NEXO_HOME}}/scripts/nexo-watchdog.sh</string>
|
|
17
|
+
<string>{{NEXO_HOME}}/core/scripts/nexo-watchdog.sh</string>
|
|
18
18
|
</array>
|
|
19
19
|
<key>StartInterval</key>
|
|
20
20
|
<integer>1800</integer>
|
|
21
21
|
<key>RunAtLoad</key>
|
|
22
22
|
<true/>
|
|
23
23
|
<key>StandardOutPath</key>
|
|
24
|
-
<string>{{NEXO_HOME}}/logs/watchdog-stdout.log</string>
|
|
24
|
+
<string>{{NEXO_HOME}}/runtime/logs/watchdog-stdout.log</string>
|
|
25
25
|
<key>StandardErrorPath</key>
|
|
26
|
-
<string>{{NEXO_HOME}}/logs/watchdog-stderr.log</string>
|
|
26
|
+
<string>{{NEXO_HOME}}/runtime/logs/watchdog-stderr.log</string>
|
|
27
27
|
<key>EnvironmentVariables</key>
|
|
28
28
|
<dict>
|
|
29
29
|
<key>PATH</key>
|
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:
|