ai-memory-layer 2.0.1 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +19 -12
- package/README.md +435 -320
- package/bin/memory-server.mjs +0 -0
- package/dist/adapters/memory/embeddings.d.ts.map +1 -1
- package/dist/adapters/memory/embeddings.js +12 -1
- package/dist/adapters/memory/embeddings.js.map +1 -1
- package/dist/adapters/memory/index.d.ts.map +1 -1
- package/dist/adapters/memory/index.js +1281 -48
- package/dist/adapters/memory/index.js.map +1 -1
- package/dist/adapters/postgres/index.d.ts +1 -0
- package/dist/adapters/postgres/index.d.ts.map +1 -1
- package/dist/adapters/postgres/index.js +1770 -42
- package/dist/adapters/postgres/index.js.map +1 -1
- package/dist/adapters/sqlite/embeddings.d.ts.map +1 -1
- package/dist/adapters/sqlite/embeddings.js +49 -12
- package/dist/adapters/sqlite/embeddings.js.map +1 -1
- package/dist/adapters/sqlite/index.d.ts.map +1 -1
- package/dist/adapters/sqlite/index.js +1720 -38
- package/dist/adapters/sqlite/index.js.map +1 -1
- package/dist/adapters/sqlite/mappers.d.ts +39 -4
- package/dist/adapters/sqlite/mappers.d.ts.map +1 -1
- package/dist/adapters/sqlite/mappers.js +87 -0
- package/dist/adapters/sqlite/mappers.js.map +1 -1
- package/dist/adapters/sqlite/schema.d.ts +1 -1
- package/dist/adapters/sqlite/schema.d.ts.map +1 -1
- package/dist/adapters/sqlite/schema.js +297 -1
- package/dist/adapters/sqlite/schema.js.map +1 -1
- package/dist/adapters/sync-to-async.d.ts.map +1 -1
- package/dist/adapters/sync-to-async.js +54 -0
- package/dist/adapters/sync-to-async.js.map +1 -1
- package/dist/contracts/async-storage.d.ts +61 -1
- package/dist/contracts/async-storage.d.ts.map +1 -1
- package/dist/contracts/cognitive.d.ts +37 -0
- package/dist/contracts/cognitive.d.ts.map +1 -0
- package/dist/contracts/cognitive.js +24 -0
- package/dist/contracts/cognitive.js.map +1 -0
- package/dist/contracts/coordination.d.ts +101 -0
- package/dist/contracts/coordination.d.ts.map +1 -0
- package/dist/contracts/coordination.js +26 -0
- package/dist/contracts/coordination.js.map +1 -0
- package/dist/contracts/embedding.d.ts +1 -1
- package/dist/contracts/embedding.d.ts.map +1 -1
- package/dist/contracts/errors.d.ts +28 -0
- package/dist/contracts/errors.d.ts.map +1 -0
- package/dist/contracts/errors.js +41 -0
- package/dist/contracts/errors.js.map +1 -0
- package/dist/contracts/identity.d.ts +2 -0
- package/dist/contracts/identity.d.ts.map +1 -1
- package/dist/contracts/identity.js +26 -1
- package/dist/contracts/identity.js.map +1 -1
- package/dist/contracts/observability.d.ts +2 -1
- package/dist/contracts/observability.d.ts.map +1 -1
- package/dist/contracts/observability.js +11 -0
- package/dist/contracts/observability.js.map +1 -1
- package/dist/contracts/profile.d.ts +29 -0
- package/dist/contracts/profile.d.ts.map +1 -0
- package/dist/contracts/profile.js +2 -0
- package/dist/contracts/profile.js.map +1 -0
- package/dist/contracts/session-state.d.ts +10 -0
- package/dist/contracts/session-state.d.ts.map +1 -0
- package/dist/contracts/session-state.js +2 -0
- package/dist/contracts/session-state.js.map +1 -0
- package/dist/contracts/storage.d.ts +73 -1
- package/dist/contracts/storage.d.ts.map +1 -1
- package/dist/contracts/storage.js +16 -1
- package/dist/contracts/storage.js.map +1 -1
- package/dist/contracts/temporal.d.ts +112 -0
- package/dist/contracts/temporal.d.ts.map +1 -0
- package/dist/contracts/temporal.js +31 -0
- package/dist/contracts/temporal.js.map +1 -0
- package/dist/contracts/types.d.ts +135 -0
- package/dist/contracts/types.d.ts.map +1 -1
- package/dist/contracts/types.js +27 -0
- package/dist/contracts/types.js.map +1 -1
- package/dist/core/associations.d.ts +18 -0
- package/dist/core/associations.d.ts.map +1 -0
- package/dist/core/associations.js +185 -0
- package/dist/core/associations.js.map +1 -0
- package/dist/core/circuit-breaker.d.ts +9 -0
- package/dist/core/circuit-breaker.d.ts.map +1 -1
- package/dist/core/circuit-breaker.js +13 -1
- package/dist/core/circuit-breaker.js.map +1 -1
- package/dist/core/cognitive.d.ts +5 -0
- package/dist/core/cognitive.d.ts.map +1 -0
- package/dist/core/cognitive.js +120 -0
- package/dist/core/cognitive.js.map +1 -0
- package/dist/core/context.d.ts +72 -1
- package/dist/core/context.d.ts.map +1 -1
- package/dist/core/context.js +471 -45
- package/dist/core/context.js.map +1 -1
- package/dist/core/episodic.d.ts +28 -0
- package/dist/core/episodic.d.ts.map +1 -0
- package/dist/core/episodic.js +371 -0
- package/dist/core/episodic.js.map +1 -0
- package/dist/core/formatter.d.ts +4 -0
- package/dist/core/formatter.d.ts.map +1 -1
- package/dist/core/formatter.js +103 -0
- package/dist/core/formatter.js.map +1 -1
- package/dist/core/maintenance.d.ts +1 -0
- package/dist/core/maintenance.d.ts.map +1 -1
- package/dist/core/maintenance.js +75 -0
- package/dist/core/maintenance.js.map +1 -1
- package/dist/core/manager.d.ts +159 -7
- package/dist/core/manager.d.ts.map +1 -1
- package/dist/core/manager.js +740 -31
- package/dist/core/manager.js.map +1 -1
- package/dist/core/orchestrator.d.ts.map +1 -1
- package/dist/core/orchestrator.js +210 -178
- package/dist/core/orchestrator.js.map +1 -1
- package/dist/core/playbook.d.ts +35 -0
- package/dist/core/playbook.d.ts.map +1 -0
- package/dist/core/playbook.js +184 -0
- package/dist/core/playbook.js.map +1 -0
- package/dist/core/profile.d.ts +8 -0
- package/dist/core/profile.d.ts.map +1 -0
- package/dist/core/profile.js +103 -0
- package/dist/core/profile.js.map +1 -0
- package/dist/core/quick.d.ts +5 -0
- package/dist/core/quick.d.ts.map +1 -1
- package/dist/core/quick.js +10 -1
- package/dist/core/quick.js.map +1 -1
- package/dist/core/runtime.d.ts +17 -1
- package/dist/core/runtime.d.ts.map +1 -1
- package/dist/core/runtime.js +88 -5
- package/dist/core/runtime.js.map +1 -1
- package/dist/core/streaming.d.ts +1 -1
- package/dist/core/streaming.d.ts.map +1 -1
- package/dist/core/temporal.d.ts +29 -0
- package/dist/core/temporal.d.ts.map +1 -0
- package/dist/core/temporal.js +447 -0
- package/dist/core/temporal.js.map +1 -0
- package/dist/core/validation.d.ts +3 -0
- package/dist/core/validation.d.ts.map +1 -1
- package/dist/core/validation.js +25 -10
- package/dist/core/validation.js.map +1 -1
- package/dist/core/workspace-detect.d.ts +17 -0
- package/dist/core/workspace-detect.d.ts.map +1 -0
- package/dist/core/workspace-detect.js +55 -0
- package/dist/core/workspace-detect.js.map +1 -0
- package/dist/embeddings/resilience.d.ts.map +1 -1
- package/dist/embeddings/resilience.js +19 -8
- package/dist/embeddings/resilience.js.map +1 -1
- package/dist/index.d.ts +21 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -1
- package/dist/integrations/claude-agent.d.ts +6 -0
- package/dist/integrations/claude-agent.d.ts.map +1 -1
- package/dist/integrations/claude-agent.js +5 -1
- package/dist/integrations/claude-agent.js.map +1 -1
- package/dist/integrations/claude-tools.d.ts +5 -4
- package/dist/integrations/claude-tools.d.ts.map +1 -1
- package/dist/integrations/claude-tools.js +155 -2
- package/dist/integrations/claude-tools.js.map +1 -1
- package/dist/integrations/middleware.d.ts +6 -0
- package/dist/integrations/middleware.d.ts.map +1 -1
- package/dist/integrations/middleware.js +11 -1
- package/dist/integrations/middleware.js.map +1 -1
- package/dist/integrations/openai-tools.d.ts +5 -4
- package/dist/integrations/openai-tools.d.ts.map +1 -1
- package/dist/integrations/openai-tools.js +170 -2
- package/dist/integrations/openai-tools.js.map +1 -1
- package/dist/integrations/vercel-ai.d.ts +6 -0
- package/dist/integrations/vercel-ai.d.ts.map +1 -1
- package/dist/integrations/vercel-ai.js +4 -0
- package/dist/integrations/vercel-ai.js.map +1 -1
- package/dist/server/http-server.d.ts +8 -0
- package/dist/server/http-server.d.ts.map +1 -1
- package/dist/server/http-server.js +976 -58
- package/dist/server/http-server.js.map +1 -1
- package/dist/server/mcp-server.d.ts +8 -0
- package/dist/server/mcp-server.d.ts.map +1 -1
- package/dist/server/mcp-server.js +1157 -37
- package/dist/server/mcp-server.js.map +1 -1
- package/dist/server/parsing.d.ts +12 -0
- package/dist/server/parsing.d.ts.map +1 -0
- package/dist/server/parsing.js +42 -0
- package/dist/server/parsing.js.map +1 -0
- package/dist/summarizers/prompts.d.ts +4 -0
- package/dist/summarizers/prompts.d.ts.map +1 -1
- package/dist/summarizers/prompts.js +42 -0
- package/dist/summarizers/prompts.js.map +1 -1
- package/docs/ULTIMATE_MEMORY_LAYER_ROADMAP.md +291 -0
- package/docs/prd.json +1498 -0
- package/openapi.yaml +1945 -112
- package/package.json +4 -2
|
@@ -1,9 +1,52 @@
|
|
|
1
1
|
import { createMemoryWithAsyncAdapter, } from '../core/quick.js';
|
|
2
|
+
import { createMemoryRuntime } from '../core/runtime.js';
|
|
2
3
|
import { normalizeScope } from '../contracts/identity.js';
|
|
4
|
+
import { isMemoryDomainError } from '../contracts/errors.js';
|
|
5
|
+
import { ASSOCIATION_TARGET_KINDS, ASSOCIATION_TYPES } from '../contracts/types.js';
|
|
6
|
+
import { ACTOR_KINDS, CONTEXT_VIEW_POLICIES, MEMORY_VISIBILITY_CLASSES } from '../contracts/coordination.js';
|
|
3
7
|
import { createSQLiteAdapterWithEmbeddings } from '../adapters/sqlite/index.js';
|
|
4
8
|
import { wrapSyncAdapter } from '../adapters/sync-to-async.js';
|
|
9
|
+
import { parseOptionalFiniteInteger, parseOptionalFiniteNumber, parseOptionalTemporalIdValue, } from './parsing.js';
|
|
5
10
|
class McpValidationError extends Error {
|
|
6
11
|
}
|
|
12
|
+
const MAX_LIST_LIMIT = 100;
|
|
13
|
+
const MANAGER_CACHE_LIMIT = 256;
|
|
14
|
+
const SESSION_MANAGER_CACHE_LIMIT = 256;
|
|
15
|
+
const RUNTIME_CACHE_LIMIT = 256;
|
|
16
|
+
const DEFAULT_DIFF_MAX_EVENTS = 5000;
|
|
17
|
+
const MAX_DIFF_MAX_EVENTS = 20000;
|
|
18
|
+
const TEMPORAL_ENTITY_KINDS = [
|
|
19
|
+
'turn',
|
|
20
|
+
'working_memory',
|
|
21
|
+
'knowledge_memory',
|
|
22
|
+
'work_item',
|
|
23
|
+
'association',
|
|
24
|
+
'playbook',
|
|
25
|
+
'playbook_revision',
|
|
26
|
+
'session_state',
|
|
27
|
+
'work_claim',
|
|
28
|
+
'handoff',
|
|
29
|
+
];
|
|
30
|
+
function failMcpValidation(message) {
|
|
31
|
+
throw new McpValidationError(message);
|
|
32
|
+
}
|
|
33
|
+
function resolveDiffEventCaps(defaultMaxEvents, maxMaxEvents) {
|
|
34
|
+
const resolvedMax = maxMaxEvents ?? MAX_DIFF_MAX_EVENTS;
|
|
35
|
+
const resolvedDefault = defaultMaxEvents ?? DEFAULT_DIFF_MAX_EVENTS;
|
|
36
|
+
if (!Number.isInteger(resolvedMax) || resolvedMax < 1) {
|
|
37
|
+
throw new Error('memory-layer: maxDiffMaxEvents must be a positive integer');
|
|
38
|
+
}
|
|
39
|
+
if (!Number.isInteger(resolvedDefault) || resolvedDefault < 1) {
|
|
40
|
+
throw new Error('memory-layer: defaultDiffMaxEvents must be a positive integer');
|
|
41
|
+
}
|
|
42
|
+
if (resolvedDefault > resolvedMax) {
|
|
43
|
+
throw new Error('memory-layer: defaultDiffMaxEvents must not exceed maxDiffMaxEvents');
|
|
44
|
+
}
|
|
45
|
+
return {
|
|
46
|
+
defaultDiffMaxEvents: resolvedDefault,
|
|
47
|
+
maxDiffMaxEvents: resolvedMax,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
7
50
|
const TOOLS = [
|
|
8
51
|
{
|
|
9
52
|
name: 'memory_store_turn',
|
|
@@ -37,6 +80,103 @@ const TOOLS = [
|
|
|
37
80
|
type: 'object',
|
|
38
81
|
properties: {
|
|
39
82
|
relevanceQuery: { type: 'string', description: 'Optional query to rank knowledge by relevance' },
|
|
83
|
+
view: {
|
|
84
|
+
type: 'string',
|
|
85
|
+
enum: ['local_only', 'local_plus_shared_collaboration', 'operator_supervisor', 'workspace_shared'],
|
|
86
|
+
description: 'Optional context visibility/view policy',
|
|
87
|
+
},
|
|
88
|
+
viewer: { type: 'object', description: 'Optional viewer actor for operator/supervisor coordination views' },
|
|
89
|
+
includeCoordinationState: { type: 'boolean', description: 'Include coordination state in the response' },
|
|
90
|
+
includeDebug: { type: 'boolean', description: 'Include selection reasons and debug trace' },
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
name: 'memory_get_state_at',
|
|
96
|
+
description: 'Get exact temporal state and assembled context at a specific unix timestamp.',
|
|
97
|
+
inputSchema: {
|
|
98
|
+
type: 'object',
|
|
99
|
+
properties: {
|
|
100
|
+
asOf: { type: 'number', description: 'Unix timestamp to replay at' },
|
|
101
|
+
relevanceQuery: { type: 'string', description: 'Optional query to rank knowledge' },
|
|
102
|
+
view: {
|
|
103
|
+
type: 'string',
|
|
104
|
+
enum: ['local_only', 'local_plus_shared_collaboration', 'operator_supervisor', 'workspace_shared'],
|
|
105
|
+
description: 'Optional context visibility/view policy',
|
|
106
|
+
},
|
|
107
|
+
viewer: { type: 'object', description: 'Optional viewer actor for operator/supervisor coordination views' },
|
|
108
|
+
includeCoordinationState: { type: 'boolean', description: 'Include coordination state in the response' },
|
|
109
|
+
includeDebug: { type: 'boolean', description: 'Include debug traces in the context payload' },
|
|
110
|
+
},
|
|
111
|
+
required: ['asOf'],
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
name: 'memory_get_timeline',
|
|
116
|
+
description: 'List memory events in chronological order.',
|
|
117
|
+
inputSchema: {
|
|
118
|
+
type: 'object',
|
|
119
|
+
properties: {
|
|
120
|
+
sessionId: { type: 'string', description: 'Optional session filter' },
|
|
121
|
+
entityKind: {
|
|
122
|
+
type: 'string',
|
|
123
|
+
enum: [...TEMPORAL_ENTITY_KINDS],
|
|
124
|
+
description: 'Optional entity kind filter',
|
|
125
|
+
},
|
|
126
|
+
entityId: { type: 'string', description: 'Optional entity id filter' },
|
|
127
|
+
startAt: { type: 'number', description: 'Optional start unix timestamp' },
|
|
128
|
+
endAt: { type: 'number', description: 'Optional end unix timestamp' },
|
|
129
|
+
limit: { type: 'number', description: 'Optional page size' },
|
|
130
|
+
cursor: {
|
|
131
|
+
anyOf: [{ type: 'number' }, { type: 'string' }],
|
|
132
|
+
description: 'Optional event-id cursor',
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
name: 'memory_diff_state',
|
|
139
|
+
description: 'Summarize memory events between two unix timestamps.',
|
|
140
|
+
inputSchema: {
|
|
141
|
+
type: 'object',
|
|
142
|
+
properties: {
|
|
143
|
+
from: { type: 'number', description: 'Start unix timestamp (exclusive)' },
|
|
144
|
+
to: { type: 'number', description: 'End unix timestamp (inclusive)' },
|
|
145
|
+
sessionId: { type: 'string', description: 'Optional session filter' },
|
|
146
|
+
entityKind: {
|
|
147
|
+
type: 'string',
|
|
148
|
+
enum: [...TEMPORAL_ENTITY_KINDS],
|
|
149
|
+
description: 'Optional entity kind filter',
|
|
150
|
+
},
|
|
151
|
+
entityId: { type: 'string', description: 'Optional entity id filter' },
|
|
152
|
+
maxEvents: {
|
|
153
|
+
type: 'number',
|
|
154
|
+
description: 'Optional maximum event count to accumulate. Defaults to the server-configured diff cap and must not exceed the configured maximum.',
|
|
155
|
+
},
|
|
156
|
+
},
|
|
157
|
+
required: ['from', 'to'],
|
|
158
|
+
},
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
name: 'memory_list_events',
|
|
162
|
+
description: 'List memory events in reverse chronological order for inspection.',
|
|
163
|
+
inputSchema: {
|
|
164
|
+
type: 'object',
|
|
165
|
+
properties: {
|
|
166
|
+
sessionId: { type: 'string', description: 'Optional session filter' },
|
|
167
|
+
entityKind: {
|
|
168
|
+
type: 'string',
|
|
169
|
+
enum: [...TEMPORAL_ENTITY_KINDS],
|
|
170
|
+
description: 'Optional entity kind filter',
|
|
171
|
+
},
|
|
172
|
+
entityId: { type: 'string', description: 'Optional entity id filter' },
|
|
173
|
+
startAt: { type: 'number', description: 'Optional start unix timestamp' },
|
|
174
|
+
endAt: { type: 'number', description: 'Optional end unix timestamp' },
|
|
175
|
+
limit: { type: 'number', description: 'Optional page size' },
|
|
176
|
+
cursor: {
|
|
177
|
+
anyOf: [{ type: 'number' }, { type: 'string' }],
|
|
178
|
+
description: 'Optional event-id cursor',
|
|
179
|
+
},
|
|
40
180
|
},
|
|
41
181
|
},
|
|
42
182
|
},
|
|
@@ -104,6 +244,148 @@ const TOOLS = [
|
|
|
104
244
|
required: ['title'],
|
|
105
245
|
},
|
|
106
246
|
},
|
|
247
|
+
{
|
|
248
|
+
name: 'memory_update_work_item',
|
|
249
|
+
description: 'Update an existing work item.',
|
|
250
|
+
inputSchema: {
|
|
251
|
+
type: 'object',
|
|
252
|
+
properties: {
|
|
253
|
+
id: { type: 'number' },
|
|
254
|
+
title: { type: 'string' },
|
|
255
|
+
detail: { type: 'string' },
|
|
256
|
+
status: { type: 'string', enum: ['open', 'in_progress', 'blocked', 'done'] },
|
|
257
|
+
visibility_class: { type: 'string', enum: ['private', 'shared_collaboration', 'workspace', 'tenant'] },
|
|
258
|
+
expectedVersion: { type: 'number' },
|
|
259
|
+
},
|
|
260
|
+
required: ['id'],
|
|
261
|
+
},
|
|
262
|
+
},
|
|
263
|
+
{
|
|
264
|
+
name: 'memory_claim_work_item',
|
|
265
|
+
description: 'Acquire or renew an exclusive claim on a work item.',
|
|
266
|
+
inputSchema: {
|
|
267
|
+
type: 'object',
|
|
268
|
+
properties: {
|
|
269
|
+
workItemId: { type: 'number' },
|
|
270
|
+
actor: { type: 'object' },
|
|
271
|
+
leaseSeconds: { type: 'number' },
|
|
272
|
+
},
|
|
273
|
+
required: ['workItemId', 'actor'],
|
|
274
|
+
},
|
|
275
|
+
},
|
|
276
|
+
{
|
|
277
|
+
name: 'memory_renew_work_claim',
|
|
278
|
+
description: 'Renew an exclusive claim on a work item.',
|
|
279
|
+
inputSchema: {
|
|
280
|
+
type: 'object',
|
|
281
|
+
properties: {
|
|
282
|
+
claimId: { type: 'number' },
|
|
283
|
+
actor: { type: 'object' },
|
|
284
|
+
leaseSeconds: { type: 'number' },
|
|
285
|
+
},
|
|
286
|
+
required: ['claimId', 'actor'],
|
|
287
|
+
},
|
|
288
|
+
},
|
|
289
|
+
{
|
|
290
|
+
name: 'memory_release_work_claim',
|
|
291
|
+
description: 'Release a claimed work item.',
|
|
292
|
+
inputSchema: {
|
|
293
|
+
type: 'object',
|
|
294
|
+
properties: {
|
|
295
|
+
claimId: { type: 'number' },
|
|
296
|
+
actor: { type: 'object' },
|
|
297
|
+
reason: { type: 'string' },
|
|
298
|
+
},
|
|
299
|
+
required: ['claimId', 'actor'],
|
|
300
|
+
},
|
|
301
|
+
},
|
|
302
|
+
{
|
|
303
|
+
name: 'memory_list_work_claims',
|
|
304
|
+
description: 'List current work claims.',
|
|
305
|
+
inputSchema: {
|
|
306
|
+
type: 'object',
|
|
307
|
+
properties: {},
|
|
308
|
+
},
|
|
309
|
+
},
|
|
310
|
+
{
|
|
311
|
+
name: 'memory_handoff_work_item',
|
|
312
|
+
description: 'Create a handoff for a work item.',
|
|
313
|
+
inputSchema: {
|
|
314
|
+
type: 'object',
|
|
315
|
+
properties: {
|
|
316
|
+
workItemId: { type: 'number' },
|
|
317
|
+
fromActor: { type: 'object' },
|
|
318
|
+
toActor: { type: 'object' },
|
|
319
|
+
summary: { type: 'string' },
|
|
320
|
+
contextBundleRef: { type: 'string' },
|
|
321
|
+
expiresAt: { type: 'number' },
|
|
322
|
+
},
|
|
323
|
+
required: ['workItemId', 'fromActor', 'toActor', 'summary'],
|
|
324
|
+
},
|
|
325
|
+
},
|
|
326
|
+
{
|
|
327
|
+
name: 'memory_accept_handoff',
|
|
328
|
+
description: 'Accept a handoff.',
|
|
329
|
+
inputSchema: {
|
|
330
|
+
type: 'object',
|
|
331
|
+
properties: {
|
|
332
|
+
handoffId: { type: 'number' },
|
|
333
|
+
actor: { type: 'object' },
|
|
334
|
+
reason: { type: 'string' },
|
|
335
|
+
},
|
|
336
|
+
required: ['handoffId', 'actor'],
|
|
337
|
+
},
|
|
338
|
+
},
|
|
339
|
+
{
|
|
340
|
+
name: 'memory_reject_handoff',
|
|
341
|
+
description: 'Reject a handoff.',
|
|
342
|
+
inputSchema: {
|
|
343
|
+
type: 'object',
|
|
344
|
+
properties: {
|
|
345
|
+
handoffId: { type: 'number' },
|
|
346
|
+
actor: { type: 'object' },
|
|
347
|
+
reason: { type: 'string' },
|
|
348
|
+
},
|
|
349
|
+
required: ['handoffId', 'actor'],
|
|
350
|
+
},
|
|
351
|
+
},
|
|
352
|
+
{
|
|
353
|
+
name: 'memory_cancel_handoff',
|
|
354
|
+
description: 'Cancel a handoff.',
|
|
355
|
+
inputSchema: {
|
|
356
|
+
type: 'object',
|
|
357
|
+
properties: {
|
|
358
|
+
handoffId: { type: 'number' },
|
|
359
|
+
actor: { type: 'object' },
|
|
360
|
+
reason: { type: 'string' },
|
|
361
|
+
},
|
|
362
|
+
required: ['handoffId', 'actor'],
|
|
363
|
+
},
|
|
364
|
+
},
|
|
365
|
+
{
|
|
366
|
+
name: 'memory_list_pending_handoffs',
|
|
367
|
+
description: 'List pending handoffs.',
|
|
368
|
+
inputSchema: {
|
|
369
|
+
type: 'object',
|
|
370
|
+
properties: {
|
|
371
|
+
direction: { type: 'string', enum: ['inbound', 'outbound', 'all'] },
|
|
372
|
+
},
|
|
373
|
+
},
|
|
374
|
+
},
|
|
375
|
+
{
|
|
376
|
+
name: 'memory_stream_changes',
|
|
377
|
+
description: 'List durable change events after an optional cursor.',
|
|
378
|
+
inputSchema: {
|
|
379
|
+
type: 'object',
|
|
380
|
+
properties: {
|
|
381
|
+
cursor: { anyOf: [{ type: 'number' }, { type: 'string' }] },
|
|
382
|
+
sessionId: { type: 'string' },
|
|
383
|
+
entityKind: { type: 'string', enum: [...TEMPORAL_ENTITY_KINDS] },
|
|
384
|
+
entityId: { type: 'string' },
|
|
385
|
+
limit: { type: 'number' },
|
|
386
|
+
},
|
|
387
|
+
},
|
|
388
|
+
},
|
|
107
389
|
{
|
|
108
390
|
name: 'memory_force_compact',
|
|
109
391
|
description: 'Force compaction of conversation history into a summary.',
|
|
@@ -128,12 +410,403 @@ const TOOLS = [
|
|
|
128
410
|
properties: {},
|
|
129
411
|
},
|
|
130
412
|
},
|
|
413
|
+
{
|
|
414
|
+
name: 'memory_search_episodes',
|
|
415
|
+
description: 'Search episodic memory for past sessions matching a query. Returns structured recaps with objectives, actions, outcomes, and unresolved items.',
|
|
416
|
+
inputSchema: {
|
|
417
|
+
type: 'object',
|
|
418
|
+
properties: {
|
|
419
|
+
query: { type: 'string', description: 'Search query' },
|
|
420
|
+
detailLevel: {
|
|
421
|
+
type: 'string',
|
|
422
|
+
enum: ['abstract', 'overview', 'full'],
|
|
423
|
+
description: 'Detail level for results (default: overview)',
|
|
424
|
+
},
|
|
425
|
+
limit: { type: 'number', description: 'Max results (default: 10)' },
|
|
426
|
+
timeRange: {
|
|
427
|
+
type: 'object',
|
|
428
|
+
properties: {
|
|
429
|
+
start_at: { type: 'number', description: 'Unix timestamp lower bound' },
|
|
430
|
+
end_at: { type: 'number', description: 'Unix timestamp upper bound' },
|
|
431
|
+
},
|
|
432
|
+
description: 'Optional time range filter',
|
|
433
|
+
},
|
|
434
|
+
},
|
|
435
|
+
required: ['query'],
|
|
436
|
+
},
|
|
437
|
+
},
|
|
438
|
+
{
|
|
439
|
+
name: 'memory_summarize_episode',
|
|
440
|
+
description: 'Summarize a specific session into a structured episodic recap.',
|
|
441
|
+
inputSchema: {
|
|
442
|
+
type: 'object',
|
|
443
|
+
properties: {
|
|
444
|
+
sessionId: { type: 'string', description: 'Session ID to summarize' },
|
|
445
|
+
detailLevel: {
|
|
446
|
+
type: 'string',
|
|
447
|
+
enum: ['abstract', 'overview', 'full'],
|
|
448
|
+
description: 'Detail level (default: overview)',
|
|
449
|
+
},
|
|
450
|
+
},
|
|
451
|
+
required: ['sessionId'],
|
|
452
|
+
},
|
|
453
|
+
},
|
|
454
|
+
{
|
|
455
|
+
name: 'memory_reflect',
|
|
456
|
+
description: 'Synthesize an answer from episodic and declarative memory. Returns a coherent synthesis with source attribution.',
|
|
457
|
+
inputSchema: {
|
|
458
|
+
type: 'object',
|
|
459
|
+
properties: {
|
|
460
|
+
query: { type: 'string', description: 'Reflection query' },
|
|
461
|
+
detailLevel: {
|
|
462
|
+
type: 'string',
|
|
463
|
+
enum: ['abstract', 'overview', 'full'],
|
|
464
|
+
description: 'Detail level (default: overview)',
|
|
465
|
+
},
|
|
466
|
+
includeEpisodic: { type: 'boolean', description: 'Include episodic memory (default: true)' },
|
|
467
|
+
includeDeclarative: { type: 'boolean', description: 'Include declarative memory (default: true)' },
|
|
468
|
+
limit: { type: 'number', description: 'Max sources (default: 10)' },
|
|
469
|
+
timeRange: {
|
|
470
|
+
type: 'object',
|
|
471
|
+
properties: {
|
|
472
|
+
start_at: { type: 'number', description: 'Unix timestamp lower bound' },
|
|
473
|
+
end_at: { type: 'number', description: 'Unix timestamp upper bound' },
|
|
474
|
+
},
|
|
475
|
+
description: 'Optional time range filter',
|
|
476
|
+
},
|
|
477
|
+
},
|
|
478
|
+
required: ['query'],
|
|
479
|
+
},
|
|
480
|
+
},
|
|
481
|
+
{
|
|
482
|
+
name: 'memory_search_cognitive',
|
|
483
|
+
description: 'Search memory using the cognitive taxonomy (episodic, semantic, procedural, working). Returns results grouped by cognitive type.',
|
|
484
|
+
inputSchema: {
|
|
485
|
+
type: 'object',
|
|
486
|
+
properties: {
|
|
487
|
+
query: { type: 'string', description: 'Search query' },
|
|
488
|
+
types: {
|
|
489
|
+
type: 'array',
|
|
490
|
+
items: { type: 'string', enum: ['episodic', 'semantic', 'procedural', 'working'] },
|
|
491
|
+
description: 'Cognitive memory types to search (default: all)',
|
|
492
|
+
},
|
|
493
|
+
limit: { type: 'number', description: 'Max results (default: 10)' },
|
|
494
|
+
minimumTrustScore: { type: 'number', description: 'Minimum trust score filter' },
|
|
495
|
+
activeOnly: { type: 'boolean', description: 'Only return active memories (default: true)' },
|
|
496
|
+
},
|
|
497
|
+
required: ['query'],
|
|
498
|
+
},
|
|
499
|
+
},
|
|
500
|
+
{
|
|
501
|
+
name: 'memory_get_profile',
|
|
502
|
+
description: 'Get a materialized profile view built from trusted knowledge. Returns identity, preferences, communication, constraints, and workflows.',
|
|
503
|
+
inputSchema: {
|
|
504
|
+
type: 'object',
|
|
505
|
+
properties: {
|
|
506
|
+
view: {
|
|
507
|
+
type: 'string',
|
|
508
|
+
enum: ['user', 'operator', 'workspace'],
|
|
509
|
+
description: 'Profile view type (default: user)',
|
|
510
|
+
},
|
|
511
|
+
sections: {
|
|
512
|
+
type: 'array',
|
|
513
|
+
items: { type: 'string', enum: ['identity', 'preferences', 'communication', 'constraints', 'workflows'] },
|
|
514
|
+
description: 'Specific sections to include (default: all)',
|
|
515
|
+
},
|
|
516
|
+
minimumTrustScore: { type: 'number', description: 'Minimum trust score filter' },
|
|
517
|
+
includeProvisional: { type: 'boolean', description: 'Include provisional knowledge entries (default: false — profiles are trusted-only)' },
|
|
518
|
+
includeDisputed: { type: 'boolean', description: 'Include disputed knowledge entries (default: false)' },
|
|
519
|
+
},
|
|
520
|
+
},
|
|
521
|
+
},
|
|
522
|
+
{
|
|
523
|
+
name: 'memory_create_playbook',
|
|
524
|
+
description: 'Create a reusable playbook from a title, description, and instructions.',
|
|
525
|
+
inputSchema: {
|
|
526
|
+
type: 'object',
|
|
527
|
+
properties: {
|
|
528
|
+
title: { type: 'string', description: 'Playbook title' },
|
|
529
|
+
description: { type: 'string', description: 'What this playbook is for' },
|
|
530
|
+
instructions: { type: 'string', description: 'Step-by-step instructions' },
|
|
531
|
+
references: { type: 'array', items: { type: 'string' }, description: 'Referenced files, URLs, or tools' },
|
|
532
|
+
templates: { type: 'array', items: { type: 'string' }, description: 'Reusable templates or patterns' },
|
|
533
|
+
scripts: { type: 'array', items: { type: 'string' }, description: 'Commands or scripts' },
|
|
534
|
+
assets: { type: 'array', items: { type: 'string' }, description: 'Associated assets' },
|
|
535
|
+
tags: {
|
|
536
|
+
type: 'array',
|
|
537
|
+
items: { type: 'string' },
|
|
538
|
+
description: 'Optional tags for categorization',
|
|
539
|
+
},
|
|
540
|
+
status: {
|
|
541
|
+
type: 'string',
|
|
542
|
+
enum: ['draft', 'active', 'deprecated', 'archived'],
|
|
543
|
+
description: 'Playbook status (default: draft)',
|
|
544
|
+
},
|
|
545
|
+
},
|
|
546
|
+
required: ['title', 'description', 'instructions'],
|
|
547
|
+
},
|
|
548
|
+
},
|
|
549
|
+
{
|
|
550
|
+
name: 'memory_search_playbooks',
|
|
551
|
+
description: 'Search for relevant playbooks by query. Returns ranked results.',
|
|
552
|
+
inputSchema: {
|
|
553
|
+
type: 'object',
|
|
554
|
+
properties: {
|
|
555
|
+
query: { type: 'string', description: 'Search query' },
|
|
556
|
+
limit: { type: 'number', description: 'Max results (default: 20)' },
|
|
557
|
+
},
|
|
558
|
+
required: ['query'],
|
|
559
|
+
},
|
|
560
|
+
},
|
|
561
|
+
{
|
|
562
|
+
name: 'memory_revise_playbook',
|
|
563
|
+
description: 'Revise a playbook by storing the current instructions as a revision and updating with new instructions.',
|
|
564
|
+
inputSchema: {
|
|
565
|
+
type: 'object',
|
|
566
|
+
properties: {
|
|
567
|
+
playbookId: { type: 'number', description: 'ID of the playbook to revise' },
|
|
568
|
+
newInstructions: { type: 'string', description: 'Updated instructions' },
|
|
569
|
+
revisionReason: { type: 'string', description: 'Why the playbook is being revised' },
|
|
570
|
+
sourceSessionId: { type: 'string', description: 'Optional session ID that triggered the revision' },
|
|
571
|
+
},
|
|
572
|
+
required: ['playbookId', 'newInstructions', 'revisionReason'],
|
|
573
|
+
},
|
|
574
|
+
},
|
|
575
|
+
{
|
|
576
|
+
name: 'memory_create_playbook_from_task',
|
|
577
|
+
description: 'Create a playbook by summarizing the turns of a completed task session into structured instructions.',
|
|
578
|
+
inputSchema: {
|
|
579
|
+
type: 'object',
|
|
580
|
+
properties: {
|
|
581
|
+
title: { type: 'string', description: 'Playbook title' },
|
|
582
|
+
description: { type: 'string', description: 'What this playbook is for' },
|
|
583
|
+
sessionId: { type: 'string', description: 'Session to derive the playbook from' },
|
|
584
|
+
tags: { type: 'array', items: { type: 'string' }, description: 'Optional tags' },
|
|
585
|
+
sourceWorkingMemoryId: {
|
|
586
|
+
type: 'number',
|
|
587
|
+
description: 'Optional working memory id that anchors the source task',
|
|
588
|
+
},
|
|
589
|
+
},
|
|
590
|
+
required: ['title', 'description', 'sessionId'],
|
|
591
|
+
},
|
|
592
|
+
},
|
|
593
|
+
{
|
|
594
|
+
name: 'memory_use_playbook',
|
|
595
|
+
description: 'Record that a playbook was used and return the full playbook record.',
|
|
596
|
+
inputSchema: {
|
|
597
|
+
type: 'object',
|
|
598
|
+
properties: {
|
|
599
|
+
playbookId: { type: 'number', description: 'ID of the playbook that was used' },
|
|
600
|
+
},
|
|
601
|
+
required: ['playbookId'],
|
|
602
|
+
},
|
|
603
|
+
},
|
|
604
|
+
{
|
|
605
|
+
name: 'memory_get_associations',
|
|
606
|
+
description: 'Get or traverse associations for a memory artifact. Use traverse mode for multi-hop graph expansion.',
|
|
607
|
+
inputSchema: {
|
|
608
|
+
type: 'object',
|
|
609
|
+
properties: {
|
|
610
|
+
kind: { type: 'string', enum: ['knowledge', 'playbook', 'working_memory', 'work_item'], description: 'Type of the source artifact' },
|
|
611
|
+
id: { type: 'number', description: 'ID of the source artifact' },
|
|
612
|
+
traverse: { type: 'boolean', description: 'If true, perform BFS traversal instead of direct lookup' },
|
|
613
|
+
maxDepth: { type: 'number', description: 'Max traversal depth (default 2, only used with traverse)' },
|
|
614
|
+
maxNodes: { type: 'number', description: 'Max nodes to return (default 20, only used with traverse)' },
|
|
615
|
+
},
|
|
616
|
+
required: ['kind', 'id'],
|
|
617
|
+
},
|
|
618
|
+
},
|
|
619
|
+
{
|
|
620
|
+
name: 'memory_add_association',
|
|
621
|
+
description: 'Create an association between two memory artifacts.',
|
|
622
|
+
inputSchema: {
|
|
623
|
+
type: 'object',
|
|
624
|
+
properties: {
|
|
625
|
+
source_kind: { type: 'string', enum: ['knowledge', 'playbook', 'working_memory', 'work_item'] },
|
|
626
|
+
source_id: { type: 'number' },
|
|
627
|
+
target_kind: { type: 'string', enum: ['knowledge', 'playbook', 'working_memory', 'work_item'] },
|
|
628
|
+
target_id: { type: 'number' },
|
|
629
|
+
association_type: { type: 'string', enum: ['related_to', 'supports', 'contradicts', 'supersedes', 'depends_on', 'solves', 'applies_to', 'derived_from'] },
|
|
630
|
+
confidence: { type: 'number', description: 'Confidence score 0-1 (default 0.5)' },
|
|
631
|
+
},
|
|
632
|
+
required: ['source_kind', 'source_id', 'target_kind', 'target_id', 'association_type'],
|
|
633
|
+
},
|
|
634
|
+
},
|
|
635
|
+
{
|
|
636
|
+
name: 'memory_remove_association',
|
|
637
|
+
description: 'Delete an association by ID.',
|
|
638
|
+
inputSchema: {
|
|
639
|
+
type: 'object',
|
|
640
|
+
properties: {
|
|
641
|
+
id: { type: 'number', description: 'Association ID to delete' },
|
|
642
|
+
},
|
|
643
|
+
required: ['id'],
|
|
644
|
+
},
|
|
645
|
+
},
|
|
646
|
+
{
|
|
647
|
+
name: 'memory_snapshot',
|
|
648
|
+
description: 'Manage session snapshots for cache-stable prompt injection. Actions: capture (freeze current state), refresh (recapture), get (return cached snapshot or null).',
|
|
649
|
+
inputSchema: {
|
|
650
|
+
type: 'object',
|
|
651
|
+
properties: {
|
|
652
|
+
action: {
|
|
653
|
+
type: 'string',
|
|
654
|
+
enum: ['capture', 'refresh', 'get'],
|
|
655
|
+
description: 'Snapshot action to perform',
|
|
656
|
+
},
|
|
657
|
+
sessionId: {
|
|
658
|
+
type: 'string',
|
|
659
|
+
description: 'Session ID the snapshot belongs to',
|
|
660
|
+
},
|
|
661
|
+
relevanceQuery: {
|
|
662
|
+
type: 'string',
|
|
663
|
+
description: 'Optional query to rank knowledge during capture/refresh',
|
|
664
|
+
},
|
|
665
|
+
},
|
|
666
|
+
required: ['action', 'sessionId'],
|
|
667
|
+
},
|
|
668
|
+
},
|
|
131
669
|
];
|
|
132
670
|
function jsonResult(data) {
|
|
133
671
|
return {
|
|
134
672
|
content: [{ type: 'text', text: JSON.stringify(data, null, 2) }],
|
|
135
673
|
};
|
|
136
674
|
}
|
|
675
|
+
function serializeContextResponse(context, options = {}) {
|
|
676
|
+
return {
|
|
677
|
+
currentObjective: context.currentObjective,
|
|
678
|
+
sessionState: context.sessionState,
|
|
679
|
+
activeTurnCount: context.activeTurns.length,
|
|
680
|
+
workingMemory: context.workingMemory
|
|
681
|
+
? {
|
|
682
|
+
summary: context.workingMemory.summary,
|
|
683
|
+
key_entities: context.workingMemory.key_entities,
|
|
684
|
+
topic_tags: context.workingMemory.topic_tags,
|
|
685
|
+
}
|
|
686
|
+
: null,
|
|
687
|
+
relevantKnowledge: context.relevantKnowledge.map((knowledge) => ({
|
|
688
|
+
id: knowledge.id,
|
|
689
|
+
fact: knowledge.fact,
|
|
690
|
+
fact_type: knowledge.fact_type,
|
|
691
|
+
confidence: knowledge.confidence,
|
|
692
|
+
})),
|
|
693
|
+
activeObjectives: context.activeObjectives.map((objective) => ({
|
|
694
|
+
id: objective.id,
|
|
695
|
+
title: objective.title,
|
|
696
|
+
status: objective.status,
|
|
697
|
+
visibility_class: objective.visibility_class,
|
|
698
|
+
})),
|
|
699
|
+
associatedKnowledge: context.associatedKnowledge.map((knowledge) => ({
|
|
700
|
+
id: knowledge.id,
|
|
701
|
+
fact: knowledge.fact,
|
|
702
|
+
fact_type: knowledge.fact_type,
|
|
703
|
+
knowledge_class: knowledge.knowledge_class,
|
|
704
|
+
trust_score: knowledge.trust_score,
|
|
705
|
+
})),
|
|
706
|
+
unresolvedWork: context.unresolvedWork,
|
|
707
|
+
coordinationState: context.coordinationState
|
|
708
|
+
? {
|
|
709
|
+
ownedClaims: context.coordinationState.ownedClaims.map(serializeWorkClaim),
|
|
710
|
+
pendingInboundHandoffs: context.coordinationState.pendingInboundHandoffs.map(serializeHandoffRecord),
|
|
711
|
+
pendingOutboundHandoffs: context.coordinationState.pendingOutboundHandoffs.map(serializeHandoffRecord),
|
|
712
|
+
sharedWorkItems: context.coordinationState.sharedWorkItems.map((item) => ({
|
|
713
|
+
id: item.id,
|
|
714
|
+
title: item.title,
|
|
715
|
+
status: item.status,
|
|
716
|
+
visibility_class: item.visibility_class,
|
|
717
|
+
})),
|
|
718
|
+
}
|
|
719
|
+
: null,
|
|
720
|
+
tokenEstimate: context.tokenEstimate,
|
|
721
|
+
...(options.includeDebug
|
|
722
|
+
? {
|
|
723
|
+
debugTrace: context.debugTrace,
|
|
724
|
+
knowledgeSelectionReasons: context.knowledgeSelectionReasons,
|
|
725
|
+
}
|
|
726
|
+
: {}),
|
|
727
|
+
};
|
|
728
|
+
}
|
|
729
|
+
function serializeActorRef(actor) {
|
|
730
|
+
return {
|
|
731
|
+
actor_kind: actor.actor_kind,
|
|
732
|
+
actor_id: actor.actor_id,
|
|
733
|
+
system_id: actor.system_id,
|
|
734
|
+
display_name: actor.display_name,
|
|
735
|
+
metadata: actor.metadata,
|
|
736
|
+
};
|
|
737
|
+
}
|
|
738
|
+
function serializeWorkClaim(claim) {
|
|
739
|
+
return {
|
|
740
|
+
id: claim.id,
|
|
741
|
+
work_item_id: claim.work_item_id,
|
|
742
|
+
actor: serializeActorRef(claim.actor),
|
|
743
|
+
session_id: claim.session_id,
|
|
744
|
+
claim_token: claim.claim_token,
|
|
745
|
+
status: claim.status,
|
|
746
|
+
claimed_at: claim.claimed_at,
|
|
747
|
+
expires_at: claim.expires_at,
|
|
748
|
+
released_at: claim.released_at,
|
|
749
|
+
release_reason: claim.release_reason,
|
|
750
|
+
source_event_id: claim.source_event_id,
|
|
751
|
+
visibility_class: claim.visibility_class,
|
|
752
|
+
version: claim.version,
|
|
753
|
+
};
|
|
754
|
+
}
|
|
755
|
+
function serializeHandoffRecord(handoff) {
|
|
756
|
+
return {
|
|
757
|
+
id: handoff.id,
|
|
758
|
+
work_item_id: handoff.work_item_id,
|
|
759
|
+
from_actor: serializeActorRef(handoff.from_actor),
|
|
760
|
+
to_actor: serializeActorRef(handoff.to_actor),
|
|
761
|
+
session_id: handoff.session_id,
|
|
762
|
+
summary: handoff.summary,
|
|
763
|
+
context_bundle_ref: handoff.context_bundle_ref,
|
|
764
|
+
status: handoff.status,
|
|
765
|
+
created_at: handoff.created_at,
|
|
766
|
+
accepted_at: handoff.accepted_at,
|
|
767
|
+
rejected_at: handoff.rejected_at,
|
|
768
|
+
canceled_at: handoff.canceled_at,
|
|
769
|
+
expires_at: handoff.expires_at,
|
|
770
|
+
decision_reason: handoff.decision_reason,
|
|
771
|
+
source_event_id: handoff.source_event_id,
|
|
772
|
+
visibility_class: handoff.visibility_class,
|
|
773
|
+
version: handoff.version,
|
|
774
|
+
};
|
|
775
|
+
}
|
|
776
|
+
function serializeTimelineResult(result) {
|
|
777
|
+
return {
|
|
778
|
+
events: result.events,
|
|
779
|
+
nextCursor: result.nextCursor,
|
|
780
|
+
};
|
|
781
|
+
}
|
|
782
|
+
function serializeTemporalState(state, options = {}) {
|
|
783
|
+
return {
|
|
784
|
+
asOf: state.asOf,
|
|
785
|
+
exact: state.exact,
|
|
786
|
+
cutoverAt: state.cutoverAt,
|
|
787
|
+
watermarkEventId: state.watermarkEventId,
|
|
788
|
+
context: serializeContextResponse(state.context, {
|
|
789
|
+
includeDebug: options.includeDebug,
|
|
790
|
+
}),
|
|
791
|
+
sessionState: state.sessionState,
|
|
792
|
+
turns: state.turns,
|
|
793
|
+
workingMemory: state.workingMemory,
|
|
794
|
+
knowledge: state.knowledge,
|
|
795
|
+
workItems: state.workItems,
|
|
796
|
+
workClaims: state.workClaims.map(serializeWorkClaim),
|
|
797
|
+
handoffs: state.handoffs.map(serializeHandoffRecord),
|
|
798
|
+
coordinationState: state.coordinationState
|
|
799
|
+
? {
|
|
800
|
+
ownedClaims: state.coordinationState.ownedClaims.map(serializeWorkClaim),
|
|
801
|
+
pendingInboundHandoffs: state.coordinationState.pendingInboundHandoffs.map(serializeHandoffRecord),
|
|
802
|
+
pendingOutboundHandoffs: state.coordinationState.pendingOutboundHandoffs.map(serializeHandoffRecord),
|
|
803
|
+
sharedWorkItems: state.coordinationState.sharedWorkItems,
|
|
804
|
+
}
|
|
805
|
+
: null,
|
|
806
|
+
associations: state.associations,
|
|
807
|
+
playbooks: state.playbooks,
|
|
808
|
+
};
|
|
809
|
+
}
|
|
137
810
|
function errorResult(message) {
|
|
138
811
|
return {
|
|
139
812
|
content: [{ type: 'text', text: message }],
|
|
@@ -157,6 +830,25 @@ function optionalString(value, name) {
|
|
|
157
830
|
}
|
|
158
831
|
return value;
|
|
159
832
|
}
|
|
833
|
+
function parseContextViewPolicy(value, name = 'view') {
|
|
834
|
+
if (value == null)
|
|
835
|
+
return undefined;
|
|
836
|
+
return requireEnum(value, CONTEXT_VIEW_POLICIES, name);
|
|
837
|
+
}
|
|
838
|
+
function parseActorRef(value, name = 'actor') {
|
|
839
|
+
if (value == null)
|
|
840
|
+
return undefined;
|
|
841
|
+
if (!isRecord(value)) {
|
|
842
|
+
throw new McpValidationError(`Invalid field: ${name}`);
|
|
843
|
+
}
|
|
844
|
+
return {
|
|
845
|
+
actor_kind: requireEnum(value.actor_kind, ACTOR_KINDS, `${name}.actor_kind`),
|
|
846
|
+
actor_id: requireString(value.actor_id, `${name}.actor_id`),
|
|
847
|
+
system_id: value.system_id == null ? null : requireString(value.system_id, `${name}.system_id`),
|
|
848
|
+
display_name: value.display_name == null ? null : requireString(value.display_name, `${name}.display_name`),
|
|
849
|
+
metadata: isRecord(value.metadata) ? value.metadata : null,
|
|
850
|
+
};
|
|
851
|
+
}
|
|
160
852
|
function requireEnum(value, allowed, name) {
|
|
161
853
|
if (typeof value !== 'string' || !allowed.includes(value)) {
|
|
162
854
|
throw new McpValidationError(`Invalid field: ${name}`);
|
|
@@ -169,8 +861,29 @@ function parseLimit(value) {
|
|
|
169
861
|
if (typeof value !== 'number' || !Number.isInteger(value)) {
|
|
170
862
|
throw new McpValidationError('Invalid field: limit');
|
|
171
863
|
}
|
|
864
|
+
if (value > MAX_LIST_LIMIT) {
|
|
865
|
+
throw new McpValidationError(`Invalid field: limit (maximum ${MAX_LIST_LIMIT})`);
|
|
866
|
+
}
|
|
172
867
|
return value;
|
|
173
868
|
}
|
|
869
|
+
function parseOptionalNonNegativeInteger(value, name) {
|
|
870
|
+
if (value === undefined || value === null)
|
|
871
|
+
return undefined;
|
|
872
|
+
if (typeof value !== 'number' || !Number.isInteger(value) || value < 0) {
|
|
873
|
+
throw new McpValidationError(`Invalid field: ${name} (must be a non-negative integer)`);
|
|
874
|
+
}
|
|
875
|
+
return value;
|
|
876
|
+
}
|
|
877
|
+
function parseRequiredFiniteInteger(value, name, options = {}) {
|
|
878
|
+
const parsed = parseOptionalFiniteInteger(value, { name, ...options }, failMcpValidation);
|
|
879
|
+
if (parsed == null) {
|
|
880
|
+
throw new McpValidationError(`Missing or invalid field: ${name}`);
|
|
881
|
+
}
|
|
882
|
+
return parsed;
|
|
883
|
+
}
|
|
884
|
+
function parseOptionalTemporalId(value, name) {
|
|
885
|
+
return parseOptionalTemporalIdValue(value, name, failMcpValidation);
|
|
886
|
+
}
|
|
174
887
|
function resolveScopeInput(fallbackScope, args) {
|
|
175
888
|
if (args.scope) {
|
|
176
889
|
if (!isRecord(args.scope)) {
|
|
@@ -190,8 +903,24 @@ function resolveScopeInput(fallbackScope, args) {
|
|
|
190
903
|
* For a ready-to-run stdio server, use `startMcpServer()`.
|
|
191
904
|
*/
|
|
192
905
|
export function createMcpServerHandler(config = {}) {
|
|
906
|
+
const { defaultDiffMaxEvents, maxDiffMaxEvents } = resolveDiffEventCaps(config.defaultDiffMaxEvents, config.maxDiffMaxEvents);
|
|
193
907
|
const managers = new Map();
|
|
908
|
+
const runtimes = new Map();
|
|
909
|
+
const sessionManagers = new Map();
|
|
910
|
+
const sessionRuntimes = new Map();
|
|
194
911
|
let adapterPromise = null;
|
|
912
|
+
function touchCache(cache, key, value, limit, onEvict) {
|
|
913
|
+
cache.delete(key);
|
|
914
|
+
cache.set(key, value);
|
|
915
|
+
while (cache.size > limit) {
|
|
916
|
+
const oldestEntry = cache.entries().next().value;
|
|
917
|
+
if (!oldestEntry)
|
|
918
|
+
break;
|
|
919
|
+
const [oldestKey, oldestValue] = oldestEntry;
|
|
920
|
+
cache.delete(oldestKey);
|
|
921
|
+
onEvict?.(oldestKey, oldestValue);
|
|
922
|
+
}
|
|
923
|
+
}
|
|
195
924
|
async function getAsyncAdapter() {
|
|
196
925
|
if (!adapterPromise) {
|
|
197
926
|
adapterPromise = (async () => {
|
|
@@ -200,6 +929,9 @@ export function createMcpServerHandler(config = {}) {
|
|
|
200
929
|
return {
|
|
201
930
|
asyncAdapter: wrapSyncAdapter(sqlite),
|
|
202
931
|
embeddingAdapter: sqlite.embeddings,
|
|
932
|
+
close: async () => {
|
|
933
|
+
sqlite.close();
|
|
934
|
+
},
|
|
203
935
|
};
|
|
204
936
|
}
|
|
205
937
|
const moduleName = 'pg';
|
|
@@ -211,10 +943,13 @@ export function createMcpServerHandler(config = {}) {
|
|
|
211
943
|
const pool = new Pool({
|
|
212
944
|
connectionString: config.databaseUrl ?? process.env.MEMORY_DATABASE_URL,
|
|
213
945
|
});
|
|
214
|
-
const asyncAdapter = createPostgresAdapter(pool);
|
|
946
|
+
const asyncAdapter = createPostgresAdapter(pool, { ownsPool: false });
|
|
215
947
|
return {
|
|
216
948
|
asyncAdapter,
|
|
217
949
|
embeddingAdapter: createPostgresEmbeddingAdapter(pool),
|
|
950
|
+
close: async () => {
|
|
951
|
+
await pool.end();
|
|
952
|
+
},
|
|
218
953
|
};
|
|
219
954
|
})();
|
|
220
955
|
}
|
|
@@ -225,11 +960,11 @@ export function createMcpServerHandler(config = {}) {
|
|
|
225
960
|
? `scope:${scopeInput}`
|
|
226
961
|
: JSON.stringify(normalizeScope(scopeInput));
|
|
227
962
|
const existing = managers.get(key);
|
|
228
|
-
if (existing)
|
|
963
|
+
if (existing) {
|
|
964
|
+
touchCache(managers, key, existing, MANAGER_CACHE_LIMIT);
|
|
229
965
|
return existing;
|
|
966
|
+
}
|
|
230
967
|
const baseOptions = {
|
|
231
|
-
adapter: 'sqlite',
|
|
232
|
-
path: config.dbPath ?? ':memory:',
|
|
233
968
|
scope: scopeInput,
|
|
234
969
|
summarizer: config.summarizer ?? 'extractive',
|
|
235
970
|
extractor: config.extractor ?? 'regex',
|
|
@@ -237,17 +972,80 @@ export function createMcpServerHandler(config = {}) {
|
|
|
237
972
|
qualityMode: config.qualityMode,
|
|
238
973
|
qualityTier: config.qualityTier,
|
|
239
974
|
crossScopeLevel: config.crossScopeLevel,
|
|
975
|
+
autoDetectWorkspace: config.autoDetectWorkspace,
|
|
976
|
+
structuredClient: config.structuredClient,
|
|
240
977
|
};
|
|
241
978
|
const adapterContext = await getAsyncAdapter();
|
|
242
979
|
const manager = createMemoryWithAsyncAdapter({
|
|
243
980
|
...baseOptions,
|
|
244
981
|
asyncAdapter: adapterContext.asyncAdapter,
|
|
245
982
|
embeddingAdapter: adapterContext.embeddingAdapter,
|
|
983
|
+
closeAdapter: false,
|
|
984
|
+
});
|
|
985
|
+
touchCache(managers, key, manager, MANAGER_CACHE_LIMIT, (evictedKey) => {
|
|
986
|
+
runtimes.delete(evictedKey);
|
|
246
987
|
});
|
|
247
|
-
managers.set(key, manager);
|
|
248
988
|
return manager;
|
|
249
989
|
}
|
|
250
|
-
|
|
990
|
+
async function getRuntime(scopeInput) {
|
|
991
|
+
const key = typeof scopeInput === 'string'
|
|
992
|
+
? `scope:${scopeInput}`
|
|
993
|
+
: JSON.stringify(normalizeScope(scopeInput));
|
|
994
|
+
const existing = runtimes.get(key);
|
|
995
|
+
if (existing) {
|
|
996
|
+
runtimes.delete(key);
|
|
997
|
+
runtimes.set(key, existing);
|
|
998
|
+
return existing;
|
|
999
|
+
}
|
|
1000
|
+
const manager = await getManager(scopeInput);
|
|
1001
|
+
const runtime = createMemoryRuntime(manager, { snapshotMode: true });
|
|
1002
|
+
touchCache(runtimes, key, runtime, RUNTIME_CACHE_LIMIT);
|
|
1003
|
+
return runtime;
|
|
1004
|
+
}
|
|
1005
|
+
/**
|
|
1006
|
+
* Snapshot-specific runtime keyed by (scope, sessionId). The plain
|
|
1007
|
+
* getRuntime/getManager pair binds each scope to a single session, which
|
|
1008
|
+
* collapses snapshots across multiple URL-named sessions. Snapshot actions
|
|
1009
|
+
* must route to a manager whose bound sessionId matches the caller.
|
|
1010
|
+
*/
|
|
1011
|
+
async function getSessionRuntime(scopeInput, sessionId) {
|
|
1012
|
+
const scopeKey = typeof scopeInput === 'string'
|
|
1013
|
+
? `scope:${scopeInput}`
|
|
1014
|
+
: JSON.stringify(normalizeScope(scopeInput));
|
|
1015
|
+
const key = `${scopeKey}|session:${sessionId}`;
|
|
1016
|
+
const existing = sessionRuntimes.get(key);
|
|
1017
|
+
if (existing) {
|
|
1018
|
+
sessionRuntimes.delete(key);
|
|
1019
|
+
sessionRuntimes.set(key, existing);
|
|
1020
|
+
return existing;
|
|
1021
|
+
}
|
|
1022
|
+
const baseOptions = {
|
|
1023
|
+
scope: scopeInput,
|
|
1024
|
+
sessionId,
|
|
1025
|
+
summarizer: config.summarizer ?? 'extractive',
|
|
1026
|
+
extractor: config.extractor ?? 'regex',
|
|
1027
|
+
preset: config.preset,
|
|
1028
|
+
qualityMode: config.qualityMode,
|
|
1029
|
+
qualityTier: config.qualityTier,
|
|
1030
|
+
crossScopeLevel: config.crossScopeLevel,
|
|
1031
|
+
autoDetectWorkspace: config.autoDetectWorkspace,
|
|
1032
|
+
structuredClient: config.structuredClient,
|
|
1033
|
+
};
|
|
1034
|
+
const adapterContext = await getAsyncAdapter();
|
|
1035
|
+
const manager = createMemoryWithAsyncAdapter({
|
|
1036
|
+
...baseOptions,
|
|
1037
|
+
asyncAdapter: adapterContext.asyncAdapter,
|
|
1038
|
+
embeddingAdapter: adapterContext.embeddingAdapter,
|
|
1039
|
+
closeAdapter: false,
|
|
1040
|
+
});
|
|
1041
|
+
touchCache(sessionManagers, key, manager, SESSION_MANAGER_CACHE_LIMIT, (evictedKey, evictedManager) => {
|
|
1042
|
+
sessionRuntimes.delete(evictedKey);
|
|
1043
|
+
void evictedManager.close().catch(() => undefined);
|
|
1044
|
+
});
|
|
1045
|
+
const runtime = createMemoryRuntime(manager, { snapshotMode: true });
|
|
1046
|
+
touchCache(sessionRuntimes, key, runtime, RUNTIME_CACHE_LIMIT);
|
|
1047
|
+
return runtime;
|
|
1048
|
+
}
|
|
251
1049
|
async function callTool(name, args) {
|
|
252
1050
|
try {
|
|
253
1051
|
const requestManager = await getManager(resolveScopeInput(config.scope, args));
|
|
@@ -266,30 +1064,68 @@ export function createMcpServerHandler(config = {}) {
|
|
|
266
1064
|
});
|
|
267
1065
|
}
|
|
268
1066
|
case 'memory_get_context': {
|
|
269
|
-
const context = await requestManager.getContext(args.relevanceQuery ? String(args.relevanceQuery) : undefined
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
1067
|
+
const context = await requestManager.getContext(args.relevanceQuery ? String(args.relevanceQuery) : undefined, {
|
|
1068
|
+
view: parseContextViewPolicy(args.view),
|
|
1069
|
+
viewer: parseActorRef(args.viewer, 'viewer'),
|
|
1070
|
+
includeCoordinationState: args.includeCoordinationState === true,
|
|
1071
|
+
});
|
|
1072
|
+
return jsonResult(serializeContextResponse(context, {
|
|
1073
|
+
includeDebug: args.includeDebug === true,
|
|
1074
|
+
}));
|
|
1075
|
+
}
|
|
1076
|
+
case 'memory_get_state_at': {
|
|
1077
|
+
const asOf = parseOptionalFiniteNumber(args.asOf, { name: 'asOf' }, failMcpValidation);
|
|
1078
|
+
if (asOf == null) {
|
|
1079
|
+
throw new McpValidationError('Missing or invalid field: asOf');
|
|
1080
|
+
}
|
|
1081
|
+
const state = await requestManager.getStateAt(asOf, {
|
|
1082
|
+
relevanceQuery: optionalString(args.relevanceQuery, 'relevanceQuery'),
|
|
1083
|
+
view: parseContextViewPolicy(args.view),
|
|
1084
|
+
viewer: parseActorRef(args.viewer, 'viewer'),
|
|
1085
|
+
includeCoordinationState: args.includeCoordinationState === true,
|
|
1086
|
+
});
|
|
1087
|
+
return jsonResult(serializeTemporalState(state, {
|
|
1088
|
+
includeDebug: args.includeDebug === true,
|
|
1089
|
+
}));
|
|
1090
|
+
}
|
|
1091
|
+
case 'memory_get_timeline': {
|
|
1092
|
+
const timeline = await requestManager.getTimeline({
|
|
1093
|
+
sessionId: optionalString(args.sessionId, 'sessionId'),
|
|
1094
|
+
entityKind: args.entityKind,
|
|
1095
|
+
entityId: optionalString(args.entityId, 'entityId'),
|
|
1096
|
+
startAt: parseOptionalFiniteNumber(args.startAt, { name: 'startAt' }, failMcpValidation),
|
|
1097
|
+
endAt: parseOptionalFiniteNumber(args.endAt, { name: 'endAt' }, failMcpValidation),
|
|
1098
|
+
limit: parseLimit(args.limit),
|
|
1099
|
+
cursor: parseOptionalTemporalId(args.cursor, 'cursor'),
|
|
1100
|
+
});
|
|
1101
|
+
return jsonResult(serializeTimelineResult(timeline));
|
|
1102
|
+
}
|
|
1103
|
+
case 'memory_diff_state': {
|
|
1104
|
+
const from = parseOptionalFiniteNumber(args.from, { name: 'from' }, failMcpValidation);
|
|
1105
|
+
const to = parseOptionalFiniteNumber(args.to, { name: 'to' }, failMcpValidation);
|
|
1106
|
+
const maxEvents = parseOptionalFiniteInteger(args.maxEvents, { name: 'maxEvents', min: 1, max: maxDiffMaxEvents }, failMcpValidation);
|
|
1107
|
+
if (from == null || to == null) {
|
|
1108
|
+
throw new McpValidationError('Missing or invalid fields: from/to');
|
|
1109
|
+
}
|
|
1110
|
+
const diff = await requestManager.diffState(from, to, {
|
|
1111
|
+
sessionId: optionalString(args.sessionId, 'sessionId'),
|
|
1112
|
+
entityKind: args.entityKind,
|
|
1113
|
+
entityId: optionalString(args.entityId, 'entityId'),
|
|
1114
|
+
maxEvents: maxEvents ?? defaultDiffMaxEvents,
|
|
292
1115
|
});
|
|
1116
|
+
return jsonResult(diff);
|
|
1117
|
+
}
|
|
1118
|
+
case 'memory_list_events': {
|
|
1119
|
+
const events = await requestManager.listMemoryEvents({
|
|
1120
|
+
sessionId: optionalString(args.sessionId, 'sessionId'),
|
|
1121
|
+
entityKind: args.entityKind,
|
|
1122
|
+
entityId: optionalString(args.entityId, 'entityId'),
|
|
1123
|
+
startAt: parseOptionalFiniteNumber(args.startAt, { name: 'startAt' }, failMcpValidation),
|
|
1124
|
+
endAt: parseOptionalFiniteNumber(args.endAt, { name: 'endAt' }, failMcpValidation),
|
|
1125
|
+
limit: parseLimit(args.limit),
|
|
1126
|
+
cursor: parseOptionalTemporalId(args.cursor, 'cursor'),
|
|
1127
|
+
});
|
|
1128
|
+
return jsonResult(serializeTimelineResult(events));
|
|
293
1129
|
}
|
|
294
1130
|
case 'memory_search': {
|
|
295
1131
|
const results = await requestManager.search(requireString(args.query, 'query'), args.limit != null ? { limit: parseLimit(args.limit) } : undefined);
|
|
@@ -330,9 +1166,112 @@ export function createMcpServerHandler(config = {}) {
|
|
|
330
1166
|
return jsonResult({ stored: true, knowledgeId: fact.id });
|
|
331
1167
|
}
|
|
332
1168
|
case 'memory_track_work': {
|
|
333
|
-
const item = await requestManager.trackWorkItem(requireString(args.title, 'title'), requireEnum(args.kind ?? 'objective', ['objective', 'unresolved_work', 'constraint'], 'kind'), requireEnum(args.status ?? 'open', ['open', 'in_progress', 'blocked', 'done'], 'status'), optionalString(args.detail, 'detail')
|
|
1169
|
+
const item = await requestManager.trackWorkItem(requireString(args.title, 'title'), requireEnum(args.kind ?? 'objective', ['objective', 'unresolved_work', 'constraint'], 'kind'), requireEnum(args.status ?? 'open', ['open', 'in_progress', 'blocked', 'done'], 'status'), optionalString(args.detail, 'detail'), {
|
|
1170
|
+
visibilityClass: args.visibility_class == null
|
|
1171
|
+
? undefined
|
|
1172
|
+
: requireEnum(args.visibility_class, MEMORY_VISIBILITY_CLASSES, 'visibility_class'),
|
|
1173
|
+
});
|
|
334
1174
|
return jsonResult({ tracked: true, workItemId: item.id });
|
|
335
1175
|
}
|
|
1176
|
+
case 'memory_update_work_item': {
|
|
1177
|
+
const item = await requestManager.updateWorkItem(parseRequiredFiniteInteger(args.id, 'id', { min: 1 }), {
|
|
1178
|
+
title: args.title != null ? requireString(args.title, 'title') : undefined,
|
|
1179
|
+
detail: args.detail != null ? optionalString(args.detail, 'detail') ?? null : undefined,
|
|
1180
|
+
status: args.status != null
|
|
1181
|
+
? requireEnum(args.status, ['open', 'in_progress', 'blocked', 'done'], 'status')
|
|
1182
|
+
: undefined,
|
|
1183
|
+
visibility_class: args.visibility_class != null
|
|
1184
|
+
? requireEnum(args.visibility_class, MEMORY_VISIBILITY_CLASSES, 'visibility_class')
|
|
1185
|
+
: undefined,
|
|
1186
|
+
}, {
|
|
1187
|
+
expectedVersion: parseOptionalFiniteInteger(args.expectedVersion, { name: 'expectedVersion', min: 0 }, failMcpValidation),
|
|
1188
|
+
});
|
|
1189
|
+
return jsonResult({ workItem: item });
|
|
1190
|
+
}
|
|
1191
|
+
case 'memory_claim_work_item': {
|
|
1192
|
+
const actor = parseActorRef(args.actor, 'actor');
|
|
1193
|
+
if (!actor)
|
|
1194
|
+
throw new McpValidationError('Missing or invalid field: actor');
|
|
1195
|
+
const claim = await requestManager.claimWorkItem({
|
|
1196
|
+
workItemId: parseRequiredFiniteInteger(args.workItemId, 'workItemId', { min: 1 }),
|
|
1197
|
+
actor,
|
|
1198
|
+
leaseSeconds: parseOptionalFiniteInteger(args.leaseSeconds, { name: 'leaseSeconds', min: 1 }, failMcpValidation),
|
|
1199
|
+
});
|
|
1200
|
+
return jsonResult({ claim: serializeWorkClaim(claim) });
|
|
1201
|
+
}
|
|
1202
|
+
case 'memory_renew_work_claim': {
|
|
1203
|
+
const actor = parseActorRef(args.actor, 'actor');
|
|
1204
|
+
if (!actor)
|
|
1205
|
+
throw new McpValidationError('Missing or invalid field: actor');
|
|
1206
|
+
const claim = await requestManager.renewWorkClaim(parseRequiredFiniteInteger(args.claimId, 'claimId', { min: 1 }), actor, parseOptionalFiniteInteger(args.leaseSeconds, { name: 'leaseSeconds', min: 1 }, failMcpValidation));
|
|
1207
|
+
return jsonResult({ claim: claim ? serializeWorkClaim(claim) : null });
|
|
1208
|
+
}
|
|
1209
|
+
case 'memory_release_work_claim': {
|
|
1210
|
+
const actor = parseActorRef(args.actor, 'actor');
|
|
1211
|
+
if (!actor)
|
|
1212
|
+
throw new McpValidationError('Missing or invalid field: actor');
|
|
1213
|
+
const claim = await requestManager.releaseWorkClaim(parseRequiredFiniteInteger(args.claimId, 'claimId', { min: 1 }), actor, optionalString(args.reason, 'reason'));
|
|
1214
|
+
return jsonResult({ claim: claim ? serializeWorkClaim(claim) : null });
|
|
1215
|
+
}
|
|
1216
|
+
case 'memory_list_work_claims': {
|
|
1217
|
+
const claims = await requestManager.listWorkClaims();
|
|
1218
|
+
return jsonResult({ claims: claims.map(serializeWorkClaim) });
|
|
1219
|
+
}
|
|
1220
|
+
case 'memory_handoff_work_item': {
|
|
1221
|
+
const fromActor = parseActorRef(args.fromActor, 'fromActor');
|
|
1222
|
+
const toActor = parseActorRef(args.toActor, 'toActor');
|
|
1223
|
+
if (!fromActor || !toActor) {
|
|
1224
|
+
throw new McpValidationError('Missing or invalid field: fromActor/toActor');
|
|
1225
|
+
}
|
|
1226
|
+
const handoff = await requestManager.handoffWorkItem({
|
|
1227
|
+
workItemId: parseRequiredFiniteInteger(args.workItemId, 'workItemId', { min: 1 }),
|
|
1228
|
+
fromActor,
|
|
1229
|
+
toActor,
|
|
1230
|
+
summary: requireString(args.summary, 'summary'),
|
|
1231
|
+
contextBundleRef: optionalString(args.contextBundleRef, 'contextBundleRef') ?? null,
|
|
1232
|
+
expiresAt: parseOptionalFiniteInteger(args.expiresAt, { name: 'expiresAt', min: 0 }, failMcpValidation) ?? null,
|
|
1233
|
+
});
|
|
1234
|
+
return jsonResult({ handoff: serializeHandoffRecord(handoff) });
|
|
1235
|
+
}
|
|
1236
|
+
case 'memory_accept_handoff': {
|
|
1237
|
+
const actor = parseActorRef(args.actor, 'actor');
|
|
1238
|
+
if (!actor)
|
|
1239
|
+
throw new McpValidationError('Missing or invalid field: actor');
|
|
1240
|
+
const handoff = await requestManager.acceptHandoff(parseRequiredFiniteInteger(args.handoffId, 'handoffId', { min: 1 }), actor, optionalString(args.reason, 'reason'));
|
|
1241
|
+
return jsonResult({ handoff: handoff ? serializeHandoffRecord(handoff) : null });
|
|
1242
|
+
}
|
|
1243
|
+
case 'memory_reject_handoff': {
|
|
1244
|
+
const actor = parseActorRef(args.actor, 'actor');
|
|
1245
|
+
if (!actor)
|
|
1246
|
+
throw new McpValidationError('Missing or invalid field: actor');
|
|
1247
|
+
const handoff = await requestManager.rejectHandoff(parseRequiredFiniteInteger(args.handoffId, 'handoffId', { min: 1 }), actor, optionalString(args.reason, 'reason'));
|
|
1248
|
+
return jsonResult({ handoff: handoff ? serializeHandoffRecord(handoff) : null });
|
|
1249
|
+
}
|
|
1250
|
+
case 'memory_cancel_handoff': {
|
|
1251
|
+
const actor = parseActorRef(args.actor, 'actor');
|
|
1252
|
+
if (!actor)
|
|
1253
|
+
throw new McpValidationError('Missing or invalid field: actor');
|
|
1254
|
+
const handoff = await requestManager.cancelHandoff(parseRequiredFiniteInteger(args.handoffId, 'handoffId', { min: 1 }), actor, optionalString(args.reason, 'reason'));
|
|
1255
|
+
return jsonResult({ handoff: handoff ? serializeHandoffRecord(handoff) : null });
|
|
1256
|
+
}
|
|
1257
|
+
case 'memory_list_pending_handoffs': {
|
|
1258
|
+
const handoffs = await requestManager.listPendingHandoffs({
|
|
1259
|
+
direction: args.direction == null
|
|
1260
|
+
? 'all'
|
|
1261
|
+
: requireEnum(args.direction, ['inbound', 'outbound', 'all'], 'direction'),
|
|
1262
|
+
});
|
|
1263
|
+
return jsonResult({ handoffs: handoffs.map(serializeHandoffRecord) });
|
|
1264
|
+
}
|
|
1265
|
+
case 'memory_stream_changes': {
|
|
1266
|
+
const events = await requestManager.listMemoryEvents({
|
|
1267
|
+
cursor: parseOptionalTemporalId(args.cursor, 'cursor'),
|
|
1268
|
+
sessionId: optionalString(args.sessionId, 'sessionId'),
|
|
1269
|
+
entityKind: optionalString(args.entityKind, 'entityKind'),
|
|
1270
|
+
entityId: optionalString(args.entityId, 'entityId'),
|
|
1271
|
+
limit: parseLimit(args.limit),
|
|
1272
|
+
});
|
|
1273
|
+
return jsonResult(serializeTimelineResult(events));
|
|
1274
|
+
}
|
|
336
1275
|
case 'memory_force_compact': {
|
|
337
1276
|
const result = await requestManager.forceCompact();
|
|
338
1277
|
return jsonResult({
|
|
@@ -341,13 +1280,18 @@ export function createMcpServerHandler(config = {}) {
|
|
|
341
1280
|
});
|
|
342
1281
|
}
|
|
343
1282
|
case 'memory_get_health': {
|
|
344
|
-
const context = await
|
|
1283
|
+
const [context, diagnostics] = await Promise.all([
|
|
1284
|
+
requestManager.getContext(),
|
|
1285
|
+
requestManager.getRuntimeDiagnostics(),
|
|
1286
|
+
]);
|
|
345
1287
|
return jsonResult({
|
|
346
1288
|
activeTurnCount: context.activeTurns.length,
|
|
347
1289
|
tokenEstimate: context.tokenEstimate,
|
|
348
1290
|
knowledgeCount: context.relevantKnowledge.length,
|
|
349
1291
|
objectiveCount: context.activeObjectives.length,
|
|
350
1292
|
unresolvedWorkCount: context.unresolvedWork.length,
|
|
1293
|
+
sessionStateUpdatedAt: context.sessionState.updatedAt,
|
|
1294
|
+
circuitBreakers: diagnostics.circuitBreakers,
|
|
351
1295
|
});
|
|
352
1296
|
}
|
|
353
1297
|
case 'memory_run_maintenance': {
|
|
@@ -358,11 +1302,185 @@ export function createMcpServerHandler(config = {}) {
|
|
|
358
1302
|
deletedWorkItems: report.deletedWorkItemIds.length,
|
|
359
1303
|
});
|
|
360
1304
|
}
|
|
1305
|
+
case 'memory_search_episodes': {
|
|
1306
|
+
const episodeTimeRange = isRecord(args.timeRange)
|
|
1307
|
+
? {
|
|
1308
|
+
start_at: parseOptionalFiniteNumber(args.timeRange.start_at, { name: 'timeRange.start_at' }, failMcpValidation),
|
|
1309
|
+
end_at: parseOptionalFiniteNumber(args.timeRange.end_at, { name: 'timeRange.end_at' }, failMcpValidation),
|
|
1310
|
+
}
|
|
1311
|
+
: undefined;
|
|
1312
|
+
const episodes = await requestManager.searchEpisodes({
|
|
1313
|
+
query: requireString(args.query, 'query'),
|
|
1314
|
+
detailLevel: args.detailLevel != null
|
|
1315
|
+
? requireEnum(args.detailLevel, ['abstract', 'overview', 'full'], 'detailLevel')
|
|
1316
|
+
: undefined,
|
|
1317
|
+
limit: parseLimit(args.limit),
|
|
1318
|
+
timeRange: episodeTimeRange,
|
|
1319
|
+
});
|
|
1320
|
+
return jsonResult({ episodes });
|
|
1321
|
+
}
|
|
1322
|
+
case 'memory_summarize_episode': {
|
|
1323
|
+
const summary = await requestManager.summarizeEpisode(requireString(args.sessionId, 'sessionId'), args.detailLevel != null
|
|
1324
|
+
? { detailLevel: requireEnum(args.detailLevel, ['abstract', 'overview', 'full'], 'detailLevel') }
|
|
1325
|
+
: undefined);
|
|
1326
|
+
return jsonResult(summary);
|
|
1327
|
+
}
|
|
1328
|
+
case 'memory_reflect': {
|
|
1329
|
+
const reflectTimeRange = isRecord(args.timeRange)
|
|
1330
|
+
? {
|
|
1331
|
+
start_at: parseOptionalFiniteNumber(args.timeRange.start_at, { name: 'timeRange.start_at' }, failMcpValidation),
|
|
1332
|
+
end_at: parseOptionalFiniteNumber(args.timeRange.end_at, { name: 'timeRange.end_at' }, failMcpValidation),
|
|
1333
|
+
}
|
|
1334
|
+
: undefined;
|
|
1335
|
+
const result = await requestManager.reflect({
|
|
1336
|
+
query: requireString(args.query, 'query'),
|
|
1337
|
+
detailLevel: args.detailLevel != null
|
|
1338
|
+
? requireEnum(args.detailLevel, ['abstract', 'overview', 'full'], 'detailLevel')
|
|
1339
|
+
: undefined,
|
|
1340
|
+
includeEpisodic: args.includeEpisodic != null ? Boolean(args.includeEpisodic) : undefined,
|
|
1341
|
+
includeDeclarative: args.includeDeclarative != null ? Boolean(args.includeDeclarative) : undefined,
|
|
1342
|
+
limit: parseLimit(args.limit),
|
|
1343
|
+
timeRange: reflectTimeRange,
|
|
1344
|
+
});
|
|
1345
|
+
return jsonResult(result);
|
|
1346
|
+
}
|
|
1347
|
+
case 'memory_search_cognitive': {
|
|
1348
|
+
const cognitiveResult = await requestManager.searchCognitive({
|
|
1349
|
+
query: requireString(args.query, 'query'),
|
|
1350
|
+
types: Array.isArray(args.types) ? args.types : undefined,
|
|
1351
|
+
limit: parseLimit(args.limit),
|
|
1352
|
+
minimumTrustScore: parseOptionalFiniteNumber(args.minimumTrustScore, { name: 'minimumTrustScore', min: 0, max: 1 }, failMcpValidation),
|
|
1353
|
+
activeOnly: args.activeOnly != null ? Boolean(args.activeOnly) : undefined,
|
|
1354
|
+
});
|
|
1355
|
+
return jsonResult(cognitiveResult);
|
|
1356
|
+
}
|
|
1357
|
+
case 'memory_get_profile': {
|
|
1358
|
+
const profile = await requestManager.getProfile({
|
|
1359
|
+
view: args.view != null
|
|
1360
|
+
? requireEnum(args.view, ['user', 'operator', 'workspace'], 'view')
|
|
1361
|
+
: undefined,
|
|
1362
|
+
sections: Array.isArray(args.sections)
|
|
1363
|
+
? args.sections.map((s) => requireEnum(s, ['identity', 'preferences', 'communication', 'constraints', 'workflows'], 'sections'))
|
|
1364
|
+
: undefined,
|
|
1365
|
+
minimumTrustScore: parseOptionalFiniteNumber(args.minimumTrustScore, { name: 'minimumTrustScore', min: 0, max: 1 }, failMcpValidation),
|
|
1366
|
+
includeProvisional: args.includeProvisional != null ? Boolean(args.includeProvisional) : undefined,
|
|
1367
|
+
includeDisputed: args.includeDisputed != null ? Boolean(args.includeDisputed) : undefined,
|
|
1368
|
+
});
|
|
1369
|
+
return jsonResult(profile);
|
|
1370
|
+
}
|
|
1371
|
+
case 'memory_create_playbook': {
|
|
1372
|
+
const playbook = await requestManager.createPlaybook({
|
|
1373
|
+
title: requireString(args.title, 'title'),
|
|
1374
|
+
description: requireString(args.description, 'description'),
|
|
1375
|
+
instructions: requireString(args.instructions, 'instructions'),
|
|
1376
|
+
references: Array.isArray(args.references) ? args.references.map(String) : undefined,
|
|
1377
|
+
templates: Array.isArray(args.templates) ? args.templates.map(String) : undefined,
|
|
1378
|
+
scripts: Array.isArray(args.scripts) ? args.scripts.map(String) : undefined,
|
|
1379
|
+
assets: Array.isArray(args.assets) ? args.assets.map(String) : undefined,
|
|
1380
|
+
tags: Array.isArray(args.tags) ? args.tags.map(String) : undefined,
|
|
1381
|
+
status: args.status != null
|
|
1382
|
+
? requireEnum(args.status, ['draft', 'active', 'deprecated', 'archived'], 'status')
|
|
1383
|
+
: undefined,
|
|
1384
|
+
});
|
|
1385
|
+
return jsonResult({ playbook });
|
|
1386
|
+
}
|
|
1387
|
+
case 'memory_create_playbook_from_task': {
|
|
1388
|
+
const playbook = await requestManager.createPlaybookFromTask({
|
|
1389
|
+
title: requireString(args.title, 'title'),
|
|
1390
|
+
description: requireString(args.description, 'description'),
|
|
1391
|
+
sessionId: requireString(args.sessionId, 'sessionId'),
|
|
1392
|
+
tags: Array.isArray(args.tags) ? args.tags.map(String) : undefined,
|
|
1393
|
+
sourceWorkingMemoryId: parseOptionalFiniteInteger(args.sourceWorkingMemoryId, { name: 'sourceWorkingMemoryId', min: 1 }, failMcpValidation),
|
|
1394
|
+
});
|
|
1395
|
+
return jsonResult({ playbook });
|
|
1396
|
+
}
|
|
1397
|
+
case 'memory_search_playbooks': {
|
|
1398
|
+
const results = await requestManager.searchPlaybooks(requireString(args.query, 'query'), { limit: parseLimit(args.limit) });
|
|
1399
|
+
// Return full playbook records with rank so consumers get the
|
|
1400
|
+
// same shape as the HTTP /v1/playbooks search response.
|
|
1401
|
+
return jsonResult({
|
|
1402
|
+
playbooks: results.map((r) => ({ ...r.item, rank: r.rank })),
|
|
1403
|
+
});
|
|
1404
|
+
}
|
|
1405
|
+
case 'memory_revise_playbook': {
|
|
1406
|
+
const playbookId = parseRequiredFiniteInteger(args.playbookId, 'playbookId', { min: 1 });
|
|
1407
|
+
const result = await requestManager.revisePlaybook(playbookId, requireString(args.newInstructions, 'newInstructions'), requireString(args.revisionReason, 'revisionReason'), optionalString(args.sourceSessionId, 'sourceSessionId'));
|
|
1408
|
+
return jsonResult({ playbook: result.playbook, revision: result.revision });
|
|
1409
|
+
}
|
|
1410
|
+
case 'memory_use_playbook': {
|
|
1411
|
+
const playbookId = parseRequiredFiniteInteger(args.playbookId, 'playbookId', { min: 1 });
|
|
1412
|
+
await requestManager.recordPlaybookUse(playbookId);
|
|
1413
|
+
const playbook = await requestManager.getPlaybook(playbookId);
|
|
1414
|
+
return jsonResult({ playbook });
|
|
1415
|
+
}
|
|
1416
|
+
case 'memory_get_associations': {
|
|
1417
|
+
const kind = requireEnum(args.kind, ASSOCIATION_TARGET_KINDS, 'kind');
|
|
1418
|
+
const id = parseRequiredFiniteInteger(args.id, 'id', { min: 1 });
|
|
1419
|
+
if (args.traverse) {
|
|
1420
|
+
const graph = await requestManager.traverseAssociations(kind, id, {
|
|
1421
|
+
maxDepth: parseOptionalNonNegativeInteger(args.maxDepth, 'maxDepth'),
|
|
1422
|
+
maxNodes: parseOptionalNonNegativeInteger(args.maxNodes, 'maxNodes'),
|
|
1423
|
+
});
|
|
1424
|
+
return jsonResult(graph);
|
|
1425
|
+
}
|
|
1426
|
+
const assocs = await requestManager.getAssociations(kind, id);
|
|
1427
|
+
return jsonResult(assocs);
|
|
1428
|
+
}
|
|
1429
|
+
case 'memory_add_association': {
|
|
1430
|
+
const sourceId = typeof args.source_id === 'number' && Number.isInteger(args.source_id) && args.source_id > 0
|
|
1431
|
+
? args.source_id
|
|
1432
|
+
: (() => { throw new McpValidationError('Missing or invalid field: source_id (must be positive integer)'); })();
|
|
1433
|
+
const targetId = typeof args.target_id === 'number' && Number.isInteger(args.target_id) && args.target_id > 0
|
|
1434
|
+
? args.target_id
|
|
1435
|
+
: (() => { throw new McpValidationError('Missing or invalid field: target_id (must be positive integer)'); })();
|
|
1436
|
+
let confidence;
|
|
1437
|
+
if (args.confidence !== undefined && args.confidence !== null) {
|
|
1438
|
+
if (typeof args.confidence !== 'number' || Number.isNaN(args.confidence) || args.confidence < 0 || args.confidence > 1) {
|
|
1439
|
+
throw new McpValidationError('Invalid field: confidence (must be a number in [0, 1])');
|
|
1440
|
+
}
|
|
1441
|
+
confidence = args.confidence;
|
|
1442
|
+
}
|
|
1443
|
+
const association = await requestManager.addAssociation({
|
|
1444
|
+
source_kind: requireEnum(args.source_kind, ASSOCIATION_TARGET_KINDS, 'source_kind'),
|
|
1445
|
+
source_id: sourceId,
|
|
1446
|
+
target_kind: requireEnum(args.target_kind, ASSOCIATION_TARGET_KINDS, 'target_kind'),
|
|
1447
|
+
target_id: targetId,
|
|
1448
|
+
association_type: requireEnum(args.association_type, ASSOCIATION_TYPES, 'association_type'),
|
|
1449
|
+
confidence,
|
|
1450
|
+
});
|
|
1451
|
+
return jsonResult({ created: true, associationId: association.id });
|
|
1452
|
+
}
|
|
1453
|
+
case 'memory_remove_association': {
|
|
1454
|
+
const id = parseRequiredFiniteInteger(args.id, 'id', { min: 1 });
|
|
1455
|
+
await requestManager.removeAssociation(id);
|
|
1456
|
+
return jsonResult({ deleted: true });
|
|
1457
|
+
}
|
|
1458
|
+
case 'memory_snapshot': {
|
|
1459
|
+
const action = requireEnum(args.action, ['capture', 'refresh', 'get'], 'action');
|
|
1460
|
+
const sessionId = requireString(args.sessionId, 'sessionId');
|
|
1461
|
+
const runtime = await getSessionRuntime(resolveScopeInput(config.scope, args), sessionId);
|
|
1462
|
+
const relevanceQuery = optionalString(args.relevanceQuery, 'relevanceQuery');
|
|
1463
|
+
if (action === 'capture') {
|
|
1464
|
+
await runtime.startSession(relevanceQuery);
|
|
1465
|
+
const snapshot = runtime.getSnapshot();
|
|
1466
|
+
return jsonResult({ snapshot: snapshot ? { ...snapshot, sessionId } : null });
|
|
1467
|
+
}
|
|
1468
|
+
if (action === 'refresh') {
|
|
1469
|
+
const snapshot = await runtime.refreshSnapshot(relevanceQuery);
|
|
1470
|
+
return jsonResult({ snapshot: snapshot ? { ...snapshot, sessionId } : null });
|
|
1471
|
+
}
|
|
1472
|
+
// get
|
|
1473
|
+
const snapshot = runtime.getSnapshot();
|
|
1474
|
+
return jsonResult({ snapshot: snapshot ? { ...snapshot, sessionId } : null });
|
|
1475
|
+
}
|
|
361
1476
|
default:
|
|
362
1477
|
return errorResult(`Unknown tool: ${name}`);
|
|
363
1478
|
}
|
|
364
1479
|
}
|
|
365
1480
|
catch (error) {
|
|
1481
|
+
if (isMemoryDomainError(error)) {
|
|
1482
|
+
return errorResult(`Error in ${name}: ${error.message}`);
|
|
1483
|
+
}
|
|
366
1484
|
return errorResult(`Error in ${name}: ${error instanceof Error ? error.message : String(error)}`);
|
|
367
1485
|
}
|
|
368
1486
|
}
|
|
@@ -371,12 +1489,14 @@ export function createMcpServerHandler(config = {}) {
|
|
|
371
1489
|
callTool,
|
|
372
1490
|
manager: undefined,
|
|
373
1491
|
async close() {
|
|
374
|
-
const manager = await managerPromise;
|
|
375
|
-
for (const cachedManager of managers.values()) {
|
|
376
|
-
await cachedManager.close();
|
|
377
|
-
}
|
|
378
1492
|
managers.clear();
|
|
379
|
-
|
|
1493
|
+
sessionManagers.clear();
|
|
1494
|
+
sessionRuntimes.clear();
|
|
1495
|
+
runtimes.clear();
|
|
1496
|
+
if (adapterPromise) {
|
|
1497
|
+
const adapterContext = await adapterPromise;
|
|
1498
|
+
await adapterContext.close();
|
|
1499
|
+
}
|
|
380
1500
|
},
|
|
381
1501
|
};
|
|
382
1502
|
}
|
|
@@ -405,7 +1525,7 @@ export async function startMcpServer(config = {}) {
|
|
|
405
1525
|
capabilities: { tools: {} },
|
|
406
1526
|
serverInfo: {
|
|
407
1527
|
name: 'memory-layer',
|
|
408
|
-
version: '
|
|
1528
|
+
version: '3.0.0',
|
|
409
1529
|
},
|
|
410
1530
|
},
|
|
411
1531
|
}) + '\n');
|