@danielsimonjr/memory-mcp 11.1.0 → 12.2.3
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 +220 -11
- package/dist/index.d.ts +2 -2
- package/dist/index.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 +1581 -6
- package/dist/server/toolHandlers.d.ts +1 -1
- package/dist/server/toolHandlers.d.ts.map +1 -1
- package/dist/server/toolHandlers.js +1244 -2
- package/package.json +3 -3
- package/dist/memory.jsonl +0 -161
|
@@ -1,15 +1,89 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* MCP Tool Handlers
|
|
3
3
|
*
|
|
4
|
-
* Contains handler functions for all
|
|
4
|
+
* Contains handler functions for all 137 Knowledge Graph tools.
|
|
5
5
|
* Handlers call managers directly via ManagerContext.
|
|
6
6
|
* All core functionality is imported from @danielsimonjr/memoryjs.
|
|
7
7
|
*
|
|
8
8
|
* @module server/toolHandlers
|
|
9
9
|
*/
|
|
10
|
-
import
|
|
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
12
|
import { z } from 'zod';
|
|
12
13
|
import { maybeCompressResponse } from './responseCompressor.js';
|
|
14
|
+
// ==================== SINGLETON INFRASTRUCTURE ====================
|
|
15
|
+
// WeakMap-based singletons keyed on ManagerContext to avoid re-instantiation per request.
|
|
16
|
+
// These managers are not on ManagerContext directly, so we wire them up once per ctx.
|
|
17
|
+
const refIndexMap = new WeakMap();
|
|
18
|
+
const auditLogMap = new WeakMap();
|
|
19
|
+
const governanceMap = new WeakMap();
|
|
20
|
+
const freshnessMap = new WeakMap();
|
|
21
|
+
const artifactManagerMap = new WeakMap();
|
|
22
|
+
const distillationPipelineMap = new WeakMap();
|
|
23
|
+
const failureDistillationMap = new WeakMap();
|
|
24
|
+
const consolidationSchedulerMap = new WeakMap();
|
|
25
|
+
const dreamEngineMap = new WeakMap();
|
|
26
|
+
const queryCostEstimatorMap = new WeakMap();
|
|
27
|
+
function getStorageFilePath(ctx) {
|
|
28
|
+
// GraphStorage exposes filePath publicly; fall back to cwd-relative default
|
|
29
|
+
return ctx.storage.filePath ?? 'memory.jsonl';
|
|
30
|
+
}
|
|
31
|
+
function getRefIndex(ctx) {
|
|
32
|
+
if (!refIndexMap.has(ctx)) {
|
|
33
|
+
const storagePath = getStorageFilePath(ctx);
|
|
34
|
+
const dir = path.dirname(storagePath);
|
|
35
|
+
refIndexMap.set(ctx, new RefIndex(path.join(dir, 'memory-ref-index.jsonl')));
|
|
36
|
+
}
|
|
37
|
+
return refIndexMap.get(ctx);
|
|
38
|
+
}
|
|
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
|
+
function getFreshnessManager(ctx) {
|
|
56
|
+
if (!freshnessMap.has(ctx)) {
|
|
57
|
+
freshnessMap.set(ctx, new FreshnessManager(ctx.storage));
|
|
58
|
+
}
|
|
59
|
+
return freshnessMap.get(ctx);
|
|
60
|
+
}
|
|
61
|
+
function getArtifactManager(ctx) {
|
|
62
|
+
if (!artifactManagerMap.has(ctx)) {
|
|
63
|
+
const refIndex = getRefIndex(ctx);
|
|
64
|
+
// EntityManager.registerRef() requires setRefIndex to be called first.
|
|
65
|
+
ctx.entityManager.setRefIndex(refIndex);
|
|
66
|
+
artifactManagerMap.set(ctx, new ArtifactManager(ctx.storage, ctx.entityManager, refIndex));
|
|
67
|
+
}
|
|
68
|
+
return artifactManagerMap.get(ctx);
|
|
69
|
+
}
|
|
70
|
+
function getDistillationPipeline(ctx) {
|
|
71
|
+
if (!distillationPipelineMap.has(ctx)) {
|
|
72
|
+
distillationPipelineMap.set(ctx, new DistillationPipeline());
|
|
73
|
+
}
|
|
74
|
+
return distillationPipelineMap.get(ctx);
|
|
75
|
+
}
|
|
76
|
+
function getFailureDistillation(ctx) {
|
|
77
|
+
if (!failureDistillationMap.has(ctx)) {
|
|
78
|
+
failureDistillationMap.set(ctx, new FailureDistillation(ctx.storage));
|
|
79
|
+
}
|
|
80
|
+
return failureDistillationMap.get(ctx);
|
|
81
|
+
}
|
|
82
|
+
/** Simple token estimator: ~4 chars per token */
|
|
83
|
+
function estimateTokens(entity) {
|
|
84
|
+
const text = [entity.name, entity.entityType, ...entity.observations].join(' ');
|
|
85
|
+
return Math.ceil(text.length / 4);
|
|
86
|
+
}
|
|
13
87
|
/**
|
|
14
88
|
* Wrapper to apply automatic response compression for large tool responses.
|
|
15
89
|
*
|
|
@@ -69,6 +143,37 @@ export const toolHandlers = {
|
|
|
69
143
|
: [];
|
|
70
144
|
return withCompression(async () => formatToolResponse(await ctx.searchManager.openNodes(names)));
|
|
71
145
|
},
|
|
146
|
+
// Phase 13: Project scoping + memory versioning
|
|
147
|
+
list_projects: async (ctx, _args) => {
|
|
148
|
+
const projects = await ctx.entityManager.listProjects();
|
|
149
|
+
return formatToolResponse({ projects, count: projects.length });
|
|
150
|
+
},
|
|
151
|
+
get_entity_versions: async (ctx, args) => {
|
|
152
|
+
const entityName = validateWithSchema(args.entityName, z.string().min(1), 'Invalid entityName');
|
|
153
|
+
const latest = await ctx.entityManager.getLatestVersion(entityName);
|
|
154
|
+
if (!latest) {
|
|
155
|
+
return { content: [{ type: 'text', text: `Entity '${entityName}' not found` }], isError: true };
|
|
156
|
+
}
|
|
157
|
+
return formatToolResponse(latest);
|
|
158
|
+
},
|
|
159
|
+
get_version_chain: async (ctx, args) => {
|
|
160
|
+
const entityName = validateWithSchema(args.entityName, z.string().min(1), 'Invalid entityName');
|
|
161
|
+
const chain = await ctx.entityManager.getVersionChain(entityName);
|
|
162
|
+
if (chain.length === 0) {
|
|
163
|
+
return { content: [{ type: 'text', text: `Entity '${entityName}' not found` }], isError: true };
|
|
164
|
+
}
|
|
165
|
+
return formatToolResponse({
|
|
166
|
+
rootEntity: chain[0]?.name ?? null,
|
|
167
|
+
latestEntity: chain.find(e => e.isLatest !== false)?.name ?? null,
|
|
168
|
+
versions: chain.map(e => ({
|
|
169
|
+
name: e.name,
|
|
170
|
+
version: e.version ?? 1,
|
|
171
|
+
isLatest: e.isLatest !== false,
|
|
172
|
+
observations: e.observations,
|
|
173
|
+
})),
|
|
174
|
+
count: chain.length,
|
|
175
|
+
});
|
|
176
|
+
},
|
|
72
177
|
// ==================== RELATION HANDLERS ====================
|
|
73
178
|
create_relations: async (ctx, args) => {
|
|
74
179
|
const relations = validateWithSchema(args.relations, BatchCreateRelationsSchema, 'Invalid relations data');
|
|
@@ -79,6 +184,49 @@ export const toolHandlers = {
|
|
|
79
184
|
await ctx.relationManager.deleteRelations(relations);
|
|
80
185
|
return formatTextResponse(`Deleted ${relations.length} relations`);
|
|
81
186
|
},
|
|
187
|
+
// Phase 13: Temporal knowledge graph
|
|
188
|
+
invalidate_relation: async (ctx, args) => {
|
|
189
|
+
try {
|
|
190
|
+
const from = validateWithSchema(args.from, z.string().min(1), 'Invalid from');
|
|
191
|
+
const relationType = validateWithSchema(args.relationType, z.string().min(1), 'Invalid relationType');
|
|
192
|
+
const to = validateWithSchema(args.to, z.string().min(1), 'Invalid to');
|
|
193
|
+
const ended = args.ended !== undefined ? validateWithSchema(args.ended, z.string(), 'Invalid ended') : undefined;
|
|
194
|
+
await ctx.relationManager.invalidateRelation(from, relationType, to, ended);
|
|
195
|
+
return formatTextResponse(`Invalidated: ${from} -[${relationType}]-> ${to} (ended: ${ended ?? 'now'})`);
|
|
196
|
+
}
|
|
197
|
+
catch (err) {
|
|
198
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
199
|
+
return { content: [{ type: 'text', text: msg }], isError: true };
|
|
200
|
+
}
|
|
201
|
+
},
|
|
202
|
+
query_as_of: async (ctx, args) => {
|
|
203
|
+
const entityName = validateWithSchema(args.entityName, z.string().min(1), 'Invalid entityName');
|
|
204
|
+
const asOf = validateWithSchema(args.asOf, z.string().min(1), 'Invalid asOf');
|
|
205
|
+
const direction = args.direction !== undefined
|
|
206
|
+
? validateWithSchema(args.direction, z.enum(['outgoing', 'incoming', 'both']), 'Invalid direction')
|
|
207
|
+
: undefined;
|
|
208
|
+
const relations = await ctx.relationManager.queryAsOf(entityName, asOf, { direction });
|
|
209
|
+
return formatToolResponse({ entity: entityName, asOf, relations, count: relations.length });
|
|
210
|
+
},
|
|
211
|
+
timeline: async (ctx, args) => {
|
|
212
|
+
const entityName = validateWithSchema(args.entityName, z.string().min(1), 'Invalid entityName');
|
|
213
|
+
const direction = args.direction !== undefined
|
|
214
|
+
? validateWithSchema(args.direction, z.enum(['outgoing', 'incoming', 'both']), 'Invalid direction')
|
|
215
|
+
: undefined;
|
|
216
|
+
const relations = await ctx.relationManager.timeline(entityName, { direction });
|
|
217
|
+
return formatToolResponse({
|
|
218
|
+
entity: entityName,
|
|
219
|
+
timeline: relations.map(r => ({
|
|
220
|
+
from: r.from,
|
|
221
|
+
relationType: r.relationType,
|
|
222
|
+
to: r.to,
|
|
223
|
+
validFrom: r.properties?.validFrom ?? null,
|
|
224
|
+
validUntil: r.properties?.validUntil ?? null,
|
|
225
|
+
current: r.properties?.validUntil === undefined,
|
|
226
|
+
})),
|
|
227
|
+
count: relations.length,
|
|
228
|
+
});
|
|
229
|
+
},
|
|
82
230
|
// ==================== OBSERVATION HANDLERS ====================
|
|
83
231
|
add_observations: async (ctx, args) => {
|
|
84
232
|
const observations = validateWithSchema(args.observations, AddObservationsInputSchema, 'Invalid observations data');
|
|
@@ -187,6 +335,18 @@ export const toolHandlers = {
|
|
|
187
335
|
const limit = args.limit !== undefined ? validateWithSchema(args.limit, z.number().int().positive().max(200), 'Invalid limit') : undefined;
|
|
188
336
|
return formatToolResponse(await ctx.searchManager.autoSearch(query, limit));
|
|
189
337
|
},
|
|
338
|
+
// Phase 13: Semantic forget
|
|
339
|
+
forget_memory: async (ctx, args) => {
|
|
340
|
+
const content = validateWithSchema(args.content, z.string().min(1), 'Invalid content');
|
|
341
|
+
const threshold = args.threshold !== undefined ? validateWithSchema(args.threshold, z.number().min(0).max(1), 'Invalid threshold') : undefined;
|
|
342
|
+
const projectId = args.projectId !== undefined ? validateWithSchema(args.projectId, z.string(), 'Invalid projectId') : undefined;
|
|
343
|
+
const dryRun = args.dryRun === true;
|
|
344
|
+
const result = await ctx.semanticForget.forgetByContent(content, { threshold, projectId, dryRun });
|
|
345
|
+
if (result.method === 'not_found') {
|
|
346
|
+
return formatTextResponse(`No matching memory found for: "${content}". Try a different search term.`);
|
|
347
|
+
}
|
|
348
|
+
return formatToolResponse(result);
|
|
349
|
+
},
|
|
190
350
|
// Phase 11 Sprint 2: Hybrid search
|
|
191
351
|
hybrid_search: async (ctx, args) => {
|
|
192
352
|
const query = validateWithSchema(args.query, SearchQuerySchema, 'Invalid search query');
|
|
@@ -532,6 +692,22 @@ export const toolHandlers = {
|
|
|
532
692
|
const dryRun = args.dryRun !== undefined ? validateWithSchema(args.dryRun, z.boolean(), 'Invalid dryRun value') : undefined;
|
|
533
693
|
return formatToolResponse(await ctx.ioManager.importGraph(format, data, mergeStrategy, dryRun));
|
|
534
694
|
},
|
|
695
|
+
// Phase 13: Conversation ingestion
|
|
696
|
+
ingest: async (ctx, args) => {
|
|
697
|
+
if (!args.messages || args.messages.length === 0) {
|
|
698
|
+
return formatTextResponse('No messages provided. At least one message is required.');
|
|
699
|
+
}
|
|
700
|
+
const messages = validateWithSchema(args.messages, z.array(z.object({ role: z.enum(['user', 'assistant', 'system']), content: z.string(), timestamp: z.string().optional() })), 'Invalid messages');
|
|
701
|
+
const source = args.source !== undefined ? validateWithSchema(args.source, z.string(), 'Invalid source') : undefined;
|
|
702
|
+
const projectId = args.projectId !== undefined ? validateWithSchema(args.projectId, z.string(), 'Invalid projectId') : undefined;
|
|
703
|
+
const tags = args.tags !== undefined ? validateWithSchema(args.tags, z.array(z.string()), 'Invalid tags') : undefined;
|
|
704
|
+
const chunkBy = args.chunkBy !== undefined
|
|
705
|
+
? validateWithSchema(args.chunkBy, z.enum(['exchange', 'paragraph', 'fixed']), 'Invalid chunkBy')
|
|
706
|
+
: undefined;
|
|
707
|
+
const dryRun = args.dryRun === true;
|
|
708
|
+
const result = await ctx.ioManager.ingest({ messages, source }, { projectId, tags, chunkBy, dryRun });
|
|
709
|
+
return formatToolResponse(result);
|
|
710
|
+
},
|
|
535
711
|
export_graph: async (ctx, args) => {
|
|
536
712
|
const format = validateWithSchema(args.format, ExtendedExportFormatSchema, 'Invalid export format');
|
|
537
713
|
const filter = args.filter !== undefined ? validateWithSchema(args.filter, ExportFilterSchema, 'Invalid export filter') : undefined;
|
|
@@ -540,6 +716,7 @@ export const toolHandlers = {
|
|
|
540
716
|
? validateWithSchema(args.compressionQuality, z.number().int().min(0).max(11), 'Invalid compression quality (must be 0-11)')
|
|
541
717
|
: undefined;
|
|
542
718
|
const streaming = args.streaming !== undefined ? validateWithSchema(args.streaming, z.boolean(), 'Invalid streaming value') : undefined;
|
|
719
|
+
const redactPii = args.redactPii !== undefined ? validateWithSchema(args.redactPii, z.boolean(), 'Invalid redactPii value') : false;
|
|
543
720
|
const rawOutputPath = args.outputPath !== undefined ? validateWithSchema(args.outputPath, z.string(), 'Invalid outputPath value') : undefined;
|
|
544
721
|
// Validate outputPath to prevent path traversal attacks
|
|
545
722
|
const outputPath = rawOutputPath !== undefined ? validateFilePath(rawOutputPath) : undefined;
|
|
@@ -551,6 +728,10 @@ export const toolHandlers = {
|
|
|
551
728
|
else {
|
|
552
729
|
graph = await ctx.storage.loadGraph();
|
|
553
730
|
}
|
|
731
|
+
// η.6.3 — apply PII redactor on export when requested
|
|
732
|
+
if (redactPii) {
|
|
733
|
+
graph = new PiiRedactor().redactGraph(graph);
|
|
734
|
+
}
|
|
554
735
|
// Export with optional compression and streaming
|
|
555
736
|
const result = await ctx.ioManager.exportGraphWithCompression(graph, format, {
|
|
556
737
|
filter,
|
|
@@ -655,6 +836,1067 @@ export const toolHandlers = {
|
|
|
655
836
|
stats: semanticSearch.getStats(),
|
|
656
837
|
});
|
|
657
838
|
},
|
|
839
|
+
// ==================== REF INDEX HANDLERS ====================
|
|
840
|
+
register_ref: async (ctx, args) => {
|
|
841
|
+
const ref = validateWithSchema(args.ref, z.string().min(1), 'Invalid ref');
|
|
842
|
+
const entityName = validateWithSchema(args.entityName, z.string().min(1), 'Invalid entityName');
|
|
843
|
+
const description = args.description !== undefined
|
|
844
|
+
? validateWithSchema(args.description, z.string(), 'Invalid description')
|
|
845
|
+
: undefined;
|
|
846
|
+
const entry = await getRefIndex(ctx).register(ref, entityName, description);
|
|
847
|
+
return formatToolResponse(entry);
|
|
848
|
+
},
|
|
849
|
+
resolve_ref: async (ctx, args) => {
|
|
850
|
+
const ref = validateWithSchema(args.ref, z.string().min(1), 'Invalid ref');
|
|
851
|
+
const entityName = await getRefIndex(ctx).resolve(ref);
|
|
852
|
+
if (entityName === null) {
|
|
853
|
+
return formatTextResponse(`Ref "${ref}" is not registered`);
|
|
854
|
+
}
|
|
855
|
+
return formatToolResponse({ ref, entityName });
|
|
856
|
+
},
|
|
857
|
+
deregister_ref: async (ctx, args) => {
|
|
858
|
+
const ref = validateWithSchema(args.ref, z.string().min(1), 'Invalid ref');
|
|
859
|
+
await getRefIndex(ctx).deregister(ref);
|
|
860
|
+
return formatTextResponse(`Ref "${ref}" deregistered`);
|
|
861
|
+
},
|
|
862
|
+
list_refs: async (ctx, args) => {
|
|
863
|
+
const entityName = args.entityName !== undefined
|
|
864
|
+
? validateWithSchema(args.entityName, z.string().min(1), 'Invalid entityName')
|
|
865
|
+
: undefined;
|
|
866
|
+
const refs = await getRefIndex(ctx).listRefs(entityName ? { entityName } : undefined);
|
|
867
|
+
return formatToolResponse({ refs, count: refs.length });
|
|
868
|
+
},
|
|
869
|
+
// ==================== ARTIFACT HANDLERS ====================
|
|
870
|
+
create_artifact: async (ctx, args) => {
|
|
871
|
+
const content = validateWithSchema(args.content, z.string().min(1), 'Invalid content');
|
|
872
|
+
const toolName = validateWithSchema(args.toolName, z.string().min(1), 'Invalid toolName');
|
|
873
|
+
const artifactType = validateWithSchema(args.artifactType, z.enum(['tool_output', 'code_snippet', 'api_response', 'search_result', 'file_content', 'user_input']), 'Invalid artifactType');
|
|
874
|
+
const description = args.description !== undefined
|
|
875
|
+
? validateWithSchema(args.description, z.string(), 'Invalid description')
|
|
876
|
+
: undefined;
|
|
877
|
+
const sessionId = args.sessionId !== undefined
|
|
878
|
+
? validateWithSchema(args.sessionId, z.string(), 'Invalid sessionId')
|
|
879
|
+
: undefined;
|
|
880
|
+
const artifact = await getArtifactManager(ctx).createArtifact({
|
|
881
|
+
content,
|
|
882
|
+
toolName,
|
|
883
|
+
artifactType,
|
|
884
|
+
description,
|
|
885
|
+
sessionId,
|
|
886
|
+
});
|
|
887
|
+
return formatToolResponse(artifact);
|
|
888
|
+
},
|
|
889
|
+
get_artifact: async (ctx, args) => {
|
|
890
|
+
const ref = validateWithSchema(args.ref, z.string().min(1), 'Invalid ref');
|
|
891
|
+
const artifact = await getArtifactManager(ctx).getArtifact(ref);
|
|
892
|
+
if (!artifact) {
|
|
893
|
+
return formatTextResponse(`Artifact "${ref}" not found`);
|
|
894
|
+
}
|
|
895
|
+
return formatToolResponse(artifact);
|
|
896
|
+
},
|
|
897
|
+
list_artifacts: async (ctx, args) => {
|
|
898
|
+
const filter = {};
|
|
899
|
+
if (args.toolName !== undefined) {
|
|
900
|
+
filter.toolName = validateWithSchema(args.toolName, z.string(), 'Invalid toolName');
|
|
901
|
+
}
|
|
902
|
+
if (args.artifactType !== undefined) {
|
|
903
|
+
filter.artifactType = validateWithSchema(args.artifactType, z.enum(['tool_output', 'code_snippet', 'api_response', 'search_result', 'file_content', 'user_input']), 'Invalid artifactType');
|
|
904
|
+
}
|
|
905
|
+
if (args.since !== undefined) {
|
|
906
|
+
const sinceStr = validateWithSchema(args.since, z.string().regex(/^\d{4}-\d{2}-\d{2}(T[\d:.Z+-]+)?$/, 'since must be an ISO 8601 date string'), 'Invalid since');
|
|
907
|
+
const sinceDate = new Date(sinceStr);
|
|
908
|
+
if (isNaN(sinceDate.getTime())) {
|
|
909
|
+
throw new Error(`Invalid since date: "${sinceStr}" is not a valid date`);
|
|
910
|
+
}
|
|
911
|
+
filter.since = sinceDate;
|
|
912
|
+
}
|
|
913
|
+
const artifacts = await getArtifactManager(ctx).listArtifacts(Object.keys(filter).length > 0 ? filter : undefined);
|
|
914
|
+
return formatToolResponse({ artifacts, count: artifacts.length });
|
|
915
|
+
},
|
|
916
|
+
// ==================== TEMPORAL SEARCH HANDLER ====================
|
|
917
|
+
search_by_time: async (ctx, args) => {
|
|
918
|
+
const query = validateWithSchema(args.query, z.string().min(1), 'Invalid query');
|
|
919
|
+
const options = {};
|
|
920
|
+
if (args.field !== undefined) {
|
|
921
|
+
options.field = validateWithSchema(args.field, z.enum(['createdAt', 'lastModified', 'any']), 'Invalid field');
|
|
922
|
+
}
|
|
923
|
+
if (args.includeUndated !== undefined) {
|
|
924
|
+
options.includeUndated = validateWithSchema(args.includeUndated, z.boolean(), 'Invalid includeUndated');
|
|
925
|
+
}
|
|
926
|
+
const entities = await ctx.searchManager.searchByTime(query, options);
|
|
927
|
+
return formatToolResponse({ query, entities, count: entities.length });
|
|
928
|
+
},
|
|
929
|
+
// ==================== DISTILLATION HANDLER ====================
|
|
930
|
+
configure_distillation: async (ctx, args) => {
|
|
931
|
+
const policy = validateWithSchema(args.policy, z.enum(['default', 'noop', 'none']), 'Invalid policy');
|
|
932
|
+
const pipeline = getDistillationPipeline(ctx);
|
|
933
|
+
pipeline.clearPolicies();
|
|
934
|
+
if (policy === 'default') {
|
|
935
|
+
pipeline.addPolicy(new DefaultDistillationPolicy(), 'default');
|
|
936
|
+
}
|
|
937
|
+
else if (policy === 'noop') {
|
|
938
|
+
pipeline.addPolicy(new NoOpDistillationPolicy(), 'noop');
|
|
939
|
+
}
|
|
940
|
+
// 'none' clears all policies — passthrough behavior
|
|
941
|
+
return formatTextResponse(`Distillation pipeline configured with policy: "${policy}" (${pipeline.policyCount} policies active)`);
|
|
942
|
+
},
|
|
943
|
+
// ==================== FRESHNESS HANDLERS ====================
|
|
944
|
+
check_freshness: async (ctx, args) => {
|
|
945
|
+
const entityName = validateWithSchema(args.entityName, z.string().min(1), 'Invalid entityName');
|
|
946
|
+
const graph = await ctx.storage.loadGraph();
|
|
947
|
+
const entity = graph.entities.find(e => e.name === entityName);
|
|
948
|
+
if (!entity) {
|
|
949
|
+
return formatTextResponse(`Entity "${entityName}" not found`);
|
|
950
|
+
}
|
|
951
|
+
const fm = getFreshnessManager(ctx);
|
|
952
|
+
const annotated = fm.annotateEntity(entity);
|
|
953
|
+
return formatToolResponse({
|
|
954
|
+
entityName,
|
|
955
|
+
freshnessScore: fm.calculateFreshness(entity),
|
|
956
|
+
expiresAt: fm.computeExpiresAt(entity),
|
|
957
|
+
isExpired: fm.isExpired(entity),
|
|
958
|
+
annotated,
|
|
959
|
+
});
|
|
960
|
+
},
|
|
961
|
+
get_stale_entities: async (ctx, args) => {
|
|
962
|
+
const threshold = args.threshold !== undefined
|
|
963
|
+
? validateWithSchema(args.threshold, z.number().min(0).max(1), 'Invalid threshold (0-1)')
|
|
964
|
+
: undefined;
|
|
965
|
+
const fm = getFreshnessManager(ctx);
|
|
966
|
+
const stale = await fm.getStaleEntities(ctx.storage, threshold);
|
|
967
|
+
return formatToolResponse({ entities: stale, count: stale.length });
|
|
968
|
+
},
|
|
969
|
+
get_expired_entities: async (ctx) => {
|
|
970
|
+
const fm = getFreshnessManager(ctx);
|
|
971
|
+
const expired = await fm.getExpiredEntities(ctx.storage);
|
|
972
|
+
return formatToolResponse({ entities: expired, count: expired.length });
|
|
973
|
+
},
|
|
974
|
+
refresh_entity: async (ctx, args) => {
|
|
975
|
+
const entityName = validateWithSchema(args.entityName, z.string().min(1), 'Invalid entityName');
|
|
976
|
+
const fm = getFreshnessManager(ctx);
|
|
977
|
+
const updated = await fm.refreshEntity(entityName, ctx.storage);
|
|
978
|
+
return formatToolResponse({ updated, freshnessScore: fm.calculateFreshness(updated) });
|
|
979
|
+
},
|
|
980
|
+
freshness_report: async (ctx, args) => {
|
|
981
|
+
const threshold = args.threshold !== undefined
|
|
982
|
+
? validateWithSchema(args.threshold, z.number().min(0).max(1), 'Invalid threshold (0-1)')
|
|
983
|
+
: undefined;
|
|
984
|
+
const fm = getFreshnessManager(ctx);
|
|
985
|
+
const report = await fm.generateReport(ctx.storage, threshold);
|
|
986
|
+
return formatToolResponse(report);
|
|
987
|
+
},
|
|
988
|
+
// ==================== LLM QUERY PLANNER HANDLER ====================
|
|
989
|
+
query_natural_language: async (ctx, args) => {
|
|
990
|
+
const query = validateWithSchema(args.query, z.string().min(1), 'Invalid query');
|
|
991
|
+
const entities = await ctx.queryNaturalLanguage(query);
|
|
992
|
+
return formatToolResponse({ query, entities, count: entities.length });
|
|
993
|
+
},
|
|
994
|
+
// ==================== GOVERNANCE HANDLERS ====================
|
|
995
|
+
set_governance_policy: async (ctx, args) => {
|
|
996
|
+
const gm = getGovernanceManager(ctx);
|
|
997
|
+
// GovernancePolicy uses function callbacks; we translate boolean args into allow/deny functions
|
|
998
|
+
const allowCreate = args.canCreate !== undefined
|
|
999
|
+
? validateWithSchema(args.canCreate, z.boolean(), 'Invalid canCreate')
|
|
1000
|
+
: true;
|
|
1001
|
+
const allowUpdate = args.canUpdate !== undefined
|
|
1002
|
+
? validateWithSchema(args.canUpdate, z.boolean(), 'Invalid canUpdate')
|
|
1003
|
+
: true;
|
|
1004
|
+
const allowDelete = args.canDelete !== undefined
|
|
1005
|
+
? validateWithSchema(args.canDelete, z.boolean(), 'Invalid canDelete')
|
|
1006
|
+
: true;
|
|
1007
|
+
gm.setPolicy({
|
|
1008
|
+
canCreate: allowCreate ? undefined : () => false,
|
|
1009
|
+
canUpdate: allowUpdate ? undefined : () => false,
|
|
1010
|
+
canDelete: allowDelete ? undefined : () => false,
|
|
1011
|
+
});
|
|
1012
|
+
return formatTextResponse(`Governance policy set: canCreate=${allowCreate}, canUpdate=${allowUpdate}, canDelete=${allowDelete}`);
|
|
1013
|
+
},
|
|
1014
|
+
audit_query: async (ctx, args) => {
|
|
1015
|
+
const filter = {};
|
|
1016
|
+
if (args.operation !== undefined) {
|
|
1017
|
+
filter.operation = validateWithSchema(args.operation, z.enum(['create', 'update', 'delete', 'merge', 'archive']), 'Invalid operation');
|
|
1018
|
+
}
|
|
1019
|
+
if (args.agentId !== undefined) {
|
|
1020
|
+
filter.agentId = validateWithSchema(args.agentId, z.string(), 'Invalid agentId');
|
|
1021
|
+
}
|
|
1022
|
+
if (args.entityName !== undefined) {
|
|
1023
|
+
filter.entityName = validateWithSchema(args.entityName, z.string(), 'Invalid entityName');
|
|
1024
|
+
}
|
|
1025
|
+
// AuditFilter uses fromTime/toTime (not since/until)
|
|
1026
|
+
if (args.since !== undefined) {
|
|
1027
|
+
filter.fromTime = validateWithSchema(args.since, z.string(), 'Invalid since');
|
|
1028
|
+
}
|
|
1029
|
+
if (args.until !== undefined) {
|
|
1030
|
+
filter.toTime = validateWithSchema(args.until, z.string(), 'Invalid until');
|
|
1031
|
+
}
|
|
1032
|
+
const limit = args.limit !== undefined
|
|
1033
|
+
? validateWithSchema(args.limit, z.number().int().min(1).max(1000), 'Invalid limit')
|
|
1034
|
+
: 50;
|
|
1035
|
+
const al = getAuditLog(ctx);
|
|
1036
|
+
let entries = await al.query(filter);
|
|
1037
|
+
entries = entries.slice(0, limit);
|
|
1038
|
+
return formatToolResponse({ entries, count: entries.length });
|
|
1039
|
+
},
|
|
1040
|
+
audit_history: async (ctx, args) => {
|
|
1041
|
+
const entityName = validateWithSchema(args.entityName, z.string().min(1), 'Invalid entityName');
|
|
1042
|
+
const al = getAuditLog(ctx);
|
|
1043
|
+
const entries = await al.getHistory(entityName);
|
|
1044
|
+
return formatToolResponse({ entityName, entries, count: entries.length });
|
|
1045
|
+
},
|
|
1046
|
+
rollback_operation: async (ctx, args) => {
|
|
1047
|
+
const auditEntryId = validateWithSchema(args.auditEntryId, z.string().min(1), 'Invalid auditEntryId');
|
|
1048
|
+
const gm = getGovernanceManager(ctx);
|
|
1049
|
+
await gm.rollback(auditEntryId);
|
|
1050
|
+
return formatTextResponse(`Operation "${auditEntryId}" rolled back successfully`);
|
|
1051
|
+
},
|
|
1052
|
+
// ==================== ROLE PROFILE HANDLERS ====================
|
|
1053
|
+
set_agent_role: async (_ctx, args) => {
|
|
1054
|
+
const role = validateWithSchema(args.role, z.enum(['researcher', 'planner', 'executor', 'reviewer', 'default']), 'Invalid role');
|
|
1055
|
+
const profile = getRoleProfile(role);
|
|
1056
|
+
return formatToolResponse({
|
|
1057
|
+
role,
|
|
1058
|
+
label: profile.label,
|
|
1059
|
+
salienceConfig: profile.salienceConfig,
|
|
1060
|
+
contextConfig: profile.contextConfig,
|
|
1061
|
+
message: `Role profile "${role}" retrieved. Apply salienceConfig and contextConfig to your agent memory system.`,
|
|
1062
|
+
});
|
|
1063
|
+
},
|
|
1064
|
+
list_role_profiles: async (_ctx) => {
|
|
1065
|
+
const profiles = listRoleProfiles();
|
|
1066
|
+
return formatToolResponse({ profiles, count: profiles.length });
|
|
1067
|
+
},
|
|
1068
|
+
// ==================== ENTROPY FILTER HANDLERS ====================
|
|
1069
|
+
enable_entropy_filter: async (ctx, args) => {
|
|
1070
|
+
const enabled = validateWithSchema(args.enabled, z.boolean(), 'Invalid enabled');
|
|
1071
|
+
const minEntropy = args.minEntropy !== undefined
|
|
1072
|
+
? validateWithSchema(args.minEntropy, z.number().min(0), 'Invalid minEntropy')
|
|
1073
|
+
: 1.5;
|
|
1074
|
+
const minLength = args.minLength !== undefined
|
|
1075
|
+
? validateWithSchema(args.minLength, z.number().int().min(0), 'Invalid minLength')
|
|
1076
|
+
: 10;
|
|
1077
|
+
// Register (or clear) the entropy filter stage on the agent memory facade's pipeline
|
|
1078
|
+
const agentMem = ctx.agentMemory();
|
|
1079
|
+
const pipeline = agentMem.consolidationPipeline;
|
|
1080
|
+
if (enabled) {
|
|
1081
|
+
const stage = new EntropyFilterStage({ minEntropy, minLength });
|
|
1082
|
+
pipeline.registerStage(stage);
|
|
1083
|
+
return formatTextResponse(`Entropy filter enabled (minEntropy=${minEntropy}, minLength=${minLength}). Stage registered on consolidation pipeline.`);
|
|
1084
|
+
}
|
|
1085
|
+
else {
|
|
1086
|
+
return formatTextResponse('Entropy filter disabled. No new entropy-filter stage will be registered on the consolidation pipeline.');
|
|
1087
|
+
}
|
|
1088
|
+
},
|
|
1089
|
+
compute_entropy: async (_ctx, args) => {
|
|
1090
|
+
const text = validateWithSchema(args.text, z.string(), 'Invalid text');
|
|
1091
|
+
const entropy = computeEntropy(text);
|
|
1092
|
+
const result = { text: text.length > 100 ? text.slice(0, 100) + '...' : text, entropy };
|
|
1093
|
+
if (args.minEntropy !== undefined) {
|
|
1094
|
+
const minEntropy = validateWithSchema(args.minEntropy, z.number().min(0), 'Invalid minEntropy');
|
|
1095
|
+
result.minEntropy = minEntropy;
|
|
1096
|
+
result.passes = passesEntropyFilter(text, minEntropy);
|
|
1097
|
+
}
|
|
1098
|
+
return formatToolResponse(result);
|
|
1099
|
+
},
|
|
1100
|
+
// ==================== CONSOLIDATION HANDLERS ====================
|
|
1101
|
+
start_consolidation: async (ctx, args) => {
|
|
1102
|
+
const intervalMs = args.intervalMs !== undefined
|
|
1103
|
+
? validateWithSchema(args.intervalMs, z.number().int().min(1000), 'Invalid intervalMs')
|
|
1104
|
+
: undefined;
|
|
1105
|
+
const autoMergeDuplicates = args.autoMergeDuplicates !== undefined
|
|
1106
|
+
? validateWithSchema(args.autoMergeDuplicates, z.boolean(), 'Invalid autoMergeDuplicates')
|
|
1107
|
+
: undefined;
|
|
1108
|
+
// Prefer ctx.consolidationScheduler (set via MEMORY_AUTO_CONSOLIDATION env var).
|
|
1109
|
+
// Otherwise use the per-ctx singleton so stop/run_now can retrieve the same instance.
|
|
1110
|
+
let scheduler = ctx.consolidationScheduler ?? consolidationSchedulerMap.get(ctx);
|
|
1111
|
+
if (!scheduler) {
|
|
1112
|
+
const agentMem = ctx.agentMemory();
|
|
1113
|
+
scheduler = new ConsolidationScheduler(agentMem.consolidationPipeline, ctx.compressionManager, {
|
|
1114
|
+
consolidationIntervalMs: intervalMs,
|
|
1115
|
+
autoMergeDuplicates: autoMergeDuplicates,
|
|
1116
|
+
});
|
|
1117
|
+
consolidationSchedulerMap.set(ctx, scheduler);
|
|
1118
|
+
}
|
|
1119
|
+
scheduler.start();
|
|
1120
|
+
return formatTextResponse(`Consolidation scheduler started (interval: ${scheduler.getInterval()}ms, autoMerge: ${scheduler.getConfig().autoMergeDuplicates})`);
|
|
1121
|
+
},
|
|
1122
|
+
stop_consolidation: async (ctx) => {
|
|
1123
|
+
const scheduler = ctx.consolidationScheduler ?? consolidationSchedulerMap.get(ctx);
|
|
1124
|
+
if (!scheduler) {
|
|
1125
|
+
return formatTextResponse('No active consolidation scheduler found. Start one first with start_consolidation.');
|
|
1126
|
+
}
|
|
1127
|
+
scheduler.stop();
|
|
1128
|
+
return formatTextResponse('Consolidation scheduler stopped');
|
|
1129
|
+
},
|
|
1130
|
+
run_consolidation_now: async (ctx) => {
|
|
1131
|
+
// Use ctx.consolidationScheduler or the per-ctx singleton if available,
|
|
1132
|
+
// otherwise create a temporary scheduler just for this one run.
|
|
1133
|
+
let scheduler = ctx.consolidationScheduler ?? consolidationSchedulerMap.get(ctx);
|
|
1134
|
+
if (!scheduler) {
|
|
1135
|
+
const agentMem = ctx.agentMemory();
|
|
1136
|
+
scheduler = new ConsolidationScheduler(agentMem.consolidationPipeline, ctx.compressionManager);
|
|
1137
|
+
}
|
|
1138
|
+
const result = await scheduler.runNow();
|
|
1139
|
+
return formatToolResponse(result);
|
|
1140
|
+
},
|
|
1141
|
+
// ==================== DREAM ENGINE HANDLERS ====================
|
|
1142
|
+
dream_start: async (ctx, args) => {
|
|
1143
|
+
const intervalMs = args.intervalMs !== undefined
|
|
1144
|
+
? validateWithSchema(args.intervalMs, z.number().int().min(1000), 'Invalid intervalMs')
|
|
1145
|
+
: undefined;
|
|
1146
|
+
const runOnSessionEnd = args.runOnSessionEnd !== undefined
|
|
1147
|
+
? validateWithSchema(args.runOnSessionEnd, z.boolean(), 'Invalid runOnSessionEnd')
|
|
1148
|
+
: undefined;
|
|
1149
|
+
const maxDurationMs = args.maxDurationMs !== undefined
|
|
1150
|
+
? validateWithSchema(args.maxDurationMs, z.number().int().min(1000), 'Invalid maxDurationMs')
|
|
1151
|
+
: undefined;
|
|
1152
|
+
const phases = args.phases !== undefined
|
|
1153
|
+
? validateWithSchema(args.phases, z.object({
|
|
1154
|
+
temporalAnchoring: z.boolean().optional(),
|
|
1155
|
+
freshnessSweep: z.boolean().optional(),
|
|
1156
|
+
entropyPruning: z.boolean().optional(),
|
|
1157
|
+
consolidation: z.boolean().optional(),
|
|
1158
|
+
compression: z.boolean().optional(),
|
|
1159
|
+
entityEnrichment: z.boolean().optional(),
|
|
1160
|
+
patternPromotion: z.boolean().optional(),
|
|
1161
|
+
graphHygiene: z.boolean().optional(),
|
|
1162
|
+
}), 'Invalid phases')
|
|
1163
|
+
: undefined;
|
|
1164
|
+
let engine = dreamEngineMap.get(ctx);
|
|
1165
|
+
if (!engine) {
|
|
1166
|
+
const agentMem = ctx.agentMemory();
|
|
1167
|
+
const config = {
|
|
1168
|
+
...(intervalMs !== undefined && { intervalMs }),
|
|
1169
|
+
...(runOnSessionEnd !== undefined && { runOnSessionEnd }),
|
|
1170
|
+
...(maxDurationMs !== undefined && { maxDurationMs }),
|
|
1171
|
+
...(phases !== undefined && { phases }),
|
|
1172
|
+
};
|
|
1173
|
+
engine = new DreamEngine(ctx.storage, agentMem.consolidationPipeline, config);
|
|
1174
|
+
dreamEngineMap.set(ctx, engine);
|
|
1175
|
+
}
|
|
1176
|
+
engine.start();
|
|
1177
|
+
return formatTextResponse('DreamEngine started — background memory maintenance active');
|
|
1178
|
+
},
|
|
1179
|
+
dream_stop: async (ctx) => {
|
|
1180
|
+
const engine = dreamEngineMap.get(ctx);
|
|
1181
|
+
if (!engine) {
|
|
1182
|
+
return formatTextResponse('No active DreamEngine found. Start one first with dream_start.');
|
|
1183
|
+
}
|
|
1184
|
+
engine.stop();
|
|
1185
|
+
return formatTextResponse('DreamEngine stopped');
|
|
1186
|
+
},
|
|
1187
|
+
dream_run_now: async (ctx, args) => {
|
|
1188
|
+
const phases = args.phases !== undefined
|
|
1189
|
+
? validateWithSchema(args.phases, z.object({
|
|
1190
|
+
temporalAnchoring: z.boolean().optional(),
|
|
1191
|
+
freshnessSweep: z.boolean().optional(),
|
|
1192
|
+
entropyPruning: z.boolean().optional(),
|
|
1193
|
+
consolidation: z.boolean().optional(),
|
|
1194
|
+
compression: z.boolean().optional(),
|
|
1195
|
+
entityEnrichment: z.boolean().optional(),
|
|
1196
|
+
patternPromotion: z.boolean().optional(),
|
|
1197
|
+
graphHygiene: z.boolean().optional(),
|
|
1198
|
+
}), 'Invalid phases')
|
|
1199
|
+
: undefined;
|
|
1200
|
+
// Use the per-ctx singleton if available, otherwise create a one-off engine.
|
|
1201
|
+
let engine = dreamEngineMap.get(ctx);
|
|
1202
|
+
if (!engine) {
|
|
1203
|
+
const agentMem = ctx.agentMemory();
|
|
1204
|
+
const config = {
|
|
1205
|
+
...(phases !== undefined && { phases }),
|
|
1206
|
+
};
|
|
1207
|
+
engine = new DreamEngine(ctx.storage, agentMem.consolidationPipeline, config);
|
|
1208
|
+
}
|
|
1209
|
+
const result = await engine.runDreamCycle();
|
|
1210
|
+
return formatToolResponse(result);
|
|
1211
|
+
},
|
|
1212
|
+
// ==================== MEMORY FORMATTER HANDLER ====================
|
|
1213
|
+
format_with_salience_budget: async (ctx, args) => {
|
|
1214
|
+
const entityNames = validateWithSchema(args.entityNames, z.array(z.string().min(1)), 'Invalid entityNames');
|
|
1215
|
+
const salienceScoresRaw = validateWithSchema(args.salienceScores, z.record(z.string(), z.number()), 'Invalid salienceScores');
|
|
1216
|
+
const totalTokenBudget = validateWithSchema(args.totalTokenBudget, z.number().int().min(1), 'Invalid totalTokenBudget');
|
|
1217
|
+
const header = args.header !== undefined
|
|
1218
|
+
? validateWithSchema(args.header, z.string(), 'Invalid header')
|
|
1219
|
+
: undefined;
|
|
1220
|
+
const separator = args.separator !== undefined
|
|
1221
|
+
? validateWithSchema(args.separator, z.string(), 'Invalid separator')
|
|
1222
|
+
: undefined;
|
|
1223
|
+
const graph = await ctx.storage.loadGraph();
|
|
1224
|
+
const memories = graph.entities.filter(e => entityNames.includes(e.name));
|
|
1225
|
+
const salienceScores = new Map(Object.entries(salienceScoresRaw));
|
|
1226
|
+
const formatted = ctx.memoryFormatter.formatWithSalienceBudget(memories, salienceScores, totalTokenBudget, { header, separator });
|
|
1227
|
+
return formatTextResponse(formatted);
|
|
1228
|
+
},
|
|
1229
|
+
// ==================== COLLABORATIVE SYNTHESIS HANDLER ====================
|
|
1230
|
+
synthesize_collaborative_context: async (ctx, args) => {
|
|
1231
|
+
const seedEntityName = validateWithSchema(args.seedEntityName, z.string().min(1), 'Invalid seedEntityName');
|
|
1232
|
+
const config = {};
|
|
1233
|
+
if (args.maxDepth !== undefined) {
|
|
1234
|
+
config.maxDepth = validateWithSchema(args.maxDepth, z.number().int().min(1).max(10), 'Invalid maxDepth');
|
|
1235
|
+
}
|
|
1236
|
+
if (args.minNeighborSalience !== undefined) {
|
|
1237
|
+
config.minNeighborSalience = validateWithSchema(args.minNeighborSalience, z.number().min(0).max(1), 'Invalid minNeighborSalience');
|
|
1238
|
+
}
|
|
1239
|
+
if (args.maxNeighbors !== undefined) {
|
|
1240
|
+
config.maxNeighbors = validateWithSchema(args.maxNeighbors, z.number().int().min(1).max(100), 'Invalid maxNeighbors');
|
|
1241
|
+
}
|
|
1242
|
+
const salienceContext = {};
|
|
1243
|
+
if (args.queryText !== undefined) {
|
|
1244
|
+
salienceContext.queryText = validateWithSchema(args.queryText, z.string(), 'Invalid queryText');
|
|
1245
|
+
}
|
|
1246
|
+
if (args.currentTask !== undefined) {
|
|
1247
|
+
salienceContext.currentTask = validateWithSchema(args.currentTask, z.string(), 'Invalid currentTask');
|
|
1248
|
+
}
|
|
1249
|
+
const synthesis = new CollaborativeSynthesis(ctx.storage, ctx.graphTraversal, ctx.salienceEngine, config);
|
|
1250
|
+
return withCompression(async () => {
|
|
1251
|
+
const result = await synthesis.synthesize(seedEntityName, Object.keys(salienceContext).length > 0 ? salienceContext : undefined);
|
|
1252
|
+
return formatToolResponse(result);
|
|
1253
|
+
});
|
|
1254
|
+
},
|
|
1255
|
+
// ==================== FAILURE DISTILLATION HANDLERS ====================
|
|
1256
|
+
distill_failure: async (ctx, args) => {
|
|
1257
|
+
const sessionId = validateWithSchema(args.sessionId, z.string().min(1), 'Invalid sessionId');
|
|
1258
|
+
const config = {};
|
|
1259
|
+
if (args.minLessonConfidence !== undefined) {
|
|
1260
|
+
config.minLessonConfidence = validateWithSchema(args.minLessonConfidence, z.number().min(0).max(1), 'Invalid minLessonConfidence');
|
|
1261
|
+
}
|
|
1262
|
+
if (args.maxCauseChainLength !== undefined) {
|
|
1263
|
+
config.maxCauseChainLength = validateWithSchema(args.maxCauseChainLength, z.number().int().min(1).max(20), 'Invalid maxCauseChainLength');
|
|
1264
|
+
}
|
|
1265
|
+
const fd = Object.keys(config).length > 0
|
|
1266
|
+
? new FailureDistillation(ctx.storage, config)
|
|
1267
|
+
: getFailureDistillation(ctx);
|
|
1268
|
+
const result = await fd.distillFromSession(sessionId);
|
|
1269
|
+
return formatToolResponse(result);
|
|
1270
|
+
},
|
|
1271
|
+
end_session: async (ctx, args) => {
|
|
1272
|
+
const sessionId = validateWithSchema(args.sessionId, z.string().min(1), 'Invalid sessionId');
|
|
1273
|
+
const outcome = validateWithSchema(args.outcome, z.enum(['success', 'failure', 'partial']), 'Invalid outcome');
|
|
1274
|
+
const distillFailures = args.distillFailures !== undefined
|
|
1275
|
+
? validateWithSchema(args.distillFailures, z.boolean(), 'Invalid distillFailures')
|
|
1276
|
+
: true;
|
|
1277
|
+
const graph = await ctx.storage.loadGraph();
|
|
1278
|
+
const sessionEntity = graph.entities.find(e => e.name === sessionId);
|
|
1279
|
+
if (!sessionEntity) {
|
|
1280
|
+
return formatTextResponse(`Session "${sessionId}" not found`);
|
|
1281
|
+
}
|
|
1282
|
+
// Update session outcome
|
|
1283
|
+
const updatedEntities = graph.entities.map(e => e.name === sessionId
|
|
1284
|
+
? { ...e, observations: [...e.observations, `outcome: ${outcome}`], lastModified: new Date().toISOString() }
|
|
1285
|
+
: e);
|
|
1286
|
+
await ctx.storage.saveGraph({ entities: updatedEntities, relations: [...graph.relations] });
|
|
1287
|
+
let distillationResult = null;
|
|
1288
|
+
if (outcome === 'failure' && distillFailures) {
|
|
1289
|
+
const fd = getFailureDistillation(ctx);
|
|
1290
|
+
distillationResult = await fd.distillFromSession(sessionId);
|
|
1291
|
+
}
|
|
1292
|
+
return formatToolResponse({
|
|
1293
|
+
sessionId,
|
|
1294
|
+
outcome,
|
|
1295
|
+
distillationResult,
|
|
1296
|
+
message: `Session "${sessionId}" ended with outcome: ${outcome}`,
|
|
1297
|
+
});
|
|
1298
|
+
},
|
|
1299
|
+
// ==================== COGNITIVE LOAD HANDLERS ====================
|
|
1300
|
+
analyze_cognitive_load: async (ctx, args) => {
|
|
1301
|
+
const entityNames = validateWithSchema(args.entityNames, z.array(z.string().min(1)), 'Invalid entityNames');
|
|
1302
|
+
const loadThreshold = args.loadThreshold !== undefined
|
|
1303
|
+
? validateWithSchema(args.loadThreshold, z.number().min(0).max(1), 'Invalid loadThreshold')
|
|
1304
|
+
: undefined;
|
|
1305
|
+
const graph = await ctx.storage.loadGraph();
|
|
1306
|
+
const memories = graph.entities.filter((e) => entityNames.includes(e.name));
|
|
1307
|
+
if (memories.length === 0) {
|
|
1308
|
+
return formatTextResponse('No entities found matching the provided names');
|
|
1309
|
+
}
|
|
1310
|
+
const analyzer = new CognitiveLoadAnalyzer(loadThreshold ? { loadThreshold } : undefined);
|
|
1311
|
+
const metrics = analyzer.computeMetrics(memories, estimateTokens);
|
|
1312
|
+
return formatToolResponse({ entityCount: memories.length, metrics });
|
|
1313
|
+
},
|
|
1314
|
+
adaptive_reduce_memories: async (ctx, args) => {
|
|
1315
|
+
const entityNames = validateWithSchema(args.entityNames, z.array(z.string().min(1)), 'Invalid entityNames');
|
|
1316
|
+
const salienceScoresRaw = validateWithSchema(args.salienceScores, z.record(z.string(), z.number()), 'Invalid salienceScores');
|
|
1317
|
+
const loadThreshold = args.loadThreshold !== undefined
|
|
1318
|
+
? validateWithSchema(args.loadThreshold, z.number().min(0).max(1), 'Invalid loadThreshold')
|
|
1319
|
+
: undefined;
|
|
1320
|
+
const graph = await ctx.storage.loadGraph();
|
|
1321
|
+
const memories = graph.entities.filter((e) => entityNames.includes(e.name));
|
|
1322
|
+
if (memories.length === 0) {
|
|
1323
|
+
return formatTextResponse('No entities found matching the provided names');
|
|
1324
|
+
}
|
|
1325
|
+
const salienceScores = new Map(Object.entries(salienceScoresRaw));
|
|
1326
|
+
const analyzer = new CognitiveLoadAnalyzer(loadThreshold ? { loadThreshold } : undefined);
|
|
1327
|
+
const result = analyzer.adaptiveReduce(memories, salienceScores, estimateTokens);
|
|
1328
|
+
return formatToolResponse({
|
|
1329
|
+
retained: result.retained.map(e => e.name),
|
|
1330
|
+
removed: result.removed.map(e => e.name),
|
|
1331
|
+
retainedCount: result.retained.length,
|
|
1332
|
+
removedCount: result.removed.length,
|
|
1333
|
+
beforeMetrics: result.beforeMetrics,
|
|
1334
|
+
afterMetrics: result.afterMetrics,
|
|
1335
|
+
});
|
|
1336
|
+
},
|
|
1337
|
+
// Phase 13: User profile + agent diary
|
|
1338
|
+
get_profile: async (ctx, args) => {
|
|
1339
|
+
const projectId = args.projectId !== undefined ? validateWithSchema(args.projectId, z.string(), 'Invalid projectId') : undefined;
|
|
1340
|
+
const amm = ctx.agentMemory();
|
|
1341
|
+
const profile = await amm.profileManager.getProfile({ projectId });
|
|
1342
|
+
return formatToolResponse(profile);
|
|
1343
|
+
},
|
|
1344
|
+
update_profile: async (ctx, args) => {
|
|
1345
|
+
const content = validateWithSchema(args.content, z.string().min(1), 'Invalid content');
|
|
1346
|
+
const type = validateWithSchema(args.type, z.enum(['static', 'dynamic']), 'Invalid type');
|
|
1347
|
+
const projectId = args.projectId !== undefined ? validateWithSchema(args.projectId, z.string(), 'Invalid projectId') : undefined;
|
|
1348
|
+
const amm = ctx.agentMemory();
|
|
1349
|
+
await amm.profileManager.addFact(content, type, { projectId });
|
|
1350
|
+
return formatTextResponse(`Added ${type} profile fact: "${content}"`);
|
|
1351
|
+
},
|
|
1352
|
+
diary_write: async (ctx, args) => {
|
|
1353
|
+
const agentId = validateWithSchema(args.agentId, z.string().min(1), 'Invalid agentId');
|
|
1354
|
+
const entry = validateWithSchema(args.entry, z.string().min(1), 'Invalid entry');
|
|
1355
|
+
const topic = args.topic !== undefined ? validateWithSchema(args.topic, z.string(), 'Invalid topic') : undefined;
|
|
1356
|
+
const amm = ctx.agentMemory();
|
|
1357
|
+
await amm.writeDiary(agentId, entry, { topic });
|
|
1358
|
+
return formatTextResponse(`Diary entry written for agent '${agentId}'`);
|
|
1359
|
+
},
|
|
1360
|
+
diary_read: async (ctx, args) => {
|
|
1361
|
+
const agentId = validateWithSchema(args.agentId, z.string().min(1), 'Invalid agentId');
|
|
1362
|
+
const lastN = args.lastN !== undefined ? validateWithSchema(args.lastN, z.number().int().positive(), 'Invalid lastN') : undefined;
|
|
1363
|
+
const topic = args.topic !== undefined ? validateWithSchema(args.topic, z.string(), 'Invalid topic') : undefined;
|
|
1364
|
+
const amm = ctx.agentMemory();
|
|
1365
|
+
const entries = await amm.readDiary(agentId, { lastN, topic });
|
|
1366
|
+
return formatToolResponse({ agentId, entries, count: entries.length });
|
|
1367
|
+
},
|
|
1368
|
+
// ==================== SESSION & WORKING MEMORY HANDLERS ====================
|
|
1369
|
+
session_start: async (ctx, args) => {
|
|
1370
|
+
const goalDescription = args.taskDescription !== undefined ? validateWithSchema(args.taskDescription, z.string(), 'Invalid taskDescription') : undefined;
|
|
1371
|
+
const previousSessionId = args.parentSessionId !== undefined ? validateWithSchema(args.parentSessionId, z.string(), 'Invalid parentSessionId') : undefined;
|
|
1372
|
+
const amm = ctx.agentMemory();
|
|
1373
|
+
const session = await amm.startSession({ goalDescription, previousSessionId });
|
|
1374
|
+
return formatToolResponse(session);
|
|
1375
|
+
},
|
|
1376
|
+
session_end: async (ctx, args) => {
|
|
1377
|
+
const sessionId = validateWithSchema(args.sessionId, z.string().min(1), 'Invalid sessionId');
|
|
1378
|
+
const status = args.status !== undefined
|
|
1379
|
+
? validateWithSchema(args.status, z.enum(['completed', 'abandoned']), 'Invalid status')
|
|
1380
|
+
: undefined;
|
|
1381
|
+
const amm = ctx.agentMemory();
|
|
1382
|
+
const result = await amm.endSession(sessionId, status);
|
|
1383
|
+
return formatToolResponse(result);
|
|
1384
|
+
},
|
|
1385
|
+
session_checkpoint: async (ctx, args) => {
|
|
1386
|
+
const sessionId = validateWithSchema(args.sessionId, z.string().min(1), 'Invalid sessionId');
|
|
1387
|
+
const name = args.name !== undefined ? validateWithSchema(args.name, z.string(), 'Invalid name') : undefined;
|
|
1388
|
+
const amm = ctx.agentMemory();
|
|
1389
|
+
const checkpoint = await amm.checkpointSession(sessionId, name);
|
|
1390
|
+
return formatToolResponse(checkpoint);
|
|
1391
|
+
},
|
|
1392
|
+
session_restore: async (ctx, args) => {
|
|
1393
|
+
const checkpointId = validateWithSchema(args.checkpointId, z.string().min(1), 'Invalid checkpointId');
|
|
1394
|
+
const amm = ctx.agentMemory();
|
|
1395
|
+
await amm.restoreSession(checkpointId);
|
|
1396
|
+
return formatTextResponse(`Session restored from checkpoint '${checkpointId}'`);
|
|
1397
|
+
},
|
|
1398
|
+
add_working_memory: async (ctx, args) => {
|
|
1399
|
+
const sessionId = validateWithSchema(args.sessionId, z.string().min(1), 'Invalid sessionId');
|
|
1400
|
+
const content = validateWithSchema(args.content, z.string().min(1), 'Invalid content');
|
|
1401
|
+
const taskId = args.taskId !== undefined ? validateWithSchema(args.taskId, z.string(), 'Invalid taskId') : undefined;
|
|
1402
|
+
const importance = args.importance !== undefined ? validateWithSchema(args.importance, z.number().min(0).max(10), 'Invalid importance') : undefined;
|
|
1403
|
+
const ttlHours = args.ttlHours !== undefined ? validateWithSchema(args.ttlHours, z.number().positive(), 'Invalid ttlHours') : undefined;
|
|
1404
|
+
const amm = ctx.agentMemory();
|
|
1405
|
+
const memory = await amm.addWorkingMemory({ sessionId, content, taskId, importance, ttlHours });
|
|
1406
|
+
return formatToolResponse(memory);
|
|
1407
|
+
},
|
|
1408
|
+
promote_working_memory: async (ctx, args) => {
|
|
1409
|
+
const memoryName = validateWithSchema(args.memoryName, z.string().min(1), 'Invalid memoryName');
|
|
1410
|
+
const targetType = args.targetType !== undefined
|
|
1411
|
+
? validateWithSchema(args.targetType, z.enum(['episodic', 'semantic']), 'Invalid targetType')
|
|
1412
|
+
: undefined;
|
|
1413
|
+
const amm = ctx.agentMemory();
|
|
1414
|
+
const result = await amm.promoteMemory(memoryName, targetType);
|
|
1415
|
+
return formatToolResponse(result);
|
|
1416
|
+
},
|
|
1417
|
+
confirm_memory: async (ctx, args) => {
|
|
1418
|
+
const memoryName = validateWithSchema(args.memoryName, z.string().min(1), 'Invalid memoryName');
|
|
1419
|
+
const confidenceBoost = args.confidenceBoost !== undefined ? validateWithSchema(args.confidenceBoost, z.number(), 'Invalid confidenceBoost') : undefined;
|
|
1420
|
+
const amm = ctx.agentMemory();
|
|
1421
|
+
const result = await amm.confirmMemory(memoryName, confidenceBoost);
|
|
1422
|
+
return formatToolResponse(result);
|
|
1423
|
+
},
|
|
1424
|
+
clear_expired_memories: async (ctx) => {
|
|
1425
|
+
const amm = ctx.agentMemory();
|
|
1426
|
+
const count = await amm.clearExpiredMemories();
|
|
1427
|
+
return formatTextResponse(`Cleared ${count} expired working memories`);
|
|
1428
|
+
},
|
|
1429
|
+
wake_up: async (ctx, args) => {
|
|
1430
|
+
const compress = args.compress !== undefined ? validateWithSchema(args.compress, z.boolean(), 'Invalid compress') : undefined;
|
|
1431
|
+
const result = await ctx.contextWindowManager.wakeUp({ compress });
|
|
1432
|
+
return formatToolResponse(result);
|
|
1433
|
+
},
|
|
1434
|
+
// ==================== AUTO-ENHANCEMENT HANDLERS ====================
|
|
1435
|
+
auto_link_observations: async (ctx, args) => {
|
|
1436
|
+
const text = validateWithSchema(args.text, z.string().min(1), 'Invalid text');
|
|
1437
|
+
const graph = await ctx.storage.loadGraph();
|
|
1438
|
+
const mentions = ctx.autoLinker.detectMentions(text, graph.entities);
|
|
1439
|
+
return formatToolResponse({ mentions, count: mentions.length });
|
|
1440
|
+
},
|
|
1441
|
+
extract_facts: async (ctx, args) => {
|
|
1442
|
+
const text = validateWithSchema(args.text, z.string().min(1), 'Invalid text');
|
|
1443
|
+
const facts = ctx.factExtractor.extract(text);
|
|
1444
|
+
return formatToolResponse({ facts, count: facts.length });
|
|
1445
|
+
},
|
|
1446
|
+
detect_contradictions: async (ctx, args) => {
|
|
1447
|
+
const entityName = validateWithSchema(args.entityName, z.string().min(1), 'Invalid entityName');
|
|
1448
|
+
const threshold = args.threshold !== undefined ? validateWithSchema(args.threshold, z.number().min(0).max(1), 'Invalid threshold') : undefined;
|
|
1449
|
+
const graph = await ctx.storage.loadGraph();
|
|
1450
|
+
const entity = graph.entities.find(e => e.name === entityName);
|
|
1451
|
+
if (!entity) {
|
|
1452
|
+
return { content: [{ type: 'text', text: `Entity '${entityName}' not found` }], isError: true };
|
|
1453
|
+
}
|
|
1454
|
+
if (!ctx.semanticSearch) {
|
|
1455
|
+
return formatTextResponse('Contradiction detection requires semantic search (set MEMORY_EMBEDDING_PROVIDER)');
|
|
1456
|
+
}
|
|
1457
|
+
const detector = new ContradictionDetector(ctx.semanticSearch, threshold ?? 0.85);
|
|
1458
|
+
const contradictions = await detector.detect(entity, entity.observations);
|
|
1459
|
+
return formatToolResponse({ entityName, contradictions, count: contradictions.length });
|
|
1460
|
+
},
|
|
1461
|
+
consolidate_session: async (ctx, args) => {
|
|
1462
|
+
const sessionId = validateWithSchema(args.sessionId, z.string().min(1), 'Invalid sessionId');
|
|
1463
|
+
const amm = ctx.agentMemory();
|
|
1464
|
+
const result = await amm.consolidateSession(sessionId);
|
|
1465
|
+
return formatToolResponse(result);
|
|
1466
|
+
},
|
|
1467
|
+
detect_patterns: async (ctx, args) => {
|
|
1468
|
+
const entityType = validateWithSchema(args.entityType, z.string().min(1), 'Invalid entityType');
|
|
1469
|
+
const minOccurrences = args.minOccurrences !== undefined ? validateWithSchema(args.minOccurrences, z.number().int().min(2), 'Invalid minOccurrences') : undefined;
|
|
1470
|
+
const amm = ctx.agentMemory();
|
|
1471
|
+
const patterns = await amm.consolidationPipeline.extractPatterns(entityType, minOccurrences);
|
|
1472
|
+
return formatToolResponse({ patterns, count: patterns.length });
|
|
1473
|
+
},
|
|
1474
|
+
summarize_entity: async (ctx, args) => {
|
|
1475
|
+
const entityName = validateWithSchema(args.entityName, z.string().min(1), 'Invalid entityName');
|
|
1476
|
+
const threshold = args.threshold !== undefined ? validateWithSchema(args.threshold, z.number().min(0).max(1), 'Invalid threshold') : undefined;
|
|
1477
|
+
const amm = ctx.agentMemory();
|
|
1478
|
+
const result = await amm.consolidationPipeline.applySummarizationToEntity(entityName, threshold);
|
|
1479
|
+
return formatToolResponse(result);
|
|
1480
|
+
},
|
|
1481
|
+
priority_dedup: async (ctx) => {
|
|
1482
|
+
const result = await ctx.compressionManager.priorityDedup();
|
|
1483
|
+
return formatToolResponse(result);
|
|
1484
|
+
},
|
|
1485
|
+
// ==================== CONTEXT COMPRESSION HANDLERS ====================
|
|
1486
|
+
compress_context: async (ctx, args) => {
|
|
1487
|
+
const text = validateWithSchema(args.text, z.string().min(1), 'Invalid text');
|
|
1488
|
+
const level = args.level !== undefined
|
|
1489
|
+
? validateWithSchema(args.level, z.enum(['light', 'medium', 'aggressive']), 'Invalid level')
|
|
1490
|
+
: undefined;
|
|
1491
|
+
const result = ctx.contextWindowManager.compressForContext(text, { level });
|
|
1492
|
+
return formatToolResponse(result);
|
|
1493
|
+
},
|
|
1494
|
+
// ==================== DECAY & SALIENCE HANDLERS ====================
|
|
1495
|
+
run_decay_cycle: async (ctx) => {
|
|
1496
|
+
const amm = ctx.agentMemory();
|
|
1497
|
+
const result = await amm.runDecayCycle();
|
|
1498
|
+
return formatToolResponse(result);
|
|
1499
|
+
},
|
|
1500
|
+
get_decayed_memories: async (ctx, args) => {
|
|
1501
|
+
const threshold = args.threshold !== undefined ? validateWithSchema(args.threshold, z.number(), 'Invalid threshold') : undefined;
|
|
1502
|
+
const amm = ctx.agentMemory();
|
|
1503
|
+
const memories = await amm.getDecayedMemories(threshold);
|
|
1504
|
+
return formatToolResponse({ memories, count: memories.length });
|
|
1505
|
+
},
|
|
1506
|
+
forget_weak_memories: async (ctx, args) => {
|
|
1507
|
+
const effectiveImportanceThreshold = args.threshold !== undefined
|
|
1508
|
+
? validateWithSchema(args.threshold, z.number(), 'Invalid threshold')
|
|
1509
|
+
: 0.1;
|
|
1510
|
+
const dryRun = args.dryRun !== undefined ? validateWithSchema(args.dryRun, z.boolean(), 'Invalid dryRun') : undefined;
|
|
1511
|
+
const olderThanHours = args.maxCount !== undefined ? validateWithSchema(args.maxCount, z.number().int().positive(), 'Invalid olderThanHours') : undefined;
|
|
1512
|
+
const options = { effectiveImportanceThreshold, dryRun, olderThanHours };
|
|
1513
|
+
const amm = ctx.agentMemory();
|
|
1514
|
+
const result = await amm.forgetWeakMemories(options);
|
|
1515
|
+
return formatToolResponse(result);
|
|
1516
|
+
},
|
|
1517
|
+
reinforce_memory: async (ctx, args) => {
|
|
1518
|
+
const memoryName = validateWithSchema(args.memoryName, z.string().min(1), 'Invalid memoryName');
|
|
1519
|
+
const confirmationBoost = args.confirmationBoost !== undefined ? validateWithSchema(args.confirmationBoost, z.number(), 'Invalid confirmationBoost') : undefined;
|
|
1520
|
+
const confidenceBoost = args.confidenceBoost !== undefined ? validateWithSchema(args.confidenceBoost, z.number(), 'Invalid confidenceBoost') : undefined;
|
|
1521
|
+
const amm = ctx.agentMemory();
|
|
1522
|
+
await amm.reinforceMemory(memoryName, { confirmationBoost, confidenceBoost });
|
|
1523
|
+
return formatTextResponse(`Memory '${memoryName}' reinforced`);
|
|
1524
|
+
},
|
|
1525
|
+
score_salience: async (ctx, args) => {
|
|
1526
|
+
const entityName = validateWithSchema(args.entityName, z.string().min(1), 'Invalid entityName');
|
|
1527
|
+
const queryText = args.queryText !== undefined ? validateWithSchema(args.queryText, z.string(), 'Invalid queryText') : undefined;
|
|
1528
|
+
const taskDescription = args.taskDescription !== undefined ? validateWithSchema(args.taskDescription, z.string(), 'Invalid taskDescription') : undefined;
|
|
1529
|
+
const sessionId = args.sessionId !== undefined ? validateWithSchema(args.sessionId, z.string(), 'Invalid sessionId') : undefined;
|
|
1530
|
+
const graph = await ctx.storage.loadGraph();
|
|
1531
|
+
const entity = graph.entities.find(e => e.name === entityName);
|
|
1532
|
+
if (!entity) {
|
|
1533
|
+
return { content: [{ type: 'text', text: `Entity '${entityName}' not found` }], isError: true };
|
|
1534
|
+
}
|
|
1535
|
+
const context = { queryText, currentTask: taskDescription, currentSession: sessionId };
|
|
1536
|
+
const scored = await ctx.salienceEngine.calculateSalience(entity, context);
|
|
1537
|
+
return formatToolResponse(scored);
|
|
1538
|
+
},
|
|
1539
|
+
// ==================== MULTI-AGENT HANDLERS ====================
|
|
1540
|
+
register_agent: async (ctx, args) => {
|
|
1541
|
+
const agentId = validateWithSchema(args.agentId, z.string().min(1), 'Invalid agentId');
|
|
1542
|
+
const type = args.type !== undefined ? validateWithSchema(args.type, z.string(), 'Invalid type') : 'default';
|
|
1543
|
+
const trustLevel = args.trustLevel !== undefined ? validateWithSchema(args.trustLevel, z.number().min(0).max(1), 'Invalid trustLevel') : 0.5;
|
|
1544
|
+
const capabilities = args.capabilities !== undefined ? validateWithSchema(args.capabilities, z.array(z.string()), 'Invalid capabilities') : [];
|
|
1545
|
+
const amm = ctx.agentMemory();
|
|
1546
|
+
amm.registerAgent(agentId, { name: agentId, type, trustLevel, capabilities });
|
|
1547
|
+
return formatTextResponse(`Agent '${agentId}' registered (type: ${type}, trust: ${trustLevel})`);
|
|
1548
|
+
},
|
|
1549
|
+
search_cross_agent: async (ctx, args) => {
|
|
1550
|
+
const requestingAgentId = validateWithSchema(args.requestingAgentId, z.string().min(1), 'Invalid requestingAgentId');
|
|
1551
|
+
const query = validateWithSchema(args.query, z.string().min(1), 'Invalid query');
|
|
1552
|
+
const agentIds = args.agentIds !== undefined ? validateWithSchema(args.agentIds, z.array(z.string()), 'Invalid agentIds') : undefined;
|
|
1553
|
+
const amm = ctx.agentMemory();
|
|
1554
|
+
const results = await amm.searchCrossAgent(requestingAgentId, query, { agentIds });
|
|
1555
|
+
return withCompression(async () => formatToolResponse({ results, count: results.length }));
|
|
1556
|
+
},
|
|
1557
|
+
set_memory_visibility: async (ctx, args) => {
|
|
1558
|
+
const memoryName = validateWithSchema(args.memoryName, z.string().min(1), 'Invalid memoryName');
|
|
1559
|
+
const agentId = validateWithSchema(args.agentId, z.string().min(1), 'Invalid agentId');
|
|
1560
|
+
const visibility = validateWithSchema(args.visibility, z.enum(['private', 'team', 'org', 'shared', 'public']), 'Invalid visibility');
|
|
1561
|
+
// η.5.5.b extensions
|
|
1562
|
+
const allowedRoles = args.allowedRoles !== undefined
|
|
1563
|
+
? validateWithSchema(args.allowedRoles, z.array(z.string()), 'Invalid allowedRoles')
|
|
1564
|
+
: undefined;
|
|
1565
|
+
const visibleFrom = args.visibleFrom !== undefined ? validateWithSchema(args.visibleFrom, z.string(), 'Invalid visibleFrom') : undefined;
|
|
1566
|
+
const visibleUntil = args.visibleUntil !== undefined ? validateWithSchema(args.visibleUntil, z.string(), 'Invalid visibleUntil') : undefined;
|
|
1567
|
+
const amm = ctx.agentMemory();
|
|
1568
|
+
const result = await amm.multiAgentManager.setMemoryVisibility(memoryName, agentId, visibility);
|
|
1569
|
+
// Bug 3 fix: previously returned null silently when entity wasn't an
|
|
1570
|
+
// AgentEntity. Auto-promote: stamp agentId + memoryType + role/window
|
|
1571
|
+
// fields so the visibility takes effect. Error if the entity doesn't
|
|
1572
|
+
// exist at all.
|
|
1573
|
+
if (result === null) {
|
|
1574
|
+
const graph = await ctx.storage.getGraphForMutation();
|
|
1575
|
+
const entity = graph.entities.find(e => e.name === memoryName);
|
|
1576
|
+
if (!entity) {
|
|
1577
|
+
return {
|
|
1578
|
+
content: [{ type: 'text', text: `Entity '${memoryName}' not found — cannot set visibility` }],
|
|
1579
|
+
isError: true,
|
|
1580
|
+
};
|
|
1581
|
+
}
|
|
1582
|
+
// Promote plain Entity → AgentEntity by stamping multi-agent fields
|
|
1583
|
+
entity.agentId = agentId;
|
|
1584
|
+
entity.visibility = visibility;
|
|
1585
|
+
if (entity.memoryType === undefined)
|
|
1586
|
+
entity.memoryType = 'semantic';
|
|
1587
|
+
if (entity.confidence === undefined)
|
|
1588
|
+
entity.confidence = 0.8;
|
|
1589
|
+
if (entity.confirmationCount === undefined)
|
|
1590
|
+
entity.confirmationCount = 0;
|
|
1591
|
+
if (entity.accessCount === undefined)
|
|
1592
|
+
entity.accessCount = 0;
|
|
1593
|
+
// η.5.5.b extension fields
|
|
1594
|
+
if (allowedRoles !== undefined)
|
|
1595
|
+
entity.allowedRoles = allowedRoles;
|
|
1596
|
+
if (visibleFrom !== undefined)
|
|
1597
|
+
entity.visibleFrom = visibleFrom;
|
|
1598
|
+
if (visibleUntil !== undefined)
|
|
1599
|
+
entity.visibleUntil = visibleUntil;
|
|
1600
|
+
await ctx.storage.saveGraph(graph);
|
|
1601
|
+
return formatToolResponse({
|
|
1602
|
+
memoryName,
|
|
1603
|
+
agentId,
|
|
1604
|
+
visibility,
|
|
1605
|
+
allowedRoles,
|
|
1606
|
+
visibleFrom,
|
|
1607
|
+
visibleUntil,
|
|
1608
|
+
promoted: true,
|
|
1609
|
+
message: 'Plain entity auto-promoted to AgentEntity with visibility set',
|
|
1610
|
+
});
|
|
1611
|
+
}
|
|
1612
|
+
// Already an AgentEntity — apply η.5.5.b extension fields if provided
|
|
1613
|
+
if (allowedRoles !== undefined || visibleFrom !== undefined || visibleUntil !== undefined) {
|
|
1614
|
+
const graph = await ctx.storage.getGraphForMutation();
|
|
1615
|
+
const entity = graph.entities.find(e => e.name === memoryName);
|
|
1616
|
+
if (entity) {
|
|
1617
|
+
if (allowedRoles !== undefined)
|
|
1618
|
+
entity.allowedRoles = allowedRoles;
|
|
1619
|
+
if (visibleFrom !== undefined)
|
|
1620
|
+
entity.visibleFrom = visibleFrom;
|
|
1621
|
+
if (visibleUntil !== undefined)
|
|
1622
|
+
entity.visibleUntil = visibleUntil;
|
|
1623
|
+
await ctx.storage.saveGraph(graph);
|
|
1624
|
+
}
|
|
1625
|
+
}
|
|
1626
|
+
return formatToolResponse(result);
|
|
1627
|
+
},
|
|
1628
|
+
get_visible_memories: async (ctx, args) => {
|
|
1629
|
+
const agentId = validateWithSchema(args.agentId, z.string().min(1), 'Invalid agentId');
|
|
1630
|
+
const amm = ctx.agentMemory();
|
|
1631
|
+
const memories = await amm.multiAgentManager.getVisibleMemories(agentId);
|
|
1632
|
+
return withCompression(async () => formatToolResponse({ memories, count: memories.length }));
|
|
1633
|
+
},
|
|
1634
|
+
resolve_agent_conflict: async (ctx, args) => {
|
|
1635
|
+
const primaryMemory = validateWithSchema(args.primaryMemory, z.string().min(1), 'Invalid primaryMemory');
|
|
1636
|
+
const conflictingMemory = validateWithSchema(args.conflictingMemory, z.string().min(1), 'Invalid conflictingMemory');
|
|
1637
|
+
const strategy = args.strategy !== undefined
|
|
1638
|
+
? validateWithSchema(args.strategy, z.enum(['most_recent', 'highest_confidence', 'most_confirmations', 'trusted_agent']), 'Invalid strategy')
|
|
1639
|
+
: undefined;
|
|
1640
|
+
const amm = ctx.agentMemory();
|
|
1641
|
+
// Build a ConflictInfo structure for the two memories
|
|
1642
|
+
const conflictInfo = {
|
|
1643
|
+
primaryMemory,
|
|
1644
|
+
conflictingMemories: [conflictingMemory],
|
|
1645
|
+
detectionMethod: 'manual',
|
|
1646
|
+
suggestedStrategy: strategy ?? 'most_recent',
|
|
1647
|
+
detectedAt: new Date().toISOString(),
|
|
1648
|
+
};
|
|
1649
|
+
const result = await amm.resolveConflict(conflictInfo, strategy);
|
|
1650
|
+
return formatToolResponse(result);
|
|
1651
|
+
},
|
|
1652
|
+
// ==================== OBSERVABILITY HANDLERS ====================
|
|
1653
|
+
visualize_graph: async (ctx, args) => {
|
|
1654
|
+
const maxEntities = args.maxEntities !== undefined ? validateWithSchema(args.maxEntities, z.number().int().positive(), 'Invalid maxEntities') : undefined;
|
|
1655
|
+
const title = args.title !== undefined ? validateWithSchema(args.title, z.string(), 'Invalid title') : undefined;
|
|
1656
|
+
const html = await ctx.ioManager.visualizeGraph({ maxEntities, title });
|
|
1657
|
+
return formatRawResponse(html);
|
|
1658
|
+
},
|
|
1659
|
+
split_transcript: async (ctx, args) => {
|
|
1660
|
+
const text = validateWithSchema(args.text, z.string().min(1), 'Invalid text');
|
|
1661
|
+
const result = ctx.ioManager.splitTranscript(text);
|
|
1662
|
+
return formatToolResponse(result);
|
|
1663
|
+
},
|
|
1664
|
+
estimate_query_cost: async (ctx, args) => {
|
|
1665
|
+
const query = validateWithSchema(args.query, z.string().min(1), 'Invalid query');
|
|
1666
|
+
let estimator = queryCostEstimatorMap.get(ctx);
|
|
1667
|
+
if (!estimator) {
|
|
1668
|
+
estimator = new QueryCostEstimator();
|
|
1669
|
+
queryCostEstimatorMap.set(ctx, estimator);
|
|
1670
|
+
}
|
|
1671
|
+
const graph = await ctx.storage.loadGraph();
|
|
1672
|
+
const estimates = estimator.estimateAllMethods(query, graph.entities.length);
|
|
1673
|
+
return formatToolResponse({ query, entityCount: graph.entities.length, estimates });
|
|
1674
|
+
},
|
|
1675
|
+
get_context_profile: async (ctx, args) => {
|
|
1676
|
+
const name = validateWithSchema(args.name, z.string().min(1), 'Invalid name');
|
|
1677
|
+
const profileManager = ctx.contextWindowManager.getContextProfileManager();
|
|
1678
|
+
const profile = profileManager.getProfile(name);
|
|
1679
|
+
return formatToolResponse(profile);
|
|
1680
|
+
},
|
|
1681
|
+
// ==================== η.4.4 BITEMPORAL ENTITY HANDLERS ====================
|
|
1682
|
+
invalidate_entity: async (ctx, args) => {
|
|
1683
|
+
const name = validateWithSchema(args.name, z.string().min(1), 'Invalid name');
|
|
1684
|
+
const ended = args.ended !== undefined
|
|
1685
|
+
? validateWithSchema(args.ended, z.string(), 'Invalid ended')
|
|
1686
|
+
: undefined;
|
|
1687
|
+
await ctx.entityManager.invalidateEntity(name, ended);
|
|
1688
|
+
return formatTextResponse(`Invalidated entity '${name}' (ended: ${ended ?? 'now'})`);
|
|
1689
|
+
},
|
|
1690
|
+
entity_as_of: async (ctx, args) => {
|
|
1691
|
+
const name = validateWithSchema(args.name, z.string().min(1), 'Invalid name');
|
|
1692
|
+
const asOf = validateWithSchema(args.asOf, z.string().min(1), 'Invalid asOf');
|
|
1693
|
+
const entity = await ctx.entityManager.entityAsOf(name, asOf);
|
|
1694
|
+
if (!entity)
|
|
1695
|
+
return formatToolResponse({ entity: null, valid: false, asOf });
|
|
1696
|
+
return formatToolResponse({ entity, valid: true, asOf });
|
|
1697
|
+
},
|
|
1698
|
+
entity_timeline: async (ctx, args) => {
|
|
1699
|
+
const name = validateWithSchema(args.name, z.string().min(1), 'Invalid name');
|
|
1700
|
+
const versions = await ctx.entityManager.entityTimeline(name);
|
|
1701
|
+
return formatToolResponse({ name, versions, count: versions.length });
|
|
1702
|
+
},
|
|
1703
|
+
invalidate_observation: async (ctx, args) => {
|
|
1704
|
+
const entityName = validateWithSchema(args.entityName, z.string().min(1), 'Invalid entityName');
|
|
1705
|
+
const content = validateWithSchema(args.content, z.string().min(1), 'Invalid content');
|
|
1706
|
+
const ended = args.ended !== undefined
|
|
1707
|
+
? validateWithSchema(args.ended, z.string(), 'Invalid ended')
|
|
1708
|
+
: undefined;
|
|
1709
|
+
await ctx.observationManager.invalidateObservation(entityName, content, ended);
|
|
1710
|
+
return formatTextResponse(`Invalidated observation on '${entityName}' (ended: ${ended ?? 'now'})`);
|
|
1711
|
+
},
|
|
1712
|
+
observations_as_of: async (ctx, args) => {
|
|
1713
|
+
const entityName = validateWithSchema(args.entityName, z.string().min(1), 'Invalid entityName');
|
|
1714
|
+
const asOf = validateWithSchema(args.asOf, z.string().min(1), 'Invalid asOf');
|
|
1715
|
+
const observations = await ctx.observationManager.observationsAsOf(entityName, asOf);
|
|
1716
|
+
return formatToolResponse({ entityName, asOf, observations, count: observations.length });
|
|
1717
|
+
},
|
|
1718
|
+
// ==================== η.5.5.c OCC HANDLER ====================
|
|
1719
|
+
update_entity: async (ctx, args) => {
|
|
1720
|
+
const name = validateWithSchema(args.name, z.string().min(1), 'Invalid name');
|
|
1721
|
+
const updates = validateWithSchema(args.updates, z.record(z.unknown()), 'Invalid updates');
|
|
1722
|
+
const expectedVersion = args.expectedVersion !== undefined
|
|
1723
|
+
? validateWithSchema(args.expectedVersion, z.number().int().positive(), 'Invalid expectedVersion')
|
|
1724
|
+
: undefined;
|
|
1725
|
+
const updated = await ctx.entityManager.updateEntity(name, updates, expectedVersion !== undefined ? { expectedVersion } : undefined);
|
|
1726
|
+
return formatToolResponse(updated);
|
|
1727
|
+
},
|
|
1728
|
+
// ==================== η.6.1 RBAC HANDLERS ====================
|
|
1729
|
+
rbac_assign_role: async (ctx, args) => {
|
|
1730
|
+
const agentId = validateWithSchema(args.agentId, z.string().min(1), 'Invalid agentId');
|
|
1731
|
+
const role = validateWithSchema(args.role, z.string().min(1), 'Invalid role');
|
|
1732
|
+
const resourceType = args.resourceType !== undefined
|
|
1733
|
+
? validateWithSchema(args.resourceType, z.enum(['entity', 'relation', 'observation', 'session', 'artifact']), 'Invalid resourceType')
|
|
1734
|
+
: undefined;
|
|
1735
|
+
const scope = args.scope !== undefined ? validateWithSchema(args.scope, z.string(), 'Invalid scope') : undefined;
|
|
1736
|
+
const validFrom = args.validFrom !== undefined ? validateWithSchema(args.validFrom, z.string(), 'Invalid validFrom') : undefined;
|
|
1737
|
+
const validUntil = args.validUntil !== undefined ? validateWithSchema(args.validUntil, z.string(), 'Invalid validUntil') : undefined;
|
|
1738
|
+
const notes = args.notes !== undefined ? validateWithSchema(args.notes, z.string(), 'Invalid notes') : undefined;
|
|
1739
|
+
await ctx.roleAssignmentStore.assign({ agentId, role, resourceType, scope, validFrom, validUntil, notes });
|
|
1740
|
+
return formatTextResponse(`Assigned role '${role}' to agent '${agentId}'${resourceType ? ` (resourceType=${resourceType})` : ''}${scope ? ` (scope=${scope})` : ''}`);
|
|
1741
|
+
},
|
|
1742
|
+
rbac_revoke_role: async (ctx, args) => {
|
|
1743
|
+
const agentId = validateWithSchema(args.agentId, z.string().min(1), 'Invalid agentId');
|
|
1744
|
+
const role = validateWithSchema(args.role, z.string().min(1), 'Invalid role');
|
|
1745
|
+
const resourceType = args.resourceType !== undefined
|
|
1746
|
+
? validateWithSchema(args.resourceType, z.enum(['entity', 'relation', 'observation', 'session', 'artifact']), 'Invalid resourceType')
|
|
1747
|
+
: undefined;
|
|
1748
|
+
await ctx.roleAssignmentStore.revoke(agentId, role, resourceType);
|
|
1749
|
+
return formatTextResponse(`Revoked role '${role}' from agent '${agentId}'`);
|
|
1750
|
+
},
|
|
1751
|
+
rbac_check_permission: async (ctx, args) => {
|
|
1752
|
+
const agentId = validateWithSchema(args.agentId, z.string().min(1), 'Invalid agentId');
|
|
1753
|
+
const action = validateWithSchema(args.action, z.enum(['read', 'write', 'delete', 'manage']), 'Invalid action');
|
|
1754
|
+
const resourceType = validateWithSchema(args.resourceType, z.enum(['entity', 'relation', 'observation', 'session', 'artifact']), 'Invalid resourceType');
|
|
1755
|
+
const resourceName = args.resourceName !== undefined ? validateWithSchema(args.resourceName, z.string(), 'Invalid resourceName') : undefined;
|
|
1756
|
+
const now = args.now !== undefined ? validateWithSchema(args.now, z.string(), 'Invalid now') : undefined;
|
|
1757
|
+
const allowed = ctx.rbacMiddleware.checkPermission(agentId, action, resourceType, resourceName, now);
|
|
1758
|
+
return formatToolResponse({ agentId, action, resourceType, resourceName, allowed });
|
|
1759
|
+
},
|
|
1760
|
+
rbac_list_assignments: async (ctx, args) => {
|
|
1761
|
+
const agentId = validateWithSchema(args.agentId, z.string().min(1), 'Invalid agentId');
|
|
1762
|
+
const activeOnly = args.activeOnly !== undefined ? validateWithSchema(args.activeOnly, z.boolean(), 'Invalid activeOnly') : false;
|
|
1763
|
+
const now = args.now !== undefined ? validateWithSchema(args.now, z.string(), 'Invalid now') : undefined;
|
|
1764
|
+
const assignments = activeOnly
|
|
1765
|
+
? ctx.roleAssignmentStore.listActive(agentId, now)
|
|
1766
|
+
: ctx.roleAssignmentStore.list(agentId);
|
|
1767
|
+
return formatToolResponse({ agentId, activeOnly, assignments, count: assignments.length });
|
|
1768
|
+
},
|
|
1769
|
+
// ==================== 3B.4 PROCEDURAL MEMORY HANDLERS ====================
|
|
1770
|
+
add_procedure: async (ctx, args) => {
|
|
1771
|
+
const procedure = await ctx.procedureManager.addProcedure(args);
|
|
1772
|
+
return formatToolResponse(procedure);
|
|
1773
|
+
},
|
|
1774
|
+
get_procedure: async (ctx, args) => {
|
|
1775
|
+
const id = validateWithSchema(args.id, z.string().min(1), 'Invalid id');
|
|
1776
|
+
const procedure = await ctx.procedureManager.getProcedure(id);
|
|
1777
|
+
if (!procedure) {
|
|
1778
|
+
return { content: [{ type: 'text', text: `Procedure '${id}' not found` }], isError: true };
|
|
1779
|
+
}
|
|
1780
|
+
return formatToolResponse(procedure);
|
|
1781
|
+
},
|
|
1782
|
+
match_procedure: async (ctx, args) => {
|
|
1783
|
+
const context = validateWithSchema(args.context, z.string().min(1), 'Invalid context');
|
|
1784
|
+
const threshold = args.threshold !== undefined ? validateWithSchema(args.threshold, z.number().min(0).max(1), 'Invalid threshold') : 0;
|
|
1785
|
+
const candidateIds = args.candidateIds !== undefined ? validateWithSchema(args.candidateIds, z.array(z.string()), 'Invalid candidateIds') : undefined;
|
|
1786
|
+
// If specific candidates passed, load each; otherwise scan all 'procedure' entities.
|
|
1787
|
+
const graph = await ctx.storage.loadGraph();
|
|
1788
|
+
const allProcedureEntities = graph.entities.filter(e => e.entityType === 'procedure');
|
|
1789
|
+
const candidates = await Promise.all((candidateIds ?? allProcedureEntities.map(e => e.name)).map(id => ctx.procedureManager.getProcedure(id)));
|
|
1790
|
+
const valid = candidates.filter((p) => p !== null);
|
|
1791
|
+
const matches = await ctx.procedureManager.matchProcedure(context, valid, threshold);
|
|
1792
|
+
return formatToolResponse({ context, threshold, matches, count: matches.length });
|
|
1793
|
+
},
|
|
1794
|
+
refine_procedure: async (ctx, args) => {
|
|
1795
|
+
const id = validateWithSchema(args.id, z.string().min(1), 'Invalid id');
|
|
1796
|
+
const succeeded = validateWithSchema(args.succeeded, z.boolean(), 'Invalid succeeded');
|
|
1797
|
+
const notes = args.notes !== undefined ? validateWithSchema(args.notes, z.string(), 'Invalid notes') : undefined;
|
|
1798
|
+
const recordedAt = args.recordedAt !== undefined ? validateWithSchema(args.recordedAt, z.string(), 'Invalid recordedAt') : undefined;
|
|
1799
|
+
const updated = await ctx.procedureManager.refineProcedure(id, { succeeded, notes, recordedAt });
|
|
1800
|
+
return formatToolResponse(updated);
|
|
1801
|
+
},
|
|
1802
|
+
get_procedure_step: async (ctx, args) => {
|
|
1803
|
+
const id = validateWithSchema(args.id, z.string().min(1), 'Invalid id');
|
|
1804
|
+
const order = validateWithSchema(args.order, z.number().int().positive(), 'Invalid order');
|
|
1805
|
+
const next = args.next !== undefined ? validateWithSchema(args.next, z.boolean(), 'Invalid next') : false;
|
|
1806
|
+
const step = next
|
|
1807
|
+
? await ctx.procedureManager.getNextStep(id, order)
|
|
1808
|
+
: await ctx.procedureManager.getStep(id, order);
|
|
1809
|
+
if (!step) {
|
|
1810
|
+
return { content: [{ type: 'text', text: `Step ${order} not found in procedure '${id}'` }], isError: true };
|
|
1811
|
+
}
|
|
1812
|
+
return formatToolResponse(step);
|
|
1813
|
+
},
|
|
1814
|
+
// ==================== 3B.5 ACTIVE RETRIEVAL HANDLER ====================
|
|
1815
|
+
adaptive_retrieve: async (ctx, args) => {
|
|
1816
|
+
const query = validateWithSchema(args.query, z.string().min(1), 'Invalid query');
|
|
1817
|
+
const budgetTokens = args.budgetTokens !== undefined ? validateWithSchema(args.budgetTokens, z.number().int().positive(), 'Invalid budgetTokens') : undefined;
|
|
1818
|
+
const result = await ctx.activeRetrieval.adaptiveRetrieve({ query, budgetTokens });
|
|
1819
|
+
return formatToolResponse(result);
|
|
1820
|
+
},
|
|
1821
|
+
// ==================== 3B.6 CAUSAL REASONING HANDLERS ====================
|
|
1822
|
+
find_causes: async (ctx, args) => {
|
|
1823
|
+
const effect = validateWithSchema(args.effect, z.string().min(1), 'Invalid effect');
|
|
1824
|
+
const candidates = validateWithSchema(args.candidates, z.array(z.string()).min(1), 'Invalid candidates');
|
|
1825
|
+
const maxDepth = args.maxDepth !== undefined ? validateWithSchema(args.maxDepth, z.number().int().positive(), 'Invalid maxDepth') : undefined;
|
|
1826
|
+
const chains = await ctx.causalReasoner.findCauses(effect, candidates, maxDepth);
|
|
1827
|
+
return formatToolResponse({ effect, candidates, chains, count: chains.length });
|
|
1828
|
+
},
|
|
1829
|
+
find_effects: async (ctx, args) => {
|
|
1830
|
+
const cause = validateWithSchema(args.cause, z.string().min(1), 'Invalid cause');
|
|
1831
|
+
const candidates = validateWithSchema(args.candidates, z.array(z.string()).min(1), 'Invalid candidates');
|
|
1832
|
+
const maxDepth = args.maxDepth !== undefined ? validateWithSchema(args.maxDepth, z.number().int().positive(), 'Invalid maxDepth') : undefined;
|
|
1833
|
+
const chains = await ctx.causalReasoner.findEffects(cause, candidates, maxDepth);
|
|
1834
|
+
return formatToolResponse({ cause, candidates, chains, count: chains.length });
|
|
1835
|
+
},
|
|
1836
|
+
counterfactual_query: async (ctx, args) => {
|
|
1837
|
+
const seed = validateWithSchema(args.seed, z.string().min(1), 'Invalid seed');
|
|
1838
|
+
const removeFrom = validateWithSchema(args.removeFrom, z.string().min(1), 'Invalid removeFrom');
|
|
1839
|
+
const removeTo = validateWithSchema(args.removeTo, z.string().min(1), 'Invalid removeTo');
|
|
1840
|
+
const predict = validateWithSchema(args.predict, z.string().min(1), 'Invalid predict');
|
|
1841
|
+
const maxDepth = args.maxDepth !== undefined ? validateWithSchema(args.maxDepth, z.number().int().positive(), 'Invalid maxDepth') : undefined;
|
|
1842
|
+
const chains = await ctx.causalReasoner.counterfactual({ seed, removeFrom, removeTo, predict, maxDepth });
|
|
1843
|
+
return formatToolResponse({ scenario: { seed, removeFrom, removeTo, predict }, chains, count: chains.length });
|
|
1844
|
+
},
|
|
1845
|
+
detect_causal_cycles: async (ctx, args) => {
|
|
1846
|
+
const seed = validateWithSchema(args.seed, z.string().min(1), 'Invalid seed');
|
|
1847
|
+
const maxDepth = args.maxDepth !== undefined ? validateWithSchema(args.maxDepth, z.number().int().positive(), 'Invalid maxDepth') : undefined;
|
|
1848
|
+
const cycles = ctx.causalReasoner.detectCycles(seed, maxDepth);
|
|
1849
|
+
return formatToolResponse({ seed, cycles, count: cycles.length });
|
|
1850
|
+
},
|
|
1851
|
+
// ==================== 3B.7 WORLD MODEL HANDLERS ====================
|
|
1852
|
+
get_world_state: async (ctx) => {
|
|
1853
|
+
const snapshot = await ctx.worldModelManager.getCurrentState();
|
|
1854
|
+
return formatToolResponse(snapshot.toJSON());
|
|
1855
|
+
},
|
|
1856
|
+
validate_fact_against_world: async (ctx, args) => {
|
|
1857
|
+
const observation = validateWithSchema(args.observation, z.string().min(1), 'Invalid observation');
|
|
1858
|
+
const entityName = validateWithSchema(args.entityName, z.string().min(1), 'Invalid entityName');
|
|
1859
|
+
try {
|
|
1860
|
+
const result = await ctx.worldModelManager.validateFact(observation, entityName);
|
|
1861
|
+
return formatToolResponse({ observation, entityName, result });
|
|
1862
|
+
}
|
|
1863
|
+
catch (err) {
|
|
1864
|
+
// Graceful path for the documented "Returns null if no validator
|
|
1865
|
+
// is wired" semantics: when memoryjs's local embedding provider is
|
|
1866
|
+
// selected but `@xenova/transformers` isn't installed, the
|
|
1867
|
+
// underlying EmbeddingService throws a raw Node module-resolution
|
|
1868
|
+
// error. Translating to a structured null result keeps the MCP
|
|
1869
|
+
// surface coherent and surfaces the configuration issue instead
|
|
1870
|
+
// of leaking Node internals.
|
|
1871
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1872
|
+
// Anchored on memoryjs EmbeddingService.ts:368 prefix and the
|
|
1873
|
+
// package-name signal — narrow enough to avoid swallowing
|
|
1874
|
+
// legitimate "Failed to initialize world model — entity has no
|
|
1875
|
+
// embedding" or similar downstream errors.
|
|
1876
|
+
const isProviderUnavailable = /^Failed to initialize local embedding service:/.test(msg) ||
|
|
1877
|
+
/Cannot find package ['"]@xenova\/transformers['"]/.test(msg);
|
|
1878
|
+
if (isProviderUnavailable) {
|
|
1879
|
+
// Log to stderr (stdout is the MCP JSON-RPC stream — must not
|
|
1880
|
+
// be polluted). Operators piping stderr to a file see this once
|
|
1881
|
+
// per process; LLM clients see only the structured null result.
|
|
1882
|
+
console.warn('[validate_fact_against_world] embedding_provider_unavailable: ' + msg);
|
|
1883
|
+
return formatToolResponse({
|
|
1884
|
+
observation,
|
|
1885
|
+
entityName,
|
|
1886
|
+
result: null,
|
|
1887
|
+
reason: 'embedding_provider_unavailable',
|
|
1888
|
+
detail: msg,
|
|
1889
|
+
});
|
|
1890
|
+
}
|
|
1891
|
+
throw err;
|
|
1892
|
+
}
|
|
1893
|
+
},
|
|
1894
|
+
predict_outcome: async (ctx, args) => {
|
|
1895
|
+
const action = validateWithSchema(args.action, z.string().min(1), 'Invalid action');
|
|
1896
|
+
const candidates = validateWithSchema(args.candidates, z.array(z.string()).min(1), 'Invalid candidates');
|
|
1897
|
+
const chains = await ctx.worldModelManager.predictOutcome(action, candidates);
|
|
1898
|
+
return formatToolResponse({ action, candidates, chains, count: chains.length });
|
|
1899
|
+
},
|
|
658
1900
|
};
|
|
659
1901
|
/**
|
|
660
1902
|
* Handle a tool call by dispatching to the appropriate handler.
|