@j0hanz/cortex-mcp 1.5.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.d.ts +1 -1
- package/dist/engine/reasoner.js +4 -4
- package/dist/engine/session-store.d.ts +9 -7
- package/dist/engine/session-store.js +45 -22
- package/dist/lib/tool-contracts.js +1 -7
- package/dist/lib/types.d.ts +1 -0
- package/dist/lib/validators.d.ts +5 -0
- package/dist/lib/validators.js +18 -0
- package/dist/prompts/index.js +26 -22
- package/dist/prompts/templates.js +214 -93
- package/dist/resources/index.js +24 -4
- package/dist/resources/instructions.js +47 -64
- package/dist/resources/tool-catalog.js +10 -9
- package/dist/resources/tool-info.js +1 -1
- package/dist/resources/workflows.js +41 -41
- package/dist/schemas/inputs.d.ts +0 -1
- package/dist/schemas/inputs.js +12 -29
- package/dist/schemas/outputs.d.ts +3 -0
- package/dist/schemas/outputs.js +9 -9
- package/dist/server.js +29 -0
- package/dist/tools/reasoning-think.js +52 -24
- package/package.json +1 -1
|
@@ -7,55 +7,55 @@ function buildToolReference() {
|
|
|
7
7
|
.join('\n\n');
|
|
8
8
|
}
|
|
9
9
|
export function buildWorkflowGuide() {
|
|
10
|
-
return
|
|
10
|
+
return `<role>
|
|
11
|
+
You are an expert reasoning engine assistant. You decompose queries into structured thought chains at configurable depth levels (basic, normal, high).
|
|
12
|
+
</role>
|
|
11
13
|
|
|
14
|
+
<workflows>
|
|
12
15
|
### WORKFLOW A: Sequential Reasoning (Most Common)
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
1. Call \`reasoning_think\` as a task (send \`tools/call\` with \`task\` field) for long-running \`high\`-level reasoning.
|
|
36
|
-
2. Poll \`tasks/get\` until status is \`completed\` or \`failed\`.
|
|
37
|
-
3. Retrieve the result via \`tasks/result\`.
|
|
38
|
-
4. Use \`tasks/cancel\` to abort if needed.
|
|
16
|
+
1. Call \`reasoning_think\` with \`{ query: "...", level: "basic", thought: "..." }\`.
|
|
17
|
+
2. Read response: note \`sessionId\` and \`remainingThoughts\`.
|
|
18
|
+
3. **MUST continue**: Call again with \`{ sessionId: "<id>", thought: "..." }\`.
|
|
19
|
+
4. Repeat until \`status: "completed"\` or \`remainingThoughts: 0\`.
|
|
20
|
+
NOTE: \`summary\` field contains the exact next call.
|
|
21
|
+
|
|
22
|
+
### WORKFLOW B: Multi-Turn Reasoning
|
|
23
|
+
1. Call \`reasoning_think\` with \`{ query: "...", level: "normal", thought: "..." }\`.
|
|
24
|
+
2. Call \`reasoning_think\` with \`{ sessionId: "<id>", thought: "..." }\` (optional: add \`query\` for follow-up).
|
|
25
|
+
3. Repeat until completed. Read \`reasoning://sessions/{sessionId}\` for full chain.
|
|
26
|
+
NOTE: \`level\` is optional when continuing; session level is used if omitted.
|
|
27
|
+
|
|
28
|
+
### WORKFLOW C: Controlled Depth
|
|
29
|
+
1. Call \`reasoning_think\` with \`{ query: "...", level: "normal", targetThoughts: 8, thought: "..." }\`.
|
|
30
|
+
2. Repeat with \`sessionId\` and \`thought\` until \`totalThoughts\` reached.
|
|
31
|
+
NOTE: \`targetThoughts\` must fit level range (basic: 3-5, normal: 6-10, high: 15-25).
|
|
32
|
+
|
|
33
|
+
### WORKFLOW D: Async Task
|
|
34
|
+
1. Call \`reasoning_think\` as task (send \`task\` field) for long \`high\`-level reasoning.
|
|
35
|
+
2. Poll \`tasks/get\` until \`completed\`/\`failed\`.
|
|
36
|
+
3. Retrieve via \`tasks/result\`.
|
|
37
|
+
4. Abort via \`tasks/cancel\`.
|
|
39
38
|
|
|
40
39
|
### WORKFLOW E: Batched Run-To-Completion
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
## Shared Constraints
|
|
40
|
+
1. Start session with \`targetThoughts\` and \`runMode: "run_to_completion"\`.
|
|
41
|
+
2. Provide \`thought\` as string array (e.g., \`["step1", "step2"]\`).
|
|
42
|
+
3. Server consumes inputs until completion, token exhaustion, or cancellation.
|
|
43
|
+
|
|
44
|
+
### WORKFLOW F: Structured Reasoning
|
|
45
|
+
1. Call \`reasoning_think\` with \`{ query: "...", level: "normal", observation: "...", hypothesis: "...", evaluation: "..." }\`.
|
|
46
|
+
2. Server formats into structured thought in trace.
|
|
47
|
+
3. Continue with \`sessionId\` using \`thought\` or structured fields.
|
|
48
|
+
4. Use \`is_conclusion: true\` to end early, or \`rollback_to_step\` to discard/redo.
|
|
49
|
+
</workflows>
|
|
50
|
+
|
|
51
|
+
<constraints>
|
|
54
52
|
${getSharedConstraints()
|
|
55
53
|
.map((c) => `- ${c}`)
|
|
56
54
|
.join('\n')}
|
|
55
|
+
</constraints>
|
|
57
56
|
|
|
58
|
-
|
|
57
|
+
<tool_reference>
|
|
59
58
|
${buildToolReference()}
|
|
59
|
+
</tool_reference>
|
|
60
60
|
`;
|
|
61
61
|
}
|
package/dist/schemas/inputs.d.ts
CHANGED
|
@@ -13,7 +13,6 @@ export declare const ReasoningThinkInputSchema: z.ZodObject<{
|
|
|
13
13
|
run_to_completion: "run_to_completion";
|
|
14
14
|
}>>;
|
|
15
15
|
thought: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>>;
|
|
16
|
-
thoughts: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
17
16
|
is_conclusion: z.ZodOptional<z.ZodBoolean>;
|
|
18
17
|
rollback_to_step: z.ZodOptional<z.ZodNumber>;
|
|
19
18
|
step_summary: z.ZodOptional<z.ZodString>;
|
package/dist/schemas/inputs.js
CHANGED
|
@@ -17,63 +17,49 @@ function addCustomIssue(ctx, path, message) {
|
|
|
17
17
|
}
|
|
18
18
|
export const ReasoningThinkInputSchema = z
|
|
19
19
|
.strictObject({
|
|
20
|
-
query: QUERY_TEXT_SCHEMA.optional().describe('
|
|
21
|
-
level: LEVEL_SCHEMA.optional().describe(`Reasoning depth level (required for new sessions
|
|
20
|
+
query: QUERY_TEXT_SCHEMA.optional().describe('Question or problem to reason about.'),
|
|
21
|
+
level: LEVEL_SCHEMA.optional().describe(`Reasoning depth level (required for new sessions). ${getLevelDescriptionString()}.`),
|
|
22
22
|
targetThoughts: z
|
|
23
23
|
.number()
|
|
24
24
|
.int()
|
|
25
25
|
.min(1)
|
|
26
26
|
.max(25)
|
|
27
27
|
.optional()
|
|
28
|
-
.describe('
|
|
28
|
+
.describe('Explicit thought count. Must fit level range.'),
|
|
29
29
|
sessionId: z
|
|
30
30
|
.string()
|
|
31
31
|
.min(1)
|
|
32
32
|
.max(128)
|
|
33
33
|
.optional()
|
|
34
|
-
.describe('Session ID to continue.
|
|
34
|
+
.describe('Session ID to continue.'),
|
|
35
35
|
runMode: z
|
|
36
36
|
.enum(RUN_MODE_VALUES)
|
|
37
37
|
.optional()
|
|
38
|
-
.describe('Execution mode
|
|
38
|
+
.describe('Execution mode. "step" (default) or "run_to_completion".'),
|
|
39
39
|
thought: z
|
|
40
40
|
.union([THOUGHT_TEXT_SCHEMA, THOUGHT_BATCH_SCHEMA])
|
|
41
41
|
.optional()
|
|
42
|
-
.describe('
|
|
43
|
-
'The server stores this text verbatim as the thought in the session trace. ' +
|
|
44
|
-
'Write your complete analysis, observations, and conclusions here — this is what appears in trace.md. ' +
|
|
45
|
-
'Can be a single string or an array of strings (for batch execution).'),
|
|
46
|
-
thoughts: z
|
|
47
|
-
.array(THOUGHT_TEXT_SCHEMA)
|
|
48
|
-
.max(25)
|
|
49
|
-
.optional()
|
|
50
|
-
.describe('(Deprecated) Optional additional thought inputs. Use "thought" as an array instead.'),
|
|
42
|
+
.describe('Full reasoning content for this step. Stored verbatim. String or string array.'),
|
|
51
43
|
is_conclusion: z
|
|
52
44
|
.boolean()
|
|
53
45
|
.optional()
|
|
54
|
-
.describe('
|
|
46
|
+
.describe('End session early if final answer reached.'),
|
|
55
47
|
rollback_to_step: z
|
|
56
48
|
.number()
|
|
57
49
|
.int()
|
|
58
50
|
.min(0)
|
|
59
51
|
.optional()
|
|
60
|
-
.describe('
|
|
52
|
+
.describe('0-based thought index to rollback to. Discards subsequent thoughts.'),
|
|
61
53
|
step_summary: z
|
|
62
54
|
.string()
|
|
63
55
|
.optional()
|
|
64
|
-
.describe('
|
|
65
|
-
observation: z
|
|
66
|
-
.string()
|
|
67
|
-
.optional()
|
|
68
|
-
.describe('What facts are known at this step?'),
|
|
56
|
+
.describe('1-sentence summary of the conclusion reached.'),
|
|
57
|
+
observation: z.string().optional().describe('Facts known at this step.'),
|
|
69
58
|
hypothesis: z
|
|
70
59
|
.string()
|
|
71
60
|
.optional()
|
|
72
|
-
.describe('
|
|
73
|
-
evaluation: z
|
|
74
|
-
.string()
|
|
75
|
-
.optional()
|
|
76
|
-
.describe('Critique the hypothesis. Are there flaws?'),
|
|
61
|
+
.describe('Proposed idea or next logical leap.'),
|
|
62
|
+
evaluation: z.string().optional().describe('Critique of the hypothesis.'),
|
|
77
63
|
})
|
|
78
64
|
.superRefine((data, ctx) => {
|
|
79
65
|
const runMode = data.runMode ?? DEFAULT_RUN_MODE;
|
|
@@ -91,9 +77,6 @@ export const ReasoningThinkInputSchema = z
|
|
|
91
77
|
if (runMode === 'step' && Array.isArray(data.thought)) {
|
|
92
78
|
addCustomIssue(ctx, ['thought'], 'thought must be a string when runMode is "step"');
|
|
93
79
|
}
|
|
94
|
-
if (runMode === 'step' && data.thoughts !== undefined) {
|
|
95
|
-
addCustomIssue(ctx, ['thoughts'], 'thoughts is only allowed when runMode is "run_to_completion"');
|
|
96
|
-
}
|
|
97
80
|
const hasThought = data.thought !== undefined;
|
|
98
81
|
const hasStructured = data.observation !== undefined &&
|
|
99
82
|
data.hypothesis !== undefined &&
|
|
@@ -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
|
@@ -13,34 +13,34 @@ const ThoughtSchema = z.strictObject({
|
|
|
13
13
|
stepSummary: z
|
|
14
14
|
.string()
|
|
15
15
|
.optional()
|
|
16
|
-
.describe('
|
|
16
|
+
.describe('1-sentence summary of the conclusion reached.'),
|
|
17
17
|
});
|
|
18
18
|
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),
|
|
25
29
|
generatedThoughts: z.number(),
|
|
26
30
|
requestedThoughts: z.number(),
|
|
27
31
|
totalThoughts: z.number(),
|
|
28
|
-
tokenBudget: z
|
|
29
|
-
|
|
30
|
-
.describe('Approximate token budget (UTF-8 bytes ÷ 4, not true tokenization)'),
|
|
31
|
-
tokensUsed: z
|
|
32
|
-
.number()
|
|
33
|
-
.describe('Approximate tokens used (UTF-8 bytes ÷ 4, not true tokenization)'),
|
|
32
|
+
tokenBudget: z.number().describe('Approximate token budget.'),
|
|
33
|
+
tokensUsed: z.number().describe('Approximate tokens used.'),
|
|
34
34
|
ttlMs: z.number(),
|
|
35
35
|
expiresAt: z.number(),
|
|
36
36
|
createdAt: z.number(),
|
|
37
37
|
updatedAt: z.number(),
|
|
38
38
|
remainingThoughts: z
|
|
39
39
|
.number()
|
|
40
|
-
.describe('
|
|
40
|
+
.describe('Thoughts remaining before reaching totalThoughts.'),
|
|
41
41
|
summary: z
|
|
42
42
|
.string()
|
|
43
|
-
.describe('Actionable next-step instruction
|
|
43
|
+
.describe('Actionable next-step instruction or completion status.'),
|
|
44
44
|
}),
|
|
45
45
|
});
|
|
46
46
|
const ReasoningThinkErrorSchema = z.strictObject({
|
package/dist/server.js
CHANGED
|
@@ -3,6 +3,7 @@ import { findPackageJSON } from 'node:module';
|
|
|
3
3
|
import { InMemoryTaskStore } from '@modelcontextprotocol/sdk/experimental/tasks';
|
|
4
4
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
5
5
|
import { engineEvents } from './engine/events.js';
|
|
6
|
+
import { sessionStore } from './engine/reasoner.js';
|
|
6
7
|
import { getErrorMessage } from './lib/errors.js';
|
|
7
8
|
import { registerAllTools } from './tools/index.js';
|
|
8
9
|
import { registerAllPrompts } from './prompts/index.js';
|
|
@@ -20,6 +21,7 @@ const ICON_URL_CANDIDATES = [
|
|
|
20
21
|
];
|
|
21
22
|
let cachedLocalIconData;
|
|
22
23
|
let cachedVersion;
|
|
24
|
+
let activeServerCount = 0;
|
|
23
25
|
function getLocalIconData() {
|
|
24
26
|
if (cachedLocalIconData !== undefined) {
|
|
25
27
|
return cachedLocalIconData ?? undefined;
|
|
@@ -119,9 +121,22 @@ function attachEngineEventHandlers(server) {
|
|
|
119
121
|
process.stderr.write(`[cortex-mcp.server] Failed to log budget_exhausted: ${getErrorMessage(err)}\n`);
|
|
120
122
|
});
|
|
121
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
|
+
};
|
|
122
131
|
engineEvents.on('resources:changed', onResourcesChanged);
|
|
123
132
|
engineEvents.on('resource:updated', onResourceUpdated);
|
|
124
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);
|
|
125
140
|
let detached = false;
|
|
126
141
|
return () => {
|
|
127
142
|
if (detached) {
|
|
@@ -131,6 +146,12 @@ function attachEngineEventHandlers(server) {
|
|
|
131
146
|
engineEvents.off('resources:changed', onResourcesChanged);
|
|
132
147
|
engineEvents.off('resource:updated', onResourceUpdated);
|
|
133
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);
|
|
134
155
|
};
|
|
135
156
|
}
|
|
136
157
|
function installCloseCleanup(server, cleanup) {
|
|
@@ -142,10 +163,18 @@ function installCloseCleanup(server, cleanup) {
|
|
|
142
163
|
}
|
|
143
164
|
closed = true;
|
|
144
165
|
cleanup();
|
|
166
|
+
activeServerCount = Math.max(0, activeServerCount - 1);
|
|
167
|
+
if (activeServerCount === 0) {
|
|
168
|
+
sessionStore.dispose();
|
|
169
|
+
}
|
|
145
170
|
await originalClose();
|
|
146
171
|
};
|
|
147
172
|
}
|
|
148
173
|
export function createServer() {
|
|
174
|
+
if (activeServerCount === 0) {
|
|
175
|
+
sessionStore.ensureCleanupTimer();
|
|
176
|
+
}
|
|
177
|
+
activeServerCount += 1;
|
|
149
178
|
const version = loadVersion();
|
|
150
179
|
const taskStore = new InMemoryTaskStore();
|
|
151
180
|
const localIcon = getLocalIconData();
|
|
@@ -6,13 +6,30 @@ import { createTaskLimiter } from '../lib/concurrency.js';
|
|
|
6
6
|
import { createErrorResponse, getErrorMessage, InsufficientThoughtsError, InvalidRunModeArgsError, isObjectRecord, ReasoningAbortedError, ReasoningError, ServerBusyError, SessionNotFoundError, } from '../lib/errors.js';
|
|
7
7
|
import { formatProgressMessage, formatThoughtsToMarkdown, } from '../lib/formatting.js';
|
|
8
8
|
import { createToolResponse, withIconMeta } from '../lib/tool-response.js';
|
|
9
|
-
import { parsePositiveIntEnv } from '../lib/validators.js';
|
|
9
|
+
import { parseBooleanEnv, parsePositiveIntEnv } from '../lib/validators.js';
|
|
10
10
|
const DEFAULT_MAX_ACTIVE_REASONING_TASKS = 32;
|
|
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
|
-
uri: `
|
|
30
|
+
uri: `reasoning://sessions/${session.id}/trace.md`,
|
|
14
31
|
mimeType: 'text/markdown',
|
|
15
|
-
text: formatThoughtsToMarkdown(
|
|
32
|
+
text: formatThoughtsToMarkdown(sessionView),
|
|
16
33
|
};
|
|
17
34
|
}
|
|
18
35
|
const reasoningTaskLimiter = createTaskLimiter(parsePositiveIntEnv('CORTEX_MAX_ACTIVE_REASONING_TASKS', DEFAULT_MAX_ACTIVE_REASONING_TASKS));
|
|
@@ -69,7 +86,7 @@ function isReasoningTaskExtra(value) {
|
|
|
69
86
|
}
|
|
70
87
|
return true;
|
|
71
88
|
}
|
|
72
|
-
function
|
|
89
|
+
function assertReasoningTaskExtra(rawExtra) {
|
|
73
90
|
if (!isReasoningTaskExtra(rawExtra)) {
|
|
74
91
|
throw new Error('Invalid task context in request handler.');
|
|
75
92
|
}
|
|
@@ -144,7 +161,7 @@ function buildThoughtInputs(params) {
|
|
|
144
161
|
: params.thought
|
|
145
162
|
? [params.thought]
|
|
146
163
|
: [];
|
|
147
|
-
return
|
|
164
|
+
return primary;
|
|
148
165
|
}
|
|
149
166
|
function getStartingThoughtCount(sessionId) {
|
|
150
167
|
if (sessionId === undefined) {
|
|
@@ -224,6 +241,7 @@ function buildStructuredResult(session, generatedThoughts, targetThoughts) {
|
|
|
224
241
|
ok: true,
|
|
225
242
|
result: {
|
|
226
243
|
sessionId: session.id,
|
|
244
|
+
...(session.query !== undefined ? { query: session.query } : {}),
|
|
227
245
|
level: session.level,
|
|
228
246
|
status: session.status,
|
|
229
247
|
thoughts: [...session.thoughts],
|
|
@@ -243,7 +261,7 @@ function buildStructuredResult(session, generatedThoughts, targetThoughts) {
|
|
|
243
261
|
}
|
|
244
262
|
function buildSummary(session, remainingThoughts) {
|
|
245
263
|
if (session.status === 'completed') {
|
|
246
|
-
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}.`;
|
|
247
265
|
}
|
|
248
266
|
if (session.status === 'cancelled') {
|
|
249
267
|
return `Reasoning cancelled at thought ${String(session.thoughts.length)}/${String(session.totalThoughts)}. Session ${session.id}.`;
|
|
@@ -283,16 +301,22 @@ function createCancellationController(signal) {
|
|
|
283
301
|
const controller = new AbortController();
|
|
284
302
|
if (signal.aborted) {
|
|
285
303
|
controller.abort();
|
|
286
|
-
return
|
|
304
|
+
return {
|
|
305
|
+
controller,
|
|
306
|
+
cleanup: () => {
|
|
307
|
+
// No listener to clean up when already aborted.
|
|
308
|
+
},
|
|
309
|
+
};
|
|
287
310
|
}
|
|
288
311
|
const onAbort = () => {
|
|
289
312
|
controller.abort();
|
|
290
313
|
};
|
|
291
|
-
|
|
292
|
-
controller.signal.addEventListener('abort', () => {
|
|
314
|
+
const cleanup = () => {
|
|
293
315
|
signal.removeEventListener('abort', onAbort);
|
|
294
|
-
}
|
|
295
|
-
|
|
316
|
+
};
|
|
317
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
318
|
+
controller.signal.addEventListener('abort', cleanup, { once: true });
|
|
319
|
+
return { controller, cleanup };
|
|
296
320
|
}
|
|
297
321
|
async function isTaskCancelled(taskStore, taskId) {
|
|
298
322
|
try {
|
|
@@ -311,7 +335,7 @@ async function ensureTaskIsActive(taskStore, taskId, controller) {
|
|
|
311
335
|
}
|
|
312
336
|
function createProgressHandler(args) {
|
|
313
337
|
const { server, taskStore, taskId, level, progressToken, controller, startingCount, batchTotal, } = args;
|
|
314
|
-
return async (progress) => {
|
|
338
|
+
return async (progress, _total, summary) => {
|
|
315
339
|
await ensureTaskIsActive(taskStore, taskId, controller);
|
|
316
340
|
if (progressToken === undefined) {
|
|
317
341
|
return;
|
|
@@ -322,14 +346,16 @@ function createProgressHandler(args) {
|
|
|
322
346
|
const isTerminal = displayProgress >= batchTotal;
|
|
323
347
|
// We must emit if it's the terminal update for this batch,
|
|
324
348
|
// otherwise we respect the session-level skipping rules.
|
|
349
|
+
// If a summary is provided, we force an emit to show the meaningful update.
|
|
325
350
|
if (!isTerminal &&
|
|
351
|
+
!summary &&
|
|
326
352
|
!shouldEmitProgress(displayProgress, batchTotal, level)) {
|
|
327
353
|
return;
|
|
328
354
|
}
|
|
329
355
|
const message = formatProgressMessage({
|
|
330
|
-
toolName: TOOL_NAME
|
|
356
|
+
toolName: `꩜ ${TOOL_NAME}`,
|
|
331
357
|
context: 'Thought',
|
|
332
|
-
metadata:
|
|
358
|
+
...(summary ? { metadata: summary } : {}),
|
|
333
359
|
...(isTerminal ? { outcome: 'complete' } : {}),
|
|
334
360
|
});
|
|
335
361
|
await notifyProgress({
|
|
@@ -457,9 +483,9 @@ async function runReasoningTask(args) {
|
|
|
457
483
|
const normalizedBatchTotal = Math.max(1, batchTotal);
|
|
458
484
|
if (progressToken !== undefined) {
|
|
459
485
|
const message = formatProgressMessage({
|
|
460
|
-
toolName: TOOL_NAME
|
|
461
|
-
context: '
|
|
462
|
-
metadata: level ? `
|
|
486
|
+
toolName: `꩜ ${TOOL_NAME}`,
|
|
487
|
+
context: level ? 'starting' : 'continuing',
|
|
488
|
+
metadata: level ? `[${level}]` : 'session',
|
|
463
489
|
});
|
|
464
490
|
await notifyProgress({
|
|
465
491
|
server,
|
|
@@ -544,7 +570,7 @@ async function runReasoningTask(args) {
|
|
|
544
570
|
}
|
|
545
571
|
function getTaskId(extra) {
|
|
546
572
|
if (typeof extra.taskId !== 'string' || extra.taskId.length === 0) {
|
|
547
|
-
throw new
|
|
573
|
+
throw new InvalidRunModeArgsError('Task ID missing in request context.');
|
|
548
574
|
}
|
|
549
575
|
return extra.taskId;
|
|
550
576
|
}
|
|
@@ -565,7 +591,8 @@ Use step_summary for a 1-sentence conclusion per step — these accumulate in th
|
|
|
565
591
|
|
|
566
592
|
Levels: ${getLevelDescriptionString()}.
|
|
567
593
|
Alternatives: runMode="run_to_completion" (batch), or observation/hypothesis/evaluation fields (structured).
|
|
568
|
-
Errors: E_SESSION_NOT_FOUND (expired — start new), E_INVALID_THOUGHT_COUNT (check level ranges)
|
|
594
|
+
Errors: E_SESSION_NOT_FOUND (expired — start new), E_INVALID_THOUGHT_COUNT (check level ranges).
|
|
595
|
+
Protocol validation: malformed task metadata/arguments fail at request level before task start; runtime reasoning failures return tool isError=true payloads.`,
|
|
569
596
|
inputSchema: ReasoningThinkInputSchema,
|
|
570
597
|
outputSchema: ReasoningThinkToolOutputSchema,
|
|
571
598
|
annotations: {
|
|
@@ -586,7 +613,7 @@ Errors: E_SESSION_NOT_FOUND (expired — start new), E_INVALID_THOUGHT_COUNT (ch
|
|
|
586
613
|
throw new Error(`Invalid reasoning_think params: ${parseResult.error.message}`);
|
|
587
614
|
}
|
|
588
615
|
const params = parseResult.data;
|
|
589
|
-
const extra =
|
|
616
|
+
const extra = assertReasoningTaskExtra(rawExtra);
|
|
590
617
|
const progressToken = extra._meta?.progressToken;
|
|
591
618
|
if (!reasoningTaskLimiter.tryAcquire()) {
|
|
592
619
|
throw new ServerBusyError();
|
|
@@ -602,13 +629,13 @@ Errors: E_SESSION_NOT_FOUND (expired — start new), E_INVALID_THOUGHT_COUNT (ch
|
|
|
602
629
|
reasoningTaskLimiter.release();
|
|
603
630
|
throw error;
|
|
604
631
|
}
|
|
605
|
-
const
|
|
632
|
+
const cancellation = createCancellationController(extra.signal);
|
|
606
633
|
const runReasoningArgs = {
|
|
607
634
|
server,
|
|
608
635
|
taskStore: extra.taskStore,
|
|
609
636
|
taskId: task.taskId,
|
|
610
637
|
params,
|
|
611
|
-
controller,
|
|
638
|
+
controller: cancellation.controller,
|
|
612
639
|
};
|
|
613
640
|
if (progressToken !== undefined) {
|
|
614
641
|
runReasoningArgs.progressToken = progressToken;
|
|
@@ -617,16 +644,17 @@ Errors: E_SESSION_NOT_FOUND (expired — start new), E_INVALID_THOUGHT_COUNT (ch
|
|
|
617
644
|
runReasoningArgs.sessionId = extra.sessionId;
|
|
618
645
|
}
|
|
619
646
|
void runReasoningTask(runReasoningArgs).finally(() => {
|
|
647
|
+
cancellation.cleanup();
|
|
620
648
|
reasoningTaskLimiter.release();
|
|
621
649
|
});
|
|
622
650
|
return { task };
|
|
623
651
|
},
|
|
624
652
|
getTask(_params, rawExtra) {
|
|
625
|
-
const extra =
|
|
653
|
+
const extra = assertReasoningTaskExtra(rawExtra);
|
|
626
654
|
return extra.taskStore.getTask(getTaskId(extra));
|
|
627
655
|
},
|
|
628
656
|
async getTaskResult(_params, rawExtra) {
|
|
629
|
-
const extra =
|
|
657
|
+
const extra = assertReasoningTaskExtra(rawExtra);
|
|
630
658
|
const result = await extra.taskStore.getTaskResult(getTaskId(extra));
|
|
631
659
|
assertCallToolResult(result);
|
|
632
660
|
return result;
|