@j0hanz/cortex-mcp 1.4.0 → 1.6.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/config.d.ts +4 -4
- package/dist/engine/config.js +6 -4
- package/dist/engine/context.d.ts +0 -1
- package/dist/engine/context.js +0 -3
- package/dist/engine/events.d.ts +1 -1
- package/dist/engine/events.js +2 -3
- package/dist/engine/heuristics.d.ts +4 -0
- package/dist/engine/heuristics.js +65 -0
- package/dist/engine/reasoner.d.ts +1 -1
- package/dist/engine/reasoner.js +22 -98
- package/dist/engine/session-store.d.ts +7 -1
- package/dist/engine/session-store.js +68 -27
- package/dist/lib/concurrency.d.ts +5 -0
- package/dist/lib/concurrency.js +17 -0
- package/dist/lib/errors.d.ts +23 -7
- package/dist/lib/errors.js +47 -5
- package/dist/lib/formatting.d.ts +13 -0
- package/dist/lib/formatting.js +18 -0
- package/dist/lib/prompt-contracts.d.ts +1 -9
- package/dist/lib/prompt-contracts.js +33 -122
- package/dist/lib/tool-contracts.d.ts +1 -1
- package/dist/lib/tool-contracts.js +85 -90
- package/dist/lib/tool-response.d.ts +4 -0
- package/dist/lib/tool-response.js +3 -0
- package/dist/lib/types.d.ts +17 -0
- package/dist/lib/types.js +8 -1
- package/dist/lib/validators.d.ts +12 -1
- package/dist/lib/validators.js +48 -7
- package/dist/prompts/index.js +136 -47
- package/dist/prompts/templates.d.ts +2 -0
- package/dist/prompts/templates.js +227 -0
- package/dist/resources/index.js +34 -60
- package/dist/resources/instructions.js +55 -64
- package/dist/resources/tool-catalog.js +10 -9
- package/dist/resources/tool-info.js +1 -1
- package/dist/resources/workflows.js +43 -42
- package/dist/schemas/inputs.d.ts +0 -1
- package/dist/schemas/inputs.js +14 -31
- package/dist/schemas/outputs.d.ts +29 -25
- package/dist/schemas/outputs.js +18 -22
- package/dist/server.js +11 -3
- package/dist/tools/reasoning-think.js +142 -158
- package/package.json +1 -1
|
@@ -1,51 +1,38 @@
|
|
|
1
1
|
import { getLevelDescriptionString } from '../engine/config.js';
|
|
2
2
|
import { reason, sessionStore } from '../engine/reasoner.js';
|
|
3
3
|
import { ReasoningThinkInputSchema, } from '../schemas/inputs.js';
|
|
4
|
-
import { ReasoningThinkToolOutputSchema } from '../schemas/outputs.js';
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
4
|
+
import { ReasoningThinkToolOutputSchema, } from '../schemas/outputs.js';
|
|
5
|
+
import { createTaskLimiter } from '../lib/concurrency.js';
|
|
6
|
+
import { createErrorResponse, getErrorMessage, InsufficientThoughtsError, InvalidRunModeArgsError, isObjectRecord, ReasoningAbortedError, ReasoningError, ServerBusyError, SessionNotFoundError, } from '../lib/errors.js';
|
|
7
|
+
import { formatProgressMessage, formatThoughtsToMarkdown, } from '../lib/formatting.js';
|
|
8
|
+
import { createToolResponse, withIconMeta } from '../lib/tool-response.js';
|
|
9
|
+
import { parseBooleanEnv, parsePositiveIntEnv } from '../lib/validators.js';
|
|
8
10
|
const DEFAULT_MAX_ACTIVE_REASONING_TASKS = 32;
|
|
9
|
-
|
|
10
|
-
|
|
11
|
+
const REDACTED_THOUGHT_CONTENT = '[REDACTED]';
|
|
12
|
+
function shouldRedactTraceContent() {
|
|
13
|
+
return parseBooleanEnv('CORTEX_REDACT_TRACE_CONTENT', false);
|
|
14
|
+
}
|
|
11
15
|
function buildTraceResource(session) {
|
|
16
|
+
const sessionView = shouldRedactTraceContent()
|
|
17
|
+
? {
|
|
18
|
+
...session,
|
|
19
|
+
thoughts: session.thoughts.map((thought) => ({
|
|
20
|
+
index: thought.index,
|
|
21
|
+
content: REDACTED_THOUGHT_CONTENT,
|
|
22
|
+
revision: thought.revision,
|
|
23
|
+
...(thought.stepSummary !== undefined
|
|
24
|
+
? { stepSummary: REDACTED_THOUGHT_CONTENT }
|
|
25
|
+
: {}),
|
|
26
|
+
})),
|
|
27
|
+
}
|
|
28
|
+
: session;
|
|
12
29
|
return {
|
|
13
30
|
uri: `file:///cortex/sessions/${session.id}/trace.md`,
|
|
14
31
|
mimeType: 'text/markdown',
|
|
15
|
-
text: formatThoughtsToMarkdown(
|
|
32
|
+
text: formatThoughtsToMarkdown(sessionView),
|
|
16
33
|
};
|
|
17
34
|
}
|
|
18
|
-
|
|
19
|
-
if (rawValue === undefined) {
|
|
20
|
-
return fallbackValue;
|
|
21
|
-
}
|
|
22
|
-
const parsed = Number.parseInt(rawValue, 10);
|
|
23
|
-
if (!Number.isInteger(parsed) || parsed < 1) {
|
|
24
|
-
return fallbackValue;
|
|
25
|
-
}
|
|
26
|
-
return parsed;
|
|
27
|
-
}
|
|
28
|
-
function createTaskLimiter(maxActiveTasks) {
|
|
29
|
-
let activeTasks = 0;
|
|
30
|
-
return {
|
|
31
|
-
tryAcquire() {
|
|
32
|
-
if (activeTasks >= maxActiveTasks) {
|
|
33
|
-
return false;
|
|
34
|
-
}
|
|
35
|
-
activeTasks += 1;
|
|
36
|
-
return true;
|
|
37
|
-
},
|
|
38
|
-
release() {
|
|
39
|
-
if (activeTasks > 0) {
|
|
40
|
-
activeTasks -= 1;
|
|
41
|
-
}
|
|
42
|
-
},
|
|
43
|
-
};
|
|
44
|
-
}
|
|
45
|
-
const reasoningTaskLimiter = createTaskLimiter(parsePositiveInt(process.env.CORTEX_MAX_ACTIVE_REASONING_TASKS, DEFAULT_MAX_ACTIVE_REASONING_TASKS));
|
|
46
|
-
function isObjectRecord(value) {
|
|
47
|
-
return typeof value === 'object' && value !== null;
|
|
48
|
-
}
|
|
35
|
+
const reasoningTaskLimiter = createTaskLimiter(parsePositiveIntEnv('CORTEX_MAX_ACTIVE_REASONING_TASKS', DEFAULT_MAX_ACTIVE_REASONING_TASKS));
|
|
49
36
|
function isTaskStoreLike(value) {
|
|
50
37
|
if (!isObjectRecord(value)) {
|
|
51
38
|
return false;
|
|
@@ -134,27 +121,11 @@ function assertCallToolResult(value) {
|
|
|
134
121
|
throw new Error('Stored task result is not a valid CallToolResult.');
|
|
135
122
|
}
|
|
136
123
|
}
|
|
137
|
-
function
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
case message === 'Reasoning task cancelled':
|
|
141
|
-
return 'E_ABORTED';
|
|
142
|
-
case message.startsWith('targetThoughts must be'):
|
|
143
|
-
case message.startsWith('Cannot change targetThoughts'):
|
|
144
|
-
return 'E_INVALID_THOUGHT_COUNT';
|
|
145
|
-
case message.startsWith('Session not found:'):
|
|
146
|
-
return 'E_SESSION_NOT_FOUND';
|
|
147
|
-
case message.startsWith('Session level mismatch:'):
|
|
148
|
-
return 'E_SESSION_LEVEL_MISMATCH';
|
|
149
|
-
case message.startsWith('run_to_completion requires at least'):
|
|
150
|
-
return 'E_INSUFFICIENT_THOUGHTS';
|
|
151
|
-
case message.startsWith('targetThoughts is required for run_to_completion when sessionId is not provided'):
|
|
152
|
-
return 'E_INVALID_RUN_MODE_ARGS';
|
|
153
|
-
case message === TASK_OVERLOAD_MESSAGE:
|
|
154
|
-
return 'E_SERVER_BUSY';
|
|
155
|
-
default:
|
|
156
|
-
return 'E_REASONING';
|
|
124
|
+
function getReasoningErrorCode(error) {
|
|
125
|
+
if (error instanceof ReasoningError) {
|
|
126
|
+
return error.code;
|
|
157
127
|
}
|
|
128
|
+
return 'E_REASONING';
|
|
158
129
|
}
|
|
159
130
|
function shouldEmitProgress(progress, total, level) {
|
|
160
131
|
if (progress <= 1 || progress >= total) {
|
|
@@ -167,8 +138,22 @@ function shouldEmitProgress(progress, total, level) {
|
|
|
167
138
|
// Basic/Normal: emit every step
|
|
168
139
|
return true;
|
|
169
140
|
}
|
|
170
|
-
function
|
|
171
|
-
|
|
141
|
+
async function notifyProgress(args) {
|
|
142
|
+
const { server, progressToken, progress, total, message } = args;
|
|
143
|
+
try {
|
|
144
|
+
await server.server.notification({
|
|
145
|
+
method: 'notifications/progress',
|
|
146
|
+
params: {
|
|
147
|
+
progressToken,
|
|
148
|
+
progress,
|
|
149
|
+
total,
|
|
150
|
+
message,
|
|
151
|
+
},
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
catch {
|
|
155
|
+
// Ignore notification errors
|
|
156
|
+
}
|
|
172
157
|
}
|
|
173
158
|
function buildThoughtInputs(params) {
|
|
174
159
|
const primary = Array.isArray(params.thought)
|
|
@@ -176,7 +161,7 @@ function buildThoughtInputs(params) {
|
|
|
176
161
|
: params.thought
|
|
177
162
|
? [params.thought]
|
|
178
163
|
: [];
|
|
179
|
-
return
|
|
164
|
+
return primary;
|
|
180
165
|
}
|
|
181
166
|
function getStartingThoughtCount(sessionId) {
|
|
182
167
|
if (sessionId === undefined) {
|
|
@@ -203,6 +188,20 @@ async function executeReasoningSteps(args) {
|
|
|
203
188
|
rollbackToStep !== undefined)) {
|
|
204
189
|
maxSteps = 1;
|
|
205
190
|
}
|
|
191
|
+
// Build first-step-only extras once, outside the loop.
|
|
192
|
+
const firstStepExtras = {
|
|
193
|
+
...(observation !== undefined ? { observation } : {}),
|
|
194
|
+
...(hypothesis !== undefined ? { hypothesis } : {}),
|
|
195
|
+
...(evaluation !== undefined ? { evaluation } : {}),
|
|
196
|
+
...(stepSummary !== undefined ? { stepSummary } : {}),
|
|
197
|
+
...(isConclusion !== undefined ? { isConclusion } : {}),
|
|
198
|
+
...(rollbackToStep !== undefined ? { rollbackToStep } : {}),
|
|
199
|
+
};
|
|
200
|
+
const baseOptions = {
|
|
201
|
+
...(targetThoughts !== undefined ? { targetThoughts } : {}),
|
|
202
|
+
abortSignal: controller.signal,
|
|
203
|
+
onProgress,
|
|
204
|
+
};
|
|
206
205
|
for (let index = 0; index < maxSteps; index++) {
|
|
207
206
|
await ensureTaskIsActive(taskStore, taskId, controller);
|
|
208
207
|
const inputThought = thoughtInputs[index];
|
|
@@ -217,30 +216,11 @@ async function executeReasoningSteps(args) {
|
|
|
217
216
|
break;
|
|
218
217
|
}
|
|
219
218
|
const reasonOptions = {
|
|
219
|
+
...baseOptions,
|
|
220
220
|
...(inputThought !== undefined ? { thought: inputThought } : {}),
|
|
221
|
-
|
|
222
|
-
|
|
221
|
+
...(activeSessionId !== undefined ? { sessionId: activeSessionId } : {}),
|
|
222
|
+
...(index === 0 ? firstStepExtras : {}),
|
|
223
223
|
};
|
|
224
|
-
if (index === 0) {
|
|
225
|
-
if (observation !== undefined)
|
|
226
|
-
reasonOptions.observation = observation;
|
|
227
|
-
if (hypothesis !== undefined)
|
|
228
|
-
reasonOptions.hypothesis = hypothesis;
|
|
229
|
-
if (evaluation !== undefined)
|
|
230
|
-
reasonOptions.evaluation = evaluation;
|
|
231
|
-
if (stepSummary !== undefined)
|
|
232
|
-
reasonOptions.stepSummary = stepSummary;
|
|
233
|
-
if (isConclusion !== undefined)
|
|
234
|
-
reasonOptions.isConclusion = isConclusion;
|
|
235
|
-
if (rollbackToStep !== undefined)
|
|
236
|
-
reasonOptions.rollbackToStep = rollbackToStep;
|
|
237
|
-
}
|
|
238
|
-
if (activeSessionId !== undefined) {
|
|
239
|
-
reasonOptions.sessionId = activeSessionId;
|
|
240
|
-
}
|
|
241
|
-
if (targetThoughts !== undefined) {
|
|
242
|
-
reasonOptions.targetThoughts = targetThoughts;
|
|
243
|
-
}
|
|
244
224
|
session = await reason(queryText, level, reasonOptions);
|
|
245
225
|
activeSessionId = session.id;
|
|
246
226
|
if (shouldStopReasoningLoop(session, runMode)) {
|
|
@@ -263,7 +243,7 @@ function buildStructuredResult(session, generatedThoughts, targetThoughts) {
|
|
|
263
243
|
sessionId: session.id,
|
|
264
244
|
level: session.level,
|
|
265
245
|
status: session.status,
|
|
266
|
-
thoughts: session.thoughts,
|
|
246
|
+
thoughts: [...session.thoughts],
|
|
267
247
|
generatedThoughts,
|
|
268
248
|
requestedThoughts,
|
|
269
249
|
totalThoughts: session.totalThoughts,
|
|
@@ -280,10 +260,10 @@ function buildStructuredResult(session, generatedThoughts, targetThoughts) {
|
|
|
280
260
|
}
|
|
281
261
|
function buildSummary(session, remainingThoughts) {
|
|
282
262
|
if (session.status === 'completed') {
|
|
283
|
-
return `Reasoning complete
|
|
263
|
+
return `Reasoning complete — ${String(session.thoughts.length)} thoughts at [${session.level}] level. Session ${session.id}.`;
|
|
284
264
|
}
|
|
285
265
|
if (session.status === 'cancelled') {
|
|
286
|
-
return `
|
|
266
|
+
return `Reasoning cancelled at thought ${String(session.thoughts.length)}/${String(session.totalThoughts)}. Session ${session.id}.`;
|
|
287
267
|
}
|
|
288
268
|
const recentSummaries = session.thoughts
|
|
289
269
|
.filter((t) => t.stepSummary)
|
|
@@ -301,7 +281,7 @@ function buildSummary(session, remainingThoughts) {
|
|
|
301
281
|
else if (progress < 0.7) {
|
|
302
282
|
prompt = 'Formulate and critique hypotheses based on the facts.';
|
|
303
283
|
}
|
|
304
|
-
return (`CONTINUE: ${prompt} Call reasoning_think with { sessionId: "${session.id}",
|
|
284
|
+
return (`CONTINUE: ${prompt} Call reasoning_think with { sessionId: "${session.id}", thought: "<your next reasoning step>" }. ` +
|
|
305
285
|
`${summaryText}Progress: ${String(session.thoughts.length)}/${String(session.totalThoughts)} thoughts, ${String(remainingThoughts)} remaining.`);
|
|
306
286
|
}
|
|
307
287
|
async function emitLog(server, level, data, sessionId) {
|
|
@@ -320,16 +300,22 @@ function createCancellationController(signal) {
|
|
|
320
300
|
const controller = new AbortController();
|
|
321
301
|
if (signal.aborted) {
|
|
322
302
|
controller.abort();
|
|
323
|
-
return
|
|
303
|
+
return {
|
|
304
|
+
controller,
|
|
305
|
+
cleanup: () => {
|
|
306
|
+
// No listener to clean up when already aborted.
|
|
307
|
+
},
|
|
308
|
+
};
|
|
324
309
|
}
|
|
325
310
|
const onAbort = () => {
|
|
326
311
|
controller.abort();
|
|
327
312
|
};
|
|
328
|
-
|
|
329
|
-
controller.signal.addEventListener('abort', () => {
|
|
313
|
+
const cleanup = () => {
|
|
330
314
|
signal.removeEventListener('abort', onAbort);
|
|
331
|
-
}
|
|
332
|
-
|
|
315
|
+
};
|
|
316
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
317
|
+
controller.signal.addEventListener('abort', cleanup, { once: true });
|
|
318
|
+
return { controller, cleanup };
|
|
333
319
|
}
|
|
334
320
|
async function isTaskCancelled(taskStore, taskId) {
|
|
335
321
|
try {
|
|
@@ -343,12 +329,12 @@ async function isTaskCancelled(taskStore, taskId) {
|
|
|
343
329
|
async function ensureTaskIsActive(taskStore, taskId, controller) {
|
|
344
330
|
if (await isTaskCancelled(taskStore, taskId)) {
|
|
345
331
|
controller.abort();
|
|
346
|
-
throw new
|
|
332
|
+
throw new ReasoningAbortedError('Reasoning task cancelled');
|
|
347
333
|
}
|
|
348
334
|
}
|
|
349
335
|
function createProgressHandler(args) {
|
|
350
336
|
const { server, taskStore, taskId, level, progressToken, controller, startingCount, batchTotal, } = args;
|
|
351
|
-
return async (progress,
|
|
337
|
+
return async (progress, _total, summary) => {
|
|
352
338
|
await ensureTaskIsActive(taskStore, taskId, controller);
|
|
353
339
|
if (progressToken === undefined) {
|
|
354
340
|
return;
|
|
@@ -359,26 +345,25 @@ function createProgressHandler(args) {
|
|
|
359
345
|
const isTerminal = displayProgress >= batchTotal;
|
|
360
346
|
// We must emit if it's the terminal update for this batch,
|
|
361
347
|
// otherwise we respect the session-level skipping rules.
|
|
362
|
-
|
|
348
|
+
// If a summary is provided, we force an emit to show the meaningful update.
|
|
349
|
+
if (!isTerminal &&
|
|
350
|
+
!summary &&
|
|
351
|
+
!shouldEmitProgress(displayProgress, batchTotal, level)) {
|
|
363
352
|
return;
|
|
364
353
|
}
|
|
365
|
-
const message =
|
|
366
|
-
|
|
367
|
-
:
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
}
|
|
379
|
-
catch {
|
|
380
|
-
// Ignore notification errors
|
|
381
|
-
}
|
|
354
|
+
const message = formatProgressMessage({
|
|
355
|
+
toolName: `꩜ ${TOOL_NAME}`,
|
|
356
|
+
context: 'Thought',
|
|
357
|
+
metadata: `[${String(displayProgress)}/${String(batchTotal)}]${summary ? ` ${summary}` : ''}`,
|
|
358
|
+
...(isTerminal ? { outcome: 'complete' } : {}),
|
|
359
|
+
});
|
|
360
|
+
await notifyProgress({
|
|
361
|
+
server,
|
|
362
|
+
progressToken,
|
|
363
|
+
progress: displayProgress,
|
|
364
|
+
total: batchTotal,
|
|
365
|
+
message,
|
|
366
|
+
});
|
|
382
367
|
};
|
|
383
368
|
}
|
|
384
369
|
async function storeTaskFailure(taskStore, taskId, response) {
|
|
@@ -410,31 +395,29 @@ async function notifyTaskStatus(server, taskId, status) {
|
|
|
410
395
|
}
|
|
411
396
|
function assertRunToCompletionInputCount(params, thoughtInputs) {
|
|
412
397
|
const { sessionId, targetThoughts } = params;
|
|
413
|
-
if (sessionId
|
|
414
|
-
throw new
|
|
398
|
+
if (!sessionId && !targetThoughts) {
|
|
399
|
+
throw new InvalidRunModeArgsError('targetThoughts is required for run_to_completion when sessionId is not provided');
|
|
415
400
|
}
|
|
416
401
|
let requiredInputs = targetThoughts ?? 0;
|
|
417
|
-
if (sessionId
|
|
402
|
+
if (sessionId) {
|
|
418
403
|
const existing = sessionStore.get(sessionId);
|
|
419
404
|
if (!existing) {
|
|
420
|
-
throw new
|
|
405
|
+
throw new SessionNotFoundError(sessionId);
|
|
421
406
|
}
|
|
422
407
|
requiredInputs = Math.max(0, existing.totalThoughts - existing.thoughts.length);
|
|
423
408
|
}
|
|
424
409
|
if (thoughtInputs.length < requiredInputs) {
|
|
425
|
-
throw new
|
|
410
|
+
throw new InsufficientThoughtsError(`run_to_completion requires at least ${String(requiredInputs)} thought inputs; received ${String(thoughtInputs.length)}`);
|
|
426
411
|
}
|
|
427
412
|
}
|
|
428
413
|
function getActionableMessage(errorCode, originalMessage) {
|
|
429
414
|
switch (errorCode) {
|
|
430
|
-
case 'E_SESSION_LEVEL_MISMATCH':
|
|
431
|
-
return `${originalMessage} ACTION REQUIRED: Re-call reasoning_think with the correct level matching the session.`;
|
|
432
415
|
case 'E_INVALID_THOUGHT_COUNT':
|
|
433
|
-
return `${originalMessage}
|
|
416
|
+
return `${originalMessage} Fix: set targetThoughts within the level range (basic 3–5, normal 6–10, high 15–25).`;
|
|
434
417
|
case 'E_INSUFFICIENT_THOUGHTS':
|
|
435
|
-
return `${originalMessage}
|
|
418
|
+
return `${originalMessage} Fix: provide enough thought inputs for the remaining steps, or use runMode: "step".`;
|
|
436
419
|
case 'E_INVALID_RUN_MODE_ARGS':
|
|
437
|
-
return `${originalMessage}
|
|
420
|
+
return `${originalMessage} Fix: set targetThoughts when starting a new session with runMode: "run_to_completion".`;
|
|
438
421
|
default:
|
|
439
422
|
return originalMessage;
|
|
440
423
|
}
|
|
@@ -442,10 +425,13 @@ function getActionableMessage(errorCode, originalMessage) {
|
|
|
442
425
|
async function handleTaskFailure(args) {
|
|
443
426
|
const { server, taskStore, taskId, sessionId, error } = args;
|
|
444
427
|
const originalMessage = getErrorMessage(error);
|
|
445
|
-
const errorCode =
|
|
428
|
+
const errorCode = getReasoningErrorCode(error);
|
|
446
429
|
const message = getActionableMessage(errorCode, originalMessage);
|
|
447
430
|
const response = createErrorResponse(errorCode, message);
|
|
448
431
|
if (await isTaskCancelled(taskStore, taskId)) {
|
|
432
|
+
if (sessionId) {
|
|
433
|
+
sessionStore.markCancelled(sessionId);
|
|
434
|
+
}
|
|
449
435
|
await emitLog(server, 'notice', { event: 'task_cancelled', taskId, reason: message }, sessionId);
|
|
450
436
|
return;
|
|
451
437
|
}
|
|
@@ -466,9 +452,10 @@ async function handleTaskFailure(args) {
|
|
|
466
452
|
async function runReasoningTask(args) {
|
|
467
453
|
const { server, taskStore, taskId, params, progressToken, controller, sessionId, } = args;
|
|
468
454
|
const { query, level, targetThoughts } = params;
|
|
469
|
-
const runMode =
|
|
455
|
+
const runMode = params.runMode ?? 'step';
|
|
470
456
|
const thoughtInputs = buildThoughtInputs(params);
|
|
471
457
|
const queryText = query ?? '';
|
|
458
|
+
let resolvedSessionId = params.sessionId ?? sessionId;
|
|
472
459
|
await emitLog(server, 'info', {
|
|
473
460
|
event: 'task_started',
|
|
474
461
|
taskId,
|
|
@@ -477,7 +464,7 @@ async function runReasoningTask(args) {
|
|
|
477
464
|
hasSessionId: params.sessionId !== undefined,
|
|
478
465
|
targetThoughts: targetThoughts ?? null,
|
|
479
466
|
thoughtInputs: thoughtInputs.length,
|
|
480
|
-
},
|
|
467
|
+
}, resolvedSessionId);
|
|
481
468
|
try {
|
|
482
469
|
if (runMode === 'run_to_completion') {
|
|
483
470
|
assertRunToCompletionInputCount(params, thoughtInputs);
|
|
@@ -492,23 +479,20 @@ async function runReasoningTask(args) {
|
|
|
492
479
|
params.rollback_to_step !== undefined)) {
|
|
493
480
|
batchTotal = 1;
|
|
494
481
|
}
|
|
482
|
+
const normalizedBatchTotal = Math.max(1, batchTotal);
|
|
495
483
|
if (progressToken !== undefined) {
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
}
|
|
509
|
-
catch {
|
|
510
|
-
// Ignore notification errors
|
|
511
|
-
}
|
|
484
|
+
const message = formatProgressMessage({
|
|
485
|
+
toolName: `꩜ ${TOOL_NAME}`,
|
|
486
|
+
context: level ? 'starting' : 'continuing',
|
|
487
|
+
metadata: level ? `[${level}]` : 'session',
|
|
488
|
+
});
|
|
489
|
+
await notifyProgress({
|
|
490
|
+
server,
|
|
491
|
+
progressToken,
|
|
492
|
+
progress: 0,
|
|
493
|
+
total: normalizedBatchTotal,
|
|
494
|
+
message,
|
|
495
|
+
});
|
|
512
496
|
}
|
|
513
497
|
const progressArgs = {
|
|
514
498
|
server,
|
|
@@ -517,7 +501,7 @@ async function runReasoningTask(args) {
|
|
|
517
501
|
level,
|
|
518
502
|
controller,
|
|
519
503
|
startingCount,
|
|
520
|
-
batchTotal:
|
|
504
|
+
batchTotal: normalizedBatchTotal,
|
|
521
505
|
};
|
|
522
506
|
if (progressToken !== undefined) {
|
|
523
507
|
progressArgs.progressToken = progressToken;
|
|
@@ -552,8 +536,10 @@ async function runReasoningTask(args) {
|
|
|
552
536
|
if (params.rollback_to_step !== undefined)
|
|
553
537
|
executeArgs.rollbackToStep = params.rollback_to_step;
|
|
554
538
|
const session = await executeReasoningSteps(executeArgs);
|
|
539
|
+
resolvedSessionId = session.id;
|
|
555
540
|
if (await isTaskCancelled(taskStore, taskId)) {
|
|
556
|
-
|
|
541
|
+
sessionStore.markCancelled(resolvedSessionId);
|
|
542
|
+
await emitLog(server, 'notice', { event: 'task_cancelled_before_result', taskId }, resolvedSessionId);
|
|
557
543
|
return;
|
|
558
544
|
}
|
|
559
545
|
const generatedThoughts = Math.max(0, session.thoughts.length - startingCount);
|
|
@@ -575,8 +561,8 @@ async function runReasoningTask(args) {
|
|
|
575
561
|
taskId,
|
|
576
562
|
error,
|
|
577
563
|
};
|
|
578
|
-
if (
|
|
579
|
-
failureArgs.sessionId =
|
|
564
|
+
if (resolvedSessionId !== undefined) {
|
|
565
|
+
failureArgs.sessionId = resolvedSessionId;
|
|
580
566
|
}
|
|
581
567
|
await handleTaskFailure(failureArgs);
|
|
582
568
|
}
|
|
@@ -588,9 +574,6 @@ function getTaskId(extra) {
|
|
|
588
574
|
return extra.taskId;
|
|
589
575
|
}
|
|
590
576
|
const TOOL_NAME = 'reasoning_think';
|
|
591
|
-
function withIconMeta(iconMeta) {
|
|
592
|
-
return iconMeta ? { icons: [iconMeta] } : undefined;
|
|
593
|
-
}
|
|
594
577
|
export function registerReasoningThinkTool(server, iconMeta) {
|
|
595
578
|
server.experimental.tasks.registerToolTask(TOOL_NAME, {
|
|
596
579
|
title: 'Reasoning Think',
|
|
@@ -598,17 +581,17 @@ export function registerReasoningThinkTool(server, iconMeta) {
|
|
|
598
581
|
|
|
599
582
|
USAGE PATTERN:
|
|
600
583
|
1. Start: { query: "...", level: "basic"|"normal"|"high", thought: "your analysis..." }
|
|
601
|
-
2. Continue: { sessionId: "<from response>",
|
|
602
|
-
3. Repeat
|
|
584
|
+
2. Continue: { sessionId: "<from response>", thought: "next step..." } — level is optional; session level is used
|
|
585
|
+
3. Repeat until status: "completed" — the summary field contains the exact next call to make
|
|
603
586
|
|
|
604
|
-
IMPORTANT:
|
|
587
|
+
IMPORTANT: Pass the returned sessionId on every continuation call.
|
|
605
588
|
The thought parameter stores YOUR reasoning verbatim — write thorough analysis in each step.
|
|
606
|
-
Use step_summary
|
|
589
|
+
Use step_summary for a 1-sentence conclusion per step — these accumulate in the summary field for navigation.
|
|
607
590
|
|
|
608
591
|
Levels: ${getLevelDescriptionString()}.
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
592
|
+
Alternatives: runMode="run_to_completion" (batch), or observation/hypothesis/evaluation fields (structured).
|
|
593
|
+
Errors: E_SESSION_NOT_FOUND (expired — start new), E_INVALID_THOUGHT_COUNT (check level ranges).
|
|
594
|
+
Protocol validation: malformed task metadata/arguments fail at request level before task start; runtime reasoning failures return tool isError=true payloads.`,
|
|
612
595
|
inputSchema: ReasoningThinkInputSchema,
|
|
613
596
|
outputSchema: ReasoningThinkToolOutputSchema,
|
|
614
597
|
annotations: {
|
|
@@ -632,7 +615,7 @@ Error recovery: If E_SESSION_NOT_FOUND, the session expired — start a new sess
|
|
|
632
615
|
const extra = parseReasoningTaskExtra(rawExtra);
|
|
633
616
|
const progressToken = extra._meta?.progressToken;
|
|
634
617
|
if (!reasoningTaskLimiter.tryAcquire()) {
|
|
635
|
-
throw new
|
|
618
|
+
throw new ServerBusyError();
|
|
636
619
|
}
|
|
637
620
|
let task;
|
|
638
621
|
try {
|
|
@@ -645,13 +628,13 @@ Error recovery: If E_SESSION_NOT_FOUND, the session expired — start a new sess
|
|
|
645
628
|
reasoningTaskLimiter.release();
|
|
646
629
|
throw error;
|
|
647
630
|
}
|
|
648
|
-
const
|
|
631
|
+
const cancellation = createCancellationController(extra.signal);
|
|
649
632
|
const runReasoningArgs = {
|
|
650
633
|
server,
|
|
651
634
|
taskStore: extra.taskStore,
|
|
652
635
|
taskId: task.taskId,
|
|
653
636
|
params,
|
|
654
|
-
controller,
|
|
637
|
+
controller: cancellation.controller,
|
|
655
638
|
};
|
|
656
639
|
if (progressToken !== undefined) {
|
|
657
640
|
runReasoningArgs.progressToken = progressToken;
|
|
@@ -660,6 +643,7 @@ Error recovery: If E_SESSION_NOT_FOUND, the session expired — start a new sess
|
|
|
660
643
|
runReasoningArgs.sessionId = extra.sessionId;
|
|
661
644
|
}
|
|
662
645
|
void runReasoningTask(runReasoningArgs).finally(() => {
|
|
646
|
+
cancellation.cleanup();
|
|
663
647
|
reasoningTaskLimiter.release();
|
|
664
648
|
});
|
|
665
649
|
return { task };
|