exovault-mcp-server 1.0.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/auth.d.ts +41 -0
- package/dist/auth.d.ts.map +1 -0
- package/dist/auth.js +236 -0
- package/dist/auth.js.map +1 -0
- package/dist/auto-session.d.ts +39 -0
- package/dist/auto-session.d.ts.map +1 -0
- package/dist/auto-session.js +128 -0
- package/dist/auto-session.js.map +1 -0
- package/dist/buffer-persistence.d.ts +35 -0
- package/dist/buffer-persistence.d.ts.map +1 -0
- package/dist/buffer-persistence.js +110 -0
- package/dist/buffer-persistence.js.map +1 -0
- package/dist/coerce-params.d.ts +36 -0
- package/dist/coerce-params.d.ts.map +1 -0
- package/dist/coerce-params.js +120 -0
- package/dist/coerce-params.js.map +1 -0
- package/dist/crypto.d.ts +39 -0
- package/dist/crypto.d.ts.map +1 -0
- package/dist/crypto.js +119 -0
- package/dist/crypto.js.map +1 -0
- package/dist/db.d.ts +350 -0
- package/dist/db.d.ts.map +1 -0
- package/dist/db.js +866 -0
- package/dist/db.js.map +1 -0
- package/dist/embedding-config.d.ts +11 -0
- package/dist/embedding-config.d.ts.map +1 -0
- package/dist/embedding-config.js +24 -0
- package/dist/embedding-config.js.map +1 -0
- package/dist/entity-extraction.d.ts +22 -0
- package/dist/entity-extraction.d.ts.map +1 -0
- package/dist/entity-extraction.js +140 -0
- package/dist/entity-extraction.js.map +1 -0
- package/dist/episodic-headline.d.ts +6 -0
- package/dist/episodic-headline.d.ts.map +1 -0
- package/dist/episodic-headline.js +62 -0
- package/dist/episodic-headline.js.map +1 -0
- package/dist/error-sanitizer.d.ts +20 -0
- package/dist/error-sanitizer.d.ts.map +1 -0
- package/dist/error-sanitizer.js +54 -0
- package/dist/error-sanitizer.js.map +1 -0
- package/dist/extraction-budget.d.ts +39 -0
- package/dist/extraction-budget.d.ts.map +1 -0
- package/dist/extraction-budget.js +122 -0
- package/dist/extraction-budget.js.map +1 -0
- package/dist/extraction-llm.d.ts +22 -0
- package/dist/extraction-llm.d.ts.map +1 -0
- package/dist/extraction-llm.js +32 -0
- package/dist/extraction-llm.js.map +1 -0
- package/dist/extraction-prompt.d.ts +40 -0
- package/dist/extraction-prompt.d.ts.map +1 -0
- package/dist/extraction-prompt.js +176 -0
- package/dist/extraction-prompt.js.map +1 -0
- package/dist/gateway-client.d.ts +303 -0
- package/dist/gateway-client.d.ts.map +1 -0
- package/dist/gateway-client.js +285 -0
- package/dist/gateway-client.js.map +1 -0
- package/dist/gateway-init.d.ts +32 -0
- package/dist/gateway-init.d.ts.map +1 -0
- package/dist/gateway-init.js +71 -0
- package/dist/gateway-init.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1242 -0
- package/dist/index.js.map +1 -0
- package/dist/infer-task-status.d.ts +7 -0
- package/dist/infer-task-status.d.ts.map +1 -0
- package/dist/infer-task-status.js +23 -0
- package/dist/infer-task-status.js.map +1 -0
- package/dist/normalize-agent-id.d.ts +21 -0
- package/dist/normalize-agent-id.d.ts.map +1 -0
- package/dist/normalize-agent-id.js +54 -0
- package/dist/normalize-agent-id.js.map +1 -0
- package/dist/openai.d.ts +14 -0
- package/dist/openai.d.ts.map +1 -0
- package/dist/openai.js +43 -0
- package/dist/openai.js.map +1 -0
- package/dist/rlm/actions.d.ts +31 -0
- package/dist/rlm/actions.d.ts.map +1 -0
- package/dist/rlm/actions.js +241 -0
- package/dist/rlm/actions.js.map +1 -0
- package/dist/rlm/benchmark.d.ts +2 -0
- package/dist/rlm/benchmark.d.ts.map +1 -0
- package/dist/rlm/benchmark.js +215 -0
- package/dist/rlm/benchmark.js.map +1 -0
- package/dist/rlm/execute.d.ts +13 -0
- package/dist/rlm/execute.d.ts.map +1 -0
- package/dist/rlm/execute.js +366 -0
- package/dist/rlm/execute.js.map +1 -0
- package/dist/rlm/index.d.ts +6 -0
- package/dist/rlm/index.d.ts.map +1 -0
- package/dist/rlm/index.js +147 -0
- package/dist/rlm/index.js.map +1 -0
- package/dist/rlm/profiles.d.ts +9 -0
- package/dist/rlm/profiles.d.ts.map +1 -0
- package/dist/rlm/profiles.js +46 -0
- package/dist/rlm/profiles.js.map +1 -0
- package/dist/rlm/types.d.ts +98 -0
- package/dist/rlm/types.d.ts.map +1 -0
- package/dist/rlm/types.js +6 -0
- package/dist/rlm/types.js.map +1 -0
- package/dist/rlm/verify.d.ts +13 -0
- package/dist/rlm/verify.d.ts.map +1 -0
- package/dist/rlm/verify.js +58 -0
- package/dist/rlm/verify.js.map +1 -0
- package/dist/rlm/writeback.d.ts +7 -0
- package/dist/rlm/writeback.d.ts.map +1 -0
- package/dist/rlm/writeback.js +77 -0
- package/dist/rlm/writeback.js.map +1 -0
- package/dist/scripts/backfill-memory-embeddings.d.ts +2 -0
- package/dist/scripts/backfill-memory-embeddings.d.ts.map +1 -0
- package/dist/scripts/backfill-memory-embeddings.js +153 -0
- package/dist/scripts/backfill-memory-embeddings.js.map +1 -0
- package/dist/session-buffer.d.ts +104 -0
- package/dist/session-buffer.d.ts.map +1 -0
- package/dist/session-buffer.js +466 -0
- package/dist/session-buffer.js.map +1 -0
- package/dist/session-dedup.d.ts +30 -0
- package/dist/session-dedup.d.ts.map +1 -0
- package/dist/session-dedup.js +67 -0
- package/dist/session-dedup.js.map +1 -0
- package/dist/session-flush.d.ts +81 -0
- package/dist/session-flush.d.ts.map +1 -0
- package/dist/session-flush.js +169 -0
- package/dist/session-flush.js.map +1 -0
- package/dist/session-lifecycle.d.ts +72 -0
- package/dist/session-lifecycle.d.ts.map +1 -0
- package/dist/session-lifecycle.js +247 -0
- package/dist/session-lifecycle.js.map +1 -0
- package/dist/setup.d.ts +2 -0
- package/dist/setup.d.ts.map +1 -0
- package/dist/setup.js +260 -0
- package/dist/setup.js.map +1 -0
- package/dist/stopwords.d.ts +2 -0
- package/dist/stopwords.d.ts.map +1 -0
- package/dist/stopwords.js +20 -0
- package/dist/stopwords.js.map +1 -0
- package/dist/strip-html.d.ts +5 -0
- package/dist/strip-html.d.ts.map +1 -0
- package/dist/strip-html.js +35 -0
- package/dist/strip-html.js.map +1 -0
- package/dist/task-completion-flush.d.ts +36 -0
- package/dist/task-completion-flush.d.ts.map +1 -0
- package/dist/task-completion-flush.js +97 -0
- package/dist/task-completion-flush.js.map +1 -0
- package/dist/task-lifecycle-types.d.ts +13 -0
- package/dist/task-lifecycle-types.d.ts.map +1 -0
- package/dist/task-lifecycle-types.js +12 -0
- package/dist/task-lifecycle-types.js.map +1 -0
- package/dist/task-lifecycle.d.ts +78 -0
- package/dist/task-lifecycle.d.ts.map +1 -0
- package/dist/task-lifecycle.js +256 -0
- package/dist/task-lifecycle.js.map +1 -0
- package/dist/tools/agent-messages.d.ts +26 -0
- package/dist/tools/agent-messages.d.ts.map +1 -0
- package/dist/tools/agent-messages.js +123 -0
- package/dist/tools/agent-messages.js.map +1 -0
- package/dist/tools/agent-tasks.d.ts +24 -0
- package/dist/tools/agent-tasks.d.ts.map +1 -0
- package/dist/tools/agent-tasks.js +162 -0
- package/dist/tools/agent-tasks.js.map +1 -0
- package/dist/tools/archive-memory.d.ts +2 -0
- package/dist/tools/archive-memory.d.ts.map +1 -0
- package/dist/tools/archive-memory.js +19 -0
- package/dist/tools/archive-memory.js.map +1 -0
- package/dist/tools/blind-index.d.ts +29 -0
- package/dist/tools/blind-index.d.ts.map +1 -0
- package/dist/tools/blind-index.js +53 -0
- package/dist/tools/blind-index.js.map +1 -0
- package/dist/tools/cleanup-memories.d.ts +44 -0
- package/dist/tools/cleanup-memories.d.ts.map +1 -0
- package/dist/tools/cleanup-memories.js +126 -0
- package/dist/tools/cleanup-memories.js.map +1 -0
- package/dist/tools/context-checkpoint.d.ts +28 -0
- package/dist/tools/context-checkpoint.d.ts.map +1 -0
- package/dist/tools/context-checkpoint.js +140 -0
- package/dist/tools/context-checkpoint.js.map +1 -0
- package/dist/tools/context-profiles.d.ts +67 -0
- package/dist/tools/context-profiles.d.ts.map +1 -0
- package/dist/tools/context-profiles.js +30 -0
- package/dist/tools/context-profiles.js.map +1 -0
- package/dist/tools/create-note.d.ts +2 -0
- package/dist/tools/create-note.d.ts.map +1 -0
- package/dist/tools/create-note.js +60 -0
- package/dist/tools/create-note.js.map +1 -0
- package/dist/tools/create-vault.d.ts +5 -0
- package/dist/tools/create-vault.d.ts.map +1 -0
- package/dist/tools/create-vault.js +121 -0
- package/dist/tools/create-vault.js.map +1 -0
- package/dist/tools/decrypt-helpers.d.ts +31 -0
- package/dist/tools/decrypt-helpers.d.ts.map +1 -0
- package/dist/tools/decrypt-helpers.js +33 -0
- package/dist/tools/decrypt-helpers.js.map +1 -0
- package/dist/tools/delete-note.d.ts +2 -0
- package/dist/tools/delete-note.d.ts.map +1 -0
- package/dist/tools/delete-note.js +21 -0
- package/dist/tools/delete-note.js.map +1 -0
- package/dist/tools/explore-graph.d.ts +11 -0
- package/dist/tools/explore-graph.d.ts.map +1 -0
- package/dist/tools/explore-graph.js +169 -0
- package/dist/tools/explore-graph.js.map +1 -0
- package/dist/tools/get-related-memories.d.ts +2 -0
- package/dist/tools/get-related-memories.d.ts.map +1 -0
- package/dist/tools/get-related-memories.js +59 -0
- package/dist/tools/get-related-memories.js.map +1 -0
- package/dist/tools/knowledge-links.d.ts +17 -0
- package/dist/tools/knowledge-links.d.ts.map +1 -0
- package/dist/tools/knowledge-links.js +102 -0
- package/dist/tools/knowledge-links.js.map +1 -0
- package/dist/tools/list-active-agents.d.ts +5 -0
- package/dist/tools/list-active-agents.d.ts.map +1 -0
- package/dist/tools/list-active-agents.js +15 -0
- package/dist/tools/list-active-agents.js.map +1 -0
- package/dist/tools/list-notes.d.ts +2 -0
- package/dist/tools/list-notes.d.ts.map +1 -0
- package/dist/tools/list-notes.js +19 -0
- package/dist/tools/list-notes.js.map +1 -0
- package/dist/tools/list-vaults.d.ts +2 -0
- package/dist/tools/list-vaults.d.ts.map +1 -0
- package/dist/tools/list-vaults.js +19 -0
- package/dist/tools/list-vaults.js.map +1 -0
- package/dist/tools/mmr.d.ts +18 -0
- package/dist/tools/mmr.d.ts.map +1 -0
- package/dist/tools/mmr.js +67 -0
- package/dist/tools/mmr.js.map +1 -0
- package/dist/tools/read-memories.d.ts +2 -0
- package/dist/tools/read-memories.d.ts.map +1 -0
- package/dist/tools/read-memories.js +46 -0
- package/dist/tools/read-memories.js.map +1 -0
- package/dist/tools/read-note.d.ts +2 -0
- package/dist/tools/read-note.d.ts.map +1 -0
- package/dist/tools/read-note.js +35 -0
- package/dist/tools/read-note.js.map +1 -0
- package/dist/tools/read-notes.d.ts +6 -0
- package/dist/tools/read-notes.d.ts.map +1 -0
- package/dist/tools/read-notes.js +45 -0
- package/dist/tools/read-notes.js.map +1 -0
- package/dist/tools/resolve-vault-id.d.ts +6 -0
- package/dist/tools/resolve-vault-id.d.ts.map +1 -0
- package/dist/tools/resolve-vault-id.js +7 -0
- package/dist/tools/resolve-vault-id.js.map +1 -0
- package/dist/tools/rrf.d.ts +28 -0
- package/dist/tools/rrf.d.ts.map +1 -0
- package/dist/tools/rrf.js +19 -0
- package/dist/tools/rrf.js.map +1 -0
- package/dist/tools/search-and-read.d.ts +11 -0
- package/dist/tools/search-and-read.d.ts.map +1 -0
- package/dist/tools/search-and-read.js +208 -0
- package/dist/tools/search-and-read.js.map +1 -0
- package/dist/tools/search-memories.d.ts +13 -0
- package/dist/tools/search-memories.d.ts.map +1 -0
- package/dist/tools/search-memories.js +272 -0
- package/dist/tools/search-memories.js.map +1 -0
- package/dist/tools/search-notes.d.ts +2 -0
- package/dist/tools/search-notes.d.ts.map +1 -0
- package/dist/tools/search-notes.js +94 -0
- package/dist/tools/search-notes.js.map +1 -0
- package/dist/tools/semantic-search.d.ts +7 -0
- package/dist/tools/semantic-search.d.ts.map +1 -0
- package/dist/tools/semantic-search.js +85 -0
- package/dist/tools/semantic-search.js.map +1 -0
- package/dist/tools/session-start.d.ts +24 -0
- package/dist/tools/session-start.d.ts.map +1 -0
- package/dist/tools/session-start.js +256 -0
- package/dist/tools/session-start.js.map +1 -0
- package/dist/tools/stale-tasks.d.ts +22 -0
- package/dist/tools/stale-tasks.d.ts.map +1 -0
- package/dist/tools/stale-tasks.js +39 -0
- package/dist/tools/stale-tasks.js.map +1 -0
- package/dist/tools/temporal-decay.d.ts +21 -0
- package/dist/tools/temporal-decay.d.ts.map +1 -0
- package/dist/tools/temporal-decay.js +32 -0
- package/dist/tools/temporal-decay.js.map +1 -0
- package/dist/tools/update-memory.d.ts +19 -0
- package/dist/tools/update-memory.d.ts.map +1 -0
- package/dist/tools/update-memory.js +230 -0
- package/dist/tools/update-memory.js.map +1 -0
- package/dist/tools/update-note.d.ts +2 -0
- package/dist/tools/update-note.d.ts.map +1 -0
- package/dist/tools/update-note.js +79 -0
- package/dist/tools/update-note.js.map +1 -0
- package/dist/tools/vault-instruction-template.d.ts +17 -0
- package/dist/tools/vault-instruction-template.d.ts.map +1 -0
- package/dist/tools/vault-instruction-template.js +77 -0
- package/dist/tools/vault-instruction-template.js.map +1 -0
- package/dist/tools/wiki-link-sync.d.ts +34 -0
- package/dist/tools/wiki-link-sync.d.ts.map +1 -0
- package/dist/tools/wiki-link-sync.js +132 -0
- package/dist/tools/wiki-link-sync.js.map +1 -0
- package/dist/tools/wrap-tool-handler.d.ts +8 -0
- package/dist/tools/wrap-tool-handler.d.ts.map +1 -0
- package/dist/tools/wrap-tool-handler.js +32 -0
- package/dist/tools/wrap-tool-handler.js.map +1 -0
- package/dist/tools/write-memory.d.ts +34 -0
- package/dist/tools/write-memory.d.ts.map +1 -0
- package/dist/tools/write-memory.js +359 -0
- package/dist/tools/write-memory.js.map +1 -0
- package/dist/usage.d.ts +11 -0
- package/dist/usage.d.ts.map +1 -0
- package/dist/usage.js +38 -0
- package/dist/usage.js.map +1 -0
- package/dist/wiki-link-parser.d.ts +27 -0
- package/dist/wiki-link-parser.d.ts.map +1 -0
- package/dist/wiki-link-parser.js +93 -0
- package/dist/wiki-link-parser.js.map +1 -0
- package/package.json +38 -0
package/dist/auth.d.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { type SupabaseClient } from "@supabase/supabase-js";
|
|
2
|
+
export interface McpConfig {
|
|
3
|
+
supabaseUrl: string;
|
|
4
|
+
supabaseAnonKey: string;
|
|
5
|
+
accessToken: string;
|
|
6
|
+
refreshToken: string;
|
|
7
|
+
mekHex: string;
|
|
8
|
+
openaiApiKey?: string;
|
|
9
|
+
defaultVaultId?: string;
|
|
10
|
+
allowGlobalMemoryWrite?: boolean;
|
|
11
|
+
llmApiKey?: string;
|
|
12
|
+
llmBaseUrl?: string;
|
|
13
|
+
llmModelId?: string;
|
|
14
|
+
/** Agent key for gateway mode (exv_...) */
|
|
15
|
+
agentKey?: string;
|
|
16
|
+
/** Gateway base URL (default: https://exovault.co) */
|
|
17
|
+
apiUrl?: string;
|
|
18
|
+
}
|
|
19
|
+
export interface LlmConfig {
|
|
20
|
+
apiKey: string;
|
|
21
|
+
baseUrl: string;
|
|
22
|
+
modelId: string;
|
|
23
|
+
}
|
|
24
|
+
export interface McpContext {
|
|
25
|
+
supabase: SupabaseClient;
|
|
26
|
+
userId: string;
|
|
27
|
+
masterKey: CryptoKey;
|
|
28
|
+
/** Raw MEK hex for blind index key derivation (HMAC-SHA256). */
|
|
29
|
+
mekHex: string;
|
|
30
|
+
openaiApiKey?: string;
|
|
31
|
+
defaultVaultId?: string;
|
|
32
|
+
allowGlobalMemoryWrite: boolean;
|
|
33
|
+
/** Server-side LLM config for entity extraction and other server operations. */
|
|
34
|
+
llmConfig?: LlmConfig;
|
|
35
|
+
}
|
|
36
|
+
export declare function readConfig(): Promise<McpConfig>;
|
|
37
|
+
/**
|
|
38
|
+
* Reads stored config, refreshes Supabase session (with cross-process lock),
|
|
39
|
+
* and imports the MEK. Called once on MCP server startup.
|
|
40
|
+
*/
|
|
41
|
+
export declare function initialize(): Promise<McpContext>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,KAAK,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAiB1E,MAAM,WAAW,SAAS;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,MAAM,CAAC;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,sBAAsB,CAAC,EAAE,OAAO,CAAC;IACjC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,2CAA2C;IAC3C,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,uDAAuD;IACvD,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,SAAS;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,UAAU;IACzB,QAAQ,EAAE,cAAc,CAAC;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,SAAS,CAAC;IACrB,gEAAgE;IAChE,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,sBAAsB,EAAE,OAAO,CAAC;IAChC,gFAAgF;IAChF,SAAS,CAAC,EAAE,SAAS,CAAC;CACvB;AAgED,wBAAsB,UAAU,IAAI,OAAO,CAAC,SAAS,CAAC,CAmBrD;AAoHD;;;GAGG;AACH,wBAAsB,UAAU,IAAI,OAAO,CAAC,UAAU,CAAC,CAkFtD"}
|
package/dist/auth.js
ADDED
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import { createClient } from "@supabase/supabase-js";
|
|
2
|
+
import { readFile, writeFile, rename, open, unlink, stat } from "node:fs/promises";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { homedir } from "node:os";
|
|
5
|
+
import { hexToBuffer, importMasterKey } from "./crypto.js";
|
|
6
|
+
const CONFIG_DIR = join(homedir(), ".exovault-mcp");
|
|
7
|
+
const CONFIG_FILE = join(CONFIG_DIR, "config.json");
|
|
8
|
+
const LOCK_FILE = join(CONFIG_DIR, "refresh.lock");
|
|
9
|
+
/** Max age (ms) before a lock is considered stale (crashed process). */
|
|
10
|
+
const LOCK_STALE_MS = 10_000;
|
|
11
|
+
/** How long to wait between lock-retry attempts (ms). */
|
|
12
|
+
const LOCK_POLL_MS = 200;
|
|
13
|
+
/** Max total time to wait for a lock before giving up (ms). */
|
|
14
|
+
const LOCK_TIMEOUT_MS = 15_000;
|
|
15
|
+
// ── Lock helpers ──────────────────────────────────────────────────────
|
|
16
|
+
async function acquireLock() {
|
|
17
|
+
try {
|
|
18
|
+
// O_EXCL fails atomically if the file already exists
|
|
19
|
+
const handle = await open(LOCK_FILE, "wx");
|
|
20
|
+
await handle.writeFile(String(process.pid));
|
|
21
|
+
await handle.close();
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
24
|
+
catch (err) {
|
|
25
|
+
if (err instanceof Error && "code" in err && err.code === "EEXIST") {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
throw err;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
async function releaseLock() {
|
|
32
|
+
try {
|
|
33
|
+
await unlink(LOCK_FILE);
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
// Already removed — not a problem
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
async function isLockStale() {
|
|
40
|
+
try {
|
|
41
|
+
const info = await stat(LOCK_FILE);
|
|
42
|
+
return Date.now() - info.mtimeMs > LOCK_STALE_MS;
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
// Lock file gone — treat as stale so caller can proceed
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Wait until we either acquire the lock or the other holder finishes.
|
|
51
|
+
* Returns `"acquired"` if we own the lock (caller must refresh + release),
|
|
52
|
+
* or `"waited"` if another process already refreshed (caller should re-read config).
|
|
53
|
+
*/
|
|
54
|
+
async function waitForLock() {
|
|
55
|
+
const deadline = Date.now() + LOCK_TIMEOUT_MS;
|
|
56
|
+
while (Date.now() < deadline) {
|
|
57
|
+
if (await acquireLock())
|
|
58
|
+
return "acquired";
|
|
59
|
+
// Check for stale lock from a crashed process
|
|
60
|
+
if (await isLockStale()) {
|
|
61
|
+
await releaseLock();
|
|
62
|
+
if (await acquireLock())
|
|
63
|
+
return "acquired";
|
|
64
|
+
}
|
|
65
|
+
await new Promise((r) => setTimeout(r, LOCK_POLL_MS));
|
|
66
|
+
}
|
|
67
|
+
// Timeout — another process held the lock long enough that it likely refreshed
|
|
68
|
+
// Re-read config to get the new tokens
|
|
69
|
+
return "waited";
|
|
70
|
+
}
|
|
71
|
+
// ── Read config helper ────────────────────────────────────────────────
|
|
72
|
+
export async function readConfig() {
|
|
73
|
+
let configRaw;
|
|
74
|
+
try {
|
|
75
|
+
configRaw = await readFile(CONFIG_FILE, "utf-8");
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
throw new Error(`Config not found at ${CONFIG_FILE}. Run 'npx exovault-mcp setup --url <url> --key <key>' first.`);
|
|
79
|
+
}
|
|
80
|
+
const config = JSON.parse(configRaw);
|
|
81
|
+
// Gateway mode only needs agentKey — skip Supabase credential check
|
|
82
|
+
if (!config.agentKey && (!config.supabaseUrl || !config.supabaseAnonKey)) {
|
|
83
|
+
throw new Error(`Config is missing Supabase credentials. Re-run 'npx exovault-mcp setup --url <url> --key <key>'.`);
|
|
84
|
+
}
|
|
85
|
+
return config;
|
|
86
|
+
}
|
|
87
|
+
function isRefreshTokenAlreadyUsed(message) {
|
|
88
|
+
if (!message)
|
|
89
|
+
return false;
|
|
90
|
+
const normalized = message.toLowerCase();
|
|
91
|
+
return normalized.includes("refresh token") && normalized.includes("already used");
|
|
92
|
+
}
|
|
93
|
+
async function fallbackWithAccessToken(config) {
|
|
94
|
+
const supabase = createClient(config.supabaseUrl, config.supabaseAnonKey, {
|
|
95
|
+
auth: {
|
|
96
|
+
persistSession: false,
|
|
97
|
+
autoRefreshToken: false,
|
|
98
|
+
},
|
|
99
|
+
global: {
|
|
100
|
+
headers: {
|
|
101
|
+
Authorization: `Bearer ${config.accessToken}`,
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
});
|
|
105
|
+
const { data, error } = await supabase.auth.getUser(config.accessToken);
|
|
106
|
+
if (error || !data.user) {
|
|
107
|
+
throw new Error(`Session expired or invalid. Re-run 'npx exovault-mcp setup --url <url> --key <key>'. ${error?.message ?? ""}`);
|
|
108
|
+
}
|
|
109
|
+
return { supabase, userId: data.user.id };
|
|
110
|
+
}
|
|
111
|
+
async function setSessionWithConfig(supabase, config) {
|
|
112
|
+
return supabase.auth.setSession({
|
|
113
|
+
refresh_token: config.refreshToken,
|
|
114
|
+
access_token: config.accessToken,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
// ── Token persistence ─────────────────────────────────────────────────
|
|
118
|
+
async function persistTokens(accessToken, refreshToken) {
|
|
119
|
+
const config = await readConfig();
|
|
120
|
+
const content = JSON.stringify({ ...config, accessToken, refreshToken }, null, 2);
|
|
121
|
+
// Atomic write: write to temp file then rename to prevent corruption on crash
|
|
122
|
+
const tmpFile = `${CONFIG_FILE}.tmp`;
|
|
123
|
+
await writeFile(tmpFile, content, { encoding: "utf-8", mode: 0o600 });
|
|
124
|
+
await rename(tmpFile, CONFIG_FILE);
|
|
125
|
+
}
|
|
126
|
+
// ── Refresh session (called only by lock holder) ──────────────────────
|
|
127
|
+
async function refreshSession(config) {
|
|
128
|
+
const supabase = createClient(config.supabaseUrl, config.supabaseAnonKey);
|
|
129
|
+
let { data: setData, error: setError } = await setSessionWithConfig(supabase, config);
|
|
130
|
+
// Another process may have rotated refresh token just before we acquired the lock.
|
|
131
|
+
// Re-read config once and retry with newest tokens.
|
|
132
|
+
if ((setError || !setData.session) && isRefreshTokenAlreadyUsed(setError?.message)) {
|
|
133
|
+
const latest = await readConfig();
|
|
134
|
+
({ data: setData, error: setError } = await setSessionWithConfig(supabase, latest));
|
|
135
|
+
config = latest;
|
|
136
|
+
}
|
|
137
|
+
if (setError || !setData.session) {
|
|
138
|
+
if (isRefreshTokenAlreadyUsed(setError?.message)) {
|
|
139
|
+
const accessFallback = await fallbackWithAccessToken(config);
|
|
140
|
+
return {
|
|
141
|
+
supabase: accessFallback.supabase,
|
|
142
|
+
session: { user: { id: accessFallback.userId } },
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
throw new Error(`Session expired or invalid. Re-run 'npx exovault-mcp setup --url <url> --key <key>'. ${setError?.message ?? ""}`);
|
|
146
|
+
}
|
|
147
|
+
// Persist refreshed tokens so subsequent startups (or other agents) use valid tokens
|
|
148
|
+
await persistTokens(setData.session.access_token, setData.session.refresh_token);
|
|
149
|
+
return { supabase, session: setData.session };
|
|
150
|
+
}
|
|
151
|
+
// ── Public API ────────────────────────────────────────────────────────
|
|
152
|
+
function buildLlmConfig(config) {
|
|
153
|
+
const apiKey = config.llmApiKey ?? config.openaiApiKey;
|
|
154
|
+
if (!apiKey)
|
|
155
|
+
return undefined;
|
|
156
|
+
return {
|
|
157
|
+
apiKey,
|
|
158
|
+
baseUrl: config.llmBaseUrl ?? "https://api.openai.com/v1",
|
|
159
|
+
modelId: config.llmModelId ?? "gpt-4o-mini",
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Reads stored config, refreshes Supabase session (with cross-process lock),
|
|
164
|
+
* and imports the MEK. Called once on MCP server startup.
|
|
165
|
+
*/
|
|
166
|
+
export async function initialize() {
|
|
167
|
+
const lockResult = await waitForLock();
|
|
168
|
+
let config = await readConfig();
|
|
169
|
+
let supabase;
|
|
170
|
+
let userId;
|
|
171
|
+
if (lockResult === "acquired") {
|
|
172
|
+
try {
|
|
173
|
+
const result = await refreshSession(config);
|
|
174
|
+
supabase = result.supabase;
|
|
175
|
+
userId = result.session.user.id;
|
|
176
|
+
}
|
|
177
|
+
finally {
|
|
178
|
+
await releaseLock();
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
else {
|
|
182
|
+
// Another process already refreshed — re-read config with fresh tokens
|
|
183
|
+
config = await readConfig();
|
|
184
|
+
supabase = createClient(config.supabaseUrl, config.supabaseAnonKey);
|
|
185
|
+
let { data, error } = await setSessionWithConfig(supabase, config);
|
|
186
|
+
if ((error || !data.session) && isRefreshTokenAlreadyUsed(error?.message)) {
|
|
187
|
+
await new Promise((r) => setTimeout(r, 300));
|
|
188
|
+
config = await readConfig();
|
|
189
|
+
({ data, error } = await setSessionWithConfig(supabase, config));
|
|
190
|
+
}
|
|
191
|
+
if (error || !data.session) {
|
|
192
|
+
if (isRefreshTokenAlreadyUsed(error?.message)) {
|
|
193
|
+
const accessFallback = await fallbackWithAccessToken(config);
|
|
194
|
+
userId = accessFallback.userId;
|
|
195
|
+
supabase = accessFallback.supabase;
|
|
196
|
+
// Continue using current access token for this process.
|
|
197
|
+
// Future startups can still refresh after setup or next token persistence cycle.
|
|
198
|
+
const mekBytes = hexToBuffer(config.mekHex);
|
|
199
|
+
const masterKey = await importMasterKey(mekBytes);
|
|
200
|
+
return {
|
|
201
|
+
supabase,
|
|
202
|
+
userId,
|
|
203
|
+
masterKey,
|
|
204
|
+
mekHex: config.mekHex,
|
|
205
|
+
openaiApiKey: config.openaiApiKey,
|
|
206
|
+
defaultVaultId: config.defaultVaultId,
|
|
207
|
+
allowGlobalMemoryWrite: config.allowGlobalMemoryWrite === true,
|
|
208
|
+
llmConfig: buildLlmConfig(config),
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
throw new Error(`Session expired after waiting for lock. Re-run 'npx exovault-mcp setup --url <url> --key <key>'. ${error?.message ?? ""}`);
|
|
212
|
+
}
|
|
213
|
+
userId = data.session.user.id;
|
|
214
|
+
}
|
|
215
|
+
// Import MEK
|
|
216
|
+
const mekBytes = hexToBuffer(config.mekHex);
|
|
217
|
+
const masterKey = await importMasterKey(mekBytes);
|
|
218
|
+
// Persist tokens whenever the Supabase client auto-refreshes mid-session.
|
|
219
|
+
// Without this, config.json goes stale and future restarts / other processes
|
|
220
|
+
// see expired tokens, causing silent auth failures (RLS returns empty sets).
|
|
221
|
+
supabase.auth.onAuthStateChange((event, session) => {
|
|
222
|
+
if (event === "TOKEN_REFRESHED" && session) {
|
|
223
|
+
persistTokens(session.access_token, session.refresh_token).then(() => process.stderr.write("[exovault-mcp] Token auto-refreshed and persisted\n"), (err) => process.stderr.write(`[exovault-mcp] Failed to persist refreshed token: ${err.message}\n`));
|
|
224
|
+
}
|
|
225
|
+
});
|
|
226
|
+
return {
|
|
227
|
+
supabase,
|
|
228
|
+
userId,
|
|
229
|
+
masterKey,
|
|
230
|
+
mekHex: config.mekHex,
|
|
231
|
+
openaiApiKey: config.openaiApiKey,
|
|
232
|
+
defaultVaultId: config.defaultVaultId,
|
|
233
|
+
allowGlobalMemoryWrite: config.allowGlobalMemoryWrite === true,
|
|
234
|
+
llmConfig: buildLlmConfig(config),
|
|
235
|
+
};
|
|
236
|
+
}
|
package/dist/auth.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auto-session inject: ensures session context is loaded on the first tool call,
|
|
3
|
+
* even if the agent doesn't explicitly call `session_start`.
|
|
4
|
+
*
|
|
5
|
+
* Also tracks tool call count and appends checkpoint reminders at configurable
|
|
6
|
+
* thresholds, coaching agents toward better memory habits.
|
|
7
|
+
*
|
|
8
|
+
* Usage in index.ts:
|
|
9
|
+
* const auto = createAutoSession(() => runSessionStart());
|
|
10
|
+
* server.registerTool("some_tool", opts, auto.wrap(handler));
|
|
11
|
+
* // For session_start itself: call auto.markInjected() inside its handler
|
|
12
|
+
*/
|
|
13
|
+
export type ToolResponse = {
|
|
14
|
+
content: {
|
|
15
|
+
type: "text";
|
|
16
|
+
text: string;
|
|
17
|
+
}[];
|
|
18
|
+
isError?: true;
|
|
19
|
+
};
|
|
20
|
+
export type SessionStartFn = () => Promise<string>;
|
|
21
|
+
export type LogFn = (msg: string) => void;
|
|
22
|
+
/**
|
|
23
|
+
* Callback fired on every tool call for session tracking.
|
|
24
|
+
* Called fire-and-forget — errors are swallowed.
|
|
25
|
+
*/
|
|
26
|
+
export type TrackFn = (toolName: string) => void;
|
|
27
|
+
export interface AutoSessionOptions {
|
|
28
|
+
/** Tool call counts at which to append checkpoint reminders. Default: [25, 50, 75]. */
|
|
29
|
+
reminderThresholds?: number[];
|
|
30
|
+
/** Called on every tool call for session tracking (fire-and-forget). */
|
|
31
|
+
onToolCall?: TrackFn;
|
|
32
|
+
}
|
|
33
|
+
export declare function createAutoSession(runSessionStart: SessionStartFn, log?: LogFn, options?: AutoSessionOptions): {
|
|
34
|
+
markInjected: () => void;
|
|
35
|
+
isInjected: () => boolean;
|
|
36
|
+
getPrefix: () => Promise<string | null>;
|
|
37
|
+
getToolCallCount: () => number;
|
|
38
|
+
wrap: <T>(handler: (args: T) => Promise<ToolResponse>, toolName?: string) => (args: T) => Promise<ToolResponse>;
|
|
39
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auto-session.d.ts","sourceRoot":"","sources":["../src/auto-session.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,MAAM,MAAM,YAAY,GAAG;IACzB,OAAO,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAC1C,OAAO,CAAC,EAAE,IAAI,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC;AACnD,MAAM,MAAM,KAAK,GAAG,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;AAE1C;;;GAGG;AACH,MAAM,MAAM,OAAO,GAAG,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;AAEjD,MAAM,WAAW,kBAAkB;IACjC,uFAAuF;IACvF,kBAAkB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC9B,wEAAwE;IACxE,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAID,wBAAgB,iBAAiB,CAC/B,eAAe,EAAE,cAAc,EAC/B,GAAG,GAAE,KAAgB,EACrB,OAAO,GAAE,kBAAuB;wBASP,IAAI;sBAKN,OAAO;qBAaF,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;4BARrB,MAAM;WAqDrB,CAAC,WACJ,CAAC,IAAI,EAAE,CAAC,KAAK,OAAO,CAAC,YAAY,CAAC,aAChC,MAAM,KAChB,CAAC,IAAI,EAAE,CAAC,KAAK,OAAO,CAAC,YAAY,CAAC;EAsDtC"}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auto-session inject: ensures session context is loaded on the first tool call,
|
|
3
|
+
* even if the agent doesn't explicitly call `session_start`.
|
|
4
|
+
*
|
|
5
|
+
* Also tracks tool call count and appends checkpoint reminders at configurable
|
|
6
|
+
* thresholds, coaching agents toward better memory habits.
|
|
7
|
+
*
|
|
8
|
+
* Usage in index.ts:
|
|
9
|
+
* const auto = createAutoSession(() => runSessionStart());
|
|
10
|
+
* server.registerTool("some_tool", opts, auto.wrap(handler));
|
|
11
|
+
* // For session_start itself: call auto.markInjected() inside its handler
|
|
12
|
+
*/
|
|
13
|
+
const DEFAULT_REMINDER_THRESHOLDS = [10, 25, 50];
|
|
14
|
+
export function createAutoSession(runSessionStart, log = () => { }, options = {}) {
|
|
15
|
+
let injected = false;
|
|
16
|
+
let toolCallCount = 0;
|
|
17
|
+
const thresholds = options.reminderThresholds ?? DEFAULT_REMINDER_THRESHOLDS;
|
|
18
|
+
const firedThresholds = new Set();
|
|
19
|
+
const onToolCall = options.onToolCall;
|
|
20
|
+
/** Mark session context as already loaded (e.g. when session_start is called explicitly). */
|
|
21
|
+
function markInjected() {
|
|
22
|
+
injected = true;
|
|
23
|
+
}
|
|
24
|
+
/** Check whether session context has been loaded (either auto or explicit). */
|
|
25
|
+
function isInjected() {
|
|
26
|
+
return injected;
|
|
27
|
+
}
|
|
28
|
+
/** Get the current tool call count (for testing/monitoring). */
|
|
29
|
+
function getToolCallCount() {
|
|
30
|
+
return toolCallCount;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Attempt to load session context. Returns the context string on first call,
|
|
34
|
+
* null on subsequent calls. Errors are swallowed — the original tool must still work.
|
|
35
|
+
*/
|
|
36
|
+
async function getPrefix() {
|
|
37
|
+
if (injected)
|
|
38
|
+
return null;
|
|
39
|
+
injected = true;
|
|
40
|
+
try {
|
|
41
|
+
log("[exovault-mcp] Auto-injecting session context (agent did not call session_start)");
|
|
42
|
+
const result = await runSessionStart();
|
|
43
|
+
log("[exovault-mcp] Auto-session context loaded successfully");
|
|
44
|
+
return result;
|
|
45
|
+
}
|
|
46
|
+
catch (e) {
|
|
47
|
+
log(`[exovault-mcp] Auto-session context failed: ${e.message}`);
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Build a checkpoint reminder suffix if the current tool call count
|
|
53
|
+
* just crossed a threshold. Returns null if no reminder is due.
|
|
54
|
+
*/
|
|
55
|
+
function getCheckpointReminder() {
|
|
56
|
+
for (const threshold of thresholds) {
|
|
57
|
+
if (toolCallCount >= threshold && !firedThresholds.has(threshold)) {
|
|
58
|
+
firedThresholds.add(threshold);
|
|
59
|
+
return (`\n\n---\n**ExoVault Reminder** (${toolCallCount} tool calls this session): ` +
|
|
60
|
+
`Have you saved what you've learned? Check these triggers:\n` +
|
|
61
|
+
`- Preference/rule stated by user? → \`write_memory\`(preference/constraint)\n` +
|
|
62
|
+
`- Non-obvious fact discovered? → \`write_memory\`(fact)\n` +
|
|
63
|
+
`- Decision made? → \`write_memory\`(fact, importance 4-5)\n` +
|
|
64
|
+
`- Problem solved or procedure learned? → \`write_memory\`(skill)\n` +
|
|
65
|
+
`- Previous knowledge wrong? → \`write_memory\`(correction + supersededById)\n` +
|
|
66
|
+
`If the session is winding down, call \`context_checkpoint\` to preserve session context.`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Wrap a tool handler so that the first successful tool call in this connection
|
|
73
|
+
* gets session context prepended to its response. Also tracks tool call count
|
|
74
|
+
* and appends checkpoint reminders at thresholds.
|
|
75
|
+
*
|
|
76
|
+
* @param handler - The original tool handler function.
|
|
77
|
+
* @param toolName - Optional tool name for session tracking (passed to onToolCall).
|
|
78
|
+
*/
|
|
79
|
+
function wrap(handler, toolName) {
|
|
80
|
+
return async (args) => {
|
|
81
|
+
const prefix = await getPrefix();
|
|
82
|
+
const result = await handler(args);
|
|
83
|
+
toolCallCount++;
|
|
84
|
+
// Fire session tracking callback (fire-and-forget)
|
|
85
|
+
if (onToolCall && toolName) {
|
|
86
|
+
try {
|
|
87
|
+
onToolCall(toolName);
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
// Never fail the tool call because of tracking
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
// Only modify successful responses
|
|
94
|
+
if (result.isError)
|
|
95
|
+
return result;
|
|
96
|
+
// Auto-inject takes priority — don't also append a reminder on the same call
|
|
97
|
+
if (prefix) {
|
|
98
|
+
return {
|
|
99
|
+
content: [
|
|
100
|
+
{
|
|
101
|
+
type: "text",
|
|
102
|
+
text: `## Session Context (auto-loaded)\n\n` +
|
|
103
|
+
`The following context was automatically loaded because \`session_start\` was not called explicitly. ` +
|
|
104
|
+
`You should still follow the instructions and documents below.\n\n` +
|
|
105
|
+
`${prefix}\n\n` +
|
|
106
|
+
`---\n\n` +
|
|
107
|
+
`## Tool Result\n\n${result.content[0].text}`,
|
|
108
|
+
},
|
|
109
|
+
],
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
// Check for checkpoint reminder
|
|
113
|
+
const reminder = getCheckpointReminder();
|
|
114
|
+
if (reminder) {
|
|
115
|
+
return {
|
|
116
|
+
content: [
|
|
117
|
+
{
|
|
118
|
+
type: "text",
|
|
119
|
+
text: result.content[0].text + reminder,
|
|
120
|
+
},
|
|
121
|
+
],
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
return result;
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
return { markInjected, isInjected, getPrefix, getToolCallCount, wrap };
|
|
128
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auto-session.js","sourceRoot":"","sources":["../src/auto-session.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Buffer disk persistence — crash recovery for session buffers.
|
|
3
|
+
*
|
|
4
|
+
* Writes session buffer data to disk periodically so that if the MCP process
|
|
5
|
+
* crashes or is killed without a clean flush, the orphaned buffer can be
|
|
6
|
+
* recovered on the next startup.
|
|
7
|
+
*
|
|
8
|
+
* Files: ~/.exovault-mcp/sessions/session-{agentRunId}.json
|
|
9
|
+
*/
|
|
10
|
+
import type { SessionBufferData } from "./session-buffer.js";
|
|
11
|
+
/**
|
|
12
|
+
* Write session buffer data to disk for crash recovery.
|
|
13
|
+
* Creates the sessions directory if it doesn't exist.
|
|
14
|
+
*/
|
|
15
|
+
export declare function writeBufferToDisk(data: SessionBufferData): Promise<void>;
|
|
16
|
+
/**
|
|
17
|
+
* Read a session buffer from disk by agentRunId.
|
|
18
|
+
* Returns null if file doesn't exist, is corrupt, or is invalid.
|
|
19
|
+
*/
|
|
20
|
+
export declare function readBufferFromDisk(agentRunId: string): Promise<SessionBufferData | null>;
|
|
21
|
+
/**
|
|
22
|
+
* Delete a session buffer file after successful flush.
|
|
23
|
+
* Silently ignores ENOENT (file already gone).
|
|
24
|
+
*/
|
|
25
|
+
export declare function deleteBuffer(agentRunId: string): Promise<void>;
|
|
26
|
+
/**
|
|
27
|
+
* Scan for orphaned session buffers that are stale and unflushed.
|
|
28
|
+
*
|
|
29
|
+
* Returns buffers where:
|
|
30
|
+
* - lastActivityAt is older than staleMinutes
|
|
31
|
+
* - lastFlushedAt is null (never flushed)
|
|
32
|
+
*
|
|
33
|
+
* Skips invalid files silently.
|
|
34
|
+
*/
|
|
35
|
+
export declare function scanOrphanedBuffers(staleMinutes?: number): Promise<SessionBufferData[]>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"buffer-persistence.d.ts","sourceRoot":"","sources":["../src/buffer-persistence.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAKH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAW7D;;;GAGG;AACH,wBAAsB,iBAAiB,CAAC,IAAI,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CAO9E;AAED;;;GAGG;AACH,wBAAsB,kBAAkB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,GAAG,IAAI,CAAC,CAY9F;AAED;;;GAGG;AACH,wBAAsB,YAAY,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CASpE;AAED;;;;;;;;GAQG;AACH,wBAAsB,mBAAmB,CACvC,YAAY,GAAE,MAA8B,GAC3C,OAAO,CAAC,iBAAiB,EAAE,CAAC,CAgD9B"}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Buffer disk persistence — crash recovery for session buffers.
|
|
3
|
+
*
|
|
4
|
+
* Writes session buffer data to disk periodically so that if the MCP process
|
|
5
|
+
* crashes or is killed without a clean flush, the orphaned buffer can be
|
|
6
|
+
* recovered on the next startup.
|
|
7
|
+
*
|
|
8
|
+
* Files: ~/.exovault-mcp/sessions/session-{agentRunId}.json
|
|
9
|
+
*/
|
|
10
|
+
import { readFile, writeFile, mkdir, unlink, readdir } from "node:fs/promises";
|
|
11
|
+
import { join } from "node:path";
|
|
12
|
+
import { homedir } from "node:os";
|
|
13
|
+
const CONFIG_DIR = join(homedir(), ".exovault-mcp");
|
|
14
|
+
const SESSIONS_DIR = join(CONFIG_DIR, "sessions");
|
|
15
|
+
const DEFAULT_STALE_MINUTES = 10;
|
|
16
|
+
function bufferPath(agentRunId) {
|
|
17
|
+
return join(SESSIONS_DIR, `session-${agentRunId}.json`);
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Write session buffer data to disk for crash recovery.
|
|
21
|
+
* Creates the sessions directory if it doesn't exist.
|
|
22
|
+
*/
|
|
23
|
+
export async function writeBufferToDisk(data) {
|
|
24
|
+
await mkdir(SESSIONS_DIR, { recursive: true, mode: 0o700 });
|
|
25
|
+
await writeFile(bufferPath(data.agentRunId), JSON.stringify(data, null, 2), { encoding: "utf-8", mode: 0o600 });
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Read a session buffer from disk by agentRunId.
|
|
29
|
+
* Returns null if file doesn't exist, is corrupt, or is invalid.
|
|
30
|
+
*/
|
|
31
|
+
export async function readBufferFromDisk(agentRunId) {
|
|
32
|
+
try {
|
|
33
|
+
const raw = await readFile(bufferPath(agentRunId), "utf-8");
|
|
34
|
+
const parsed = JSON.parse(raw);
|
|
35
|
+
// Basic validation — must have agentRunId
|
|
36
|
+
if (!parsed || typeof parsed.agentRunId !== "string") {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
return parsed;
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Delete a session buffer file after successful flush.
|
|
47
|
+
* Silently ignores ENOENT (file already gone).
|
|
48
|
+
*/
|
|
49
|
+
export async function deleteBuffer(agentRunId) {
|
|
50
|
+
try {
|
|
51
|
+
await unlink(bufferPath(agentRunId));
|
|
52
|
+
}
|
|
53
|
+
catch (e) {
|
|
54
|
+
if (e && typeof e === "object" && "code" in e && e.code === "ENOENT") {
|
|
55
|
+
return; // Already deleted — fine
|
|
56
|
+
}
|
|
57
|
+
throw e;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Scan for orphaned session buffers that are stale and unflushed.
|
|
62
|
+
*
|
|
63
|
+
* Returns buffers where:
|
|
64
|
+
* - lastActivityAt is older than staleMinutes
|
|
65
|
+
* - lastFlushedAt is null (never flushed)
|
|
66
|
+
*
|
|
67
|
+
* Skips invalid files silently.
|
|
68
|
+
*/
|
|
69
|
+
export async function scanOrphanedBuffers(staleMinutes = DEFAULT_STALE_MINUTES) {
|
|
70
|
+
// Ensure directory exists
|
|
71
|
+
await mkdir(SESSIONS_DIR, { recursive: true, mode: 0o700 });
|
|
72
|
+
let files;
|
|
73
|
+
try {
|
|
74
|
+
files = (await readdir(SESSIONS_DIR));
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
return [];
|
|
78
|
+
}
|
|
79
|
+
const staleThreshold = Date.now() - staleMinutes * 60_000;
|
|
80
|
+
const orphans = [];
|
|
81
|
+
for (const file of files) {
|
|
82
|
+
// Only process session-*.json files
|
|
83
|
+
if (!file.startsWith("session-") || !file.endsWith(".json")) {
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
try {
|
|
87
|
+
const raw = await readFile(join(SESSIONS_DIR, file), "utf-8");
|
|
88
|
+
const parsed = JSON.parse(raw);
|
|
89
|
+
// Validate basic shape
|
|
90
|
+
if (!parsed || typeof parsed.agentRunId !== "string") {
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
const data = parsed;
|
|
94
|
+
// Skip already flushed
|
|
95
|
+
if (data.lastFlushedAt) {
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
// Check staleness
|
|
99
|
+
const lastActivity = new Date(data.lastActivityAt).getTime();
|
|
100
|
+
if (lastActivity < staleThreshold) {
|
|
101
|
+
orphans.push(data);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
// Invalid file — skip silently
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return orphans;
|
|
110
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"buffer-persistence.js","sourceRoot":"","sources":["../src/buffer-persistence.ts"],"names":[],"mappings":"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"}
|