@j0hanz/thinkseq-mcp 1.2.1 → 1.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -2
- package/dist/app.js +0 -2
- package/dist/appConfig/runDependencies.js +11 -28
- package/dist/appConfig/shutdown.js +18 -13
- package/dist/appConfig/types.d.ts +1 -5
- package/dist/engine/revision.js +8 -15
- package/dist/engine/thoughtQueries.js +1 -6
- package/dist/engine/thoughtStore.js +19 -10
- package/dist/engine.js +20 -15
- package/dist/instructions.md +52 -42
- package/dist/lib/cli.js +22 -16
- package/dist/lib/package.js +3 -13
- package/dist/lib/protocolGuards.js +10 -12
- package/dist/lib/stdioGuards.d.ts +1 -0
- package/dist/lib/stdioGuards.js +125 -11
- package/dist/lib/types.d.ts +6 -0
- package/dist/schemas/inputs.d.ts +1 -1
- package/dist/schemas/inputs.js +1 -1
- package/dist/tools/thinkseq.d.ts +1 -5
- package/dist/tools/thinkseq.js +32 -21
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -268,7 +268,7 @@ Output:
|
|
|
268
268
|
|
|
269
269
|
## Behavior and validation
|
|
270
270
|
|
|
271
|
-
- Inputs are validated with Zod and unknown keys are
|
|
271
|
+
- Inputs are validated with Zod and unknown keys are rejected.
|
|
272
272
|
- `thoughtNumber` is auto-incremented (1, 2, 3...).
|
|
273
273
|
- `totalThoughts` defaults to 3, must be in 1-25, and is adjusted up to at least `thoughtNumber`.
|
|
274
274
|
- The engine stores thoughts in memory and prunes when limits are exceeded:
|
|
@@ -282,7 +282,6 @@ This server publishes events via `node:diagnostics_channel`:
|
|
|
282
282
|
|
|
283
283
|
- `thinkseq:tool` for `tool.start` and `tool.end` (includes duration, errors, and request context).
|
|
284
284
|
- `thinkseq:lifecycle` for `lifecycle.started` and `lifecycle.shutdown`.
|
|
285
|
-
- `thinkseq:engine` for internal engine events such as `engine.sequence_gap`.
|
|
286
285
|
|
|
287
286
|
## Configuration
|
|
288
287
|
|
package/dist/app.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { buildShutdownDependencies, resolvePackageIdentity, resolveRunDependencies, } from './appConfig.js';
|
|
2
|
-
import { installInitializationGuards } from './lib/protocolGuards.js';
|
|
3
2
|
const toError = (value) => value instanceof Error ? value : new Error(String(value));
|
|
4
3
|
const createExit = (proc, exit) => exit ?? ((code) => proc.exit(code));
|
|
5
4
|
const createHandlerFor = (logError, exit) => (label) => (value) => {
|
|
@@ -26,7 +25,6 @@ export async function run(deps = {}) {
|
|
|
26
25
|
const server = resolved.createServer(name, version);
|
|
27
26
|
const engine = resolved.engineFactory();
|
|
28
27
|
resolved.registerTool(server, engine);
|
|
29
|
-
installInitializationGuards(server);
|
|
30
28
|
const transport = await resolved.connectServer(server);
|
|
31
29
|
resolved.installShutdownHandlers(buildShutdownDependencies(resolved, { server, engine, transport }));
|
|
32
30
|
}
|
|
@@ -1,23 +1,23 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
1
2
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
3
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
3
|
-
import { readFileSync } from 'node:fs';
|
|
4
4
|
import { ThinkingEngine } from '../engine.js';
|
|
5
5
|
import { publishLifecycleEvent } from '../lib/diagnostics.js';
|
|
6
6
|
import { readSelfPackageJson } from '../lib/package.js';
|
|
7
|
-
import { installStdioInvalidMessageGuards, installStdioParseErrorResponder, } from '../lib/stdioGuards.js';
|
|
7
|
+
import { installStdioInitializationGuards, installStdioInvalidMessageGuards, installStdioParseErrorResponder, } from '../lib/stdioGuards.js';
|
|
8
8
|
import { registerThinkSeq } from '../tools/thinkseq.js';
|
|
9
9
|
import { installShutdownHandlers } from './shutdown.js';
|
|
10
|
-
const DEFAULT_SERVER_INSTRUCTIONS = 'ThinkSeq is a tool for structured, sequential thinking with revision support.';
|
|
11
10
|
function loadServerInstructions() {
|
|
11
|
+
const fallback = 'ThinkSeq is a tool for structured, sequential thinking with revision support.';
|
|
12
12
|
try {
|
|
13
13
|
const raw = readFileSync(new URL('../instructions.md', import.meta.url), {
|
|
14
14
|
encoding: 'utf8',
|
|
15
15
|
});
|
|
16
16
|
const trimmed = raw.trim();
|
|
17
|
-
return trimmed.length > 0 ? trimmed :
|
|
17
|
+
return trimmed.length > 0 ? trimmed : fallback;
|
|
18
18
|
}
|
|
19
19
|
catch {
|
|
20
|
-
return
|
|
20
|
+
return fallback;
|
|
21
21
|
}
|
|
22
22
|
}
|
|
23
23
|
const SERVER_INSTRUCTIONS = loadServerInstructions();
|
|
@@ -31,43 +31,26 @@ const defaultCreateServer = (name, version) => {
|
|
|
31
31
|
const defaultConnectServer = async (server, createTransport = () => new StdioServerTransport()) => {
|
|
32
32
|
const transport = createTransport();
|
|
33
33
|
await server.connect(transport);
|
|
34
|
+
installStdioInitializationGuards(transport);
|
|
34
35
|
installStdioInvalidMessageGuards(transport);
|
|
35
36
|
installStdioParseErrorResponder(transport);
|
|
36
37
|
return transport;
|
|
37
38
|
};
|
|
38
|
-
function
|
|
39
|
+
export function resolveRunDependencies(deps) {
|
|
39
40
|
return {
|
|
40
41
|
processLike: deps.processLike ?? process,
|
|
41
42
|
packageReadTimeoutMs: deps.packageReadTimeoutMs ?? DEFAULT_PACKAGE_READ_TIMEOUT_MS,
|
|
42
43
|
readPackageJson: deps.readPackageJson ?? readSelfPackageJson,
|
|
43
44
|
publishLifecycleEvent: deps.publishLifecycleEvent ?? publishLifecycleEvent,
|
|
44
|
-
now: deps.now ?? Date.now,
|
|
45
|
-
};
|
|
46
|
-
}
|
|
47
|
-
function resolveServerDependencies(deps) {
|
|
48
|
-
return {
|
|
49
45
|
createServer: deps.createServer ?? defaultCreateServer,
|
|
50
46
|
connectServer: deps.connectServer ?? defaultConnectServer,
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
function resolveEngineDependencies(deps) {
|
|
54
|
-
return {
|
|
55
47
|
registerTool: deps.registerTool ?? registerThinkSeq,
|
|
56
48
|
engineFactory: deps.engineFactory ?? (() => new ThinkingEngine()),
|
|
57
49
|
installShutdownHandlers: deps.installShutdownHandlers ?? installShutdownHandlers,
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
return {};
|
|
63
|
-
return { shutdownTimeoutMs: deps.shutdownTimeoutMs };
|
|
64
|
-
}
|
|
65
|
-
export function resolveRunDependencies(deps) {
|
|
66
|
-
return {
|
|
67
|
-
...resolveCoreDependencies(deps),
|
|
68
|
-
...resolveServerDependencies(deps),
|
|
69
|
-
...resolveEngineDependencies(deps),
|
|
70
|
-
...resolveShutdownTimeout(deps),
|
|
50
|
+
now: deps.now ?? Date.now,
|
|
51
|
+
...(deps.shutdownTimeoutMs !== undefined
|
|
52
|
+
? { shutdownTimeoutMs: deps.shutdownTimeoutMs }
|
|
53
|
+
: {}),
|
|
71
54
|
};
|
|
72
55
|
}
|
|
73
56
|
export function resolvePackageIdentity(pkg) {
|
|
@@ -6,17 +6,24 @@ function isRecord(value) {
|
|
|
6
6
|
function hasClose(value) {
|
|
7
7
|
return isRecord(value) && typeof value.close === 'function';
|
|
8
8
|
}
|
|
9
|
-
async function
|
|
9
|
+
async function closeSafely(value) {
|
|
10
|
+
try {
|
|
11
|
+
if (hasClose(value))
|
|
12
|
+
await value.close();
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
async function closeAllWithinTimeout(values, timeoutMs) {
|
|
10
19
|
const timeout = new Promise((resolve) => setTimeout(resolve, timeoutMs));
|
|
11
|
-
const attempt = (
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
}
|
|
19
|
-
})();
|
|
20
|
+
const attempt = Promise.allSettled(values.map((value) => closeSafely(value)))
|
|
21
|
+
.then(() => {
|
|
22
|
+
return;
|
|
23
|
+
})
|
|
24
|
+
.catch(() => {
|
|
25
|
+
return;
|
|
26
|
+
});
|
|
20
27
|
await Promise.race([attempt, timeout]);
|
|
21
28
|
}
|
|
22
29
|
function buildShutdownRunner(deps, proc) {
|
|
@@ -33,9 +40,7 @@ function buildShutdownRunner(deps, proc) {
|
|
|
33
40
|
ts: timestamp(),
|
|
34
41
|
signal,
|
|
35
42
|
});
|
|
36
|
-
await
|
|
37
|
-
await closeWithTimeout(deps.engine, timeoutMs);
|
|
38
|
-
await closeWithTimeout(deps.transport, timeoutMs);
|
|
43
|
+
await closeAllWithinTimeout([deps.server, deps.engine, deps.transport], timeoutMs);
|
|
39
44
|
proc.exit(0);
|
|
40
45
|
};
|
|
41
46
|
}
|
|
@@ -1,9 +1,5 @@
|
|
|
1
1
|
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
-
|
|
3
|
-
export type CloseFn = () => Promise<void> | void;
|
|
2
|
+
export type { CloseFn, EngineLike } from '../lib/types.js';
|
|
4
3
|
export type ProcessLike = Pick<typeof process, 'on' | 'exit'>;
|
|
5
4
|
export type TransportLike = Parameters<McpServer['connect']>[0];
|
|
6
5
|
export type ServerLike = Pick<McpServer, 'connect' | 'registerTool'>;
|
|
7
|
-
export type EngineLike = Pick<ThinkingEngine, 'processThought'> & {
|
|
8
|
-
close?: CloseFn;
|
|
9
|
-
};
|
package/dist/engine/revision.js
CHANGED
|
@@ -1,23 +1,16 @@
|
|
|
1
1
|
export function resolveRevisionTarget(input, getThoughtByNumber) {
|
|
2
|
-
const
|
|
3
|
-
if (
|
|
4
|
-
return
|
|
2
|
+
const targetNumber = input.revisesThought;
|
|
3
|
+
if (targetNumber === undefined) {
|
|
4
|
+
return {
|
|
5
|
+
ok: false,
|
|
6
|
+
error: buildRevisionError('E_REVISION_MISSING', 'revisesThought is required for revision'),
|
|
7
|
+
};
|
|
5
8
|
}
|
|
6
|
-
const validationError = validateRevisionTarget(getThoughtByNumber,
|
|
9
|
+
const validationError = validateRevisionTarget(getThoughtByNumber, targetNumber);
|
|
7
10
|
if (validationError) {
|
|
8
11
|
return { ok: false, error: validationError };
|
|
9
12
|
}
|
|
10
|
-
return { ok: true, targetNumber
|
|
11
|
-
}
|
|
12
|
-
function getRevisionTargetNumber(input) {
|
|
13
|
-
const targetNumber = input.revisesThought;
|
|
14
|
-
if (targetNumber !== undefined) {
|
|
15
|
-
return { ok: true, targetNumber };
|
|
16
|
-
}
|
|
17
|
-
return {
|
|
18
|
-
ok: false,
|
|
19
|
-
error: buildRevisionError('E_REVISION_MISSING', 'revisesThought is required for revision'),
|
|
20
|
-
};
|
|
13
|
+
return { ok: true, targetNumber };
|
|
21
14
|
}
|
|
22
15
|
function validateRevisionTarget(getThoughtByNumber, targetNumber) {
|
|
23
16
|
const target = getThoughtByNumber(targetNumber);
|
|
@@ -1,10 +1,5 @@
|
|
|
1
|
-
function getRecentActiveThoughts(activeThoughts, limit) {
|
|
2
|
-
if (limit <= 0)
|
|
3
|
-
return [];
|
|
4
|
-
return activeThoughts.slice(-limit);
|
|
5
|
-
}
|
|
6
1
|
export function buildContextSummary(activeThoughts, revisionInfo) {
|
|
7
|
-
const recent =
|
|
2
|
+
const recent = activeThoughts.slice(-5);
|
|
8
3
|
const startIndex = activeThoughts.length - recent.length;
|
|
9
4
|
const recentThoughts = recent.map((thought, index) => ({
|
|
10
5
|
stepIndex: startIndex + index + 1,
|
|
@@ -4,6 +4,7 @@ export class ThoughtStore {
|
|
|
4
4
|
#thoughtIndex = new Map();
|
|
5
5
|
#activeThoughts = [];
|
|
6
6
|
#activeThoughtNumbers = [];
|
|
7
|
+
#activeMaxTotalThoughts = 0;
|
|
7
8
|
#headIndex = 0;
|
|
8
9
|
#nextThoughtNumber = 1;
|
|
9
10
|
#estimatedBytes = 0;
|
|
@@ -18,9 +19,10 @@ export class ThoughtStore {
|
|
|
18
19
|
nextThoughtNumbers(totalThoughts) {
|
|
19
20
|
const thoughtNumber = this.#nextThoughtNumber;
|
|
20
21
|
this.#nextThoughtNumber += 1;
|
|
22
|
+
const effectiveTotalThoughts = Math.max(totalThoughts, thoughtNumber, this.#activeMaxTotalThoughts);
|
|
21
23
|
return {
|
|
22
24
|
thoughtNumber,
|
|
23
|
-
totalThoughts:
|
|
25
|
+
totalThoughts: effectiveTotalThoughts,
|
|
24
26
|
};
|
|
25
27
|
}
|
|
26
28
|
storeThought(stored) {
|
|
@@ -29,9 +31,17 @@ export class ThoughtStore {
|
|
|
29
31
|
if (stored.isActive) {
|
|
30
32
|
this.#activeThoughts.push(stored);
|
|
31
33
|
this.#activeThoughtNumbers.push(stored.thoughtNumber);
|
|
34
|
+
this.#activeMaxTotalThoughts = Math.max(this.#activeMaxTotalThoughts, stored.totalThoughts);
|
|
32
35
|
}
|
|
33
36
|
this.#estimatedBytes += this.#estimateThoughtBytes(stored);
|
|
34
37
|
}
|
|
38
|
+
#recomputeActiveMaxTotalThoughts() {
|
|
39
|
+
let maxTotal = 0;
|
|
40
|
+
for (const thought of this.#activeThoughts) {
|
|
41
|
+
maxTotal = Math.max(maxTotal, thought.totalThoughts);
|
|
42
|
+
}
|
|
43
|
+
this.#activeMaxTotalThoughts = maxTotal;
|
|
44
|
+
}
|
|
35
45
|
#findActiveThoughtIndex(thoughtNumber) {
|
|
36
46
|
const activeThoughtNumbers = this.#activeThoughtNumbers;
|
|
37
47
|
let low = 0;
|
|
@@ -71,6 +81,7 @@ export class ThoughtStore {
|
|
|
71
81
|
}
|
|
72
82
|
this.#activeThoughts.length = startIndex;
|
|
73
83
|
this.#activeThoughtNumbers.length = startIndex;
|
|
84
|
+
this.#recomputeActiveMaxTotalThoughts();
|
|
74
85
|
return supersedes;
|
|
75
86
|
}
|
|
76
87
|
getActiveThoughts() {
|
|
@@ -114,6 +125,7 @@ export class ThoughtStore {
|
|
|
114
125
|
return;
|
|
115
126
|
this.#activeThoughts = this.#activeThoughts.slice(startIndex);
|
|
116
127
|
this.#activeThoughtNumbers = this.#activeThoughtNumbers.slice(startIndex);
|
|
128
|
+
this.#recomputeActiveMaxTotalThoughts();
|
|
117
129
|
}
|
|
118
130
|
#removeOldest(count, options = {}) {
|
|
119
131
|
const totalLength = this.getTotalLength();
|
|
@@ -147,27 +159,24 @@ export class ThoughtStore {
|
|
|
147
159
|
#compactIfNeeded(force = false) {
|
|
148
160
|
if (this.#headIndex === 0)
|
|
149
161
|
return;
|
|
150
|
-
|
|
151
|
-
if (totalLength === 0) {
|
|
162
|
+
if (this.getTotalLength() === 0) {
|
|
152
163
|
this.#resetThoughts();
|
|
153
164
|
return;
|
|
154
165
|
}
|
|
155
|
-
if (!
|
|
166
|
+
if (!force &&
|
|
167
|
+
this.#headIndex < COMPACT_THRESHOLD &&
|
|
168
|
+
this.#headIndex < this.#thoughts.length * COMPACT_RATIO) {
|
|
156
169
|
return;
|
|
170
|
+
}
|
|
157
171
|
this.#thoughts = this.#thoughts.slice(this.#headIndex);
|
|
158
172
|
this.#headIndex = 0;
|
|
159
173
|
}
|
|
160
|
-
#shouldCompact(force) {
|
|
161
|
-
if (force)
|
|
162
|
-
return true;
|
|
163
|
-
return (this.#headIndex >= COMPACT_THRESHOLD ||
|
|
164
|
-
this.#headIndex >= this.#thoughts.length * COMPACT_RATIO);
|
|
165
|
-
}
|
|
166
174
|
#resetThoughts() {
|
|
167
175
|
this.#thoughts = [];
|
|
168
176
|
this.#thoughtIndex.clear();
|
|
169
177
|
this.#activeThoughts = [];
|
|
170
178
|
this.#activeThoughtNumbers = [];
|
|
179
|
+
this.#activeMaxTotalThoughts = 0;
|
|
171
180
|
this.#headIndex = 0;
|
|
172
181
|
this.#nextThoughtNumber = 1;
|
|
173
182
|
this.#estimatedBytes = 0;
|
package/dist/engine.js
CHANGED
|
@@ -24,11 +24,8 @@ export class ThinkingEngine {
|
|
|
24
24
|
return this.#processNewThought(input);
|
|
25
25
|
}
|
|
26
26
|
#processNewThought(input) {
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
const stored = this.#buildStoredThought(input, numbers);
|
|
30
|
-
this.#store.storeThought(stored);
|
|
31
|
-
this.#store.pruneHistoryIfNeeded();
|
|
27
|
+
const { stored } = this.#createStoredThought(input);
|
|
28
|
+
this.#commitThought(stored);
|
|
32
29
|
return this.#buildProcessResult(stored);
|
|
33
30
|
}
|
|
34
31
|
#processRevision(input) {
|
|
@@ -36,18 +33,14 @@ export class ThinkingEngine {
|
|
|
36
33
|
if (!resolved.ok)
|
|
37
34
|
return resolved.error;
|
|
38
35
|
const { targetNumber } = resolved;
|
|
39
|
-
const
|
|
40
|
-
|
|
36
|
+
const { numbers, stored } = this.#createStoredThought(input, {
|
|
37
|
+
revisionOf: targetNumber,
|
|
38
|
+
});
|
|
41
39
|
const supersedesAll = this.#store.supersedeFrom(targetNumber, numbers.thoughtNumber);
|
|
42
40
|
const supersedesTotal = supersedesAll.length;
|
|
43
41
|
const supersedes = capArrayStart(supersedesAll, MAX_SUPERSEDES);
|
|
44
|
-
const stored = this.#buildStoredThought(input, {
|
|
45
|
-
...numbers,
|
|
46
|
-
revisionOf: targetNumber,
|
|
47
|
-
});
|
|
48
|
-
this.#store.storeThought(stored);
|
|
49
42
|
this.#hasRevisions = true;
|
|
50
|
-
this.#
|
|
43
|
+
this.#commitThought(stored);
|
|
51
44
|
return this.#buildProcessResult(stored, {
|
|
52
45
|
revises: targetNumber,
|
|
53
46
|
supersedes,
|
|
@@ -69,14 +62,26 @@ export class ThinkingEngine {
|
|
|
69
62
|
}),
|
|
70
63
|
};
|
|
71
64
|
}
|
|
65
|
+
#createStoredThought(input, extras = {}) {
|
|
66
|
+
const effectiveTotalThoughts = this.#resolveEffectiveTotalThoughts(input);
|
|
67
|
+
const numbers = this.#store.nextThoughtNumbers(effectiveTotalThoughts);
|
|
68
|
+
const stored = this.#buildStoredThought(input, {
|
|
69
|
+
...numbers,
|
|
70
|
+
...extras,
|
|
71
|
+
});
|
|
72
|
+
return { stored, numbers };
|
|
73
|
+
}
|
|
74
|
+
#commitThought(stored) {
|
|
75
|
+
this.#store.storeThought(stored);
|
|
76
|
+
this.#store.pruneHistoryIfNeeded();
|
|
77
|
+
}
|
|
72
78
|
#resolveEffectiveTotalThoughts(input) {
|
|
73
79
|
if (input.totalThoughts !== undefined) {
|
|
74
80
|
return input.totalThoughts;
|
|
75
81
|
}
|
|
76
82
|
const activeThoughts = this.#store.getActiveThoughts();
|
|
77
83
|
const lastActive = activeThoughts[activeThoughts.length - 1];
|
|
78
|
-
if (lastActive !== undefined
|
|
79
|
-
lastActive.totalThoughts > _a.DEFAULT_TOTAL_THOUGHTS) {
|
|
84
|
+
if (lastActive !== undefined) {
|
|
80
85
|
return lastActive.totalThoughts;
|
|
81
86
|
}
|
|
82
87
|
return _a.DEFAULT_TOTAL_THOUGHTS;
|
package/dist/instructions.md
CHANGED
|
@@ -1,66 +1,76 @@
|
|
|
1
|
-
# ThinkSeq MCP Server — Instructions
|
|
1
|
+
# ThinkSeq MCP Server — AI Usage Instructions
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Use this server to record sequential thinking steps to plan, reason, and debug. Prefer these tools over "remembering" state in chat.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
## Operating Rules
|
|
6
6
|
|
|
7
|
-
-
|
|
8
|
-
-
|
|
7
|
+
- Keep thoughts atomic: one decision, calculation, or action per step.
|
|
8
|
+
- Use revisions to fix mistakes in the active chain instead of apologizing in chat.
|
|
9
|
+
- If request is vague, ask clarifying questions.
|
|
9
10
|
|
|
10
|
-
|
|
11
|
+
### Strategies
|
|
11
12
|
|
|
12
|
-
|
|
13
|
+
- **Discovery:** Read the tool output's `context` to see recent thoughts and available revision targets.
|
|
14
|
+
- **Action:** Use `thinkseq` to advance the reasoning chain or `revisesThought` to rewind and correct.
|
|
13
15
|
|
|
14
|
-
|
|
16
|
+
## Data Model
|
|
15
17
|
|
|
16
|
-
**
|
|
18
|
+
- **Thinking Step:** `thought` (text), `thoughtNumber` (int), `progress` (0-1), `isComplete` (bool)
|
|
17
19
|
|
|
18
|
-
|
|
19
|
-
- You want a lightweight decision log (assumptions → choice → next action).
|
|
20
|
-
- You are debugging and want a clear hypothesis → check → result chain.
|
|
20
|
+
## Runtime Controls
|
|
21
21
|
|
|
22
|
-
**
|
|
22
|
+
- **Retention (server CLI):** the server keeps a rolling in-memory history.
|
|
23
|
+
- `--max-thoughts <number>` controls how many total stored thoughts are retained (default is 500).
|
|
24
|
+
- `--max-memory-mb <number>` caps estimated memory use for stored thoughts (default is 100MB).
|
|
25
|
+
- **Text content compatibility:** by default the tool returns both `structuredContent` and a JSON string in `content`.
|
|
26
|
+
- Set `THINKSEQ_INCLUDE_TEXT_CONTENT=0|false|no|off` to omit the JSON string and return only `structuredContent`.
|
|
23
27
|
|
|
24
|
-
|
|
25
|
-
- Storing secrets, credentials, or personal data.
|
|
28
|
+
## Workflows
|
|
26
29
|
|
|
27
|
-
|
|
30
|
+
### 1) Structured Reasoning
|
|
28
31
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
+
```text
|
|
33
|
+
thinkseq(thought="Plan: 1. check, 2. fix", totalThoughts=5) → Start chain
|
|
34
|
+
thinkseq(thought="Check passed, starting fix") → Progress chain
|
|
35
|
+
thinkseq(thought="Revised plan: use new API", revisesThought=1) → Correction
|
|
36
|
+
```
|
|
32
37
|
|
|
33
|
-
|
|
38
|
+
Notes:
|
|
34
39
|
|
|
35
|
-
-
|
|
36
|
-
-
|
|
37
|
-
- Superseded thoughts remain in history for audit, but they are no longer in the active chain.
|
|
40
|
+
- `totalThoughts` is only an estimate for progress/completion (max 25); it does **not** change retention.
|
|
41
|
+
- Revisions can only target active (non-superseded) thoughts.
|
|
38
42
|
|
|
39
|
-
|
|
43
|
+
## Tools
|
|
40
44
|
|
|
41
|
-
|
|
42
|
-
- `result.revisableThoughts`: thought numbers you can revise
|
|
43
|
-
- `result.context.recentThoughts`: previews of the current active chain
|
|
44
|
-
- `result.context.revisionInfo`: present only when a revision occurred
|
|
45
|
+
### thinkseq
|
|
45
46
|
|
|
46
|
-
|
|
47
|
+
Record a concise thinking step (max 5000 chars). Be brief: capture only the essential insight, calculation, or decision.
|
|
47
48
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
49
|
+
- **Use when:** You need to structured reasoning, planning, or a decision log.
|
|
50
|
+
- **Args:**
|
|
51
|
+
- `thought` (string, required): Your current thinking step.
|
|
52
|
+
- `totalThoughts` (number, optional): Estimated total thoughts (1-25, default: 3).
|
|
53
|
+
- `revisesThought` (number, optional): Revise a previous thought by number.
|
|
54
|
+
- **Returns:** `thoughtNumber`, `progress`, `isComplete`, `revisableThoughts`, `context` (history previews).
|
|
52
55
|
|
|
53
|
-
## Response
|
|
56
|
+
## Response Shape
|
|
54
57
|
|
|
55
|
-
|
|
56
|
-
|
|
58
|
+
Success: `{ "ok": true, "result": { ... } }`
|
|
59
|
+
Error: `{ "ok": false, "error": { "code": "...", "message": "..." } }`
|
|
57
60
|
|
|
58
|
-
|
|
61
|
+
### Common Errors
|
|
59
62
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
+
| Code | Meaning | Resolution |
|
|
64
|
+
| ------------------------------ | -------------------------------------- | ----------------------------------------- |
|
|
65
|
+
| `E_REVISION_TARGET_NOT_FOUND` | Revision target ID does not exist | Check `revisableThoughts` for valid IDs |
|
|
66
|
+
| `E_REVISION_TARGET_SUPERSEDED` | Target thought was already overwritten | Revise the current active thought instead |
|
|
67
|
+
| `E_THINK` | Generic engine error | Check arguments and retry |
|
|
63
68
|
|
|
64
|
-
##
|
|
69
|
+
## Limits
|
|
65
70
|
|
|
66
|
-
-
|
|
71
|
+
- **Max Thoughts:** 25 (default estimate)
|
|
72
|
+
- **Max Length:** 5000 chars per thought
|
|
73
|
+
|
|
74
|
+
## Security
|
|
75
|
+
|
|
76
|
+
- Do not store credentials, secrets, or PII in thoughts. State is in-memory only but may be logged.
|
package/dist/lib/cli.js
CHANGED
|
@@ -20,11 +20,24 @@ Options:
|
|
|
20
20
|
--package-read-timeout-ms <number> Package.json read timeout
|
|
21
21
|
-h, --help Show this help`;
|
|
22
22
|
const BYTES_PER_MB = 1024 * 1024;
|
|
23
|
+
const CLI_OPTION_SPECS = [
|
|
24
|
+
{ key: 'max-thoughts', configKey: 'maxThoughts' },
|
|
25
|
+
{
|
|
26
|
+
key: 'max-memory-mb',
|
|
27
|
+
configKey: 'maxMemoryBytes',
|
|
28
|
+
map: (value) => value * BYTES_PER_MB,
|
|
29
|
+
},
|
|
30
|
+
{ key: 'shutdown-timeout-ms', configKey: 'shutdownTimeoutMs' },
|
|
31
|
+
{ key: 'package-read-timeout-ms', configKey: 'packageReadTimeoutMs' },
|
|
32
|
+
];
|
|
23
33
|
function parsePositiveInt(value, label) {
|
|
24
34
|
if (value === undefined)
|
|
25
35
|
return undefined;
|
|
26
|
-
|
|
27
|
-
|
|
36
|
+
if (!/^\d+$/.test(value)) {
|
|
37
|
+
throw new Error(`Invalid ${label}: ${value}`);
|
|
38
|
+
}
|
|
39
|
+
const parsed = Number(value);
|
|
40
|
+
if (!Number.isFinite(parsed) || !Number.isInteger(parsed) || parsed <= 0) {
|
|
28
41
|
throw new Error(`Invalid ${label}: ${value}`);
|
|
29
42
|
}
|
|
30
43
|
return parsed;
|
|
@@ -40,21 +53,14 @@ function getStringOption(values, key) {
|
|
|
40
53
|
return typeof value === 'string' ? value : undefined;
|
|
41
54
|
}
|
|
42
55
|
function buildCliConfig(values) {
|
|
43
|
-
const maxThoughts = parsePositiveInt(getStringOption(values, 'max-thoughts'), 'max-thoughts');
|
|
44
|
-
const maxMemoryMb = parsePositiveInt(getStringOption(values, 'max-memory-mb'), 'max-memory-mb');
|
|
45
|
-
const shutdownTimeoutMs = parsePositiveInt(getStringOption(values, 'shutdown-timeout-ms'), 'shutdown-timeout-ms');
|
|
46
|
-
const packageReadTimeoutMs = parsePositiveInt(getStringOption(values, 'package-read-timeout-ms'), 'package-read-timeout-ms');
|
|
47
56
|
const config = {};
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
config.
|
|
55
|
-
}
|
|
56
|
-
if (packageReadTimeoutMs !== undefined) {
|
|
57
|
-
config.packageReadTimeoutMs = packageReadTimeoutMs;
|
|
57
|
+
for (const spec of CLI_OPTION_SPECS) {
|
|
58
|
+
const raw = getStringOption(values, spec.key);
|
|
59
|
+
const parsed = parsePositiveInt(raw, spec.key);
|
|
60
|
+
if (parsed === undefined)
|
|
61
|
+
continue;
|
|
62
|
+
const mapped = spec.map ? spec.map(parsed) : parsed;
|
|
63
|
+
config[spec.configKey] = mapped;
|
|
58
64
|
}
|
|
59
65
|
return config;
|
|
60
66
|
}
|
package/dist/lib/package.js
CHANGED
|
@@ -2,7 +2,6 @@ import { readFile } from 'node:fs/promises';
|
|
|
2
2
|
import { join } from 'node:path';
|
|
3
3
|
import { fileURLToPath } from 'node:url';
|
|
4
4
|
const defaultReadFile = (path, options) => readFile(path, options);
|
|
5
|
-
const defaultCwd = () => process.cwd();
|
|
6
5
|
const defaultPackageJsonPath = fileURLToPath(new URL('../../package.json', import.meta.url));
|
|
7
6
|
function isRecord(value) {
|
|
8
7
|
return typeof value === 'object' && value !== null;
|
|
@@ -30,25 +29,16 @@ function parsePackageJson(raw) {
|
|
|
30
29
|
return {};
|
|
31
30
|
}
|
|
32
31
|
}
|
|
33
|
-
function resolveReadFile(deps) {
|
|
34
|
-
return deps?.readFile ?? defaultReadFile;
|
|
35
|
-
}
|
|
36
|
-
function resolveCwd(deps) {
|
|
37
|
-
return deps?.cwd ?? defaultCwd;
|
|
38
|
-
}
|
|
39
32
|
function resolvePackageJsonPath(deps) {
|
|
40
33
|
// Only honor cwd injection when readFile is also injected (test seam).
|
|
41
34
|
if (deps?.readFile && deps.cwd)
|
|
42
|
-
return join(
|
|
35
|
+
return join(deps.cwd(), 'package.json');
|
|
43
36
|
return defaultPackageJsonPath;
|
|
44
37
|
}
|
|
45
|
-
function buildReadOptions(signal) {
|
|
46
|
-
return signal ? { encoding: 'utf8', signal } : { encoding: 'utf8' };
|
|
47
|
-
}
|
|
48
38
|
export async function readSelfPackageJson(signal, deps) {
|
|
49
39
|
try {
|
|
50
|
-
const readFileImpl =
|
|
51
|
-
const raw = await readFileImpl(resolvePackageJsonPath(deps),
|
|
40
|
+
const readFileImpl = deps?.readFile ?? defaultReadFile;
|
|
41
|
+
const raw = await readFileImpl(resolvePackageJsonPath(deps), signal ? { encoding: 'utf8', signal } : { encoding: 'utf8' });
|
|
52
42
|
return parsePackageJson(raw);
|
|
53
43
|
}
|
|
54
44
|
catch {
|
|
@@ -42,15 +42,16 @@ function wrapWithInitializationGuard(method, handler, state) {
|
|
|
42
42
|
return await handler(request, extra);
|
|
43
43
|
};
|
|
44
44
|
}
|
|
45
|
-
function
|
|
45
|
+
function getProtocolHandlers(server) {
|
|
46
46
|
if (!isRecord(server))
|
|
47
47
|
return undefined;
|
|
48
48
|
const protocol = Reflect.get(server, 'server');
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
function getRequestHandlers(protocol) {
|
|
49
|
+
if (!isRecord(protocol))
|
|
50
|
+
return undefined;
|
|
52
51
|
const handlers = Reflect.get(protocol, '_requestHandlers');
|
|
53
|
-
|
|
52
|
+
if (!(handlers instanceof Map))
|
|
53
|
+
return undefined;
|
|
54
|
+
return { protocol, handlers };
|
|
54
55
|
}
|
|
55
56
|
function installFallbackRequestHandler(protocol, state) {
|
|
56
57
|
// Guard unknown methods as well (so pre-init calls to unknown methods don't
|
|
@@ -76,13 +77,10 @@ function wrapRequestHandlers(handlers, state) {
|
|
|
76
77
|
}
|
|
77
78
|
}
|
|
78
79
|
export function installInitializationGuards(server) {
|
|
79
|
-
const
|
|
80
|
-
if (!
|
|
81
|
-
return;
|
|
82
|
-
const handlers = getRequestHandlers(protocol);
|
|
83
|
-
if (!handlers)
|
|
80
|
+
const resolved = getProtocolHandlers(server);
|
|
81
|
+
if (!resolved)
|
|
84
82
|
return;
|
|
85
83
|
const state = { sawInitialize: false };
|
|
86
|
-
wrapRequestHandlers(handlers, state);
|
|
87
|
-
installFallbackRequestHandler(protocol, state);
|
|
84
|
+
wrapRequestHandlers(resolved.handlers, state);
|
|
85
|
+
installFallbackRequestHandler(resolved.protocol, state);
|
|
88
86
|
}
|