@j0hanz/cortex-mcp 1.6.0 → 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/engine/context.d.ts +0 -1
- package/dist/engine/events.d.ts +6 -0
- package/dist/engine/reasoner.js +3 -3
- package/dist/engine/session-store.d.ts +6 -6
- package/dist/engine/session-store.js +31 -21
- package/dist/lib/tool-contracts.js +1 -1
- package/dist/lib/types.d.ts +1 -0
- package/dist/schemas/outputs.d.ts +3 -0
- package/dist/schemas/outputs.js +4 -0
- package/dist/server.js +19 -0
- package/dist/tools/reasoning-think.js +9 -8
- package/package.json +1 -1
package/dist/engine/context.d.ts
CHANGED
package/dist/engine/events.d.ts
CHANGED
package/dist/engine/reasoner.js
CHANGED
|
@@ -20,7 +20,7 @@ export async function reason(query, level, options) {
|
|
|
20
20
|
const session = resolveSession(level, sessionId, query, targetThoughts);
|
|
21
21
|
const config = getLevelConfig(session.level);
|
|
22
22
|
const { totalThoughts } = session;
|
|
23
|
-
return runWithContext({ sessionId: session.id
|
|
23
|
+
return runWithContext({ sessionId: session.id }, () => withSessionLock(session.id, async () => {
|
|
24
24
|
throwIfReasoningAborted(abortSignal);
|
|
25
25
|
if (rollbackToStep !== undefined) {
|
|
26
26
|
sessionStore.rollback(session.id, rollbackToStep);
|
|
@@ -53,7 +53,7 @@ export async function reason(query, level, options) {
|
|
|
53
53
|
content: addedThought.content,
|
|
54
54
|
});
|
|
55
55
|
const updated = getSessionOrThrow(session.id);
|
|
56
|
-
|
|
56
|
+
emitBudgetExhaustedIfNeeded({
|
|
57
57
|
session: updated,
|
|
58
58
|
tokenBudget: config.tokenBudget,
|
|
59
59
|
generatedThoughts: addedThought.index + 1,
|
|
@@ -129,7 +129,7 @@ function resolveSession(level, sessionId, query, targetThoughts) {
|
|
|
129
129
|
}
|
|
130
130
|
const config = getLevelConfig(level);
|
|
131
131
|
const totalThoughts = resolveThoughtCount(level, query, config, targetThoughts);
|
|
132
|
-
const session = sessionStore.create(level, totalThoughts);
|
|
132
|
+
const session = sessionStore.create(level, totalThoughts, query);
|
|
133
133
|
engineEvents.emit('session:created', {
|
|
134
134
|
sessionId: session.id,
|
|
135
135
|
level,
|
|
@@ -16,7 +16,7 @@ export declare class SessionStore {
|
|
|
16
16
|
constructor(ttlMs?: number, maxSessions?: number, maxTotalTokens?: number);
|
|
17
17
|
ensureCleanupTimer(): void;
|
|
18
18
|
dispose(): void;
|
|
19
|
-
create(level: ReasoningLevel, totalThoughts?: number): Readonly<Session>;
|
|
19
|
+
create(level: ReasoningLevel, totalThoughts?: number, query?: string): Readonly<Session>;
|
|
20
20
|
get(id: string): Readonly<Session> | undefined;
|
|
21
21
|
getSummary(id: string): Readonly<SessionSummary> | undefined;
|
|
22
22
|
list(): Readonly<Session>[];
|
|
@@ -28,7 +28,7 @@ export declare class SessionStore {
|
|
|
28
28
|
delete(id: string): boolean;
|
|
29
29
|
addThought(sessionId: string, content: string, stepSummary?: string): Thought;
|
|
30
30
|
rollback(sessionId: string, toIndex: number): void;
|
|
31
|
-
reviseThought(sessionId: string, thoughtIndex: number, content: string): Thought;
|
|
31
|
+
reviseThought(sessionId: string, thoughtIndex: number, content: string, stepSummary?: string): Thought;
|
|
32
32
|
markCompleted(sessionId: string): void;
|
|
33
33
|
markCancelled(sessionId: string): void;
|
|
34
34
|
private updateSessionStatus;
|
|
@@ -49,9 +49,9 @@ export declare class SessionStore {
|
|
|
49
49
|
private snapshotThought;
|
|
50
50
|
private snapshotSession;
|
|
51
51
|
private snapshotSessionSummary;
|
|
52
|
-
private
|
|
53
|
-
private
|
|
54
|
-
private
|
|
52
|
+
private emitListChanged;
|
|
53
|
+
private emitListResourceUpdated;
|
|
54
|
+
private emitCollectionUpdated;
|
|
55
55
|
private emitSessionEvicted;
|
|
56
|
-
private
|
|
56
|
+
private emitSessionResourceUpdated;
|
|
57
57
|
}
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { Buffer } from 'node:buffer';
|
|
2
2
|
import { randomUUID } from 'node:crypto';
|
|
3
|
+
import { SessionNotFoundError } from '../lib/errors.js';
|
|
3
4
|
import { getLevelConfig } from './config.js';
|
|
4
5
|
import { engineEvents } from './events.js';
|
|
5
6
|
export const DEFAULT_SESSION_TTL_MS = 30 * 60 * 1000; // 30 minutes
|
|
6
7
|
export const DEFAULT_MAX_SESSIONS = 100;
|
|
7
8
|
export const DEFAULT_MAX_TOTAL_TOKENS = 500_000;
|
|
8
|
-
const TOKEN_ESTIMATE_DIVISOR =
|
|
9
|
+
const TOKEN_ESTIMATE_DIVISOR = 3.5;
|
|
9
10
|
const MIN_SWEEP_INTERVAL_MS = 10;
|
|
10
11
|
const MAX_SWEEP_INTERVAL_MS = 60_000;
|
|
11
12
|
function estimateTokens(text) {
|
|
@@ -52,7 +53,7 @@ export class SessionStore {
|
|
|
52
53
|
clearInterval(this.cleanupInterval);
|
|
53
54
|
this.cleanupInterval = undefined;
|
|
54
55
|
}
|
|
55
|
-
create(level, totalThoughts) {
|
|
56
|
+
create(level, totalThoughts, query) {
|
|
56
57
|
this.evictIfAtCapacity();
|
|
57
58
|
const config = getLevelConfig(level);
|
|
58
59
|
const now = Date.now();
|
|
@@ -66,12 +67,13 @@ export class SessionStore {
|
|
|
66
67
|
tokensUsed: 0,
|
|
67
68
|
createdAt: now,
|
|
68
69
|
updatedAt: now,
|
|
70
|
+
...(query !== undefined ? { query } : {}),
|
|
69
71
|
};
|
|
70
72
|
this.sessions.set(session.id, session);
|
|
71
73
|
this.addToOrder(session.id);
|
|
72
74
|
this.sortedSessionIdsCache = null;
|
|
73
|
-
this.
|
|
74
|
-
this.
|
|
75
|
+
this.emitListChanged();
|
|
76
|
+
this.emitListResourceUpdated();
|
|
75
77
|
return this.snapshotSession(session);
|
|
76
78
|
}
|
|
77
79
|
get(id) {
|
|
@@ -111,13 +113,13 @@ export class SessionStore {
|
|
|
111
113
|
return false;
|
|
112
114
|
}
|
|
113
115
|
engineEvents.emit('session:deleted', { sessionId: id });
|
|
114
|
-
this.
|
|
116
|
+
this.emitCollectionUpdated();
|
|
115
117
|
return true;
|
|
116
118
|
}
|
|
117
119
|
addThought(sessionId, content, stepSummary) {
|
|
118
120
|
const session = this.sessions.get(sessionId);
|
|
119
121
|
if (!session) {
|
|
120
|
-
throw new
|
|
122
|
+
throw new SessionNotFoundError(sessionId);
|
|
121
123
|
}
|
|
122
124
|
const tokens = estimateTokens(content);
|
|
123
125
|
this.evictForTokenHeadroom(tokens, sessionId);
|
|
@@ -137,7 +139,7 @@ export class SessionStore {
|
|
|
137
139
|
rollback(sessionId, toIndex) {
|
|
138
140
|
const session = this.sessions.get(sessionId);
|
|
139
141
|
if (!session) {
|
|
140
|
-
throw new
|
|
142
|
+
throw new SessionNotFoundError(sessionId);
|
|
141
143
|
}
|
|
142
144
|
// If toIndex is out of bounds or implies no change, return.
|
|
143
145
|
// We keep thoughts up to and including toIndex.
|
|
@@ -154,10 +156,10 @@ export class SessionStore {
|
|
|
154
156
|
this.totalTokens -= removedTokens;
|
|
155
157
|
this.markSessionTouched(session);
|
|
156
158
|
}
|
|
157
|
-
reviseThought(sessionId, thoughtIndex, content) {
|
|
159
|
+
reviseThought(sessionId, thoughtIndex, content, stepSummary) {
|
|
158
160
|
const session = this.sessions.get(sessionId);
|
|
159
161
|
if (!session) {
|
|
160
|
-
throw new
|
|
162
|
+
throw new SessionNotFoundError(sessionId);
|
|
161
163
|
}
|
|
162
164
|
const existing = session.thoughts[thoughtIndex];
|
|
163
165
|
if (!existing) {
|
|
@@ -169,13 +171,14 @@ export class SessionStore {
|
|
|
169
171
|
if (delta > 0) {
|
|
170
172
|
this.evictForTokenHeadroom(delta, sessionId);
|
|
171
173
|
}
|
|
174
|
+
const effectiveStepSummary = stepSummary ?? existing.stepSummary;
|
|
172
175
|
const revised = {
|
|
173
176
|
index: thoughtIndex,
|
|
174
177
|
content,
|
|
175
178
|
revision: existing.revision + 1,
|
|
176
179
|
tokenCount: newTokens,
|
|
177
|
-
...(
|
|
178
|
-
? { stepSummary:
|
|
180
|
+
...(effectiveStepSummary !== undefined
|
|
181
|
+
? { stepSummary: effectiveStepSummary }
|
|
179
182
|
: {}),
|
|
180
183
|
};
|
|
181
184
|
session.thoughts[thoughtIndex] = revised;
|
|
@@ -202,6 +205,12 @@ export class SessionStore {
|
|
|
202
205
|
if (session?.status === 'active') {
|
|
203
206
|
session.status = status;
|
|
204
207
|
this.markSessionTouched(session);
|
|
208
|
+
if (status === 'completed') {
|
|
209
|
+
engineEvents.emit('session:completed', { sessionId });
|
|
210
|
+
}
|
|
211
|
+
else {
|
|
212
|
+
engineEvents.emit('session:cancelled', { sessionId });
|
|
213
|
+
}
|
|
205
214
|
}
|
|
206
215
|
}
|
|
207
216
|
evictIfAtCapacity() {
|
|
@@ -254,7 +263,7 @@ export class SessionStore {
|
|
|
254
263
|
currentId = nextId;
|
|
255
264
|
}
|
|
256
265
|
if (changed) {
|
|
257
|
-
this.
|
|
266
|
+
this.emitCollectionUpdated();
|
|
258
267
|
}
|
|
259
268
|
}
|
|
260
269
|
buildSortedSessionIdsCache() {
|
|
@@ -362,7 +371,7 @@ export class SessionStore {
|
|
|
362
371
|
session._cachedSummary = undefined;
|
|
363
372
|
this.touchOrder(session.id);
|
|
364
373
|
this.sortedSessionIdsCache = null;
|
|
365
|
-
this.
|
|
374
|
+
this.emitSessionResourceUpdated(session.id);
|
|
366
375
|
}
|
|
367
376
|
getSessionIdsForIteration() {
|
|
368
377
|
this.sortedSessionIdsCache ??= this.buildSortedSessionIdsCache();
|
|
@@ -405,6 +414,7 @@ export class SessionStore {
|
|
|
405
414
|
tokensUsed: session.tokensUsed,
|
|
406
415
|
createdAt: session.createdAt,
|
|
407
416
|
updatedAt: session.updatedAt,
|
|
417
|
+
...(session.query !== undefined ? { query: session.query } : {}),
|
|
408
418
|
};
|
|
409
419
|
Object.freeze(snapshot);
|
|
410
420
|
Object.freeze(snapshot.thoughts);
|
|
@@ -430,27 +440,27 @@ export class SessionStore {
|
|
|
430
440
|
session._cachedSummary = summary;
|
|
431
441
|
return summary;
|
|
432
442
|
}
|
|
433
|
-
|
|
443
|
+
emitListChanged() {
|
|
434
444
|
engineEvents.emit('resources:changed', { uri: 'reasoning://sessions' });
|
|
435
445
|
}
|
|
436
|
-
|
|
446
|
+
emitListResourceUpdated() {
|
|
437
447
|
engineEvents.emit('resource:updated', { uri: 'reasoning://sessions' });
|
|
438
448
|
}
|
|
439
|
-
|
|
440
|
-
this.
|
|
441
|
-
this.
|
|
449
|
+
emitCollectionUpdated() {
|
|
450
|
+
this.emitListChanged();
|
|
451
|
+
this.emitListResourceUpdated();
|
|
442
452
|
}
|
|
443
453
|
emitSessionEvicted(sessionId, reason) {
|
|
444
454
|
engineEvents.emit('session:evicted', {
|
|
445
455
|
sessionId,
|
|
446
456
|
reason,
|
|
447
457
|
});
|
|
448
|
-
this.
|
|
458
|
+
this.emitCollectionUpdated();
|
|
449
459
|
}
|
|
450
|
-
|
|
460
|
+
emitSessionResourceUpdated(sessionId) {
|
|
451
461
|
engineEvents.emit('resource:updated', {
|
|
452
462
|
uri: `reasoning://sessions/${sessionId}`,
|
|
453
463
|
});
|
|
454
|
-
this.
|
|
464
|
+
this.emitListResourceUpdated();
|
|
455
465
|
}
|
|
456
466
|
}
|
|
@@ -79,7 +79,7 @@ const TOOL_CONTRACTS = [
|
|
|
79
79
|
constraints: 'optional',
|
|
80
80
|
},
|
|
81
81
|
],
|
|
82
|
-
outputShape: '{ok, result: {sessionId, level, status, thoughts[], generatedThoughts, requestedThoughts, totalThoughts, remainingThoughts, tokenBudget, tokensUsed, ttlMs, expiresAt, createdAt, updatedAt, summary}}',
|
|
82
|
+
outputShape: '{ok, result: {sessionId, query?, level, status, thoughts[], generatedThoughts, requestedThoughts, totalThoughts, remainingThoughts, tokenBudget, tokensUsed, ttlMs, expiresAt, createdAt, updatedAt, summary}}',
|
|
83
83
|
},
|
|
84
84
|
];
|
|
85
85
|
export function getToolContracts() {
|
package/dist/lib/types.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ declare const ReasoningThinkSuccessSchema: z.ZodObject<{
|
|
|
3
3
|
ok: z.ZodLiteral<true>;
|
|
4
4
|
result: z.ZodObject<{
|
|
5
5
|
sessionId: z.ZodString;
|
|
6
|
+
query: z.ZodOptional<z.ZodString>;
|
|
6
7
|
level: z.ZodEnum<{
|
|
7
8
|
basic: "basic";
|
|
8
9
|
normal: "normal";
|
|
@@ -40,6 +41,7 @@ export declare const ReasoningThinkToolOutputSchema: z.ZodObject<{
|
|
|
40
41
|
ok: z.ZodBoolean;
|
|
41
42
|
result: z.ZodOptional<z.ZodObject<{
|
|
42
43
|
sessionId: z.ZodString;
|
|
44
|
+
query: z.ZodOptional<z.ZodString>;
|
|
43
45
|
level: z.ZodEnum<{
|
|
44
46
|
basic: "basic";
|
|
45
47
|
normal: "normal";
|
|
@@ -90,6 +92,7 @@ export declare const ReasoningThinkResultSchema: z.ZodUnion<readonly [z.ZodObjec
|
|
|
90
92
|
ok: z.ZodLiteral<true>;
|
|
91
93
|
result: z.ZodObject<{
|
|
92
94
|
sessionId: z.ZodString;
|
|
95
|
+
query: z.ZodOptional<z.ZodString>;
|
|
93
96
|
level: z.ZodEnum<{
|
|
94
97
|
basic: "basic";
|
|
95
98
|
normal: "normal";
|
package/dist/schemas/outputs.js
CHANGED
|
@@ -19,6 +19,10 @@ const ReasoningThinkSuccessSchema = z.strictObject({
|
|
|
19
19
|
ok: z.literal(true),
|
|
20
20
|
result: z.strictObject({
|
|
21
21
|
sessionId: z.string(),
|
|
22
|
+
query: z
|
|
23
|
+
.string()
|
|
24
|
+
.optional()
|
|
25
|
+
.describe('Original query text for this session.'),
|
|
22
26
|
level: z.enum(REASONING_LEVELS),
|
|
23
27
|
status: z.enum(SESSION_STATUSES),
|
|
24
28
|
thoughts: z.array(ThoughtSchema),
|
package/dist/server.js
CHANGED
|
@@ -121,9 +121,22 @@ function attachEngineEventHandlers(server) {
|
|
|
121
121
|
process.stderr.write(`[cortex-mcp.server] Failed to log budget_exhausted: ${getErrorMessage(err)}\n`);
|
|
122
122
|
});
|
|
123
123
|
};
|
|
124
|
+
const onSessionLifecycle = (data) => {
|
|
125
|
+
void server.server.sendResourceListChanged().catch((err) => {
|
|
126
|
+
logNotificationFailure(RESOURCE_LIST_CHANGED_METHOD, err, {
|
|
127
|
+
sessionId: data.sessionId,
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
};
|
|
124
131
|
engineEvents.on('resources:changed', onResourcesChanged);
|
|
125
132
|
engineEvents.on('resource:updated', onResourceUpdated);
|
|
126
133
|
engineEvents.on('thought:budget-exhausted', onBudgetExhausted);
|
|
134
|
+
engineEvents.on('session:created', onSessionLifecycle);
|
|
135
|
+
engineEvents.on('session:completed', onSessionLifecycle);
|
|
136
|
+
engineEvents.on('session:cancelled', onSessionLifecycle);
|
|
137
|
+
engineEvents.on('session:expired', onSessionLifecycle);
|
|
138
|
+
engineEvents.on('session:evicted', onSessionLifecycle);
|
|
139
|
+
engineEvents.on('session:deleted', onSessionLifecycle);
|
|
127
140
|
let detached = false;
|
|
128
141
|
return () => {
|
|
129
142
|
if (detached) {
|
|
@@ -133,6 +146,12 @@ function attachEngineEventHandlers(server) {
|
|
|
133
146
|
engineEvents.off('resources:changed', onResourcesChanged);
|
|
134
147
|
engineEvents.off('resource:updated', onResourceUpdated);
|
|
135
148
|
engineEvents.off('thought:budget-exhausted', onBudgetExhausted);
|
|
149
|
+
engineEvents.off('session:created', onSessionLifecycle);
|
|
150
|
+
engineEvents.off('session:completed', onSessionLifecycle);
|
|
151
|
+
engineEvents.off('session:cancelled', onSessionLifecycle);
|
|
152
|
+
engineEvents.off('session:expired', onSessionLifecycle);
|
|
153
|
+
engineEvents.off('session:evicted', onSessionLifecycle);
|
|
154
|
+
engineEvents.off('session:deleted', onSessionLifecycle);
|
|
136
155
|
};
|
|
137
156
|
}
|
|
138
157
|
function installCloseCleanup(server, cleanup) {
|
|
@@ -27,7 +27,7 @@ function buildTraceResource(session) {
|
|
|
27
27
|
}
|
|
28
28
|
: session;
|
|
29
29
|
return {
|
|
30
|
-
uri: `
|
|
30
|
+
uri: `reasoning://sessions/${session.id}/trace.md`,
|
|
31
31
|
mimeType: 'text/markdown',
|
|
32
32
|
text: formatThoughtsToMarkdown(sessionView),
|
|
33
33
|
};
|
|
@@ -86,7 +86,7 @@ function isReasoningTaskExtra(value) {
|
|
|
86
86
|
}
|
|
87
87
|
return true;
|
|
88
88
|
}
|
|
89
|
-
function
|
|
89
|
+
function assertReasoningTaskExtra(rawExtra) {
|
|
90
90
|
if (!isReasoningTaskExtra(rawExtra)) {
|
|
91
91
|
throw new Error('Invalid task context in request handler.');
|
|
92
92
|
}
|
|
@@ -241,6 +241,7 @@ function buildStructuredResult(session, generatedThoughts, targetThoughts) {
|
|
|
241
241
|
ok: true,
|
|
242
242
|
result: {
|
|
243
243
|
sessionId: session.id,
|
|
244
|
+
...(session.query !== undefined ? { query: session.query } : {}),
|
|
244
245
|
level: session.level,
|
|
245
246
|
status: session.status,
|
|
246
247
|
thoughts: [...session.thoughts],
|
|
@@ -260,7 +261,7 @@ function buildStructuredResult(session, generatedThoughts, targetThoughts) {
|
|
|
260
261
|
}
|
|
261
262
|
function buildSummary(session, remainingThoughts) {
|
|
262
263
|
if (session.status === 'completed') {
|
|
263
|
-
return `Reasoning complete — ${String(session.thoughts.length)} thoughts at [${session.level}] level. Session ${session.id}.`;
|
|
264
|
+
return `Reasoning complete — ${String(session.thoughts.length)} thought${session.thoughts.length === 1 ? '' : 's'} at [${session.level}] level. Session ${session.id}.`;
|
|
264
265
|
}
|
|
265
266
|
if (session.status === 'cancelled') {
|
|
266
267
|
return `Reasoning cancelled at thought ${String(session.thoughts.length)}/${String(session.totalThoughts)}. Session ${session.id}.`;
|
|
@@ -354,7 +355,7 @@ function createProgressHandler(args) {
|
|
|
354
355
|
const message = formatProgressMessage({
|
|
355
356
|
toolName: `꩜ ${TOOL_NAME}`,
|
|
356
357
|
context: 'Thought',
|
|
357
|
-
|
|
358
|
+
...(summary ? { metadata: summary } : {}),
|
|
358
359
|
...(isTerminal ? { outcome: 'complete' } : {}),
|
|
359
360
|
});
|
|
360
361
|
await notifyProgress({
|
|
@@ -569,7 +570,7 @@ async function runReasoningTask(args) {
|
|
|
569
570
|
}
|
|
570
571
|
function getTaskId(extra) {
|
|
571
572
|
if (typeof extra.taskId !== 'string' || extra.taskId.length === 0) {
|
|
572
|
-
throw new
|
|
573
|
+
throw new InvalidRunModeArgsError('Task ID missing in request context.');
|
|
573
574
|
}
|
|
574
575
|
return extra.taskId;
|
|
575
576
|
}
|
|
@@ -612,7 +613,7 @@ Protocol validation: malformed task metadata/arguments fail at request level bef
|
|
|
612
613
|
throw new Error(`Invalid reasoning_think params: ${parseResult.error.message}`);
|
|
613
614
|
}
|
|
614
615
|
const params = parseResult.data;
|
|
615
|
-
const extra =
|
|
616
|
+
const extra = assertReasoningTaskExtra(rawExtra);
|
|
616
617
|
const progressToken = extra._meta?.progressToken;
|
|
617
618
|
if (!reasoningTaskLimiter.tryAcquire()) {
|
|
618
619
|
throw new ServerBusyError();
|
|
@@ -649,11 +650,11 @@ Protocol validation: malformed task metadata/arguments fail at request level bef
|
|
|
649
650
|
return { task };
|
|
650
651
|
},
|
|
651
652
|
getTask(_params, rawExtra) {
|
|
652
|
-
const extra =
|
|
653
|
+
const extra = assertReasoningTaskExtra(rawExtra);
|
|
653
654
|
return extra.taskStore.getTask(getTaskId(extra));
|
|
654
655
|
},
|
|
655
656
|
async getTaskResult(_params, rawExtra) {
|
|
656
|
-
const extra =
|
|
657
|
+
const extra = assertReasoningTaskExtra(rawExtra);
|
|
657
658
|
const result = await extra.taskStore.getTaskResult(getTaskId(extra));
|
|
658
659
|
assertCallToolResult(result);
|
|
659
660
|
return result;
|