@danielsimonjr/memory-mcp 12.2.3 → 12.7.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/README.md +235 -105
- package/dist/index.js +12 -0
- package/dist/server/MCPServer.d.ts.map +1 -1
- package/dist/server/toolDefinitions.d.ts +1 -1
- package/dist/server/toolDefinitions.d.ts.map +1 -1
- package/dist/server/toolDefinitions.js +999 -5
- package/dist/server/toolHandlers.d.ts +1 -9
- package/dist/server/toolHandlers.d.ts.map +1 -1
- package/dist/server/toolHandlers.js +1099 -35
- package/package.json +12 -9
|
@@ -8,15 +8,15 @@
|
|
|
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,
|
|
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, FreshnessManager, ArtifactManager, CollaborativeSynthesis, FailureDistillation, CognitiveLoadAnalyzer, ConsolidationScheduler, DreamEngine, DistillationPipeline, DefaultDistillationPolicy, NoOpDistillationPolicy, computeEntropy, passesEntropyFilter, EntropyFilterStage, getRoleProfile, listRoleProfiles, QueryCostEstimator, ContradictionDetector, PiiRedactor, DecisionManager, RankedSearch, RelationConsolidator, 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 ====================
|
|
15
17
|
// WeakMap-based singletons keyed on ManagerContext to avoid re-instantiation per request.
|
|
16
18
|
// These managers are not on ManagerContext directly, so we wire them up once per ctx.
|
|
17
19
|
const refIndexMap = new WeakMap();
|
|
18
|
-
const auditLogMap = new WeakMap();
|
|
19
|
-
const governanceMap = new WeakMap();
|
|
20
20
|
const freshnessMap = new WeakMap();
|
|
21
21
|
const artifactManagerMap = new WeakMap();
|
|
22
22
|
const distillationPipelineMap = new WeakMap();
|
|
@@ -24,9 +24,29 @@ const failureDistillationMap = new WeakMap();
|
|
|
24
24
|
const consolidationSchedulerMap = new WeakMap();
|
|
25
25
|
const dreamEngineMap = new WeakMap();
|
|
26
26
|
const queryCostEstimatorMap = new WeakMap();
|
|
27
|
+
// Active project scope (Phase 13 deferred item; ctx.defaultProjectId is readonly,
|
|
28
|
+
// so we keep mutable scope state in a WeakMap keyed on the context. Set via the
|
|
29
|
+
// `set_project_scope` tool; handlers may consult `getActiveProjectScope(ctx)`
|
|
30
|
+
// when they want to auto-apply the scope.
|
|
31
|
+
const projectScopeMap = new WeakMap();
|
|
32
|
+
export function getActiveProjectScope(ctx) {
|
|
33
|
+
return projectScopeMap.get(ctx);
|
|
34
|
+
}
|
|
27
35
|
function getStorageFilePath(ctx) {
|
|
28
|
-
// GraphStorage
|
|
29
|
-
|
|
36
|
+
// GraphStorage stores the path on `memoryFilePath` (private but reachable via
|
|
37
|
+
// type-cast). Older versions of this helper looked for `filePath` and
|
|
38
|
+
// silently fell back to 'memory.jsonl' — which made diag / size / reindex
|
|
39
|
+
// reports point at the wrong file. Try both field names defensively.
|
|
40
|
+
const storage = ctx.storage;
|
|
41
|
+
return storage.memoryFilePath ?? storage.filePath ?? 'memory.jsonl';
|
|
42
|
+
}
|
|
43
|
+
// Sidecar path for the serialized Cue–Tag–Content graph, following the
|
|
44
|
+
// library's `<basename>-<suffix>` convention (memory.jsonl → memory-reconstructive.json).
|
|
45
|
+
function reconstructiveSidecarPath(ctx) {
|
|
46
|
+
const storagePath = getStorageFilePath(ctx);
|
|
47
|
+
const ext = path.extname(storagePath);
|
|
48
|
+
const base = ext ? storagePath.slice(0, -ext.length) : storagePath;
|
|
49
|
+
return `${base}-reconstructive.json`;
|
|
30
50
|
}
|
|
31
51
|
function getRefIndex(ctx) {
|
|
32
52
|
if (!refIndexMap.has(ctx)) {
|
|
@@ -36,22 +56,6 @@ function getRefIndex(ctx) {
|
|
|
36
56
|
}
|
|
37
57
|
return refIndexMap.get(ctx);
|
|
38
58
|
}
|
|
39
|
-
function getAuditLog(ctx) {
|
|
40
|
-
if (!auditLogMap.has(ctx)) {
|
|
41
|
-
const storagePath = getStorageFilePath(ctx);
|
|
42
|
-
const dir = path.dirname(storagePath);
|
|
43
|
-
auditLogMap.set(ctx, new AuditLog(path.join(dir, 'memory-audit.jsonl')));
|
|
44
|
-
}
|
|
45
|
-
return auditLogMap.get(ctx);
|
|
46
|
-
}
|
|
47
|
-
function getGovernanceManager(ctx) {
|
|
48
|
-
if (!governanceMap.has(ctx)) {
|
|
49
|
-
// GovernanceManager constructor accepts GraphStorage; ctx.storage is GraphStorage
|
|
50
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
51
|
-
governanceMap.set(ctx, new GovernanceManager(ctx.storage, getAuditLog(ctx)));
|
|
52
|
-
}
|
|
53
|
-
return governanceMap.get(ctx);
|
|
54
|
-
}
|
|
55
59
|
function getFreshnessManager(ctx) {
|
|
56
60
|
if (!freshnessMap.has(ctx)) {
|
|
57
61
|
freshnessMap.set(ctx, new FreshnessManager(ctx.storage));
|
|
@@ -125,11 +129,43 @@ async function withCompression(handler) {
|
|
|
125
129
|
* Filtered/limited search tools (search_nodes_ranked, fuzzy_search, etc.) are not
|
|
126
130
|
* wrapped because their results are bounded by query specificity or limit params.
|
|
127
131
|
*/
|
|
132
|
+
/**
|
|
133
|
+
* v2.1.0 — Parse `find_duplicate_observations` / `find_jaccard_duplicate_observations`
|
|
134
|
+
* filter args into the manager's `ObservationDedupFilter` shape. Validates each
|
|
135
|
+
* field independently so a stray garbage value falls back to the manager default
|
|
136
|
+
* rather than throwing — REST-like leniency since these are diagnostic tools.
|
|
137
|
+
*/
|
|
138
|
+
function parseObservationDedupFilter(args) {
|
|
139
|
+
const out = {};
|
|
140
|
+
if (typeof args.entityType === 'string') {
|
|
141
|
+
out.entityType = args.entityType;
|
|
142
|
+
}
|
|
143
|
+
else if (Array.isArray(args.entityType) && args.entityType.every((s) => typeof s === 'string')) {
|
|
144
|
+
out.entityType = args.entityType;
|
|
145
|
+
}
|
|
146
|
+
if (typeof args.projectId === 'string')
|
|
147
|
+
out.projectId = args.projectId;
|
|
148
|
+
if (typeof args.sessionId === 'string')
|
|
149
|
+
out.sessionId = args.sessionId;
|
|
150
|
+
if (typeof args.minOccurrences === 'number' && Number.isFinite(args.minOccurrences) && args.minOccurrences >= 2) {
|
|
151
|
+
out.minOccurrences = args.minOccurrences;
|
|
152
|
+
}
|
|
153
|
+
if (typeof args.maxGroups === 'number' && Number.isFinite(args.maxGroups) && args.maxGroups >= 1) {
|
|
154
|
+
out.maxGroups = args.maxGroups;
|
|
155
|
+
}
|
|
156
|
+
return out;
|
|
157
|
+
}
|
|
128
158
|
export const toolHandlers = {
|
|
129
159
|
// ==================== ENTITY HANDLERS ====================
|
|
130
160
|
create_entities: async (ctx, args) => {
|
|
131
161
|
const entities = validateWithSchema(args.entities, BatchCreateEntitiesSchema, 'Invalid entities data');
|
|
132
|
-
|
|
162
|
+
// Phase 13 (v12.4.0): auto-stamp active project scope on entities lacking
|
|
163
|
+
// an explicit projectId. Set via set_project_scope; explicit projectId wins.
|
|
164
|
+
const activeScope = getActiveProjectScope(ctx);
|
|
165
|
+
const scoped = activeScope
|
|
166
|
+
? entities.map((e) => (e.projectId === undefined ? { ...e, projectId: activeScope } : e))
|
|
167
|
+
: entities;
|
|
168
|
+
return formatToolResponse(await ctx.entityManager.createEntities(scoped));
|
|
133
169
|
},
|
|
134
170
|
delete_entities: async (ctx, args) => {
|
|
135
171
|
const entityNames = validateWithSchema(args.entityNames, EntityNamesSchema, 'Invalid entity names');
|
|
@@ -148,6 +184,19 @@ export const toolHandlers = {
|
|
|
148
184
|
const projects = await ctx.entityManager.listProjects();
|
|
149
185
|
return formatToolResponse({ projects, count: projects.length });
|
|
150
186
|
},
|
|
187
|
+
set_project_scope: async (ctx, args) => {
|
|
188
|
+
const projectId = validateWithSchema(args.projectId, z.string(), 'Invalid projectId');
|
|
189
|
+
if (projectId === '') {
|
|
190
|
+
projectScopeMap.delete(ctx);
|
|
191
|
+
return formatToolResponse({ projectId: null });
|
|
192
|
+
}
|
|
193
|
+
projectScopeMap.set(ctx, projectId);
|
|
194
|
+
return formatToolResponse({ projectId });
|
|
195
|
+
},
|
|
196
|
+
get_project_scope: async (ctx, _args) => {
|
|
197
|
+
const projectId = projectScopeMap.get(ctx) ?? null;
|
|
198
|
+
return formatToolResponse({ projectId });
|
|
199
|
+
},
|
|
151
200
|
get_entity_versions: async (ctx, args) => {
|
|
152
201
|
const entityName = validateWithSchema(args.entityName, z.string().min(1), 'Invalid entityName');
|
|
153
202
|
const latest = await ctx.entityManager.getLatestVersion(entityName);
|
|
@@ -242,10 +291,20 @@ export const toolHandlers = {
|
|
|
242
291
|
const entityName = args.entityName !== undefined
|
|
243
292
|
? validateWithSchema(args.entityName, z.string().min(1), 'Invalid entity name')
|
|
244
293
|
: undefined;
|
|
245
|
-
const options = args.options
|
|
294
|
+
const options = args.options !== undefined
|
|
295
|
+
? validateWithSchema(args.options, z.object({
|
|
296
|
+
resolveCoreferences: z.boolean().optional(),
|
|
297
|
+
anchorTimestamps: z.boolean().optional(),
|
|
298
|
+
extractKeywords: z.boolean().optional(),
|
|
299
|
+
}).strict(), 'Invalid options')
|
|
300
|
+
: {};
|
|
246
301
|
const persist = args.persist === true;
|
|
247
302
|
const normalizer = new ObservationNormalizer();
|
|
248
|
-
|
|
303
|
+
// Use getGraphForMutation() to acquire the storage write lock — prevents
|
|
304
|
+
// concurrent calls from overwriting each other's persisted normalizations.
|
|
305
|
+
const graph = persist
|
|
306
|
+
? await ctx.storage.getGraphForMutation()
|
|
307
|
+
: await ctx.storage.loadGraph();
|
|
249
308
|
const entities = entityName
|
|
250
309
|
? graph.entities.filter(e => e.name === entityName)
|
|
251
310
|
: graph.entities;
|
|
@@ -350,12 +409,52 @@ export const toolHandlers = {
|
|
|
350
409
|
// Phase 11 Sprint 2: Hybrid search
|
|
351
410
|
hybrid_search: async (ctx, args) => {
|
|
352
411
|
const query = validateWithSchema(args.query, SearchQuerySchema, 'Invalid search query');
|
|
353
|
-
const weights = args.weights
|
|
354
|
-
|
|
412
|
+
const weights = args.weights !== undefined
|
|
413
|
+
? validateWithSchema(args.weights, z.object({
|
|
414
|
+
semantic: z.number().optional(),
|
|
415
|
+
lexical: z.number().optional(),
|
|
416
|
+
symbolic: z.number().optional(),
|
|
417
|
+
}).strict(), 'Invalid weights')
|
|
418
|
+
: undefined;
|
|
419
|
+
const filters = args.filters !== undefined
|
|
420
|
+
? validateWithSchema(args.filters, z.object({
|
|
421
|
+
tags: z.array(z.string()).optional(),
|
|
422
|
+
entityTypes: z.array(z.string()).optional(),
|
|
423
|
+
dateRange: z.object({ start: z.string(), end: z.string() }).strict().optional(),
|
|
424
|
+
minImportance: z.number().optional(),
|
|
425
|
+
maxImportance: z.number().optional(),
|
|
426
|
+
}).strict(), 'Invalid filters')
|
|
427
|
+
: undefined;
|
|
355
428
|
const limit = args.limit !== undefined
|
|
356
429
|
? validateWithSchema(args.limit, z.number().int().positive().max(200), 'Invalid limit')
|
|
357
430
|
: 10;
|
|
358
|
-
const
|
|
431
|
+
const graphWeight = args.graphWeight !== undefined
|
|
432
|
+
? validateWithSchema(args.graphWeight, z.number().min(0).max(1), 'Invalid graphWeight')
|
|
433
|
+
: undefined;
|
|
434
|
+
const expandNeighborsArgs = args.expandNeighbors !== undefined
|
|
435
|
+
? validateWithSchema(args.expandNeighbors, z.object({
|
|
436
|
+
topK: z.number().int().positive().max(100).optional(),
|
|
437
|
+
damping: z.number().min(0).max(1).optional(),
|
|
438
|
+
}).strict(), 'Invalid expandNeighbors')
|
|
439
|
+
: undefined;
|
|
440
|
+
// Only 1-hop expansion is supported by memoryjs, so `hops` is fixed here
|
|
441
|
+
// rather than exposed in the tool schema.
|
|
442
|
+
const expandNeighbors = expandNeighborsArgs !== undefined
|
|
443
|
+
? { hops: 1, ...expandNeighborsArgs }
|
|
444
|
+
: undefined;
|
|
445
|
+
const explain = args.explain !== undefined
|
|
446
|
+
? validateWithSchema(args.explain, z.boolean(), 'Invalid explain flag')
|
|
447
|
+
: undefined;
|
|
448
|
+
const lookFor = args.lookFor !== undefined
|
|
449
|
+
? validateWithSchema(args.lookFor, z.string().min(1), 'Invalid lookFor')
|
|
450
|
+
: undefined;
|
|
451
|
+
// ctx.hybridSearchManager only attaches the GraphRankPrior when
|
|
452
|
+
// MEMORY_HYBRID_GRAPH_WEIGHT is set; per-call graph options need the prior
|
|
453
|
+
// wired explicitly or they would be silently inert.
|
|
454
|
+
const wantsGraphChannel = (graphWeight !== undefined && graphWeight > 0) || expandNeighbors !== undefined;
|
|
455
|
+
const hybridSearch = wantsGraphChannel
|
|
456
|
+
? new HybridSearchManager(ctx.semanticSearch, ctx.rankedSearch, ctx.graphRankPrior)
|
|
457
|
+
: ctx.hybridSearchManager;
|
|
359
458
|
const graph = await ctx.storage.loadGraph();
|
|
360
459
|
const results = await hybridSearch.searchWithEntities(graph, query, {
|
|
361
460
|
semanticWeight: weights?.semantic ?? 0.5,
|
|
@@ -373,6 +472,10 @@ export const toolHandlers = {
|
|
|
373
472
|
}
|
|
374
473
|
: undefined,
|
|
375
474
|
limit,
|
|
475
|
+
graphWeight,
|
|
476
|
+
expandNeighbors,
|
|
477
|
+
explain,
|
|
478
|
+
lookFor,
|
|
376
479
|
});
|
|
377
480
|
return formatToolResponse({
|
|
378
481
|
query,
|
|
@@ -380,6 +483,7 @@ export const toolHandlers = {
|
|
|
380
483
|
semantic: weights?.semantic ?? 0.5,
|
|
381
484
|
lexical: weights?.lexical ?? 0.3,
|
|
382
485
|
symbolic: weights?.symbolic ?? 0.2,
|
|
486
|
+
...(graphWeight !== undefined ? { graph: graphWeight } : {}),
|
|
383
487
|
},
|
|
384
488
|
resultCount: results.length,
|
|
385
489
|
results: results.map((r) => ({
|
|
@@ -389,6 +493,9 @@ export const toolHandlers = {
|
|
|
389
493
|
matchedLayers: r.matchedLayers,
|
|
390
494
|
observations: r.entity.observations.slice(0, 3),
|
|
391
495
|
tags: r.entity.tags,
|
|
496
|
+
...(r.evidencePaths !== undefined ? { evidencePaths: r.evidencePaths } : {}),
|
|
497
|
+
...(r.evidenceTruncated !== undefined ? { evidenceTruncated: r.evidenceTruncated } : {}),
|
|
498
|
+
...(r.lookForScore !== undefined ? { lookForScore: r.lookForScore } : {}),
|
|
392
499
|
})),
|
|
393
500
|
});
|
|
394
501
|
},
|
|
@@ -429,7 +536,7 @@ export const toolHandlers = {
|
|
|
429
536
|
const planner = new QueryPlanner();
|
|
430
537
|
plan = planner.createPlan(query, analysis);
|
|
431
538
|
}
|
|
432
|
-
const hybridSearch =
|
|
539
|
+
const hybridSearch = ctx.hybridSearchManager;
|
|
433
540
|
const reflection = new ReflectionManager(hybridSearch, analyzer);
|
|
434
541
|
const graph = await ctx.storage.loadGraph();
|
|
435
542
|
const result = await reflection.retrieveWithReflection(graph, query, {
|
|
@@ -863,7 +970,7 @@ export const toolHandlers = {
|
|
|
863
970
|
const entityName = args.entityName !== undefined
|
|
864
971
|
? validateWithSchema(args.entityName, z.string().min(1), 'Invalid entityName')
|
|
865
972
|
: undefined;
|
|
866
|
-
const refs = await getRefIndex(ctx).listRefs(entityName
|
|
973
|
+
const refs = await getRefIndex(ctx).listRefs(entityName);
|
|
867
974
|
return formatToolResponse({ refs, count: refs.length });
|
|
868
975
|
},
|
|
869
976
|
// ==================== ARTIFACT HANDLERS ====================
|
|
@@ -993,7 +1100,7 @@ export const toolHandlers = {
|
|
|
993
1100
|
},
|
|
994
1101
|
// ==================== GOVERNANCE HANDLERS ====================
|
|
995
1102
|
set_governance_policy: async (ctx, args) => {
|
|
996
|
-
const gm =
|
|
1103
|
+
const gm = ctx.governanceManager;
|
|
997
1104
|
// GovernancePolicy uses function callbacks; we translate boolean args into allow/deny functions
|
|
998
1105
|
const allowCreate = args.canCreate !== undefined
|
|
999
1106
|
? validateWithSchema(args.canCreate, z.boolean(), 'Invalid canCreate')
|
|
@@ -1032,20 +1139,20 @@ export const toolHandlers = {
|
|
|
1032
1139
|
const limit = args.limit !== undefined
|
|
1033
1140
|
? validateWithSchema(args.limit, z.number().int().min(1).max(1000), 'Invalid limit')
|
|
1034
1141
|
: 50;
|
|
1035
|
-
const al =
|
|
1142
|
+
const al = ctx.governanceManager.auditLog;
|
|
1036
1143
|
let entries = await al.query(filter);
|
|
1037
1144
|
entries = entries.slice(0, limit);
|
|
1038
1145
|
return formatToolResponse({ entries, count: entries.length });
|
|
1039
1146
|
},
|
|
1040
1147
|
audit_history: async (ctx, args) => {
|
|
1041
1148
|
const entityName = validateWithSchema(args.entityName, z.string().min(1), 'Invalid entityName');
|
|
1042
|
-
const al =
|
|
1149
|
+
const al = ctx.governanceManager.auditLog;
|
|
1043
1150
|
const entries = await al.getHistory(entityName);
|
|
1044
1151
|
return formatToolResponse({ entityName, entries, count: entries.length });
|
|
1045
1152
|
},
|
|
1046
1153
|
rollback_operation: async (ctx, args) => {
|
|
1047
1154
|
const auditEntryId = validateWithSchema(args.auditEntryId, z.string().min(1), 'Invalid auditEntryId');
|
|
1048
|
-
const gm =
|
|
1155
|
+
const gm = ctx.governanceManager;
|
|
1049
1156
|
await gm.rollback(auditEntryId);
|
|
1050
1157
|
return formatTextResponse(`Operation "${auditEntryId}" rolled back successfully`);
|
|
1051
1158
|
},
|
|
@@ -1274,7 +1381,9 @@ export const toolHandlers = {
|
|
|
1274
1381
|
const distillFailures = args.distillFailures !== undefined
|
|
1275
1382
|
? validateWithSchema(args.distillFailures, z.boolean(), 'Invalid distillFailures')
|
|
1276
1383
|
: true;
|
|
1277
|
-
|
|
1384
|
+
// Use getGraphForMutation() to acquire the storage write lock — prevents
|
|
1385
|
+
// concurrent end_session calls from clobbering each other's outcome writes.
|
|
1386
|
+
const graph = await ctx.storage.getGraphForMutation();
|
|
1278
1387
|
const sessionEntity = graph.entities.find(e => e.name === sessionId);
|
|
1279
1388
|
if (!sessionEntity) {
|
|
1280
1389
|
return formatTextResponse(`Session "${sessionId}" not found`);
|
|
@@ -1718,7 +1827,7 @@ export const toolHandlers = {
|
|
|
1718
1827
|
// ==================== η.5.5.c OCC HANDLER ====================
|
|
1719
1828
|
update_entity: async (ctx, args) => {
|
|
1720
1829
|
const name = validateWithSchema(args.name, z.string().min(1), 'Invalid name');
|
|
1721
|
-
const updates = validateWithSchema(args.updates, z.record(z.unknown()), 'Invalid updates');
|
|
1830
|
+
const updates = validateWithSchema(args.updates, z.record(z.string(), z.unknown()), 'Invalid updates');
|
|
1722
1831
|
const expectedVersion = args.expectedVersion !== undefined
|
|
1723
1832
|
? validateWithSchema(args.expectedVersion, z.number().int().positive(), 'Invalid expectedVersion')
|
|
1724
1833
|
: undefined;
|
|
@@ -1897,6 +2006,961 @@ export const toolHandlers = {
|
|
|
1897
2006
|
const chains = await ctx.worldModelManager.predictOutcome(action, candidates);
|
|
1898
2007
|
return formatToolResponse({ action, candidates, chains, count: chains.length });
|
|
1899
2008
|
},
|
|
2009
|
+
// ==================== v2.1.0 TOOL AFFORDANCE ====================
|
|
2010
|
+
record_tool_outcome: async (ctx, args) => {
|
|
2011
|
+
const toolName = validateWithSchema(args.toolName, z.string().min(1), 'Invalid toolName');
|
|
2012
|
+
const outcome = validateWithSchema(args.outcome, z.enum(['success', 'failure', 'partial']), 'Invalid outcome');
|
|
2013
|
+
const errorMessage = args.errorMessage === undefined
|
|
2014
|
+
? undefined
|
|
2015
|
+
: validateWithSchema(args.errorMessage, z.string(), 'Invalid errorMessage');
|
|
2016
|
+
const durationMs = args.durationMs === undefined
|
|
2017
|
+
? undefined
|
|
2018
|
+
: validateWithSchema(args.durationMs, z.number().min(0), 'Invalid durationMs');
|
|
2019
|
+
const record = await ctx.toolAffordanceManager.recordOutcome(toolName, {
|
|
2020
|
+
outcome,
|
|
2021
|
+
errorMessage,
|
|
2022
|
+
durationMs,
|
|
2023
|
+
});
|
|
2024
|
+
return formatToolResponse({ record });
|
|
2025
|
+
},
|
|
2026
|
+
get_tool_affordance_stats: async (ctx, args) => {
|
|
2027
|
+
const toolName = validateWithSchema(args.toolName, z.string().min(1), 'Invalid toolName');
|
|
2028
|
+
const stats = ctx.toolAffordanceManager.rollingStats(toolName);
|
|
2029
|
+
return formatToolResponse({ toolName, stats: stats ?? null });
|
|
2030
|
+
},
|
|
2031
|
+
suggest_tool: async (ctx, args) => {
|
|
2032
|
+
const taskHint = validateWithSchema(args.taskHint, z.string().min(1), 'Invalid taskHint');
|
|
2033
|
+
const limit = args.limit === undefined
|
|
2034
|
+
? undefined
|
|
2035
|
+
: validateWithSchema(args.limit, z.number().int().min(1), 'Invalid limit');
|
|
2036
|
+
const minScore = args.minScore === undefined
|
|
2037
|
+
? undefined
|
|
2038
|
+
: validateWithSchema(args.minScore, z.number().min(0).max(1), 'Invalid minScore');
|
|
2039
|
+
const suggestions = await ctx.toolAffordanceManager.suggestTool(taskHint, { limit, minScore });
|
|
2040
|
+
return formatToolResponse({ taskHint, suggestions, count: suggestions.length });
|
|
2041
|
+
},
|
|
2042
|
+
list_tool_affordances: async (ctx) => {
|
|
2043
|
+
const records = await ctx.toolAffordanceManager.list();
|
|
2044
|
+
return formatToolResponse({ records, count: records.length });
|
|
2045
|
+
},
|
|
2046
|
+
remove_tool_affordance: async (ctx, args) => {
|
|
2047
|
+
const toolName = validateWithSchema(args.toolName, z.string().min(1), 'Invalid toolName');
|
|
2048
|
+
const removed = await ctx.toolAffordanceManager.remove(toolName);
|
|
2049
|
+
return formatToolResponse({ toolName, removed });
|
|
2050
|
+
},
|
|
2051
|
+
observe_tool_start: (ctx, args) => {
|
|
2052
|
+
const toolName = validateWithSchema(args.toolName, z.string().min(1), 'Invalid toolName');
|
|
2053
|
+
const argsField = args.args === undefined
|
|
2054
|
+
? undefined
|
|
2055
|
+
: validateWithSchema(args.args, z.record(z.string(), z.unknown()), 'Invalid args (must be an object)');
|
|
2056
|
+
const callId = ctx.toolCallObserver.observeStart(toolName, argsField);
|
|
2057
|
+
return Promise.resolve(formatToolResponse({ callId, toolName }));
|
|
2058
|
+
},
|
|
2059
|
+
observe_tool_complete: async (ctx, args) => {
|
|
2060
|
+
const callId = validateWithSchema(args.callId, z.string().min(1), 'Invalid callId');
|
|
2061
|
+
const result = args.result === undefined
|
|
2062
|
+
? undefined
|
|
2063
|
+
: validateWithSchema(args.result, z.string(), 'Invalid result');
|
|
2064
|
+
await ctx.toolCallObserver.observeComplete(callId, result === undefined ? undefined : { result });
|
|
2065
|
+
return formatToolResponse({ callId, recorded: 'success' });
|
|
2066
|
+
},
|
|
2067
|
+
observe_tool_error: async (ctx, args) => {
|
|
2068
|
+
const callId = validateWithSchema(args.callId, z.string().min(1), 'Invalid callId');
|
|
2069
|
+
const errorMessage = validateWithSchema(args.errorMessage, z.string().min(1), 'Invalid errorMessage');
|
|
2070
|
+
await ctx.toolCallObserver.observeError(callId, errorMessage);
|
|
2071
|
+
return formatToolResponse({ callId, recorded: 'failure', errorMessage });
|
|
2072
|
+
},
|
|
2073
|
+
observe_tool_partial: async (ctx, args) => {
|
|
2074
|
+
const callId = validateWithSchema(args.callId, z.string().min(1), 'Invalid callId');
|
|
2075
|
+
const reason = validateWithSchema(args.reason, z.string().min(1), 'Invalid reason');
|
|
2076
|
+
await ctx.toolCallObserver.observePartial(callId, reason);
|
|
2077
|
+
return formatToolResponse({ callId, recorded: 'partial', reason });
|
|
2078
|
+
},
|
|
2079
|
+
observe_tool_cancel: (ctx, args) => {
|
|
2080
|
+
const callId = validateWithSchema(args.callId, z.string().min(1), 'Invalid callId');
|
|
2081
|
+
ctx.toolCallObserver.cancel(callId);
|
|
2082
|
+
return Promise.resolve(formatToolResponse({ callId, cancelled: true }));
|
|
2083
|
+
},
|
|
2084
|
+
tool_observer_in_flight_count: (ctx) => {
|
|
2085
|
+
const count = ctx.toolCallObserver.inFlightCount();
|
|
2086
|
+
return Promise.resolve(formatToolResponse({ inFlightCount: count }));
|
|
2087
|
+
},
|
|
2088
|
+
// ==================== v2.1.0 HEURISTIC GUIDELINES ====================
|
|
2089
|
+
add_heuristic: async (ctx, args) => {
|
|
2090
|
+
const condition = validateWithSchema(args.condition, z.string().min(1), 'Invalid condition');
|
|
2091
|
+
const action = validateWithSchema(args.action, z.string().min(1), 'Invalid action');
|
|
2092
|
+
const priority = args.priority === undefined
|
|
2093
|
+
? undefined
|
|
2094
|
+
: validateWithSchema(args.priority, z.number(), 'Invalid priority');
|
|
2095
|
+
const initialConfidence = args.initialConfidence === undefined
|
|
2096
|
+
? undefined
|
|
2097
|
+
: validateWithSchema(args.initialConfidence, z.number().min(0).max(1), 'Invalid initialConfidence');
|
|
2098
|
+
const importance = args.importance === undefined
|
|
2099
|
+
? undefined
|
|
2100
|
+
: validateWithSchema(args.importance, z.number().min(0).max(10), 'Invalid importance');
|
|
2101
|
+
const agentId = args.agentId === undefined
|
|
2102
|
+
? undefined
|
|
2103
|
+
: validateWithSchema(args.agentId, z.string().min(1), 'Invalid agentId');
|
|
2104
|
+
const id = args.id === undefined
|
|
2105
|
+
? undefined
|
|
2106
|
+
: validateWithSchema(args.id, z.string().min(1), 'Invalid id');
|
|
2107
|
+
const heuristicId = await ctx.heuristicManager.add({
|
|
2108
|
+
condition,
|
|
2109
|
+
action,
|
|
2110
|
+
priority,
|
|
2111
|
+
initialConfidence,
|
|
2112
|
+
importance,
|
|
2113
|
+
agentId,
|
|
2114
|
+
id: id,
|
|
2115
|
+
});
|
|
2116
|
+
return formatToolResponse({ id: heuristicId });
|
|
2117
|
+
},
|
|
2118
|
+
get_heuristic: async (ctx, args) => {
|
|
2119
|
+
const id = validateWithSchema(args.id, z.string().min(1), 'Invalid id');
|
|
2120
|
+
const heuristic = ctx.heuristicManager.get(id);
|
|
2121
|
+
return formatToolResponse({ id, heuristic: heuristic ?? null });
|
|
2122
|
+
},
|
|
2123
|
+
list_heuristics: async (ctx) => {
|
|
2124
|
+
const heuristics = await ctx.heuristicManager.list();
|
|
2125
|
+
return formatToolResponse({ heuristics, count: heuristics.length });
|
|
2126
|
+
},
|
|
2127
|
+
heuristic_count: async (ctx) => {
|
|
2128
|
+
const size = await ctx.heuristicManager.size();
|
|
2129
|
+
return formatToolResponse({ count: size });
|
|
2130
|
+
},
|
|
2131
|
+
match_heuristics: async (ctx, args) => {
|
|
2132
|
+
const input = validateWithSchema(args.input, z.string().min(1), 'Invalid input');
|
|
2133
|
+
const limit = args.limit === undefined
|
|
2134
|
+
? undefined
|
|
2135
|
+
: validateWithSchema(args.limit, z.number().int().min(1), 'Invalid limit');
|
|
2136
|
+
const minScore = args.minScore === undefined
|
|
2137
|
+
? undefined
|
|
2138
|
+
: validateWithSchema(args.minScore, z.number().min(0).max(1), 'Invalid minScore');
|
|
2139
|
+
const matches = await ctx.heuristicManager.match(input, { limit, minScore });
|
|
2140
|
+
return formatToolResponse({ input, matches, count: matches.length });
|
|
2141
|
+
},
|
|
2142
|
+
reinforce_heuristic: async (ctx, args) => {
|
|
2143
|
+
const id = validateWithSchema(args.id, z.string().min(1), 'Invalid id');
|
|
2144
|
+
const result = await ctx.heuristicManager.reinforce(id);
|
|
2145
|
+
return formatToolResponse({ id, result });
|
|
2146
|
+
},
|
|
2147
|
+
record_heuristic_contradiction: async (ctx, args) => {
|
|
2148
|
+
const id = validateWithSchema(args.id, z.string().min(1), 'Invalid id');
|
|
2149
|
+
const result = await ctx.heuristicManager.recordContradiction(id);
|
|
2150
|
+
return formatToolResponse({ id, result });
|
|
2151
|
+
},
|
|
2152
|
+
detect_heuristic_conflicts: async (ctx) => {
|
|
2153
|
+
const conflicts = await ctx.heuristicManager.detectConflicts();
|
|
2154
|
+
return formatToolResponse({ conflicts, count: conflicts.length });
|
|
2155
|
+
},
|
|
2156
|
+
remove_heuristic: async (ctx, args) => {
|
|
2157
|
+
const id = validateWithSchema(args.id, z.string().min(1), 'Invalid id');
|
|
2158
|
+
const removed = await ctx.heuristicManager.remove(id);
|
|
2159
|
+
return formatToolResponse({ id, removed });
|
|
2160
|
+
},
|
|
2161
|
+
clear_heuristics: async (ctx) => {
|
|
2162
|
+
await ctx.heuristicManager.clear();
|
|
2163
|
+
return formatToolResponse({ cleared: true });
|
|
2164
|
+
},
|
|
2165
|
+
// ==================== v2.1.0 PROJECT CONTEXT ====================
|
|
2166
|
+
upsert_project_context: async (ctx, args) => {
|
|
2167
|
+
const projectId = validateWithSchema(args.projectId, z.string().min(1), 'Invalid projectId');
|
|
2168
|
+
const facts = args.facts === undefined
|
|
2169
|
+
? undefined
|
|
2170
|
+
: validateWithSchema(args.facts, z.array(z.string()), 'Invalid facts');
|
|
2171
|
+
const conventions = args.conventions === undefined
|
|
2172
|
+
? undefined
|
|
2173
|
+
: validateWithSchema(args.conventions, z.array(z.string()), 'Invalid conventions');
|
|
2174
|
+
const commands = args.commands === undefined
|
|
2175
|
+
? undefined
|
|
2176
|
+
: validateWithSchema(args.commands, z.array(z.object({
|
|
2177
|
+
name: z.string().min(1),
|
|
2178
|
+
command: z.string().min(1),
|
|
2179
|
+
purpose: z.string().min(1),
|
|
2180
|
+
})), 'Invalid commands');
|
|
2181
|
+
const glossary = args.glossary === undefined
|
|
2182
|
+
? undefined
|
|
2183
|
+
: validateWithSchema(args.glossary, z.array(z.object({
|
|
2184
|
+
term: z.string().min(1),
|
|
2185
|
+
definition: z.string().min(1),
|
|
2186
|
+
})), 'Invalid glossary');
|
|
2187
|
+
const record = await ctx.projectContextManager.upsert(projectId, {
|
|
2188
|
+
facts, conventions, commands, glossary,
|
|
2189
|
+
});
|
|
2190
|
+
return formatToolResponse({ record });
|
|
2191
|
+
},
|
|
2192
|
+
get_project_context: async (ctx, args) => {
|
|
2193
|
+
const projectId = validateWithSchema(args.projectId, z.string().min(1), 'Invalid projectId');
|
|
2194
|
+
const record = ctx.projectContextManager.get(projectId);
|
|
2195
|
+
return formatToolResponse({ projectId, record: record ?? null });
|
|
2196
|
+
},
|
|
2197
|
+
append_project_fact: async (ctx, args) => {
|
|
2198
|
+
const projectId = validateWithSchema(args.projectId, z.string().min(1), 'Invalid projectId');
|
|
2199
|
+
const fact = validateWithSchema(args.fact, z.string().min(1), 'Invalid fact');
|
|
2200
|
+
const record = await ctx.projectContextManager.appendFact(projectId, fact);
|
|
2201
|
+
return formatToolResponse({ record });
|
|
2202
|
+
},
|
|
2203
|
+
append_project_convention: async (ctx, args) => {
|
|
2204
|
+
const projectId = validateWithSchema(args.projectId, z.string().min(1), 'Invalid projectId');
|
|
2205
|
+
const convention = validateWithSchema(args.convention, z.string().min(1), 'Invalid convention');
|
|
2206
|
+
const record = await ctx.projectContextManager.appendConvention(projectId, convention);
|
|
2207
|
+
return formatToolResponse({ record });
|
|
2208
|
+
},
|
|
2209
|
+
append_project_command: async (ctx, args) => {
|
|
2210
|
+
const projectId = validateWithSchema(args.projectId, z.string().min(1), 'Invalid projectId');
|
|
2211
|
+
const name = validateWithSchema(args.name, z.string().min(1), 'Invalid name');
|
|
2212
|
+
const command = validateWithSchema(args.command, z.string().min(1), 'Invalid command');
|
|
2213
|
+
const purpose = validateWithSchema(args.purpose, z.string().min(1), 'Invalid purpose');
|
|
2214
|
+
const record = await ctx.projectContextManager.appendCommand(projectId, { name, command, purpose });
|
|
2215
|
+
return formatToolResponse({ record });
|
|
2216
|
+
},
|
|
2217
|
+
append_project_glossary_term: async (ctx, args) => {
|
|
2218
|
+
const projectId = validateWithSchema(args.projectId, z.string().min(1), 'Invalid projectId');
|
|
2219
|
+
const term = validateWithSchema(args.term, z.string().min(1), 'Invalid term');
|
|
2220
|
+
const definition = validateWithSchema(args.definition, z.string().min(1), 'Invalid definition');
|
|
2221
|
+
const record = await ctx.projectContextManager.appendGlossaryTerm(projectId, { term, definition });
|
|
2222
|
+
return formatToolResponse({ record });
|
|
2223
|
+
},
|
|
2224
|
+
remove_project_fact: async (ctx, args) => {
|
|
2225
|
+
const projectId = validateWithSchema(args.projectId, z.string().min(1), 'Invalid projectId');
|
|
2226
|
+
const fact = validateWithSchema(args.fact, z.string().min(1), 'Invalid fact');
|
|
2227
|
+
const removed = await ctx.projectContextManager.removeFact(projectId, fact);
|
|
2228
|
+
return formatToolResponse({ projectId, fact, removed });
|
|
2229
|
+
},
|
|
2230
|
+
remove_project_convention: async (ctx, args) => {
|
|
2231
|
+
const projectId = validateWithSchema(args.projectId, z.string().min(1), 'Invalid projectId');
|
|
2232
|
+
const convention = validateWithSchema(args.convention, z.string().min(1), 'Invalid convention');
|
|
2233
|
+
const removed = await ctx.projectContextManager.removeConvention(projectId, convention);
|
|
2234
|
+
return formatToolResponse({ projectId, convention, removed });
|
|
2235
|
+
},
|
|
2236
|
+
remove_project_command: async (ctx, args) => {
|
|
2237
|
+
const projectId = validateWithSchema(args.projectId, z.string().min(1), 'Invalid projectId');
|
|
2238
|
+
const commandName = validateWithSchema(args.commandName, z.string().min(1), 'Invalid commandName');
|
|
2239
|
+
const removed = await ctx.projectContextManager.removeCommand(projectId, commandName);
|
|
2240
|
+
return formatToolResponse({ projectId, commandName, removed });
|
|
2241
|
+
},
|
|
2242
|
+
remove_project_glossary_term: async (ctx, args) => {
|
|
2243
|
+
const projectId = validateWithSchema(args.projectId, z.string().min(1), 'Invalid projectId');
|
|
2244
|
+
const term = validateWithSchema(args.term, z.string().min(1), 'Invalid term');
|
|
2245
|
+
const removed = await ctx.projectContextManager.removeGlossaryTerm(projectId, term);
|
|
2246
|
+
return formatToolResponse({ projectId, term, removed });
|
|
2247
|
+
},
|
|
2248
|
+
clear_project_context: async (ctx, args) => {
|
|
2249
|
+
const projectId = validateWithSchema(args.projectId, z.string().min(1), 'Invalid projectId');
|
|
2250
|
+
const cleared = await ctx.projectContextManager.clear(projectId);
|
|
2251
|
+
return formatToolResponse({ projectId, cleared });
|
|
2252
|
+
},
|
|
2253
|
+
format_project_context_for_llm: async (ctx, args) => {
|
|
2254
|
+
const projectId = validateWithSchema(args.projectId, z.string().min(1), 'Invalid projectId');
|
|
2255
|
+
const budgetChars = args.budgetChars === undefined
|
|
2256
|
+
? undefined
|
|
2257
|
+
: validateWithSchema(args.budgetChars, z.number().int().min(1), 'Invalid budgetChars');
|
|
2258
|
+
const prose = await ctx.projectContextManager.forContext(projectId, { budgetChars });
|
|
2259
|
+
return formatTextResponse(prose);
|
|
2260
|
+
},
|
|
2261
|
+
// ==================== v2.1.0 DECISION RATIONALE ====================
|
|
2262
|
+
propose_decision: async (ctx, args) => {
|
|
2263
|
+
const context = validateWithSchema(args.context, z.string().min(1), 'Invalid context');
|
|
2264
|
+
const decision = validateWithSchema(args.decision, z.string().min(1), 'Invalid decision');
|
|
2265
|
+
const alternatives = args.alternatives === undefined
|
|
2266
|
+
? []
|
|
2267
|
+
: validateWithSchema(args.alternatives, z.array(z.string()), 'Invalid alternatives');
|
|
2268
|
+
const consequences = args.consequences === undefined
|
|
2269
|
+
? []
|
|
2270
|
+
: validateWithSchema(args.consequences, z.array(z.string()), 'Invalid consequences');
|
|
2271
|
+
const relatedFiles = args.relatedFiles === undefined
|
|
2272
|
+
? undefined
|
|
2273
|
+
: validateWithSchema(args.relatedFiles, z.array(z.string()), 'Invalid relatedFiles');
|
|
2274
|
+
const supersedes = args.supersedes === undefined
|
|
2275
|
+
? undefined
|
|
2276
|
+
: validateWithSchema(args.supersedes, z.string().min(1), 'Invalid supersedes');
|
|
2277
|
+
const sourceSessionId = args.sourceSessionId === undefined
|
|
2278
|
+
? undefined
|
|
2279
|
+
: validateWithSchema(args.sourceSessionId, z.string().min(1), 'Invalid sourceSessionId');
|
|
2280
|
+
const sourceProjectId = args.sourceProjectId === undefined
|
|
2281
|
+
? undefined
|
|
2282
|
+
: validateWithSchema(args.sourceProjectId, z.string().min(1), 'Invalid sourceProjectId');
|
|
2283
|
+
const importance = args.importance === undefined
|
|
2284
|
+
? undefined
|
|
2285
|
+
: validateWithSchema(args.importance, z.number().min(0).max(10), 'Invalid importance');
|
|
2286
|
+
const agentId = args.agentId === undefined
|
|
2287
|
+
? undefined
|
|
2288
|
+
: validateWithSchema(args.agentId, z.string().min(1), 'Invalid agentId');
|
|
2289
|
+
// `supersedes` is a branded DecisionId at the type level; the
|
|
2290
|
+
// manager casts internally so a string is fine here.
|
|
2291
|
+
const record = await ctx.decisionManager.propose({
|
|
2292
|
+
context,
|
|
2293
|
+
decision,
|
|
2294
|
+
alternatives,
|
|
2295
|
+
consequences,
|
|
2296
|
+
relatedFiles,
|
|
2297
|
+
supersedes: supersedes,
|
|
2298
|
+
sourceSessionId,
|
|
2299
|
+
sourceProjectId,
|
|
2300
|
+
}, { importance, agentId });
|
|
2301
|
+
return formatToolResponse({ record });
|
|
2302
|
+
},
|
|
2303
|
+
accept_decision: async (ctx, args) => {
|
|
2304
|
+
const id = validateWithSchema(args.id, z.string().min(1), 'Invalid id');
|
|
2305
|
+
const result = await ctx.decisionManager.accept(id);
|
|
2306
|
+
return formatToolResponse({ id, result });
|
|
2307
|
+
},
|
|
2308
|
+
reject_decision: async (ctx, args) => {
|
|
2309
|
+
const id = validateWithSchema(args.id, z.string().min(1), 'Invalid id');
|
|
2310
|
+
const reason = validateWithSchema(args.reason, z.string().min(1), 'Invalid reason');
|
|
2311
|
+
const result = await ctx.decisionManager.reject(id, reason);
|
|
2312
|
+
return formatToolResponse({ id, reason, result });
|
|
2313
|
+
},
|
|
2314
|
+
supersede_decision: async (ctx, args) => {
|
|
2315
|
+
const id = validateWithSchema(args.id, z.string().min(1), 'Invalid id');
|
|
2316
|
+
const by = validateWithSchema(args.by, z.string().min(1), 'Invalid by');
|
|
2317
|
+
const result = await ctx.decisionManager.supersede(id, by);
|
|
2318
|
+
return formatToolResponse({ id, by, result });
|
|
2319
|
+
},
|
|
2320
|
+
find_decisions_by_context: async (ctx, args) => {
|
|
2321
|
+
const query = validateWithSchema(args.query, z.string().min(1), 'Invalid query');
|
|
2322
|
+
const records = await ctx.decisionManager.findByContext(query);
|
|
2323
|
+
return formatToolResponse({ query, records, count: records.length });
|
|
2324
|
+
},
|
|
2325
|
+
get_decision_chain: async (ctx, args) => {
|
|
2326
|
+
const id = validateWithSchema(args.id, z.string().min(1), 'Invalid id');
|
|
2327
|
+
const chain = await ctx.decisionManager.getChain(id);
|
|
2328
|
+
return formatToolResponse({ id, chain, length: chain.length });
|
|
2329
|
+
},
|
|
2330
|
+
list_decisions: async (ctx, args) => {
|
|
2331
|
+
const status = args.status === undefined
|
|
2332
|
+
? undefined
|
|
2333
|
+
: validateWithSchema(args.status, z.enum(['proposed', 'accepted', 'superseded', 'rejected']), 'Invalid status');
|
|
2334
|
+
const sourceSessionId = args.sourceSessionId === undefined
|
|
2335
|
+
? undefined
|
|
2336
|
+
: validateWithSchema(args.sourceSessionId, z.string().min(1), 'Invalid sourceSessionId');
|
|
2337
|
+
const sourceProjectId = args.sourceProjectId === undefined
|
|
2338
|
+
? undefined
|
|
2339
|
+
: validateWithSchema(args.sourceProjectId, z.string().min(1), 'Invalid sourceProjectId');
|
|
2340
|
+
const limit = args.limit === undefined
|
|
2341
|
+
? undefined
|
|
2342
|
+
: validateWithSchema(args.limit, z.number().int().min(1), 'Invalid limit');
|
|
2343
|
+
const records = await ctx.decisionManager.list({ status, sourceSessionId, sourceProjectId, limit });
|
|
2344
|
+
return formatToolResponse({ records, count: records.length });
|
|
2345
|
+
},
|
|
2346
|
+
get_decision: async (ctx, args) => {
|
|
2347
|
+
const id = validateWithSchema(args.id, z.string().min(1), 'Invalid id');
|
|
2348
|
+
const record = ctx.decisionManager.get(id);
|
|
2349
|
+
return formatToolResponse({ id, record: record ?? null });
|
|
2350
|
+
},
|
|
2351
|
+
export_decision_as_adr_markdown: async (ctx, args) => {
|
|
2352
|
+
const id = validateWithSchema(args.id, z.string().min(1), 'Invalid id');
|
|
2353
|
+
const markdown = ctx.decisionManager.exportAsAdrMarkdown(id);
|
|
2354
|
+
return formatTextResponse(markdown);
|
|
2355
|
+
},
|
|
2356
|
+
parse_adr_markdown: async (_ctx, args) => {
|
|
2357
|
+
const text = validateWithSchema(args.text, z.string().min(1), 'Invalid text');
|
|
2358
|
+
const parsed = DecisionManager.parseAdrMarkdown(text);
|
|
2359
|
+
return formatToolResponse({ parsed });
|
|
2360
|
+
},
|
|
2361
|
+
// ==================== v2.1.0 do_not_remember (Exclusion) ====================
|
|
2362
|
+
add_exclusion_rule: async (ctx, args) => {
|
|
2363
|
+
const pattern = validateWithSchema(args.pattern, z.string().min(1), 'Invalid pattern');
|
|
2364
|
+
const scope = args.scope === undefined
|
|
2365
|
+
? undefined
|
|
2366
|
+
: validateWithSchema(args.scope, z.enum(['future-only', 'past-only', 'both']), 'Invalid scope');
|
|
2367
|
+
const entityType = args.entityType === undefined
|
|
2368
|
+
? undefined
|
|
2369
|
+
: validateWithSchema(args.entityType, z.string().min(1), 'Invalid entityType');
|
|
2370
|
+
const reason = args.reason === undefined
|
|
2371
|
+
? undefined
|
|
2372
|
+
: validateWithSchema(args.reason, z.string().min(1), 'Invalid reason');
|
|
2373
|
+
const rule = await ctx.exclusionManager.add({ pattern, scope, entityType, reason });
|
|
2374
|
+
return formatToolResponse({ rule });
|
|
2375
|
+
},
|
|
2376
|
+
list_exclusion_rules: async (ctx) => {
|
|
2377
|
+
const rules = await ctx.exclusionManager.list();
|
|
2378
|
+
return formatToolResponse({ rules, count: rules.length });
|
|
2379
|
+
},
|
|
2380
|
+
remove_exclusion_rule: async (ctx, args) => {
|
|
2381
|
+
const id = validateWithSchema(args.id, z.string().min(1), 'Invalid id');
|
|
2382
|
+
const removed = await ctx.exclusionManager.remove(id);
|
|
2383
|
+
return formatToolResponse({ id, removed });
|
|
2384
|
+
},
|
|
2385
|
+
check_exclusion: async (ctx, args) => {
|
|
2386
|
+
const content = validateWithSchema(args.content, z.string(), 'Invalid content');
|
|
2387
|
+
const entityType = args.entityType === undefined
|
|
2388
|
+
? undefined
|
|
2389
|
+
: validateWithSchema(args.entityType, z.string().min(1), 'Invalid entityType');
|
|
2390
|
+
const verdict = await ctx.exclusionManager.check(content, entityType);
|
|
2391
|
+
return formatToolResponse(verdict);
|
|
2392
|
+
},
|
|
2393
|
+
find_matching_memories_for_rule: async (ctx, args) => {
|
|
2394
|
+
const pattern = validateWithSchema(args.pattern, z.string().min(1), 'Invalid pattern');
|
|
2395
|
+
const entityType = args.entityType === undefined
|
|
2396
|
+
? undefined
|
|
2397
|
+
: validateWithSchema(args.entityType, z.string().min(1), 'Invalid entityType');
|
|
2398
|
+
const matches = await ctx.exclusionManager.findMatchingMemories({ pattern, entityType });
|
|
2399
|
+
return formatToolResponse({
|
|
2400
|
+
pattern,
|
|
2401
|
+
entityType,
|
|
2402
|
+
matches: matches.map((m) => ({ name: m.name, entityType: m.entityType })),
|
|
2403
|
+
count: matches.length,
|
|
2404
|
+
});
|
|
2405
|
+
},
|
|
2406
|
+
// ==================== v2.1.0 OBSERVATION DEDUP ====================
|
|
2407
|
+
find_duplicate_observations: async (ctx, args) => {
|
|
2408
|
+
const filter = parseObservationDedupFilter(args);
|
|
2409
|
+
const groups = await ctx.observationDedupManager.findDuplicateObservations(filter);
|
|
2410
|
+
return formatToolResponse({ filter, groups, count: groups.length });
|
|
2411
|
+
},
|
|
2412
|
+
find_jaccard_duplicate_observations: async (ctx, args) => {
|
|
2413
|
+
const filter = parseObservationDedupFilter(args);
|
|
2414
|
+
const groups = await ctx.observationDedupManager.findJaccardDuplicates(filter);
|
|
2415
|
+
return formatToolResponse({ filter, groups, count: groups.length });
|
|
2416
|
+
},
|
|
2417
|
+
// ==================== v2.1.0 SPELL CORRECTION ====================
|
|
2418
|
+
spell_suggest: async (ctx, args) => {
|
|
2419
|
+
const query = validateWithSchema(args.query, z.string().min(1), 'Invalid query');
|
|
2420
|
+
const limit = args.limit === undefined
|
|
2421
|
+
? undefined
|
|
2422
|
+
: validateWithSchema(args.limit, z.number().int().min(1), 'Invalid limit');
|
|
2423
|
+
const minScore = args.minScore === undefined
|
|
2424
|
+
? undefined
|
|
2425
|
+
: validateWithSchema(args.minScore, z.number().min(0).max(1), 'Invalid minScore');
|
|
2426
|
+
const maxDistance = args.maxDistance === undefined
|
|
2427
|
+
? undefined
|
|
2428
|
+
: validateWithSchema(args.maxDistance, z.number().min(0), 'Invalid maxDistance');
|
|
2429
|
+
const suggestions = await ctx.spellChecker.suggest(query, { limit, minScore, maxDistance });
|
|
2430
|
+
return formatToolResponse({ query, suggestions, count: suggestions.length });
|
|
2431
|
+
},
|
|
2432
|
+
spell_rebuild_vocabulary: async (ctx) => {
|
|
2433
|
+
await ctx.spellChecker.rebuild();
|
|
2434
|
+
return formatToolResponse({ rebuilt: true, vocabularySize: ctx.spellChecker.vocabularySize() });
|
|
2435
|
+
},
|
|
2436
|
+
spell_vocabulary_size: async (ctx) => {
|
|
2437
|
+
const size = ctx.spellChecker.vocabularySize();
|
|
2438
|
+
return formatToolResponse({ vocabularySize: size });
|
|
2439
|
+
},
|
|
2440
|
+
// ==================== ENGINEERING / DIAGNOSTIC TOOLS (v12.5.0) ====================
|
|
2441
|
+
// Parallel to the `memory diag` / `memory check` / etc. CLI surface in memoryjs v2.2+.
|
|
2442
|
+
// Useful when the MCP server is up but the graph state is suspect.
|
|
2443
|
+
diag: async (ctx) => {
|
|
2444
|
+
const storagePath = getStorageFilePath(ctx);
|
|
2445
|
+
let exists = false;
|
|
2446
|
+
let sizeBytes = 0;
|
|
2447
|
+
try {
|
|
2448
|
+
const stat = await fs.stat(storagePath);
|
|
2449
|
+
exists = true;
|
|
2450
|
+
sizeBytes = stat.size;
|
|
2451
|
+
}
|
|
2452
|
+
catch { /* file may not exist yet */ }
|
|
2453
|
+
const stats = await ctx.analyticsManager.getGraphStats();
|
|
2454
|
+
return formatToolResponse({
|
|
2455
|
+
runtime: { node: process.version, platform: process.platform, arch: process.arch, pid: process.pid },
|
|
2456
|
+
storage: {
|
|
2457
|
+
path: storagePath,
|
|
2458
|
+
type: process.env.MEMORY_STORAGE_TYPE ?? 'jsonl',
|
|
2459
|
+
exists,
|
|
2460
|
+
sizeBytes,
|
|
2461
|
+
entities: stats.totalEntities,
|
|
2462
|
+
relations: stats.totalRelations,
|
|
2463
|
+
},
|
|
2464
|
+
loadedAt: new Date().toISOString(),
|
|
2465
|
+
});
|
|
2466
|
+
},
|
|
2467
|
+
health: async (ctx) => {
|
|
2468
|
+
const checks = [];
|
|
2469
|
+
const t1 = performance.now();
|
|
2470
|
+
let graph;
|
|
2471
|
+
try {
|
|
2472
|
+
graph = await ctx.storage.loadGraph();
|
|
2473
|
+
checks.push({ name: 'storage:loadGraph', ok: true, durationMs: performance.now() - t1 });
|
|
2474
|
+
}
|
|
2475
|
+
catch (e) {
|
|
2476
|
+
checks.push({
|
|
2477
|
+
name: 'storage:loadGraph',
|
|
2478
|
+
ok: false,
|
|
2479
|
+
durationMs: performance.now() - t1,
|
|
2480
|
+
detail: e instanceof Error ? e.message : String(e),
|
|
2481
|
+
});
|
|
2482
|
+
return formatToolResponse({ ok: false, failed: 1, totalChecks: 1, totalMs: performance.now() - t1, checks });
|
|
2483
|
+
}
|
|
2484
|
+
const t2 = performance.now();
|
|
2485
|
+
const names = new Set();
|
|
2486
|
+
const dupes = [];
|
|
2487
|
+
for (const e of graph.entities) {
|
|
2488
|
+
if (names.has(e.name))
|
|
2489
|
+
dupes.push(e.name);
|
|
2490
|
+
else
|
|
2491
|
+
names.add(e.name);
|
|
2492
|
+
}
|
|
2493
|
+
checks.push({
|
|
2494
|
+
name: 'entities:distinct-names',
|
|
2495
|
+
ok: dupes.length === 0,
|
|
2496
|
+
durationMs: performance.now() - t2,
|
|
2497
|
+
detail: dupes.length === 0 ? undefined : `${dupes.length} duplicate(s)`,
|
|
2498
|
+
});
|
|
2499
|
+
const t3 = performance.now();
|
|
2500
|
+
const orphans = [];
|
|
2501
|
+
for (const r of graph.relations) {
|
|
2502
|
+
if (!names.has(r.from) || !names.has(r.to)) {
|
|
2503
|
+
orphans.push(`${r.from} → ${r.to}`);
|
|
2504
|
+
}
|
|
2505
|
+
}
|
|
2506
|
+
checks.push({
|
|
2507
|
+
name: 'relations:no-orphans',
|
|
2508
|
+
ok: orphans.length === 0,
|
|
2509
|
+
durationMs: performance.now() - t3,
|
|
2510
|
+
detail: orphans.length === 0 ? undefined : `${orphans.length} orphan(s)`,
|
|
2511
|
+
});
|
|
2512
|
+
const t4 = performance.now();
|
|
2513
|
+
const byName = new Map();
|
|
2514
|
+
for (const e of graph.entities)
|
|
2515
|
+
byName.set(e.name, e);
|
|
2516
|
+
const cycleIssues = [];
|
|
2517
|
+
for (const e of graph.entities) {
|
|
2518
|
+
if (!e.parentId)
|
|
2519
|
+
continue;
|
|
2520
|
+
if (!byName.has(e.parentId)) {
|
|
2521
|
+
cycleIssues.push(`${e.name}.parentId='${e.parentId}' missing`);
|
|
2522
|
+
continue;
|
|
2523
|
+
}
|
|
2524
|
+
const visited = new Set();
|
|
2525
|
+
let cur = byName.get(e.parentId);
|
|
2526
|
+
while (cur && cur.parentId) {
|
|
2527
|
+
if (visited.has(cur.name)) {
|
|
2528
|
+
cycleIssues.push(`cycle through ${cur.name}`);
|
|
2529
|
+
break;
|
|
2530
|
+
}
|
|
2531
|
+
visited.add(cur.name);
|
|
2532
|
+
cur = byName.get(cur.parentId);
|
|
2533
|
+
}
|
|
2534
|
+
}
|
|
2535
|
+
checks.push({
|
|
2536
|
+
name: 'hierarchy:no-cycles-no-missing-parents',
|
|
2537
|
+
ok: cycleIssues.length === 0,
|
|
2538
|
+
durationMs: performance.now() - t4,
|
|
2539
|
+
detail: cycleIssues.length === 0 ? undefined : cycleIssues.slice(0, 3).join('; '),
|
|
2540
|
+
});
|
|
2541
|
+
const failed = checks.filter((c) => !c.ok).length;
|
|
2542
|
+
return formatToolResponse({
|
|
2543
|
+
ok: failed === 0,
|
|
2544
|
+
failed,
|
|
2545
|
+
totalChecks: checks.length,
|
|
2546
|
+
totalMs: Number(checks.reduce((a, c) => a + c.durationMs, 0).toFixed(2)),
|
|
2547
|
+
checks,
|
|
2548
|
+
});
|
|
2549
|
+
},
|
|
2550
|
+
check_graph: async (ctx, args) => {
|
|
2551
|
+
const apply = args.apply === undefined
|
|
2552
|
+
? false
|
|
2553
|
+
: validateWithSchema(args.apply, z.boolean(), 'Invalid apply');
|
|
2554
|
+
const graph = await ctx.storage.loadGraph();
|
|
2555
|
+
const names = new Set(graph.entities.map((e) => e.name));
|
|
2556
|
+
const orphans = [];
|
|
2557
|
+
for (const r of graph.relations) {
|
|
2558
|
+
const fromMissing = !names.has(r.from);
|
|
2559
|
+
const toMissing = !names.has(r.to);
|
|
2560
|
+
if (fromMissing || toMissing) {
|
|
2561
|
+
orphans.push({
|
|
2562
|
+
from: r.from, to: r.to, relationType: r.relationType,
|
|
2563
|
+
reason: fromMissing && toMissing ? 'both-missing' : fromMissing ? 'from-missing' : 'to-missing',
|
|
2564
|
+
});
|
|
2565
|
+
}
|
|
2566
|
+
}
|
|
2567
|
+
const byName = new Map();
|
|
2568
|
+
for (const e of graph.entities)
|
|
2569
|
+
byName.set(e.name, { name: e.name, parentId: e.parentId });
|
|
2570
|
+
const missing = [];
|
|
2571
|
+
const cycles = [];
|
|
2572
|
+
for (const e of graph.entities) {
|
|
2573
|
+
if (!e.parentId)
|
|
2574
|
+
continue;
|
|
2575
|
+
if (!byName.has(e.parentId)) {
|
|
2576
|
+
missing.push({ entity: e.name, parentId: e.parentId });
|
|
2577
|
+
continue;
|
|
2578
|
+
}
|
|
2579
|
+
const visited = new Set([e.name]);
|
|
2580
|
+
let cur = byName.get(e.parentId);
|
|
2581
|
+
while (cur && cur.parentId) {
|
|
2582
|
+
if (visited.has(cur.name)) {
|
|
2583
|
+
cycles.push({ entityInCycle: e.name, cycleThrough: cur.name });
|
|
2584
|
+
break;
|
|
2585
|
+
}
|
|
2586
|
+
visited.add(cur.name);
|
|
2587
|
+
cur = byName.get(cur.parentId);
|
|
2588
|
+
}
|
|
2589
|
+
}
|
|
2590
|
+
const ok = orphans.length === 0 && missing.length === 0 && cycles.length === 0;
|
|
2591
|
+
const result = {
|
|
2592
|
+
ok, applied: apply, orphanRelations: orphans, missingParents: missing, hierarchyCycles: cycles,
|
|
2593
|
+
};
|
|
2594
|
+
if (apply && (orphans.length > 0 || missing.length > 0)) {
|
|
2595
|
+
let deleted = 0;
|
|
2596
|
+
let cleared = 0;
|
|
2597
|
+
if (orphans.length > 0) {
|
|
2598
|
+
await ctx.relationManager.deleteRelations(orphans.map((o) => ({ from: o.from, to: o.to, relationType: o.relationType })));
|
|
2599
|
+
deleted = orphans.length;
|
|
2600
|
+
}
|
|
2601
|
+
for (const m of missing) {
|
|
2602
|
+
try {
|
|
2603
|
+
await ctx.hierarchyManager.setEntityParent(m.entity, null);
|
|
2604
|
+
cleared += 1;
|
|
2605
|
+
}
|
|
2606
|
+
catch { /* skip; entity may have vanished */ }
|
|
2607
|
+
}
|
|
2608
|
+
result.actions = { orphanRelationsDeleted: deleted, missingParentsCleared: cleared };
|
|
2609
|
+
}
|
|
2610
|
+
return formatToolResponse(result);
|
|
2611
|
+
},
|
|
2612
|
+
reindex: async (ctx, args) => {
|
|
2613
|
+
const ranked = args.ranked === undefined
|
|
2614
|
+
? true
|
|
2615
|
+
: validateWithSchema(args.ranked, z.boolean(), 'Invalid ranked');
|
|
2616
|
+
const spell = args.spell === undefined
|
|
2617
|
+
? true
|
|
2618
|
+
: validateWithSchema(args.spell, z.boolean(), 'Invalid spell');
|
|
2619
|
+
if (!ranked && !spell) {
|
|
2620
|
+
return formatToolResponse({ ok: true, failed: 0, result: {} });
|
|
2621
|
+
}
|
|
2622
|
+
const result = {};
|
|
2623
|
+
if (ranked) {
|
|
2624
|
+
const t = performance.now();
|
|
2625
|
+
try {
|
|
2626
|
+
// Same workaround as the CLI: default ctx.rankedSearch is constructed
|
|
2627
|
+
// without a storageDir so buildIndex() refuses. Construct an ad-hoc one
|
|
2628
|
+
// alongside the JSONL.
|
|
2629
|
+
const storageDir = path.dirname(getStorageFilePath(ctx));
|
|
2630
|
+
const r = new RankedSearch(ctx.storage, storageDir);
|
|
2631
|
+
await r.buildIndex();
|
|
2632
|
+
result.ranked = { ok: true, durationMs: performance.now() - t };
|
|
2633
|
+
}
|
|
2634
|
+
catch (e) {
|
|
2635
|
+
result.ranked = {
|
|
2636
|
+
ok: false,
|
|
2637
|
+
durationMs: performance.now() - t,
|
|
2638
|
+
detail: e instanceof Error ? e.message : String(e),
|
|
2639
|
+
};
|
|
2640
|
+
}
|
|
2641
|
+
}
|
|
2642
|
+
if (spell) {
|
|
2643
|
+
const t = performance.now();
|
|
2644
|
+
try {
|
|
2645
|
+
await ctx.spellChecker.rebuild();
|
|
2646
|
+
result.spell = { ok: true, durationMs: performance.now() - t };
|
|
2647
|
+
}
|
|
2648
|
+
catch (e) {
|
|
2649
|
+
result.spell = {
|
|
2650
|
+
ok: false,
|
|
2651
|
+
durationMs: performance.now() - t,
|
|
2652
|
+
detail: e instanceof Error ? e.message : String(e),
|
|
2653
|
+
};
|
|
2654
|
+
}
|
|
2655
|
+
}
|
|
2656
|
+
const failed = Object.values(result).filter((r) => !r.ok).length;
|
|
2657
|
+
return formatToolResponse({ ok: failed === 0, failed, result });
|
|
2658
|
+
},
|
|
2659
|
+
cache_stats: async (_ctx) => {
|
|
2660
|
+
return formatToolResponse({ stats: getAllCacheStats() });
|
|
2661
|
+
},
|
|
2662
|
+
cache_clear: async (_ctx) => {
|
|
2663
|
+
clearAllSearchCaches();
|
|
2664
|
+
return formatToolResponse({ cleared: true, caches: ['basic', 'ranked', 'boolean', 'fuzzy'] });
|
|
2665
|
+
},
|
|
2666
|
+
graph_size: async (ctx) => {
|
|
2667
|
+
const storagePath = getStorageFilePath(ctx);
|
|
2668
|
+
const graph = await ctx.storage.loadGraph();
|
|
2669
|
+
let observationCount = 0;
|
|
2670
|
+
const tagSet = new Set();
|
|
2671
|
+
for (const e of graph.entities) {
|
|
2672
|
+
observationCount += e.observations.length;
|
|
2673
|
+
if (e.tags)
|
|
2674
|
+
for (const t of e.tags)
|
|
2675
|
+
tagSet.add(t);
|
|
2676
|
+
}
|
|
2677
|
+
let exists = false;
|
|
2678
|
+
let sizeBytes = 0;
|
|
2679
|
+
let lineCount = 0;
|
|
2680
|
+
try {
|
|
2681
|
+
const stat = await fs.stat(storagePath);
|
|
2682
|
+
exists = true;
|
|
2683
|
+
sizeBytes = stat.size;
|
|
2684
|
+
if (sizeBytes > 0) {
|
|
2685
|
+
const content = await fs.readFile(storagePath, 'utf8');
|
|
2686
|
+
lineCount = content.split('\n').filter((l) => l.length > 0).length;
|
|
2687
|
+
}
|
|
2688
|
+
}
|
|
2689
|
+
catch { /* file may not exist */ }
|
|
2690
|
+
return formatToolResponse({
|
|
2691
|
+
graph: {
|
|
2692
|
+
entities: graph.entities.length,
|
|
2693
|
+
relations: graph.relations.length,
|
|
2694
|
+
observations: observationCount,
|
|
2695
|
+
distinctTags: tagSet.size,
|
|
2696
|
+
avgObservationsPerEntity: graph.entities.length === 0
|
|
2697
|
+
? 0
|
|
2698
|
+
: Number((observationCount / graph.entities.length).toFixed(2)),
|
|
2699
|
+
},
|
|
2700
|
+
storage: { path: storagePath, exists, sizeBytes, lineCount },
|
|
2701
|
+
});
|
|
2702
|
+
},
|
|
2703
|
+
inspect_entity: async (ctx, args) => {
|
|
2704
|
+
const name = validateWithSchema(args.name, z.string().min(1), 'Invalid name');
|
|
2705
|
+
// getEntityByName reads an in-memory nameIndex hydrated by loadGraph.
|
|
2706
|
+
const graph = await ctx.storage.loadGraph();
|
|
2707
|
+
const entity = ctx.storage.getEntityByName(name);
|
|
2708
|
+
if (!entity)
|
|
2709
|
+
throw new Error(`entity not found: ${name}`);
|
|
2710
|
+
const observations = await ctx.observationManager.getObservationsFor(name);
|
|
2711
|
+
const outgoing = graph.relations
|
|
2712
|
+
.filter((r) => r.from === name)
|
|
2713
|
+
.map((r) => ({ to: r.to, type: r.relationType }));
|
|
2714
|
+
const incoming = graph.relations
|
|
2715
|
+
.filter((r) => r.to === name)
|
|
2716
|
+
.map((r) => ({ from: r.from, type: r.relationType }));
|
|
2717
|
+
const children = (await ctx.hierarchyManager.getChildren(name)).map((c) => c.name);
|
|
2718
|
+
const ancestors = (await ctx.hierarchyManager.getAncestors(name)).map((a) => a.name);
|
|
2719
|
+
return formatToolResponse({
|
|
2720
|
+
name: entity.name,
|
|
2721
|
+
entityType: entity.entityType,
|
|
2722
|
+
observations,
|
|
2723
|
+
tags: entity.tags,
|
|
2724
|
+
importance: entity.importance,
|
|
2725
|
+
createdAt: entity.createdAt,
|
|
2726
|
+
lastModified: entity.lastModified,
|
|
2727
|
+
parentId: entity.parentId,
|
|
2728
|
+
children,
|
|
2729
|
+
ancestors,
|
|
2730
|
+
relations: { outgoing, incoming },
|
|
2731
|
+
});
|
|
2732
|
+
},
|
|
2733
|
+
hierarchy_tree: async (ctx, args) => {
|
|
2734
|
+
const root = args.root === undefined
|
|
2735
|
+
? undefined
|
|
2736
|
+
: validateWithSchema(args.root, z.string().min(1), 'Invalid root');
|
|
2737
|
+
await ctx.storage.loadGraph();
|
|
2738
|
+
async function walk(n) {
|
|
2739
|
+
const e = ctx.storage.getEntityByName(n);
|
|
2740
|
+
const kids = await ctx.hierarchyManager.getChildren(n);
|
|
2741
|
+
const childNodes = [];
|
|
2742
|
+
for (const c of kids)
|
|
2743
|
+
childNodes.push(await walk(c.name));
|
|
2744
|
+
return { name: n, entityType: e?.entityType ?? 'unknown', children: childNodes };
|
|
2745
|
+
}
|
|
2746
|
+
if (root) {
|
|
2747
|
+
const e = ctx.storage.getEntityByName(root);
|
|
2748
|
+
if (!e)
|
|
2749
|
+
throw new Error(`entity not found: ${root}`);
|
|
2750
|
+
return formatToolResponse({ trees: [await walk(root)] });
|
|
2751
|
+
}
|
|
2752
|
+
const roots = await ctx.hierarchyManager.getRootEntities();
|
|
2753
|
+
const trees = await Promise.all(roots.map((r) => walk(r.name)));
|
|
2754
|
+
return formatToolResponse({ trees });
|
|
2755
|
+
},
|
|
2756
|
+
entity_neighbors: async (ctx, args) => {
|
|
2757
|
+
const name = validateWithSchema(args.name, z.string().min(1), 'Invalid name');
|
|
2758
|
+
const graph = await ctx.storage.loadGraph();
|
|
2759
|
+
const entity = ctx.storage.getEntityByName(name);
|
|
2760
|
+
if (!entity)
|
|
2761
|
+
throw new Error(`entity not found: ${name}`);
|
|
2762
|
+
const outgoing = graph.relations.filter((r) => r.from === name).map((r) => ({ to: r.to, type: r.relationType }));
|
|
2763
|
+
const incoming = graph.relations.filter((r) => r.to === name).map((r) => ({ from: r.from, type: r.relationType }));
|
|
2764
|
+
return formatToolResponse({
|
|
2765
|
+
entity: name,
|
|
2766
|
+
outgoing,
|
|
2767
|
+
incoming,
|
|
2768
|
+
outDegree: outgoing.length,
|
|
2769
|
+
inDegree: incoming.length,
|
|
2770
|
+
});
|
|
2771
|
+
},
|
|
2772
|
+
// ==================== EVENT MEMORY HANDLERS (memoryjs v3.0.0) ====================
|
|
2773
|
+
record_event: async (ctx, args) => {
|
|
2774
|
+
const input = validateWithSchema(args, z.object({
|
|
2775
|
+
action: z.string().min(1),
|
|
2776
|
+
actor: z.string().min(1),
|
|
2777
|
+
target: z.string().min(1).optional(),
|
|
2778
|
+
context: z.string().min(1).optional(),
|
|
2779
|
+
participants: z.array(z.string().min(1)).optional(),
|
|
2780
|
+
occurredAt: z.string().min(1).optional(),
|
|
2781
|
+
flowKey: z.string().min(1).optional(),
|
|
2782
|
+
detail: z.array(z.string()).optional(),
|
|
2783
|
+
importance: z.number().optional(),
|
|
2784
|
+
}).strict(), 'Invalid event input');
|
|
2785
|
+
const event = await ctx.eventManager.recordEvent(input);
|
|
2786
|
+
return formatToolResponse({ event });
|
|
2787
|
+
},
|
|
2788
|
+
get_event: async (ctx, args) => {
|
|
2789
|
+
const name = validateWithSchema(args.name, z.string().min(1), 'Invalid event name');
|
|
2790
|
+
const event = await ctx.eventManager.getEvent(name);
|
|
2791
|
+
if (!event) {
|
|
2792
|
+
return formatTextResponse(`Event "${name}" not found`);
|
|
2793
|
+
}
|
|
2794
|
+
return formatToolResponse({ event });
|
|
2795
|
+
},
|
|
2796
|
+
query_events: async (ctx, args) => {
|
|
2797
|
+
const filter = validateWithSchema(args, z.object({
|
|
2798
|
+
actor: z.string().min(1).optional(),
|
|
2799
|
+
target: z.string().min(1).optional(),
|
|
2800
|
+
action: z.string().min(1).optional(),
|
|
2801
|
+
flowKey: z.string().min(1).optional(),
|
|
2802
|
+
timeRange: z.object({
|
|
2803
|
+
start: z.string().min(1).optional(),
|
|
2804
|
+
end: z.string().min(1).optional(),
|
|
2805
|
+
}).strict().optional(),
|
|
2806
|
+
limit: z.number().int().positive().max(1000).optional(),
|
|
2807
|
+
}).strict(), 'Invalid event filter');
|
|
2808
|
+
const events = await ctx.eventManager.queryEvents(filter);
|
|
2809
|
+
return formatToolResponse({ events, count: events.length });
|
|
2810
|
+
},
|
|
2811
|
+
get_event_flow: async (ctx, args) => {
|
|
2812
|
+
const flowKey = validateWithSchema(args.flowKey, z.string().min(1), 'Invalid flowKey');
|
|
2813
|
+
const events = await ctx.eventManager.getFlow(flowKey);
|
|
2814
|
+
return formatToolResponse({ flowKey, events, count: events.length });
|
|
2815
|
+
},
|
|
2816
|
+
who_did_what: async (ctx, args) => {
|
|
2817
|
+
const filter = validateWithSchema(args, z.object({
|
|
2818
|
+
target: z.string().min(1).optional(),
|
|
2819
|
+
context: z.string().min(1).optional(),
|
|
2820
|
+
timeRange: z.object({
|
|
2821
|
+
start: z.string().min(1).optional(),
|
|
2822
|
+
end: z.string().min(1).optional(),
|
|
2823
|
+
}).strict().optional(),
|
|
2824
|
+
limit: z.number().int().positive().max(1000).optional(),
|
|
2825
|
+
}).strict(), 'Invalid filter');
|
|
2826
|
+
const entries = await ctx.eventManager.whoDidWhat(filter);
|
|
2827
|
+
return formatToolResponse({ entries, count: entries.length });
|
|
2828
|
+
},
|
|
2829
|
+
// ==================== RECONSTRUCTIVE MEMORY HANDLERS (memoryjs v3.0.0) ====================
|
|
2830
|
+
ingest_dialogue: async (ctx, args) => {
|
|
2831
|
+
const turns = validateWithSchema(args.turns, z.array(z.object({
|
|
2832
|
+
id: z.string().min(1),
|
|
2833
|
+
speaker: z.string().optional(),
|
|
2834
|
+
text: z.string().min(1),
|
|
2835
|
+
timestamp: z.string().optional(),
|
|
2836
|
+
}).strict()).min(1), 'Invalid dialogue turns');
|
|
2837
|
+
const rm = ctx.reconstructiveMemory();
|
|
2838
|
+
const result = await rm.ingest(turns);
|
|
2839
|
+
return formatToolResponse({
|
|
2840
|
+
distillation: result,
|
|
2841
|
+
persisted: rm.lastPersistResult,
|
|
2842
|
+
stats: rm.stats(),
|
|
2843
|
+
});
|
|
2844
|
+
},
|
|
2845
|
+
reconstruct_memory: async (ctx, args) => {
|
|
2846
|
+
const query = validateWithSchema(args.query, z.string().min(1), 'Invalid query');
|
|
2847
|
+
const options = validateWithSchema({
|
|
2848
|
+
maxSteps: args.maxSteps,
|
|
2849
|
+
perStepBudget: args.perStepBudget,
|
|
2850
|
+
evidenceTarget: args.evidenceTarget,
|
|
2851
|
+
}, z.object({
|
|
2852
|
+
maxSteps: z.number().int().positive().max(32).optional(),
|
|
2853
|
+
perStepBudget: z.number().int().positive().max(100).optional(),
|
|
2854
|
+
evidenceTarget: z.number().int().positive().max(100).optional(),
|
|
2855
|
+
}), 'Invalid reconstruction options');
|
|
2856
|
+
const result = await ctx.reconstructiveMemory().reconstruct(query, options);
|
|
2857
|
+
return formatToolResponse(result);
|
|
2858
|
+
},
|
|
2859
|
+
reconstructive_memory_stats: async (ctx) => {
|
|
2860
|
+
return formatToolResponse(ctx.reconstructiveMemory().stats());
|
|
2861
|
+
},
|
|
2862
|
+
// ==================== RELATION CONSOLIDATION HANDLERS (memoryjs v3.0.0) ====================
|
|
2863
|
+
analyze_relation_duplicates: async (ctx) => {
|
|
2864
|
+
const consolidator = new RelationConsolidator(ctx.relationManager, ctx.entityManager, {
|
|
2865
|
+
embedding: ctx.semanticSearch?.getEmbeddingService(),
|
|
2866
|
+
});
|
|
2867
|
+
const report = await consolidator.analyze();
|
|
2868
|
+
return formatToolResponse(report);
|
|
2869
|
+
},
|
|
2870
|
+
consolidate_relations: async (ctx, args) => {
|
|
2871
|
+
const apply = args.apply !== undefined
|
|
2872
|
+
? validateWithSchema(args.apply, z.boolean(), 'Invalid apply flag')
|
|
2873
|
+
: false;
|
|
2874
|
+
const consolidator = new RelationConsolidator(ctx.relationManager, ctx.entityManager, {
|
|
2875
|
+
embedding: ctx.semanticSearch?.getEmbeddingService(),
|
|
2876
|
+
});
|
|
2877
|
+
const result = await consolidator.consolidate({ apply });
|
|
2878
|
+
return formatToolResponse(result);
|
|
2879
|
+
},
|
|
2880
|
+
// ==================== AGENT REFLECTION HANDLERS (memoryjs v3.0.0) ====================
|
|
2881
|
+
create_reflection: async (ctx, args) => {
|
|
2882
|
+
const parsed = validateWithSchema(args, z.object({
|
|
2883
|
+
scope: z.enum(['session', 'project', 'global']),
|
|
2884
|
+
summary: z.string().min(1),
|
|
2885
|
+
evidence: z.array(z.string().min(1)).min(1),
|
|
2886
|
+
generalizationConfidence: z.number().min(0).max(1),
|
|
2887
|
+
keyInsights: z.array(z.string().min(1)).max(5).optional(),
|
|
2888
|
+
experienceType: z.string().min(1).optional(),
|
|
2889
|
+
sourceSessionId: z.string().min(1).optional(),
|
|
2890
|
+
sourceProjectId: z.string().min(1).optional(),
|
|
2891
|
+
importance: z.number().optional(),
|
|
2892
|
+
agentId: z.string().min(1).optional(),
|
|
2893
|
+
}).strict(), 'Invalid reflection input');
|
|
2894
|
+
const reflection = await ctx.reflectionManager.create({
|
|
2895
|
+
scope: parsed.scope,
|
|
2896
|
+
summary: parsed.summary,
|
|
2897
|
+
evidence: parsed.evidence,
|
|
2898
|
+
generalization_confidence: parsed.generalizationConfidence,
|
|
2899
|
+
keyInsights: parsed.keyInsights,
|
|
2900
|
+
experienceType: parsed.experienceType,
|
|
2901
|
+
sourceSessionId: parsed.sourceSessionId,
|
|
2902
|
+
sourceProjectId: parsed.sourceProjectId,
|
|
2903
|
+
}, { importance: parsed.importance, agentId: parsed.agentId });
|
|
2904
|
+
return formatToolResponse({ reflection });
|
|
2905
|
+
},
|
|
2906
|
+
list_reflections: async (ctx, args) => {
|
|
2907
|
+
const options = validateWithSchema(args, z.object({
|
|
2908
|
+
scope: z.enum(['session', 'project', 'global']).optional(),
|
|
2909
|
+
sourceSessionId: z.string().min(1).optional(),
|
|
2910
|
+
sourceProjectId: z.string().min(1).optional(),
|
|
2911
|
+
minConfidence: z.number().min(0).max(1).optional(),
|
|
2912
|
+
includeArchived: z.boolean().optional(),
|
|
2913
|
+
limit: z.number().int().positive().max(1000).optional(),
|
|
2914
|
+
}).strict(), 'Invalid list options');
|
|
2915
|
+
const reflections = await ctx.reflectionManager.list(options);
|
|
2916
|
+
return formatToolResponse({ reflections, count: reflections.length });
|
|
2917
|
+
},
|
|
2918
|
+
get_relevant_reflections: async (ctx, args) => {
|
|
2919
|
+
const sessionId = validateWithSchema(args.sessionId, z.string().min(1), 'Invalid sessionId');
|
|
2920
|
+
const options = validateWithSchema({
|
|
2921
|
+
sessionEntityNames: args.sessionEntityNames,
|
|
2922
|
+
minConfidence: args.minConfidence,
|
|
2923
|
+
limit: args.limit,
|
|
2924
|
+
}, z.object({
|
|
2925
|
+
sessionEntityNames: z.array(z.string().min(1)).optional(),
|
|
2926
|
+
minConfidence: z.number().min(0).max(1).optional(),
|
|
2927
|
+
limit: z.number().int().positive().max(1000).optional(),
|
|
2928
|
+
}), 'Invalid relevance options');
|
|
2929
|
+
const reflections = await ctx.reflectionManager.getRelevantForSession(sessionId, options);
|
|
2930
|
+
return formatToolResponse({ sessionId, reflections, count: reflections.length });
|
|
2931
|
+
},
|
|
2932
|
+
archive_reflection: async (ctx, args) => {
|
|
2933
|
+
const id = validateWithSchema(args.id, z.string().min(1), 'Invalid reflection id');
|
|
2934
|
+
const result = await ctx.reflectionManager.archive(id);
|
|
2935
|
+
return formatToolResponse(result);
|
|
2936
|
+
},
|
|
2937
|
+
// ==================== RECONSTRUCTIVE MEMORY PERSISTENCE HANDLERS (memoryjs v3.0.0) ====================
|
|
2938
|
+
save_reconstructive_memory: async (ctx) => {
|
|
2939
|
+
const rm = ctx.reconstructiveMemory();
|
|
2940
|
+
const snapshot = rm.toSnapshot();
|
|
2941
|
+
const sidecarPath = reconstructiveSidecarPath(ctx);
|
|
2942
|
+
await fs.writeFile(sidecarPath, JSON.stringify(snapshot), 'utf-8');
|
|
2943
|
+
return formatToolResponse({ path: sidecarPath, stats: rm.stats() });
|
|
2944
|
+
},
|
|
2945
|
+
load_reconstructive_memory: async (ctx) => {
|
|
2946
|
+
const sidecarPath = reconstructiveSidecarPath(ctx);
|
|
2947
|
+
let raw;
|
|
2948
|
+
try {
|
|
2949
|
+
raw = await fs.readFile(sidecarPath, 'utf-8');
|
|
2950
|
+
}
|
|
2951
|
+
catch {
|
|
2952
|
+
throw new Error(`No reconstructive memory snapshot at "${sidecarPath}" — run save_reconstructive_memory first`);
|
|
2953
|
+
}
|
|
2954
|
+
const snapshot = validateWithSchema(JSON.parse(raw), z.object({
|
|
2955
|
+
cues: z.array(z.unknown()),
|
|
2956
|
+
tags: z.array(z.unknown()),
|
|
2957
|
+
contents: z.array(z.unknown()),
|
|
2958
|
+
triples: z.array(z.unknown()),
|
|
2959
|
+
}), `Invalid reconstructive memory snapshot at "${sidecarPath}"`);
|
|
2960
|
+
const rm = ctx.reconstructiveMemory();
|
|
2961
|
+
rm.loadSnapshot(snapshot);
|
|
2962
|
+
return formatToolResponse({ path: sidecarPath, stats: rm.stats() });
|
|
2963
|
+
},
|
|
1900
2964
|
};
|
|
1901
2965
|
/**
|
|
1902
2966
|
* Handle a tool call by dispatching to the appropriate handler.
|