@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.
@@ -1,15 +1,89 @@
1
1
  /**
2
2
  * MCP Tool Handlers
3
3
  *
4
- * Contains handler functions for all 59 Knowledge Graph tools.
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 { formatToolResponse, formatTextResponse, formatRawResponse, validateWithSchema, validateFilePath, BatchCreateEntitiesSchema, BatchCreateRelationsSchema, EntityNamesSchema, DeleteRelationsSchema, AddObservationsInputSchema, DeleteObservationsInputSchema, ArchiveCriteriaSchema, SavedSearchInputSchema, SavedSearchUpdateSchema, ImportFormatSchema, ExtendedExportFormatSchema, MergeStrategySchema, ExportFilterSchema, SearchQuerySchema, HybridSearchManager, QueryAnalyzer, QueryPlanner, ReflectionManager, ObservationNormalizer, } from '@danielsimonjr/memoryjs';
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.