@mneme-ai/core 1.65.0 → 1.66.0
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/dist/ai_compliance.d.ts +16 -0
- package/dist/ai_compliance.d.ts.map +1 -1
- package/dist/ai_compliance.js +33 -1
- package/dist/ai_compliance.js.map +1 -1
- package/dist/ai_compliance_windowed.test.d.ts +5 -0
- package/dist/ai_compliance_windowed.test.d.ts.map +1 -0
- package/dist/ai_compliance_windowed.test.js +70 -0
- package/dist/ai_compliance_windowed.test.js.map +1 -0
- package/dist/autarchy/autarchy.test.d.ts +5 -0
- package/dist/autarchy/autarchy.test.d.ts.map +1 -0
- package/dist/autarchy/autarchy.test.js +219 -0
- package/dist/autarchy/autarchy.test.js.map +1 -0
- package/dist/autarchy/baked_pharmacopoeia.d.ts +57 -0
- package/dist/autarchy/baked_pharmacopoeia.d.ts.map +1 -0
- package/dist/autarchy/baked_pharmacopoeia.js +139 -0
- package/dist/autarchy/baked_pharmacopoeia.js.map +1 -0
- package/dist/autarchy/eager_pin.d.ts +48 -0
- package/dist/autarchy/eager_pin.d.ts.map +1 -0
- package/dist/autarchy/eager_pin.js +140 -0
- package/dist/autarchy/eager_pin.js.map +1 -0
- package/dist/autarchy/index.d.ts +56 -0
- package/dist/autarchy/index.d.ts.map +1 -0
- package/dist/autarchy/index.js +104 -0
- package/dist/autarchy/index.js.map +1 -0
- package/dist/autarchy/mesh_as_cloud.d.ts +40 -0
- package/dist/autarchy/mesh_as_cloud.d.ts.map +1 -0
- package/dist/autarchy/mesh_as_cloud.js +115 -0
- package/dist/autarchy/mesh_as_cloud.js.map +1 -0
- package/dist/autarchy/schroedinger_embedder.d.ts +54 -0
- package/dist/autarchy/schroedinger_embedder.d.ts.map +1 -0
- package/dist/autarchy/schroedinger_embedder.js +122 -0
- package/dist/autarchy/schroedinger_embedder.js.map +1 -0
- package/dist/embedder_autodiagnose.d.ts +64 -0
- package/dist/embedder_autodiagnose.d.ts.map +1 -0
- package/dist/embedder_autodiagnose.js +153 -0
- package/dist/embedder_autodiagnose.js.map +1 -0
- package/dist/embedder_autodiagnose.test.d.ts +5 -0
- package/dist/embedder_autodiagnose.test.d.ts.map +1 -0
- package/dist/embedder_autodiagnose.test.js +101 -0
- package/dist/embedder_autodiagnose.test.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* v1.66.0 -- AUTARCHY A1: MESH-AS-CLOUD.
|
|
3
|
+
*
|
|
4
|
+
* Wild idea: the cloud doesn't have to be central. When brain.mneme.dev
|
|
5
|
+
* is unreachable (e.g. the user destroyed their droplet), the FEDERATION
|
|
6
|
+
* MESH acts as the cloud surrogate. Mneme reports "cloud=online via
|
|
7
|
+
* N mesh peers" instead of "cloud=offline brain.mneme.dev unreachable".
|
|
8
|
+
*
|
|
9
|
+
* Sources of peer state (read-only):
|
|
10
|
+
* .mneme/mesh-seen.jsonl peers who've shipped us packets
|
|
11
|
+
* .mneme/wisdom-inheritance.jsonl peers who've fed us wisdom packs
|
|
12
|
+
* .mneme/whisper/inbox.jsonl peers who've sent signed whispers
|
|
13
|
+
*
|
|
14
|
+
* Aggregates unique peer ids; treats >=1 reachable peer in the last
|
|
15
|
+
* 24h as "mesh-cloud online". The classic central-cloud probe still
|
|
16
|
+
* runs for compat; this layer adds a parallel surrogate.
|
|
17
|
+
*/
|
|
18
|
+
import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
|
|
19
|
+
import { join } from "node:path";
|
|
20
|
+
function readJsonl(path) {
|
|
21
|
+
if (!existsSync(path))
|
|
22
|
+
return [];
|
|
23
|
+
try {
|
|
24
|
+
return readFileSync(path, "utf8")
|
|
25
|
+
.split("\n").filter(Boolean)
|
|
26
|
+
.map((l) => { try {
|
|
27
|
+
return JSON.parse(l);
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return null;
|
|
31
|
+
} })
|
|
32
|
+
.filter((x) => x !== null);
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
return [];
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
function readJsonlDir(dir) {
|
|
39
|
+
if (!existsSync(dir))
|
|
40
|
+
return [];
|
|
41
|
+
const out = [];
|
|
42
|
+
let entries = [];
|
|
43
|
+
try {
|
|
44
|
+
entries = readdirSync(dir);
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
return [];
|
|
48
|
+
}
|
|
49
|
+
for (const e of entries) {
|
|
50
|
+
const p = join(dir, e);
|
|
51
|
+
try {
|
|
52
|
+
const s = statSync(p);
|
|
53
|
+
if (s.isFile() && e.endsWith(".jsonl"))
|
|
54
|
+
out.push(...readJsonl(p));
|
|
55
|
+
}
|
|
56
|
+
catch { /* */ }
|
|
57
|
+
}
|
|
58
|
+
return out;
|
|
59
|
+
}
|
|
60
|
+
function isoToMs(s) {
|
|
61
|
+
if (typeof s !== "string")
|
|
62
|
+
return null;
|
|
63
|
+
const t = Date.parse(s);
|
|
64
|
+
return Number.isFinite(t) ? t : null;
|
|
65
|
+
}
|
|
66
|
+
export function meshCloudReport(repoRoot, opts) {
|
|
67
|
+
const lookbackHours = opts?.lookbackHours ?? 24;
|
|
68
|
+
const cutoffMs = Date.now() - lookbackHours * 3600 * 1000;
|
|
69
|
+
const mesh = readJsonl(join(repoRoot, ".mneme/mesh-seen.jsonl"));
|
|
70
|
+
const wisdom = readJsonl(join(repoRoot, ".mneme/wisdom-inheritance.jsonl"));
|
|
71
|
+
const whisper = readJsonlDir(join(repoRoot, ".mneme/whisper"));
|
|
72
|
+
const peers = new Set();
|
|
73
|
+
const sources = { meshGossip: 0, wisdomImports: 0, whisperInbox: 0 };
|
|
74
|
+
let lastPeerMs = -1;
|
|
75
|
+
const consume = (rows, label) => {
|
|
76
|
+
for (const r of rows) {
|
|
77
|
+
const id = (r["peer"] ?? r["from"] ?? r["instanceId"] ?? r["sender"]);
|
|
78
|
+
const tsRaw = r["ts"] ?? r["at"] ?? r["seenAt"] ?? r["observedAt"];
|
|
79
|
+
const tsMs = isoToMs(tsRaw);
|
|
80
|
+
if (typeof id !== "string" || !id)
|
|
81
|
+
continue;
|
|
82
|
+
if (tsMs !== null && tsMs < cutoffMs)
|
|
83
|
+
continue;
|
|
84
|
+
peers.add(id);
|
|
85
|
+
sources[label] += 1;
|
|
86
|
+
if (tsMs !== null && tsMs > lastPeerMs)
|
|
87
|
+
lastPeerMs = tsMs;
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
consume(mesh, "meshGossip");
|
|
91
|
+
consume(wisdom, "wisdomImports");
|
|
92
|
+
consume(whisper, "whisperInbox");
|
|
93
|
+
let state;
|
|
94
|
+
if (opts?.centralOnline)
|
|
95
|
+
state = "central-online";
|
|
96
|
+
else if (peers.size > 0)
|
|
97
|
+
state = "mesh-only";
|
|
98
|
+
else
|
|
99
|
+
state = "isolated";
|
|
100
|
+
const lastPeerSeen = lastPeerMs > 0 ? new Date(lastPeerMs).toISOString() : null;
|
|
101
|
+
const headline = state === "central-online"
|
|
102
|
+
? `Cloud online (central brain.mneme.dev + ${peers.size} mesh peer(s) in last ${lookbackHours}h).`
|
|
103
|
+
: state === "mesh-only"
|
|
104
|
+
? `Cloud online via mesh-as-cloud (${peers.size} peer(s) in last ${lookbackHours}h; central brain offline).`
|
|
105
|
+
: `Cloud isolated (no central + no mesh peers in last ${lookbackHours}h). Autarchy mode -- local-only.`;
|
|
106
|
+
return {
|
|
107
|
+
state,
|
|
108
|
+
uniquePeers: peers.size,
|
|
109
|
+
lastPeerSeen,
|
|
110
|
+
lookbackHours,
|
|
111
|
+
sources,
|
|
112
|
+
headline,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
//# sourceMappingURL=mesh_as_cloud.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mesh_as_cloud.js","sourceRoot":"","sources":["../../src/autarchy/mesh_as_cloud.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* v1.66.0 -- AUTARCHY A2: SCHROEDINGER EMBEDDER.
|
|
3
|
+
*
|
|
4
|
+
* Wild idea: don't pick ONE embedder at config time. Probe all four
|
|
5
|
+
* tiers concurrently at startup, write the AUTHORITATIVE winner to
|
|
6
|
+
* `.mneme/embedder-status.json`, and let the pulse + every other
|
|
7
|
+
* consumer read from THAT instead of guessing at runtime.
|
|
8
|
+
*
|
|
9
|
+
* This kills the "phantom WASM fallback" report once and for all:
|
|
10
|
+
* if WASM verified successfully, the status file says so -- no
|
|
11
|
+
* stale pulse text can override observed truth.
|
|
12
|
+
*
|
|
13
|
+
* Status file format:
|
|
14
|
+
* {
|
|
15
|
+
* ts: ISO,
|
|
16
|
+
* winner: "openai"|"ollama"|"bundled"|"hash",
|
|
17
|
+
* allTiers: [{ tier, available, verifyMs, reason? }, ...],
|
|
18
|
+
* stableSince: ISO,
|
|
19
|
+
* winnerChangedAt: ISO|null,
|
|
20
|
+
* }
|
|
21
|
+
*
|
|
22
|
+
* Re-probed once per session via cooldownMs. Sub-second on warm path.
|
|
23
|
+
*/
|
|
24
|
+
import { type MemoryTierName } from "../memory_tier.js";
|
|
25
|
+
export interface TierProbeResult {
|
|
26
|
+
tier: MemoryTierName;
|
|
27
|
+
available: boolean;
|
|
28
|
+
verifyMs: number;
|
|
29
|
+
reason?: string;
|
|
30
|
+
}
|
|
31
|
+
export interface EmbedderStatus {
|
|
32
|
+
ts: string;
|
|
33
|
+
winner: MemoryTierName;
|
|
34
|
+
allTiers: TierProbeResult[];
|
|
35
|
+
stableSince: string;
|
|
36
|
+
winnerChangedAt: string | null;
|
|
37
|
+
/** Plain-English. */
|
|
38
|
+
headline: string;
|
|
39
|
+
}
|
|
40
|
+
export interface ProbeOptions {
|
|
41
|
+
/** Re-probe even if a recent status exists. Default false. */
|
|
42
|
+
force?: boolean;
|
|
43
|
+
/** Skip Ollama probe (e.g. CI). */
|
|
44
|
+
skipOllama?: boolean;
|
|
45
|
+
/** Skip Bundled probe. */
|
|
46
|
+
skipBundled?: boolean;
|
|
47
|
+
/** Reprobe cooldown ms; if last probe is younger, reuse cached. */
|
|
48
|
+
cooldownMs?: number;
|
|
49
|
+
}
|
|
50
|
+
/** Run the parallel race. Always writes `.mneme/embedder-status.json`. */
|
|
51
|
+
export declare function observeEmbedders(repoRoot: string, opts?: ProbeOptions): Promise<EmbedderStatus>;
|
|
52
|
+
/** Read the persisted authoritative status. Returns null if not probed yet. */
|
|
53
|
+
export declare function readEmbedderStatus(repoRoot: string): EmbedderStatus | null;
|
|
54
|
+
//# sourceMappingURL=schroedinger_embedder.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schroedinger_embedder.d.ts","sourceRoot":"","sources":["../../src/autarchy/schroedinger_embedder.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAMH,OAAO,EAAkC,KAAK,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAOxF,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,cAAc,CAAC;IACrB,SAAS,EAAE,OAAO,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,cAAc,CAAC;IACvB,QAAQ,EAAE,eAAe,EAAE,CAAC;IAC5B,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,qBAAqB;IACrB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAsDD,MAAM,WAAW,YAAY;IAC3B,8DAA8D;IAC9D,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,mCAAmC;IACnC,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,0BAA0B;IAC1B,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,mEAAmE;IACnE,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,0EAA0E;AAC1E,wBAAsB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,YAAY,GAAG,OAAO,CAAC,cAAc,CAAC,CA2BrG;AAED,+EAA+E;AAC/E,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,cAAc,GAAG,IAAI,CAE1E"}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* v1.66.0 -- AUTARCHY A2: SCHROEDINGER EMBEDDER.
|
|
3
|
+
*
|
|
4
|
+
* Wild idea: don't pick ONE embedder at config time. Probe all four
|
|
5
|
+
* tiers concurrently at startup, write the AUTHORITATIVE winner to
|
|
6
|
+
* `.mneme/embedder-status.json`, and let the pulse + every other
|
|
7
|
+
* consumer read from THAT instead of guessing at runtime.
|
|
8
|
+
*
|
|
9
|
+
* This kills the "phantom WASM fallback" report once and for all:
|
|
10
|
+
* if WASM verified successfully, the status file says so -- no
|
|
11
|
+
* stale pulse text can override observed truth.
|
|
12
|
+
*
|
|
13
|
+
* Status file format:
|
|
14
|
+
* {
|
|
15
|
+
* ts: ISO,
|
|
16
|
+
* winner: "openai"|"ollama"|"bundled"|"hash",
|
|
17
|
+
* allTiers: [{ tier, available, verifyMs, reason? }, ...],
|
|
18
|
+
* stableSince: ISO,
|
|
19
|
+
* winnerChangedAt: ISO|null,
|
|
20
|
+
* }
|
|
21
|
+
*
|
|
22
|
+
* Re-probed once per session via cooldownMs. Sub-second on warm path.
|
|
23
|
+
*/
|
|
24
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync } from "node:fs";
|
|
25
|
+
import { join } from "node:path";
|
|
26
|
+
import { request } from "node:http";
|
|
27
|
+
import { tierInfo } from "../memory_tier.js";
|
|
28
|
+
const STATUS_FILE = ".mneme/embedder-status.json";
|
|
29
|
+
const TIER_RANK = {
|
|
30
|
+
openai: 4, ollama: 3, bundled: 2, hash: 1, unknown: 0,
|
|
31
|
+
};
|
|
32
|
+
function probeOpenAI() {
|
|
33
|
+
const t0 = Date.now();
|
|
34
|
+
const ok = typeof process.env["OPENAI_API_KEY"] === "string" && process.env["OPENAI_API_KEY"].length >= 10;
|
|
35
|
+
return { tier: "openai", available: ok, verifyMs: Date.now() - t0, reason: ok ? undefined : "OPENAI_API_KEY not set" };
|
|
36
|
+
}
|
|
37
|
+
function probeOllama(timeoutMs = 500) {
|
|
38
|
+
return new Promise((resolve) => {
|
|
39
|
+
const t0 = Date.now();
|
|
40
|
+
const req = request({ host: "127.0.0.1", port: 11434, path: "/api/tags", method: "GET", timeout: timeoutMs }, (res) => {
|
|
41
|
+
res.resume();
|
|
42
|
+
resolve({ tier: "ollama", available: (res.statusCode ?? 0) < 500, verifyMs: Date.now() - t0 });
|
|
43
|
+
});
|
|
44
|
+
req.on("timeout", () => { req.destroy(); resolve({ tier: "ollama", available: false, verifyMs: Date.now() - t0, reason: "timeout" }); });
|
|
45
|
+
req.on("error", (e) => resolve({ tier: "ollama", available: false, verifyMs: Date.now() - t0, reason: e.message }));
|
|
46
|
+
req.end();
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
async function probeBundled() {
|
|
50
|
+
const t0 = Date.now();
|
|
51
|
+
try {
|
|
52
|
+
await import("@huggingface/transformers");
|
|
53
|
+
return { tier: "bundled", available: true, verifyMs: Date.now() - t0 };
|
|
54
|
+
}
|
|
55
|
+
catch (e) {
|
|
56
|
+
return { tier: "bundled", available: false, verifyMs: Date.now() - t0, reason: e.message };
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
function pickWinner(probes) {
|
|
60
|
+
let best = "hash";
|
|
61
|
+
let bestRank = TIER_RANK.hash;
|
|
62
|
+
for (const p of probes) {
|
|
63
|
+
if (!p.available)
|
|
64
|
+
continue;
|
|
65
|
+
const r = TIER_RANK[p.tier];
|
|
66
|
+
if (r > bestRank) {
|
|
67
|
+
best = p.tier;
|
|
68
|
+
bestRank = r;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return best;
|
|
72
|
+
}
|
|
73
|
+
function readPrior(repoRoot) {
|
|
74
|
+
const p = join(repoRoot, STATUS_FILE);
|
|
75
|
+
if (!existsSync(p))
|
|
76
|
+
return null;
|
|
77
|
+
try {
|
|
78
|
+
return JSON.parse(readFileSync(p, "utf8"));
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
function persist(repoRoot, status) {
|
|
85
|
+
const dir = join(repoRoot, ".mneme");
|
|
86
|
+
if (!existsSync(dir))
|
|
87
|
+
mkdirSync(dir, { recursive: true });
|
|
88
|
+
writeFileSync(join(dir, "embedder-status.json"), JSON.stringify(status, null, 2) + "\n", "utf8");
|
|
89
|
+
}
|
|
90
|
+
/** Run the parallel race. Always writes `.mneme/embedder-status.json`. */
|
|
91
|
+
export async function observeEmbedders(repoRoot, opts) {
|
|
92
|
+
const cooldownMs = opts?.cooldownMs ?? 60_000;
|
|
93
|
+
const prior = readPrior(repoRoot);
|
|
94
|
+
if (!opts?.force && prior) {
|
|
95
|
+
const age = Date.now() - Date.parse(prior.ts);
|
|
96
|
+
if (Number.isFinite(age) && age < cooldownMs)
|
|
97
|
+
return prior;
|
|
98
|
+
}
|
|
99
|
+
// Parallel race -- all probes fire at once.
|
|
100
|
+
const promises = [];
|
|
101
|
+
promises.push(Promise.resolve(probeOpenAI()));
|
|
102
|
+
if (!opts?.skipOllama)
|
|
103
|
+
promises.push(probeOllama());
|
|
104
|
+
if (!opts?.skipBundled)
|
|
105
|
+
promises.push(probeBundled());
|
|
106
|
+
promises.push(Promise.resolve({ tier: "hash", available: true, verifyMs: 0 }));
|
|
107
|
+
const allTiers = await Promise.all(promises);
|
|
108
|
+
const winner = pickWinner(allTiers);
|
|
109
|
+
const ts = new Date().toISOString();
|
|
110
|
+
const winnerChangedAt = prior && prior.winner !== winner ? ts : prior?.winnerChangedAt ?? null;
|
|
111
|
+
const stableSince = prior && prior.winner === winner ? prior.stableSince : ts;
|
|
112
|
+
const tierBadge = `${tierInfo(winner).display} ★${tierInfo(winner).stars}`;
|
|
113
|
+
const headline = `Schroedinger collapse: ${tierBadge}. Probed ${allTiers.length} tiers in parallel; ${allTiers.filter((t) => t.available).length} available.`;
|
|
114
|
+
const status = { ts, winner, allTiers, stableSince, winnerChangedAt, headline };
|
|
115
|
+
persist(repoRoot, status);
|
|
116
|
+
return status;
|
|
117
|
+
}
|
|
118
|
+
/** Read the persisted authoritative status. Returns null if not probed yet. */
|
|
119
|
+
export function readEmbedderStatus(repoRoot) {
|
|
120
|
+
return readPrior(repoRoot);
|
|
121
|
+
}
|
|
122
|
+
//# sourceMappingURL=schroedinger_embedder.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schroedinger_embedder.js","sourceRoot":"","sources":["../../src/autarchy/schroedinger_embedder.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* v1.65.1 -- EMBEDDER AUTODIAGNOSE.
|
|
3
|
+
*
|
|
4
|
+
* Detects the GAP between the highest tier actually available on the
|
|
5
|
+
* machine and the tier the user's config selected. If the user is
|
|
6
|
+
* sitting on hash tier (★★) while bundled WASM or Ollama is reachable,
|
|
7
|
+
* the autodiagnose flags it AND offers a one-call upgrade.
|
|
8
|
+
*
|
|
9
|
+
* Probe priority (best -> worst):
|
|
10
|
+
* 1. openai -- env OPENAI_API_KEY set
|
|
11
|
+
* 2. ollama -- 127.0.0.1:11434 reachable
|
|
12
|
+
* 3. bundled -- @huggingface/transformers importable + cache writable
|
|
13
|
+
* 4. hash -- always available
|
|
14
|
+
*
|
|
15
|
+
* Pure-read by default; persist=true rewrites .mneme/config.json so
|
|
16
|
+
* the next session picks the upgraded provider.
|
|
17
|
+
*
|
|
18
|
+
* No actual embed calls are made -- probe is cheap (<200ms). The
|
|
19
|
+
* actual model download still happens lazily on first `mneme index`.
|
|
20
|
+
*/
|
|
21
|
+
import { type MemoryTierInfo, type MemoryTierName } from "./memory_tier.js";
|
|
22
|
+
export interface EmbedderProbe {
|
|
23
|
+
tier: MemoryTierName;
|
|
24
|
+
available: boolean;
|
|
25
|
+
/** Brief why-not when available=false. */
|
|
26
|
+
reason?: string;
|
|
27
|
+
/** Time spent in probe, ms. */
|
|
28
|
+
ms: number;
|
|
29
|
+
}
|
|
30
|
+
export interface AutodiagnoseReport {
|
|
31
|
+
/** Tier currently selected in .mneme/config.json (or default). */
|
|
32
|
+
currentTier: MemoryTierName;
|
|
33
|
+
/** Highest available tier on this machine. */
|
|
34
|
+
bestAvailable: MemoryTierName;
|
|
35
|
+
/** True iff bestAvailable is strictly above currentTier. */
|
|
36
|
+
hasUpgrade: boolean;
|
|
37
|
+
/** Probes for each candidate tier. */
|
|
38
|
+
probes: EmbedderProbe[];
|
|
39
|
+
/** Plain-English headline. */
|
|
40
|
+
headline: string;
|
|
41
|
+
/** Recommended action when hasUpgrade=true; null otherwise. */
|
|
42
|
+
recommendation: {
|
|
43
|
+
action: "switch-to";
|
|
44
|
+
tier: MemoryTierName;
|
|
45
|
+
rationale: string;
|
|
46
|
+
} | null;
|
|
47
|
+
/** When persist=true and recommendation acted on, the config path written. */
|
|
48
|
+
configPathWritten: string | null;
|
|
49
|
+
/** Total wall-time, ms. */
|
|
50
|
+
ms: number;
|
|
51
|
+
}
|
|
52
|
+
export interface AutodiagnoseOptions {
|
|
53
|
+
/** If true, rewrite .mneme/config.json to switch to bestAvailable. Default false. */
|
|
54
|
+
persist?: boolean;
|
|
55
|
+
/** Skip Ollama probe (e.g. test environments). Default false. */
|
|
56
|
+
skipOllama?: boolean;
|
|
57
|
+
/** Skip Bundled probe (e.g. CI without network). Default false. */
|
|
58
|
+
skipBundled?: boolean;
|
|
59
|
+
}
|
|
60
|
+
export declare function autodiagnose(repoRoot: string, opts?: AutodiagnoseOptions): Promise<AutodiagnoseReport>;
|
|
61
|
+
/** Returns the resolved memory-tier info for the (possibly upgraded)
|
|
62
|
+
* current config. Convenience for pulse/status surfaces. */
|
|
63
|
+
export declare function currentTierInfo(repoRoot: string): MemoryTierInfo;
|
|
64
|
+
//# sourceMappingURL=embedder_autodiagnose.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"embedder_autodiagnose.d.ts","sourceRoot":"","sources":["../src/embedder_autodiagnose.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAMH,OAAO,EAAkC,KAAK,cAAc,EAAE,KAAK,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAE5G,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,cAAc,CAAC;IACrB,SAAS,EAAE,OAAO,CAAC;IACnB,0CAA0C;IAC1C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,+BAA+B;IAC/B,EAAE,EAAE,MAAM,CAAC;CACZ;AAED,MAAM,WAAW,kBAAkB;IACjC,kEAAkE;IAClE,WAAW,EAAE,cAAc,CAAC;IAC5B,8CAA8C;IAC9C,aAAa,EAAE,cAAc,CAAC;IAC9B,4DAA4D;IAC5D,UAAU,EAAE,OAAO,CAAC;IACpB,sCAAsC;IACtC,MAAM,EAAE,aAAa,EAAE,CAAC;IACxB,8BAA8B;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,+DAA+D;IAC/D,cAAc,EAAE;QAAE,MAAM,EAAE,WAAW,CAAC;QAAC,IAAI,EAAE,cAAc,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IACxF,8EAA8E;IAC9E,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,2BAA2B;IAC3B,EAAE,EAAE,MAAM,CAAC;CACZ;AA+DD,MAAM,WAAW,mBAAmB;IAClC,qFAAqF;IACrF,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,iEAAiE;IACjE,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,mEAAmE;IACnE,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,wBAAsB,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,mBAAmB,GAAG,OAAO,CAAC,kBAAkB,CAAC,CA6D5G;AAED;6DAC6D;AAC7D,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,cAAc,CAIhE"}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* v1.65.1 -- EMBEDDER AUTODIAGNOSE.
|
|
3
|
+
*
|
|
4
|
+
* Detects the GAP between the highest tier actually available on the
|
|
5
|
+
* machine and the tier the user's config selected. If the user is
|
|
6
|
+
* sitting on hash tier (★★) while bundled WASM or Ollama is reachable,
|
|
7
|
+
* the autodiagnose flags it AND offers a one-call upgrade.
|
|
8
|
+
*
|
|
9
|
+
* Probe priority (best -> worst):
|
|
10
|
+
* 1. openai -- env OPENAI_API_KEY set
|
|
11
|
+
* 2. ollama -- 127.0.0.1:11434 reachable
|
|
12
|
+
* 3. bundled -- @huggingface/transformers importable + cache writable
|
|
13
|
+
* 4. hash -- always available
|
|
14
|
+
*
|
|
15
|
+
* Pure-read by default; persist=true rewrites .mneme/config.json so
|
|
16
|
+
* the next session picks the upgraded provider.
|
|
17
|
+
*
|
|
18
|
+
* No actual embed calls are made -- probe is cheap (<200ms). The
|
|
19
|
+
* actual model download still happens lazily on first `mneme index`.
|
|
20
|
+
*/
|
|
21
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync } from "node:fs";
|
|
22
|
+
import { join } from "node:path";
|
|
23
|
+
import { request } from "node:http";
|
|
24
|
+
import { classifyEmbedderName, tierInfo } from "./memory_tier.js";
|
|
25
|
+
const TIER_RANK = {
|
|
26
|
+
openai: 4, ollama: 3, bundled: 2, hash: 1, unknown: 0,
|
|
27
|
+
};
|
|
28
|
+
function readConfig(repoRoot) {
|
|
29
|
+
const p = join(repoRoot, ".mneme/config.json");
|
|
30
|
+
if (!existsSync(p))
|
|
31
|
+
return {};
|
|
32
|
+
try {
|
|
33
|
+
return JSON.parse(readFileSync(p, "utf8"));
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return {};
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
function writeConfig(repoRoot, cfg) {
|
|
40
|
+
const dir = join(repoRoot, ".mneme");
|
|
41
|
+
if (!existsSync(dir))
|
|
42
|
+
mkdirSync(dir, { recursive: true });
|
|
43
|
+
const p = join(dir, "config.json");
|
|
44
|
+
writeFileSync(p, JSON.stringify(cfg, null, 2) + "\n", "utf8");
|
|
45
|
+
return p;
|
|
46
|
+
}
|
|
47
|
+
function probeOpenAI() {
|
|
48
|
+
const t0 = Date.now();
|
|
49
|
+
const hasKey = typeof process.env["OPENAI_API_KEY"] === "string" && process.env["OPENAI_API_KEY"].length >= 10;
|
|
50
|
+
return {
|
|
51
|
+
tier: "openai",
|
|
52
|
+
available: hasKey,
|
|
53
|
+
reason: hasKey ? undefined : "OPENAI_API_KEY not set",
|
|
54
|
+
ms: Date.now() - t0,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
function probeOllama(timeoutMs = 500) {
|
|
58
|
+
return new Promise((resolve) => {
|
|
59
|
+
const t0 = Date.now();
|
|
60
|
+
const req = request({ host: "127.0.0.1", port: 11434, path: "/api/tags", method: "GET", timeout: timeoutMs }, (res) => {
|
|
61
|
+
res.resume();
|
|
62
|
+
resolve({ tier: "ollama", available: (res.statusCode ?? 0) < 500, ms: Date.now() - t0 });
|
|
63
|
+
});
|
|
64
|
+
req.on("timeout", () => {
|
|
65
|
+
req.destroy();
|
|
66
|
+
resolve({ tier: "ollama", available: false, reason: "Ollama 127.0.0.1:11434 timeout", ms: Date.now() - t0 });
|
|
67
|
+
});
|
|
68
|
+
req.on("error", (e) => resolve({ tier: "ollama", available: false, reason: `Ollama unreachable: ${e.message}`, ms: Date.now() - t0 }));
|
|
69
|
+
req.end();
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
async function probeBundled() {
|
|
73
|
+
const t0 = Date.now();
|
|
74
|
+
try {
|
|
75
|
+
// Just check that the package resolves; do NOT load model (kept cheap).
|
|
76
|
+
// The actual download happens lazily on first embed.
|
|
77
|
+
await import("@huggingface/transformers");
|
|
78
|
+
return { tier: "bundled", available: true, ms: Date.now() - t0 };
|
|
79
|
+
}
|
|
80
|
+
catch (e) {
|
|
81
|
+
return { tier: "bundled", available: false, reason: `@huggingface/transformers import failed: ${e.message}`, ms: Date.now() - t0 };
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
function probeHash() {
|
|
85
|
+
return { tier: "hash", available: true, ms: 0 };
|
|
86
|
+
}
|
|
87
|
+
export async function autodiagnose(repoRoot, opts) {
|
|
88
|
+
const t0 = Date.now();
|
|
89
|
+
const cfg = readConfig(repoRoot);
|
|
90
|
+
const currentName = cfg.embeddings?.provider ?? "unknown";
|
|
91
|
+
const currentTier = classifyEmbedderName(currentName);
|
|
92
|
+
const probes = [];
|
|
93
|
+
probes.push(probeOpenAI());
|
|
94
|
+
if (!opts?.skipOllama)
|
|
95
|
+
probes.push(await probeOllama());
|
|
96
|
+
if (!opts?.skipBundled)
|
|
97
|
+
probes.push(await probeBundled());
|
|
98
|
+
probes.push(probeHash());
|
|
99
|
+
// Pick best available.
|
|
100
|
+
let bestAvailable = "hash";
|
|
101
|
+
let bestRank = TIER_RANK.hash;
|
|
102
|
+
for (const p of probes) {
|
|
103
|
+
if (!p.available)
|
|
104
|
+
continue;
|
|
105
|
+
const r = TIER_RANK[p.tier];
|
|
106
|
+
if (r > bestRank) {
|
|
107
|
+
bestRank = r;
|
|
108
|
+
bestAvailable = p.tier;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
const hasUpgrade = TIER_RANK[bestAvailable] > TIER_RANK[currentTier];
|
|
112
|
+
let configPathWritten = null;
|
|
113
|
+
if (opts?.persist && hasUpgrade) {
|
|
114
|
+
const next = { ...cfg, embeddings: { ...(cfg.embeddings ?? {}), provider: bestAvailable } };
|
|
115
|
+
configPathWritten = writeConfig(repoRoot, next);
|
|
116
|
+
}
|
|
117
|
+
const currentBadge = tierInfo(currentTier).display;
|
|
118
|
+
const bestBadge = tierInfo(bestAvailable).display;
|
|
119
|
+
const headline = hasUpgrade
|
|
120
|
+
? `Upgrade available: currently on ${currentBadge} (★${tierInfo(currentTier).stars}), can upgrade to ${bestBadge} (★${tierInfo(bestAvailable).stars}).`
|
|
121
|
+
: `Already at best available tier: ${currentBadge} (★${tierInfo(currentTier).stars}).`;
|
|
122
|
+
const recommendation = hasUpgrade
|
|
123
|
+
? {
|
|
124
|
+
action: "switch-to",
|
|
125
|
+
tier: bestAvailable,
|
|
126
|
+
rationale: bestAvailable === "openai"
|
|
127
|
+
? "OPENAI_API_KEY detected; switch to ★★★★★ tier."
|
|
128
|
+
: bestAvailable === "ollama"
|
|
129
|
+
? "Ollama reachable locally; free ★★★★ semantic embeddings."
|
|
130
|
+
: bestAvailable === "bundled"
|
|
131
|
+
? "Bundled WASM available; ★★★ semantic embeddings without external service."
|
|
132
|
+
: "(no upgrade)",
|
|
133
|
+
}
|
|
134
|
+
: null;
|
|
135
|
+
return {
|
|
136
|
+
currentTier,
|
|
137
|
+
bestAvailable,
|
|
138
|
+
hasUpgrade,
|
|
139
|
+
probes,
|
|
140
|
+
headline,
|
|
141
|
+
recommendation,
|
|
142
|
+
configPathWritten,
|
|
143
|
+
ms: Date.now() - t0,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
/** Returns the resolved memory-tier info for the (possibly upgraded)
|
|
147
|
+
* current config. Convenience for pulse/status surfaces. */
|
|
148
|
+
export function currentTierInfo(repoRoot) {
|
|
149
|
+
const cfg = readConfig(repoRoot);
|
|
150
|
+
const name = cfg.embeddings?.provider ?? "unknown";
|
|
151
|
+
return tierInfo(classifyEmbedderName(name));
|
|
152
|
+
}
|
|
153
|
+
//# sourceMappingURL=embedder_autodiagnose.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"embedder_autodiagnose.js","sourceRoot":"","sources":["../src/embedder_autodiagnose.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"embedder_autodiagnose.test.d.ts","sourceRoot":"","sources":["../src/embedder_autodiagnose.test.ts"],"names":[],"mappings":"AAAA;;GAEG"}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* v1.65.1 -- Embedder autodiagnose tests.
|
|
3
|
+
*/
|
|
4
|
+
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
|
5
|
+
import { mkdtempSync, rmSync, mkdirSync, writeFileSync, readFileSync } from "node:fs";
|
|
6
|
+
import { tmpdir } from "node:os";
|
|
7
|
+
import { join } from "node:path";
|
|
8
|
+
import { autodiagnose, currentTierInfo } from "./embedder_autodiagnose.js";
|
|
9
|
+
function setup() { return mkdtempSync(join(tmpdir(), "mneme-autodiag-")); }
|
|
10
|
+
function cleanup(r) { try {
|
|
11
|
+
rmSync(r, { recursive: true, force: true });
|
|
12
|
+
}
|
|
13
|
+
catch { /* */ } }
|
|
14
|
+
function writeConfig(r, provider) {
|
|
15
|
+
mkdirSync(join(r, ".mneme"), { recursive: true });
|
|
16
|
+
writeFileSync(join(r, ".mneme/config.json"), JSON.stringify({
|
|
17
|
+
schemaVersion: 1,
|
|
18
|
+
embeddings: { provider, model: "Xenova/all-MiniLM-L6-v2" },
|
|
19
|
+
}, null, 2), "utf8");
|
|
20
|
+
}
|
|
21
|
+
describe("v1.65.1 EmbedderAutodiagnose", () => {
|
|
22
|
+
let r;
|
|
23
|
+
beforeEach(() => { r = setup(); });
|
|
24
|
+
afterEach(() => cleanup(r));
|
|
25
|
+
it("reports gap when config says hash but bundled is available", async () => {
|
|
26
|
+
writeConfig(r, "hash");
|
|
27
|
+
const report = await autodiagnose(r, { skipOllama: true });
|
|
28
|
+
expect(report.currentTier).toBe("hash");
|
|
29
|
+
// Bundled is reachable from this test environment (transformers installed).
|
|
30
|
+
expect(["bundled", "openai"]).toContain(report.bestAvailable);
|
|
31
|
+
expect(report.hasUpgrade).toBe(true);
|
|
32
|
+
expect(report.recommendation).not.toBeNull();
|
|
33
|
+
expect(report.recommendation?.action).toBe("switch-to");
|
|
34
|
+
});
|
|
35
|
+
it("reports no upgrade when already on best available", async () => {
|
|
36
|
+
writeConfig(r, "bundled");
|
|
37
|
+
const report = await autodiagnose(r, { skipOllama: true });
|
|
38
|
+
// If OPENAI_API_KEY is set in test env, bundled could be below best; allow either.
|
|
39
|
+
if (process.env["OPENAI_API_KEY"]) {
|
|
40
|
+
expect(report.hasUpgrade).toBe(true);
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
expect(report.hasUpgrade).toBe(false);
|
|
44
|
+
expect(report.recommendation).toBeNull();
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
it("persist=true rewrites config to bestAvailable", async () => {
|
|
48
|
+
writeConfig(r, "hash");
|
|
49
|
+
const report = await autodiagnose(r, { skipOllama: true, persist: true });
|
|
50
|
+
expect(report.configPathWritten).toBeTruthy();
|
|
51
|
+
const updated = JSON.parse(readFileSync(join(r, ".mneme/config.json"), "utf8"));
|
|
52
|
+
expect(updated.embeddings?.provider).toBe(report.bestAvailable);
|
|
53
|
+
});
|
|
54
|
+
it("persist=false leaves config untouched even when upgrade exists", async () => {
|
|
55
|
+
writeConfig(r, "hash");
|
|
56
|
+
const report = await autodiagnose(r, { skipOllama: true, persist: false });
|
|
57
|
+
expect(report.configPathWritten).toBeNull();
|
|
58
|
+
const after = JSON.parse(readFileSync(join(r, ".mneme/config.json"), "utf8"));
|
|
59
|
+
expect(after.embeddings?.provider).toBe("hash");
|
|
60
|
+
});
|
|
61
|
+
it("skipBundled + skipOllama falls back to hash baseline", async () => {
|
|
62
|
+
writeConfig(r, "hash");
|
|
63
|
+
const report = await autodiagnose(r, { skipOllama: true, skipBundled: true });
|
|
64
|
+
if (!process.env["OPENAI_API_KEY"]) {
|
|
65
|
+
expect(report.bestAvailable).toBe("hash");
|
|
66
|
+
expect(report.hasUpgrade).toBe(false);
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
it("returns probes for each candidate tier", async () => {
|
|
70
|
+
writeConfig(r, "hash");
|
|
71
|
+
const report = await autodiagnose(r, { skipOllama: true });
|
|
72
|
+
const tiers = report.probes.map((p) => p.tier);
|
|
73
|
+
expect(tiers).toContain("openai");
|
|
74
|
+
expect(tiers).toContain("bundled");
|
|
75
|
+
expect(tiers).toContain("hash");
|
|
76
|
+
});
|
|
77
|
+
it("currentTierInfo reads from config", () => {
|
|
78
|
+
writeConfig(r, "bundled");
|
|
79
|
+
const info = currentTierInfo(r);
|
|
80
|
+
expect(info.name).toBe("bundled");
|
|
81
|
+
expect(info.semantic).toBe(true);
|
|
82
|
+
});
|
|
83
|
+
it("currentTierInfo returns unknown when no config", () => {
|
|
84
|
+
const info = currentTierInfo(r);
|
|
85
|
+
expect(info.name).toBe("unknown");
|
|
86
|
+
});
|
|
87
|
+
it("headline mentions upgrade when one exists", async () => {
|
|
88
|
+
writeConfig(r, "hash");
|
|
89
|
+
const report = await autodiagnose(r, { skipOllama: true });
|
|
90
|
+
if (report.hasUpgrade) {
|
|
91
|
+
expect(report.headline.toLowerCase()).toContain("upgrade");
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
it("hash tier always available as floor", async () => {
|
|
95
|
+
writeConfig(r, "hash");
|
|
96
|
+
const report = await autodiagnose(r, { skipOllama: true, skipBundled: true });
|
|
97
|
+
const hashProbe = report.probes.find((p) => p.tier === "hash");
|
|
98
|
+
expect(hashProbe?.available).toBe(true);
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
//# sourceMappingURL=embedder_autodiagnose.test.js.map
|