@danielsimonjr/memory-mcp 12.2.3 → 12.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +88 -102
- package/dist/index.js +6 -0
- package/dist/server/toolDefinitions.d.ts.map +1 -1
- package/dist/server/toolDefinitions.js +745 -3
- package/dist/server/toolHandlers.d.ts +1 -9
- package/dist/server/toolHandlers.d.ts.map +1 -1
- package/dist/server/toolHandlers.js +859 -11
- package/package.json +9 -6
- package/dist/core/EntityManager.d.ts +0 -263
- package/dist/core/EntityManager.d.ts.map +0 -1
- package/dist/core/EntityManager.js +0 -478
- package/dist/core/GraphStorage.d.ts +0 -344
- package/dist/core/GraphStorage.d.ts.map +0 -1
- package/dist/core/GraphStorage.js +0 -708
- package/dist/core/GraphTraversal.d.ts +0 -136
- package/dist/core/GraphTraversal.d.ts.map +0 -1
- package/dist/core/GraphTraversal.js +0 -556
- package/dist/core/HierarchyManager.d.ts +0 -111
- package/dist/core/HierarchyManager.d.ts.map +0 -1
- package/dist/core/HierarchyManager.js +0 -224
- package/dist/core/ManagerContext.d.ts +0 -72
- package/dist/core/ManagerContext.d.ts.map +0 -1
- package/dist/core/ManagerContext.js +0 -118
- package/dist/core/ObservationManager.d.ts +0 -85
- package/dist/core/ObservationManager.d.ts.map +0 -1
- package/dist/core/ObservationManager.js +0 -123
- package/dist/core/RelationManager.d.ts +0 -131
- package/dist/core/RelationManager.d.ts.map +0 -1
- package/dist/core/RelationManager.js +0 -210
- package/dist/core/SQLiteStorage.d.ts +0 -354
- package/dist/core/SQLiteStorage.d.ts.map +0 -1
- package/dist/core/SQLiteStorage.js +0 -917
- package/dist/core/StorageFactory.d.ts +0 -45
- package/dist/core/StorageFactory.d.ts.map +0 -1
- package/dist/core/StorageFactory.js +0 -64
- package/dist/core/TransactionManager.d.ts +0 -259
- package/dist/core/TransactionManager.d.ts.map +0 -1
- package/dist/core/TransactionManager.js +0 -392
- package/dist/core/index.d.ts +0 -16
- package/dist/core/index.d.ts.map +0 -1
- package/dist/core/index.js +0 -17
- package/dist/features/AnalyticsManager.d.ts +0 -44
- package/dist/features/AnalyticsManager.d.ts.map +0 -1
- package/dist/features/AnalyticsManager.js +0 -223
- package/dist/features/ArchiveManager.d.ts +0 -121
- package/dist/features/ArchiveManager.d.ts.map +0 -1
- package/dist/features/ArchiveManager.js +0 -240
- package/dist/features/CompressionManager.d.ts +0 -109
- package/dist/features/CompressionManager.d.ts.map +0 -1
- package/dist/features/CompressionManager.js +0 -404
- package/dist/features/IOManager.d.ts +0 -221
- package/dist/features/IOManager.d.ts.map +0 -1
- package/dist/features/IOManager.js +0 -997
- package/dist/features/StreamingExporter.d.ts +0 -105
- package/dist/features/StreamingExporter.d.ts.map +0 -1
- package/dist/features/StreamingExporter.js +0 -150
- package/dist/features/TagManager.d.ts +0 -147
- package/dist/features/TagManager.d.ts.map +0 -1
- package/dist/features/TagManager.js +0 -210
- package/dist/features/index.d.ts +0 -12
- package/dist/features/index.d.ts.map +0 -1
- package/dist/features/index.js +0 -11
- package/dist/search/BasicSearch.d.ts +0 -51
- package/dist/search/BasicSearch.d.ts.map +0 -1
- package/dist/search/BasicSearch.js +0 -137
- package/dist/search/BooleanSearch.d.ts +0 -98
- package/dist/search/BooleanSearch.d.ts.map +0 -1
- package/dist/search/BooleanSearch.js +0 -430
- package/dist/search/EmbeddingService.d.ts +0 -178
- package/dist/search/EmbeddingService.d.ts.map +0 -1
- package/dist/search/EmbeddingService.js +0 -358
- package/dist/search/FuzzySearch.d.ts +0 -118
- package/dist/search/FuzzySearch.d.ts.map +0 -1
- package/dist/search/FuzzySearch.js +0 -312
- package/dist/search/RankedSearch.d.ts +0 -71
- package/dist/search/RankedSearch.d.ts.map +0 -1
- package/dist/search/RankedSearch.js +0 -238
- package/dist/search/SavedSearchManager.d.ts +0 -79
- package/dist/search/SavedSearchManager.d.ts.map +0 -1
- package/dist/search/SavedSearchManager.js +0 -145
- package/dist/search/SearchFilterChain.d.ts +0 -120
- package/dist/search/SearchFilterChain.d.ts.map +0 -1
- package/dist/search/SearchFilterChain.js +0 -185
- package/dist/search/SearchManager.d.ts +0 -285
- package/dist/search/SearchManager.d.ts.map +0 -1
- package/dist/search/SearchManager.js +0 -338
- package/dist/search/SearchSuggestions.d.ts +0 -27
- package/dist/search/SearchSuggestions.d.ts.map +0 -1
- package/dist/search/SearchSuggestions.js +0 -57
- package/dist/search/SemanticSearch.d.ts +0 -146
- package/dist/search/SemanticSearch.d.ts.map +0 -1
- package/dist/search/SemanticSearch.js +0 -313
- package/dist/search/TFIDFIndexManager.d.ts +0 -63
- package/dist/search/TFIDFIndexManager.d.ts.map +0 -1
- package/dist/search/TFIDFIndexManager.js +0 -215
- package/dist/search/VectorStore.d.ts +0 -235
- package/dist/search/VectorStore.d.ts.map +0 -1
- package/dist/search/VectorStore.js +0 -311
- package/dist/search/index.d.ts +0 -18
- package/dist/search/index.d.ts.map +0 -1
- package/dist/search/index.js +0 -21
- package/dist/types/index.d.ts +0 -13
- package/dist/types/index.d.ts.map +0 -1
- package/dist/types/index.js +0 -12
- package/dist/types/types.d.ts +0 -1234
- package/dist/types/types.d.ts.map +0 -1
- package/dist/types/types.js +0 -9
- package/dist/utils/compressedCache.d.ts +0 -192
- package/dist/utils/compressedCache.d.ts.map +0 -1
- package/dist/utils/compressedCache.js +0 -309
- package/dist/utils/compressionUtil.d.ts +0 -214
- package/dist/utils/compressionUtil.d.ts.map +0 -1
- package/dist/utils/compressionUtil.js +0 -247
- package/dist/utils/constants.d.ts +0 -245
- package/dist/utils/constants.d.ts.map +0 -1
- package/dist/utils/constants.js +0 -252
- package/dist/utils/entityUtils.d.ts +0 -321
- package/dist/utils/entityUtils.d.ts.map +0 -1
- package/dist/utils/entityUtils.js +0 -538
- package/dist/utils/errors.d.ts +0 -77
- package/dist/utils/errors.d.ts.map +0 -1
- package/dist/utils/errors.js +0 -121
- package/dist/utils/formatters.d.ts +0 -145
- package/dist/utils/formatters.d.ts.map +0 -1
- package/dist/utils/formatters.js +0 -132
- package/dist/utils/index.d.ts +0 -22
- package/dist/utils/index.d.ts.map +0 -1
- package/dist/utils/index.js +0 -75
- package/dist/utils/indexes.d.ts +0 -270
- package/dist/utils/indexes.d.ts.map +0 -1
- package/dist/utils/indexes.js +0 -526
- package/dist/utils/logger.d.ts +0 -24
- package/dist/utils/logger.d.ts.map +0 -1
- package/dist/utils/logger.js +0 -33
- package/dist/utils/parallelUtils.d.ts +0 -72
- package/dist/utils/parallelUtils.d.ts.map +0 -1
- package/dist/utils/parallelUtils.js +0 -169
- package/dist/utils/schemas.d.ts +0 -374
- package/dist/utils/schemas.d.ts.map +0 -1
- package/dist/utils/schemas.js +0 -484
- package/dist/utils/searchAlgorithms.d.ts +0 -99
- package/dist/utils/searchAlgorithms.d.ts.map +0 -1
- package/dist/utils/searchAlgorithms.js +0 -167
- package/dist/utils/searchCache.d.ts +0 -108
- package/dist/utils/searchCache.d.ts.map +0 -1
- package/dist/utils/searchCache.js +0 -209
- package/dist/utils/taskScheduler.d.ts +0 -290
- package/dist/utils/taskScheduler.d.ts.map +0 -1
- package/dist/utils/taskScheduler.js +0 -466
- package/dist/workers/WorkerPool.d.ts +0 -81
- package/dist/workers/WorkerPool.d.ts.map +0 -1
- package/dist/workers/WorkerPool.js +0 -121
- package/dist/workers/index.d.ts +0 -12
- package/dist/workers/index.d.ts.map +0 -1
- package/dist/workers/index.js +0 -9
- package/dist/workers/levenshteinWorker.d.ts +0 -60
- package/dist/workers/levenshteinWorker.d.ts.map +0 -1
- package/dist/workers/levenshteinWorker.js +0 -98
|
@@ -8,7 +8,9 @@
|
|
|
8
8
|
* @module server/toolHandlers
|
|
9
9
|
*/
|
|
10
10
|
import path from 'node:path';
|
|
11
|
-
import { formatToolResponse, formatTextResponse, formatRawResponse, validateWithSchema, validateFilePath, BatchCreateEntitiesSchema, BatchCreateRelationsSchema, EntityNamesSchema, DeleteRelationsSchema, AddObservationsInputSchema, DeleteObservationsInputSchema, ArchiveCriteriaSchema, SavedSearchInputSchema, SavedSearchUpdateSchema, ImportFormatSchema, ExtendedExportFormatSchema, MergeStrategySchema, ExportFilterSchema, SearchQuerySchema, HybridSearchManager, QueryAnalyzer, QueryPlanner, ReflectionManager, ObservationNormalizer, RefIndex, AuditLog, GovernanceManager, FreshnessManager, ArtifactManager, CollaborativeSynthesis, FailureDistillation, CognitiveLoadAnalyzer, ConsolidationScheduler, DreamEngine, DistillationPipeline, DefaultDistillationPolicy, NoOpDistillationPolicy, computeEntropy, passesEntropyFilter, EntropyFilterStage, getRoleProfile, listRoleProfiles, QueryCostEstimator, ContradictionDetector, PiiRedactor, } from '@danielsimonjr/memoryjs';
|
|
11
|
+
import { formatToolResponse, formatTextResponse, formatRawResponse, validateWithSchema, validateFilePath, BatchCreateEntitiesSchema, BatchCreateRelationsSchema, EntityNamesSchema, DeleteRelationsSchema, AddObservationsInputSchema, DeleteObservationsInputSchema, ArchiveCriteriaSchema, SavedSearchInputSchema, SavedSearchUpdateSchema, ImportFormatSchema, ExtendedExportFormatSchema, MergeStrategySchema, ExportFilterSchema, SearchQuerySchema, HybridSearchManager, QueryAnalyzer, QueryPlanner, ReflectionManager, ObservationNormalizer, RefIndex, AuditLog, GovernanceManager, FreshnessManager, ArtifactManager, CollaborativeSynthesis, FailureDistillation, CognitiveLoadAnalyzer, ConsolidationScheduler, DreamEngine, DistillationPipeline, DefaultDistillationPolicy, NoOpDistillationPolicy, computeEntropy, passesEntropyFilter, EntropyFilterStage, getRoleProfile, listRoleProfiles, QueryCostEstimator, ContradictionDetector, PiiRedactor, DecisionManager, RankedSearch, clearAllSearchCaches, getAllCacheStats, } from '@danielsimonjr/memoryjs';
|
|
12
|
+
import { promises as fs } from 'node:fs';
|
|
13
|
+
import { performance } from 'node:perf_hooks';
|
|
12
14
|
import { z } from 'zod';
|
|
13
15
|
import { maybeCompressResponse } from './responseCompressor.js';
|
|
14
16
|
// ==================== SINGLETON INFRASTRUCTURE ====================
|
|
@@ -24,9 +26,21 @@ const failureDistillationMap = new WeakMap();
|
|
|
24
26
|
const consolidationSchedulerMap = new WeakMap();
|
|
25
27
|
const dreamEngineMap = new WeakMap();
|
|
26
28
|
const queryCostEstimatorMap = new WeakMap();
|
|
29
|
+
// Active project scope (Phase 13 deferred item; ctx.defaultProjectId is readonly,
|
|
30
|
+
// so we keep mutable scope state in a WeakMap keyed on the context. Set via the
|
|
31
|
+
// `set_project_scope` tool; handlers may consult `getActiveProjectScope(ctx)`
|
|
32
|
+
// when they want to auto-apply the scope.
|
|
33
|
+
const projectScopeMap = new WeakMap();
|
|
34
|
+
export function getActiveProjectScope(ctx) {
|
|
35
|
+
return projectScopeMap.get(ctx);
|
|
36
|
+
}
|
|
27
37
|
function getStorageFilePath(ctx) {
|
|
28
|
-
// GraphStorage
|
|
29
|
-
|
|
38
|
+
// GraphStorage stores the path on `memoryFilePath` (private but reachable via
|
|
39
|
+
// type-cast). Older versions of this helper looked for `filePath` and
|
|
40
|
+
// silently fell back to 'memory.jsonl' — which made diag / size / reindex
|
|
41
|
+
// reports point at the wrong file. Try both field names defensively.
|
|
42
|
+
const storage = ctx.storage;
|
|
43
|
+
return storage.memoryFilePath ?? storage.filePath ?? 'memory.jsonl';
|
|
30
44
|
}
|
|
31
45
|
function getRefIndex(ctx) {
|
|
32
46
|
if (!refIndexMap.has(ctx)) {
|
|
@@ -125,11 +139,43 @@ async function withCompression(handler) {
|
|
|
125
139
|
* Filtered/limited search tools (search_nodes_ranked, fuzzy_search, etc.) are not
|
|
126
140
|
* wrapped because their results are bounded by query specificity or limit params.
|
|
127
141
|
*/
|
|
142
|
+
/**
|
|
143
|
+
* v2.1.0 — Parse `find_duplicate_observations` / `find_jaccard_duplicate_observations`
|
|
144
|
+
* filter args into the manager's `ObservationDedupFilter` shape. Validates each
|
|
145
|
+
* field independently so a stray garbage value falls back to the manager default
|
|
146
|
+
* rather than throwing — REST-like leniency since these are diagnostic tools.
|
|
147
|
+
*/
|
|
148
|
+
function parseObservationDedupFilter(args) {
|
|
149
|
+
const out = {};
|
|
150
|
+
if (typeof args.entityType === 'string') {
|
|
151
|
+
out.entityType = args.entityType;
|
|
152
|
+
}
|
|
153
|
+
else if (Array.isArray(args.entityType) && args.entityType.every((s) => typeof s === 'string')) {
|
|
154
|
+
out.entityType = args.entityType;
|
|
155
|
+
}
|
|
156
|
+
if (typeof args.projectId === 'string')
|
|
157
|
+
out.projectId = args.projectId;
|
|
158
|
+
if (typeof args.sessionId === 'string')
|
|
159
|
+
out.sessionId = args.sessionId;
|
|
160
|
+
if (typeof args.minOccurrences === 'number' && Number.isFinite(args.minOccurrences) && args.minOccurrences >= 2) {
|
|
161
|
+
out.minOccurrences = args.minOccurrences;
|
|
162
|
+
}
|
|
163
|
+
if (typeof args.maxGroups === 'number' && Number.isFinite(args.maxGroups) && args.maxGroups >= 1) {
|
|
164
|
+
out.maxGroups = args.maxGroups;
|
|
165
|
+
}
|
|
166
|
+
return out;
|
|
167
|
+
}
|
|
128
168
|
export const toolHandlers = {
|
|
129
169
|
// ==================== ENTITY HANDLERS ====================
|
|
130
170
|
create_entities: async (ctx, args) => {
|
|
131
171
|
const entities = validateWithSchema(args.entities, BatchCreateEntitiesSchema, 'Invalid entities data');
|
|
132
|
-
|
|
172
|
+
// Phase 13 (v12.4.0): auto-stamp active project scope on entities lacking
|
|
173
|
+
// an explicit projectId. Set via set_project_scope; explicit projectId wins.
|
|
174
|
+
const activeScope = getActiveProjectScope(ctx);
|
|
175
|
+
const scoped = activeScope
|
|
176
|
+
? entities.map((e) => (e.projectId === undefined ? { ...e, projectId: activeScope } : e))
|
|
177
|
+
: entities;
|
|
178
|
+
return formatToolResponse(await ctx.entityManager.createEntities(scoped));
|
|
133
179
|
},
|
|
134
180
|
delete_entities: async (ctx, args) => {
|
|
135
181
|
const entityNames = validateWithSchema(args.entityNames, EntityNamesSchema, 'Invalid entity names');
|
|
@@ -148,6 +194,19 @@ export const toolHandlers = {
|
|
|
148
194
|
const projects = await ctx.entityManager.listProjects();
|
|
149
195
|
return formatToolResponse({ projects, count: projects.length });
|
|
150
196
|
},
|
|
197
|
+
set_project_scope: async (ctx, args) => {
|
|
198
|
+
const projectId = validateWithSchema(args.projectId, z.string(), 'Invalid projectId');
|
|
199
|
+
if (projectId === '') {
|
|
200
|
+
projectScopeMap.delete(ctx);
|
|
201
|
+
return formatToolResponse({ projectId: null });
|
|
202
|
+
}
|
|
203
|
+
projectScopeMap.set(ctx, projectId);
|
|
204
|
+
return formatToolResponse({ projectId });
|
|
205
|
+
},
|
|
206
|
+
get_project_scope: async (ctx, _args) => {
|
|
207
|
+
const projectId = projectScopeMap.get(ctx) ?? null;
|
|
208
|
+
return formatToolResponse({ projectId });
|
|
209
|
+
},
|
|
151
210
|
get_entity_versions: async (ctx, args) => {
|
|
152
211
|
const entityName = validateWithSchema(args.entityName, z.string().min(1), 'Invalid entityName');
|
|
153
212
|
const latest = await ctx.entityManager.getLatestVersion(entityName);
|
|
@@ -242,10 +301,20 @@ export const toolHandlers = {
|
|
|
242
301
|
const entityName = args.entityName !== undefined
|
|
243
302
|
? validateWithSchema(args.entityName, z.string().min(1), 'Invalid entity name')
|
|
244
303
|
: undefined;
|
|
245
|
-
const options = args.options
|
|
304
|
+
const options = args.options !== undefined
|
|
305
|
+
? validateWithSchema(args.options, z.object({
|
|
306
|
+
resolveCoreferences: z.boolean().optional(),
|
|
307
|
+
anchorTimestamps: z.boolean().optional(),
|
|
308
|
+
extractKeywords: z.boolean().optional(),
|
|
309
|
+
}).strict(), 'Invalid options')
|
|
310
|
+
: {};
|
|
246
311
|
const persist = args.persist === true;
|
|
247
312
|
const normalizer = new ObservationNormalizer();
|
|
248
|
-
|
|
313
|
+
// Use getGraphForMutation() to acquire the storage write lock — prevents
|
|
314
|
+
// concurrent calls from overwriting each other's persisted normalizations.
|
|
315
|
+
const graph = persist
|
|
316
|
+
? await ctx.storage.getGraphForMutation()
|
|
317
|
+
: await ctx.storage.loadGraph();
|
|
249
318
|
const entities = entityName
|
|
250
319
|
? graph.entities.filter(e => e.name === entityName)
|
|
251
320
|
: graph.entities;
|
|
@@ -350,8 +419,22 @@ export const toolHandlers = {
|
|
|
350
419
|
// Phase 11 Sprint 2: Hybrid search
|
|
351
420
|
hybrid_search: async (ctx, args) => {
|
|
352
421
|
const query = validateWithSchema(args.query, SearchQuerySchema, 'Invalid search query');
|
|
353
|
-
const weights = args.weights
|
|
354
|
-
|
|
422
|
+
const weights = args.weights !== undefined
|
|
423
|
+
? validateWithSchema(args.weights, z.object({
|
|
424
|
+
semantic: z.number().optional(),
|
|
425
|
+
lexical: z.number().optional(),
|
|
426
|
+
symbolic: z.number().optional(),
|
|
427
|
+
}).strict(), 'Invalid weights')
|
|
428
|
+
: undefined;
|
|
429
|
+
const filters = args.filters !== undefined
|
|
430
|
+
? validateWithSchema(args.filters, z.object({
|
|
431
|
+
tags: z.array(z.string()).optional(),
|
|
432
|
+
entityTypes: z.array(z.string()).optional(),
|
|
433
|
+
dateRange: z.object({ start: z.string(), end: z.string() }).strict().optional(),
|
|
434
|
+
minImportance: z.number().optional(),
|
|
435
|
+
maxImportance: z.number().optional(),
|
|
436
|
+
}).strict(), 'Invalid filters')
|
|
437
|
+
: undefined;
|
|
355
438
|
const limit = args.limit !== undefined
|
|
356
439
|
? validateWithSchema(args.limit, z.number().int().positive().max(200), 'Invalid limit')
|
|
357
440
|
: 10;
|
|
@@ -863,7 +946,7 @@ export const toolHandlers = {
|
|
|
863
946
|
const entityName = args.entityName !== undefined
|
|
864
947
|
? validateWithSchema(args.entityName, z.string().min(1), 'Invalid entityName')
|
|
865
948
|
: undefined;
|
|
866
|
-
const refs = await getRefIndex(ctx).listRefs(entityName
|
|
949
|
+
const refs = await getRefIndex(ctx).listRefs(entityName);
|
|
867
950
|
return formatToolResponse({ refs, count: refs.length });
|
|
868
951
|
},
|
|
869
952
|
// ==================== ARTIFACT HANDLERS ====================
|
|
@@ -1274,7 +1357,9 @@ export const toolHandlers = {
|
|
|
1274
1357
|
const distillFailures = args.distillFailures !== undefined
|
|
1275
1358
|
? validateWithSchema(args.distillFailures, z.boolean(), 'Invalid distillFailures')
|
|
1276
1359
|
: true;
|
|
1277
|
-
|
|
1360
|
+
// Use getGraphForMutation() to acquire the storage write lock — prevents
|
|
1361
|
+
// concurrent end_session calls from clobbering each other's outcome writes.
|
|
1362
|
+
const graph = await ctx.storage.getGraphForMutation();
|
|
1278
1363
|
const sessionEntity = graph.entities.find(e => e.name === sessionId);
|
|
1279
1364
|
if (!sessionEntity) {
|
|
1280
1365
|
return formatTextResponse(`Session "${sessionId}" not found`);
|
|
@@ -1718,7 +1803,7 @@ export const toolHandlers = {
|
|
|
1718
1803
|
// ==================== η.5.5.c OCC HANDLER ====================
|
|
1719
1804
|
update_entity: async (ctx, args) => {
|
|
1720
1805
|
const name = validateWithSchema(args.name, z.string().min(1), 'Invalid name');
|
|
1721
|
-
const updates = validateWithSchema(args.updates, z.record(z.unknown()), 'Invalid updates');
|
|
1806
|
+
const updates = validateWithSchema(args.updates, z.record(z.string(), z.unknown()), 'Invalid updates');
|
|
1722
1807
|
const expectedVersion = args.expectedVersion !== undefined
|
|
1723
1808
|
? validateWithSchema(args.expectedVersion, z.number().int().positive(), 'Invalid expectedVersion')
|
|
1724
1809
|
: undefined;
|
|
@@ -1897,6 +1982,769 @@ export const toolHandlers = {
|
|
|
1897
1982
|
const chains = await ctx.worldModelManager.predictOutcome(action, candidates);
|
|
1898
1983
|
return formatToolResponse({ action, candidates, chains, count: chains.length });
|
|
1899
1984
|
},
|
|
1985
|
+
// ==================== v2.1.0 TOOL AFFORDANCE ====================
|
|
1986
|
+
record_tool_outcome: async (ctx, args) => {
|
|
1987
|
+
const toolName = validateWithSchema(args.toolName, z.string().min(1), 'Invalid toolName');
|
|
1988
|
+
const outcome = validateWithSchema(args.outcome, z.enum(['success', 'failure', 'partial']), 'Invalid outcome');
|
|
1989
|
+
const errorMessage = args.errorMessage === undefined
|
|
1990
|
+
? undefined
|
|
1991
|
+
: validateWithSchema(args.errorMessage, z.string(), 'Invalid errorMessage');
|
|
1992
|
+
const durationMs = args.durationMs === undefined
|
|
1993
|
+
? undefined
|
|
1994
|
+
: validateWithSchema(args.durationMs, z.number().min(0), 'Invalid durationMs');
|
|
1995
|
+
const record = await ctx.toolAffordanceManager.recordOutcome(toolName, {
|
|
1996
|
+
outcome,
|
|
1997
|
+
errorMessage,
|
|
1998
|
+
durationMs,
|
|
1999
|
+
});
|
|
2000
|
+
return formatToolResponse({ record });
|
|
2001
|
+
},
|
|
2002
|
+
get_tool_affordance_stats: async (ctx, args) => {
|
|
2003
|
+
const toolName = validateWithSchema(args.toolName, z.string().min(1), 'Invalid toolName');
|
|
2004
|
+
const stats = ctx.toolAffordanceManager.rollingStats(toolName);
|
|
2005
|
+
return formatToolResponse({ toolName, stats: stats ?? null });
|
|
2006
|
+
},
|
|
2007
|
+
suggest_tool: async (ctx, args) => {
|
|
2008
|
+
const taskHint = validateWithSchema(args.taskHint, z.string().min(1), 'Invalid taskHint');
|
|
2009
|
+
const limit = args.limit === undefined
|
|
2010
|
+
? undefined
|
|
2011
|
+
: validateWithSchema(args.limit, z.number().int().min(1), 'Invalid limit');
|
|
2012
|
+
const minScore = args.minScore === undefined
|
|
2013
|
+
? undefined
|
|
2014
|
+
: validateWithSchema(args.minScore, z.number().min(0).max(1), 'Invalid minScore');
|
|
2015
|
+
const suggestions = await ctx.toolAffordanceManager.suggestTool(taskHint, { limit, minScore });
|
|
2016
|
+
return formatToolResponse({ taskHint, suggestions, count: suggestions.length });
|
|
2017
|
+
},
|
|
2018
|
+
list_tool_affordances: async (ctx) => {
|
|
2019
|
+
const records = await ctx.toolAffordanceManager.list();
|
|
2020
|
+
return formatToolResponse({ records, count: records.length });
|
|
2021
|
+
},
|
|
2022
|
+
remove_tool_affordance: async (ctx, args) => {
|
|
2023
|
+
const toolName = validateWithSchema(args.toolName, z.string().min(1), 'Invalid toolName');
|
|
2024
|
+
const removed = await ctx.toolAffordanceManager.remove(toolName);
|
|
2025
|
+
return formatToolResponse({ toolName, removed });
|
|
2026
|
+
},
|
|
2027
|
+
observe_tool_start: (ctx, args) => {
|
|
2028
|
+
const toolName = validateWithSchema(args.toolName, z.string().min(1), 'Invalid toolName');
|
|
2029
|
+
const argsField = args.args === undefined
|
|
2030
|
+
? undefined
|
|
2031
|
+
: validateWithSchema(args.args, z.record(z.string(), z.unknown()), 'Invalid args (must be an object)');
|
|
2032
|
+
const callId = ctx.toolCallObserver.observeStart(toolName, argsField);
|
|
2033
|
+
return Promise.resolve(formatToolResponse({ callId, toolName }));
|
|
2034
|
+
},
|
|
2035
|
+
observe_tool_complete: async (ctx, args) => {
|
|
2036
|
+
const callId = validateWithSchema(args.callId, z.string().min(1), 'Invalid callId');
|
|
2037
|
+
const result = args.result === undefined
|
|
2038
|
+
? undefined
|
|
2039
|
+
: validateWithSchema(args.result, z.string(), 'Invalid result');
|
|
2040
|
+
await ctx.toolCallObserver.observeComplete(callId, result === undefined ? undefined : { result });
|
|
2041
|
+
return formatToolResponse({ callId, recorded: 'success' });
|
|
2042
|
+
},
|
|
2043
|
+
observe_tool_error: async (ctx, args) => {
|
|
2044
|
+
const callId = validateWithSchema(args.callId, z.string().min(1), 'Invalid callId');
|
|
2045
|
+
const errorMessage = validateWithSchema(args.errorMessage, z.string().min(1), 'Invalid errorMessage');
|
|
2046
|
+
await ctx.toolCallObserver.observeError(callId, errorMessage);
|
|
2047
|
+
return formatToolResponse({ callId, recorded: 'failure', errorMessage });
|
|
2048
|
+
},
|
|
2049
|
+
observe_tool_partial: async (ctx, args) => {
|
|
2050
|
+
const callId = validateWithSchema(args.callId, z.string().min(1), 'Invalid callId');
|
|
2051
|
+
const reason = validateWithSchema(args.reason, z.string().min(1), 'Invalid reason');
|
|
2052
|
+
await ctx.toolCallObserver.observePartial(callId, reason);
|
|
2053
|
+
return formatToolResponse({ callId, recorded: 'partial', reason });
|
|
2054
|
+
},
|
|
2055
|
+
observe_tool_cancel: (ctx, args) => {
|
|
2056
|
+
const callId = validateWithSchema(args.callId, z.string().min(1), 'Invalid callId');
|
|
2057
|
+
ctx.toolCallObserver.cancel(callId);
|
|
2058
|
+
return Promise.resolve(formatToolResponse({ callId, cancelled: true }));
|
|
2059
|
+
},
|
|
2060
|
+
tool_observer_in_flight_count: (ctx) => {
|
|
2061
|
+
const count = ctx.toolCallObserver.inFlightCount();
|
|
2062
|
+
return Promise.resolve(formatToolResponse({ inFlightCount: count }));
|
|
2063
|
+
},
|
|
2064
|
+
// ==================== v2.1.0 HEURISTIC GUIDELINES ====================
|
|
2065
|
+
add_heuristic: async (ctx, args) => {
|
|
2066
|
+
const condition = validateWithSchema(args.condition, z.string().min(1), 'Invalid condition');
|
|
2067
|
+
const action = validateWithSchema(args.action, z.string().min(1), 'Invalid action');
|
|
2068
|
+
const priority = args.priority === undefined
|
|
2069
|
+
? undefined
|
|
2070
|
+
: validateWithSchema(args.priority, z.number(), 'Invalid priority');
|
|
2071
|
+
const initialConfidence = args.initialConfidence === undefined
|
|
2072
|
+
? undefined
|
|
2073
|
+
: validateWithSchema(args.initialConfidence, z.number().min(0).max(1), 'Invalid initialConfidence');
|
|
2074
|
+
const importance = args.importance === undefined
|
|
2075
|
+
? undefined
|
|
2076
|
+
: validateWithSchema(args.importance, z.number().min(0).max(10), 'Invalid importance');
|
|
2077
|
+
const agentId = args.agentId === undefined
|
|
2078
|
+
? undefined
|
|
2079
|
+
: validateWithSchema(args.agentId, z.string().min(1), 'Invalid agentId');
|
|
2080
|
+
const id = args.id === undefined
|
|
2081
|
+
? undefined
|
|
2082
|
+
: validateWithSchema(args.id, z.string().min(1), 'Invalid id');
|
|
2083
|
+
const heuristicId = await ctx.heuristicManager.add({
|
|
2084
|
+
condition,
|
|
2085
|
+
action,
|
|
2086
|
+
priority,
|
|
2087
|
+
initialConfidence,
|
|
2088
|
+
importance,
|
|
2089
|
+
agentId,
|
|
2090
|
+
id: id,
|
|
2091
|
+
});
|
|
2092
|
+
return formatToolResponse({ id: heuristicId });
|
|
2093
|
+
},
|
|
2094
|
+
get_heuristic: async (ctx, args) => {
|
|
2095
|
+
const id = validateWithSchema(args.id, z.string().min(1), 'Invalid id');
|
|
2096
|
+
const heuristic = ctx.heuristicManager.get(id);
|
|
2097
|
+
return formatToolResponse({ id, heuristic: heuristic ?? null });
|
|
2098
|
+
},
|
|
2099
|
+
list_heuristics: async (ctx) => {
|
|
2100
|
+
const heuristics = await ctx.heuristicManager.list();
|
|
2101
|
+
return formatToolResponse({ heuristics, count: heuristics.length });
|
|
2102
|
+
},
|
|
2103
|
+
heuristic_count: async (ctx) => {
|
|
2104
|
+
const size = await ctx.heuristicManager.size();
|
|
2105
|
+
return formatToolResponse({ count: size });
|
|
2106
|
+
},
|
|
2107
|
+
match_heuristics: async (ctx, args) => {
|
|
2108
|
+
const input = validateWithSchema(args.input, z.string().min(1), 'Invalid input');
|
|
2109
|
+
const limit = args.limit === undefined
|
|
2110
|
+
? undefined
|
|
2111
|
+
: validateWithSchema(args.limit, z.number().int().min(1), 'Invalid limit');
|
|
2112
|
+
const minScore = args.minScore === undefined
|
|
2113
|
+
? undefined
|
|
2114
|
+
: validateWithSchema(args.minScore, z.number().min(0).max(1), 'Invalid minScore');
|
|
2115
|
+
const matches = await ctx.heuristicManager.match(input, { limit, minScore });
|
|
2116
|
+
return formatToolResponse({ input, matches, count: matches.length });
|
|
2117
|
+
},
|
|
2118
|
+
reinforce_heuristic: async (ctx, args) => {
|
|
2119
|
+
const id = validateWithSchema(args.id, z.string().min(1), 'Invalid id');
|
|
2120
|
+
const result = await ctx.heuristicManager.reinforce(id);
|
|
2121
|
+
return formatToolResponse({ id, result });
|
|
2122
|
+
},
|
|
2123
|
+
record_heuristic_contradiction: async (ctx, args) => {
|
|
2124
|
+
const id = validateWithSchema(args.id, z.string().min(1), 'Invalid id');
|
|
2125
|
+
const result = await ctx.heuristicManager.recordContradiction(id);
|
|
2126
|
+
return formatToolResponse({ id, result });
|
|
2127
|
+
},
|
|
2128
|
+
detect_heuristic_conflicts: async (ctx) => {
|
|
2129
|
+
const conflicts = await ctx.heuristicManager.detectConflicts();
|
|
2130
|
+
return formatToolResponse({ conflicts, count: conflicts.length });
|
|
2131
|
+
},
|
|
2132
|
+
remove_heuristic: async (ctx, args) => {
|
|
2133
|
+
const id = validateWithSchema(args.id, z.string().min(1), 'Invalid id');
|
|
2134
|
+
const removed = await ctx.heuristicManager.remove(id);
|
|
2135
|
+
return formatToolResponse({ id, removed });
|
|
2136
|
+
},
|
|
2137
|
+
clear_heuristics: async (ctx) => {
|
|
2138
|
+
await ctx.heuristicManager.clear();
|
|
2139
|
+
return formatToolResponse({ cleared: true });
|
|
2140
|
+
},
|
|
2141
|
+
// ==================== v2.1.0 PROJECT CONTEXT ====================
|
|
2142
|
+
upsert_project_context: async (ctx, args) => {
|
|
2143
|
+
const projectId = validateWithSchema(args.projectId, z.string().min(1), 'Invalid projectId');
|
|
2144
|
+
const facts = args.facts === undefined
|
|
2145
|
+
? undefined
|
|
2146
|
+
: validateWithSchema(args.facts, z.array(z.string()), 'Invalid facts');
|
|
2147
|
+
const conventions = args.conventions === undefined
|
|
2148
|
+
? undefined
|
|
2149
|
+
: validateWithSchema(args.conventions, z.array(z.string()), 'Invalid conventions');
|
|
2150
|
+
const commands = args.commands === undefined
|
|
2151
|
+
? undefined
|
|
2152
|
+
: validateWithSchema(args.commands, z.array(z.object({
|
|
2153
|
+
name: z.string().min(1),
|
|
2154
|
+
command: z.string().min(1),
|
|
2155
|
+
purpose: z.string().min(1),
|
|
2156
|
+
})), 'Invalid commands');
|
|
2157
|
+
const glossary = args.glossary === undefined
|
|
2158
|
+
? undefined
|
|
2159
|
+
: validateWithSchema(args.glossary, z.array(z.object({
|
|
2160
|
+
term: z.string().min(1),
|
|
2161
|
+
definition: z.string().min(1),
|
|
2162
|
+
})), 'Invalid glossary');
|
|
2163
|
+
const record = await ctx.projectContextManager.upsert(projectId, {
|
|
2164
|
+
facts, conventions, commands, glossary,
|
|
2165
|
+
});
|
|
2166
|
+
return formatToolResponse({ record });
|
|
2167
|
+
},
|
|
2168
|
+
get_project_context: async (ctx, args) => {
|
|
2169
|
+
const projectId = validateWithSchema(args.projectId, z.string().min(1), 'Invalid projectId');
|
|
2170
|
+
const record = ctx.projectContextManager.get(projectId);
|
|
2171
|
+
return formatToolResponse({ projectId, record: record ?? null });
|
|
2172
|
+
},
|
|
2173
|
+
append_project_fact: async (ctx, args) => {
|
|
2174
|
+
const projectId = validateWithSchema(args.projectId, z.string().min(1), 'Invalid projectId');
|
|
2175
|
+
const fact = validateWithSchema(args.fact, z.string().min(1), 'Invalid fact');
|
|
2176
|
+
const record = await ctx.projectContextManager.appendFact(projectId, fact);
|
|
2177
|
+
return formatToolResponse({ record });
|
|
2178
|
+
},
|
|
2179
|
+
append_project_convention: async (ctx, args) => {
|
|
2180
|
+
const projectId = validateWithSchema(args.projectId, z.string().min(1), 'Invalid projectId');
|
|
2181
|
+
const convention = validateWithSchema(args.convention, z.string().min(1), 'Invalid convention');
|
|
2182
|
+
const record = await ctx.projectContextManager.appendConvention(projectId, convention);
|
|
2183
|
+
return formatToolResponse({ record });
|
|
2184
|
+
},
|
|
2185
|
+
append_project_command: async (ctx, args) => {
|
|
2186
|
+
const projectId = validateWithSchema(args.projectId, z.string().min(1), 'Invalid projectId');
|
|
2187
|
+
const name = validateWithSchema(args.name, z.string().min(1), 'Invalid name');
|
|
2188
|
+
const command = validateWithSchema(args.command, z.string().min(1), 'Invalid command');
|
|
2189
|
+
const purpose = validateWithSchema(args.purpose, z.string().min(1), 'Invalid purpose');
|
|
2190
|
+
const record = await ctx.projectContextManager.appendCommand(projectId, { name, command, purpose });
|
|
2191
|
+
return formatToolResponse({ record });
|
|
2192
|
+
},
|
|
2193
|
+
append_project_glossary_term: async (ctx, args) => {
|
|
2194
|
+
const projectId = validateWithSchema(args.projectId, z.string().min(1), 'Invalid projectId');
|
|
2195
|
+
const term = validateWithSchema(args.term, z.string().min(1), 'Invalid term');
|
|
2196
|
+
const definition = validateWithSchema(args.definition, z.string().min(1), 'Invalid definition');
|
|
2197
|
+
const record = await ctx.projectContextManager.appendGlossaryTerm(projectId, { term, definition });
|
|
2198
|
+
return formatToolResponse({ record });
|
|
2199
|
+
},
|
|
2200
|
+
remove_project_fact: async (ctx, args) => {
|
|
2201
|
+
const projectId = validateWithSchema(args.projectId, z.string().min(1), 'Invalid projectId');
|
|
2202
|
+
const fact = validateWithSchema(args.fact, z.string().min(1), 'Invalid fact');
|
|
2203
|
+
const removed = await ctx.projectContextManager.removeFact(projectId, fact);
|
|
2204
|
+
return formatToolResponse({ projectId, fact, removed });
|
|
2205
|
+
},
|
|
2206
|
+
remove_project_convention: async (ctx, args) => {
|
|
2207
|
+
const projectId = validateWithSchema(args.projectId, z.string().min(1), 'Invalid projectId');
|
|
2208
|
+
const convention = validateWithSchema(args.convention, z.string().min(1), 'Invalid convention');
|
|
2209
|
+
const removed = await ctx.projectContextManager.removeConvention(projectId, convention);
|
|
2210
|
+
return formatToolResponse({ projectId, convention, removed });
|
|
2211
|
+
},
|
|
2212
|
+
remove_project_command: async (ctx, args) => {
|
|
2213
|
+
const projectId = validateWithSchema(args.projectId, z.string().min(1), 'Invalid projectId');
|
|
2214
|
+
const commandName = validateWithSchema(args.commandName, z.string().min(1), 'Invalid commandName');
|
|
2215
|
+
const removed = await ctx.projectContextManager.removeCommand(projectId, commandName);
|
|
2216
|
+
return formatToolResponse({ projectId, commandName, removed });
|
|
2217
|
+
},
|
|
2218
|
+
remove_project_glossary_term: async (ctx, args) => {
|
|
2219
|
+
const projectId = validateWithSchema(args.projectId, z.string().min(1), 'Invalid projectId');
|
|
2220
|
+
const term = validateWithSchema(args.term, z.string().min(1), 'Invalid term');
|
|
2221
|
+
const removed = await ctx.projectContextManager.removeGlossaryTerm(projectId, term);
|
|
2222
|
+
return formatToolResponse({ projectId, term, removed });
|
|
2223
|
+
},
|
|
2224
|
+
clear_project_context: async (ctx, args) => {
|
|
2225
|
+
const projectId = validateWithSchema(args.projectId, z.string().min(1), 'Invalid projectId');
|
|
2226
|
+
const cleared = await ctx.projectContextManager.clear(projectId);
|
|
2227
|
+
return formatToolResponse({ projectId, cleared });
|
|
2228
|
+
},
|
|
2229
|
+
format_project_context_for_llm: async (ctx, args) => {
|
|
2230
|
+
const projectId = validateWithSchema(args.projectId, z.string().min(1), 'Invalid projectId');
|
|
2231
|
+
const budgetChars = args.budgetChars === undefined
|
|
2232
|
+
? undefined
|
|
2233
|
+
: validateWithSchema(args.budgetChars, z.number().int().min(1), 'Invalid budgetChars');
|
|
2234
|
+
const prose = await ctx.projectContextManager.forContext(projectId, { budgetChars });
|
|
2235
|
+
return formatTextResponse(prose);
|
|
2236
|
+
},
|
|
2237
|
+
// ==================== v2.1.0 DECISION RATIONALE ====================
|
|
2238
|
+
propose_decision: async (ctx, args) => {
|
|
2239
|
+
const context = validateWithSchema(args.context, z.string().min(1), 'Invalid context');
|
|
2240
|
+
const decision = validateWithSchema(args.decision, z.string().min(1), 'Invalid decision');
|
|
2241
|
+
const alternatives = args.alternatives === undefined
|
|
2242
|
+
? []
|
|
2243
|
+
: validateWithSchema(args.alternatives, z.array(z.string()), 'Invalid alternatives');
|
|
2244
|
+
const consequences = args.consequences === undefined
|
|
2245
|
+
? []
|
|
2246
|
+
: validateWithSchema(args.consequences, z.array(z.string()), 'Invalid consequences');
|
|
2247
|
+
const relatedFiles = args.relatedFiles === undefined
|
|
2248
|
+
? undefined
|
|
2249
|
+
: validateWithSchema(args.relatedFiles, z.array(z.string()), 'Invalid relatedFiles');
|
|
2250
|
+
const supersedes = args.supersedes === undefined
|
|
2251
|
+
? undefined
|
|
2252
|
+
: validateWithSchema(args.supersedes, z.string().min(1), 'Invalid supersedes');
|
|
2253
|
+
const sourceSessionId = args.sourceSessionId === undefined
|
|
2254
|
+
? undefined
|
|
2255
|
+
: validateWithSchema(args.sourceSessionId, z.string().min(1), 'Invalid sourceSessionId');
|
|
2256
|
+
const sourceProjectId = args.sourceProjectId === undefined
|
|
2257
|
+
? undefined
|
|
2258
|
+
: validateWithSchema(args.sourceProjectId, z.string().min(1), 'Invalid sourceProjectId');
|
|
2259
|
+
const importance = args.importance === undefined
|
|
2260
|
+
? undefined
|
|
2261
|
+
: validateWithSchema(args.importance, z.number().min(0).max(10), 'Invalid importance');
|
|
2262
|
+
const agentId = args.agentId === undefined
|
|
2263
|
+
? undefined
|
|
2264
|
+
: validateWithSchema(args.agentId, z.string().min(1), 'Invalid agentId');
|
|
2265
|
+
// `supersedes` is a branded DecisionId at the type level; the
|
|
2266
|
+
// manager casts internally so a string is fine here.
|
|
2267
|
+
const record = await ctx.decisionManager.propose({
|
|
2268
|
+
context,
|
|
2269
|
+
decision,
|
|
2270
|
+
alternatives,
|
|
2271
|
+
consequences,
|
|
2272
|
+
relatedFiles,
|
|
2273
|
+
supersedes: supersedes,
|
|
2274
|
+
sourceSessionId,
|
|
2275
|
+
sourceProjectId,
|
|
2276
|
+
}, { importance, agentId });
|
|
2277
|
+
return formatToolResponse({ record });
|
|
2278
|
+
},
|
|
2279
|
+
accept_decision: async (ctx, args) => {
|
|
2280
|
+
const id = validateWithSchema(args.id, z.string().min(1), 'Invalid id');
|
|
2281
|
+
const result = await ctx.decisionManager.accept(id);
|
|
2282
|
+
return formatToolResponse({ id, result });
|
|
2283
|
+
},
|
|
2284
|
+
reject_decision: async (ctx, args) => {
|
|
2285
|
+
const id = validateWithSchema(args.id, z.string().min(1), 'Invalid id');
|
|
2286
|
+
const reason = validateWithSchema(args.reason, z.string().min(1), 'Invalid reason');
|
|
2287
|
+
const result = await ctx.decisionManager.reject(id, reason);
|
|
2288
|
+
return formatToolResponse({ id, reason, result });
|
|
2289
|
+
},
|
|
2290
|
+
supersede_decision: async (ctx, args) => {
|
|
2291
|
+
const id = validateWithSchema(args.id, z.string().min(1), 'Invalid id');
|
|
2292
|
+
const by = validateWithSchema(args.by, z.string().min(1), 'Invalid by');
|
|
2293
|
+
const result = await ctx.decisionManager.supersede(id, by);
|
|
2294
|
+
return formatToolResponse({ id, by, result });
|
|
2295
|
+
},
|
|
2296
|
+
find_decisions_by_context: async (ctx, args) => {
|
|
2297
|
+
const query = validateWithSchema(args.query, z.string().min(1), 'Invalid query');
|
|
2298
|
+
const records = await ctx.decisionManager.findByContext(query);
|
|
2299
|
+
return formatToolResponse({ query, records, count: records.length });
|
|
2300
|
+
},
|
|
2301
|
+
get_decision_chain: async (ctx, args) => {
|
|
2302
|
+
const id = validateWithSchema(args.id, z.string().min(1), 'Invalid id');
|
|
2303
|
+
const chain = await ctx.decisionManager.getChain(id);
|
|
2304
|
+
return formatToolResponse({ id, chain, length: chain.length });
|
|
2305
|
+
},
|
|
2306
|
+
list_decisions: async (ctx, args) => {
|
|
2307
|
+
const status = args.status === undefined
|
|
2308
|
+
? undefined
|
|
2309
|
+
: validateWithSchema(args.status, z.enum(['proposed', 'accepted', 'superseded', 'rejected']), 'Invalid status');
|
|
2310
|
+
const sourceSessionId = args.sourceSessionId === undefined
|
|
2311
|
+
? undefined
|
|
2312
|
+
: validateWithSchema(args.sourceSessionId, z.string().min(1), 'Invalid sourceSessionId');
|
|
2313
|
+
const sourceProjectId = args.sourceProjectId === undefined
|
|
2314
|
+
? undefined
|
|
2315
|
+
: validateWithSchema(args.sourceProjectId, z.string().min(1), 'Invalid sourceProjectId');
|
|
2316
|
+
const limit = args.limit === undefined
|
|
2317
|
+
? undefined
|
|
2318
|
+
: validateWithSchema(args.limit, z.number().int().min(1), 'Invalid limit');
|
|
2319
|
+
const records = await ctx.decisionManager.list({ status, sourceSessionId, sourceProjectId, limit });
|
|
2320
|
+
return formatToolResponse({ records, count: records.length });
|
|
2321
|
+
},
|
|
2322
|
+
get_decision: async (ctx, args) => {
|
|
2323
|
+
const id = validateWithSchema(args.id, z.string().min(1), 'Invalid id');
|
|
2324
|
+
const record = ctx.decisionManager.get(id);
|
|
2325
|
+
return formatToolResponse({ id, record: record ?? null });
|
|
2326
|
+
},
|
|
2327
|
+
export_decision_as_adr_markdown: async (ctx, args) => {
|
|
2328
|
+
const id = validateWithSchema(args.id, z.string().min(1), 'Invalid id');
|
|
2329
|
+
const markdown = ctx.decisionManager.exportAsAdrMarkdown(id);
|
|
2330
|
+
return formatTextResponse(markdown);
|
|
2331
|
+
},
|
|
2332
|
+
parse_adr_markdown: async (_ctx, args) => {
|
|
2333
|
+
const text = validateWithSchema(args.text, z.string().min(1), 'Invalid text');
|
|
2334
|
+
const parsed = DecisionManager.parseAdrMarkdown(text);
|
|
2335
|
+
return formatToolResponse({ parsed });
|
|
2336
|
+
},
|
|
2337
|
+
// ==================== v2.1.0 do_not_remember (Exclusion) ====================
|
|
2338
|
+
add_exclusion_rule: async (ctx, args) => {
|
|
2339
|
+
const pattern = validateWithSchema(args.pattern, z.string().min(1), 'Invalid pattern');
|
|
2340
|
+
const scope = args.scope === undefined
|
|
2341
|
+
? undefined
|
|
2342
|
+
: validateWithSchema(args.scope, z.enum(['future-only', 'past-only', 'both']), 'Invalid scope');
|
|
2343
|
+
const entityType = args.entityType === undefined
|
|
2344
|
+
? undefined
|
|
2345
|
+
: validateWithSchema(args.entityType, z.string().min(1), 'Invalid entityType');
|
|
2346
|
+
const reason = args.reason === undefined
|
|
2347
|
+
? undefined
|
|
2348
|
+
: validateWithSchema(args.reason, z.string().min(1), 'Invalid reason');
|
|
2349
|
+
const rule = await ctx.exclusionManager.add({ pattern, scope, entityType, reason });
|
|
2350
|
+
return formatToolResponse({ rule });
|
|
2351
|
+
},
|
|
2352
|
+
list_exclusion_rules: async (ctx) => {
|
|
2353
|
+
const rules = await ctx.exclusionManager.list();
|
|
2354
|
+
return formatToolResponse({ rules, count: rules.length });
|
|
2355
|
+
},
|
|
2356
|
+
remove_exclusion_rule: async (ctx, args) => {
|
|
2357
|
+
const id = validateWithSchema(args.id, z.string().min(1), 'Invalid id');
|
|
2358
|
+
const removed = await ctx.exclusionManager.remove(id);
|
|
2359
|
+
return formatToolResponse({ id, removed });
|
|
2360
|
+
},
|
|
2361
|
+
check_exclusion: async (ctx, args) => {
|
|
2362
|
+
const content = validateWithSchema(args.content, z.string(), 'Invalid content');
|
|
2363
|
+
const entityType = args.entityType === undefined
|
|
2364
|
+
? undefined
|
|
2365
|
+
: validateWithSchema(args.entityType, z.string().min(1), 'Invalid entityType');
|
|
2366
|
+
const verdict = await ctx.exclusionManager.check(content, entityType);
|
|
2367
|
+
return formatToolResponse(verdict);
|
|
2368
|
+
},
|
|
2369
|
+
find_matching_memories_for_rule: async (ctx, args) => {
|
|
2370
|
+
const pattern = validateWithSchema(args.pattern, z.string().min(1), 'Invalid pattern');
|
|
2371
|
+
const entityType = args.entityType === undefined
|
|
2372
|
+
? undefined
|
|
2373
|
+
: validateWithSchema(args.entityType, z.string().min(1), 'Invalid entityType');
|
|
2374
|
+
const matches = await ctx.exclusionManager.findMatchingMemories({ pattern, entityType });
|
|
2375
|
+
return formatToolResponse({
|
|
2376
|
+
pattern,
|
|
2377
|
+
entityType,
|
|
2378
|
+
matches: matches.map((m) => ({ name: m.name, entityType: m.entityType })),
|
|
2379
|
+
count: matches.length,
|
|
2380
|
+
});
|
|
2381
|
+
},
|
|
2382
|
+
// ==================== v2.1.0 OBSERVATION DEDUP ====================
|
|
2383
|
+
find_duplicate_observations: async (ctx, args) => {
|
|
2384
|
+
const filter = parseObservationDedupFilter(args);
|
|
2385
|
+
const groups = await ctx.observationDedupManager.findDuplicateObservations(filter);
|
|
2386
|
+
return formatToolResponse({ filter, groups, count: groups.length });
|
|
2387
|
+
},
|
|
2388
|
+
find_jaccard_duplicate_observations: async (ctx, args) => {
|
|
2389
|
+
const filter = parseObservationDedupFilter(args);
|
|
2390
|
+
const groups = await ctx.observationDedupManager.findJaccardDuplicates(filter);
|
|
2391
|
+
return formatToolResponse({ filter, groups, count: groups.length });
|
|
2392
|
+
},
|
|
2393
|
+
// ==================== v2.1.0 SPELL CORRECTION ====================
|
|
2394
|
+
spell_suggest: async (ctx, args) => {
|
|
2395
|
+
const query = validateWithSchema(args.query, z.string().min(1), 'Invalid query');
|
|
2396
|
+
const limit = args.limit === undefined
|
|
2397
|
+
? undefined
|
|
2398
|
+
: validateWithSchema(args.limit, z.number().int().min(1), 'Invalid limit');
|
|
2399
|
+
const minScore = args.minScore === undefined
|
|
2400
|
+
? undefined
|
|
2401
|
+
: validateWithSchema(args.minScore, z.number().min(0).max(1), 'Invalid minScore');
|
|
2402
|
+
const maxDistance = args.maxDistance === undefined
|
|
2403
|
+
? undefined
|
|
2404
|
+
: validateWithSchema(args.maxDistance, z.number().min(0), 'Invalid maxDistance');
|
|
2405
|
+
const suggestions = await ctx.spellChecker.suggest(query, { limit, minScore, maxDistance });
|
|
2406
|
+
return formatToolResponse({ query, suggestions, count: suggestions.length });
|
|
2407
|
+
},
|
|
2408
|
+
spell_rebuild_vocabulary: async (ctx) => {
|
|
2409
|
+
await ctx.spellChecker.rebuild();
|
|
2410
|
+
return formatToolResponse({ rebuilt: true, vocabularySize: ctx.spellChecker.vocabularySize() });
|
|
2411
|
+
},
|
|
2412
|
+
spell_vocabulary_size: async (ctx) => {
|
|
2413
|
+
const size = ctx.spellChecker.vocabularySize();
|
|
2414
|
+
return formatToolResponse({ vocabularySize: size });
|
|
2415
|
+
},
|
|
2416
|
+
// ==================== ENGINEERING / DIAGNOSTIC TOOLS (v12.5.0) ====================
|
|
2417
|
+
// Parallel to the `memory diag` / `memory check` / etc. CLI surface in memoryjs v2.2+.
|
|
2418
|
+
// Useful when the MCP server is up but the graph state is suspect.
|
|
2419
|
+
diag: async (ctx) => {
|
|
2420
|
+
const storagePath = getStorageFilePath(ctx);
|
|
2421
|
+
let exists = false;
|
|
2422
|
+
let sizeBytes = 0;
|
|
2423
|
+
try {
|
|
2424
|
+
const stat = await fs.stat(storagePath);
|
|
2425
|
+
exists = true;
|
|
2426
|
+
sizeBytes = stat.size;
|
|
2427
|
+
}
|
|
2428
|
+
catch { /* file may not exist yet */ }
|
|
2429
|
+
const stats = await ctx.analyticsManager.getGraphStats();
|
|
2430
|
+
return formatToolResponse({
|
|
2431
|
+
runtime: { node: process.version, platform: process.platform, arch: process.arch, pid: process.pid },
|
|
2432
|
+
storage: {
|
|
2433
|
+
path: storagePath,
|
|
2434
|
+
type: process.env.MEMORY_STORAGE_TYPE ?? 'jsonl',
|
|
2435
|
+
exists,
|
|
2436
|
+
sizeBytes,
|
|
2437
|
+
entities: stats.totalEntities,
|
|
2438
|
+
relations: stats.totalRelations,
|
|
2439
|
+
},
|
|
2440
|
+
loadedAt: new Date().toISOString(),
|
|
2441
|
+
});
|
|
2442
|
+
},
|
|
2443
|
+
health: async (ctx) => {
|
|
2444
|
+
const checks = [];
|
|
2445
|
+
const t1 = performance.now();
|
|
2446
|
+
let graph;
|
|
2447
|
+
try {
|
|
2448
|
+
graph = await ctx.storage.loadGraph();
|
|
2449
|
+
checks.push({ name: 'storage:loadGraph', ok: true, durationMs: performance.now() - t1 });
|
|
2450
|
+
}
|
|
2451
|
+
catch (e) {
|
|
2452
|
+
checks.push({
|
|
2453
|
+
name: 'storage:loadGraph',
|
|
2454
|
+
ok: false,
|
|
2455
|
+
durationMs: performance.now() - t1,
|
|
2456
|
+
detail: e instanceof Error ? e.message : String(e),
|
|
2457
|
+
});
|
|
2458
|
+
return formatToolResponse({ ok: false, failed: 1, totalChecks: 1, totalMs: performance.now() - t1, checks });
|
|
2459
|
+
}
|
|
2460
|
+
const t2 = performance.now();
|
|
2461
|
+
const names = new Set();
|
|
2462
|
+
const dupes = [];
|
|
2463
|
+
for (const e of graph.entities) {
|
|
2464
|
+
if (names.has(e.name))
|
|
2465
|
+
dupes.push(e.name);
|
|
2466
|
+
else
|
|
2467
|
+
names.add(e.name);
|
|
2468
|
+
}
|
|
2469
|
+
checks.push({
|
|
2470
|
+
name: 'entities:distinct-names',
|
|
2471
|
+
ok: dupes.length === 0,
|
|
2472
|
+
durationMs: performance.now() - t2,
|
|
2473
|
+
detail: dupes.length === 0 ? undefined : `${dupes.length} duplicate(s)`,
|
|
2474
|
+
});
|
|
2475
|
+
const t3 = performance.now();
|
|
2476
|
+
const orphans = [];
|
|
2477
|
+
for (const r of graph.relations) {
|
|
2478
|
+
if (!names.has(r.from) || !names.has(r.to)) {
|
|
2479
|
+
orphans.push(`${r.from} → ${r.to}`);
|
|
2480
|
+
}
|
|
2481
|
+
}
|
|
2482
|
+
checks.push({
|
|
2483
|
+
name: 'relations:no-orphans',
|
|
2484
|
+
ok: orphans.length === 0,
|
|
2485
|
+
durationMs: performance.now() - t3,
|
|
2486
|
+
detail: orphans.length === 0 ? undefined : `${orphans.length} orphan(s)`,
|
|
2487
|
+
});
|
|
2488
|
+
const t4 = performance.now();
|
|
2489
|
+
const byName = new Map();
|
|
2490
|
+
for (const e of graph.entities)
|
|
2491
|
+
byName.set(e.name, e);
|
|
2492
|
+
const cycleIssues = [];
|
|
2493
|
+
for (const e of graph.entities) {
|
|
2494
|
+
if (!e.parentId)
|
|
2495
|
+
continue;
|
|
2496
|
+
if (!byName.has(e.parentId)) {
|
|
2497
|
+
cycleIssues.push(`${e.name}.parentId='${e.parentId}' missing`);
|
|
2498
|
+
continue;
|
|
2499
|
+
}
|
|
2500
|
+
const visited = new Set();
|
|
2501
|
+
let cur = byName.get(e.parentId);
|
|
2502
|
+
while (cur && cur.parentId) {
|
|
2503
|
+
if (visited.has(cur.name)) {
|
|
2504
|
+
cycleIssues.push(`cycle through ${cur.name}`);
|
|
2505
|
+
break;
|
|
2506
|
+
}
|
|
2507
|
+
visited.add(cur.name);
|
|
2508
|
+
cur = byName.get(cur.parentId);
|
|
2509
|
+
}
|
|
2510
|
+
}
|
|
2511
|
+
checks.push({
|
|
2512
|
+
name: 'hierarchy:no-cycles-no-missing-parents',
|
|
2513
|
+
ok: cycleIssues.length === 0,
|
|
2514
|
+
durationMs: performance.now() - t4,
|
|
2515
|
+
detail: cycleIssues.length === 0 ? undefined : cycleIssues.slice(0, 3).join('; '),
|
|
2516
|
+
});
|
|
2517
|
+
const failed = checks.filter((c) => !c.ok).length;
|
|
2518
|
+
return formatToolResponse({
|
|
2519
|
+
ok: failed === 0,
|
|
2520
|
+
failed,
|
|
2521
|
+
totalChecks: checks.length,
|
|
2522
|
+
totalMs: Number(checks.reduce((a, c) => a + c.durationMs, 0).toFixed(2)),
|
|
2523
|
+
checks,
|
|
2524
|
+
});
|
|
2525
|
+
},
|
|
2526
|
+
check_graph: async (ctx, args) => {
|
|
2527
|
+
const apply = args.apply === undefined
|
|
2528
|
+
? false
|
|
2529
|
+
: validateWithSchema(args.apply, z.boolean(), 'Invalid apply');
|
|
2530
|
+
const graph = await ctx.storage.loadGraph();
|
|
2531
|
+
const names = new Set(graph.entities.map((e) => e.name));
|
|
2532
|
+
const orphans = [];
|
|
2533
|
+
for (const r of graph.relations) {
|
|
2534
|
+
const fromMissing = !names.has(r.from);
|
|
2535
|
+
const toMissing = !names.has(r.to);
|
|
2536
|
+
if (fromMissing || toMissing) {
|
|
2537
|
+
orphans.push({
|
|
2538
|
+
from: r.from, to: r.to, relationType: r.relationType,
|
|
2539
|
+
reason: fromMissing && toMissing ? 'both-missing' : fromMissing ? 'from-missing' : 'to-missing',
|
|
2540
|
+
});
|
|
2541
|
+
}
|
|
2542
|
+
}
|
|
2543
|
+
const byName = new Map();
|
|
2544
|
+
for (const e of graph.entities)
|
|
2545
|
+
byName.set(e.name, { name: e.name, parentId: e.parentId });
|
|
2546
|
+
const missing = [];
|
|
2547
|
+
const cycles = [];
|
|
2548
|
+
for (const e of graph.entities) {
|
|
2549
|
+
if (!e.parentId)
|
|
2550
|
+
continue;
|
|
2551
|
+
if (!byName.has(e.parentId)) {
|
|
2552
|
+
missing.push({ entity: e.name, parentId: e.parentId });
|
|
2553
|
+
continue;
|
|
2554
|
+
}
|
|
2555
|
+
const visited = new Set([e.name]);
|
|
2556
|
+
let cur = byName.get(e.parentId);
|
|
2557
|
+
while (cur && cur.parentId) {
|
|
2558
|
+
if (visited.has(cur.name)) {
|
|
2559
|
+
cycles.push({ entityInCycle: e.name, cycleThrough: cur.name });
|
|
2560
|
+
break;
|
|
2561
|
+
}
|
|
2562
|
+
visited.add(cur.name);
|
|
2563
|
+
cur = byName.get(cur.parentId);
|
|
2564
|
+
}
|
|
2565
|
+
}
|
|
2566
|
+
const ok = orphans.length === 0 && missing.length === 0 && cycles.length === 0;
|
|
2567
|
+
const result = {
|
|
2568
|
+
ok, applied: apply, orphanRelations: orphans, missingParents: missing, hierarchyCycles: cycles,
|
|
2569
|
+
};
|
|
2570
|
+
if (apply && (orphans.length > 0 || missing.length > 0)) {
|
|
2571
|
+
let deleted = 0;
|
|
2572
|
+
let cleared = 0;
|
|
2573
|
+
if (orphans.length > 0) {
|
|
2574
|
+
await ctx.relationManager.deleteRelations(orphans.map((o) => ({ from: o.from, to: o.to, relationType: o.relationType })));
|
|
2575
|
+
deleted = orphans.length;
|
|
2576
|
+
}
|
|
2577
|
+
for (const m of missing) {
|
|
2578
|
+
try {
|
|
2579
|
+
await ctx.hierarchyManager.setEntityParent(m.entity, null);
|
|
2580
|
+
cleared += 1;
|
|
2581
|
+
}
|
|
2582
|
+
catch { /* skip; entity may have vanished */ }
|
|
2583
|
+
}
|
|
2584
|
+
result.actions = { orphanRelationsDeleted: deleted, missingParentsCleared: cleared };
|
|
2585
|
+
}
|
|
2586
|
+
return formatToolResponse(result);
|
|
2587
|
+
},
|
|
2588
|
+
reindex: async (ctx, args) => {
|
|
2589
|
+
const ranked = args.ranked === undefined
|
|
2590
|
+
? true
|
|
2591
|
+
: validateWithSchema(args.ranked, z.boolean(), 'Invalid ranked');
|
|
2592
|
+
const spell = args.spell === undefined
|
|
2593
|
+
? true
|
|
2594
|
+
: validateWithSchema(args.spell, z.boolean(), 'Invalid spell');
|
|
2595
|
+
if (!ranked && !spell) {
|
|
2596
|
+
return formatToolResponse({ ok: true, failed: 0, result: {} });
|
|
2597
|
+
}
|
|
2598
|
+
const result = {};
|
|
2599
|
+
if (ranked) {
|
|
2600
|
+
const t = performance.now();
|
|
2601
|
+
try {
|
|
2602
|
+
// Same workaround as the CLI: default ctx.rankedSearch is constructed
|
|
2603
|
+
// without a storageDir so buildIndex() refuses. Construct an ad-hoc one
|
|
2604
|
+
// alongside the JSONL.
|
|
2605
|
+
const storageDir = path.dirname(getStorageFilePath(ctx));
|
|
2606
|
+
const r = new RankedSearch(ctx.storage, storageDir);
|
|
2607
|
+
await r.buildIndex();
|
|
2608
|
+
result.ranked = { ok: true, durationMs: performance.now() - t };
|
|
2609
|
+
}
|
|
2610
|
+
catch (e) {
|
|
2611
|
+
result.ranked = {
|
|
2612
|
+
ok: false,
|
|
2613
|
+
durationMs: performance.now() - t,
|
|
2614
|
+
detail: e instanceof Error ? e.message : String(e),
|
|
2615
|
+
};
|
|
2616
|
+
}
|
|
2617
|
+
}
|
|
2618
|
+
if (spell) {
|
|
2619
|
+
const t = performance.now();
|
|
2620
|
+
try {
|
|
2621
|
+
await ctx.spellChecker.rebuild();
|
|
2622
|
+
result.spell = { ok: true, durationMs: performance.now() - t };
|
|
2623
|
+
}
|
|
2624
|
+
catch (e) {
|
|
2625
|
+
result.spell = {
|
|
2626
|
+
ok: false,
|
|
2627
|
+
durationMs: performance.now() - t,
|
|
2628
|
+
detail: e instanceof Error ? e.message : String(e),
|
|
2629
|
+
};
|
|
2630
|
+
}
|
|
2631
|
+
}
|
|
2632
|
+
const failed = Object.values(result).filter((r) => !r.ok).length;
|
|
2633
|
+
return formatToolResponse({ ok: failed === 0, failed, result });
|
|
2634
|
+
},
|
|
2635
|
+
cache_stats: async (_ctx) => {
|
|
2636
|
+
return formatToolResponse({ stats: getAllCacheStats() });
|
|
2637
|
+
},
|
|
2638
|
+
cache_clear: async (_ctx) => {
|
|
2639
|
+
clearAllSearchCaches();
|
|
2640
|
+
return formatToolResponse({ cleared: true, caches: ['basic', 'ranked', 'boolean', 'fuzzy'] });
|
|
2641
|
+
},
|
|
2642
|
+
graph_size: async (ctx) => {
|
|
2643
|
+
const storagePath = getStorageFilePath(ctx);
|
|
2644
|
+
const graph = await ctx.storage.loadGraph();
|
|
2645
|
+
let observationCount = 0;
|
|
2646
|
+
const tagSet = new Set();
|
|
2647
|
+
for (const e of graph.entities) {
|
|
2648
|
+
observationCount += e.observations.length;
|
|
2649
|
+
if (e.tags)
|
|
2650
|
+
for (const t of e.tags)
|
|
2651
|
+
tagSet.add(t);
|
|
2652
|
+
}
|
|
2653
|
+
let exists = false;
|
|
2654
|
+
let sizeBytes = 0;
|
|
2655
|
+
let lineCount = 0;
|
|
2656
|
+
try {
|
|
2657
|
+
const stat = await fs.stat(storagePath);
|
|
2658
|
+
exists = true;
|
|
2659
|
+
sizeBytes = stat.size;
|
|
2660
|
+
if (sizeBytes > 0) {
|
|
2661
|
+
const content = await fs.readFile(storagePath, 'utf8');
|
|
2662
|
+
lineCount = content.split('\n').filter((l) => l.length > 0).length;
|
|
2663
|
+
}
|
|
2664
|
+
}
|
|
2665
|
+
catch { /* file may not exist */ }
|
|
2666
|
+
return formatToolResponse({
|
|
2667
|
+
graph: {
|
|
2668
|
+
entities: graph.entities.length,
|
|
2669
|
+
relations: graph.relations.length,
|
|
2670
|
+
observations: observationCount,
|
|
2671
|
+
distinctTags: tagSet.size,
|
|
2672
|
+
avgObservationsPerEntity: graph.entities.length === 0
|
|
2673
|
+
? 0
|
|
2674
|
+
: Number((observationCount / graph.entities.length).toFixed(2)),
|
|
2675
|
+
},
|
|
2676
|
+
storage: { path: storagePath, exists, sizeBytes, lineCount },
|
|
2677
|
+
});
|
|
2678
|
+
},
|
|
2679
|
+
inspect_entity: async (ctx, args) => {
|
|
2680
|
+
const name = validateWithSchema(args.name, z.string().min(1), 'Invalid name');
|
|
2681
|
+
// getEntityByName reads an in-memory nameIndex hydrated by loadGraph.
|
|
2682
|
+
const graph = await ctx.storage.loadGraph();
|
|
2683
|
+
const entity = ctx.storage.getEntityByName(name);
|
|
2684
|
+
if (!entity)
|
|
2685
|
+
throw new Error(`entity not found: ${name}`);
|
|
2686
|
+
const observations = await ctx.observationManager.getObservationsFor(name);
|
|
2687
|
+
const outgoing = graph.relations
|
|
2688
|
+
.filter((r) => r.from === name)
|
|
2689
|
+
.map((r) => ({ to: r.to, type: r.relationType }));
|
|
2690
|
+
const incoming = graph.relations
|
|
2691
|
+
.filter((r) => r.to === name)
|
|
2692
|
+
.map((r) => ({ from: r.from, type: r.relationType }));
|
|
2693
|
+
const children = (await ctx.hierarchyManager.getChildren(name)).map((c) => c.name);
|
|
2694
|
+
const ancestors = (await ctx.hierarchyManager.getAncestors(name)).map((a) => a.name);
|
|
2695
|
+
return formatToolResponse({
|
|
2696
|
+
name: entity.name,
|
|
2697
|
+
entityType: entity.entityType,
|
|
2698
|
+
observations,
|
|
2699
|
+
tags: entity.tags,
|
|
2700
|
+
importance: entity.importance,
|
|
2701
|
+
createdAt: entity.createdAt,
|
|
2702
|
+
lastModified: entity.lastModified,
|
|
2703
|
+
parentId: entity.parentId,
|
|
2704
|
+
children,
|
|
2705
|
+
ancestors,
|
|
2706
|
+
relations: { outgoing, incoming },
|
|
2707
|
+
});
|
|
2708
|
+
},
|
|
2709
|
+
hierarchy_tree: async (ctx, args) => {
|
|
2710
|
+
const root = args.root === undefined
|
|
2711
|
+
? undefined
|
|
2712
|
+
: validateWithSchema(args.root, z.string().min(1), 'Invalid root');
|
|
2713
|
+
await ctx.storage.loadGraph();
|
|
2714
|
+
async function walk(n) {
|
|
2715
|
+
const e = ctx.storage.getEntityByName(n);
|
|
2716
|
+
const kids = await ctx.hierarchyManager.getChildren(n);
|
|
2717
|
+
const childNodes = [];
|
|
2718
|
+
for (const c of kids)
|
|
2719
|
+
childNodes.push(await walk(c.name));
|
|
2720
|
+
return { name: n, entityType: e?.entityType ?? 'unknown', children: childNodes };
|
|
2721
|
+
}
|
|
2722
|
+
if (root) {
|
|
2723
|
+
const e = ctx.storage.getEntityByName(root);
|
|
2724
|
+
if (!e)
|
|
2725
|
+
throw new Error(`entity not found: ${root}`);
|
|
2726
|
+
return formatToolResponse({ trees: [await walk(root)] });
|
|
2727
|
+
}
|
|
2728
|
+
const roots = await ctx.hierarchyManager.getRootEntities();
|
|
2729
|
+
const trees = await Promise.all(roots.map((r) => walk(r.name)));
|
|
2730
|
+
return formatToolResponse({ trees });
|
|
2731
|
+
},
|
|
2732
|
+
entity_neighbors: async (ctx, args) => {
|
|
2733
|
+
const name = validateWithSchema(args.name, z.string().min(1), 'Invalid name');
|
|
2734
|
+
const graph = await ctx.storage.loadGraph();
|
|
2735
|
+
const entity = ctx.storage.getEntityByName(name);
|
|
2736
|
+
if (!entity)
|
|
2737
|
+
throw new Error(`entity not found: ${name}`);
|
|
2738
|
+
const outgoing = graph.relations.filter((r) => r.from === name).map((r) => ({ to: r.to, type: r.relationType }));
|
|
2739
|
+
const incoming = graph.relations.filter((r) => r.to === name).map((r) => ({ from: r.from, type: r.relationType }));
|
|
2740
|
+
return formatToolResponse({
|
|
2741
|
+
entity: name,
|
|
2742
|
+
outgoing,
|
|
2743
|
+
incoming,
|
|
2744
|
+
outDegree: outgoing.length,
|
|
2745
|
+
inDegree: incoming.length,
|
|
2746
|
+
});
|
|
2747
|
+
},
|
|
1900
2748
|
};
|
|
1901
2749
|
/**
|
|
1902
2750
|
* Handle a tool call by dispatching to the appropriate handler.
|