gnosys 5.9.0 → 5.9.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/dist/index.d.ts +6 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +158 -94
- package/dist/index.js.map +1 -1
- package/dist/lib/projectIdentity.d.ts.map +1 -1
- package/dist/lib/projectIdentity.js +14 -0
- package/dist/lib/projectIdentity.js.map +1 -1
- package/dist/lib/resolver.d.ts.map +1 -1
- package/dist/lib/resolver.js +39 -4
- package/dist/lib/resolver.js.map +1 -1
- package/dist/lib/setup/sections/preferences.d.ts +8 -3
- package/dist/lib/setup/sections/preferences.d.ts.map +1 -1
- package/dist/lib/setup/sections/preferences.js +42 -15
- package/dist/lib/setup/sections/preferences.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -4,5 +4,10 @@
|
|
|
4
4
|
* Exposes memory operations as MCP tools that any agent can call.
|
|
5
5
|
* Supports layered stores: project (auto-discovered), personal, global, optional.
|
|
6
6
|
*/
|
|
7
|
-
|
|
7
|
+
/**
|
|
8
|
+
* Returns once the heavy deps have been loaded and module-level vars
|
|
9
|
+
* (ingestion, hybridSearch, askEngine) are populated. Handlers that
|
|
10
|
+
* need any of these should `await ensureHeavyDeps()` first.
|
|
11
|
+
*/
|
|
12
|
+
export declare function ensureHeavyDeps(): Promise<void>;
|
|
8
13
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA;;;;GAIG"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA;;;;GAIG;AAg/GH;;;;GAIG;AACH,wBAAgB,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC,CAE/C"}
|
package/dist/index.js
CHANGED
|
@@ -43,26 +43,17 @@ import { z } from "zod";
|
|
|
43
43
|
import fs from "fs/promises";
|
|
44
44
|
import { GnosysSearch } from "./lib/search.js";
|
|
45
45
|
import { GnosysTagRegistry } from "./lib/tags.js";
|
|
46
|
-
import { performImport, formatImportSummary, estimateDuration } from "./lib/import.js";
|
|
47
|
-
import { GnosysIngestion } from "./lib/ingest.js";
|
|
48
46
|
import { GnosysResolver } from "./lib/resolver.js";
|
|
49
47
|
import { applyLens } from "./lib/lensing.js";
|
|
50
48
|
import { getFileHistory, rollbackToCommit, hasGitHistory } from "./lib/history.js";
|
|
51
49
|
import { groupByPeriod, computeStats } from "./lib/timeline.js";
|
|
52
50
|
import { buildLinkGraph, getBacklinks, getOutgoingLinks, formatGraphSummary } from "./lib/wikilinks.js";
|
|
53
|
-
import { bootstrap } from "./lib/bootstrap.js";
|
|
54
51
|
import { loadConfig, DEFAULT_CONFIG } from "./lib/config.js";
|
|
55
|
-
import { GnosysEmbeddings } from "./lib/embeddings.js";
|
|
56
|
-
import { GnosysHybridSearch } from "./lib/hybridSearch.js";
|
|
57
|
-
import { GnosysAsk } from "./lib/ask.js";
|
|
58
52
|
import { getLLMProvider } from "./lib/llm.js";
|
|
59
|
-
import { GnosysMaintenanceEngine, formatMaintenanceReport } from "./lib/maintenance.js";
|
|
60
53
|
import { recall, formatRecall } from "./lib/recall.js";
|
|
61
54
|
import { initAudit, readAuditLog, formatAuditTimeline } from "./lib/audit.js";
|
|
62
55
|
import { GnosysDB } from "./lib/db.js";
|
|
63
56
|
import { syncMemoryToDb, syncUpdateToDb, syncDearchiveToDb, syncReinforcementToDb, auditToDb } from "./lib/dbWrite.js";
|
|
64
|
-
import { GnosysDreamEngine, DreamScheduler, formatDreamReport } from "./lib/dream.js";
|
|
65
|
-
import { GnosysExporter, formatExportReport } from "./lib/export.js";
|
|
66
57
|
import { createProjectIdentity, readProjectIdentity } from "./lib/projectIdentity.js";
|
|
67
58
|
import { setPreference, getPreference, getAllPreferences, deletePreference } from "./lib/preferences.js";
|
|
68
59
|
import { syncRules, generateRulesBlock } from "./lib/rulesGen.js";
|
|
@@ -503,7 +494,11 @@ server.tool("gnosys_add", "Add a new memory. Accepts raw text — an LLM structu
|
|
|
503
494
|
isError: true,
|
|
504
495
|
};
|
|
505
496
|
}
|
|
506
|
-
//
|
|
497
|
+
// v5.9.1 (#100): ingestion is constructed in the background after
|
|
498
|
+
// server.connect() responds to the MCP handshake. Wait for that to
|
|
499
|
+
// finish before proceeding — first call after a fresh MCP spawn
|
|
500
|
+
// may pause here briefly.
|
|
501
|
+
await ensureHeavyDeps();
|
|
507
502
|
if (!ingestion) {
|
|
508
503
|
return {
|
|
509
504
|
content: [
|
|
@@ -796,6 +791,7 @@ server.tool("gnosys_init", "Initialize Gnosys in a project directory. Creates .g
|
|
|
796
791
|
search = new GnosysSearch(writeTarget.store.getStorePath());
|
|
797
792
|
tagRegistry = new GnosysTagRegistry(writeTarget.store.getStorePath());
|
|
798
793
|
await tagRegistry.load();
|
|
794
|
+
const { GnosysIngestion } = await import("./lib/ingest.js");
|
|
799
795
|
ingestion = new GnosysIngestion(writeTarget.store, tagRegistry);
|
|
800
796
|
await reindexAllStores();
|
|
801
797
|
}
|
|
@@ -876,6 +872,7 @@ server.tool("gnosys_migrate", "Migrate a Gnosys store (.gnosys/) from one direct
|
|
|
876
872
|
search = new GnosysSearch(writeTarget.store.getStorePath());
|
|
877
873
|
tagRegistry = new GnosysTagRegistry(writeTarget.store.getStorePath());
|
|
878
874
|
await tagRegistry.load();
|
|
875
|
+
const { GnosysIngestion } = await import("./lib/ingest.js");
|
|
879
876
|
ingestion = new GnosysIngestion(writeTarget.store, tagRegistry);
|
|
880
877
|
await reindexAllStores();
|
|
881
878
|
}
|
|
@@ -1057,6 +1054,11 @@ server.tool("gnosys_commit_context", "Pre-compaction memory sweep. Call this bef
|
|
|
1057
1054
|
// happens against `ctx.config` (merged project+global) below; if that
|
|
1058
1055
|
// can't find a provider, getLLMProvider() surfaces a provider-specific
|
|
1059
1056
|
// error message.
|
|
1057
|
+
//
|
|
1058
|
+
// v5.9.1 (#100): wait for the background heavy-init to populate
|
|
1059
|
+
// `ingestion` if it hasn't yet (first call after a fresh MCP spawn
|
|
1060
|
+
// may pause here while @huggingface/transformers etc. load).
|
|
1061
|
+
await ensureHeavyDeps();
|
|
1060
1062
|
if (!ingestion) {
|
|
1061
1063
|
return {
|
|
1062
1064
|
content: [{ type: "text", text: "Ingestion module not initialized." }],
|
|
@@ -1471,6 +1473,8 @@ server.tool("gnosys_bootstrap", "Batch-import existing documents from a director
|
|
|
1471
1473
|
return { content: [{ type: "text", text: "No writable store found." }], isError: true };
|
|
1472
1474
|
}
|
|
1473
1475
|
try {
|
|
1476
|
+
// v5.9.1 (#100): bootstrap pulls in heavy file-walking deps — load lazily.
|
|
1477
|
+
const { bootstrap } = await import("./lib/bootstrap.js");
|
|
1474
1478
|
const result = await bootstrap(writeTarget.store, {
|
|
1475
1479
|
sourceDir,
|
|
1476
1480
|
patterns,
|
|
@@ -1549,6 +1553,8 @@ server.tool("gnosys_import", "Bulk import structured data (CSV, JSON, JSONL) int
|
|
|
1549
1553
|
}
|
|
1550
1554
|
const effectiveMode = mode || "structured";
|
|
1551
1555
|
try {
|
|
1556
|
+
// v5.9.1 (#100): import.js pulls mammoth + pdf-parse + turndown.
|
|
1557
|
+
const { performImport, formatImportSummary, estimateDuration } = await import("./lib/import.js");
|
|
1552
1558
|
const result = await performImport(writeTarget.store, ingestion, {
|
|
1553
1559
|
format: format,
|
|
1554
1560
|
data,
|
|
@@ -1599,6 +1605,8 @@ server.tool("gnosys_hybrid_search", "Search memories using hybrid keyword + sema
|
|
|
1599
1605
|
}, async ({ query, limit, mode, projectRoot }) => {
|
|
1600
1606
|
// Note: hybridSearch is module-level (heavy) and not scoped per project
|
|
1601
1607
|
(projectRoot); // quiets unused warning if any
|
|
1608
|
+
// v5.9.1 (#100): wait for the background heavy-init to finish.
|
|
1609
|
+
await ensureHeavyDeps();
|
|
1602
1610
|
if (!hybridSearch) {
|
|
1603
1611
|
return {
|
|
1604
1612
|
content: [{ type: "text", text: "Hybrid search not initialized. No stores found." }],
|
|
@@ -1619,6 +1627,8 @@ server.tool("gnosys_hybrid_search", "Search memories using hybrid keyword + sema
|
|
|
1619
1627
|
// Use default resolver here since hybridSearch operates across all stores
|
|
1620
1628
|
const writeTarget = resolver.getWriteTarget();
|
|
1621
1629
|
if (writeTarget) {
|
|
1630
|
+
// v5.9.1 (#100): lazy-load the maintenance module here too.
|
|
1631
|
+
const { GnosysMaintenanceEngine } = await import("./lib/maintenance.js");
|
|
1622
1632
|
GnosysMaintenanceEngine.reinforceBatch(writeTarget.store, results.map((r) => r.relativePath)).catch(() => { }); // Fire-and-forget
|
|
1623
1633
|
}
|
|
1624
1634
|
const embCount = hybridSearch.embeddingCount();
|
|
@@ -1646,6 +1656,7 @@ server.tool("gnosys_semantic_search", "Search memories using semantic similarity
|
|
|
1646
1656
|
}, async ({ query, limit, projectRoot }) => {
|
|
1647
1657
|
// Note: hybridSearch is module-level (heavy) and not scoped per project
|
|
1648
1658
|
(projectRoot); // quiets unused warning if any
|
|
1659
|
+
await ensureHeavyDeps();
|
|
1649
1660
|
if (!hybridSearch) {
|
|
1650
1661
|
return {
|
|
1651
1662
|
content: [{ type: "text", text: "Search not initialized. No stores found." }],
|
|
@@ -1677,6 +1688,7 @@ server.tool("gnosys_semantic_search", "Search memories using semantic similarity
|
|
|
1677
1688
|
server.tool("gnosys_reindex", "Rebuild all semantic embeddings from every memory file. Downloads the embedding model (~80 MB) on first run. Required before hybrid/semantic search can be used. Safe to re-run — fully regenerates the index.", { projectRoot: projectRootParam }, async ({ projectRoot }) => {
|
|
1678
1689
|
// Note: reindex operates on all stores, projectRoot is for API consistency
|
|
1679
1690
|
(projectRoot); // quiets unused warning if any
|
|
1691
|
+
await ensureHeavyDeps();
|
|
1680
1692
|
if (!hybridSearch) {
|
|
1681
1693
|
return {
|
|
1682
1694
|
content: [{ type: "text", text: "No stores found. Initialize a store with gnosys_init first." }],
|
|
@@ -1712,6 +1724,7 @@ server.tool("gnosys_ask", "Ask a natural-language question and get a synthesized
|
|
|
1712
1724
|
}, async ({ question, limit, mode, projectRoot }) => {
|
|
1713
1725
|
// Note: askEngine is module-level (heavy) and not scoped per project
|
|
1714
1726
|
(projectRoot); // quiets unused warning if any
|
|
1727
|
+
await ensureHeavyDeps();
|
|
1715
1728
|
if (!askEngine) {
|
|
1716
1729
|
return {
|
|
1717
1730
|
content: [{ type: "text", text: "Ask engine not initialized. Ensure stores exist and an LLM provider is configured." }],
|
|
@@ -1726,6 +1739,8 @@ server.tool("gnosys_ask", "Ask a natural-language question and get a synthesized
|
|
|
1726
1739
|
// Reinforce used memories (best-effort, non-blocking)
|
|
1727
1740
|
const writeTarget = resolver.getWriteTarget();
|
|
1728
1741
|
if (writeTarget && result.sources.length > 0) {
|
|
1742
|
+
// v5.9.1 (#100): lazy-load the maintenance module here too.
|
|
1743
|
+
const { GnosysMaintenanceEngine } = await import("./lib/maintenance.js");
|
|
1729
1744
|
GnosysMaintenanceEngine.reinforceBatch(writeTarget.store, result.sources.map((s) => s.relativePath)).catch(() => { }); // Fire-and-forget
|
|
1730
1745
|
}
|
|
1731
1746
|
const sourcesText = result.sources.length > 0
|
|
@@ -1765,6 +1780,9 @@ server.tool("gnosys_maintain", "Run vault maintenance: detect duplicate memories
|
|
|
1765
1780
|
}, async ({ dryRun, autoApply, projectRoot }) => {
|
|
1766
1781
|
const ctx = await resolveToolContext(projectRoot);
|
|
1767
1782
|
try {
|
|
1783
|
+
// v5.9.1 (#100): maintenance engine pulls LLM machinery for the
|
|
1784
|
+
// discoverRelationships / generateSummaries phases — load lazily.
|
|
1785
|
+
const { GnosysMaintenanceEngine, formatMaintenanceReport } = await import("./lib/maintenance.js");
|
|
1768
1786
|
const engine = new GnosysMaintenanceEngine(ctx.resolver, ctx.config);
|
|
1769
1787
|
const report = await engine.maintain({
|
|
1770
1788
|
dryRun: dryRun ?? true,
|
|
@@ -1893,6 +1911,8 @@ server.tool("gnosys_dream", "Run a Dream Mode cycle — idle-time consolidation
|
|
|
1893
1911
|
provider: ctx.config?.dream?.provider || "ollama",
|
|
1894
1912
|
model: ctx.config?.dream?.model,
|
|
1895
1913
|
};
|
|
1914
|
+
// v5.9.1 (#100): dream engine pulls LLM provider machinery — load lazily.
|
|
1915
|
+
const { GnosysDreamEngine, formatDreamReport } = await import("./lib/dream.js");
|
|
1896
1916
|
const engine = new GnosysDreamEngine(ctx.centralDb, ctx.config || DEFAULT_CONFIG, dreamConfig);
|
|
1897
1917
|
const report = await engine.dream((phase, detail) => {
|
|
1898
1918
|
console.error(`[dream:${phase}] ${detail}`);
|
|
@@ -1927,6 +1947,8 @@ server.tool("gnosys_export", "Export gnosys.db to Obsidian-compatible vault —
|
|
|
1927
1947
|
],
|
|
1928
1948
|
};
|
|
1929
1949
|
}
|
|
1950
|
+
// v5.9.1 (#100): exporter pulls obsidian-flavored markdown gen — lazy.
|
|
1951
|
+
const { GnosysExporter, formatExportReport } = await import("./lib/export.js");
|
|
1930
1952
|
const exporter = new GnosysExporter(ctx.centralDb);
|
|
1931
1953
|
const report = await exporter.export({
|
|
1932
1954
|
targetDir: params.targetDir,
|
|
@@ -2780,6 +2802,113 @@ This marks the conversation checkpoint so the next /gnosys-memorize only process
|
|
|
2780
2802
|
],
|
|
2781
2803
|
};
|
|
2782
2804
|
});
|
|
2805
|
+
// ─── Heavy module initialization (deferred) ───────────────────────────────
|
|
2806
|
+
//
|
|
2807
|
+
// v5.9.1 (#100). Constructs GnosysIngestion / GnosysEmbeddings /
|
|
2808
|
+
// GnosysHybridSearch / GnosysAsk / GnosysDreamEngine. These modules pull
|
|
2809
|
+
// in @huggingface/transformers (80MB), mammoth, pdf-parse, turndown, and
|
|
2810
|
+
// LLM provider SDKs — together ~24s of import time on cold disk. By
|
|
2811
|
+
// running them AFTER server.connect() responds to the MCP handshake, we
|
|
2812
|
+
// avoid the client-side timeout (Grok Build = 10s default).
|
|
2813
|
+
let heavyDepsReadyResolve = null;
|
|
2814
|
+
const heavyDepsReady = new Promise((resolve) => {
|
|
2815
|
+
heavyDepsReadyResolve = resolve;
|
|
2816
|
+
});
|
|
2817
|
+
/**
|
|
2818
|
+
* Returns once the heavy deps have been loaded and module-level vars
|
|
2819
|
+
* (ingestion, hybridSearch, askEngine) are populated. Handlers that
|
|
2820
|
+
* need any of these should `await ensureHeavyDeps()` first.
|
|
2821
|
+
*/
|
|
2822
|
+
export function ensureHeavyDeps() {
|
|
2823
|
+
return heavyDepsReady;
|
|
2824
|
+
}
|
|
2825
|
+
async function initHeavyDeps() {
|
|
2826
|
+
const writeTarget = resolver.getWriteTarget();
|
|
2827
|
+
if (!writeTarget || !tagRegistry || !search) {
|
|
2828
|
+
heavyDepsReadyResolve?.();
|
|
2829
|
+
return;
|
|
2830
|
+
}
|
|
2831
|
+
// Ingestion (used by gnosys_add, gnosys_commit_context).
|
|
2832
|
+
const { GnosysIngestion } = await import("./lib/ingest.js");
|
|
2833
|
+
ingestion = new GnosysIngestion(writeTarget.store, tagRegistry, config);
|
|
2834
|
+
console.error(`LLM ingestion: ${ingestion.isLLMAvailable ? `enabled (${ingestion.providerName})` : "disabled (configure LLM provider)"}`);
|
|
2835
|
+
// Hybrid search + ask (used by gnosys_hybrid_search / gnosys_ask).
|
|
2836
|
+
const { GnosysEmbeddings } = await import("./lib/embeddings.js");
|
|
2837
|
+
const { GnosysHybridSearch } = await import("./lib/hybridSearch.js");
|
|
2838
|
+
const { GnosysAsk } = await import("./lib/ask.js");
|
|
2839
|
+
const embeddings = new GnosysEmbeddings(writeTarget.store.getStorePath());
|
|
2840
|
+
hybridSearch = new GnosysHybridSearch(search, embeddings, resolver, writeTarget.store.getStorePath(), gnosysDb || undefined);
|
|
2841
|
+
askEngine = new GnosysAsk(hybridSearch, config, resolver, writeTarget.store.getStorePath());
|
|
2842
|
+
const embCount = embeddings.hasEmbeddings() ? embeddings.count() : 0;
|
|
2843
|
+
console.error(`Hybrid search: ${embCount > 0 ? `ready (${embCount} embeddings)` : "available (run gnosys_reindex to build embeddings)"}`);
|
|
2844
|
+
console.error(`Ask engine: ${askEngine.isLLMAvailable ? `ready (${askEngine.providerName}/${askEngine.modelName})` : "disabled (configure LLM provider)"}`);
|
|
2845
|
+
// Dream mode (only constructed if enabled; designation gate inside start()).
|
|
2846
|
+
if (gnosysDb && config.dream?.enabled) {
|
|
2847
|
+
const { GnosysDreamEngine, DreamScheduler } = await import("./lib/dream.js");
|
|
2848
|
+
const dreamEngine = new GnosysDreamEngine(gnosysDb, config, config.dream);
|
|
2849
|
+
dreamScheduler = new DreamScheduler(dreamEngine, config.dream);
|
|
2850
|
+
// Layer 3: probe the dream provider if this machine is the dream node.
|
|
2851
|
+
try {
|
|
2852
|
+
const designated = gnosysDb.getDreamMachineId();
|
|
2853
|
+
const localId = gnosysDb.getMeta("machine_id");
|
|
2854
|
+
if (designated && designated === localId) {
|
|
2855
|
+
const dreamProvider = config.dream.provider || "ollama";
|
|
2856
|
+
const dreamModel = config.dream.model || "(default)";
|
|
2857
|
+
const { validateModel } = await import("./lib/modelValidation.js");
|
|
2858
|
+
const envVarName = `GNOSYS_${dreamProvider.toUpperCase()}_KEY`;
|
|
2859
|
+
let apiKey = process.env[envVarName] || "";
|
|
2860
|
+
if (!apiKey && process.platform === "darwin" && dreamProvider !== "ollama" && dreamProvider !== "lmstudio") {
|
|
2861
|
+
try {
|
|
2862
|
+
const { execSync } = await import("child_process");
|
|
2863
|
+
apiKey = execSync(`security find-generic-password -a "$USER" -s "${envVarName}" -w 2>/dev/null`, {
|
|
2864
|
+
stdio: "pipe", encoding: "utf-8", timeout: 2000,
|
|
2865
|
+
}).trim();
|
|
2866
|
+
}
|
|
2867
|
+
catch {
|
|
2868
|
+
// No key in keychain
|
|
2869
|
+
}
|
|
2870
|
+
}
|
|
2871
|
+
if (!apiKey && dreamProvider !== "ollama" && dreamProvider !== "lmstudio") {
|
|
2872
|
+
process.stderr.write(`gnosys: dream provider '${dreamProvider}/${dreamModel}' has no API key configured.\n` +
|
|
2873
|
+
` This machine is designated to dream, but the LLM cannot be called.\n` +
|
|
2874
|
+
` Run 'gnosys setup dream' to reconfigure.\n`);
|
|
2875
|
+
}
|
|
2876
|
+
else {
|
|
2877
|
+
const result = await Promise.race([
|
|
2878
|
+
validateModel(dreamProvider, dreamModel, apiKey),
|
|
2879
|
+
new Promise((resolve) => setTimeout(() => resolve({ ok: false, error: "probe timeout (5s)" }), 5000)),
|
|
2880
|
+
]);
|
|
2881
|
+
if (!result.ok) {
|
|
2882
|
+
process.stderr.write(`gnosys: dream provider '${dreamProvider}/${dreamModel}' is unreachable at startup.\n` +
|
|
2883
|
+
` This machine is designated to dream, but the LLM cannot be called.\n` +
|
|
2884
|
+
` Error: ${("error" in result && result.error) || "unknown"}\n` +
|
|
2885
|
+
` Run 'gnosys setup dream' to reconfigure.\n`);
|
|
2886
|
+
}
|
|
2887
|
+
}
|
|
2888
|
+
}
|
|
2889
|
+
}
|
|
2890
|
+
catch {
|
|
2891
|
+
// Probe failed — non-fatal. Continue with scheduler start.
|
|
2892
|
+
}
|
|
2893
|
+
dreamScheduler.start();
|
|
2894
|
+
const designated = gnosysDb.getDreamMachineId();
|
|
2895
|
+
const localId = gnosysDb.getMeta("machine_id");
|
|
2896
|
+
if (!designated) {
|
|
2897
|
+
console.error(`Dream Mode: enabled but no machine designated. Run 'gnosys setup dream' on the machine you want to host dreams.`);
|
|
2898
|
+
}
|
|
2899
|
+
else if (designated !== localId) {
|
|
2900
|
+
console.error(`Dream Mode: enabled — designated to '${designated}'. This machine (${localId || "?"}) will not dream.`);
|
|
2901
|
+
}
|
|
2902
|
+
else {
|
|
2903
|
+
console.error(`Dream Mode: enabled on this machine (idle ${config.dream.idleMinutes}min, max ${config.dream.maxRuntimeMinutes}min)`);
|
|
2904
|
+
}
|
|
2905
|
+
}
|
|
2906
|
+
else {
|
|
2907
|
+
console.error(`Dream Mode: disabled (run 'gnosys setup dream' to configure)`);
|
|
2908
|
+
}
|
|
2909
|
+
console.error("Gnosys MCP: heavy modules ready");
|
|
2910
|
+
heavyDepsReadyResolve?.();
|
|
2911
|
+
}
|
|
2783
2912
|
// ─── Start the server ────────────────────────────────────────────────────
|
|
2784
2913
|
async function main() {
|
|
2785
2914
|
// v5.7.1 (#15): start the upgrade-marker watcher BEFORE anything else.
|
|
@@ -2810,7 +2939,12 @@ async function main() {
|
|
|
2810
2939
|
console.error("Gnosys MCP server starting.");
|
|
2811
2940
|
console.error("Active stores:");
|
|
2812
2941
|
console.error(resolver.getSummary());
|
|
2813
|
-
// Initialize search from the first writable store
|
|
2942
|
+
// Initialize search from the first writable store. Everything in this
|
|
2943
|
+
// block is FAST — opening the search index + tag registry + loading
|
|
2944
|
+
// gnosys.json. The slow stuff (LLM providers, transformers embeddings,
|
|
2945
|
+
// pdf/docx parsers) is deferred to `initHeavyDeps()` below so the MCP
|
|
2946
|
+
// server can answer the `initialize` handshake within the client's
|
|
2947
|
+
// timeout (Grok Build is 10s, Claude Code is ~15s).
|
|
2814
2948
|
const writeTarget = resolver.getWriteTarget();
|
|
2815
2949
|
if (writeTarget) {
|
|
2816
2950
|
search = new GnosysSearch(writeTarget.store.getStorePath());
|
|
@@ -2823,7 +2957,6 @@ async function main() {
|
|
|
2823
2957
|
catch (err) {
|
|
2824
2958
|
console.error(`Warning: Failed to load gnosys.json: ${err instanceof Error ? err.message : err}`);
|
|
2825
2959
|
}
|
|
2826
|
-
ingestion = new GnosysIngestion(writeTarget.store, tagRegistry, config);
|
|
2827
2960
|
// Initialize audit logging
|
|
2828
2961
|
initAudit(writeTarget.store.getStorePath());
|
|
2829
2962
|
// Build search index across all stores
|
|
@@ -2831,91 +2964,22 @@ async function main() {
|
|
|
2831
2964
|
// v5.2: gnosysDb now points to the central DB (sole source of truth).
|
|
2832
2965
|
// No local project DB is created or opened.
|
|
2833
2966
|
gnosysDb = centralDb;
|
|
2834
|
-
|
|
2835
|
-
const embeddings = new GnosysEmbeddings(writeTarget.store.getStorePath());
|
|
2836
|
-
hybridSearch = new GnosysHybridSearch(search, embeddings, resolver, writeTarget.store.getStorePath(), gnosysDb || undefined);
|
|
2837
|
-
askEngine = new GnosysAsk(hybridSearch, config, resolver, writeTarget.store.getStorePath());
|
|
2838
|
-
const embCount = embeddings.hasEmbeddings() ? embeddings.count() : 0;
|
|
2839
|
-
console.error(`LLM ingestion: ${ingestion.isLLMAvailable ? `enabled (${ingestion.providerName})` : "disabled (configure LLM provider)"}`);
|
|
2840
|
-
console.error(`Hybrid search: ${embCount > 0 ? `ready (${embCount} embeddings)` : "available (run gnosys_reindex to build embeddings)"}`);
|
|
2841
|
-
console.error(`Ask engine: ${askEngine.isLLMAvailable ? `ready (${askEngine.providerName}/${askEngine.modelName})` : "disabled (configure LLM provider)"}`);
|
|
2842
|
-
// v2.0: Initialize Dream Mode (idle-time consolidation)
|
|
2843
|
-
// v5.4.2: Designation gate inside DreamScheduler.start() — only the
|
|
2844
|
-
// machine designated via `gnosys setup dream` arms the timer. Other
|
|
2845
|
-
// machines no-op silently. Layer 3 startup probe surfaces a stderr
|
|
2846
|
-
// warning if this machine is designated but the dream provider is
|
|
2847
|
-
// unreachable.
|
|
2848
|
-
if (gnosysDb && config.dream?.enabled) {
|
|
2849
|
-
const dreamEngine = new GnosysDreamEngine(gnosysDb, config, config.dream);
|
|
2850
|
-
dreamScheduler = new DreamScheduler(dreamEngine, config.dream);
|
|
2851
|
-
// Layer 3: probe the dream provider if this machine is the dream node.
|
|
2852
|
-
// Done before scheduler.start() so users see the warning immediately
|
|
2853
|
-
// alongside other startup output.
|
|
2854
|
-
try {
|
|
2855
|
-
const designated = gnosysDb.getDreamMachineId();
|
|
2856
|
-
const localId = gnosysDb.getMeta("machine_id");
|
|
2857
|
-
if (designated && designated === localId) {
|
|
2858
|
-
// Quick reachability probe — 5s timeout to avoid blocking startup.
|
|
2859
|
-
const dreamProvider = config.dream.provider || "ollama";
|
|
2860
|
-
const dreamModel = config.dream.model || "(default)";
|
|
2861
|
-
const { validateModel } = await import("./lib/modelValidation.js");
|
|
2862
|
-
// Resolve API key from env or keychain (mirroring resolveApiKey precedence).
|
|
2863
|
-
const envVarName = `GNOSYS_${dreamProvider.toUpperCase()}_KEY`;
|
|
2864
|
-
let apiKey = process.env[envVarName] || "";
|
|
2865
|
-
if (!apiKey && process.platform === "darwin" && dreamProvider !== "ollama" && dreamProvider !== "lmstudio") {
|
|
2866
|
-
try {
|
|
2867
|
-
const { execSync } = await import("child_process");
|
|
2868
|
-
apiKey = execSync(`security find-generic-password -a "$USER" -s "${envVarName}" -w 2>/dev/null`, {
|
|
2869
|
-
stdio: "pipe", encoding: "utf-8", timeout: 2000,
|
|
2870
|
-
}).trim();
|
|
2871
|
-
}
|
|
2872
|
-
catch {
|
|
2873
|
-
// No key in keychain
|
|
2874
|
-
}
|
|
2875
|
-
}
|
|
2876
|
-
// Skip the network probe if there's clearly no key configured for a
|
|
2877
|
-
// remote provider — surface the obvious config gap instead.
|
|
2878
|
-
if (!apiKey && dreamProvider !== "ollama" && dreamProvider !== "lmstudio") {
|
|
2879
|
-
process.stderr.write(`gnosys: dream provider '${dreamProvider}/${dreamModel}' has no API key configured.\n` +
|
|
2880
|
-
` This machine is designated to dream, but the LLM cannot be called.\n` +
|
|
2881
|
-
` Run 'gnosys setup dream' to reconfigure.\n`);
|
|
2882
|
-
}
|
|
2883
|
-
else {
|
|
2884
|
-
const result = await Promise.race([
|
|
2885
|
-
validateModel(dreamProvider, dreamModel, apiKey),
|
|
2886
|
-
new Promise((resolve) => setTimeout(() => resolve({ ok: false, error: "probe timeout (5s)" }), 5000)),
|
|
2887
|
-
]);
|
|
2888
|
-
if (!result.ok) {
|
|
2889
|
-
process.stderr.write(`gnosys: dream provider '${dreamProvider}/${dreamModel}' is unreachable at startup.\n` +
|
|
2890
|
-
` This machine is designated to dream, but the LLM cannot be called.\n` +
|
|
2891
|
-
` Error: ${("error" in result && result.error) || "unknown"}\n` +
|
|
2892
|
-
` Run 'gnosys setup dream' to reconfigure.\n`);
|
|
2893
|
-
}
|
|
2894
|
-
}
|
|
2895
|
-
}
|
|
2896
|
-
}
|
|
2897
|
-
catch {
|
|
2898
|
-
// Probe failed — non-fatal. Continue with scheduler start.
|
|
2899
|
-
}
|
|
2900
|
-
dreamScheduler.start();
|
|
2901
|
-
const designated = gnosysDb.getDreamMachineId();
|
|
2902
|
-
const localId = gnosysDb.getMeta("machine_id");
|
|
2903
|
-
if (!designated) {
|
|
2904
|
-
console.error(`Dream Mode: enabled but no machine designated. Run 'gnosys setup dream' on the machine you want to host dreams.`);
|
|
2905
|
-
}
|
|
2906
|
-
else if (designated !== localId) {
|
|
2907
|
-
console.error(`Dream Mode: enabled — designated to '${designated}'. This machine (${localId || "?"}) will not dream.`);
|
|
2908
|
-
}
|
|
2909
|
-
else {
|
|
2910
|
-
console.error(`Dream Mode: enabled on this machine (idle ${config.dream.idleMinutes}min, max ${config.dream.maxRuntimeMinutes}min)`);
|
|
2911
|
-
}
|
|
2912
|
-
}
|
|
2913
|
-
else {
|
|
2914
|
-
console.error(`Dream Mode: disabled (run 'gnosys setup dream' to configure)`);
|
|
2915
|
-
}
|
|
2967
|
+
console.error("Gnosys MCP: light init complete; LLM/embeddings/dream loading in background…");
|
|
2916
2968
|
}
|
|
2969
|
+
// v5.9.1 (#100): connect EARLY so the MCP `initialize` handshake responds
|
|
2970
|
+
// before we incur the ~24s heavy-import cost. After connect, kick off
|
|
2971
|
+
// heavy module initialization in the background. Handlers that use the
|
|
2972
|
+
// module-level `ingestion` / `hybridSearch` / `askEngine` vars guard
|
|
2973
|
+
// against null and either await readiness or surface a clear error.
|
|
2917
2974
|
const transport = new StdioServerTransport();
|
|
2918
2975
|
await server.connect(transport);
|
|
2976
|
+
console.error("Gnosys MCP: handshake ready (heavy modules still loading)");
|
|
2977
|
+
// Kick off heavy deps; fire-and-forget. Errors here are non-fatal —
|
|
2978
|
+
// they're logged to stderr and the affected handlers will report the
|
|
2979
|
+
// failure on first use.
|
|
2980
|
+
void initHeavyDeps().catch((err) => {
|
|
2981
|
+
console.error(`Gnosys MCP: heavy-init failed — ${err instanceof Error ? err.message : err}`);
|
|
2982
|
+
});
|
|
2919
2983
|
// ─── MCP Roots Support (multi-project awareness) ───────────────────────
|
|
2920
2984
|
// After connecting, request workspace roots from the host. This lets us
|
|
2921
2985
|
// discover .gnosys stores in all open projects, not just the cwd.
|