@gotza02/sequential-thinking 2026.2.19 → 2026.2.21
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 -1
- package/dist/codestore.d.ts +28 -0
- package/dist/graph.d.ts +60 -0
- package/dist/graph.js +19 -0
- package/dist/http-server.d.ts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/lib.d.ts +67 -0
- package/dist/lib.js +323 -83
- package/dist/notes.d.ts +25 -0
- package/dist/system_test.d.ts +1 -0
- package/dist/test_ts_req.d.ts +1 -0
- package/dist/tools/codestore.d.ts +3 -0
- package/dist/tools/coding.d.ts +3 -0
- package/dist/tools/coding.js +40 -3
- package/dist/tools/filesystem.d.ts +2 -0
- package/dist/tools/graph.d.ts +3 -0
- package/dist/tools/graph.js +18 -0
- package/dist/tools/human.d.ts +65 -0
- package/dist/tools/human.js +305 -0
- package/dist/tools/notes.d.ts +3 -0
- package/dist/tools/thinking.d.ts +3 -0
- package/dist/tools/thinking.js +137 -65
- package/dist/tools/web.d.ts +2 -0
- package/dist/utils.d.ts +32 -0
- package/package.json +3 -1
- package/dist/chaos.test.js +0 -72
- package/dist/codestore.test.js +0 -59
- package/dist/coding.test.js +0 -130
- package/dist/e2e.test.js +0 -122
- package/dist/filesystem.test.js +0 -189
- package/dist/graph.test.js +0 -150
- package/dist/graph_repro.test.js +0 -63
- package/dist/integration.test.js +0 -58
- package/dist/notes.test.js +0 -74
- package/dist/registration.test.js +0 -39
- package/dist/repro_dollar.js +0 -30
- package/dist/repro_dollar_simple.js +0 -22
- package/dist/repro_history.js +0 -41
- package/dist/repro_path.js +0 -17
- package/dist/repro_search.test.js +0 -79
- package/dist/repro_ts_req.js +0 -3
- package/dist/server.test.js +0 -127
- package/dist/stress.test.js +0 -68
- package/dist/utils.test.js +0 -40
- package/dist/verify_cache.test.js +0 -27
- package/dist/verify_edit.test.js +0 -66
- package/dist/verify_notes.test.js +0 -36
- package/dist/verify_viz.test.js +0 -25
- package/dist/web_fallback.test.js +0 -103
- package/dist/web_read.test.js +0 -60
package/dist/lib.js
CHANGED
|
@@ -5,12 +5,12 @@ import * as path from 'path';
|
|
|
5
5
|
import { AsyncMutex } from './utils.js';
|
|
6
6
|
export class SequentialThinkingServer {
|
|
7
7
|
thoughtHistory = [];
|
|
8
|
+
blocks = [];
|
|
9
|
+
currentBlockId = null;
|
|
8
10
|
branches = {};
|
|
9
11
|
disableThoughtLogging;
|
|
10
12
|
storagePath;
|
|
11
13
|
delayMs;
|
|
12
|
-
isSaving = false;
|
|
13
|
-
hasPendingSave = false;
|
|
14
14
|
saveMutex = new AsyncMutex();
|
|
15
15
|
constructor(storagePath = 'thoughts_history.json', delayMs = 0) {
|
|
16
16
|
this.disableThoughtLogging = (process.env.DISABLE_THOUGHT_LOGGING || "").toLowerCase() === "true";
|
|
@@ -23,39 +23,38 @@ export class SequentialThinkingServer {
|
|
|
23
23
|
if (existsSync(this.storagePath)) {
|
|
24
24
|
const data = readFileSync(this.storagePath, 'utf-8');
|
|
25
25
|
if (!data.trim())
|
|
26
|
-
return;
|
|
26
|
+
return;
|
|
27
27
|
try {
|
|
28
|
-
const
|
|
29
|
-
|
|
28
|
+
const parsed = JSON.parse(data);
|
|
29
|
+
// New format with version
|
|
30
|
+
if (parsed.version && parsed.blocks) {
|
|
31
|
+
this.blocks = parsed.blocks;
|
|
32
|
+
this.thoughtHistory = parsed.thoughtHistory || [];
|
|
33
|
+
if (this.blocks.length > 0) {
|
|
34
|
+
const activeBlock = this.blocks.find(b => b.status === 'active');
|
|
35
|
+
this.currentBlockId = activeBlock?.id || this.blocks[this.blocks.length - 1].id;
|
|
36
|
+
}
|
|
37
|
+
this.rebuildBranches();
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
// Legacy format (array of thoughts)
|
|
41
|
+
if (Array.isArray(parsed)) {
|
|
30
42
|
this.thoughtHistory = [];
|
|
31
43
|
this.branches = {};
|
|
32
|
-
|
|
44
|
+
parsed.forEach(thought => {
|
|
33
45
|
if (thought && typeof thought === 'object' && 'thought' in thought) {
|
|
46
|
+
// Migrate to new format
|
|
47
|
+
thought.thoughtType = thought.thoughtType || 'analysis';
|
|
48
|
+
thought.blockId = thought.blockId || 'legacy-default';
|
|
34
49
|
this.addToMemory(thought);
|
|
35
50
|
}
|
|
36
51
|
});
|
|
52
|
+
this.rebuildBlocks();
|
|
37
53
|
}
|
|
38
54
|
}
|
|
39
55
|
catch (parseError) {
|
|
40
56
|
console.error(`Error parsing history from ${this.storagePath}, attempting recovery:`, parseError);
|
|
41
|
-
|
|
42
|
-
// This is a simple heuristic: find the last '}' that closes a thought object
|
|
43
|
-
const lastBrace = data.lastIndexOf('}');
|
|
44
|
-
if (lastBrace !== -1) {
|
|
45
|
-
try {
|
|
46
|
-
const recoveredData = data.substring(0, lastBrace + 1).trim();
|
|
47
|
-
// If it's part of an array, we might need to add ']'
|
|
48
|
-
const attemptedJson = recoveredData.endsWith(']') ? recoveredData : recoveredData + ']';
|
|
49
|
-
const history = JSON.parse(attemptedJson);
|
|
50
|
-
if (Array.isArray(history)) {
|
|
51
|
-
history.forEach(thought => this.addToMemory(thought));
|
|
52
|
-
console.log(`Successfully recovered ${history.length} thoughts.`);
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
catch (recoveryError) {
|
|
56
|
-
console.error('Recovery failed, starting with empty history.');
|
|
57
|
-
}
|
|
58
|
-
}
|
|
57
|
+
this.attemptRecovery(data);
|
|
59
58
|
}
|
|
60
59
|
}
|
|
61
60
|
}
|
|
@@ -63,12 +62,72 @@ export class SequentialThinkingServer {
|
|
|
63
62
|
console.error(`Error loading history from ${this.storagePath}:`, error);
|
|
64
63
|
}
|
|
65
64
|
}
|
|
65
|
+
attemptRecovery(data) {
|
|
66
|
+
const lastBrace = data.lastIndexOf('}');
|
|
67
|
+
if (lastBrace !== -1) {
|
|
68
|
+
try {
|
|
69
|
+
const recoveredData = data.substring(0, lastBrace + 1).trim();
|
|
70
|
+
const attemptedJson = recoveredData.endsWith(']') ? recoveredData : recoveredData + ']';
|
|
71
|
+
const history = JSON.parse(attemptedJson);
|
|
72
|
+
if (Array.isArray(history)) {
|
|
73
|
+
history.forEach(thought => this.addToMemory(thought));
|
|
74
|
+
this.rebuildBlocks();
|
|
75
|
+
console.log(`Successfully recovered ${history.length} thoughts.`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
catch (recoveryError) {
|
|
79
|
+
console.error('Recovery failed, starting with empty history.');
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
rebuildBlocks() {
|
|
84
|
+
const blockMap = new Map();
|
|
85
|
+
for (const t of this.thoughtHistory) {
|
|
86
|
+
const bid = t.blockId || 'default';
|
|
87
|
+
if (!blockMap.has(bid)) {
|
|
88
|
+
const newBlock = {
|
|
89
|
+
id: bid,
|
|
90
|
+
topic: t.thought.substring(0, 50),
|
|
91
|
+
status: 'active',
|
|
92
|
+
thoughts: [],
|
|
93
|
+
createdAt: new Date().toISOString(),
|
|
94
|
+
updatedAt: new Date().toISOString()
|
|
95
|
+
};
|
|
96
|
+
blockMap.set(bid, newBlock);
|
|
97
|
+
this.blocks.push(newBlock);
|
|
98
|
+
}
|
|
99
|
+
blockMap.get(bid).thoughts.push(t);
|
|
100
|
+
}
|
|
101
|
+
if (this.blocks.length > 0) {
|
|
102
|
+
this.currentBlockId = this.blocks[this.blocks.length - 1].id;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
rebuildBranches() {
|
|
106
|
+
this.branches = {};
|
|
107
|
+
for (const t of this.thoughtHistory) {
|
|
108
|
+
if (t.branchFromThought && t.branchId) {
|
|
109
|
+
const branchKey = `${t.branchFromThought}-${t.branchId}`;
|
|
110
|
+
if (!this.branches[branchKey]) {
|
|
111
|
+
this.branches[branchKey] = [];
|
|
112
|
+
}
|
|
113
|
+
this.branches[branchKey].push(t);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
66
117
|
async saveHistory() {
|
|
67
|
-
// Use mutex to ensure only one file operation happens at a time
|
|
68
118
|
await this.saveMutex.dispatch(async () => {
|
|
69
119
|
try {
|
|
120
|
+
// Auto-Pruning: Keep max 100 thoughts, summarize older blocks
|
|
121
|
+
if (this.thoughtHistory.length > 100) {
|
|
122
|
+
this.autoPrune();
|
|
123
|
+
}
|
|
124
|
+
const storage = {
|
|
125
|
+
version: '2.0',
|
|
126
|
+
blocks: this.blocks,
|
|
127
|
+
thoughtHistory: this.thoughtHistory
|
|
128
|
+
};
|
|
70
129
|
const tmpPath = `${this.storagePath}.tmp`;
|
|
71
|
-
await fs.writeFile(tmpPath, JSON.stringify(
|
|
130
|
+
await fs.writeFile(tmpPath, JSON.stringify(storage, null, 2), 'utf-8');
|
|
72
131
|
await fs.rename(tmpPath, this.storagePath);
|
|
73
132
|
}
|
|
74
133
|
catch (error) {
|
|
@@ -76,9 +135,24 @@ export class SequentialThinkingServer {
|
|
|
76
135
|
}
|
|
77
136
|
});
|
|
78
137
|
}
|
|
138
|
+
autoPrune() {
|
|
139
|
+
// Keep last 2 blocks full, mark others as completed
|
|
140
|
+
if (this.blocks.length > 2) {
|
|
141
|
+
for (let i = 0; i < this.blocks.length - 2; i++) {
|
|
142
|
+
this.blocks[i].status = 'completed';
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
// Keep only last 100 thoughts
|
|
146
|
+
if (this.thoughtHistory.length > 100) {
|
|
147
|
+
const removed = this.thoughtHistory.splice(0, this.thoughtHistory.length - 100);
|
|
148
|
+
console.error(`[AutoPrune] Removed ${removed.length} old thoughts to maintain performance.`);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
79
151
|
async clearHistory() {
|
|
80
152
|
this.thoughtHistory = [];
|
|
81
153
|
this.branches = {};
|
|
154
|
+
this.blocks = [];
|
|
155
|
+
this.currentBlockId = null;
|
|
82
156
|
await this.saveHistory();
|
|
83
157
|
}
|
|
84
158
|
async archiveHistory(startIndex, endIndex, summary) {
|
|
@@ -90,29 +164,25 @@ export class SequentialThinkingServer {
|
|
|
90
164
|
thoughtNumber: startIndex,
|
|
91
165
|
totalThoughts: this.thoughtHistory[this.thoughtHistory.length - 1].totalThoughts - (endIndex - startIndex),
|
|
92
166
|
nextThoughtNeeded: true,
|
|
93
|
-
thoughtType: 'analysis'
|
|
167
|
+
thoughtType: 'analysis',
|
|
168
|
+
blockId: this.currentBlockId || undefined
|
|
94
169
|
};
|
|
95
|
-
// Remove the range and insert summary
|
|
96
170
|
const removedCount = endIndex - startIndex + 1;
|
|
97
171
|
this.thoughtHistory.splice(startIndex - 1, removedCount, summaryThought);
|
|
98
|
-
// Update all thoughts to reflect the new total and renumber subsequent ones
|
|
99
172
|
const newTotal = this.thoughtHistory.length;
|
|
100
173
|
const shiftAmount = removedCount - 1;
|
|
101
174
|
for (let i = 0; i < this.thoughtHistory.length; i++) {
|
|
102
175
|
const t = this.thoughtHistory[i];
|
|
103
|
-
// 1. Update total thoughts for everyone
|
|
104
176
|
t.totalThoughts = newTotal;
|
|
105
|
-
// 2. Renumber thoughts that came after the summarized range
|
|
106
177
|
if (i >= startIndex) {
|
|
107
178
|
t.thoughtNumber -= shiftAmount;
|
|
108
179
|
}
|
|
109
|
-
// 3. Update references for all thoughts
|
|
110
180
|
if (t.branchFromThought) {
|
|
111
181
|
if (t.branchFromThought > endIndex) {
|
|
112
182
|
t.branchFromThought -= shiftAmount;
|
|
113
183
|
}
|
|
114
184
|
else if (t.branchFromThought >= startIndex) {
|
|
115
|
-
t.branchFromThought = startIndex;
|
|
185
|
+
t.branchFromThought = startIndex;
|
|
116
186
|
}
|
|
117
187
|
}
|
|
118
188
|
if (t.revisesThought) {
|
|
@@ -120,20 +190,12 @@ export class SequentialThinkingServer {
|
|
|
120
190
|
t.revisesThought -= shiftAmount;
|
|
121
191
|
}
|
|
122
192
|
else if (t.revisesThought >= startIndex) {
|
|
123
|
-
t.revisesThought = startIndex;
|
|
193
|
+
t.revisesThought = startIndex;
|
|
124
194
|
}
|
|
125
195
|
}
|
|
126
196
|
}
|
|
127
|
-
|
|
128
|
-
this.
|
|
129
|
-
this.thoughtHistory.forEach(t => {
|
|
130
|
-
if (t.branchFromThought && t.branchId) {
|
|
131
|
-
const branchKey = `${t.branchFromThought}-${t.branchId}`;
|
|
132
|
-
if (!this.branches[branchKey])
|
|
133
|
-
this.branches[branchKey] = [];
|
|
134
|
-
this.branches[branchKey].push(t);
|
|
135
|
-
}
|
|
136
|
-
});
|
|
197
|
+
this.rebuildBranches();
|
|
198
|
+
this.rebuildBlocks();
|
|
137
199
|
await this.saveHistory();
|
|
138
200
|
return {
|
|
139
201
|
newHistoryLength: this.thoughtHistory.length,
|
|
@@ -144,7 +206,9 @@ export class SequentialThinkingServer {
|
|
|
144
206
|
const lowerQuery = query.toLowerCase();
|
|
145
207
|
return this.thoughtHistory.filter(t => t.thought.toLowerCase().includes(lowerQuery) ||
|
|
146
208
|
(t.thoughtType && t.thoughtType.toLowerCase().includes(lowerQuery)) ||
|
|
147
|
-
(t.branchId && t.branchId.toLowerCase().includes(lowerQuery))
|
|
209
|
+
(t.branchId && t.branchId.toLowerCase().includes(lowerQuery)) ||
|
|
210
|
+
(t.blockId && t.blockId.toLowerCase().includes(lowerQuery)) ||
|
|
211
|
+
(t.relatedToolCall && t.relatedToolCall.toLowerCase().includes(lowerQuery)));
|
|
148
212
|
}
|
|
149
213
|
addToMemory(input) {
|
|
150
214
|
if (input.thoughtNumber > input.totalThoughts) {
|
|
@@ -158,81 +222,190 @@ export class SequentialThinkingServer {
|
|
|
158
222
|
}
|
|
159
223
|
this.branches[branchKey].push(input);
|
|
160
224
|
}
|
|
225
|
+
// Update block
|
|
226
|
+
if (input.blockId) {
|
|
227
|
+
let block = this.blocks.find(b => b.id === input.blockId);
|
|
228
|
+
if (!block) {
|
|
229
|
+
block = {
|
|
230
|
+
id: input.blockId,
|
|
231
|
+
topic: input.thought.substring(0, 50),
|
|
232
|
+
status: 'active',
|
|
233
|
+
thoughts: [],
|
|
234
|
+
createdAt: new Date().toISOString(),
|
|
235
|
+
updatedAt: new Date().toISOString()
|
|
236
|
+
};
|
|
237
|
+
this.blocks.push(block);
|
|
238
|
+
}
|
|
239
|
+
block.thoughts.push(input);
|
|
240
|
+
block.updatedAt = new Date().toISOString();
|
|
241
|
+
}
|
|
161
242
|
}
|
|
162
243
|
formatThought(thoughtData) {
|
|
163
|
-
const { thoughtNumber, totalThoughts, thought, isRevision, revisesThought, branchFromThought, branchId, thoughtType, score, options, selectedOption } = thoughtData;
|
|
244
|
+
const { thoughtNumber, totalThoughts, thought, isRevision, revisesThought, branchFromThought, branchId, thoughtType, score, options, selectedOption, blockId, relatedToolCall } = thoughtData;
|
|
245
|
+
const typeEmoji = {
|
|
246
|
+
'analysis': '🔍',
|
|
247
|
+
'planning': '📋',
|
|
248
|
+
'execution': '⚡',
|
|
249
|
+
'observation': '👁️',
|
|
250
|
+
'hypothesis': '💡',
|
|
251
|
+
'reflexion': '🔄',
|
|
252
|
+
'solution': '✅',
|
|
253
|
+
'generation': '💭',
|
|
254
|
+
'evaluation': '⚖️',
|
|
255
|
+
'selection': '🎯'
|
|
256
|
+
};
|
|
164
257
|
let prefix = '';
|
|
165
258
|
let context = '';
|
|
166
|
-
|
|
167
|
-
|
|
259
|
+
const type = thoughtType || 'analysis';
|
|
260
|
+
const emoji = typeEmoji[type] || '💭';
|
|
261
|
+
if (type === 'reflexion' || isRevision) {
|
|
262
|
+
prefix = chalk.yellow(`${emoji} Reflexion`);
|
|
168
263
|
if (revisesThought)
|
|
169
|
-
context += ` (revising
|
|
264
|
+
context += ` (revising #${revisesThought})`;
|
|
265
|
+
}
|
|
266
|
+
else if (type === 'execution') {
|
|
267
|
+
prefix = chalk.magenta(`${emoji} Execution`);
|
|
268
|
+
if (relatedToolCall)
|
|
269
|
+
context += ` [Tool: ${relatedToolCall}]`;
|
|
170
270
|
}
|
|
171
|
-
else if (
|
|
172
|
-
prefix = chalk.
|
|
271
|
+
else if (type === 'observation') {
|
|
272
|
+
prefix = chalk.cyan(`${emoji} Observation`);
|
|
173
273
|
}
|
|
174
|
-
else if (
|
|
175
|
-
prefix = chalk.
|
|
176
|
-
if (score)
|
|
177
|
-
context += ` (Score: ${score})`;
|
|
274
|
+
else if (type === 'planning') {
|
|
275
|
+
prefix = chalk.blue(`${emoji} Planning`);
|
|
178
276
|
}
|
|
179
|
-
else if (
|
|
180
|
-
prefix = chalk.green(
|
|
181
|
-
if (selectedOption)
|
|
182
|
-
context += ` (Selected: ${selectedOption})`;
|
|
277
|
+
else if (type === 'solution') {
|
|
278
|
+
prefix = chalk.green(`${emoji} Solution`);
|
|
183
279
|
}
|
|
184
280
|
else if (branchFromThought) {
|
|
185
|
-
prefix = chalk.green(
|
|
186
|
-
context = ` (from
|
|
281
|
+
prefix = chalk.green(`🌿 Branch`);
|
|
282
|
+
context = ` (from #${branchFromThought}, ID: ${branchId})`;
|
|
187
283
|
}
|
|
188
284
|
else {
|
|
189
|
-
prefix = chalk.blue(
|
|
190
|
-
context = '';
|
|
285
|
+
prefix = chalk.blue(`${emoji} ${type.charAt(0).toUpperCase() + type.slice(1)}`);
|
|
191
286
|
}
|
|
287
|
+
if (blockId && blockId !== 'default' && blockId !== 'legacy-default') {
|
|
288
|
+
context += ` [Block: ${blockId}]`;
|
|
289
|
+
}
|
|
290
|
+
if (score)
|
|
291
|
+
context += ` (Score: ${score})`;
|
|
292
|
+
if (selectedOption)
|
|
293
|
+
context += ` (Selected: ${selectedOption})`;
|
|
192
294
|
const header = `${prefix} ${thoughtNumber}/${totalThoughts}${context}`;
|
|
193
|
-
const borderLength = Math.max(header.length, thought.length) + 4;
|
|
295
|
+
const borderLength = Math.max(header.length, Math.min(thought.length, 80)) + 4;
|
|
194
296
|
const border = '─'.repeat(borderLength);
|
|
195
297
|
let extraContent = '';
|
|
196
298
|
if (options && options.length > 0) {
|
|
197
|
-
extraContent += `
|
|
198
|
-
│ Options:
|
|
199
|
-
` + options.map(o => `│ - ${o}`).join('\n');
|
|
299
|
+
extraContent += `\n│ Options:\n` + options.map(o => `│ - ${o}`).join('\n');
|
|
200
300
|
}
|
|
301
|
+
// Wrap long thoughts
|
|
302
|
+
const wrappedThought = thought.length > 76
|
|
303
|
+
? thought.match(/.{1,76}/g)?.map(line => `│ ${line.padEnd(borderLength - 2)} │`).join('\n') || thought
|
|
304
|
+
: `│ ${thought.padEnd(borderLength - 2)} │`;
|
|
201
305
|
return `
|
|
202
306
|
┌${border}┐
|
|
203
|
-
│ ${header} │
|
|
307
|
+
│ ${header.padEnd(borderLength - 2)} │
|
|
204
308
|
├${border}┤
|
|
205
|
-
│ ${thought.padEnd(borderLength - 2)}
|
|
309
|
+
${typeof wrappedThought === 'string' && wrappedThought.startsWith('│') ? wrappedThought : `│ ${thought.padEnd(borderLength - 2)} │`}${extraContent}
|
|
206
310
|
└${border}┘`;
|
|
207
311
|
}
|
|
312
|
+
// --- 2. The New Brain: Process Thought with Interleaved Logic (Opus 4.5 Style) ---
|
|
208
313
|
async processThought(input) {
|
|
209
314
|
try {
|
|
210
315
|
if (this.delayMs > 0) {
|
|
211
316
|
await new Promise(resolve => setTimeout(resolve, this.delayMs));
|
|
212
317
|
}
|
|
213
|
-
//
|
|
318
|
+
// A. Block Management (Auto-Create Block)
|
|
319
|
+
if (!input.blockId) {
|
|
320
|
+
if (!this.currentBlockId) {
|
|
321
|
+
this.currentBlockId = `block-${Date.now()}`;
|
|
322
|
+
this.blocks.push({
|
|
323
|
+
id: this.currentBlockId,
|
|
324
|
+
topic: input.thought.substring(0, 50),
|
|
325
|
+
status: 'active',
|
|
326
|
+
thoughts: [],
|
|
327
|
+
createdAt: new Date().toISOString(),
|
|
328
|
+
updatedAt: new Date().toISOString()
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
input.blockId = this.currentBlockId;
|
|
332
|
+
}
|
|
333
|
+
else if (input.blockId !== this.currentBlockId) {
|
|
334
|
+
// Context switch detected
|
|
335
|
+
const oldBlock = this.blocks.find(b => b.id === this.currentBlockId);
|
|
336
|
+
if (oldBlock) {
|
|
337
|
+
oldBlock.status = 'completed';
|
|
338
|
+
}
|
|
339
|
+
this.currentBlockId = input.blockId;
|
|
340
|
+
const existingBlock = this.blocks.find(b => b.id === input.blockId);
|
|
341
|
+
if (!existingBlock) {
|
|
342
|
+
this.blocks.push({
|
|
343
|
+
id: input.blockId,
|
|
344
|
+
topic: input.thought.substring(0, 50),
|
|
345
|
+
status: 'active',
|
|
346
|
+
thoughts: [],
|
|
347
|
+
createdAt: new Date().toISOString(),
|
|
348
|
+
updatedAt: new Date().toISOString()
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
else {
|
|
352
|
+
existingBlock.status = 'active';
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
// B. Smart Loop Detection (Opus Style)
|
|
214
356
|
const warnings = [];
|
|
215
|
-
|
|
216
|
-
const
|
|
217
|
-
|
|
218
|
-
|
|
357
|
+
const blockThoughts = this.thoughtHistory.filter(t => t.blockId === input.blockId);
|
|
358
|
+
const recentInBlock = blockThoughts.slice(-3);
|
|
359
|
+
const lastThought = this.thoughtHistory[this.thoughtHistory.length - 1];
|
|
360
|
+
// Rule 1: Stalling Detection (Thinking too much without Action)
|
|
361
|
+
const thinkingTypes = ['analysis', 'planning', 'hypothesis', 'generation'];
|
|
362
|
+
if (recentInBlock.length >= 3 &&
|
|
363
|
+
recentInBlock.every(t => thinkingTypes.includes(t.thoughtType || 'analysis')) &&
|
|
364
|
+
thinkingTypes.includes(input.thoughtType || 'analysis')) {
|
|
365
|
+
warnings.push(`⚠️ STALLING DETECTED: You have been thinking for 4 consecutive steps without execution. Consider changing thoughtType to 'execution' and calling a tool to make progress.`);
|
|
366
|
+
}
|
|
367
|
+
// Rule 2: Repeating Action Detection (Insanity Check)
|
|
368
|
+
if (input.thoughtType === 'execution' &&
|
|
369
|
+
recentInBlock.some(t => t.thoughtType === 'execution' && t.thought === input.thought)) {
|
|
370
|
+
warnings.push(`🛑 LOOP DETECTED: You are attempting to execute the exact same action again. You MUST change your strategy or create a branch with a different approach.`);
|
|
371
|
+
}
|
|
372
|
+
// Rule 3: Missing Observation (Interleaved Enforcer)
|
|
373
|
+
if (lastThought &&
|
|
374
|
+
lastThought.thoughtType === 'execution' &&
|
|
375
|
+
input.thoughtType !== 'observation') {
|
|
376
|
+
warnings.push(`💡 INTERLEAVED HINT: Your last step was 'execution'. The next step SHOULD be 'observation' to record and analyze the tool result before continuing.`);
|
|
219
377
|
}
|
|
220
|
-
//
|
|
378
|
+
// Rule 4: Skipping Planning (Rushing Detection)
|
|
379
|
+
const hasPlanning = blockThoughts.some(t => t.thoughtType === 'planning');
|
|
380
|
+
if (!hasPlanning &&
|
|
381
|
+
blockThoughts.length >= 2 &&
|
|
382
|
+
input.thoughtType === 'execution') {
|
|
383
|
+
warnings.push(`📋 PLANNING HINT: You're about to execute without a planning step. Consider adding a 'planning' thought first to outline your approach.`);
|
|
384
|
+
}
|
|
385
|
+
// Rule 5: Ending without Verification
|
|
221
386
|
if (input.nextThoughtNeeded === false) {
|
|
222
387
|
const hasVerification = this.thoughtHistory.some(t => t.thought.toLowerCase().includes('test') ||
|
|
223
388
|
t.thought.toLowerCase().includes('verify') ||
|
|
224
|
-
t.thought.toLowerCase().includes('check')
|
|
389
|
+
t.thought.toLowerCase().includes('check') ||
|
|
390
|
+
t.thoughtType === 'observation');
|
|
225
391
|
if (!hasVerification) {
|
|
226
|
-
warnings.push(`🚨 CRITICAL: You are ending
|
|
392
|
+
warnings.push(`🚨 CRITICAL: You are ending without explicit verification/testing. Consider adding an 'observation' or verification step.`);
|
|
227
393
|
}
|
|
228
394
|
}
|
|
395
|
+
// Rule 6: Same type loop (general)
|
|
396
|
+
if (recentInBlock.length >= 3 &&
|
|
397
|
+
recentInBlock.every(t => t.thoughtType === input.thoughtType)) {
|
|
398
|
+
warnings.push(`⚠️ TYPE LOOP: You've used '${input.thoughtType}' for 4 consecutive steps. Consider using a different thought type to progress.`);
|
|
399
|
+
}
|
|
400
|
+
// C. Update State
|
|
229
401
|
this.addToMemory(input);
|
|
230
402
|
await this.saveHistory();
|
|
231
403
|
if (!this.disableThoughtLogging) {
|
|
232
404
|
const formattedThought = this.formatThought(input);
|
|
233
405
|
console.error(formattedThought);
|
|
234
406
|
}
|
|
235
|
-
//
|
|
407
|
+
// D. Generate Contextual Output
|
|
408
|
+
const currentBlock = this.blocks.find(b => b.id === input.blockId);
|
|
236
409
|
const mermaid = this.generateMermaid();
|
|
237
410
|
return {
|
|
238
411
|
content: [{
|
|
@@ -241,9 +414,14 @@ export class SequentialThinkingServer {
|
|
|
241
414
|
thoughtNumber: input.thoughtNumber,
|
|
242
415
|
totalThoughts: input.totalThoughts,
|
|
243
416
|
nextThoughtNeeded: input.nextThoughtNeeded,
|
|
417
|
+
currentBlock: input.blockId,
|
|
418
|
+
blockContext: currentBlock
|
|
419
|
+
? `Block '${currentBlock.topic.substring(0, 30)}' has ${currentBlock.thoughts.length} thoughts.`
|
|
420
|
+
: 'No active block',
|
|
244
421
|
branches: Object.keys(this.branches),
|
|
245
422
|
thoughtHistoryLength: this.thoughtHistory.length,
|
|
246
|
-
feedback: warnings.length > 0 ? warnings : "
|
|
423
|
+
feedback: warnings.length > 0 ? warnings : "Flow Healthy ✅",
|
|
424
|
+
interleavedState: this.getInterleavedState(input),
|
|
247
425
|
visualization: mermaid
|
|
248
426
|
}, null, 2)
|
|
249
427
|
}]
|
|
@@ -262,22 +440,84 @@ export class SequentialThinkingServer {
|
|
|
262
440
|
};
|
|
263
441
|
}
|
|
264
442
|
}
|
|
443
|
+
getInterleavedState(input) {
|
|
444
|
+
const lastThought = this.thoughtHistory[this.thoughtHistory.length - 2]; // -2 because current is already added
|
|
445
|
+
if (!lastThought)
|
|
446
|
+
return 'START';
|
|
447
|
+
if (lastThought.thoughtType === 'execution') {
|
|
448
|
+
return input.thoughtType === 'observation'
|
|
449
|
+
? 'INTERLEAVED_CORRECT'
|
|
450
|
+
: 'INTERLEAVED_EXPECTED_OBSERVATION';
|
|
451
|
+
}
|
|
452
|
+
if (lastThought.thoughtType === 'observation') {
|
|
453
|
+
return 'POST_OBSERVATION';
|
|
454
|
+
}
|
|
455
|
+
return 'THINKING';
|
|
456
|
+
}
|
|
265
457
|
generateMermaid() {
|
|
266
458
|
let diagram = 'graph TD\n';
|
|
267
|
-
this.thoughtHistory.
|
|
459
|
+
const recentThoughts = this.thoughtHistory.slice(-15); // Last 15 for readability
|
|
460
|
+
recentThoughts.forEach((t, i) => {
|
|
268
461
|
const id = `T${t.thoughtNumber}`;
|
|
269
|
-
const
|
|
462
|
+
const typeIcon = {
|
|
463
|
+
'analysis': '🔍',
|
|
464
|
+
'planning': '📋',
|
|
465
|
+
'execution': '⚡',
|
|
466
|
+
'observation': '👁️',
|
|
467
|
+
'hypothesis': '💡',
|
|
468
|
+
'reflexion': '🔄',
|
|
469
|
+
'solution': '✅',
|
|
470
|
+
'generation': '💭',
|
|
471
|
+
'evaluation': '⚖️',
|
|
472
|
+
'selection': '🎯'
|
|
473
|
+
}[t.thoughtType || 'analysis'] || '💭';
|
|
474
|
+
const label = `${typeIcon} ${t.thoughtNumber}`;
|
|
270
475
|
diagram += ` ${id}("${label}")\n`;
|
|
271
476
|
if (i > 0) {
|
|
272
|
-
const
|
|
477
|
+
const prevT = recentThoughts[i - 1];
|
|
478
|
+
const prevId = `T${prevT.thoughtNumber}`;
|
|
273
479
|
if (t.branchFromThought) {
|
|
274
480
|
diagram += ` T${t.branchFromThought} -->|branch: ${t.branchId}| ${id}\n`;
|
|
275
481
|
}
|
|
276
482
|
else {
|
|
277
|
-
|
|
483
|
+
const edgeLabel = t.thoughtType === 'observation' ? '|result|' : '';
|
|
484
|
+
diagram += ` ${prevId} -->${edgeLabel} ${id}\n`;
|
|
278
485
|
}
|
|
279
486
|
}
|
|
280
487
|
});
|
|
281
488
|
return diagram;
|
|
282
489
|
}
|
|
490
|
+
// Public method to start a new block explicitly
|
|
491
|
+
startNewBlock(blockId, topic) {
|
|
492
|
+
if (this.currentBlockId) {
|
|
493
|
+
const oldBlock = this.blocks.find(b => b.id === this.currentBlockId);
|
|
494
|
+
if (oldBlock) {
|
|
495
|
+
oldBlock.status = 'completed';
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
this.currentBlockId = blockId;
|
|
499
|
+
this.blocks.push({
|
|
500
|
+
id: blockId,
|
|
501
|
+
topic: topic,
|
|
502
|
+
status: 'active',
|
|
503
|
+
thoughts: [],
|
|
504
|
+
createdAt: new Date().toISOString(),
|
|
505
|
+
updatedAt: new Date().toISOString()
|
|
506
|
+
});
|
|
507
|
+
}
|
|
508
|
+
// Get current block info
|
|
509
|
+
getCurrentBlock() {
|
|
510
|
+
if (!this.currentBlockId)
|
|
511
|
+
return null;
|
|
512
|
+
return this.blocks.find(b => b.id === this.currentBlockId) || null;
|
|
513
|
+
}
|
|
514
|
+
// Get all blocks summary
|
|
515
|
+
getBlocksSummary() {
|
|
516
|
+
return this.blocks.map(b => ({
|
|
517
|
+
id: b.id,
|
|
518
|
+
topic: b.topic,
|
|
519
|
+
status: b.status,
|
|
520
|
+
thoughtCount: b.thoughts.length
|
|
521
|
+
}));
|
|
522
|
+
}
|
|
283
523
|
}
|
package/dist/notes.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export type Priority = 'low' | 'medium' | 'high' | 'critical';
|
|
2
|
+
export interface Note {
|
|
3
|
+
id: string;
|
|
4
|
+
title: string;
|
|
5
|
+
content: string;
|
|
6
|
+
tags: string[];
|
|
7
|
+
priority: Priority;
|
|
8
|
+
expiresAt?: string;
|
|
9
|
+
createdAt: string;
|
|
10
|
+
updatedAt: string;
|
|
11
|
+
}
|
|
12
|
+
export declare class NotesManager {
|
|
13
|
+
private filePath;
|
|
14
|
+
private notes;
|
|
15
|
+
private loaded;
|
|
16
|
+
private mutex;
|
|
17
|
+
constructor(storagePath?: string);
|
|
18
|
+
private load;
|
|
19
|
+
private save;
|
|
20
|
+
addNote(title: string, content: string, tags?: string[], priority?: Priority, expiresAt?: string): Promise<Note>;
|
|
21
|
+
listNotes(tag?: string, includeExpired?: boolean): Promise<Note[]>;
|
|
22
|
+
searchNotes(query: string): Promise<Note[]>;
|
|
23
|
+
deleteNote(id: string): Promise<boolean>;
|
|
24
|
+
updateNote(id: string, updates: Partial<Pick<Note, 'title' | 'content' | 'tags'>>): Promise<Note | null>;
|
|
25
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/tools/coding.js
CHANGED
|
@@ -31,9 +31,46 @@ export function registerCodingTools(server, graph) {
|
|
|
31
31
|
doc += `- ${d.path} (Symbols: ${d.symbols.join(', ')})\n`;
|
|
32
32
|
});
|
|
33
33
|
doc += `\nDEPENDENTS (Files that use this file):\n`;
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
34
|
+
// Helper to find usages (defined inline to capture context/scope)
|
|
35
|
+
const findUsages = async (fileToSearch, targetPath, symbols) => {
|
|
36
|
+
try {
|
|
37
|
+
const content = await fs.readFile(fileToSearch, 'utf-8');
|
|
38
|
+
const lines = content.split('\n');
|
|
39
|
+
const matches = [];
|
|
40
|
+
const baseName = path.basename(targetPath, path.extname(targetPath));
|
|
41
|
+
// Escape regex special chars
|
|
42
|
+
const escapeRegExp = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
43
|
+
const symbolRegexes = symbols.map(s => new RegExp(`\\b${escapeRegExp(s)}\\b`));
|
|
44
|
+
lines.forEach((line, index) => {
|
|
45
|
+
// Check for import of the file itself (simple heuristic)
|
|
46
|
+
if ((line.includes('import') || line.includes('require')) && line.includes(baseName)) {
|
|
47
|
+
matches.push(`Line ${index + 1}: ${line.trim()}`);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
// Check for symbols
|
|
51
|
+
for (const regex of symbolRegexes) {
|
|
52
|
+
if (regex.test(line)) {
|
|
53
|
+
matches.push(`Line ${index + 1}: ${line.trim()}`);
|
|
54
|
+
break; // One match per line is enough
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
return matches;
|
|
59
|
+
}
|
|
60
|
+
catch (e) {
|
|
61
|
+
return [];
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
for (const d of context.dependents) {
|
|
65
|
+
doc += `- ${d.path}\n`;
|
|
66
|
+
const usages = await findUsages(d.path, filePath, context.targetFile.symbols);
|
|
67
|
+
if (usages.length > 0) {
|
|
68
|
+
// Limit to top 3 to save context
|
|
69
|
+
usages.slice(0, 3).forEach(u => doc += ` ${u}\n`);
|
|
70
|
+
if (usages.length > 3)
|
|
71
|
+
doc += ` ... (${usages.length - 3} more references)\n`;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
37
74
|
doc += `\n--- END OF CONTEXT ---`;
|
|
38
75
|
return {
|
|
39
76
|
content: [{ type: "text", text: doc }]
|