nexo-brain 7.30.15 → 7.30.17
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 +3 -1
- package/bin/nexo-brain.js +177 -72
- package/package.json +1 -1
- package/src/auto_update.py +120 -0
- package/src/cognitive/__init__.py +2 -0
- package/src/cognitive/_core.py +618 -48
- package/src/cognitive/_decay.py +30 -10
- package/src/cognitive/_ingest.py +133 -38
- package/src/cognitive/_memory.py +52 -11
- package/src/cognitive/_search.py +39 -15
- package/src/cognitive/_trust.py +13 -3
- package/src/hnsw_index.py +33 -5
- package/src/model_warmup.py +15 -11
- package/src/scripts/nexo-email-monitor.py +5 -71
- package/src/scripts/nexo-send-reply.py +2 -15
- package/src/server.py +8 -0
- package/templates/core-prompts/email-monitor.md +1 -6
- package/tool-enforcement-map.json +15 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nexo-brain",
|
|
3
|
-
"version": "7.30.
|
|
3
|
+
"version": "7.30.17",
|
|
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,9 @@
|
|
|
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.30.
|
|
21
|
+
Version `7.30.17` is the current packaged-runtime line. Patch release over v7.30.16 - F0.6 repairs promoted helper imports for personal scripts by adding a core-backed compatibility shim without duplicating the script catalog.
|
|
22
|
+
|
|
23
|
+
Previously in `7.30.16`: patch release over v7.30.14 - Desktop diagnostics can read embedding migration status without warming models, and the coordinated Desktop update path is covered for bundled model verification and obsolete managed model cleanup.
|
|
22
24
|
|
|
23
25
|
Previously in `7.30.14`: patch release over v7.30.13 - support tickets, provider capability discovery, task-close rearming, internal audit followups, and the memory-observation watchdog are aligned for Desktop-managed agents.
|
|
24
26
|
|
package/bin/nexo-brain.js
CHANGED
|
@@ -666,6 +666,11 @@ function shouldSkipModelWarmup() {
|
|
|
666
666
|
return ["1", "true", "yes", "on"].includes(flag);
|
|
667
667
|
}
|
|
668
668
|
|
|
669
|
+
function shouldInstallLocalClassifierWarmupDeps() {
|
|
670
|
+
const flag = String(process.env.NEXO_LOCAL_CLASSIFIER || "").trim().toLowerCase();
|
|
671
|
+
return ["1", "true", "on", "auto"].includes(flag);
|
|
672
|
+
}
|
|
673
|
+
|
|
669
674
|
function resolveSystemPython() {
|
|
670
675
|
return run("which python3") || run("which python") || "python3";
|
|
671
676
|
}
|
|
@@ -705,13 +710,15 @@ function installWarmupPythonDependencies(pythonPath, { quiet = false, installRun
|
|
|
705
710
|
}
|
|
706
711
|
}
|
|
707
712
|
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
713
|
+
if (shouldInstallLocalClassifierWarmupDeps()) {
|
|
714
|
+
const classifierResult = spawnSync(
|
|
715
|
+
pythonPath,
|
|
716
|
+
[...pipCommon, ...WARMUP_PIP_PACKAGES],
|
|
717
|
+
{ stdio, timeout: WARMUP_TIMEOUT_MS }
|
|
718
|
+
);
|
|
719
|
+
if (classifierResult.status !== 0) {
|
|
720
|
+
throw new Error("failed to install local classifier dependencies for model warmup");
|
|
721
|
+
}
|
|
715
722
|
}
|
|
716
723
|
}
|
|
717
724
|
|
|
@@ -766,6 +773,157 @@ function runDesktopAwareModelWarmup(pythonPath, nexoHome = NEXO_HOME, options =
|
|
|
766
773
|
runMandatoryModelWarmup(pythonPath, nexoHome, options);
|
|
767
774
|
}
|
|
768
775
|
|
|
776
|
+
function slugifyLocalModelName(value) {
|
|
777
|
+
return String(value || "").trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
function readManagedModelLock(dir) {
|
|
781
|
+
try {
|
|
782
|
+
const lockPath = path.join(dir, ".nexo-model-lock.json");
|
|
783
|
+
if (!fs.existsSync(lockPath)) return null;
|
|
784
|
+
const payload = JSON.parse(fs.readFileSync(lockPath, "utf8"));
|
|
785
|
+
return payload && typeof payload === "object" ? payload : null;
|
|
786
|
+
} catch (_) {
|
|
787
|
+
return null;
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
function isManagedModelRevisionDir(dir, { slug = "", revision = "" } = {}) {
|
|
792
|
+
const lock = readManagedModelLock(dir);
|
|
793
|
+
if (!lock) return false;
|
|
794
|
+
if (!lock.name || !lock.revision || (!lock.model_id && !lock.source_repo)) return false;
|
|
795
|
+
if (slug && slugifyLocalModelName(lock.name) !== slug) return false;
|
|
796
|
+
if (revision && String(lock.revision || "") !== String(revision || "")) return false;
|
|
797
|
+
return true;
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
function sha256File(filePath) {
|
|
801
|
+
return crypto.createHash("sha256").update(fs.readFileSync(filePath)).digest("hex");
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
function cleanupObsoleteRuntimeLlmModels(runtimeModelsDir, manifest, { reason = "install" } = {}) {
|
|
805
|
+
if (String(process.env.NEXO_KEEP_OBSOLETE_LLM_MODELS || "").trim() === "1") {
|
|
806
|
+
log(` Keeping obsolete LLM models by NEXO_KEEP_OBSOLETE_LLM_MODELS=1 (${reason}).`);
|
|
807
|
+
return [];
|
|
808
|
+
}
|
|
809
|
+
if (!fs.existsSync(runtimeModelsDir)) return [];
|
|
810
|
+
|
|
811
|
+
const allowed = new Map();
|
|
812
|
+
for (const spec of manifest.models || []) {
|
|
813
|
+
const slug = slugifyLocalModelName(spec.name || "");
|
|
814
|
+
const revision = String(spec.revision || "").trim();
|
|
815
|
+
if (!slug || !revision) continue;
|
|
816
|
+
if (!allowed.has(slug)) allowed.set(slug, new Set());
|
|
817
|
+
allowed.get(slug).add(revision);
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
const removed = [];
|
|
821
|
+
for (const slugEntry of fs.readdirSync(runtimeModelsDir, { withFileTypes: true })) {
|
|
822
|
+
if (!slugEntry.isDirectory()) continue;
|
|
823
|
+
const slug = slugEntry.name;
|
|
824
|
+
if (slug.startsWith(".")) continue;
|
|
825
|
+
if (slug === "_hf-cache") continue;
|
|
826
|
+
const slugDir = path.join(runtimeModelsDir, slug);
|
|
827
|
+
const allowedRevisions = allowed.get(slug) || new Set();
|
|
828
|
+
for (const revisionEntry of fs.readdirSync(slugDir, { withFileTypes: true })) {
|
|
829
|
+
if (!revisionEntry.isDirectory()) continue;
|
|
830
|
+
const revision = revisionEntry.name;
|
|
831
|
+
const revisionDir = path.join(slugDir, revision);
|
|
832
|
+
if (allowedRevisions.has(revision)) continue;
|
|
833
|
+
if (!isManagedModelRevisionDir(revisionDir, { slug, revision })) continue;
|
|
834
|
+
fs.rmSync(revisionDir, { recursive: true, force: true });
|
|
835
|
+
removed.push(path.relative(runtimeModelsDir, revisionDir));
|
|
836
|
+
}
|
|
837
|
+
try {
|
|
838
|
+
if (fs.readdirSync(slugDir).length === 0) fs.rmdirSync(slugDir);
|
|
839
|
+
} catch (_) {}
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
if (removed.length > 0) {
|
|
843
|
+
log(` Removed ${removed.length} obsolete managed LLM model revision(s) (${reason}).`);
|
|
844
|
+
}
|
|
845
|
+
return removed;
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
function copyBundledLlmModelsToRuntime(nexoHome = NEXO_HOME, {
|
|
849
|
+
reason = "install",
|
|
850
|
+
bundledModelsDir = path.join(__dirname, "..", "models"),
|
|
851
|
+
manifestPath = path.join(__dirname, "..", "src", "local_model_manifest.json"),
|
|
852
|
+
} = {}) {
|
|
853
|
+
// OFFLINE-FIRST: copy bundled LLM models to runtime/models BEFORE warmup,
|
|
854
|
+
// so fastembed finds them locally and skips HuggingFace downloads.
|
|
855
|
+
// Bundle layout: resources/brain-bundle/models/<source-repo-name>/<all files>.
|
|
856
|
+
// Target layout: <NEXO_HOME>/runtime/models/<spec.name slugified>/<revision>/<files>.
|
|
857
|
+
// We map by source_repo basename to match local_model_manifest.json.
|
|
858
|
+
if (!fs.existsSync(bundledModelsDir)) return 0;
|
|
859
|
+
try {
|
|
860
|
+
const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf8"));
|
|
861
|
+
const runtimeModelsDir = path.join(nexoHome, "runtime", "models");
|
|
862
|
+
let modelsCopied = 0;
|
|
863
|
+
for (const spec of manifest.models || []) {
|
|
864
|
+
// Bundle layout supports either model_id basename (e.g.
|
|
865
|
+
// "bge-base-en-v1.5" from "BAAI/bge-base-en-v1.5") or source_repo
|
|
866
|
+
// basename (e.g. "bge-base-en-v1.5-onnx-q" from "qdrant/...").
|
|
867
|
+
const modelIdName = (spec.model_id || "").split("/").pop();
|
|
868
|
+
const sourceRepoName = (spec.source_repo || "").split("/").pop();
|
|
869
|
+
let sourceDir = path.join(bundledModelsDir, sourceRepoName);
|
|
870
|
+
if (!fs.existsSync(sourceDir)) {
|
|
871
|
+
sourceDir = path.join(bundledModelsDir, modelIdName);
|
|
872
|
+
}
|
|
873
|
+
if (!fs.existsSync(sourceDir)) continue;
|
|
874
|
+
const slug = slugifyLocalModelName(spec.name || "");
|
|
875
|
+
const targetDir = path.join(runtimeModelsDir, slug, spec.revision);
|
|
876
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
877
|
+
const missingFiles = [];
|
|
878
|
+
for (const f of (spec.required_files || [])) {
|
|
879
|
+
const src = path.join(sourceDir, f.path);
|
|
880
|
+
const dst = path.join(targetDir, f.path);
|
|
881
|
+
if (!fs.existsSync(src)) {
|
|
882
|
+
missingFiles.push(f.path);
|
|
883
|
+
continue;
|
|
884
|
+
}
|
|
885
|
+
fs.mkdirSync(path.dirname(dst), { recursive: true });
|
|
886
|
+
let shouldCopy = !fs.existsSync(dst) || (f.size && fs.statSync(dst).size !== f.size);
|
|
887
|
+
if (!shouldCopy && f.sha256 && sha256File(dst) !== f.sha256) {
|
|
888
|
+
shouldCopy = true;
|
|
889
|
+
}
|
|
890
|
+
if (shouldCopy) {
|
|
891
|
+
fs.copyFileSync(src, dst);
|
|
892
|
+
}
|
|
893
|
+
if (f.size && fs.statSync(dst).size !== f.size) {
|
|
894
|
+
missingFiles.push(`${f.path}:size`);
|
|
895
|
+
continue;
|
|
896
|
+
}
|
|
897
|
+
if (f.sha256) {
|
|
898
|
+
const actual = sha256File(dst);
|
|
899
|
+
if (actual !== f.sha256) {
|
|
900
|
+
missingFiles.push(`${f.path}:sha256`);
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
if (missingFiles.length) {
|
|
905
|
+
log(` WARN: bundled LLM model ${spec.name} incomplete (${missingFiles.join(", ")})`);
|
|
906
|
+
continue;
|
|
907
|
+
}
|
|
908
|
+
// Write the lock file to match revision (avoids re-download).
|
|
909
|
+
fs.writeFileSync(path.join(targetDir, ".nexo-model-lock.json"), JSON.stringify({
|
|
910
|
+
name: spec.name, kind: spec.kind, model_id: spec.model_id,
|
|
911
|
+
source_repo: spec.source_repo, revision: spec.revision, model_file: spec.model_file,
|
|
912
|
+
required_files: spec.required_files,
|
|
913
|
+
}, null, 2));
|
|
914
|
+
modelsCopied++;
|
|
915
|
+
}
|
|
916
|
+
if (modelsCopied > 0) log(` Copied ${modelsCopied} pre-bundled LLM model(s) (offline, ${reason}).`);
|
|
917
|
+
if (modelsCopied > 0 && modelsCopied === (manifest.models || []).length) {
|
|
918
|
+
cleanupObsoleteRuntimeLlmModels(runtimeModelsDir, manifest, { reason });
|
|
919
|
+
}
|
|
920
|
+
return modelsCopied;
|
|
921
|
+
} catch (err) {
|
|
922
|
+
log(` WARN: bundled models copy failed during ${reason}: ${err.message}`);
|
|
923
|
+
return 0;
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
|
|
769
927
|
async function runWarmupModelsCommand(args) {
|
|
770
928
|
const dryRun = args.includes("--dry-run");
|
|
771
929
|
const json = args.includes("--json");
|
|
@@ -3216,6 +3374,7 @@ async function runSetup() {
|
|
|
3216
3374
|
log(" Python dependencies reconciled.");
|
|
3217
3375
|
}
|
|
3218
3376
|
|
|
3377
|
+
copyBundledLlmModelsToRuntime(NEXO_HOME, { reason: "update" });
|
|
3219
3378
|
const migPythonForWarmup = findVenvPython(NEXO_HOME) || "python3";
|
|
3220
3379
|
runDesktopAwareModelWarmup(migPythonForWarmup, NEXO_HOME, { reason: "update", installRuntimeDeps: false });
|
|
3221
3380
|
|
|
@@ -3492,6 +3651,7 @@ async function runSetup() {
|
|
|
3492
3651
|
stampRuntimeRepairBaseline(NEXO_HOME, "bin.nexo-brain.same-version-repair")
|
|
3493
3652
|
);
|
|
3494
3653
|
|
|
3654
|
+
copyBundledLlmModelsToRuntime(NEXO_HOME, { reason: "repair" });
|
|
3495
3655
|
runDesktopAwareModelWarmup(syncPython, NEXO_HOME, { reason: "repair" });
|
|
3496
3656
|
logMacPermissionsNotice(NEXO_HOME, syncPython);
|
|
3497
3657
|
|
|
@@ -3935,71 +4095,7 @@ async function runSetup() {
|
|
|
3935
4095
|
}
|
|
3936
4096
|
log("Dependencies installed.");
|
|
3937
4097
|
|
|
3938
|
-
|
|
3939
|
-
// so fastembed finds them locally and skips the ~217MB HuggingFace download.
|
|
3940
|
-
// Bundle layout: resources/brain-bundle/models/<source-repo-name>/<all files>.
|
|
3941
|
-
// Target layout: <NEXO_HOME>/runtime/models/<spec.name slugified>/<revision>/<files>.
|
|
3942
|
-
// We map by source_repo basename to match local_model_manifest.json.
|
|
3943
|
-
const bundledModelsDir = path.join(__dirname, "..", "models");
|
|
3944
|
-
if (fs.existsSync(bundledModelsDir)) {
|
|
3945
|
-
try {
|
|
3946
|
-
const manifest = JSON.parse(fs.readFileSync(path.join(__dirname, "..", "src", "local_model_manifest.json"), "utf8"));
|
|
3947
|
-
const runtimeModelsDir = path.join(NEXO_HOME, "runtime", "models");
|
|
3948
|
-
let modelsCopied = 0;
|
|
3949
|
-
for (const spec of manifest.models || []) {
|
|
3950
|
-
// Bundle layout supports either model_id basename (e.g.
|
|
3951
|
-
// "bge-base-en-v1.5" from "BAAI/bge-base-en-v1.5") or source_repo
|
|
3952
|
-
// basename (e.g. "bge-base-en-v1.5-onnx-q" from "qdrant/...").
|
|
3953
|
-
const modelIdName = (spec.model_id || "").split("/").pop();
|
|
3954
|
-
const sourceRepoName = (spec.source_repo || "").split("/").pop();
|
|
3955
|
-
let sourceDir = path.join(bundledModelsDir, modelIdName);
|
|
3956
|
-
if (!fs.existsSync(sourceDir)) {
|
|
3957
|
-
sourceDir = path.join(bundledModelsDir, sourceRepoName);
|
|
3958
|
-
}
|
|
3959
|
-
if (!fs.existsSync(sourceDir)) continue;
|
|
3960
|
-
const slug = (spec.name || "").trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
3961
|
-
const targetDir = path.join(runtimeModelsDir, slug, spec.revision);
|
|
3962
|
-
fs.mkdirSync(targetDir, { recursive: true });
|
|
3963
|
-
const missingFiles = [];
|
|
3964
|
-
for (const f of (spec.required_files || [])) {
|
|
3965
|
-
const src = path.join(sourceDir, f.path);
|
|
3966
|
-
const dst = path.join(targetDir, f.path);
|
|
3967
|
-
if (!fs.existsSync(src)) {
|
|
3968
|
-
missingFiles.push(f.path);
|
|
3969
|
-
continue;
|
|
3970
|
-
}
|
|
3971
|
-
fs.mkdirSync(path.dirname(dst), { recursive: true });
|
|
3972
|
-
if (!fs.existsSync(dst) || (f.size && fs.statSync(dst).size !== f.size)) {
|
|
3973
|
-
fs.copyFileSync(src, dst);
|
|
3974
|
-
}
|
|
3975
|
-
if (f.size && fs.statSync(dst).size !== f.size) {
|
|
3976
|
-
missingFiles.push(`${f.path}:size`);
|
|
3977
|
-
continue;
|
|
3978
|
-
}
|
|
3979
|
-
if (f.sha256) {
|
|
3980
|
-
const actual = crypto.createHash("sha256").update(fs.readFileSync(dst)).digest("hex");
|
|
3981
|
-
if (actual !== f.sha256) {
|
|
3982
|
-
missingFiles.push(`${f.path}:sha256`);
|
|
3983
|
-
}
|
|
3984
|
-
}
|
|
3985
|
-
}
|
|
3986
|
-
if (missingFiles.length) {
|
|
3987
|
-
log(` WARN: bundled LLM model ${spec.name} incomplete (${missingFiles.join(", ")})`);
|
|
3988
|
-
continue;
|
|
3989
|
-
}
|
|
3990
|
-
// Write the lock file to match revision (avoids re-download).
|
|
3991
|
-
fs.writeFileSync(path.join(targetDir, ".nexo-model-lock.json"), JSON.stringify({
|
|
3992
|
-
name: spec.name, kind: spec.kind, model_id: spec.model_id,
|
|
3993
|
-
source_repo: spec.source_repo, revision: spec.revision, model_file: spec.model_file,
|
|
3994
|
-
required_files: spec.required_files,
|
|
3995
|
-
}, null, 2));
|
|
3996
|
-
modelsCopied++;
|
|
3997
|
-
}
|
|
3998
|
-
if (modelsCopied > 0) log(` Copied ${modelsCopied} pre-bundled LLM model(s) (offline).`);
|
|
3999
|
-
} catch (err) {
|
|
4000
|
-
log(` WARN: bundled models copy failed: ${err.message}`);
|
|
4001
|
-
}
|
|
4002
|
-
}
|
|
4098
|
+
copyBundledLlmModelsToRuntime(NEXO_HOME, { reason: "install" });
|
|
4003
4099
|
|
|
4004
4100
|
runDesktopAwareModelWarmup(python, NEXO_HOME, { reason: "install", installRuntimeDeps: false });
|
|
4005
4101
|
|
|
@@ -5121,4 +5217,13 @@ if (isCliEntrypoint()) {
|
|
|
5121
5217
|
console.error("Setup failed:", err.message);
|
|
5122
5218
|
process.exit(1);
|
|
5123
5219
|
});
|
|
5220
|
+
} else {
|
|
5221
|
+
module.exports = {
|
|
5222
|
+
cleanupObsoleteRuntimeLlmModels,
|
|
5223
|
+
copyBundledLlmModelsToRuntime,
|
|
5224
|
+
isManagedModelRevisionDir,
|
|
5225
|
+
readManagedModelLock,
|
|
5226
|
+
sha256File,
|
|
5227
|
+
slugifyLocalModelName,
|
|
5228
|
+
};
|
|
5124
5229
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nexo-brain",
|
|
3
|
-
"version": "7.30.
|
|
3
|
+
"version": "7.30.17",
|
|
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/auto_update.py
CHANGED
|
@@ -1211,6 +1211,15 @@ def _local_classifier_install_command() -> list[str]:
|
|
|
1211
1211
|
return cmd
|
|
1212
1212
|
|
|
1213
1213
|
|
|
1214
|
+
def _local_classifier_auto_install_enabled() -> bool:
|
|
1215
|
+
return str(os.environ.get("NEXO_LOCAL_CLASSIFIER", "")).strip().lower() in {
|
|
1216
|
+
"1",
|
|
1217
|
+
"true",
|
|
1218
|
+
"on",
|
|
1219
|
+
"auto",
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1222
|
+
|
|
1214
1223
|
def _install_local_classifier_worker() -> None:
|
|
1215
1224
|
from classifier_local import MODEL_REVISION
|
|
1216
1225
|
|
|
@@ -1311,6 +1320,18 @@ def _maybe_install_local_classifier() -> None:
|
|
|
1311
1320
|
"opt_out": True,
|
|
1312
1321
|
})
|
|
1313
1322
|
return
|
|
1323
|
+
if not _local_classifier_auto_install_enabled():
|
|
1324
|
+
from classifier_local import MODEL_REVISION
|
|
1325
|
+
|
|
1326
|
+
_write_classifier_install_log("[classifier-install] deferred: model is not bundled; set NEXO_LOCAL_CLASSIFIER=auto to install")
|
|
1327
|
+
_write_classifier_install_state({
|
|
1328
|
+
"installed_at": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
|
|
1329
|
+
"model_revision": MODEL_REVISION,
|
|
1330
|
+
"deps_ok": False,
|
|
1331
|
+
"deferred": True,
|
|
1332
|
+
"reason": "not_bundled",
|
|
1333
|
+
})
|
|
1334
|
+
return
|
|
1314
1335
|
try:
|
|
1315
1336
|
deps_ok, versions, _missing = _probe_local_classifier_dependencies()
|
|
1316
1337
|
if deps_ok:
|
|
@@ -3133,6 +3154,103 @@ def _ensure_f06_legacy_shims() -> None:
|
|
|
3133
3154
|
paths.finalize_backup_snapshot(conflict_root)
|
|
3134
3155
|
|
|
3135
3156
|
|
|
3157
|
+
def _ensure_f06_personal_script_import_shims() -> None:
|
|
3158
|
+
"""Expose promoted core helpers to legacy personal scripts.
|
|
3159
|
+
|
|
3160
|
+
Some operator-owned scripts are executed directly from
|
|
3161
|
+
``NEXO_HOME/personal/scripts`` and therefore only get that directory on
|
|
3162
|
+
``sys.path``. When Block E.6 promoted ``nexo_personal_automation.py`` into
|
|
3163
|
+
``core/scripts``, those direct scripts lost the bare import
|
|
3164
|
+
``from nexo_personal_automation import ...``. Keep the source of truth in
|
|
3165
|
+
core, but add a tiny compatibility entry in personal/scripts.
|
|
3166
|
+
"""
|
|
3167
|
+
|
|
3168
|
+
helper_names = ("nexo_personal_automation.py",)
|
|
3169
|
+
core_scripts = NEXO_HOME / "core" / "scripts"
|
|
3170
|
+
personal_scripts = NEXO_HOME / "personal" / "scripts"
|
|
3171
|
+
stub_marker = "# Auto-generated by NEXO F0.6 personal import shim."
|
|
3172
|
+
|
|
3173
|
+
def _same_file(a: Path, b: Path) -> bool:
|
|
3174
|
+
try:
|
|
3175
|
+
return a.is_file() and b.is_file() and a.read_bytes() == b.read_bytes()
|
|
3176
|
+
except Exception:
|
|
3177
|
+
return False
|
|
3178
|
+
|
|
3179
|
+
def _managed_stub(path: Path) -> bool:
|
|
3180
|
+
try:
|
|
3181
|
+
return path.is_file() and path.read_text(encoding="utf-8", errors="ignore").startswith(stub_marker)
|
|
3182
|
+
except Exception:
|
|
3183
|
+
return False
|
|
3184
|
+
|
|
3185
|
+
def _write_stub(shim: Path, target: Path) -> None:
|
|
3186
|
+
relative = os.path.relpath(str(target), str(shim.parent))
|
|
3187
|
+
shim.write_text(
|
|
3188
|
+
f"""{stub_marker}
|
|
3189
|
+
from __future__ import annotations
|
|
3190
|
+
|
|
3191
|
+
import importlib.util as _importlib_util
|
|
3192
|
+
from pathlib import Path as _Path
|
|
3193
|
+
|
|
3194
|
+
_TARGET = (_Path(__file__).resolve().parent / {relative!r}).resolve()
|
|
3195
|
+
_SPEC = _importlib_util.spec_from_file_location("_nexo_core_personal_automation", _TARGET)
|
|
3196
|
+
if _SPEC is None or _SPEC.loader is None:
|
|
3197
|
+
raise ImportError(f"Cannot load NEXO core helper at {{_TARGET}}")
|
|
3198
|
+
_MODULE = _importlib_util.module_from_spec(_SPEC)
|
|
3199
|
+
_SPEC.loader.exec_module(_MODULE)
|
|
3200
|
+
__all__ = getattr(_MODULE, "__all__", [name for name in vars(_MODULE) if not name.startswith("_")])
|
|
3201
|
+
globals().update({{name: getattr(_MODULE, name) for name in __all__}})
|
|
3202
|
+
""",
|
|
3203
|
+
encoding="utf-8",
|
|
3204
|
+
)
|
|
3205
|
+
|
|
3206
|
+
try:
|
|
3207
|
+
personal_scripts.mkdir(parents=True, exist_ok=True)
|
|
3208
|
+
except Exception as exc:
|
|
3209
|
+
_log(f"[F0.6 shim] could not create personal/scripts for import shims: {exc}")
|
|
3210
|
+
return
|
|
3211
|
+
|
|
3212
|
+
for name in helper_names:
|
|
3213
|
+
target = core_scripts / name
|
|
3214
|
+
if not target.is_file():
|
|
3215
|
+
continue
|
|
3216
|
+
shim = personal_scripts / name
|
|
3217
|
+
|
|
3218
|
+
if shim.is_symlink():
|
|
3219
|
+
try:
|
|
3220
|
+
if shim.resolve(strict=False) == target.resolve(strict=False):
|
|
3221
|
+
continue
|
|
3222
|
+
except Exception:
|
|
3223
|
+
pass
|
|
3224
|
+
try:
|
|
3225
|
+
shim.unlink()
|
|
3226
|
+
except Exception as exc:
|
|
3227
|
+
_log(f"[F0.6 shim] could not replace personal import symlink {name}: {exc}")
|
|
3228
|
+
continue
|
|
3229
|
+
|
|
3230
|
+
if shim.exists():
|
|
3231
|
+
if shim.is_file() and (_same_file(shim, target) or _managed_stub(shim)):
|
|
3232
|
+
try:
|
|
3233
|
+
shim.unlink()
|
|
3234
|
+
except Exception as exc:
|
|
3235
|
+
_log(f"[F0.6 shim] could not replace personal import copy {name}: {exc}")
|
|
3236
|
+
continue
|
|
3237
|
+
else:
|
|
3238
|
+
_log(f"[F0.6 shim] preserving distinct personal import helper {shim}")
|
|
3239
|
+
continue
|
|
3240
|
+
|
|
3241
|
+
try:
|
|
3242
|
+
relative = os.path.relpath(str(target), str(shim.parent))
|
|
3243
|
+
shim.symlink_to(relative)
|
|
3244
|
+
continue
|
|
3245
|
+
except Exception as exc:
|
|
3246
|
+
_log(f"[F0.6 shim] symlink create failed for personal import helper {name}: {exc}")
|
|
3247
|
+
|
|
3248
|
+
try:
|
|
3249
|
+
_write_stub(shim, target)
|
|
3250
|
+
except Exception as exc:
|
|
3251
|
+
_log(f"[F0.6 shim] stub create failed for personal import helper {name}: {exc}")
|
|
3252
|
+
|
|
3253
|
+
|
|
3136
3254
|
def _rewrite_f06_launch_agents() -> int:
|
|
3137
3255
|
"""Rewrite lingering LaunchAgent paths to canonical F0.6 locations."""
|
|
3138
3256
|
import re as _re
|
|
@@ -3258,6 +3376,7 @@ def _maybe_migrate_to_f06_layout() -> None:
|
|
|
3258
3376
|
_promote_packaged_runtime_code_to_core()
|
|
3259
3377
|
if _f06_live_legacy_paths():
|
|
3260
3378
|
_ensure_f06_legacy_shims()
|
|
3379
|
+
_ensure_f06_personal_script_import_shims()
|
|
3261
3380
|
_cleanup_f06_root_residue()
|
|
3262
3381
|
try:
|
|
3263
3382
|
_rewrite_f06_launch_agents()
|
|
@@ -3477,6 +3596,7 @@ def _maybe_migrate_to_f06_layout() -> None:
|
|
|
3477
3596
|
except Exception as e:
|
|
3478
3597
|
_log(f"[F0.6] marker write failed: {e}")
|
|
3479
3598
|
_ensure_f06_legacy_shims()
|
|
3599
|
+
_ensure_f06_personal_script_import_shims()
|
|
3480
3600
|
_cleanup_f06_root_residue()
|
|
3481
3601
|
try:
|
|
3482
3602
|
rewritten = _rewrite_f06_launch_agents()
|
|
@@ -17,6 +17,8 @@ from cognitive._core import (
|
|
|
17
17
|
_get_db, _init_tables, _migrate_lifecycle, _migrate_co_activation,
|
|
18
18
|
_migrate_memory_personalization,
|
|
19
19
|
_auto_migrate_embeddings,
|
|
20
|
+
embedding_migration_status,
|
|
21
|
+
_active_embedding_context, _row_embedding_array, _embedding_migration_uses_shadow,
|
|
20
22
|
_get_model, _get_reranker, rerank_results,
|
|
21
23
|
embed, cosine_similarity, _array_to_blob, _blob_to_array,
|
|
22
24
|
extract_temporal_date, redact_secrets,
|