@andespindola/brainlink 0.1.0-beta.98 → 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/AGENTS.md +6 -6
- package/CHANGELOG.md +14 -0
- package/README.md +186 -38
- package/dist/application/add-note.js +13 -44
- package/dist/application/analyze-vault.js +1 -1
- package/dist/application/auto-migrate-configured-vault.js +37 -0
- package/dist/application/build-context.js +119 -20
- package/dist/application/canonical-context-links.js +209 -0
- package/dist/application/frontend/client-css.js +212 -42
- package/dist/application/frontend/client-html.js +42 -28
- package/dist/application/frontend/client-js.js +1294 -3217
- package/dist/application/frontend/client-render-worker-js.js +676 -0
- package/dist/application/get-graph-contexts.js +33 -0
- package/dist/application/get-graph-layout.js +62 -8
- package/dist/application/get-graph-stream-chunk.js +326 -0
- package/dist/application/get-graph-view.js +246 -0
- package/dist/application/graph-view-state.js +66 -0
- package/dist/application/import-legacy-sqlite.js +3 -33
- package/dist/application/index-vault.js +35 -22
- package/dist/application/migrate-context-links.js +79 -0
- package/dist/application/search-graph-node-ids.js +63 -3
- package/dist/application/server/routes.js +197 -12
- package/dist/cli/commands/read-commands.js +39 -3
- package/dist/cli/commands/vault-commands.js +182 -0
- package/dist/cli/commands/write-commands.js +147 -12
- package/dist/cli/main.js +2 -0
- package/dist/cli/runtime.js +10 -2
- package/dist/domain/context.js +1 -0
- package/dist/domain/graph-contexts.js +180 -0
- package/dist/domain/graph-layout.js +347 -21
- package/dist/domain/markdown.js +53 -9
- package/dist/infrastructure/config.js +105 -6
- package/dist/infrastructure/context-packs.js +122 -0
- package/dist/infrastructure/file-index.js +6 -3
- package/dist/infrastructure/index-state.js +2 -0
- package/dist/infrastructure/vault-migration-state.js +69 -0
- package/dist/infrastructure/volatile-memory.js +100 -0
- package/dist/mcp/http-server.js +97 -0
- package/dist/mcp/runtime.js +20 -0
- package/dist/mcp/server.js +36 -13
- package/dist/mcp/tools.js +203 -14
- package/docs/AGENT_USAGE.md +50 -5
- package/docs/ARCHITECTURE.md +11 -0
- package/docs/QUICKSTART.md +3 -1
- package/docs/RELEASE.md +4 -3
- package/package.json +3 -1
package/dist/mcp/server.js
CHANGED
|
@@ -1,18 +1,11 @@
|
|
|
1
1
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { fileURLToPath } from 'node:url';
|
|
5
|
-
import { addNoteInputSchema, addFileInputSchema, addFileTool, addNoteTool, dedupeInputSchema, dedupeResolveInputSchema, dedupeResolveTool, dedupeTool, brokenLinksInputSchema, brokenLinksTool, bootstrapInputSchema, bootstrapTool, contextInputSchema, contextTool, graphInputSchema, graphTool, indexInputSchema, indexTool, orphansInputSchema, orphansTool, policyInputSchema, policyTool, recommendationsInputSchema, recommendationsTool, searchInputSchema, searchTool, statsInputSchema, statsTool, syncInputSchema, syncTool, validateInputSchema, validateTool } from './tools.js';
|
|
6
|
-
const readPackageVersion = () => {
|
|
7
|
-
const packagePath = join(dirname(fileURLToPath(import.meta.url)), '../../package.json');
|
|
8
|
-
const metadata = JSON.parse(readFileSync(packagePath, 'utf8'));
|
|
9
|
-
return metadata.version ?? '0.0.0';
|
|
10
|
-
};
|
|
2
|
+
import { addNoteInputSchema, addFileInputSchema, addFileTool, addNoteTool, volatileAddInputSchema, volatileAddTool, volatileClearInputSchema, volatileClearTool, dedupeInputSchema, dedupeResolveInputSchema, dedupeResolveTool, dedupeTool, brokenLinksInputSchema, brokenLinksTool, bootstrapInputSchema, bootstrapTool, canonicalizeContextLinksInputSchema, canonicalizeContextLinksTool, contextInputSchema, contextPacksInputSchema, contextPacksTool, contextTool, graphContextsInputSchema, graphContextsTool, graphInputSchema, graphTool, indexInputSchema, indexTool, orphansInputSchema, orphansTool, policyInputSchema, policyTool, recommendationsInputSchema, recommendationsTool, searchInputSchema, searchTool, statsInputSchema, statsTool, syncInputSchema, syncTool, validateInputSchema, validateTool, versionInputSchema, versionTool } from './tools.js';
|
|
3
|
+
import { getRuntimeVersion } from './runtime.js';
|
|
11
4
|
export const createBrainlinkMcpServer = () => {
|
|
12
5
|
const server = new McpServer({
|
|
13
6
|
name: 'brainlink',
|
|
14
7
|
title: 'Brainlink',
|
|
15
|
-
version:
|
|
8
|
+
version: getRuntimeVersion(),
|
|
16
9
|
description: 'Local-first Markdown memory tools for AI agents.'
|
|
17
10
|
});
|
|
18
11
|
server.registerTool('brainlink_bootstrap', {
|
|
@@ -25,16 +18,26 @@ export const createBrainlinkMcpServer = () => {
|
|
|
25
18
|
description: 'Read or update bootstrap enforcement policy and inspect bootstrap readiness for the current vault/agent.',
|
|
26
19
|
inputSchema: policyInputSchema
|
|
27
20
|
}, policyTool);
|
|
21
|
+
server.registerTool('brainlink_version', {
|
|
22
|
+
title: 'Read Brainlink Runtime Version',
|
|
23
|
+
description: 'Return the current Brainlink MCP runtime package version and metadata.',
|
|
24
|
+
inputSchema: versionInputSchema
|
|
25
|
+
}, versionTool);
|
|
28
26
|
server.registerTool('brainlink_recommendations', {
|
|
29
27
|
title: 'Brainlink Recommended MCP Workflow',
|
|
30
|
-
description: 'Return a plug-and-play action plan for this vault/agent, including policy, bootstrap, context retrieval and durable write guidance.',
|
|
28
|
+
description: 'Return a plug-and-play action plan for this vault/agent, including policy, bootstrap, RAG/CAG context strategy, retrieval and durable write guidance.',
|
|
31
29
|
inputSchema: recommendationsInputSchema
|
|
32
30
|
}, recommendationsTool);
|
|
33
31
|
server.registerTool('brainlink_context', {
|
|
34
32
|
title: 'Build Brainlink Context',
|
|
35
|
-
description: 'Read indexed Brainlink memory for a task or question. Usually called after brainlink_bootstrap. This is read-only and does not create graph links.',
|
|
33
|
+
description: 'Read indexed Brainlink memory for a task or question. Agents can choose strategy per call: rag for fresh retrieval assembly, cag for persisted context packs, or auto for pack-hit CAG with RAG fallback. Usually called after brainlink_bootstrap. This is read-only and does not create graph links.',
|
|
36
34
|
inputSchema: contextInputSchema
|
|
37
35
|
}, contextTool);
|
|
36
|
+
server.registerTool('brainlink_context_packs', {
|
|
37
|
+
title: 'Manage Brainlink Context Packs',
|
|
38
|
+
description: 'List or clear persisted CAG context packs. Packs are derived artifacts and can be rebuilt from Markdown/index state.',
|
|
39
|
+
inputSchema: contextPacksInputSchema
|
|
40
|
+
}, contextPacksTool);
|
|
38
41
|
server.registerTool('brainlink_search', {
|
|
39
42
|
title: 'Search Brainlink Memory',
|
|
40
43
|
description: 'Search indexed Brainlink notes with FTS, semantic or hybrid retrieval.',
|
|
@@ -55,14 +58,29 @@ export const createBrainlinkMcpServer = () => {
|
|
|
55
58
|
description: 'Write durable Markdown memory, then reindex the vault. Include explicit [[wiki links]] for connected graph memory. Add priority markers near links, such as priority: high, #important or #critical, when a relationship should be weighted higher.',
|
|
56
59
|
inputSchema: addNoteInputSchema
|
|
57
60
|
}, addNoteTool);
|
|
61
|
+
server.registerTool('brainlink_volatile_add', {
|
|
62
|
+
title: 'Add Volatile Brainlink Memory',
|
|
63
|
+
description: 'Write temporary agent-decided memory with TTL. Use for transient task state without polluting durable Markdown memory.',
|
|
64
|
+
inputSchema: volatileAddInputSchema
|
|
65
|
+
}, volatileAddTool);
|
|
66
|
+
server.registerTool('brainlink_volatile_clear', {
|
|
67
|
+
title: 'Clear Volatile Brainlink Memory',
|
|
68
|
+
description: 'Clear active volatile memory for the current vault/agent namespace.',
|
|
69
|
+
inputSchema: volatileClearInputSchema
|
|
70
|
+
}, volatileClearTool);
|
|
58
71
|
server.registerTool('brainlink_add_file', {
|
|
59
72
|
title: 'Ingest Markdown File',
|
|
60
73
|
description: 'Read a local markdown/text file and ingest it as a Brainlink note. Reindex defaults to true.',
|
|
61
74
|
inputSchema: addFileInputSchema
|
|
62
75
|
}, addFileTool);
|
|
76
|
+
server.registerTool('brainlink_canonicalize_context_links', {
|
|
77
|
+
title: 'Canonicalize Brainlink Context Links',
|
|
78
|
+
description: 'Ensure notes have canonical Context Links to inferred context hubs. Supports dry-run and can create missing hub notes.',
|
|
79
|
+
inputSchema: canonicalizeContextLinksInputSchema
|
|
80
|
+
}, canonicalizeContextLinksTool);
|
|
63
81
|
server.registerTool('brainlink_index', {
|
|
64
82
|
title: 'Index Brainlink Vault',
|
|
65
|
-
description: 'Rebuild the local Brainlink index from Markdown notes.',
|
|
83
|
+
description: 'Rebuild the local Brainlink index from Markdown notes. Pass full=true to force a complete source rebuild.',
|
|
66
84
|
inputSchema: indexInputSchema
|
|
67
85
|
}, indexTool);
|
|
68
86
|
server.registerTool('brainlink_stats', {
|
|
@@ -85,6 +103,11 @@ export const createBrainlinkMcpServer = () => {
|
|
|
85
103
|
description: 'Read indexed graph nodes and wiki-link edges. Edges include weight and priority fields so agents can rank importance and priority.',
|
|
86
104
|
inputSchema: graphInputSchema
|
|
87
105
|
}, graphTool);
|
|
106
|
+
server.registerTool('brainlink_graph_contexts', {
|
|
107
|
+
title: 'List Brainlink Graph Contexts',
|
|
108
|
+
description: 'List visual graph contexts used by the Brainlink server to separate memory domains such as preferences, repositories and machine configuration.',
|
|
109
|
+
inputSchema: graphContextsInputSchema
|
|
110
|
+
}, graphContextsTool);
|
|
88
111
|
server.registerTool('brainlink_broken_links', {
|
|
89
112
|
title: 'List Brainlink Broken Links',
|
|
90
113
|
description: 'List unresolved indexed wiki links.',
|
package/dist/mcp/tools.js
CHANGED
|
@@ -3,15 +3,20 @@ import { basename, extname } from 'node:path';
|
|
|
3
3
|
import { z } from 'zod';
|
|
4
4
|
import { getBrokenLinksReport, getOrphansReport, getStats, validateVault } from '../application/analyze-vault.js';
|
|
5
5
|
import { addNoteWithMetadata } from '../application/add-note.js';
|
|
6
|
-
import { buildContextPackage } from '../application/build-context.js';
|
|
6
|
+
import { buildContextPackage, readContextDataSignature } from '../application/build-context.js';
|
|
7
|
+
import { canonicalizeContextLinks } from '../application/canonical-context-links.js';
|
|
7
8
|
import { resolveDuplicateNotes, scanDuplicateNotes } from '../application/dedupe-notes.js';
|
|
8
9
|
import { getGraph } from '../application/get-graph.js';
|
|
10
|
+
import { getGraphContexts } from '../application/get-graph-contexts.js';
|
|
9
11
|
import { indexVault } from '../application/index-vault.js';
|
|
10
12
|
import { searchKnowledge } from '../application/search-knowledge.js';
|
|
11
|
-
import { resolveAgentRuntimeDefaults, sanitizeSearchMode } from '../infrastructure/config.js';
|
|
13
|
+
import { resolveAgentRuntimeDefaults, sanitizeContextStrategy, sanitizeSearchMode } from '../infrastructure/config.js';
|
|
14
|
+
import { clearContextPacks, listContextPacks } from '../infrastructure/context-packs.js';
|
|
12
15
|
import { loadBrainlinkConfig } from '../infrastructure/config.js';
|
|
13
16
|
import { assertVaultAllowed } from '../infrastructure/file-system-vault.js';
|
|
17
|
+
import { addVolatileMemory, clearVolatileMemory } from '../infrastructure/volatile-memory.js';
|
|
14
18
|
import { getBootstrapPolicy, getBootstrapSessionStatus, getContextSessionStatus, setBootstrapPolicy, touchBootstrapSession, touchContextSession } from '../infrastructure/session-state.js';
|
|
19
|
+
import { getRuntimeMetadata } from './runtime.js';
|
|
15
20
|
const positiveInteger = (fallback) => z
|
|
16
21
|
.number()
|
|
17
22
|
.int()
|
|
@@ -36,6 +41,12 @@ const agentInput = {
|
|
|
36
41
|
const searchModeInput = {
|
|
37
42
|
mode: z.enum(['fts', 'semantic', 'hybrid']).optional().describe('Search mode. Defaults to the Brainlink config value.')
|
|
38
43
|
};
|
|
44
|
+
const contextStrategyInput = {
|
|
45
|
+
strategy: z
|
|
46
|
+
.enum(['rag', 'cag', 'auto'])
|
|
47
|
+
.optional()
|
|
48
|
+
.describe('Context strategy per call. Use rag for fresh retrieval assembly, cag to reuse persisted context packs when fresh, or auto to choose CAG on fresh pack hits and RAG otherwise. Defaults to the Brainlink config value.')
|
|
49
|
+
};
|
|
39
50
|
const resolveExecutionContext = async (input) => {
|
|
40
51
|
const config = await loadBrainlinkConfig();
|
|
41
52
|
const vault = await assertVaultAllowed(input.vault ?? config.vault, config.allowedVaults);
|
|
@@ -118,12 +129,14 @@ const ensureBootstrapReady = async (context, input, toolName) => {
|
|
|
118
129
|
};
|
|
119
130
|
}
|
|
120
131
|
const mode = typeof input.mode === 'string' && ['fts', 'semantic', 'hybrid'].includes(input.mode) ? input.mode : 'hybrid';
|
|
132
|
+
const strategy = sanitizeContextStrategy(typeof input.strategy === 'string' ? input.strategy : undefined, 'rag');
|
|
121
133
|
const query = typeof input.query === 'string' && input.query.trim().length > 0 ? input.query : undefined;
|
|
122
134
|
const bootstrapArgs = {
|
|
123
135
|
vault: context.vault,
|
|
124
136
|
...(context.agent ? { agent: context.agent } : {}),
|
|
125
137
|
...(query ? { query } : {}),
|
|
126
|
-
mode
|
|
138
|
+
mode,
|
|
139
|
+
strategy
|
|
127
140
|
};
|
|
128
141
|
const nextActions = [
|
|
129
142
|
{
|
|
@@ -174,6 +187,7 @@ const ensureContextReady = async (context, input, toolName) => {
|
|
|
174
187
|
? input.contextQuery
|
|
175
188
|
: '<task>';
|
|
176
189
|
const mode = sanitizeSearchMode(typeof input.mode === 'string' ? input.mode : undefined, context.defaults.defaultSearchMode);
|
|
190
|
+
const strategy = sanitizeContextStrategy(typeof input.strategy === 'string' ? input.strategy : undefined, context.defaults.defaultContextStrategy);
|
|
177
191
|
const limit = typeof input.limit === 'number' && Number.isFinite(input.limit) && input.limit > 0
|
|
178
192
|
? input.limit
|
|
179
193
|
: typeof input.contextLimit === 'number' && Number.isFinite(input.contextLimit) && input.contextLimit > 0
|
|
@@ -189,6 +203,7 @@ const ensureContextReady = async (context, input, toolName) => {
|
|
|
189
203
|
...(context.agent ? { agent: context.agent } : {}),
|
|
190
204
|
query: queryFromInput,
|
|
191
205
|
mode,
|
|
206
|
+
strategy,
|
|
192
207
|
limit,
|
|
193
208
|
tokens
|
|
194
209
|
};
|
|
@@ -215,10 +230,17 @@ export const contextInputSchema = {
|
|
|
215
230
|
...vaultInput,
|
|
216
231
|
...agentInput,
|
|
217
232
|
...searchModeInput,
|
|
233
|
+
...contextStrategyInput,
|
|
218
234
|
query: z.string().min(1).describe('Task or question to retrieve Brainlink context for.'),
|
|
219
235
|
limit: optionalPositiveInteger().describe('Maximum search results before context selection.'),
|
|
220
236
|
tokens: optionalPositiveInteger().describe('Maximum estimated context tokens.')
|
|
221
237
|
};
|
|
238
|
+
export const contextPacksInputSchema = {
|
|
239
|
+
...vaultInput,
|
|
240
|
+
...agentInput,
|
|
241
|
+
action: z.enum(['list', 'clear']).optional().default('list').describe('Action to perform on persisted CAG context packs.'),
|
|
242
|
+
staleOnly: z.boolean().optional().default(false).describe('When clearing, remove only packs stale for the current index and volatile-memory signature.')
|
|
243
|
+
};
|
|
222
244
|
export const searchInputSchema = {
|
|
223
245
|
...vaultInput,
|
|
224
246
|
...agentInput,
|
|
@@ -235,7 +257,25 @@ export const addNoteInputSchema = {
|
|
|
235
257
|
.describe('Durable Markdown memory. Include explicit [[wiki links]] and #tags when the memory should be connected. Put priority markers near important links, for example priority: high, #important or #critical.'),
|
|
236
258
|
...agentInput,
|
|
237
259
|
allowSensitive: z.boolean().optional().default(false).describe('Allow content that looks like a secret.'),
|
|
238
|
-
autoIndex: z.boolean().optional().default(true).describe('Reindex vault after writing note.')
|
|
260
|
+
autoIndex: z.boolean().optional().default(true).describe('Reindex vault after writing note.'),
|
|
261
|
+
autoContextLinks: z
|
|
262
|
+
.boolean()
|
|
263
|
+
.optional()
|
|
264
|
+
.describe('Automatically add canonical Context Links to the inferred visual context hub. Defaults to Brainlink config.')
|
|
265
|
+
};
|
|
266
|
+
export const volatileAddInputSchema = {
|
|
267
|
+
...vaultInput,
|
|
268
|
+
...agentInput,
|
|
269
|
+
content: z
|
|
270
|
+
.string()
|
|
271
|
+
.min(1)
|
|
272
|
+
.describe('Temporary agent-decided memory. Use for current task state, hypotheses, transient user preferences and unconfirmed findings.'),
|
|
273
|
+
ttlMinutes: optionalPositiveInteger().describe('Minutes before this volatile memory expires. Defaults to 240.'),
|
|
274
|
+
tags: z.array(z.string()).optional().default([]).describe('Optional tags for volatile retrieval.')
|
|
275
|
+
};
|
|
276
|
+
export const volatileClearInputSchema = {
|
|
277
|
+
...vaultInput,
|
|
278
|
+
...agentInput
|
|
239
279
|
};
|
|
240
280
|
export const addFileInputSchema = {
|
|
241
281
|
...vaultInput,
|
|
@@ -245,8 +285,20 @@ export const addFileInputSchema = {
|
|
|
245
285
|
autoIndex: z.boolean().optional().default(true).describe('Reindex vault after ingesting file.'),
|
|
246
286
|
allowSensitive: z.boolean().optional().default(false).describe('Allow content that looks like a secret.')
|
|
247
287
|
};
|
|
288
|
+
export const canonicalizeContextLinksInputSchema = {
|
|
289
|
+
...vaultInput,
|
|
290
|
+
...agentInput,
|
|
291
|
+
dryRun: z.boolean().optional().default(false).describe('Preview canonical context-link writes without changing Markdown.'),
|
|
292
|
+
createHubs: z.boolean().optional().default(true).describe('Create missing context hub notes when needed.'),
|
|
293
|
+
autoIndex: z.boolean().optional().default(true).describe('Reindex after canonicalization when files changed.')
|
|
294
|
+
};
|
|
248
295
|
export const indexInputSchema = {
|
|
249
|
-
...vaultInput
|
|
296
|
+
...vaultInput,
|
|
297
|
+
full: z
|
|
298
|
+
.boolean()
|
|
299
|
+
.optional()
|
|
300
|
+
.default(false)
|
|
301
|
+
.describe('Force a complete reindex from Markdown source without reusing unchanged index entries.')
|
|
250
302
|
};
|
|
251
303
|
export const validateInputSchema = {
|
|
252
304
|
...vaultInput,
|
|
@@ -256,6 +308,10 @@ export const graphInputSchema = {
|
|
|
256
308
|
...vaultInput,
|
|
257
309
|
...agentInput
|
|
258
310
|
};
|
|
311
|
+
export const graphContextsInputSchema = {
|
|
312
|
+
...vaultInput,
|
|
313
|
+
...agentInput
|
|
314
|
+
};
|
|
259
315
|
export const brokenLinksInputSchema = {
|
|
260
316
|
...vaultInput,
|
|
261
317
|
...agentInput
|
|
@@ -273,6 +329,7 @@ export const syncInputSchema = {
|
|
|
273
329
|
...agentInput,
|
|
274
330
|
contextQuery: z.string().min(1).optional().describe('Optional context smoke query. Omit to skip context probe.'),
|
|
275
331
|
mode: z.enum(['fts', 'semantic', 'hybrid']).optional().describe('Search mode for the optional context probe. Defaults to config value.'),
|
|
332
|
+
strategy: z.enum(['rag', 'cag', 'auto']).optional().describe('Context strategy for the optional context probe. Defaults to the Brainlink config value.'),
|
|
276
333
|
contextLimit: optionalPositiveInteger().describe('Context smoke result limit when contextQuery is provided.'),
|
|
277
334
|
contextTokens: optionalPositiveInteger().describe('Context smoke token target when contextQuery is provided.')
|
|
278
335
|
};
|
|
@@ -280,6 +337,7 @@ export const bootstrapInputSchema = {
|
|
|
280
337
|
...vaultInput,
|
|
281
338
|
...agentInput,
|
|
282
339
|
...searchModeInput,
|
|
340
|
+
...contextStrategyInput,
|
|
283
341
|
query: z
|
|
284
342
|
.string()
|
|
285
343
|
.min(1)
|
|
@@ -304,10 +362,15 @@ export const policyInputSchema = {
|
|
|
304
362
|
.describe('Run automatic bootstrap during MCP server startup using configured default vault/agent.'),
|
|
305
363
|
staleAfterMinutes: positiveInteger(120).describe('Bootstrap freshness window in minutes before read tools require a new bootstrap.')
|
|
306
364
|
};
|
|
365
|
+
export const versionInputSchema = {
|
|
366
|
+
...vaultInput,
|
|
367
|
+
...agentInput
|
|
368
|
+
};
|
|
307
369
|
export const recommendationsInputSchema = {
|
|
308
370
|
...vaultInput,
|
|
309
371
|
...agentInput,
|
|
310
372
|
...searchModeInput,
|
|
373
|
+
...contextStrategyInput,
|
|
311
374
|
query: z.string().min(1).optional().describe('Optional current task query to generate context-focused recommendations.'),
|
|
312
375
|
limit: optionalPositiveInteger().describe('Optional context limit override for generated recommendations.'),
|
|
313
376
|
tokens: optionalPositiveInteger().describe('Optional context token budget override for generated recommendations.')
|
|
@@ -333,14 +396,16 @@ export const contextTool = async (input) => {
|
|
|
333
396
|
return readiness.preflight;
|
|
334
397
|
}
|
|
335
398
|
const mode = sanitizeSearchMode(input.mode, context.defaults.defaultSearchMode);
|
|
399
|
+
const strategy = sanitizeContextStrategy(input.strategy, context.defaults.defaultContextStrategy);
|
|
336
400
|
const limit = input.limit ?? context.defaults.defaultSearchLimit;
|
|
337
401
|
const tokens = input.tokens ?? context.defaults.defaultContextTokens;
|
|
338
|
-
const contextPackage = await buildContextPackage(context.vault, input.query, limit, tokens, context.agent, mode);
|
|
402
|
+
const contextPackage = await buildContextPackage(context.vault, input.query, limit, tokens, context.agent, mode, strategy, context.defaults.defaultContextCacheTtlMs);
|
|
339
403
|
const contextSession = await touchContextSession(context.vault, context.agent);
|
|
340
404
|
return jsonResult({
|
|
341
405
|
vault: context.vault,
|
|
342
406
|
agent: context.agent,
|
|
343
407
|
mode,
|
|
408
|
+
strategy,
|
|
344
409
|
limit,
|
|
345
410
|
tokens,
|
|
346
411
|
contextSession,
|
|
@@ -348,6 +413,32 @@ export const contextTool = async (input) => {
|
|
|
348
413
|
...contextPackage
|
|
349
414
|
});
|
|
350
415
|
};
|
|
416
|
+
export const contextPacksTool = async (input) => {
|
|
417
|
+
const context = await resolveExecutionContext(input);
|
|
418
|
+
const dataSignature = await readContextDataSignature(context.vault);
|
|
419
|
+
if (input.action === 'clear') {
|
|
420
|
+
const result = await clearContextPacks(context.vault, {
|
|
421
|
+
staleOnly: input.staleOnly === true,
|
|
422
|
+
dataSignature
|
|
423
|
+
});
|
|
424
|
+
return jsonResult({
|
|
425
|
+
vault: context.vault,
|
|
426
|
+
agent: context.agent,
|
|
427
|
+
dataSignature,
|
|
428
|
+
action: 'clear',
|
|
429
|
+
staleOnly: input.staleOnly === true,
|
|
430
|
+
...result
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
const packs = await listContextPacks(context.vault, dataSignature);
|
|
434
|
+
return jsonResult({
|
|
435
|
+
vault: context.vault,
|
|
436
|
+
agent: context.agent,
|
|
437
|
+
dataSignature,
|
|
438
|
+
action: 'list',
|
|
439
|
+
packs
|
|
440
|
+
});
|
|
441
|
+
};
|
|
351
442
|
export const searchTool = async (input) => {
|
|
352
443
|
const context = await resolveExecutionContext(input);
|
|
353
444
|
const readiness = await ensureBootstrapReady(context, input, 'brainlink_search');
|
|
@@ -376,7 +467,8 @@ export const addNoteTool = async (input) => {
|
|
|
376
467
|
const context = await resolveExecutionContext(input);
|
|
377
468
|
const shouldIndex = isTruthy(input.autoIndex);
|
|
378
469
|
const added = await addNoteWithMetadata(context.vault, input.title, input.content, context.agent, {
|
|
379
|
-
allowSensitive: input.allowSensitive
|
|
470
|
+
allowSensitive: input.allowSensitive,
|
|
471
|
+
autoContextLinks: input.autoContextLinks ?? context.config.autoCanonicalContextLinks
|
|
380
472
|
});
|
|
381
473
|
const index = shouldIndex ? await indexVault(context.vault) : undefined;
|
|
382
474
|
const focusPath = added.path.includes('agents/') ? added.path.slice(added.path.indexOf('agents/')).replaceAll('\\', '/') : undefined;
|
|
@@ -395,12 +487,33 @@ export const addNoteTool = async (input) => {
|
|
|
395
487
|
writeConnectivity: {
|
|
396
488
|
autoLinked: added.autoLinked,
|
|
397
489
|
linkTarget: added.linkTarget,
|
|
398
|
-
|
|
490
|
+
context: added.context,
|
|
491
|
+
hubCreated: added.hubCreated,
|
|
492
|
+
guaranteedEdge: added.autoLinked
|
|
399
493
|
},
|
|
400
494
|
possibleDuplicates,
|
|
401
495
|
...(index ? { index } : {})
|
|
402
496
|
});
|
|
403
497
|
};
|
|
498
|
+
export const volatileAddTool = async (input) => {
|
|
499
|
+
const context = await resolveExecutionContext(input);
|
|
500
|
+
const entry = await addVolatileMemory(context.vault, input.content, context.agent ?? 'shared', input.ttlMinutes ?? 240, input.tags);
|
|
501
|
+
return jsonResult({
|
|
502
|
+
vault: context.vault,
|
|
503
|
+
agent: context.agent,
|
|
504
|
+
volatile: true,
|
|
505
|
+
entry
|
|
506
|
+
});
|
|
507
|
+
};
|
|
508
|
+
export const volatileClearTool = async (input) => {
|
|
509
|
+
const context = await resolveExecutionContext(input);
|
|
510
|
+
const cleared = await clearVolatileMemory(context.vault, context.agent);
|
|
511
|
+
return jsonResult({
|
|
512
|
+
vault: context.vault,
|
|
513
|
+
agent: context.agent,
|
|
514
|
+
cleared
|
|
515
|
+
});
|
|
516
|
+
};
|
|
404
517
|
export const addFileTool = async (input) => {
|
|
405
518
|
const context = await resolveExecutionContext(input);
|
|
406
519
|
const content = await readFile(input.filePath, 'utf8');
|
|
@@ -411,7 +524,8 @@ export const addFileTool = async (input) => {
|
|
|
411
524
|
}
|
|
412
525
|
const shouldIndex = isTruthy(input.autoIndex);
|
|
413
526
|
const added = await addNoteWithMetadata(context.vault, title, content, context.agent, {
|
|
414
|
-
allowSensitive: input.allowSensitive
|
|
527
|
+
allowSensitive: input.allowSensitive,
|
|
528
|
+
autoContextLinks: context.config.autoCanonicalContextLinks
|
|
415
529
|
});
|
|
416
530
|
const index = shouldIndex ? await indexVault(context.vault) : undefined;
|
|
417
531
|
return jsonResult({
|
|
@@ -423,14 +537,35 @@ export const addFileTool = async (input) => {
|
|
|
423
537
|
writeConnectivity: {
|
|
424
538
|
autoLinked: added.autoLinked,
|
|
425
539
|
linkTarget: added.linkTarget,
|
|
426
|
-
|
|
540
|
+
context: added.context,
|
|
541
|
+
hubCreated: added.hubCreated,
|
|
542
|
+
guaranteedEdge: added.autoLinked
|
|
427
543
|
},
|
|
428
544
|
...(index ? { index } : {})
|
|
429
545
|
});
|
|
430
546
|
};
|
|
547
|
+
export const canonicalizeContextLinksTool = async (input) => {
|
|
548
|
+
const context = await resolveExecutionContext(input);
|
|
549
|
+
const result = await canonicalizeContextLinks(context.vault, {
|
|
550
|
+
agentId: context.agent,
|
|
551
|
+
dryRun: input.dryRun === true,
|
|
552
|
+
createMissingHubs: input.createHubs !== false
|
|
553
|
+
});
|
|
554
|
+
const index = input.autoIndex !== false && !result.dryRun && result.changed > 0
|
|
555
|
+
? await indexVault(context.vault, { full: true })
|
|
556
|
+
: undefined;
|
|
557
|
+
return jsonResult({
|
|
558
|
+
vault: context.vault,
|
|
559
|
+
agent: context.agent,
|
|
560
|
+
...result,
|
|
561
|
+
...(index ? { index } : {})
|
|
562
|
+
});
|
|
563
|
+
};
|
|
431
564
|
export const indexTool = async (input) => {
|
|
432
565
|
const context = await resolveExecutionContext(input);
|
|
433
|
-
const result = await indexVault(context.vault
|
|
566
|
+
const result = await indexVault(context.vault, {
|
|
567
|
+
full: input.full === true
|
|
568
|
+
});
|
|
434
569
|
return jsonResult({
|
|
435
570
|
vault: context.vault,
|
|
436
571
|
...result
|
|
@@ -474,6 +609,25 @@ export const graphTool = async (input) => {
|
|
|
474
609
|
...graph
|
|
475
610
|
});
|
|
476
611
|
};
|
|
612
|
+
export const graphContextsTool = async (input) => {
|
|
613
|
+
const context = await resolveExecutionContext(input);
|
|
614
|
+
const readiness = await ensureBootstrapReady(context, input, 'brainlink_graph_contexts');
|
|
615
|
+
if (readiness.preflight) {
|
|
616
|
+
return readiness.preflight;
|
|
617
|
+
}
|
|
618
|
+
const contextReadiness = await ensureContextReady(context, input, 'brainlink_graph_contexts');
|
|
619
|
+
if (contextReadiness.preflight) {
|
|
620
|
+
return contextReadiness.preflight;
|
|
621
|
+
}
|
|
622
|
+
const contexts = await getGraphContexts(context.vault, context.agent);
|
|
623
|
+
return jsonResult({
|
|
624
|
+
vault: context.vault,
|
|
625
|
+
agent: context.agent,
|
|
626
|
+
...(readiness.bootstrap ? { bootstrap: readiness.bootstrap } : {}),
|
|
627
|
+
...(contextReadiness.context ? { contextReadiness: contextReadiness.context } : {}),
|
|
628
|
+
contexts
|
|
629
|
+
});
|
|
630
|
+
};
|
|
477
631
|
export const brokenLinksTool = async (input) => {
|
|
478
632
|
const context = await resolveExecutionContext(input);
|
|
479
633
|
const readiness = await ensureBootstrapReady(context, input, 'brainlink_broken_links');
|
|
@@ -561,14 +715,16 @@ export const syncTool = async (input) => {
|
|
|
561
715
|
return jsonResult(response);
|
|
562
716
|
}
|
|
563
717
|
const mode = sanitizeSearchMode(input.mode, context.defaults.defaultSearchMode);
|
|
718
|
+
const strategy = sanitizeContextStrategy(input.strategy, context.defaults.defaultContextStrategy);
|
|
564
719
|
const contextLimit = input.contextLimit ?? context.defaults.defaultSearchLimit;
|
|
565
720
|
const contextTokens = input.contextTokens ?? context.defaults.defaultContextTokens;
|
|
566
|
-
const contextPackage = await buildContextPackage(context.vault, input.contextQuery, contextLimit, contextTokens, context.agent, mode);
|
|
721
|
+
const contextPackage = await buildContextPackage(context.vault, input.contextQuery, contextLimit, contextTokens, context.agent, mode, strategy, context.defaults.defaultContextCacheTtlMs);
|
|
567
722
|
const contextSession = await touchContextSession(context.vault, context.agent);
|
|
568
723
|
return jsonResult({
|
|
569
724
|
...response,
|
|
570
725
|
context: {
|
|
571
726
|
mode,
|
|
727
|
+
strategy,
|
|
572
728
|
contextSession,
|
|
573
729
|
...contextPackage
|
|
574
730
|
}
|
|
@@ -580,10 +736,11 @@ export const bootstrapTool = async (input) => {
|
|
|
580
736
|
const stats = await getStats(context.vault, context.agent);
|
|
581
737
|
const validation = await validateVault(context.vault, context.agent);
|
|
582
738
|
const mode = sanitizeSearchMode(input.mode, context.defaults.defaultSearchMode);
|
|
739
|
+
const strategy = sanitizeContextStrategy(input.strategy, context.defaults.defaultContextStrategy);
|
|
583
740
|
const limit = input.limit ?? context.defaults.defaultSearchLimit;
|
|
584
741
|
const tokens = input.tokens ?? context.defaults.defaultContextTokens;
|
|
585
742
|
const contextPackage = input.query
|
|
586
|
-
? await buildContextPackage(context.vault, input.query, limit, tokens, context.agent, mode)
|
|
743
|
+
? await buildContextPackage(context.vault, input.query, limit, tokens, context.agent, mode, strategy, context.defaults.defaultContextCacheTtlMs)
|
|
587
744
|
: undefined;
|
|
588
745
|
const contextSession = input.query ? await touchContextSession(context.vault, context.agent) : undefined;
|
|
589
746
|
const guidance = stats.documentCount === 0
|
|
@@ -637,6 +794,7 @@ export const bootstrapTool = async (input) => {
|
|
|
637
794
|
...(context.agent ? { agent: context.agent } : {}),
|
|
638
795
|
query: '<task>',
|
|
639
796
|
mode,
|
|
797
|
+
strategy,
|
|
640
798
|
limit,
|
|
641
799
|
tokens
|
|
642
800
|
}
|
|
@@ -646,6 +804,7 @@ export const bootstrapTool = async (input) => {
|
|
|
646
804
|
vault: context.vault,
|
|
647
805
|
agent: context.agent,
|
|
648
806
|
mode,
|
|
807
|
+
strategy,
|
|
649
808
|
limit,
|
|
650
809
|
tokens,
|
|
651
810
|
index,
|
|
@@ -701,7 +860,8 @@ export const policyTool = async (input) => {
|
|
|
701
860
|
args: {
|
|
702
861
|
vault: context.vault,
|
|
703
862
|
...(context.agent ? { agent: context.agent } : {}),
|
|
704
|
-
mode: context.defaults.defaultSearchMode
|
|
863
|
+
mode: context.defaults.defaultSearchMode,
|
|
864
|
+
strategy: context.defaults.defaultContextStrategy
|
|
705
865
|
}
|
|
706
866
|
}
|
|
707
867
|
];
|
|
@@ -716,6 +876,7 @@ export const policyTool = async (input) => {
|
|
|
716
876
|
...(context.agent ? { agent: context.agent } : {}),
|
|
717
877
|
query: '<task>',
|
|
718
878
|
mode: context.defaults.defaultSearchMode,
|
|
879
|
+
strategy: context.defaults.defaultContextStrategy,
|
|
719
880
|
limit: context.defaults.defaultSearchLimit,
|
|
720
881
|
tokens: context.defaults.defaultContextTokens
|
|
721
882
|
}
|
|
@@ -725,12 +886,21 @@ export const policyTool = async (input) => {
|
|
|
725
886
|
return jsonResult(withNextActions({
|
|
726
887
|
vault: context.vault,
|
|
727
888
|
agent: context.agent,
|
|
889
|
+
runtime: getRuntimeMetadata(),
|
|
728
890
|
policy,
|
|
729
891
|
bootstrapStatus,
|
|
730
892
|
contextStatus,
|
|
731
893
|
...(input.preset ? { presetApplied: input.preset } : {})
|
|
732
894
|
}, withContextAction));
|
|
733
895
|
};
|
|
896
|
+
export const versionTool = async (input) => {
|
|
897
|
+
const context = await resolveExecutionContext(input);
|
|
898
|
+
return jsonResult({
|
|
899
|
+
vault: context.vault,
|
|
900
|
+
agent: context.agent,
|
|
901
|
+
runtime: getRuntimeMetadata()
|
|
902
|
+
});
|
|
903
|
+
};
|
|
734
904
|
export const recommendationsTool = async (input) => {
|
|
735
905
|
const context = await resolveExecutionContext(input);
|
|
736
906
|
const policy = await getBootstrapPolicy();
|
|
@@ -738,6 +908,7 @@ export const recommendationsTool = async (input) => {
|
|
|
738
908
|
const contextStatus = await getContextSessionStatus(context.vault, context.agent);
|
|
739
909
|
const stats = await getStats(context.vault, context.agent);
|
|
740
910
|
const mode = sanitizeSearchMode(input.mode, context.defaults.defaultSearchMode);
|
|
911
|
+
const strategy = sanitizeContextStrategy(input.strategy, context.defaults.defaultContextStrategy);
|
|
741
912
|
const limit = input.limit ?? context.defaults.defaultSearchLimit;
|
|
742
913
|
const tokens = input.tokens ?? context.defaults.defaultContextTokens;
|
|
743
914
|
const query = input.query?.trim();
|
|
@@ -762,6 +933,7 @@ export const recommendationsTool = async (input) => {
|
|
|
762
933
|
vault: context.vault,
|
|
763
934
|
...(context.agent ? { agent: context.agent } : {}),
|
|
764
935
|
mode,
|
|
936
|
+
strategy,
|
|
765
937
|
...(query ? { query } : {})
|
|
766
938
|
}
|
|
767
939
|
}
|
|
@@ -777,6 +949,7 @@ export const recommendationsTool = async (input) => {
|
|
|
777
949
|
...(context.agent ? { agent: context.agent } : {}),
|
|
778
950
|
query: query ?? '<task>',
|
|
779
951
|
mode,
|
|
952
|
+
strategy,
|
|
780
953
|
limit,
|
|
781
954
|
tokens
|
|
782
955
|
}
|
|
@@ -812,6 +985,7 @@ export const recommendationsTool = async (input) => {
|
|
|
812
985
|
...(context.agent ? { agent: context.agent } : {}),
|
|
813
986
|
query: query ?? '<task>',
|
|
814
987
|
mode,
|
|
988
|
+
strategy,
|
|
815
989
|
limit,
|
|
816
990
|
tokens
|
|
817
991
|
}
|
|
@@ -843,9 +1017,24 @@ export const recommendationsTool = async (input) => {
|
|
|
843
1017
|
agent: context.agent,
|
|
844
1018
|
defaults: {
|
|
845
1019
|
mode,
|
|
1020
|
+
strategy,
|
|
846
1021
|
limit,
|
|
847
1022
|
tokens
|
|
848
1023
|
},
|
|
1024
|
+
contextStrategies: [
|
|
1025
|
+
{
|
|
1026
|
+
strategy: 'rag',
|
|
1027
|
+
useWhen: 'Use for fresh retrieval and context assembly from the current index.'
|
|
1028
|
+
},
|
|
1029
|
+
{
|
|
1030
|
+
strategy: 'cag',
|
|
1031
|
+
useWhen: 'Use for repeated or stable task context so Brainlink can reuse a fresh persisted context pack.'
|
|
1032
|
+
},
|
|
1033
|
+
{
|
|
1034
|
+
strategy: 'auto',
|
|
1035
|
+
useWhen: 'Use when the agent wants Brainlink to choose CAG on fresh pack hits and RAG otherwise.'
|
|
1036
|
+
}
|
|
1037
|
+
],
|
|
849
1038
|
policy,
|
|
850
1039
|
bootstrapStatus,
|
|
851
1040
|
contextStatus,
|