@andespindola/brainlink 0.1.0-beta.8 → 0.1.0-beta.9
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/CHANGELOG.md +32 -0
- package/README.md +127 -4
- package/dist/application/add-note.js +62 -13
- package/dist/application/analyze-vault.js +89 -2
- package/dist/application/migrate-vault.js +46 -16
- package/dist/application/search-knowledge.js +56 -1
- package/dist/cli/commands/agent-commands.js +402 -0
- package/dist/cli/commands/config-commands.js +167 -0
- package/dist/cli/commands/read-commands.js +25 -8
- package/dist/cli/commands/write-commands.js +157 -4
- package/dist/cli/main.js +4 -0
- package/dist/cli/runtime.js +5 -2
- package/dist/domain/markdown.js +36 -4
- package/dist/infrastructure/config.js +94 -8
- package/dist/infrastructure/paths.js +9 -1
- package/dist/infrastructure/session-state.js +117 -0
- package/dist/mcp/main.js +11 -3
- package/dist/mcp/server.js +17 -2
- package/dist/mcp/startup.js +35 -0
- package/dist/mcp/tools.js +421 -19
- package/docs/AGENT_USAGE.md +77 -2
- package/docs/ARCHITECTURE.md +13 -1
- package/docs/QUICKSTART.md +103 -0
- package/package.json +1 -1
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { indexVault } from '../application/index-vault.js';
|
|
2
|
+
import { loadBrainlinkConfig } from '../infrastructure/config.js';
|
|
3
|
+
import { assertVaultAllowed } from '../infrastructure/file-system-vault.js';
|
|
4
|
+
import { getBootstrapPolicy, touchBootstrapSession } from '../infrastructure/session-state.js';
|
|
5
|
+
export const runStartupBootstrap = async () => {
|
|
6
|
+
try {
|
|
7
|
+
const policy = await getBootstrapPolicy();
|
|
8
|
+
if (!policy.autoBootstrapOnStartup) {
|
|
9
|
+
return {
|
|
10
|
+
attempted: false,
|
|
11
|
+
skipped: true,
|
|
12
|
+
reason: 'autoBootstrapOnStartup=false'
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
const config = await loadBrainlinkConfig();
|
|
16
|
+
const vault = assertVaultAllowed(config.vault, config.allowedVaults);
|
|
17
|
+
const agent = config.defaultAgent;
|
|
18
|
+
const index = await indexVault(vault);
|
|
19
|
+
await touchBootstrapSession(vault, agent);
|
|
20
|
+
return {
|
|
21
|
+
attempted: true,
|
|
22
|
+
skipped: false,
|
|
23
|
+
vault,
|
|
24
|
+
agent: agent ?? '*',
|
|
25
|
+
index
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
return {
|
|
30
|
+
attempted: true,
|
|
31
|
+
skipped: false,
|
|
32
|
+
error: error instanceof Error ? error.message : String(error)
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
};
|
package/dist/mcp/tools.js
CHANGED
|
@@ -2,20 +2,26 @@ import { readFile } from 'node:fs/promises';
|
|
|
2
2
|
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
|
-
import {
|
|
5
|
+
import { addNoteWithMetadata } from '../application/add-note.js';
|
|
6
6
|
import { buildContextPackage } from '../application/build-context.js';
|
|
7
7
|
import { getGraph } from '../application/get-graph.js';
|
|
8
8
|
import { indexVault } from '../application/index-vault.js';
|
|
9
9
|
import { searchKnowledge } from '../application/search-knowledge.js';
|
|
10
|
-
import { sanitizeSearchMode } from '../infrastructure/config.js';
|
|
10
|
+
import { resolveAgentRuntimeDefaults, sanitizeSearchMode } from '../infrastructure/config.js';
|
|
11
11
|
import { loadBrainlinkConfig } from '../infrastructure/config.js';
|
|
12
12
|
import { assertVaultAllowed } from '../infrastructure/file-system-vault.js';
|
|
13
|
+
import { getBootstrapPolicy, getBootstrapSessionStatus, setBootstrapPolicy, touchBootstrapSession } from '../infrastructure/session-state.js';
|
|
13
14
|
const positiveInteger = (fallback) => z
|
|
14
15
|
.number()
|
|
15
16
|
.int()
|
|
16
17
|
.positive()
|
|
17
18
|
.optional()
|
|
18
19
|
.transform((value) => value ?? fallback);
|
|
20
|
+
const optionalPositiveInteger = () => z
|
|
21
|
+
.number()
|
|
22
|
+
.int()
|
|
23
|
+
.positive()
|
|
24
|
+
.optional();
|
|
19
25
|
const vaultInput = {
|
|
20
26
|
vault: z.string().min(1).optional().describe('Vault directory. Omit to use the configured Brainlink default vault.')
|
|
21
27
|
};
|
|
@@ -33,10 +39,12 @@ const resolveExecutionContext = async (input) => {
|
|
|
33
39
|
const config = await loadBrainlinkConfig();
|
|
34
40
|
const vault = await assertVaultAllowed(input.vault ?? config.vault, config.allowedVaults);
|
|
35
41
|
const agent = input.agent ?? config.defaultAgent;
|
|
42
|
+
const defaults = resolveAgentRuntimeDefaults(config, agent);
|
|
36
43
|
return {
|
|
37
44
|
config,
|
|
38
45
|
vault,
|
|
39
|
-
agent
|
|
46
|
+
agent,
|
|
47
|
+
defaults
|
|
40
48
|
};
|
|
41
49
|
};
|
|
42
50
|
const inferTitleFromPath = (filePath) => {
|
|
@@ -58,20 +66,97 @@ const jsonResult = (value) => ({
|
|
|
58
66
|
],
|
|
59
67
|
structuredContent: value
|
|
60
68
|
});
|
|
69
|
+
const preflightResult = (value) => jsonResult({
|
|
70
|
+
preflightRequired: true,
|
|
71
|
+
...value
|
|
72
|
+
});
|
|
73
|
+
const withNextActions = (value, nextActions) => ({
|
|
74
|
+
...value,
|
|
75
|
+
nextActions
|
|
76
|
+
});
|
|
77
|
+
const ensureBootstrapReady = async (context, input, toolName) => {
|
|
78
|
+
const policy = await getBootstrapPolicy();
|
|
79
|
+
if (!policy.enforceBootstrap) {
|
|
80
|
+
return {
|
|
81
|
+
bootstrap: {
|
|
82
|
+
autoBootstrapped: false,
|
|
83
|
+
policy,
|
|
84
|
+
statusBefore: {
|
|
85
|
+
ready: true,
|
|
86
|
+
stale: false
|
|
87
|
+
},
|
|
88
|
+
reason: 'Bootstrap enforcement is disabled by policy.'
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
const status = await getBootstrapSessionStatus(context.vault, context.agent);
|
|
93
|
+
if (status.ready) {
|
|
94
|
+
return {
|
|
95
|
+
bootstrap: {
|
|
96
|
+
autoBootstrapped: false,
|
|
97
|
+
policy,
|
|
98
|
+
statusBefore: status,
|
|
99
|
+
reason: 'Bootstrap session is already fresh for this vault/agent.'
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
if (policy.autoBootstrapOnRead) {
|
|
104
|
+
const index = await indexVault(context.vault);
|
|
105
|
+
const session = await touchBootstrapSession(context.vault, context.agent);
|
|
106
|
+
const statusAfter = await getBootstrapSessionStatus(context.vault, context.agent);
|
|
107
|
+
return {
|
|
108
|
+
bootstrap: {
|
|
109
|
+
autoBootstrapped: true,
|
|
110
|
+
policy,
|
|
111
|
+
statusBefore: status,
|
|
112
|
+
statusAfter,
|
|
113
|
+
session,
|
|
114
|
+
index,
|
|
115
|
+
reason: 'Auto-bootstrap was applied for this read call because bootstrap was missing or stale.'
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
const mode = typeof input.mode === 'string' && ['fts', 'semantic', 'hybrid'].includes(input.mode) ? input.mode : 'hybrid';
|
|
120
|
+
const query = typeof input.query === 'string' && input.query.trim().length > 0 ? input.query : undefined;
|
|
121
|
+
const bootstrapArgs = {
|
|
122
|
+
vault: context.vault,
|
|
123
|
+
...(context.agent ? { agent: context.agent } : {}),
|
|
124
|
+
...(query ? { query } : {}),
|
|
125
|
+
mode
|
|
126
|
+
};
|
|
127
|
+
const nextActions = [
|
|
128
|
+
{
|
|
129
|
+
tool: 'brainlink_bootstrap',
|
|
130
|
+
reason: 'Bootstrap is required before read tools so retrieval stays grounded in current vault state.',
|
|
131
|
+
args: bootstrapArgs
|
|
132
|
+
}
|
|
133
|
+
];
|
|
134
|
+
return {
|
|
135
|
+
preflight: preflightResult(withNextActions({
|
|
136
|
+
vault: context.vault,
|
|
137
|
+
agent: context.agent,
|
|
138
|
+
blockedTool: toolName,
|
|
139
|
+
policy,
|
|
140
|
+
bootstrapStatus: status,
|
|
141
|
+
guidance: 'Run brainlink_bootstrap first for this vault/agent before using read tools. This keeps retrieval grounded and memory state consistent.',
|
|
142
|
+
bootstrapArgs
|
|
143
|
+
}, nextActions))
|
|
144
|
+
};
|
|
145
|
+
};
|
|
61
146
|
export const contextInputSchema = {
|
|
62
147
|
...vaultInput,
|
|
63
148
|
...agentInput,
|
|
64
149
|
...searchModeInput,
|
|
65
150
|
query: z.string().min(1).describe('Task or question to retrieve Brainlink context for.'),
|
|
66
|
-
limit:
|
|
67
|
-
tokens:
|
|
151
|
+
limit: optionalPositiveInteger().describe('Maximum search results before context selection.'),
|
|
152
|
+
tokens: optionalPositiveInteger().describe('Maximum estimated context tokens.')
|
|
68
153
|
};
|
|
69
154
|
export const searchInputSchema = {
|
|
70
155
|
...vaultInput,
|
|
71
156
|
...agentInput,
|
|
72
157
|
...searchModeInput,
|
|
73
158
|
query: z.string().min(1).describe('Search query.'),
|
|
74
|
-
limit:
|
|
159
|
+
limit: optionalPositiveInteger().describe('Maximum result count.')
|
|
75
160
|
};
|
|
76
161
|
export const addNoteInputSchema = {
|
|
77
162
|
...vaultInput,
|
|
@@ -120,37 +205,87 @@ export const syncInputSchema = {
|
|
|
120
205
|
...agentInput,
|
|
121
206
|
contextQuery: z.string().min(1).optional().describe('Optional context smoke query. Omit to skip context probe.'),
|
|
122
207
|
mode: z.enum(['fts', 'semantic', 'hybrid']).optional().describe('Search mode for the optional context probe. Defaults to config value.'),
|
|
123
|
-
contextLimit:
|
|
124
|
-
contextTokens:
|
|
208
|
+
contextLimit: optionalPositiveInteger().describe('Context smoke result limit when contextQuery is provided.'),
|
|
209
|
+
contextTokens: optionalPositiveInteger().describe('Context smoke token target when contextQuery is provided.')
|
|
210
|
+
};
|
|
211
|
+
export const bootstrapInputSchema = {
|
|
212
|
+
...vaultInput,
|
|
213
|
+
...agentInput,
|
|
214
|
+
...searchModeInput,
|
|
215
|
+
query: z
|
|
216
|
+
.string()
|
|
217
|
+
.min(1)
|
|
218
|
+
.optional()
|
|
219
|
+
.describe('Optional task query. When provided, Brainlink also returns a context package in the same call.'),
|
|
220
|
+
limit: optionalPositiveInteger().describe('Context limit used when query is provided.'),
|
|
221
|
+
tokens: optionalPositiveInteger().describe('Context token target used when query is provided.')
|
|
222
|
+
};
|
|
223
|
+
export const policyInputSchema = {
|
|
224
|
+
...vaultInput,
|
|
225
|
+
...agentInput,
|
|
226
|
+
preset: z.enum(['fully-auto', 'strict']).optional().describe('Apply an opinionated policy preset before explicit overrides.'),
|
|
227
|
+
enforceBootstrap: z.boolean().optional().describe('Enable or disable bootstrap enforcement for MCP read tools.'),
|
|
228
|
+
autoBootstrapOnRead: z
|
|
229
|
+
.boolean()
|
|
230
|
+
.optional()
|
|
231
|
+
.describe('When bootstrap is missing/stale, run automatic bootstrap on read tools instead of returning preflight-required responses.'),
|
|
232
|
+
autoBootstrapOnStartup: z
|
|
233
|
+
.boolean()
|
|
234
|
+
.optional()
|
|
235
|
+
.describe('Run automatic bootstrap during MCP server startup using configured default vault/agent.'),
|
|
236
|
+
staleAfterMinutes: positiveInteger(120).describe('Bootstrap freshness window in minutes before read tools require a new bootstrap.')
|
|
237
|
+
};
|
|
238
|
+
export const recommendationsInputSchema = {
|
|
239
|
+
...vaultInput,
|
|
240
|
+
...agentInput,
|
|
241
|
+
...searchModeInput,
|
|
242
|
+
query: z.string().min(1).optional().describe('Optional current task query to generate context-focused recommendations.'),
|
|
243
|
+
limit: optionalPositiveInteger().describe('Optional context limit override for generated recommendations.'),
|
|
244
|
+
tokens: optionalPositiveInteger().describe('Optional context token budget override for generated recommendations.')
|
|
125
245
|
};
|
|
126
246
|
export const contextTool = async (input) => {
|
|
127
247
|
const context = await resolveExecutionContext(input);
|
|
128
|
-
const
|
|
129
|
-
|
|
248
|
+
const readiness = await ensureBootstrapReady(context, input, 'brainlink_context');
|
|
249
|
+
if (readiness.preflight) {
|
|
250
|
+
return readiness.preflight;
|
|
251
|
+
}
|
|
252
|
+
const mode = sanitizeSearchMode(input.mode, context.defaults.defaultSearchMode);
|
|
253
|
+
const limit = input.limit ?? context.defaults.defaultSearchLimit;
|
|
254
|
+
const tokens = input.tokens ?? context.defaults.defaultContextTokens;
|
|
255
|
+
const contextPackage = await buildContextPackage(context.vault, input.query, limit, tokens, context.agent, mode);
|
|
130
256
|
return jsonResult({
|
|
131
257
|
vault: context.vault,
|
|
132
258
|
agent: context.agent,
|
|
133
259
|
mode,
|
|
260
|
+
limit,
|
|
261
|
+
tokens,
|
|
262
|
+
...(readiness.bootstrap ? { bootstrap: readiness.bootstrap } : {}),
|
|
134
263
|
...contextPackage
|
|
135
264
|
});
|
|
136
265
|
};
|
|
137
266
|
export const searchTool = async (input) => {
|
|
138
267
|
const context = await resolveExecutionContext(input);
|
|
139
|
-
const
|
|
140
|
-
|
|
268
|
+
const readiness = await ensureBootstrapReady(context, input, 'brainlink_search');
|
|
269
|
+
if (readiness.preflight) {
|
|
270
|
+
return readiness.preflight;
|
|
271
|
+
}
|
|
272
|
+
const mode = sanitizeSearchMode(input.mode, context.defaults.defaultSearchMode);
|
|
273
|
+
const limit = input.limit ?? context.defaults.defaultSearchLimit;
|
|
274
|
+
const results = await searchKnowledge(context.vault, input.query, limit, context.agent, mode);
|
|
141
275
|
return jsonResult({
|
|
142
276
|
vault: context.vault,
|
|
143
277
|
agent: context.agent,
|
|
144
278
|
query: input.query,
|
|
145
|
-
limit
|
|
279
|
+
limit,
|
|
146
280
|
mode,
|
|
281
|
+
...(readiness.bootstrap ? { bootstrap: readiness.bootstrap } : {}),
|
|
147
282
|
results
|
|
148
283
|
});
|
|
149
284
|
};
|
|
150
285
|
export const addNoteTool = async (input) => {
|
|
151
286
|
const context = await resolveExecutionContext(input);
|
|
152
287
|
const shouldIndex = isTruthy(input.autoIndex);
|
|
153
|
-
const
|
|
288
|
+
const added = await addNoteWithMetadata(context.vault, input.title, input.content, context.agent, {
|
|
154
289
|
allowSensitive: input.allowSensitive
|
|
155
290
|
});
|
|
156
291
|
const index = shouldIndex ? await indexVault(context.vault) : undefined;
|
|
@@ -158,7 +293,12 @@ export const addNoteTool = async (input) => {
|
|
|
158
293
|
vault: context.vault,
|
|
159
294
|
title: input.title,
|
|
160
295
|
agent: context.agent,
|
|
161
|
-
path,
|
|
296
|
+
path: added.path,
|
|
297
|
+
writeConnectivity: {
|
|
298
|
+
autoLinked: added.autoLinked,
|
|
299
|
+
linkTarget: added.linkTarget,
|
|
300
|
+
guaranteedEdge: true
|
|
301
|
+
},
|
|
162
302
|
...(index ? { index } : {})
|
|
163
303
|
});
|
|
164
304
|
};
|
|
@@ -171,7 +311,7 @@ export const addFileTool = async (input) => {
|
|
|
171
311
|
throw new Error('Cannot infer note title from file path. Provide a title explicitly.');
|
|
172
312
|
}
|
|
173
313
|
const shouldIndex = isTruthy(input.autoIndex);
|
|
174
|
-
const
|
|
314
|
+
const added = await addNoteWithMetadata(context.vault, title, content, context.agent, {
|
|
175
315
|
allowSensitive: input.allowSensitive
|
|
176
316
|
});
|
|
177
317
|
const index = shouldIndex ? await indexVault(context.vault) : undefined;
|
|
@@ -180,7 +320,12 @@ export const addFileTool = async (input) => {
|
|
|
180
320
|
title,
|
|
181
321
|
agent: context.agent,
|
|
182
322
|
filePath: input.filePath,
|
|
183
|
-
path,
|
|
323
|
+
path: added.path,
|
|
324
|
+
writeConnectivity: {
|
|
325
|
+
autoLinked: added.autoLinked,
|
|
326
|
+
linkTarget: added.linkTarget,
|
|
327
|
+
guaranteedEdge: true
|
|
328
|
+
},
|
|
184
329
|
...(index ? { index } : {})
|
|
185
330
|
});
|
|
186
331
|
};
|
|
@@ -194,51 +339,80 @@ export const indexTool = async (input) => {
|
|
|
194
339
|
};
|
|
195
340
|
export const validateTool = async (input) => {
|
|
196
341
|
const context = await resolveExecutionContext(input);
|
|
342
|
+
const readiness = await ensureBootstrapReady(context, input, 'brainlink_validate');
|
|
343
|
+
if (readiness.preflight) {
|
|
344
|
+
return readiness.preflight;
|
|
345
|
+
}
|
|
197
346
|
const validation = await validateVault(context.vault, context.agent);
|
|
198
347
|
return jsonResult({
|
|
199
348
|
vault: context.vault,
|
|
200
349
|
agent: context.agent,
|
|
350
|
+
...(readiness.bootstrap ? { bootstrap: readiness.bootstrap } : {}),
|
|
201
351
|
...validation
|
|
202
352
|
});
|
|
203
353
|
};
|
|
204
354
|
export const graphTool = async (input) => {
|
|
205
355
|
const context = await resolveExecutionContext(input);
|
|
356
|
+
const readiness = await ensureBootstrapReady(context, input, 'brainlink_graph');
|
|
357
|
+
if (readiness.preflight) {
|
|
358
|
+
return readiness.preflight;
|
|
359
|
+
}
|
|
206
360
|
const graph = await getGraph(context.vault, context.agent);
|
|
207
361
|
return jsonResult({
|
|
208
362
|
vault: context.vault,
|
|
209
363
|
agent: context.agent,
|
|
364
|
+
...(readiness.bootstrap ? { bootstrap: readiness.bootstrap } : {}),
|
|
210
365
|
...graph
|
|
211
366
|
});
|
|
212
367
|
};
|
|
213
368
|
export const brokenLinksTool = async (input) => {
|
|
214
369
|
const context = await resolveExecutionContext(input);
|
|
370
|
+
const readiness = await ensureBootstrapReady(context, input, 'brainlink_broken_links');
|
|
371
|
+
if (readiness.preflight) {
|
|
372
|
+
return readiness.preflight;
|
|
373
|
+
}
|
|
215
374
|
const brokenLinks = await getBrokenLinksReport(context.vault, context.agent);
|
|
216
375
|
return jsonResult({
|
|
217
376
|
vault: context.vault,
|
|
218
377
|
agent: context.agent,
|
|
378
|
+
...(readiness.bootstrap ? { bootstrap: readiness.bootstrap } : {}),
|
|
219
379
|
brokenLinks
|
|
220
380
|
});
|
|
221
381
|
};
|
|
222
382
|
export const orphansTool = async (input) => {
|
|
223
383
|
const context = await resolveExecutionContext(input);
|
|
384
|
+
const readiness = await ensureBootstrapReady(context, input, 'brainlink_orphans');
|
|
385
|
+
if (readiness.preflight) {
|
|
386
|
+
return readiness.preflight;
|
|
387
|
+
}
|
|
224
388
|
const orphans = await getOrphansReport(context.vault, context.agent);
|
|
225
389
|
return jsonResult({
|
|
226
390
|
vault: context.vault,
|
|
227
391
|
agent: context.agent,
|
|
392
|
+
...(readiness.bootstrap ? { bootstrap: readiness.bootstrap } : {}),
|
|
228
393
|
orphans
|
|
229
394
|
});
|
|
230
395
|
};
|
|
231
396
|
export const statsTool = async (input) => {
|
|
232
397
|
const context = await resolveExecutionContext(input);
|
|
398
|
+
const readiness = await ensureBootstrapReady(context, input, 'brainlink_stats');
|
|
399
|
+
if (readiness.preflight) {
|
|
400
|
+
return readiness.preflight;
|
|
401
|
+
}
|
|
233
402
|
const stats = await getStats(context.vault, context.agent);
|
|
234
403
|
return jsonResult({
|
|
235
404
|
vault: context.vault,
|
|
236
405
|
agent: context.agent,
|
|
406
|
+
...(readiness.bootstrap ? { bootstrap: readiness.bootstrap } : {}),
|
|
237
407
|
stats
|
|
238
408
|
});
|
|
239
409
|
};
|
|
240
410
|
export const syncTool = async (input) => {
|
|
241
411
|
const context = await resolveExecutionContext(input);
|
|
412
|
+
const readiness = await ensureBootstrapReady(context, input, 'brainlink_sync');
|
|
413
|
+
if (readiness.preflight) {
|
|
414
|
+
return readiness.preflight;
|
|
415
|
+
}
|
|
242
416
|
const index = await indexVault(context.vault);
|
|
243
417
|
const stats = await getStats(context.vault, context.agent);
|
|
244
418
|
const validation = await validateVault(context.vault, context.agent);
|
|
@@ -247,6 +421,7 @@ export const syncTool = async (input) => {
|
|
|
247
421
|
const response = {
|
|
248
422
|
vault: context.vault,
|
|
249
423
|
agent: context.agent,
|
|
424
|
+
...(readiness.bootstrap ? { bootstrap: readiness.bootstrap } : {}),
|
|
250
425
|
index,
|
|
251
426
|
stats,
|
|
252
427
|
validation,
|
|
@@ -256,8 +431,10 @@ export const syncTool = async (input) => {
|
|
|
256
431
|
if (!input.contextQuery) {
|
|
257
432
|
return jsonResult(response);
|
|
258
433
|
}
|
|
259
|
-
const mode = sanitizeSearchMode(input.mode, context.
|
|
260
|
-
const
|
|
434
|
+
const mode = sanitizeSearchMode(input.mode, context.defaults.defaultSearchMode);
|
|
435
|
+
const contextLimit = input.contextLimit ?? context.defaults.defaultSearchLimit;
|
|
436
|
+
const contextTokens = input.contextTokens ?? context.defaults.defaultContextTokens;
|
|
437
|
+
const contextPackage = await buildContextPackage(context.vault, input.contextQuery, contextLimit, contextTokens, context.agent, mode);
|
|
261
438
|
return jsonResult({
|
|
262
439
|
...response,
|
|
263
440
|
context: {
|
|
@@ -266,3 +443,228 @@ export const syncTool = async (input) => {
|
|
|
266
443
|
}
|
|
267
444
|
});
|
|
268
445
|
};
|
|
446
|
+
export const bootstrapTool = async (input) => {
|
|
447
|
+
const context = await resolveExecutionContext(input);
|
|
448
|
+
const index = await indexVault(context.vault);
|
|
449
|
+
const stats = await getStats(context.vault, context.agent);
|
|
450
|
+
const validation = await validateVault(context.vault, context.agent);
|
|
451
|
+
const mode = sanitizeSearchMode(input.mode, context.defaults.defaultSearchMode);
|
|
452
|
+
const limit = input.limit ?? context.defaults.defaultSearchLimit;
|
|
453
|
+
const tokens = input.tokens ?? context.defaults.defaultContextTokens;
|
|
454
|
+
const contextPackage = input.query
|
|
455
|
+
? await buildContextPackage(context.vault, input.query, limit, tokens, context.agent, mode)
|
|
456
|
+
: undefined;
|
|
457
|
+
const guidance = stats.documentCount === 0
|
|
458
|
+
? 'Vault indexed with zero documents. Add durable notes with brainlink_add_note, then run brainlink_bootstrap again.'
|
|
459
|
+
: input.query
|
|
460
|
+
? 'Use returned context as grounding baseline, then write durable updates with brainlink_add_note when needed.'
|
|
461
|
+
: 'Run brainlink_context with the current task query to retrieve grounded context before answering.';
|
|
462
|
+
const session = await touchBootstrapSession(context.vault, context.agent);
|
|
463
|
+
const policy = await getBootstrapPolicy();
|
|
464
|
+
const nextActions = stats.documentCount === 0
|
|
465
|
+
? [
|
|
466
|
+
{
|
|
467
|
+
tool: 'brainlink_add_note',
|
|
468
|
+
reason: 'No indexed documents were found. Add durable Markdown memory first.',
|
|
469
|
+
args: {
|
|
470
|
+
vault: context.vault,
|
|
471
|
+
...(context.agent ? { agent: context.agent } : {}),
|
|
472
|
+
title: 'Architecture',
|
|
473
|
+
content: 'Durable memory with explicit [[links]] and #tags.'
|
|
474
|
+
}
|
|
475
|
+
},
|
|
476
|
+
{
|
|
477
|
+
tool: 'brainlink_bootstrap',
|
|
478
|
+
reason: 'Re-run bootstrap after writing notes so context tools can work on fresh index state.',
|
|
479
|
+
args: {
|
|
480
|
+
vault: context.vault,
|
|
481
|
+
...(context.agent ? { agent: context.agent } : {}),
|
|
482
|
+
mode
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
]
|
|
486
|
+
: input.query
|
|
487
|
+
? [
|
|
488
|
+
{
|
|
489
|
+
tool: 'brainlink_add_note',
|
|
490
|
+
reason: 'Persist relevant outcomes from this task as durable memory.',
|
|
491
|
+
args: {
|
|
492
|
+
vault: context.vault,
|
|
493
|
+
...(context.agent ? { agent: context.agent } : {}),
|
|
494
|
+
title: 'Task Update',
|
|
495
|
+
content: 'Summarize durable findings and connect with [[existing notes]].'
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
]
|
|
499
|
+
: [
|
|
500
|
+
{
|
|
501
|
+
tool: 'brainlink_context',
|
|
502
|
+
reason: 'Fetch grounded context for the current task.',
|
|
503
|
+
args: {
|
|
504
|
+
vault: context.vault,
|
|
505
|
+
...(context.agent ? { agent: context.agent } : {}),
|
|
506
|
+
query: '<task>',
|
|
507
|
+
mode,
|
|
508
|
+
limit,
|
|
509
|
+
tokens
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
];
|
|
513
|
+
return jsonResult(withNextActions({
|
|
514
|
+
vault: context.vault,
|
|
515
|
+
agent: context.agent,
|
|
516
|
+
mode,
|
|
517
|
+
limit,
|
|
518
|
+
tokens,
|
|
519
|
+
index,
|
|
520
|
+
stats,
|
|
521
|
+
validation,
|
|
522
|
+
policy,
|
|
523
|
+
session,
|
|
524
|
+
guidance,
|
|
525
|
+
...(contextPackage ? { context: contextPackage } : {})
|
|
526
|
+
}, nextActions));
|
|
527
|
+
};
|
|
528
|
+
export const policyTool = async (input) => {
|
|
529
|
+
const context = await resolveExecutionContext(input);
|
|
530
|
+
const presetPatch = input.preset === 'strict'
|
|
531
|
+
? {
|
|
532
|
+
enforceBootstrap: true,
|
|
533
|
+
autoBootstrapOnRead: false,
|
|
534
|
+
autoBootstrapOnStartup: false
|
|
535
|
+
}
|
|
536
|
+
: input.preset === 'fully-auto'
|
|
537
|
+
? {
|
|
538
|
+
enforceBootstrap: true,
|
|
539
|
+
autoBootstrapOnRead: true,
|
|
540
|
+
autoBootstrapOnStartup: true
|
|
541
|
+
}
|
|
542
|
+
: {};
|
|
543
|
+
const policy = input.preset !== undefined ||
|
|
544
|
+
typeof input.enforceBootstrap === 'boolean' ||
|
|
545
|
+
typeof input.autoBootstrapOnRead === 'boolean' ||
|
|
546
|
+
typeof input.autoBootstrapOnStartup === 'boolean' ||
|
|
547
|
+
typeof input.staleAfterMinutes === 'number'
|
|
548
|
+
? await setBootstrapPolicy({
|
|
549
|
+
...presetPatch,
|
|
550
|
+
...(typeof input.enforceBootstrap === 'boolean' ? { enforceBootstrap: input.enforceBootstrap } : {}),
|
|
551
|
+
...(typeof input.autoBootstrapOnRead === 'boolean' ? { autoBootstrapOnRead: input.autoBootstrapOnRead } : {}),
|
|
552
|
+
...(typeof input.autoBootstrapOnStartup === 'boolean' ? { autoBootstrapOnStartup: input.autoBootstrapOnStartup } : {}),
|
|
553
|
+
...(typeof input.staleAfterMinutes === 'number' ? { staleAfterMinutes: input.staleAfterMinutes } : {})
|
|
554
|
+
})
|
|
555
|
+
: await getBootstrapPolicy();
|
|
556
|
+
const bootstrapStatus = await getBootstrapSessionStatus(context.vault, context.agent);
|
|
557
|
+
const nextActions = bootstrapStatus.ready
|
|
558
|
+
? []
|
|
559
|
+
: [
|
|
560
|
+
{
|
|
561
|
+
tool: 'brainlink_bootstrap',
|
|
562
|
+
reason: 'Bootstrap status is not ready. Run bootstrap before using read tools.',
|
|
563
|
+
args: {
|
|
564
|
+
vault: context.vault,
|
|
565
|
+
...(context.agent ? { agent: context.agent } : {}),
|
|
566
|
+
mode: context.defaults.defaultSearchMode
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
];
|
|
570
|
+
return jsonResult(withNextActions({
|
|
571
|
+
vault: context.vault,
|
|
572
|
+
agent: context.agent,
|
|
573
|
+
policy,
|
|
574
|
+
bootstrapStatus,
|
|
575
|
+
...(input.preset ? { presetApplied: input.preset } : {})
|
|
576
|
+
}, nextActions));
|
|
577
|
+
};
|
|
578
|
+
export const recommendationsTool = async (input) => {
|
|
579
|
+
const context = await resolveExecutionContext(input);
|
|
580
|
+
const policy = await getBootstrapPolicy();
|
|
581
|
+
const bootstrapStatus = await getBootstrapSessionStatus(context.vault, context.agent);
|
|
582
|
+
const stats = await getStats(context.vault, context.agent);
|
|
583
|
+
const mode = sanitizeSearchMode(input.mode, context.defaults.defaultSearchMode);
|
|
584
|
+
const limit = input.limit ?? context.defaults.defaultSearchLimit;
|
|
585
|
+
const tokens = input.tokens ?? context.defaults.defaultContextTokens;
|
|
586
|
+
const query = input.query?.trim();
|
|
587
|
+
const recommendations = [
|
|
588
|
+
...(policy.enforceBootstrap && (!policy.autoBootstrapOnRead || !policy.autoBootstrapOnStartup)
|
|
589
|
+
? [
|
|
590
|
+
{
|
|
591
|
+
tool: 'brainlink_policy',
|
|
592
|
+
reason: 'Enable fully automatic bootstrap for plug-and-play agent usage.',
|
|
593
|
+
args: {
|
|
594
|
+
preset: 'fully-auto'
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
]
|
|
598
|
+
: []),
|
|
599
|
+
...(!bootstrapStatus.ready && !policy.autoBootstrapOnRead
|
|
600
|
+
? [
|
|
601
|
+
{
|
|
602
|
+
tool: 'brainlink_bootstrap',
|
|
603
|
+
reason: 'Bootstrap is required before read tools when auto-bootstrap-on-read is disabled.',
|
|
604
|
+
args: {
|
|
605
|
+
vault: context.vault,
|
|
606
|
+
...(context.agent ? { agent: context.agent } : {}),
|
|
607
|
+
mode,
|
|
608
|
+
...(query ? { query } : {})
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
]
|
|
612
|
+
: []),
|
|
613
|
+
...(stats.documentCount === 0
|
|
614
|
+
? [
|
|
615
|
+
{
|
|
616
|
+
tool: 'brainlink_add_note',
|
|
617
|
+
reason: 'Seed the vault with a first durable note so retrieval can return useful context.',
|
|
618
|
+
args: {
|
|
619
|
+
vault: context.vault,
|
|
620
|
+
...(context.agent ? { agent: context.agent } : {}),
|
|
621
|
+
title: 'Architecture',
|
|
622
|
+
content: 'Seed durable memory with explicit [[links]] and #tags.'
|
|
623
|
+
}
|
|
624
|
+
},
|
|
625
|
+
{
|
|
626
|
+
tool: 'brainlink_index',
|
|
627
|
+
reason: 'Rebuild index after writing the first notes.',
|
|
628
|
+
args: {
|
|
629
|
+
vault: context.vault
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
]
|
|
633
|
+
: []),
|
|
634
|
+
{
|
|
635
|
+
tool: 'brainlink_context',
|
|
636
|
+
reason: 'Retrieve grounded memory context before responding.',
|
|
637
|
+
args: {
|
|
638
|
+
vault: context.vault,
|
|
639
|
+
...(context.agent ? { agent: context.agent } : {}),
|
|
640
|
+
query: query ?? '<task>',
|
|
641
|
+
mode,
|
|
642
|
+
limit,
|
|
643
|
+
tokens
|
|
644
|
+
}
|
|
645
|
+
},
|
|
646
|
+
{
|
|
647
|
+
tool: 'brainlink_add_note',
|
|
648
|
+
reason: 'Persist durable outcomes after task completion (write responses include connectivity metadata).',
|
|
649
|
+
args: {
|
|
650
|
+
vault: context.vault,
|
|
651
|
+
...(context.agent ? { agent: context.agent } : {}),
|
|
652
|
+
title: 'Task Update',
|
|
653
|
+
content: 'Durable findings connected to [[existing notes]].'
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
];
|
|
657
|
+
return jsonResult({
|
|
658
|
+
vault: context.vault,
|
|
659
|
+
agent: context.agent,
|
|
660
|
+
defaults: {
|
|
661
|
+
mode,
|
|
662
|
+
limit,
|
|
663
|
+
tokens
|
|
664
|
+
},
|
|
665
|
+
policy,
|
|
666
|
+
bootstrapStatus,
|
|
667
|
+
stats,
|
|
668
|
+
recommendations
|
|
669
|
+
});
|
|
670
|
+
};
|