gnosys 5.11.4 → 5.12.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +377 -5162
- package/dist/index.js +542 -244
- package/dist/lib/addCommand.d.ts +9 -0
- package/dist/lib/addCommand.js +102 -0
- package/dist/lib/addStructuredCommand.d.ts +16 -0
- package/dist/lib/addStructuredCommand.js +103 -0
- package/dist/lib/ambiguityCommand.d.ts +4 -0
- package/dist/lib/ambiguityCommand.js +36 -0
- package/dist/lib/apiKeyVault.d.ts +78 -0
- package/dist/lib/apiKeyVault.js +447 -0
- package/dist/lib/archive.js +0 -2
- package/dist/lib/askCommand.d.ts +13 -0
- package/dist/lib/askCommand.js +145 -0
- package/dist/lib/attachCommand.d.ts +17 -0
- package/dist/lib/attachCommand.js +66 -0
- package/dist/lib/attachments.d.ts +43 -2
- package/dist/lib/attachments.js +81 -2
- package/dist/lib/audioExtract.js +4 -1
- package/dist/lib/auditCommand.d.ts +7 -0
- package/dist/lib/auditCommand.js +27 -0
- package/dist/lib/backupCommand.d.ts +6 -0
- package/dist/lib/backupCommand.js +54 -0
- package/dist/lib/bootstrapCommand.d.ts +15 -0
- package/dist/lib/bootstrapCommand.js +51 -0
- package/dist/lib/briefingCommand.d.ts +7 -0
- package/dist/lib/briefingCommand.js +92 -0
- package/dist/lib/centralizeCommand.d.ts +5 -0
- package/dist/lib/centralizeCommand.js +16 -0
- package/dist/lib/chat/choose.js +2 -2
- package/dist/lib/chatCommand.d.ts +12 -0
- package/dist/lib/chatCommand.js +46 -0
- package/dist/lib/checkCommand.d.ts +4 -0
- package/dist/lib/checkCommand.js +133 -0
- package/dist/lib/clientReadOverlay.d.ts +27 -0
- package/dist/lib/clientReadOverlay.js +76 -0
- package/dist/lib/clientReadResolve.d.ts +32 -0
- package/dist/lib/clientReadResolve.js +84 -0
- package/dist/lib/commitContextCommand.d.ts +9 -0
- package/dist/lib/commitContextCommand.js +142 -0
- package/dist/lib/config.d.ts +41 -48
- package/dist/lib/config.js +58 -57
- package/dist/lib/configCommand.d.ts +10 -0
- package/dist/lib/configCommand.js +321 -0
- package/dist/lib/connectCommand.d.ts +8 -0
- package/dist/lib/connectCommand.js +19 -0
- package/dist/lib/db.d.ts +68 -1
- package/dist/lib/db.js +385 -120
- package/dist/lib/dbWrite.d.ts +1 -1
- package/dist/lib/dearchiveCommand.d.ts +7 -0
- package/dist/lib/dearchiveCommand.js +41 -0
- package/dist/lib/discoverCommand.d.ts +9 -0
- package/dist/lib/discoverCommand.js +87 -0
- package/dist/lib/doctorCommand.d.ts +6 -0
- package/dist/lib/doctorCommand.js +256 -0
- package/dist/lib/docxExtract.js +1 -1
- package/dist/lib/dream.d.ts +50 -2
- package/dist/lib/dream.js +324 -30
- package/dist/lib/dreamCommand.d.ts +10 -0
- package/dist/lib/dreamCommand.js +195 -0
- package/dist/lib/dreamLaunchd.d.ts +2 -0
- package/dist/lib/dreamLaunchd.js +72 -0
- package/dist/lib/dreamLogCommand.d.ts +10 -0
- package/dist/lib/dreamLogCommand.js +58 -0
- package/dist/lib/dreamReport.d.ts +7 -0
- package/dist/lib/dreamReport.js +114 -0
- package/dist/lib/dreamRunLog.d.ts +121 -0
- package/dist/lib/dreamRunLog.js +234 -0
- package/dist/lib/embeddings.js +3 -3
- package/dist/lib/exportCommand.d.ts +18 -0
- package/dist/lib/exportCommand.js +101 -0
- package/dist/lib/exportProject.d.ts +3 -2
- package/dist/lib/exportProject.js +2 -1
- package/dist/lib/federated.js +1 -1
- package/dist/lib/fsearchCommand.d.ts +8 -0
- package/dist/lib/fsearchCommand.js +44 -0
- package/dist/lib/graphCommand.d.ts +4 -0
- package/dist/lib/graphCommand.js +68 -0
- package/dist/lib/helperGenerateCommand.d.ts +5 -0
- package/dist/lib/helperGenerateCommand.js +27 -0
- package/dist/lib/historyCommand.d.ts +5 -0
- package/dist/lib/historyCommand.js +51 -0
- package/dist/lib/hybridSearchCommand.d.ts +12 -0
- package/dist/lib/hybridSearchCommand.js +95 -0
- package/dist/lib/importCommand.d.ts +16 -0
- package/dist/lib/importCommand.js +89 -0
- package/dist/lib/importProject.js +2 -1
- package/dist/lib/importProjectCommand.d.ts +6 -0
- package/dist/lib/importProjectCommand.js +43 -0
- package/dist/lib/ingestCommand.d.ts +13 -0
- package/dist/lib/ingestCommand.js +95 -0
- package/dist/lib/installOutput.d.ts +36 -0
- package/dist/lib/installOutput.js +55 -0
- package/dist/lib/lensCommand.d.ts +20 -0
- package/dist/lib/lensCommand.js +61 -0
- package/dist/lib/lensing.d.ts +1 -0
- package/dist/lib/lensing.js +50 -9
- package/dist/lib/linksCommand.d.ts +7 -0
- package/dist/lib/linksCommand.js +48 -0
- package/dist/lib/listCommand.d.ts +8 -0
- package/dist/lib/listCommand.js +74 -0
- package/dist/lib/llm.d.ts +1 -1
- package/dist/lib/llm.js +27 -9
- package/dist/lib/localDiskCheck.d.ts +17 -0
- package/dist/lib/localDiskCheck.js +54 -0
- package/dist/lib/lock.d.ts +1 -1
- package/dist/lib/lock.js +5 -3
- package/dist/lib/machineConfig.d.ts +11 -1
- package/dist/lib/machineConfig.js +16 -0
- package/dist/lib/machineRegistry.d.ts +61 -0
- package/dist/lib/machineRegistry.js +80 -0
- package/dist/lib/maintainCommand.d.ts +8 -0
- package/dist/lib/maintainCommand.js +34 -0
- package/dist/lib/masterLease.d.ts +20 -0
- package/dist/lib/masterLease.js +68 -0
- package/dist/lib/migrate.js +0 -1
- package/dist/lib/migrateCommand.d.ts +7 -0
- package/dist/lib/migrateCommand.js +158 -0
- package/dist/lib/migrateDbCommand.d.ts +9 -0
- package/dist/lib/migrateDbCommand.js +94 -0
- package/dist/lib/modelValidation.d.ts +5 -0
- package/dist/lib/modelValidation.js +27 -0
- package/dist/lib/multimodalIngest.js +1 -1
- package/dist/lib/openrouterTiers.d.ts +29 -0
- package/dist/lib/openrouterTiers.js +113 -0
- package/dist/lib/platform.d.ts +0 -6
- package/dist/lib/platform.js +0 -28
- package/dist/lib/prefCommand.d.ts +10 -0
- package/dist/lib/prefCommand.js +118 -0
- package/dist/lib/projectsCommand.d.ts +8 -0
- package/dist/lib/projectsCommand.js +131 -0
- package/dist/lib/readCommand.d.ts +7 -0
- package/dist/lib/readCommand.js +63 -0
- package/dist/lib/recall.d.ts +3 -0
- package/dist/lib/recall.js +19 -4
- package/dist/lib/recallCommand.d.ts +11 -0
- package/dist/lib/recallCommand.js +112 -0
- package/dist/lib/reflectCommand.d.ts +8 -0
- package/dist/lib/reflectCommand.js +61 -0
- package/dist/lib/reindexCommand.d.ts +4 -0
- package/dist/lib/reindexCommand.js +34 -0
- package/dist/lib/reindexGraphCommand.d.ts +4 -0
- package/dist/lib/reindexGraphCommand.js +12 -0
- package/dist/lib/reinforceCommand.d.ts +8 -0
- package/dist/lib/reinforceCommand.js +40 -0
- package/dist/lib/remote.d.ts +5 -1
- package/dist/lib/remote.js +5 -1
- package/dist/lib/remoteWizard.d.ts +24 -5
- package/dist/lib/remoteWizard.js +308 -319
- package/dist/lib/restoreCommand.d.ts +5 -0
- package/dist/lib/restoreCommand.js +35 -0
- package/dist/lib/rulesGen.d.ts +8 -0
- package/dist/lib/rulesGen.js +16 -0
- package/dist/lib/sandboxStartCommand.d.ts +6 -0
- package/dist/lib/sandboxStartCommand.js +25 -0
- package/dist/lib/sandboxStatusCommand.d.ts +4 -0
- package/dist/lib/sandboxStatusCommand.js +24 -0
- package/dist/lib/sandboxStopCommand.d.ts +4 -0
- package/dist/lib/sandboxStopCommand.js +21 -0
- package/dist/lib/search.d.ts +0 -2
- package/dist/lib/search.js +0 -7
- package/dist/lib/searchCommand.d.ts +9 -0
- package/dist/lib/searchCommand.js +90 -0
- package/dist/lib/semanticSearchCommand.d.ts +8 -0
- package/dist/lib/semanticSearchCommand.js +52 -0
- package/dist/lib/setup/configSetRender.js +2 -0
- package/dist/lib/setup/providerGlyphs.d.ts +19 -0
- package/dist/lib/setup/providerGlyphs.js +42 -0
- package/dist/lib/setup/remoteRender.d.ts +31 -1
- package/dist/lib/setup/remoteRender.js +95 -4
- package/dist/lib/setup/sections/providers.d.ts +17 -0
- package/dist/lib/setup/sections/providers.js +307 -0
- package/dist/lib/setup/sections/routing.d.ts +2 -6
- package/dist/lib/setup/sections/routing.js +67 -82
- package/dist/lib/setup/sections/taskRoutingEditor.d.ts +13 -0
- package/dist/lib/setup/sections/taskRoutingEditor.js +139 -0
- package/dist/lib/setup/summary.d.ts +9 -0
- package/dist/lib/setup/summary.js +51 -37
- package/dist/lib/setup/ui/header.js +0 -1
- package/dist/lib/setup.d.ts +105 -15
- package/dist/lib/setup.js +747 -287
- package/dist/lib/setupKeys.d.ts +42 -0
- package/dist/lib/setupKeys.js +564 -0
- package/dist/lib/setupRemoteCommand.d.ts +4 -0
- package/dist/lib/setupRemoteCommand.js +28 -0
- package/dist/lib/setupRemotePullCommand.d.ts +5 -0
- package/dist/lib/setupRemotePullCommand.js +52 -0
- package/dist/lib/setupRemotePushCommand.d.ts +5 -0
- package/dist/lib/setupRemotePushCommand.js +57 -0
- package/dist/lib/setupRemoteResolveCommand.d.ts +4 -0
- package/dist/lib/setupRemoteResolveCommand.js +48 -0
- package/dist/lib/setupRemoteStatusCommand.d.ts +4 -0
- package/dist/lib/setupRemoteStatusCommand.js +73 -0
- package/dist/lib/setupRemoteSyncCommand.d.ts +6 -0
- package/dist/lib/setupRemoteSyncCommand.js +65 -0
- package/dist/lib/setupSyncProjectsCommand.d.ts +4 -0
- package/dist/lib/setupSyncProjectsCommand.js +292 -0
- package/dist/lib/staleCommand.d.ts +8 -0
- package/dist/lib/staleCommand.js +34 -0
- package/dist/lib/statsCommand.d.ts +6 -0
- package/dist/lib/statsCommand.js +142 -0
- package/dist/lib/statusCommand.d.ts +18 -0
- package/dist/lib/statusCommand.js +250 -0
- package/dist/lib/storesCommand.d.ts +2 -0
- package/dist/lib/storesCommand.js +4 -0
- package/dist/lib/syncClient.d.ts +41 -0
- package/dist/lib/syncClient.js +234 -0
- package/dist/lib/syncCommand.d.ts +6 -0
- package/dist/lib/syncCommand.js +57 -0
- package/dist/lib/syncDoctorCommand.d.ts +5 -0
- package/dist/lib/syncDoctorCommand.js +100 -0
- package/dist/lib/syncIngest.d.ts +30 -0
- package/dist/lib/syncIngest.js +175 -0
- package/dist/lib/syncIngestLaunchd.d.ts +8 -0
- package/dist/lib/syncIngestLaunchd.js +93 -0
- package/dist/lib/syncIngestStartup.d.ts +5 -0
- package/dist/lib/syncIngestStartup.js +29 -0
- package/dist/lib/syncIngestSystemd.d.ts +10 -0
- package/dist/lib/syncIngestSystemd.js +97 -0
- package/dist/lib/syncIngestTimer.d.ts +8 -0
- package/dist/lib/syncIngestTimer.js +27 -0
- package/dist/lib/syncIngestTimerCommand.d.ts +7 -0
- package/dist/lib/syncIngestTimerCommand.js +83 -0
- package/dist/lib/syncLock.d.ts +6 -0
- package/dist/lib/syncLock.js +74 -0
- package/dist/lib/syncSnapshot.d.ts +32 -0
- package/dist/lib/syncSnapshot.js +188 -0
- package/dist/lib/syncStaging.d.ts +79 -0
- package/dist/lib/syncStaging.js +237 -0
- package/dist/lib/tagsAddCommand.d.ts +8 -0
- package/dist/lib/tagsAddCommand.js +18 -0
- package/dist/lib/tagsCommand.d.ts +4 -0
- package/dist/lib/tagsCommand.js +16 -0
- package/dist/lib/timelineCommand.d.ts +7 -0
- package/dist/lib/timelineCommand.js +49 -0
- package/dist/lib/traceCommand.d.ts +6 -0
- package/dist/lib/traceCommand.js +39 -0
- package/dist/lib/traverseCommand.d.ts +6 -0
- package/dist/lib/traverseCommand.js +58 -0
- package/dist/lib/updateCommand.d.ts +13 -0
- package/dist/lib/updateCommand.js +67 -0
- package/dist/lib/updateStatusCommand.d.ts +5 -0
- package/dist/lib/updateStatusCommand.js +38 -0
- package/dist/lib/webAddCommand.d.ts +8 -0
- package/dist/lib/webAddCommand.js +55 -0
- package/dist/lib/webBuildCommand.d.ts +10 -0
- package/dist/lib/webBuildCommand.js +65 -0
- package/dist/lib/webBuildIndexCommand.d.ts +8 -0
- package/dist/lib/webBuildIndexCommand.js +37 -0
- package/dist/lib/webIndex.js +0 -1
- package/dist/lib/webIngestCommand.d.ts +11 -0
- package/dist/lib/webIngestCommand.js +51 -0
- package/dist/lib/webInitCommand.d.ts +9 -0
- package/dist/lib/webInitCommand.js +167 -0
- package/dist/lib/webRemoveCommand.d.ts +5 -0
- package/dist/lib/webRemoveCommand.js +41 -0
- package/dist/lib/webStatusCommand.d.ts +5 -0
- package/dist/lib/webStatusCommand.js +94 -0
- package/dist/lib/webUpdateCommand.d.ts +7 -0
- package/dist/lib/webUpdateCommand.js +72 -0
- package/dist/lib/workingSetCommand.d.ts +6 -0
- package/dist/lib/workingSetCommand.js +37 -0
- package/dist/sandbox/client.js +1 -1
- package/dist/sandbox/manager.js +1 -14
- package/dist/sandbox/server.js +3 -5
- package/package.json +6 -2
package/dist/index.js
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
// MCP stdio JSON protocol. parse() is a pure function with no side effects.
|
|
11
11
|
import dotenv from "dotenv";
|
|
12
12
|
import path from "path";
|
|
13
|
+
import { AsyncLocalStorage } from "async_hooks";
|
|
13
14
|
import { readFileSync, realpathSync } from "fs";
|
|
14
15
|
import { fileURLToPath } from "url";
|
|
15
16
|
const home = process.env.HOME || process.env.USERPROFILE || "/tmp";
|
|
@@ -49,15 +50,20 @@ import { groupByPeriod, computeStats } from "./lib/timeline.js";
|
|
|
49
50
|
import { buildLinkGraph, getBacklinks, getOutgoingLinks, formatGraphSummary } from "./lib/wikilinks.js";
|
|
50
51
|
import { loadConfig, DEFAULT_CONFIG } from "./lib/config.js";
|
|
51
52
|
import { getLLMProvider } from "./lib/llm.js";
|
|
52
|
-
import { recall, formatRecall } from "./lib/recall.js";
|
|
53
|
+
import { recall, formatRecall, } from "./lib/recall.js";
|
|
53
54
|
import { initAudit, readAuditLog, formatAuditTimeline } from "./lib/audit.js";
|
|
55
|
+
import { logError } from "./lib/log.js";
|
|
54
56
|
import { GnosysDB } from "./lib/db.js";
|
|
55
57
|
import { syncMemoryToDb, syncUpdateToDb, syncDearchiveToDb, syncReinforcementToDb, auditToDb } from "./lib/dbWrite.js";
|
|
56
|
-
import { createProjectIdentity, readProjectIdentity } from "./lib/projectIdentity.js";
|
|
58
|
+
import { createProjectIdentity, readProjectIdentity, } from "./lib/projectIdentity.js";
|
|
57
59
|
import { setPreference, getPreference, getAllPreferences, deletePreference, KNOWN_PREFERENCE_KEYS, suggestPreferenceKey } from "./lib/preferences.js";
|
|
58
|
-
import { syncRules, generateRulesBlock } from "./lib/rulesGen.js";
|
|
60
|
+
import { syncRules, generateRulesBlock, } from "./lib/rulesGen.js";
|
|
59
61
|
import { federatedSearch, detectAmbiguity, generateBriefing, generateAllBriefings, getWorkingSet, formatWorkingSet, detectCurrentProject } from "./lib/federated.js";
|
|
60
62
|
import { generatePortfolio, formatPortfolioCompact, formatPortfolioMarkdown, generateStatusPrompt } from "./lib/portfolio.js";
|
|
63
|
+
import { applyPendingOverlay, mergeOverlayDiscoverResults, mergeOverlaySearchResults, pendingAddToDbMemory, } from "./lib/clientReadOverlay.js";
|
|
64
|
+
import { readMachineConfig } from "./lib/machineConfig.js";
|
|
65
|
+
import { getConfiguredRemotePath } from "./lib/remote.js";
|
|
66
|
+
import { closeClientReadContext, openClientReadContext, } from "./lib/syncClient.js";
|
|
61
67
|
// Initialize resolver (discovers all layered stores)
|
|
62
68
|
const resolver = new GnosysResolver();
|
|
63
69
|
let config = DEFAULT_CONFIG;
|
|
@@ -67,9 +73,47 @@ const server = new McpServer({
|
|
|
67
73
|
version: "2.0.0",
|
|
68
74
|
});
|
|
69
75
|
const _registrations = [];
|
|
76
|
+
// v5.12.1 reliability: every tool handler resolves a ToolContext whose
|
|
77
|
+
// clientRead (v13 sync) may own a DB handle. Historically only 8 of 52
|
|
78
|
+
// handlers released it, leaking handles on every early return. Enforce
|
|
79
|
+
// release centrally: resolveToolContext() registers each context in the
|
|
80
|
+
// per-call AsyncLocalStorage store, and withContextRelease() (wrapped around
|
|
81
|
+
// every tool handler at registration) releases them when the handler settles
|
|
82
|
+
// — every return path, every throw, every future tool, no per-handler code.
|
|
83
|
+
const activeToolContexts = new AsyncLocalStorage();
|
|
84
|
+
function withContextRelease(handler, toolName) {
|
|
85
|
+
return (...hargs) => {
|
|
86
|
+
const opened = [];
|
|
87
|
+
return activeToolContexts.run(opened, async () => {
|
|
88
|
+
try {
|
|
89
|
+
return await handler(...hargs);
|
|
90
|
+
}
|
|
91
|
+
catch (err) {
|
|
92
|
+
// v5.12.x observability: last-resort error envelope. 19 of 52 tools
|
|
93
|
+
// had no catch at all — a throw reached the SDK as a raw JSON-RPC
|
|
94
|
+
// error with no corruption-recovery guidance. Per-tool catches with
|
|
95
|
+
// more specific messages still take precedence; this only sees what
|
|
96
|
+
// they let through. Logged to stderr (stdout is JSON-RPC).
|
|
97
|
+
logError(err, { module: "mcp", op: toolName });
|
|
98
|
+
return {
|
|
99
|
+
content: [{ type: "text", text: formatMcpError(`in ${toolName}`, err) }],
|
|
100
|
+
isError: true,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
finally {
|
|
104
|
+
for (const c of opened)
|
|
105
|
+
releaseClientReadFromContext(c);
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
};
|
|
109
|
+
}
|
|
70
110
|
// Typed to the McpServer methods so call-site generic inference (Zod schema →
|
|
71
111
|
// handler arg types) is preserved; the body just collects a replay thunk.
|
|
72
112
|
const regTool = ((...args) => {
|
|
113
|
+
const last = args.length - 1;
|
|
114
|
+
if (typeof args[last] === "function") {
|
|
115
|
+
args[last] = withContextRelease(args[last], typeof args[0] === "string" ? args[0] : "tool");
|
|
116
|
+
}
|
|
73
117
|
_registrations.push((s) => s.tool(...args));
|
|
74
118
|
});
|
|
75
119
|
const regPrompt = ((...args) => {
|
|
@@ -136,14 +180,46 @@ let askEngine = null;
|
|
|
136
180
|
let gnosysDb = null;
|
|
137
181
|
/** v3.0: Central DB at ~/.gnosys/gnosys.db */
|
|
138
182
|
let centralDb = null;
|
|
139
|
-
/** v2.0: Dream scheduler (idle-time consolidation) */
|
|
140
|
-
let dreamScheduler = null;
|
|
141
183
|
// ─── Multi-Project Support ───────────────────────────────────────────────
|
|
142
184
|
// Each tool call can optionally pass a `projectRoot` to target a specific
|
|
143
185
|
// project's .gnosys store. This is STATELESS — no race conditions when
|
|
144
186
|
// multiple agents call tools in parallel.
|
|
145
187
|
/** Common Zod schema fragment for projectRoot parameter */
|
|
146
188
|
const projectRootParam = z.string().optional().describe("Optional project root path for multi-project support. When provided, this tool operates on projectRoot/.gnosys instead of the default store. Use gnosys_stores to see all available stores.");
|
|
189
|
+
function applyClientReadToCentralDb(localDb) {
|
|
190
|
+
if (!localDb?.isAvailable()) {
|
|
191
|
+
return { centralDb: localDb, clientRead: null };
|
|
192
|
+
}
|
|
193
|
+
const mc = readMachineConfig();
|
|
194
|
+
if (!mc?.remote.enabled || mc.remote.role !== "client") {
|
|
195
|
+
return { centralDb: localDb, clientRead: null };
|
|
196
|
+
}
|
|
197
|
+
const masterPath = getConfiguredRemotePath(localDb);
|
|
198
|
+
if (!masterPath)
|
|
199
|
+
return { centralDb: localDb, clientRead: null };
|
|
200
|
+
const clientRead = openClientReadContext(localDb, masterPath, mc.machineId);
|
|
201
|
+
return { centralDb: clientRead.db, clientRead };
|
|
202
|
+
}
|
|
203
|
+
/** Idempotent: safe to call from both a handler's own finally and the central
|
|
204
|
+
* withContextRelease wrapper. */
|
|
205
|
+
function releaseClientReadFromContext(ctx) {
|
|
206
|
+
if (ctx.clientRead) {
|
|
207
|
+
closeClientReadContext(ctx.clientRead);
|
|
208
|
+
ctx.clientRead = null;
|
|
209
|
+
}
|
|
210
|
+
// v5.12.x perf/leak: projectRoot-scoped contexts open their own
|
|
211
|
+
// GnosysSearch (SQLite handle to <store>/.config/search.db) per call.
|
|
212
|
+
if (ctx.ownsSearch && ctx.search) {
|
|
213
|
+
try {
|
|
214
|
+
ctx.search.close();
|
|
215
|
+
}
|
|
216
|
+
catch {
|
|
217
|
+
// already closed / never opened — fine
|
|
218
|
+
}
|
|
219
|
+
ctx.search = null;
|
|
220
|
+
ctx.ownsSearch = false;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
147
223
|
async function resolveToolContext(projectRoot) {
|
|
148
224
|
if (!projectRoot) {
|
|
149
225
|
// Default context — use module-level state
|
|
@@ -155,16 +231,20 @@ async function resolveToolContext(projectRoot) {
|
|
|
155
231
|
const identity = await readProjectIdentity(parentDir);
|
|
156
232
|
projectId = identity?.projectId || null;
|
|
157
233
|
}
|
|
158
|
-
|
|
234
|
+
const applied = applyClientReadToCentralDb(centralDb);
|
|
235
|
+
const ctx = {
|
|
159
236
|
resolver,
|
|
160
237
|
store: writeTarget?.store || null,
|
|
161
238
|
storePath: writeTarget?.store.getStorePath() || "",
|
|
162
239
|
config,
|
|
163
240
|
search,
|
|
164
241
|
gnosysDb,
|
|
165
|
-
centralDb,
|
|
242
|
+
centralDb: applied.centralDb,
|
|
166
243
|
projectId,
|
|
244
|
+
clientRead: applied.clientRead,
|
|
167
245
|
};
|
|
246
|
+
activeToolContexts.getStore()?.push(ctx);
|
|
247
|
+
return ctx;
|
|
168
248
|
}
|
|
169
249
|
// Scoped context — resolve for this specific project
|
|
170
250
|
const scopedResolver = await GnosysResolver.resolveForProject(projectRoot);
|
|
@@ -192,16 +272,21 @@ async function resolveToolContext(projectRoot) {
|
|
|
192
272
|
// Removed: new GnosysDB(scopedStorePath) which created an empty
|
|
193
273
|
// gnosys.db in the project's .gnosys/ directory.
|
|
194
274
|
}
|
|
195
|
-
|
|
275
|
+
const applied = applyClientReadToCentralDb(centralDb);
|
|
276
|
+
const ctx = {
|
|
196
277
|
resolver: scopedResolver,
|
|
197
278
|
store: scopedWriteTarget?.store || null,
|
|
198
279
|
storePath: scopedStorePath,
|
|
199
280
|
config: scopedConfig,
|
|
200
281
|
search: scopedSearch,
|
|
201
282
|
gnosysDb: scopedDb,
|
|
202
|
-
centralDb,
|
|
283
|
+
centralDb: applied.centralDb,
|
|
203
284
|
projectId,
|
|
285
|
+
clientRead: applied.clientRead,
|
|
286
|
+
ownsSearch: scopedSearch !== null,
|
|
204
287
|
};
|
|
288
|
+
activeToolContexts.getStore()?.push(ctx);
|
|
289
|
+
return ctx;
|
|
205
290
|
}
|
|
206
291
|
/**
|
|
207
292
|
* v5.7.1 (#13): Resolve scope + projectId for a memory write.
|
|
@@ -244,50 +329,65 @@ regTool("gnosys_discover", "Discover relevant memories by describing what you're
|
|
|
244
329
|
projectRoot: projectRootParam,
|
|
245
330
|
}, async ({ query, limit, projectRoot }) => {
|
|
246
331
|
const ctx = await resolveToolContext(projectRoot);
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
332
|
+
try {
|
|
333
|
+
// v2.0 DB-backed fast path
|
|
334
|
+
if (ctx.centralDb?.isAvailable() && ctx.centralDb?.isMigrated()) {
|
|
335
|
+
const lim = limit || 20;
|
|
336
|
+
let results = ctx.centralDb.discoverFts(query, lim);
|
|
337
|
+
if (ctx.clientRead?.pendingOverlay.length) {
|
|
338
|
+
results = mergeOverlayDiscoverResults(results, ctx.clientRead.pendingOverlay, query, lim, (p) => ({
|
|
339
|
+
id: p.id,
|
|
340
|
+
title: p.title,
|
|
341
|
+
relevance: "",
|
|
342
|
+
rank: 0,
|
|
343
|
+
project_id: p.project_id,
|
|
344
|
+
}));
|
|
345
|
+
}
|
|
346
|
+
if (results.length === 0) {
|
|
347
|
+
return {
|
|
348
|
+
content: [{ type: "text", text: `No memories found for "${query}". Try different keywords.` }],
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
const formatted = results
|
|
352
|
+
.map((r) => `**${r.title}**\n ID: ${r.id}${r.relevance ? `\n Relevance: ${r.relevance}` : ""}`)
|
|
353
|
+
.join("\n\n");
|
|
354
|
+
return {
|
|
355
|
+
content: [{ type: "text", text: `Found ${results.length} relevant memories for "${query}":\n\n${formatted}\n\nUse gnosys_read to load any of these.` }],
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
// v1.x legacy path
|
|
359
|
+
if (!ctx.search) {
|
|
360
|
+
return {
|
|
361
|
+
content: [{ type: "text", text: "Search index not initialized." }],
|
|
362
|
+
isError: true,
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
const results = ctx.search.discover(query, limit || 20);
|
|
250
366
|
if (results.length === 0) {
|
|
251
367
|
return {
|
|
252
|
-
content: [
|
|
368
|
+
content: [
|
|
369
|
+
{
|
|
370
|
+
type: "text",
|
|
371
|
+
text: `No memories found for "${query}". Try different keywords or use gnosys_search for full-text search.`,
|
|
372
|
+
},
|
|
373
|
+
],
|
|
253
374
|
};
|
|
254
375
|
}
|
|
255
376
|
const formatted = results
|
|
256
|
-
.map((r) => `**${r.title}**\n
|
|
377
|
+
.map((r) => `**${r.title}**\n Path: ${r.relative_path}${r.relevance ? `\n Relevance: ${r.relevance}` : ""}`)
|
|
257
378
|
.join("\n\n");
|
|
258
|
-
return {
|
|
259
|
-
content: [{ type: "text", text: `Found ${results.length} relevant memories for "${query}":\n\n${formatted}\n\nUse gnosys_read to load any of these.` }],
|
|
260
|
-
};
|
|
261
|
-
}
|
|
262
|
-
// v1.x legacy path
|
|
263
|
-
if (!ctx.search) {
|
|
264
|
-
return {
|
|
265
|
-
content: [{ type: "text", text: "Search index not initialized." }],
|
|
266
|
-
isError: true,
|
|
267
|
-
};
|
|
268
|
-
}
|
|
269
|
-
const results = ctx.search.discover(query, limit || 20);
|
|
270
|
-
if (results.length === 0) {
|
|
271
379
|
return {
|
|
272
380
|
content: [
|
|
273
381
|
{
|
|
274
382
|
type: "text",
|
|
275
|
-
text: `
|
|
383
|
+
text: `Found ${results.length} relevant memories for "${query}":\n\n${formatted}\n\nUse gnosys_read to load any of these.`,
|
|
276
384
|
},
|
|
277
385
|
],
|
|
278
386
|
};
|
|
279
387
|
}
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
return {
|
|
284
|
-
content: [
|
|
285
|
-
{
|
|
286
|
-
type: "text",
|
|
287
|
-
text: `Found ${results.length} relevant memories for "${query}":\n\n${formatted}\n\nUse gnosys_read to load any of these.`,
|
|
288
|
-
},
|
|
289
|
-
],
|
|
290
|
-
};
|
|
388
|
+
finally {
|
|
389
|
+
releaseClientReadFromContext(ctx);
|
|
390
|
+
}
|
|
291
391
|
});
|
|
292
392
|
// ─── Tool: gnosys_read ───────────────────────────────────────────────────
|
|
293
393
|
regTool("gnosys_read", "Read a specific memory. Accepts a memory ID (e.g., 'arch-012') or layer-prefixed path (e.g., 'project:decisions/why-not-rag.md'). Without a prefix, searches all stores in precedence order.", {
|
|
@@ -295,62 +395,72 @@ regTool("gnosys_read", "Read a specific memory. Accepts a memory ID (e.g., 'arch
|
|
|
295
395
|
projectRoot: projectRootParam,
|
|
296
396
|
}, async ({ path: memPath, projectRoot }) => {
|
|
297
397
|
const ctx = await resolveToolContext(projectRoot);
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
`title: '${dbMem.title}'`,
|
|
307
|
-
`category: ${dbMem.category}`,
|
|
308
|
-
`tags: ${tags}`,
|
|
309
|
-
`relevance: ${dbMem.relevance}`,
|
|
310
|
-
`author: ${dbMem.author}`,
|
|
311
|
-
`authority: ${dbMem.authority}`,
|
|
312
|
-
`confidence: ${dbMem.confidence}`,
|
|
313
|
-
`status: ${dbMem.status}`,
|
|
314
|
-
`tier: ${dbMem.tier}`,
|
|
315
|
-
`created: '${dbMem.created}'`,
|
|
316
|
-
`modified: '${dbMem.modified}'`,
|
|
317
|
-
];
|
|
318
|
-
if (dbMem.source_file) {
|
|
319
|
-
headerLines.push(`source_file: ${dbMem.source_file}${dbMem.source_page != null ? ` (page ${Number(dbMem.source_page)})` : ""}`);
|
|
398
|
+
try {
|
|
399
|
+
// v2.0 DB-backed fast path: try reading by memory ID from gnosys.db first
|
|
400
|
+
if (ctx.centralDb?.isAvailable() && ctx.centralDb?.isMigrated()) {
|
|
401
|
+
let dbMem = ctx.centralDb.getMemory(memPath);
|
|
402
|
+
if (!dbMem && ctx.clientRead?.pendingOverlay.length) {
|
|
403
|
+
const pending = ctx.clientRead.pendingOverlay.find((p) => p.id === memPath);
|
|
404
|
+
if (pending)
|
|
405
|
+
dbMem = pendingAddToDbMemory(pending);
|
|
320
406
|
}
|
|
321
|
-
if (dbMem
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
407
|
+
if (dbMem) {
|
|
408
|
+
const tags = dbMem.tags || "[]";
|
|
409
|
+
const headerLines = [
|
|
410
|
+
`---`,
|
|
411
|
+
`id: ${dbMem.id}`,
|
|
412
|
+
`title: '${dbMem.title}'`,
|
|
413
|
+
`category: ${dbMem.category}`,
|
|
414
|
+
`tags: ${tags}`,
|
|
415
|
+
`relevance: ${dbMem.relevance}`,
|
|
416
|
+
`author: ${dbMem.author}`,
|
|
417
|
+
`authority: ${dbMem.authority}`,
|
|
418
|
+
`confidence: ${dbMem.confidence}`,
|
|
419
|
+
`status: ${dbMem.status}`,
|
|
420
|
+
`tier: ${dbMem.tier}`,
|
|
421
|
+
`created: '${dbMem.created}'`,
|
|
422
|
+
`modified: '${dbMem.modified}'`,
|
|
423
|
+
];
|
|
424
|
+
if (dbMem.source_file) {
|
|
425
|
+
headerLines.push(`source_file: ${dbMem.source_file}${dbMem.source_page != null ? ` (page ${Number(dbMem.source_page)})` : ""}`);
|
|
426
|
+
}
|
|
427
|
+
if (dbMem.source_path)
|
|
428
|
+
headerLines.push(`source_path: ${dbMem.source_path}`);
|
|
429
|
+
headerLines.push(`---`);
|
|
430
|
+
const header = headerLines.join("\n");
|
|
431
|
+
return {
|
|
432
|
+
content: [{ type: "text", text: `[Source: gnosys.db]\n\n${header}\n\n${dbMem.content}` }],
|
|
433
|
+
};
|
|
434
|
+
}
|
|
435
|
+
// Not found in db — fall through to legacy path
|
|
436
|
+
}
|
|
437
|
+
// v1.x legacy path
|
|
438
|
+
const memory = await ctx.resolver.readMemory(memPath);
|
|
439
|
+
if (!memory) {
|
|
325
440
|
return {
|
|
326
|
-
content: [{ type: "text", text: `
|
|
441
|
+
content: [{ type: "text", text: `Memory not found: ${memPath}` }],
|
|
442
|
+
isError: true,
|
|
327
443
|
};
|
|
328
444
|
}
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
445
|
+
let raw;
|
|
446
|
+
try {
|
|
447
|
+
raw = await fs.readFile(memory.filePath, "utf-8");
|
|
448
|
+
}
|
|
449
|
+
catch (err) {
|
|
450
|
+
return { content: [{ type: "text", text: formatMcpError("reading memory", err) }], isError: true };
|
|
451
|
+
}
|
|
334
452
|
return {
|
|
335
|
-
content: [
|
|
336
|
-
|
|
453
|
+
content: [
|
|
454
|
+
{
|
|
455
|
+
type: "text",
|
|
456
|
+
text: `[Source: ${memory.sourceLabel}]\n\n${raw}`,
|
|
457
|
+
},
|
|
458
|
+
],
|
|
337
459
|
};
|
|
338
460
|
}
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
raw = await fs.readFile(memory.filePath, "utf-8");
|
|
342
|
-
}
|
|
343
|
-
catch (err) {
|
|
344
|
-
return { content: [{ type: "text", text: formatMcpError("reading memory", err) }], isError: true };
|
|
461
|
+
finally {
|
|
462
|
+
releaseClientReadFromContext(ctx);
|
|
345
463
|
}
|
|
346
|
-
return {
|
|
347
|
-
content: [
|
|
348
|
-
{
|
|
349
|
-
type: "text",
|
|
350
|
-
text: `[Source: ${memory.sourceLabel}]\n\n${raw}`,
|
|
351
|
-
},
|
|
352
|
-
],
|
|
353
|
-
};
|
|
354
464
|
});
|
|
355
465
|
// ─── Tool: gnosys_search ─────────────────────────────────────────────────
|
|
356
466
|
regTool("gnosys_search", "Search memories by keyword across all stores. Returns matching file paths with relevance snippets.", {
|
|
@@ -359,50 +469,65 @@ regTool("gnosys_search", "Search memories by keyword across all stores. Returns
|
|
|
359
469
|
projectRoot: projectRootParam,
|
|
360
470
|
}, async ({ query, limit, projectRoot }) => {
|
|
361
471
|
const ctx = await resolveToolContext(projectRoot);
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
472
|
+
try {
|
|
473
|
+
// v2.0 DB-backed fast path
|
|
474
|
+
if (ctx.centralDb?.isAvailable() && ctx.centralDb?.isMigrated()) {
|
|
475
|
+
const lim = limit || 20;
|
|
476
|
+
let results = ctx.centralDb.searchFts(query, lim);
|
|
477
|
+
if (ctx.clientRead?.pendingOverlay.length) {
|
|
478
|
+
results = mergeOverlaySearchResults(results, ctx.clientRead.pendingOverlay, query, lim, (p) => ({
|
|
479
|
+
id: p.id,
|
|
480
|
+
title: p.title,
|
|
481
|
+
snippet: p.content.substring(0, 200),
|
|
482
|
+
rank: 0,
|
|
483
|
+
project_id: p.project_id,
|
|
484
|
+
}));
|
|
485
|
+
}
|
|
486
|
+
if (results.length === 0) {
|
|
487
|
+
return {
|
|
488
|
+
content: [{ type: "text", text: `No results for "${query}". Try different keywords.` }],
|
|
489
|
+
};
|
|
490
|
+
}
|
|
491
|
+
const formatted = results
|
|
492
|
+
.map((r) => `**${r.title}** (${r.id})\n${r.snippet.replace(/>>>/g, "**").replace(/<<</g, "**")}`)
|
|
493
|
+
.join("\n\n");
|
|
494
|
+
return {
|
|
495
|
+
content: [{ type: "text", text: `Found ${results.length} results for "${query}":\n\n${formatted}` }],
|
|
496
|
+
};
|
|
497
|
+
}
|
|
498
|
+
// v1.x legacy path
|
|
499
|
+
if (!ctx.search) {
|
|
500
|
+
return {
|
|
501
|
+
content: [{ type: "text", text: "Search index not initialized." }],
|
|
502
|
+
isError: true,
|
|
503
|
+
};
|
|
504
|
+
}
|
|
505
|
+
const results = ctx.search.search(query, limit || 20);
|
|
365
506
|
if (results.length === 0) {
|
|
366
507
|
return {
|
|
367
|
-
content: [
|
|
508
|
+
content: [
|
|
509
|
+
{
|
|
510
|
+
type: "text",
|
|
511
|
+
text: `No results for "${query}". Try different keywords or use gnosys_discover.`,
|
|
512
|
+
},
|
|
513
|
+
],
|
|
368
514
|
};
|
|
369
515
|
}
|
|
370
516
|
const formatted = results
|
|
371
|
-
.map((r) => `**${r.title}** (${r.
|
|
517
|
+
.map((r) => `**${r.title}** (${r.relative_path})\n${r.snippet.replace(/>>>/g, "**").replace(/<<</g, "**")}`)
|
|
372
518
|
.join("\n\n");
|
|
373
|
-
return {
|
|
374
|
-
content: [{ type: "text", text: `Found ${results.length} results for "${query}":\n\n${formatted}` }],
|
|
375
|
-
};
|
|
376
|
-
}
|
|
377
|
-
// v1.x legacy path
|
|
378
|
-
if (!ctx.search) {
|
|
379
|
-
return {
|
|
380
|
-
content: [{ type: "text", text: "Search index not initialized." }],
|
|
381
|
-
isError: true,
|
|
382
|
-
};
|
|
383
|
-
}
|
|
384
|
-
const results = ctx.search.search(query, limit || 20);
|
|
385
|
-
if (results.length === 0) {
|
|
386
519
|
return {
|
|
387
520
|
content: [
|
|
388
521
|
{
|
|
389
522
|
type: "text",
|
|
390
|
-
text: `
|
|
523
|
+
text: `Found ${results.length} results for "${query}":\n\n${formatted}`,
|
|
391
524
|
},
|
|
392
525
|
],
|
|
393
526
|
};
|
|
394
527
|
}
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
return {
|
|
399
|
-
content: [
|
|
400
|
-
{
|
|
401
|
-
type: "text",
|
|
402
|
-
text: `Found ${results.length} results for "${query}":\n\n${formatted}`,
|
|
403
|
-
},
|
|
404
|
-
],
|
|
405
|
-
};
|
|
528
|
+
finally {
|
|
529
|
+
releaseClientReadFromContext(ctx);
|
|
530
|
+
}
|
|
406
531
|
});
|
|
407
532
|
// ─── Tool: gnosys_list ───────────────────────────────────────────────────
|
|
408
533
|
regTool("gnosys_list", "List memories across all stores, optionally filtered by category, tag, or store layer.", {
|
|
@@ -413,41 +538,76 @@ regTool("gnosys_list", "List memories across all stores, optionally filtered by
|
|
|
413
538
|
projectRoot: projectRootParam,
|
|
414
539
|
}, async ({ category, tag, store: storeFilter, status, projectRoot }) => {
|
|
415
540
|
const ctx = await resolveToolContext(projectRoot);
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
541
|
+
try {
|
|
542
|
+
// DB-first: read from central DB instead of scanning markdown files
|
|
543
|
+
const db = ctx.centralDb;
|
|
544
|
+
if (db?.isAvailable()) {
|
|
545
|
+
let dbMemories = status === "active" || !status
|
|
546
|
+
? db.getActiveMemories()
|
|
547
|
+
: db.getAllMemories();
|
|
548
|
+
if (ctx.clientRead?.pendingOverlay.length && (status === "active" || !status)) {
|
|
549
|
+
dbMemories = applyPendingOverlay(dbMemories, ctx.clientRead.pendingOverlay, new Set()).memories;
|
|
550
|
+
}
|
|
551
|
+
// Apply filters on DB results
|
|
552
|
+
if (status && status !== "active") {
|
|
553
|
+
dbMemories = dbMemories.filter((m) => m.status === status);
|
|
554
|
+
}
|
|
555
|
+
if (storeFilter) {
|
|
556
|
+
dbMemories = dbMemories.filter((m) => m.scope === storeFilter);
|
|
557
|
+
}
|
|
558
|
+
if (category) {
|
|
559
|
+
dbMemories = dbMemories.filter((m) => m.category === category);
|
|
560
|
+
}
|
|
561
|
+
if (tag) {
|
|
562
|
+
dbMemories = dbMemories.filter((m) => {
|
|
563
|
+
try {
|
|
564
|
+
const parsed = JSON.parse(m.tags || "[]");
|
|
565
|
+
const tagList = Array.isArray(parsed)
|
|
566
|
+
? parsed
|
|
567
|
+
: Object.values(parsed).flat();
|
|
568
|
+
return tagList.includes(tag);
|
|
569
|
+
}
|
|
570
|
+
catch {
|
|
571
|
+
return false;
|
|
572
|
+
}
|
|
573
|
+
});
|
|
574
|
+
}
|
|
575
|
+
// Filter by project if we have a project ID (so scoped queries only see their project)
|
|
576
|
+
if (ctx.projectId && !storeFilter) {
|
|
577
|
+
dbMemories = dbMemories.filter((m) => m.project_id === ctx.projectId || m.scope !== "project");
|
|
578
|
+
}
|
|
579
|
+
const lines = dbMemories.map((m) => `- [${m.scope}] **${m.title}** (${m.category}/${m.id}) [${m.status}]`);
|
|
580
|
+
return {
|
|
581
|
+
content: [
|
|
582
|
+
{
|
|
583
|
+
type: "text",
|
|
584
|
+
text: lines.length > 0
|
|
585
|
+
? `${lines.length} memories:\n\n${lines.join("\n")}`
|
|
586
|
+
: "No memories match the filter.",
|
|
587
|
+
},
|
|
588
|
+
],
|
|
589
|
+
};
|
|
425
590
|
}
|
|
591
|
+
// Fallback: read from markdown files if central DB unavailable
|
|
592
|
+
let memories = await ctx.resolver.getAllMemories();
|
|
426
593
|
if (storeFilter) {
|
|
427
|
-
|
|
594
|
+
memories = memories.filter((m) => m.sourceLayer === storeFilter || m.sourceLabel === storeFilter);
|
|
428
595
|
}
|
|
429
596
|
if (category) {
|
|
430
|
-
|
|
597
|
+
memories = memories.filter((m) => m.frontmatter.category === category);
|
|
431
598
|
}
|
|
432
599
|
if (tag) {
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
: Object.values(parsed).flat();
|
|
439
|
-
return tagList.includes(tag);
|
|
440
|
-
}
|
|
441
|
-
catch {
|
|
442
|
-
return false;
|
|
443
|
-
}
|
|
600
|
+
memories = memories.filter((m) => {
|
|
601
|
+
const tags = Array.isArray(m.frontmatter.tags)
|
|
602
|
+
? m.frontmatter.tags
|
|
603
|
+
: Object.values(m.frontmatter.tags).flat();
|
|
604
|
+
return tags.includes(tag);
|
|
444
605
|
});
|
|
445
606
|
}
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
dbMemories = dbMemories.filter((m) => m.project_id === ctx.projectId || m.scope !== "project");
|
|
607
|
+
if (status) {
|
|
608
|
+
memories = memories.filter((m) => m.frontmatter.status === status);
|
|
449
609
|
}
|
|
450
|
-
const lines =
|
|
610
|
+
const lines = memories.map((m) => `- [${m.sourceLabel}] **${m.frontmatter.title}** (${m.relativePath}) [${m.frontmatter.status}]`);
|
|
451
611
|
return {
|
|
452
612
|
content: [
|
|
453
613
|
{
|
|
@@ -459,36 +619,9 @@ regTool("gnosys_list", "List memories across all stores, optionally filtered by
|
|
|
459
619
|
],
|
|
460
620
|
};
|
|
461
621
|
}
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
if (storeFilter) {
|
|
465
|
-
memories = memories.filter((m) => m.sourceLayer === storeFilter || m.sourceLabel === storeFilter);
|
|
466
|
-
}
|
|
467
|
-
if (category) {
|
|
468
|
-
memories = memories.filter((m) => m.frontmatter.category === category);
|
|
469
|
-
}
|
|
470
|
-
if (tag) {
|
|
471
|
-
memories = memories.filter((m) => {
|
|
472
|
-
const tags = Array.isArray(m.frontmatter.tags)
|
|
473
|
-
? m.frontmatter.tags
|
|
474
|
-
: Object.values(m.frontmatter.tags).flat();
|
|
475
|
-
return tags.includes(tag);
|
|
476
|
-
});
|
|
477
|
-
}
|
|
478
|
-
if (status) {
|
|
479
|
-
memories = memories.filter((m) => m.frontmatter.status === status);
|
|
622
|
+
finally {
|
|
623
|
+
releaseClientReadFromContext(ctx);
|
|
480
624
|
}
|
|
481
|
-
const lines = memories.map((m) => `- [${m.sourceLabel}] **${m.frontmatter.title}** (${m.relativePath}) [${m.frontmatter.status}]`);
|
|
482
|
-
return {
|
|
483
|
-
content: [
|
|
484
|
-
{
|
|
485
|
-
type: "text",
|
|
486
|
-
text: lines.length > 0
|
|
487
|
-
? `${lines.length} memories:\n\n${lines.join("\n")}`
|
|
488
|
-
: "No memories match the filter.",
|
|
489
|
-
},
|
|
490
|
-
],
|
|
491
|
-
};
|
|
492
625
|
});
|
|
493
626
|
// ─── Tool: gnosys_add ────────────────────────────────────────────────────
|
|
494
627
|
regTool("gnosys_add", "Add a new memory. Accepts raw text — an LLM structures it into an atomic memory. Writes to the project store by default. Use store='personal' for cross-project knowledge, or store='global' to explicitly write to shared org knowledge.", {
|
|
@@ -1560,7 +1693,7 @@ regTool("gnosys_import", "Bulk import structured data (CSV, JSON, JSONL) into Gn
|
|
|
1560
1693
|
const effectiveMode = mode || "structured";
|
|
1561
1694
|
try {
|
|
1562
1695
|
// v5.9.1 (#100): import.js pulls mammoth + pdf-parse + turndown.
|
|
1563
|
-
const { performImport, formatImportSummary
|
|
1696
|
+
const { performImport, formatImportSummary } = await import("./lib/import.js");
|
|
1564
1697
|
const result = await performImport(writeTarget.store, ingestion, {
|
|
1565
1698
|
format: format,
|
|
1566
1699
|
data,
|
|
@@ -1585,7 +1718,6 @@ regTool("gnosys_import", "Bulk import structured data (CSV, JSON, JSONL) into Gn
|
|
|
1585
1718
|
// Smart threshold guidance
|
|
1586
1719
|
if (effectiveMode === "llm" &&
|
|
1587
1720
|
result.totalProcessed > 100) {
|
|
1588
|
-
const estimate = estimateDuration(result.totalProcessed, "llm", concurrency || 5);
|
|
1589
1721
|
response += `\n\n💡 Tip: For large LLM imports, the CLI offers progress tracking and resume:\n gnosys import ${data.length < 100 ? data : "<file>"} --format ${format} --mode llm --skip-existing`;
|
|
1590
1722
|
}
|
|
1591
1723
|
return { content: [{ type: "text", text: response }] };
|
|
@@ -1895,7 +2027,7 @@ regTool("gnosys_dream", "Run a Dream Mode cycle — idle-time consolidation that
|
|
|
1895
2027
|
}, async (params) => {
|
|
1896
2028
|
try {
|
|
1897
2029
|
const ctx = await resolveToolContext(params.projectRoot);
|
|
1898
|
-
if (!ctx.centralDb
|
|
2030
|
+
if (!ctx.centralDb?.isAvailable() || !ctx.centralDb.isMigrated()) {
|
|
1899
2031
|
return {
|
|
1900
2032
|
content: [
|
|
1901
2033
|
{
|
|
@@ -1905,8 +2037,6 @@ regTool("gnosys_dream", "Run a Dream Mode cycle — idle-time consolidation that
|
|
|
1905
2037
|
],
|
|
1906
2038
|
};
|
|
1907
2039
|
}
|
|
1908
|
-
// Record activity to reset idle timer (if scheduler is running)
|
|
1909
|
-
dreamScheduler?.recordActivity();
|
|
1910
2040
|
const dreamConfig = {
|
|
1911
2041
|
enabled: true,
|
|
1912
2042
|
idleMinutes: 0, // Run immediately (manual trigger)
|
|
@@ -1924,6 +2054,34 @@ regTool("gnosys_dream", "Run a Dream Mode cycle — idle-time consolidation that
|
|
|
1924
2054
|
const report = await engine.dream((phase, detail) => {
|
|
1925
2055
|
console.error(`[dream:${phase}] ${detail}`);
|
|
1926
2056
|
});
|
|
2057
|
+
const { appendDreamRun } = await import("./lib/dreamRunLog.js");
|
|
2058
|
+
appendDreamRun({
|
|
2059
|
+
...report,
|
|
2060
|
+
id: report.id || `dream-${Date.now()}`,
|
|
2061
|
+
trigger: report.trigger || "manual",
|
|
2062
|
+
status: report.aborted ? "aborted" : report.errors.length > 0 ? "failed" : "completed",
|
|
2063
|
+
machine: report.machine || { hostname: "unknown" },
|
|
2064
|
+
provider: report.provider || dreamConfig.provider,
|
|
2065
|
+
phases: report.phases || [],
|
|
2066
|
+
llmCalls: report.llmCalls || [],
|
|
2067
|
+
totals: report.totals || {
|
|
2068
|
+
llmCallsMade: 0,
|
|
2069
|
+
llmCallsSkipped: 0,
|
|
2070
|
+
estimatedInputTokens: 0,
|
|
2071
|
+
estimatedOutputTokens: 0,
|
|
2072
|
+
estimatedCostUsd: 0,
|
|
2073
|
+
},
|
|
2074
|
+
effectiveness: report.effectiveness || {
|
|
2075
|
+
usefulOutputScore: 0,
|
|
2076
|
+
costPerUsefulOutput: null,
|
|
2077
|
+
decaysApplied: report.decayUpdated,
|
|
2078
|
+
summariesGenerated: report.summariesGenerated,
|
|
2079
|
+
summariesUpdated: report.summariesUpdated,
|
|
2080
|
+
reviewSuggestions: report.reviewSuggestions.length,
|
|
2081
|
+
relationshipsDiscovered: report.relationshipsDiscovered,
|
|
2082
|
+
},
|
|
2083
|
+
gates: [],
|
|
2084
|
+
});
|
|
1927
2085
|
return {
|
|
1928
2086
|
content: [
|
|
1929
2087
|
{
|
|
@@ -1949,7 +2107,7 @@ regTool("gnosys_export", "Export gnosys.db to Obsidian-compatible vault — atom
|
|
|
1949
2107
|
}, async (params) => {
|
|
1950
2108
|
try {
|
|
1951
2109
|
const ctx = await resolveToolContext(params.projectRoot);
|
|
1952
|
-
if (!ctx.centralDb
|
|
2110
|
+
if (!ctx.centralDb?.isAvailable() || !ctx.centralDb.isMigrated()) {
|
|
1953
2111
|
return {
|
|
1954
2112
|
content: [
|
|
1955
2113
|
{
|
|
@@ -2072,8 +2230,6 @@ regResource("gnosys_recall", "gnosys://recall", {
|
|
|
2072
2230
|
priority: 1, // Highest priority — always inject
|
|
2073
2231
|
},
|
|
2074
2232
|
}, async () => {
|
|
2075
|
-
// Record activity for dream scheduler (this fires on every turn)
|
|
2076
|
-
dreamScheduler?.recordActivity();
|
|
2077
2233
|
if (!search) {
|
|
2078
2234
|
return {
|
|
2079
2235
|
contents: [
|
|
@@ -2086,23 +2242,31 @@ regResource("gnosys_recall", "gnosys://recall", {
|
|
|
2086
2242
|
};
|
|
2087
2243
|
}
|
|
2088
2244
|
const storePath = resolver.getWriteTarget()?.store.getStorePath() || "";
|
|
2089
|
-
const
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2245
|
+
const applied = applyClientReadToCentralDb(centralDb);
|
|
2246
|
+
try {
|
|
2247
|
+
const result = await recall("*", {
|
|
2248
|
+
limit: config.recall?.maxMemories || 8,
|
|
2249
|
+
search,
|
|
2250
|
+
resolver,
|
|
2251
|
+
storePath,
|
|
2252
|
+
recallConfig: config.recall,
|
|
2253
|
+
gnosysDb: applied.centralDb || undefined,
|
|
2254
|
+
pendingOverlay: applied.clientRead?.pendingOverlay,
|
|
2255
|
+
});
|
|
2256
|
+
return {
|
|
2257
|
+
contents: [
|
|
2258
|
+
{
|
|
2259
|
+
uri: "gnosys://recall",
|
|
2260
|
+
mimeType: "text/markdown",
|
|
2261
|
+
text: formatRecall(result),
|
|
2262
|
+
},
|
|
2263
|
+
],
|
|
2264
|
+
};
|
|
2265
|
+
}
|
|
2266
|
+
finally {
|
|
2267
|
+
if (applied.clientRead)
|
|
2268
|
+
closeClientReadContext(applied.clientRead);
|
|
2269
|
+
}
|
|
2106
2270
|
});
|
|
2107
2271
|
// ─── Tool: gnosys_recall (query-specific fallback) ──────────────────────
|
|
2108
2272
|
// For hosts that don't support MCP Resources, or when the agent wants to
|
|
@@ -2118,28 +2282,34 @@ regTool("gnosys_recall", "Fast memory recall — inject relevant memories as con
|
|
|
2118
2282
|
}, async ({ query, limit, traceId, aggressive, projectRoot }) => {
|
|
2119
2283
|
try {
|
|
2120
2284
|
const ctx = await resolveToolContext(projectRoot);
|
|
2121
|
-
|
|
2285
|
+
try {
|
|
2286
|
+
if (!ctx.search) {
|
|
2287
|
+
return {
|
|
2288
|
+
content: [{ type: "text", text: "<gnosys: no-strong-recall-needed>" }],
|
|
2289
|
+
};
|
|
2290
|
+
}
|
|
2291
|
+
const storePath = ctx.resolver.getWriteTarget()?.store.getStorePath() || "";
|
|
2292
|
+
const recallConfig = {
|
|
2293
|
+
...ctx.config.recall,
|
|
2294
|
+
...(aggressive !== undefined ? { aggressive } : {}),
|
|
2295
|
+
};
|
|
2296
|
+
const result = await recall(query, {
|
|
2297
|
+
limit: Math.min(limit || recallConfig.maxMemories, 15),
|
|
2298
|
+
search: ctx.search,
|
|
2299
|
+
resolver: ctx.resolver,
|
|
2300
|
+
storePath,
|
|
2301
|
+
traceId,
|
|
2302
|
+
recallConfig,
|
|
2303
|
+
gnosysDb: ctx.centralDb || undefined,
|
|
2304
|
+
pendingOverlay: ctx.clientRead?.pendingOverlay,
|
|
2305
|
+
});
|
|
2122
2306
|
return {
|
|
2123
|
-
content: [{ type: "text", text:
|
|
2307
|
+
content: [{ type: "text", text: formatRecall(result) }],
|
|
2124
2308
|
};
|
|
2125
2309
|
}
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
...(aggressive !== undefined ? { aggressive } : {}),
|
|
2130
|
-
};
|
|
2131
|
-
const result = await recall(query, {
|
|
2132
|
-
limit: Math.min(limit || recallConfig.maxMemories, 15),
|
|
2133
|
-
search: ctx.search,
|
|
2134
|
-
resolver: ctx.resolver,
|
|
2135
|
-
storePath,
|
|
2136
|
-
traceId,
|
|
2137
|
-
recallConfig,
|
|
2138
|
-
gnosysDb: ctx.centralDb || undefined,
|
|
2139
|
-
});
|
|
2140
|
-
return {
|
|
2141
|
-
content: [{ type: "text", text: formatRecall(result) }],
|
|
2142
|
-
};
|
|
2310
|
+
finally {
|
|
2311
|
+
releaseClientReadFromContext(ctx);
|
|
2312
|
+
}
|
|
2143
2313
|
}
|
|
2144
2314
|
catch (err) {
|
|
2145
2315
|
return { content: [{ type: "text", text: formatMcpError("recalling memories", err) }], isError: true };
|
|
@@ -2383,28 +2553,34 @@ regTool("gnosys_federated_search", "Search across all scopes (project → user
|
|
|
2383
2553
|
projectRoot: z.string().optional().describe("Project root directory for context detection"),
|
|
2384
2554
|
includeGlobal: z.boolean().optional().describe("Include global-scope memories (default: true)"),
|
|
2385
2555
|
}, async ({ query, limit, projectRoot, includeGlobal }) => {
|
|
2386
|
-
|
|
2387
|
-
|
|
2556
|
+
const ctx = await resolveToolContext(projectRoot);
|
|
2557
|
+
try {
|
|
2558
|
+
if (!ctx.centralDb?.isAvailable()) {
|
|
2559
|
+
return { content: [{ type: "text", text: "Central DB not available. Run gnosys_init first." }], isError: true };
|
|
2560
|
+
}
|
|
2561
|
+
// Auto-detect current project
|
|
2562
|
+
const projectId = await detectCurrentProject(ctx.centralDb, projectRoot || undefined);
|
|
2563
|
+
const results = federatedSearch(ctx.centralDb, query, {
|
|
2564
|
+
limit: limit || 20,
|
|
2565
|
+
projectId,
|
|
2566
|
+
includeGlobal: includeGlobal !== false,
|
|
2567
|
+
});
|
|
2568
|
+
if (results.length === 0) {
|
|
2569
|
+
return { content: [{ type: "text", text: `No results for "${query}" across any scope.` }] };
|
|
2570
|
+
}
|
|
2571
|
+
const lines = results.map((r, i) => {
|
|
2572
|
+
const projectLabel = r.projectName ? ` [${r.projectName}]` : "";
|
|
2573
|
+
const boostLabel = r.boosts.length > 0 ? ` (${r.boosts.join(", ")})` : "";
|
|
2574
|
+
return `${i + 1}. **${r.title}** (${r.category})${projectLabel}\n scope: ${r.scope} | score: ${r.score.toFixed(4)}${boostLabel}\n ${r.snippet}`;
|
|
2575
|
+
});
|
|
2576
|
+
const contextNote = projectId ? `Context: project ${projectId}` : "Context: no project detected";
|
|
2577
|
+
return {
|
|
2578
|
+
content: [{ type: "text", text: `${contextNote}\n\n${lines.join("\n\n")}` }],
|
|
2579
|
+
};
|
|
2388
2580
|
}
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
const results = federatedSearch(centralDb, query, {
|
|
2392
|
-
limit: limit || 20,
|
|
2393
|
-
projectId,
|
|
2394
|
-
includeGlobal: includeGlobal !== false,
|
|
2395
|
-
});
|
|
2396
|
-
if (results.length === 0) {
|
|
2397
|
-
return { content: [{ type: "text", text: `No results for "${query}" across any scope.` }] };
|
|
2581
|
+
finally {
|
|
2582
|
+
releaseClientReadFromContext(ctx);
|
|
2398
2583
|
}
|
|
2399
|
-
const lines = results.map((r, i) => {
|
|
2400
|
-
const projectLabel = r.projectName ? ` [${r.projectName}]` : "";
|
|
2401
|
-
const boostLabel = r.boosts.length > 0 ? ` (${r.boosts.join(", ")})` : "";
|
|
2402
|
-
return `${i + 1}. **${r.title}** (${r.category})${projectLabel}\n scope: ${r.scope} | score: ${r.score.toFixed(4)}${boostLabel}\n ${r.snippet}`;
|
|
2403
|
-
});
|
|
2404
|
-
const contextNote = projectId ? `Context: project ${projectId}` : "Context: no project detected";
|
|
2405
|
-
return {
|
|
2406
|
-
content: [{ type: "text", text: `${contextNote}\n\n${lines.join("\n\n")}` }],
|
|
2407
|
-
};
|
|
2408
2584
|
});
|
|
2409
2585
|
// ─── Tool: gnosys_detect_ambiguity ──────────────────────────────────────
|
|
2410
2586
|
regTool("gnosys_detect_ambiguity", "Check if a query matches memories in multiple projects. Use before write operations to confirm the target project when ambiguity exists.", {
|
|
@@ -2500,7 +2676,8 @@ regTool("gnosys_remote_status", "Check the status of remote sync (multi-machine)
|
|
|
2500
2676
|
if (!localDb.isAvailable()) {
|
|
2501
2677
|
return { content: [{ type: "text", text: "Local DB not available." }], isError: true };
|
|
2502
2678
|
}
|
|
2503
|
-
const
|
|
2679
|
+
const { getConfiguredRemotePath } = await import("./lib/remote.js");
|
|
2680
|
+
const remotePath = getConfiguredRemotePath(localDb);
|
|
2504
2681
|
if (!remotePath) {
|
|
2505
2682
|
return {
|
|
2506
2683
|
content: [{
|
|
@@ -2509,6 +2686,14 @@ regTool("gnosys_remote_status", "Check the status of remote sync (multi-machine)
|
|
|
2509
2686
|
}],
|
|
2510
2687
|
};
|
|
2511
2688
|
}
|
|
2689
|
+
const { readMachineConfig } = await import("./lib/machineConfig.js");
|
|
2690
|
+
if (readMachineConfig()?.remote.role) {
|
|
2691
|
+
const { getV13SyncStatus } = await import("./lib/syncClient.js");
|
|
2692
|
+
const v13 = getV13SyncStatus(localDb);
|
|
2693
|
+
return {
|
|
2694
|
+
content: [{ type: "text", text: JSON.stringify(v13, null, 2) }],
|
|
2695
|
+
};
|
|
2696
|
+
}
|
|
2512
2697
|
const { RemoteSync } = await import("./lib/remote.js");
|
|
2513
2698
|
const sync = new RemoteSync(localDb, remotePath);
|
|
2514
2699
|
const status = await sync.getStatus();
|
|
@@ -2595,6 +2780,106 @@ regTool("gnosys_remote_resolve", "Resolve a sync conflict by choosing which vers
|
|
|
2595
2780
|
localDb.close();
|
|
2596
2781
|
}
|
|
2597
2782
|
});
|
|
2783
|
+
// ─── Tool: gnosys_attach ────────────────────────────────────────────────
|
|
2784
|
+
regTool("gnosys_attach", "Attach a small binary file (logo, diagram, screenshot, small PDF) directly to a memory. The bytes are stored inline in the memory row, so the attachment travels machine-to-machine over the normal sync and works with a remote/dockerized server (no shared filesystem). Limit ~10MB — use gnosys_ingest_file for large media.", {
|
|
2785
|
+
memoryId: z.string().describe("Memory ID to attach the file to (e.g., 'deci-052')"),
|
|
2786
|
+
filePath: z.string().describe("Absolute path to the file to attach"),
|
|
2787
|
+
projectRoot: projectRootParam,
|
|
2788
|
+
}, async ({ memoryId, filePath, projectRoot }) => {
|
|
2789
|
+
const ctx = await resolveToolContext(projectRoot);
|
|
2790
|
+
if (!ctx.centralDb?.isAvailable()) {
|
|
2791
|
+
return {
|
|
2792
|
+
content: [{ type: "text", text: "Database not available. Cannot attach file." }],
|
|
2793
|
+
isError: true,
|
|
2794
|
+
};
|
|
2795
|
+
}
|
|
2796
|
+
try {
|
|
2797
|
+
const { attachFileToMemory } = await import("./lib/attachments.js");
|
|
2798
|
+
const result = await attachFileToMemory(ctx.centralDb, memoryId, filePath);
|
|
2799
|
+
auditToDb(ctx.centralDb, "write", memoryId, {
|
|
2800
|
+
tool: "gnosys_attach",
|
|
2801
|
+
name: result.name,
|
|
2802
|
+
mime: result.mime,
|
|
2803
|
+
sizeBytes: result.sizeBytes,
|
|
2804
|
+
unchanged: result.unchanged,
|
|
2805
|
+
});
|
|
2806
|
+
const sizeKb = (result.sizeBytes / 1024).toFixed(1);
|
|
2807
|
+
const verb = result.unchanged ? "already attached (no change)" : "attached";
|
|
2808
|
+
return {
|
|
2809
|
+
content: [
|
|
2810
|
+
{
|
|
2811
|
+
type: "text",
|
|
2812
|
+
text: `File ${verb}: ${result.name} (${result.mime}, ${sizeKb} KB)\nMemory: ${memoryId}`,
|
|
2813
|
+
},
|
|
2814
|
+
],
|
|
2815
|
+
};
|
|
2816
|
+
}
|
|
2817
|
+
catch (err) {
|
|
2818
|
+
return {
|
|
2819
|
+
content: [{ type: "text", text: formatMcpError("attaching file", err) }],
|
|
2820
|
+
isError: true,
|
|
2821
|
+
};
|
|
2822
|
+
}
|
|
2823
|
+
});
|
|
2824
|
+
// ─── Tool: gnosys_get_attachment ────────────────────────────────────────
|
|
2825
|
+
regTool("gnosys_get_attachment", "Retrieve the binary attachment stored on a memory. By default returns the bytes (base64, plus an inline image when the attachment is an image). Pass outputPath to write the file to disk instead.", {
|
|
2826
|
+
memoryId: z.string().describe("Memory ID that holds the attachment"),
|
|
2827
|
+
outputPath: z.string().optional().describe("If provided, write the attachment to this absolute path instead of returning bytes"),
|
|
2828
|
+
projectRoot: projectRootParam,
|
|
2829
|
+
}, async ({ memoryId, outputPath, projectRoot }) => {
|
|
2830
|
+
const ctx = await resolveToolContext(projectRoot);
|
|
2831
|
+
if (!ctx.centralDb?.isAvailable()) {
|
|
2832
|
+
return {
|
|
2833
|
+
content: [{ type: "text", text: "Database not available." }],
|
|
2834
|
+
isError: true,
|
|
2835
|
+
};
|
|
2836
|
+
}
|
|
2837
|
+
try {
|
|
2838
|
+
const { getMemoryAttachment } = await import("./lib/attachments.js");
|
|
2839
|
+
const att = getMemoryAttachment(ctx.centralDb, memoryId);
|
|
2840
|
+
if (!att) {
|
|
2841
|
+
return {
|
|
2842
|
+
content: [{ type: "text", text: `No attachment found on memory: ${memoryId}` }],
|
|
2843
|
+
isError: true,
|
|
2844
|
+
};
|
|
2845
|
+
}
|
|
2846
|
+
if (outputPath) {
|
|
2847
|
+
const { writeFile } = await import("fs/promises");
|
|
2848
|
+
await writeFile(outputPath, att.data);
|
|
2849
|
+
return {
|
|
2850
|
+
content: [
|
|
2851
|
+
{
|
|
2852
|
+
type: "text",
|
|
2853
|
+
text: `Wrote ${att.name} (${att.mime}, ${att.data.length} bytes) to ${outputPath}`,
|
|
2854
|
+
},
|
|
2855
|
+
],
|
|
2856
|
+
};
|
|
2857
|
+
}
|
|
2858
|
+
const base64 = att.data.toString("base64");
|
|
2859
|
+
if (att.mime.startsWith("image/")) {
|
|
2860
|
+
return {
|
|
2861
|
+
content: [
|
|
2862
|
+
{ type: "image", data: base64, mimeType: att.mime },
|
|
2863
|
+
{ type: "text", text: `${att.name} (${att.mime}, ${att.data.length} bytes)` },
|
|
2864
|
+
],
|
|
2865
|
+
};
|
|
2866
|
+
}
|
|
2867
|
+
return {
|
|
2868
|
+
content: [
|
|
2869
|
+
{
|
|
2870
|
+
type: "text",
|
|
2871
|
+
text: `${att.name} (${att.mime}, ${att.data.length} bytes)\n\nbase64:\n${base64}`,
|
|
2872
|
+
},
|
|
2873
|
+
],
|
|
2874
|
+
};
|
|
2875
|
+
}
|
|
2876
|
+
catch (err) {
|
|
2877
|
+
return {
|
|
2878
|
+
content: [{ type: "text", text: formatMcpError("reading attachment", err) }],
|
|
2879
|
+
isError: true,
|
|
2880
|
+
};
|
|
2881
|
+
}
|
|
2882
|
+
});
|
|
2598
2883
|
// ─── Tool: gnosys_update_status ─────────────────────────────────────────
|
|
2599
2884
|
regTool("gnosys_update_status", "Get the prompt/template for writing a dashboard-compatible status memory for this project. Returns instructions for creating a landscape memory with the correct heading format so the portfolio dashboard can parse it. Run this, then follow the instructions to analyze and write the status.", {
|
|
2600
2885
|
projectRoot: z.string().optional().describe("Project root for auto-detection"),
|
|
@@ -2887,11 +3172,10 @@ async function initHeavyDeps() {
|
|
|
2887
3172
|
const embCount = embeddings.hasEmbeddings() ? embeddings.count() : 0;
|
|
2888
3173
|
console.error(`Hybrid search: ${embCount > 0 ? `ready (${embCount} embeddings)` : "available (run gnosys_reindex to build embeddings)"}`);
|
|
2889
3174
|
console.error(`Ask engine: ${askEngine.isLLMAvailable ? `ready (${askEngine.providerName}/${askEngine.modelName})` : "disabled (configure LLM provider)"}`);
|
|
2890
|
-
// Dream mode
|
|
3175
|
+
// Dream mode scheduling is machine-level now (`gnosys dream run --scheduled`
|
|
3176
|
+
// via launchd). MCP may still run manual dream tool calls, but it no longer
|
|
3177
|
+
// owns an idle timer per connection.
|
|
2891
3178
|
if (gnosysDb && config.dream?.enabled) {
|
|
2892
|
-
const { GnosysDreamEngine, DreamScheduler } = await import("./lib/dream.js");
|
|
2893
|
-
const dreamEngine = new GnosysDreamEngine(gnosysDb, config, config.dream);
|
|
2894
|
-
dreamScheduler = new DreamScheduler(dreamEngine, config.dream);
|
|
2895
3179
|
// Layer 3: probe the dream provider if this machine is the dream node.
|
|
2896
3180
|
try {
|
|
2897
3181
|
const designated = gnosysDb.getDreamMachineId();
|
|
@@ -2933,9 +3217,8 @@ async function initHeavyDeps() {
|
|
|
2933
3217
|
}
|
|
2934
3218
|
}
|
|
2935
3219
|
catch {
|
|
2936
|
-
// Probe failed — non-fatal.
|
|
3220
|
+
// Probe failed — non-fatal.
|
|
2937
3221
|
}
|
|
2938
|
-
dreamScheduler.start();
|
|
2939
3222
|
const designated = gnosysDb.getDreamMachineId();
|
|
2940
3223
|
const localId = gnosysDb.getMeta("machine_id");
|
|
2941
3224
|
if (!designated) {
|
|
@@ -2945,7 +3228,7 @@ async function initHeavyDeps() {
|
|
|
2945
3228
|
console.error(`Dream Mode: enabled — designated to '${designated}'. This machine (${localId || "?"}) will not dream.`);
|
|
2946
3229
|
}
|
|
2947
3230
|
else {
|
|
2948
|
-
console.error(`Dream Mode: enabled on this machine (
|
|
3231
|
+
console.error(`Dream Mode: enabled on this machine (scheduled outside MCP via launchd, max ${config.dream.maxRuntimeMinutes}min)`);
|
|
2949
3232
|
}
|
|
2950
3233
|
}
|
|
2951
3234
|
else {
|
|
@@ -2957,6 +3240,15 @@ async function initHeavyDeps() {
|
|
|
2957
3240
|
// ─── Start the server ────────────────────────────────────────────────────
|
|
2958
3241
|
/** Start the MCP server (stdio or http). Called by `gnosys serve` and when invoked as `gnosys-mcp`. */
|
|
2959
3242
|
export async function startMcpServer() {
|
|
3243
|
+
// v5.12.1 reliability: an escaped async error must not kill the server.
|
|
3244
|
+
// Log to stderr (stdout is JSON-RPC in stdio mode) and keep serving —
|
|
3245
|
+
// all persistent state is transactional SQLite, so survivors are safe.
|
|
3246
|
+
process.on("unhandledRejection", (reason) => {
|
|
3247
|
+
console.error(`Gnosys MCP: unhandled rejection — ${reason instanceof Error ? reason.stack || reason.message : String(reason)}`);
|
|
3248
|
+
});
|
|
3249
|
+
process.on("uncaughtException", (err) => {
|
|
3250
|
+
console.error(`Gnosys MCP: uncaught exception — ${err.stack || err.message}`);
|
|
3251
|
+
});
|
|
2960
3252
|
// v5.7.1 (#15): start the upgrade-marker watcher BEFORE anything else.
|
|
2961
3253
|
// If `gnosys upgrade` was run on this machine while the MCP was idle,
|
|
2962
3254
|
// pick that up immediately instead of serving stale tool handlers.
|
|
@@ -2985,6 +3277,12 @@ export async function startMcpServer() {
|
|
|
2985
3277
|
console.error("Gnosys MCP server starting.");
|
|
2986
3278
|
console.error("Active stores:");
|
|
2987
3279
|
console.error(resolver.getSummary());
|
|
3280
|
+
// v13: background ingest sweep on master-role MCP startup (non-blocking).
|
|
3281
|
+
void import("./lib/syncIngestStartup.js")
|
|
3282
|
+
.then(({ maybeRunStartupIngestSweep }) => maybeRunStartupIngestSweep())
|
|
3283
|
+
.catch((err) => {
|
|
3284
|
+
console.error(`[sync] Startup ingest sweep failed: ${err instanceof Error ? err.message : err}`);
|
|
3285
|
+
});
|
|
2988
3286
|
// Initialize search from the first writable store. Everything in this
|
|
2989
3287
|
// block is FAST — opening the search index + tag registry + loading
|
|
2990
3288
|
// gnosys.json. The slow stuff (LLM providers, transformers embeddings,
|