@j0hanz/thinkseq-mcp 2.0.0 → 2.1.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/README.md +160 -237
- package/dist/app.js +18 -2
- package/dist/appConfig/env.d.ts +3 -0
- package/dist/appConfig/env.js +9 -0
- package/dist/appConfig/runDependencies.d.ts +4 -4
- package/dist/appConfig/runDependencies.js +21 -14
- package/dist/assets/logo.svg +4029 -0
- package/dist/engine/revision.js +14 -26
- package/dist/engine/thoughtQueries.js +20 -13
- package/dist/engine/thoughtStore.js +107 -102
- package/dist/engine.js +85 -61
- package/dist/index.js +1 -0
- package/dist/instructions.md +11 -16
- package/dist/lib/types.d.ts +2 -0
- package/dist/tools/thinkseq.d.ts +1 -1
- package/dist/tools/thinkseq.js +13 -11
- package/package.json +19 -14
- package/dist/tsconfig.tsbuildinfo +0 -1
package/dist/engine/revision.js
CHANGED
|
@@ -1,35 +1,23 @@
|
|
|
1
|
-
function missingRevisionError() {
|
|
2
|
-
return {
|
|
3
|
-
ok: false,
|
|
4
|
-
result: buildRevisionError('E_REVISION_MISSING', 'revisesThought is required for revision'),
|
|
5
|
-
};
|
|
6
|
-
}
|
|
7
|
-
function targetNotFoundError(targetNumber) {
|
|
8
|
-
return {
|
|
9
|
-
ok: false,
|
|
10
|
-
result: buildRevisionError('E_REVISION_TARGET_NOT_FOUND', `Thought ${targetNumber} not found`),
|
|
11
|
-
};
|
|
12
|
-
}
|
|
13
|
-
function targetSupersededError(targetNumber) {
|
|
14
|
-
return {
|
|
15
|
-
ok: false,
|
|
16
|
-
result: buildRevisionError('E_REVISION_TARGET_SUPERSEDED', `Thought ${targetNumber} was already superseded`),
|
|
17
|
-
};
|
|
18
|
-
}
|
|
19
1
|
export function resolveRevisionTarget(input, getThoughtByNumber) {
|
|
20
2
|
const targetNumber = input.revisesThought;
|
|
21
|
-
if (targetNumber === undefined)
|
|
22
|
-
return
|
|
3
|
+
if (targetNumber === undefined) {
|
|
4
|
+
return error('E_REVISION_MISSING', 'revisesThought is required for revision');
|
|
5
|
+
}
|
|
23
6
|
const target = getThoughtByNumber(targetNumber);
|
|
24
|
-
if (!target)
|
|
25
|
-
return
|
|
26
|
-
|
|
27
|
-
|
|
7
|
+
if (!target) {
|
|
8
|
+
return error('E_REVISION_TARGET_NOT_FOUND', `Thought ${targetNumber} not found`);
|
|
9
|
+
}
|
|
10
|
+
if (!target.isActive) {
|
|
11
|
+
return error('E_REVISION_TARGET_SUPERSEDED', `Thought ${targetNumber} was already superseded`);
|
|
12
|
+
}
|
|
28
13
|
return { ok: true, targetNumber, target };
|
|
29
14
|
}
|
|
30
|
-
function
|
|
15
|
+
function error(code, message) {
|
|
31
16
|
return {
|
|
32
17
|
ok: false,
|
|
33
|
-
|
|
18
|
+
result: {
|
|
19
|
+
ok: false,
|
|
20
|
+
error: { code, message },
|
|
21
|
+
},
|
|
34
22
|
};
|
|
35
23
|
}
|
|
@@ -5,6 +5,7 @@ function selectRecentThoughts(activeThoughts) {
|
|
|
5
5
|
const len = activeThoughts.length;
|
|
6
6
|
const recent = [];
|
|
7
7
|
const stepIndexes = [];
|
|
8
|
+
// If we have lots of thoughts, keep the first as an anchor.
|
|
8
9
|
if (len > MAX_RECENT_THOUGHTS) {
|
|
9
10
|
const anchor = activeThoughts[0];
|
|
10
11
|
if (!anchor)
|
|
@@ -12,21 +13,30 @@ function selectRecentThoughts(activeThoughts) {
|
|
|
12
13
|
recent.push(anchor);
|
|
13
14
|
stepIndexes.push(1);
|
|
14
15
|
}
|
|
15
|
-
const
|
|
16
|
-
for (let i =
|
|
16
|
+
const startIndex = len <= MAX_RECENT_THOUGHTS ? 0 : Math.max(0, len - RECENT_TAIL_COUNT);
|
|
17
|
+
for (let i = startIndex; i < len; i += 1) {
|
|
17
18
|
const thought = activeThoughts[i];
|
|
18
|
-
if (thought)
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
if (!thought)
|
|
20
|
+
continue;
|
|
21
|
+
recent.push(thought);
|
|
22
|
+
stepIndexes.push(i + 1);
|
|
22
23
|
}
|
|
23
24
|
return { recent, stepIndexes };
|
|
24
25
|
}
|
|
25
26
|
function truncatePreview(input, maxChars) {
|
|
26
|
-
|
|
27
|
-
|
|
27
|
+
// Preserve codepoint boundaries (emoji-safe) without depending on Intl.Segmenter.
|
|
28
|
+
const iterator = input[Symbol.iterator]();
|
|
29
|
+
const preview = [];
|
|
30
|
+
for (let i = 0; i < maxChars; i += 1) {
|
|
31
|
+
const next = iterator.next();
|
|
32
|
+
if (next.done)
|
|
33
|
+
return input;
|
|
34
|
+
preview.push(next.value);
|
|
35
|
+
}
|
|
36
|
+
const extra = iterator.next();
|
|
37
|
+
if (extra.done)
|
|
28
38
|
return input;
|
|
29
|
-
return `${
|
|
39
|
+
return `${preview.join('')}...`;
|
|
30
40
|
}
|
|
31
41
|
export function buildContextSummary(activeThoughts, revisionInfo) {
|
|
32
42
|
const { recent, stepIndexes } = selectRecentThoughts(activeThoughts);
|
|
@@ -35,8 +45,5 @@ export function buildContextSummary(activeThoughts, revisionInfo) {
|
|
|
35
45
|
number: thought.thoughtNumber,
|
|
36
46
|
preview: truncatePreview(thought.thought, MAX_PREVIEW_CHARS),
|
|
37
47
|
}));
|
|
38
|
-
|
|
39
|
-
return { recentThoughts, revisionInfo };
|
|
40
|
-
}
|
|
41
|
-
return { recentThoughts };
|
|
48
|
+
return revisionInfo ? { recentThoughts, revisionInfo } : { recentThoughts };
|
|
42
49
|
}
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { COMPACT_RATIO, COMPACT_THRESHOLD } from '../engineConfig.js';
|
|
2
2
|
export class ThoughtStore {
|
|
3
|
-
#
|
|
4
|
-
#
|
|
3
|
+
#allThoughts = [];
|
|
4
|
+
#byNumber = new Map();
|
|
5
5
|
#activeThoughts = [];
|
|
6
|
-
#
|
|
6
|
+
#activeNumbers = [];
|
|
7
7
|
#activeMaxTotalThoughts = 0;
|
|
8
|
-
#
|
|
8
|
+
#startIndex = 0;
|
|
9
9
|
#nextThoughtNumber = 1;
|
|
10
|
-
#
|
|
10
|
+
#approxBytes = 0;
|
|
11
11
|
#lastPruneStats = null;
|
|
12
12
|
#maxThoughts;
|
|
13
13
|
#maxMemoryBytes;
|
|
@@ -27,57 +27,15 @@ export class ThoughtStore {
|
|
|
27
27
|
return this.#lastPruneStats;
|
|
28
28
|
}
|
|
29
29
|
storeThought(stored) {
|
|
30
|
-
this.#
|
|
31
|
-
this.#
|
|
30
|
+
this.#allThoughts.push(stored);
|
|
31
|
+
this.#byNumber.set(stored.thoughtNumber, stored);
|
|
32
32
|
if (stored.isActive) {
|
|
33
|
-
this.#
|
|
34
|
-
this.#activeThoughtNumbers.push(stored.thoughtNumber);
|
|
35
|
-
this.#activeMaxTotalThoughts = Math.max(this.#activeMaxTotalThoughts, stored.totalThoughts);
|
|
33
|
+
this.#appendActive(stored);
|
|
36
34
|
}
|
|
37
|
-
this.#
|
|
38
|
-
}
|
|
39
|
-
#recomputeActiveMaxTotalThoughts() {
|
|
40
|
-
let maxTotal = 0;
|
|
41
|
-
for (const thought of this.#activeThoughts) {
|
|
42
|
-
maxTotal = Math.max(maxTotal, thought.totalThoughts);
|
|
43
|
-
}
|
|
44
|
-
this.#activeMaxTotalThoughts = maxTotal;
|
|
45
|
-
}
|
|
46
|
-
#findActiveLowerBound(thoughtNumber) {
|
|
47
|
-
const activeThoughtNumbers = this.#activeThoughtNumbers;
|
|
48
|
-
let low = 0;
|
|
49
|
-
let high = activeThoughtNumbers.length;
|
|
50
|
-
while (low < high) {
|
|
51
|
-
const mid = (low + high) >>> 1;
|
|
52
|
-
const midValue = activeThoughtNumbers[mid];
|
|
53
|
-
if (midValue === undefined)
|
|
54
|
-
return -1;
|
|
55
|
-
if (midValue < thoughtNumber)
|
|
56
|
-
low = mid + 1;
|
|
57
|
-
else
|
|
58
|
-
high = mid;
|
|
59
|
-
}
|
|
60
|
-
return low;
|
|
61
|
-
}
|
|
62
|
-
#findActiveThoughtIndex(thoughtNumber) {
|
|
63
|
-
const index = this.#findActiveLowerBound(thoughtNumber);
|
|
64
|
-
if (index < 0)
|
|
65
|
-
return -1;
|
|
66
|
-
if (index < this.#activeThoughtNumbers.length &&
|
|
67
|
-
this.#activeThoughtNumbers[index] === thoughtNumber)
|
|
68
|
-
return index;
|
|
69
|
-
return -1;
|
|
70
|
-
}
|
|
71
|
-
#findFirstActiveIndexAfter(thoughtNumber) {
|
|
72
|
-
const index = this.#findActiveLowerBound(thoughtNumber);
|
|
73
|
-
if (index < 0)
|
|
74
|
-
return 0;
|
|
75
|
-
if (this.#activeThoughtNumbers[index] === thoughtNumber)
|
|
76
|
-
return index + 1;
|
|
77
|
-
return index;
|
|
35
|
+
this.#approxBytes += this.#estimateThoughtBytes(stored);
|
|
78
36
|
}
|
|
79
37
|
supersedeFrom(targetNumber, supersededBy, maxSupersedes) {
|
|
80
|
-
const startIndex = this.#
|
|
38
|
+
const startIndex = this.#findActiveIndex(targetNumber);
|
|
81
39
|
if (startIndex < 0)
|
|
82
40
|
return { supersedes: [], supersedesTotal: 0 };
|
|
83
41
|
const supersedes = [];
|
|
@@ -86,17 +44,15 @@ export class ThoughtStore {
|
|
|
86
44
|
const thought = this.#activeThoughts[i];
|
|
87
45
|
if (!thought)
|
|
88
46
|
continue;
|
|
89
|
-
|
|
90
|
-
thought.isActive = false;
|
|
91
|
-
thought.supersededBy = supersededBy;
|
|
92
|
-
}
|
|
47
|
+
this.#markSuperseded(thought, supersededBy);
|
|
93
48
|
supersedesTotal += 1;
|
|
94
49
|
if (maxSupersedes === undefined || supersedes.length < maxSupersedes) {
|
|
95
50
|
supersedes.push(thought.thoughtNumber);
|
|
96
51
|
}
|
|
97
52
|
}
|
|
53
|
+
// Drop superseded suffix from active arrays.
|
|
98
54
|
this.#activeThoughts.length = startIndex;
|
|
99
|
-
this.#
|
|
55
|
+
this.#activeNumbers.length = startIndex;
|
|
100
56
|
this.#recomputeActiveMaxTotalThoughts();
|
|
101
57
|
return { supersedes, supersedesTotal };
|
|
102
58
|
}
|
|
@@ -104,21 +60,19 @@ export class ThoughtStore {
|
|
|
104
60
|
return this.#activeThoughts;
|
|
105
61
|
}
|
|
106
62
|
getActiveThoughtNumbers(max) {
|
|
107
|
-
if (max === undefined)
|
|
108
|
-
return this.#
|
|
109
|
-
}
|
|
63
|
+
if (max === undefined)
|
|
64
|
+
return this.#activeNumbers.slice();
|
|
110
65
|
if (max <= 0)
|
|
111
66
|
return [];
|
|
112
|
-
if (this.#
|
|
113
|
-
return this.#
|
|
114
|
-
|
|
115
|
-
return this.#activeThoughtNumbers.slice(-max);
|
|
67
|
+
if (this.#activeNumbers.length <= max)
|
|
68
|
+
return this.#activeNumbers.slice();
|
|
69
|
+
return this.#activeNumbers.slice(-max);
|
|
116
70
|
}
|
|
117
71
|
getThoughtByNumber(thoughtNumber) {
|
|
118
|
-
return this.#
|
|
72
|
+
return this.#byNumber.get(thoughtNumber);
|
|
119
73
|
}
|
|
120
74
|
getTotalLength() {
|
|
121
|
-
return this.#
|
|
75
|
+
return this.#allThoughts.length - this.#startIndex;
|
|
122
76
|
}
|
|
123
77
|
pruneHistoryIfNeeded() {
|
|
124
78
|
const beforeTotal = this.getTotalLength();
|
|
@@ -127,62 +81,114 @@ export class ThoughtStore {
|
|
|
127
81
|
truncatedActive: false,
|
|
128
82
|
droppedActiveCount: 0,
|
|
129
83
|
removedThoughtsCount: 0,
|
|
130
|
-
oldestAvailableThoughtNumber: this.#
|
|
84
|
+
oldestAvailableThoughtNumber: this.#allThoughts[this.#startIndex]?.thoughtNumber ?? null,
|
|
131
85
|
};
|
|
132
86
|
this.#performPruning();
|
|
133
87
|
this.#updatePruneStats(beforeTotal, beforeActive);
|
|
134
88
|
}
|
|
89
|
+
// -------------------------
|
|
90
|
+
// Active-path bookkeeping
|
|
91
|
+
// -------------------------
|
|
92
|
+
#appendActive(stored) {
|
|
93
|
+
this.#activeThoughts.push(stored);
|
|
94
|
+
this.#activeNumbers.push(stored.thoughtNumber);
|
|
95
|
+
this.#activeMaxTotalThoughts = Math.max(this.#activeMaxTotalThoughts, stored.totalThoughts);
|
|
96
|
+
}
|
|
97
|
+
#markSuperseded(thought, supersededBy) {
|
|
98
|
+
if (!thought.isActive)
|
|
99
|
+
return;
|
|
100
|
+
thought.isActive = false;
|
|
101
|
+
thought.supersededBy = supersededBy;
|
|
102
|
+
}
|
|
103
|
+
#recomputeActiveMaxTotalThoughts() {
|
|
104
|
+
let maxTotal = 0;
|
|
105
|
+
for (const thought of this.#activeThoughts) {
|
|
106
|
+
maxTotal = Math.max(maxTotal, thought.totalThoughts);
|
|
107
|
+
}
|
|
108
|
+
this.#activeMaxTotalThoughts = maxTotal;
|
|
109
|
+
}
|
|
110
|
+
// Binary search lower bound for activeNumbers
|
|
111
|
+
#lowerBoundActive(thoughtNumber) {
|
|
112
|
+
let low = 0;
|
|
113
|
+
let high = this.#activeNumbers.length;
|
|
114
|
+
while (low < high) {
|
|
115
|
+
const mid = (low + high) >>> 1;
|
|
116
|
+
const midValue = this.#activeNumbers[mid];
|
|
117
|
+
if (midValue === undefined)
|
|
118
|
+
return -1;
|
|
119
|
+
if (midValue < thoughtNumber)
|
|
120
|
+
low = mid + 1;
|
|
121
|
+
else
|
|
122
|
+
high = mid;
|
|
123
|
+
}
|
|
124
|
+
return low;
|
|
125
|
+
}
|
|
126
|
+
#findActiveIndex(thoughtNumber) {
|
|
127
|
+
const index = this.#lowerBoundActive(thoughtNumber);
|
|
128
|
+
if (index < 0)
|
|
129
|
+
return -1;
|
|
130
|
+
return this.#activeNumbers[index] === thoughtNumber ? index : -1;
|
|
131
|
+
}
|
|
132
|
+
#firstActiveIndexAfter(thoughtNumber) {
|
|
133
|
+
const index = this.#lowerBoundActive(thoughtNumber);
|
|
134
|
+
if (index < 0)
|
|
135
|
+
return 0;
|
|
136
|
+
return this.#activeNumbers[index] === thoughtNumber ? index + 1 : index;
|
|
137
|
+
}
|
|
138
|
+
#dropActiveUpTo(thoughtNumber) {
|
|
139
|
+
if (this.#activeThoughts.length === 0)
|
|
140
|
+
return;
|
|
141
|
+
const startIndex = this.#firstActiveIndexAfter(thoughtNumber);
|
|
142
|
+
if (startIndex === 0)
|
|
143
|
+
return;
|
|
144
|
+
this.#activeThoughts = this.#activeThoughts.slice(startIndex);
|
|
145
|
+
this.#activeNumbers = this.#activeNumbers.slice(startIndex);
|
|
146
|
+
this.#recomputeActiveMaxTotalThoughts();
|
|
147
|
+
}
|
|
148
|
+
// -------------------------
|
|
149
|
+
// Pruning / compaction
|
|
150
|
+
// -------------------------
|
|
135
151
|
#performPruning() {
|
|
136
152
|
const excess = this.getTotalLength() - this.#maxThoughts;
|
|
137
153
|
if (excess > 0) {
|
|
138
154
|
const batch = Math.max(excess, Math.ceil(this.#maxThoughts * 0.1));
|
|
139
155
|
this.#removeOldest(batch);
|
|
140
156
|
}
|
|
141
|
-
if (this.#
|
|
157
|
+
if (this.#approxBytes > this.#maxMemoryBytes &&
|
|
142
158
|
this.getTotalLength() > 10) {
|
|
143
159
|
const toRemove = Math.ceil(this.getTotalLength() * 0.2);
|
|
144
160
|
this.#removeOldest(toRemove, { forceCompact: true });
|
|
145
161
|
}
|
|
146
162
|
}
|
|
147
163
|
#updatePruneStats(beforeTotal, beforeActive) {
|
|
148
|
-
|
|
164
|
+
const stats = this.#lastPruneStats;
|
|
165
|
+
if (!stats)
|
|
149
166
|
return;
|
|
150
167
|
const afterActive = this.#activeThoughts.length;
|
|
151
|
-
const stats = this.#lastPruneStats;
|
|
152
168
|
stats.removedThoughtsCount = Math.max(0, beforeTotal - this.getTotalLength());
|
|
153
169
|
stats.droppedActiveCount = Math.max(0, beforeActive - afterActive);
|
|
154
170
|
stats.truncatedActive = stats.droppedActiveCount > 0;
|
|
155
171
|
stats.oldestAvailableThoughtNumber =
|
|
156
|
-
this.#
|
|
172
|
+
this.#allThoughts[this.#startIndex]?.thoughtNumber ?? null;
|
|
157
173
|
}
|
|
158
174
|
#estimateThoughtBytes(thought) {
|
|
159
175
|
return thought.byteLength + this.#estimatedThoughtOverheadBytes;
|
|
160
176
|
}
|
|
161
|
-
#dropActiveThoughtsUpTo(thoughtNumber) {
|
|
162
|
-
if (this.#activeThoughts.length === 0)
|
|
163
|
-
return;
|
|
164
|
-
const startIndex = this.#findFirstActiveIndexAfter(thoughtNumber);
|
|
165
|
-
if (startIndex === 0)
|
|
166
|
-
return;
|
|
167
|
-
this.#activeThoughts = this.#activeThoughts.slice(startIndex);
|
|
168
|
-
this.#activeThoughtNumbers = this.#activeThoughtNumbers.slice(startIndex);
|
|
169
|
-
this.#recomputeActiveMaxTotalThoughts();
|
|
170
|
-
}
|
|
171
177
|
#removeOldest(count, options = {}) {
|
|
172
178
|
const totalLength = this.getTotalLength();
|
|
173
179
|
if (count <= 0 || totalLength === 0)
|
|
174
180
|
return;
|
|
175
181
|
const actual = Math.min(count, totalLength);
|
|
176
|
-
const start = this.#
|
|
182
|
+
const start = this.#startIndex;
|
|
177
183
|
const end = start + actual;
|
|
178
184
|
const removedMaxThoughtNumber = this.#evictRange(start, end);
|
|
179
|
-
this.#
|
|
185
|
+
this.#startIndex = end;
|
|
180
186
|
if (this.getTotalLength() === 0) {
|
|
181
|
-
this.#
|
|
187
|
+
this.#reset();
|
|
182
188
|
return;
|
|
183
189
|
}
|
|
184
190
|
if (removedMaxThoughtNumber >= 0) {
|
|
185
|
-
this.#
|
|
191
|
+
this.#dropActiveUpTo(removedMaxThoughtNumber);
|
|
186
192
|
}
|
|
187
193
|
this.#compactIfNeeded(options.forceCompact);
|
|
188
194
|
}
|
|
@@ -190,42 +196,41 @@ export class ThoughtStore {
|
|
|
190
196
|
let removedBytes = 0;
|
|
191
197
|
let maxThoughtNumber = -1;
|
|
192
198
|
for (let index = start; index < end; index += 1) {
|
|
193
|
-
const thought = this.#
|
|
199
|
+
const thought = this.#allThoughts[index];
|
|
194
200
|
if (!thought)
|
|
195
201
|
continue;
|
|
196
202
|
removedBytes += this.#estimateThoughtBytes(thought);
|
|
197
|
-
this.#
|
|
203
|
+
this.#byNumber.delete(thought.thoughtNumber);
|
|
198
204
|
maxThoughtNumber = thought.thoughtNumber;
|
|
199
205
|
}
|
|
200
|
-
this.#
|
|
206
|
+
this.#approxBytes -= removedBytes;
|
|
201
207
|
return maxThoughtNumber;
|
|
202
208
|
}
|
|
203
209
|
#compactIfNeeded(force = false) {
|
|
204
|
-
if (this.#
|
|
210
|
+
if (this.#startIndex === 0)
|
|
205
211
|
return;
|
|
206
212
|
if (this.getTotalLength() === 0) {
|
|
207
|
-
this.#
|
|
213
|
+
this.#reset();
|
|
208
214
|
return;
|
|
209
215
|
}
|
|
210
|
-
if (!force && !this.#shouldCompact())
|
|
216
|
+
if (!force && !this.#shouldCompact())
|
|
211
217
|
return;
|
|
212
|
-
|
|
213
|
-
this.#
|
|
214
|
-
this.#headIndex = 0;
|
|
218
|
+
this.#allThoughts = this.#allThoughts.slice(this.#startIndex);
|
|
219
|
+
this.#startIndex = 0;
|
|
215
220
|
}
|
|
216
221
|
#shouldCompact() {
|
|
217
|
-
return (this.#
|
|
218
|
-
this.#
|
|
222
|
+
return (this.#startIndex >= COMPACT_THRESHOLD ||
|
|
223
|
+
this.#startIndex >= this.#allThoughts.length * COMPACT_RATIO);
|
|
219
224
|
}
|
|
220
|
-
#
|
|
221
|
-
this.#
|
|
222
|
-
this.#
|
|
225
|
+
#reset() {
|
|
226
|
+
this.#allThoughts = [];
|
|
227
|
+
this.#byNumber.clear();
|
|
223
228
|
this.#activeThoughts = [];
|
|
224
|
-
this.#
|
|
229
|
+
this.#activeNumbers = [];
|
|
225
230
|
this.#activeMaxTotalThoughts = 0;
|
|
226
|
-
this.#
|
|
231
|
+
this.#startIndex = 0;
|
|
227
232
|
this.#nextThoughtNumber = 1;
|
|
228
|
-
this.#
|
|
233
|
+
this.#approxBytes = 0;
|
|
229
234
|
this.#lastPruneStats = null;
|
|
230
235
|
}
|
|
231
236
|
}
|
package/dist/engine.js
CHANGED
|
@@ -1,44 +1,90 @@
|
|
|
1
1
|
var _a;
|
|
2
|
+
import { Buffer } from 'node:buffer';
|
|
2
3
|
import { resolveRevisionTarget } from './engine/revision.js';
|
|
3
4
|
import { buildContextSummary } from './engine/thoughtQueries.js';
|
|
4
5
|
import { ThoughtStore } from './engine/thoughtStore.js';
|
|
5
6
|
import { DEFAULT_MAX_THOUGHTS, ESTIMATED_THOUGHT_OVERHEAD_BYTES, MAX_MEMORY_BYTES, MAX_REVISABLE_THOUGHTS, MAX_SUPERSEDES, MAX_THOUGHTS_CAP, normalizeInt, } from './engineConfig.js';
|
|
7
|
+
class PinnedLruSessions {
|
|
8
|
+
#map = new Map();
|
|
9
|
+
#pinnedKey;
|
|
10
|
+
#max;
|
|
11
|
+
constructor(pinnedKey, max) {
|
|
12
|
+
this.#pinnedKey = pinnedKey;
|
|
13
|
+
this.#max = max;
|
|
14
|
+
}
|
|
15
|
+
get size() {
|
|
16
|
+
return this.#map.size;
|
|
17
|
+
}
|
|
18
|
+
get(key) {
|
|
19
|
+
const existing = this.#map.get(key);
|
|
20
|
+
if (!existing)
|
|
21
|
+
return undefined;
|
|
22
|
+
// bump to most-recent
|
|
23
|
+
this.#map.delete(key);
|
|
24
|
+
this.#map.set(key, existing);
|
|
25
|
+
return existing;
|
|
26
|
+
}
|
|
27
|
+
set(key, value) {
|
|
28
|
+
if (this.#map.has(key))
|
|
29
|
+
this.#map.delete(key);
|
|
30
|
+
this.#map.set(key, value);
|
|
31
|
+
this.#evictIfNeeded();
|
|
32
|
+
}
|
|
33
|
+
#evictIfNeeded() {
|
|
34
|
+
while (this.#map.size > this.#max) {
|
|
35
|
+
const oldestKey = this.#map.keys().next().value;
|
|
36
|
+
if (!oldestKey)
|
|
37
|
+
return;
|
|
38
|
+
if (oldestKey === this.#pinnedKey) {
|
|
39
|
+
const pinned = this.#map.get(this.#pinnedKey);
|
|
40
|
+
if (!pinned) {
|
|
41
|
+
// Invariant broken; safest is to stop evicting.
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
// Move pinned to most-recent and try again.
|
|
45
|
+
this.#map.delete(this.#pinnedKey);
|
|
46
|
+
this.#map.set(this.#pinnedKey, pinned);
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
this.#map.delete(oldestKey);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
function normalizeSessionId(sessionId) {
|
|
54
|
+
const key = sessionId.trim();
|
|
55
|
+
return key.length > 0 ? key : 'default';
|
|
56
|
+
}
|
|
6
57
|
export class ThinkingEngine {
|
|
7
58
|
static DEFAULT_TOTAL_THOUGHTS = 3;
|
|
8
59
|
#maxThoughts;
|
|
9
60
|
#maxMemoryBytes;
|
|
10
61
|
#estimatedThoughtOverheadBytes;
|
|
11
|
-
#
|
|
12
|
-
#sessions = new Map();
|
|
62
|
+
#sessions;
|
|
13
63
|
constructor(options = {}) {
|
|
14
64
|
this.#maxThoughts = normalizeInt(options.maxThoughts, DEFAULT_MAX_THOUGHTS, { min: 1, max: MAX_THOUGHTS_CAP });
|
|
15
65
|
this.#maxMemoryBytes = normalizeInt(options.maxMemoryBytes, MAX_MEMORY_BYTES, { min: 1, max: Number.MAX_SAFE_INTEGER });
|
|
16
66
|
this.#estimatedThoughtOverheadBytes = normalizeInt(options.estimatedThoughtOverheadBytes, ESTIMATED_THOUGHT_OVERHEAD_BYTES, { min: 1, max: Number.MAX_SAFE_INTEGER });
|
|
17
|
-
|
|
67
|
+
const maxSessions = normalizeInt(options.maxSessions, 50, {
|
|
18
68
|
min: 1,
|
|
19
69
|
max: 10_000,
|
|
20
70
|
});
|
|
21
|
-
this.#
|
|
71
|
+
this.#sessions = new PinnedLruSessions('default', maxSessions);
|
|
72
|
+
this.#getOrCreateSession('default');
|
|
22
73
|
}
|
|
23
74
|
processThought(input) {
|
|
24
75
|
return this.processThoughtWithSession('default', input);
|
|
25
76
|
}
|
|
26
77
|
processThoughtWithSession(sessionId, input) {
|
|
27
|
-
const session = this.#
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
return this.#processNewThought(session, input);
|
|
78
|
+
const session = this.#getOrCreateSession(sessionId);
|
|
79
|
+
return input.revisesThought !== undefined
|
|
80
|
+
? this.#processRevision(session, input)
|
|
81
|
+
: this.#processNewThought(session, input);
|
|
32
82
|
}
|
|
33
|
-
#
|
|
34
|
-
const key = sessionId
|
|
83
|
+
#getOrCreateSession(sessionId) {
|
|
84
|
+
const key = normalizeSessionId(sessionId);
|
|
35
85
|
const existing = this.#sessions.get(key);
|
|
36
|
-
if (existing)
|
|
37
|
-
// bump to most-recent for LRU eviction
|
|
38
|
-
this.#sessions.delete(key);
|
|
39
|
-
this.#sessions.set(key, existing);
|
|
86
|
+
if (existing)
|
|
40
87
|
return existing;
|
|
41
|
-
}
|
|
42
88
|
const state = {
|
|
43
89
|
store: new ThoughtStore({
|
|
44
90
|
maxThoughts: this.#maxThoughts,
|
|
@@ -48,28 +94,11 @@ export class ThinkingEngine {
|
|
|
48
94
|
hasRevisions: false,
|
|
49
95
|
};
|
|
50
96
|
this.#sessions.set(key, state);
|
|
51
|
-
this.#evictSessionsIfNeeded();
|
|
52
97
|
return state;
|
|
53
98
|
}
|
|
54
|
-
#evictSessionsIfNeeded() {
|
|
55
|
-
while (this.#sessions.size > this.#maxSessions) {
|
|
56
|
-
const oldestKey = this.#sessions.keys().next().value;
|
|
57
|
-
if (!oldestKey)
|
|
58
|
-
return;
|
|
59
|
-
if (oldestKey === 'default') {
|
|
60
|
-
const def = this.#sessions.get('default');
|
|
61
|
-
if (!def)
|
|
62
|
-
return;
|
|
63
|
-
this.#sessions.delete('default');
|
|
64
|
-
this.#sessions.set('default', def);
|
|
65
|
-
continue;
|
|
66
|
-
}
|
|
67
|
-
this.#sessions.delete(oldestKey);
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
99
|
#processNewThought(session, input) {
|
|
71
|
-
const
|
|
72
|
-
this.#
|
|
100
|
+
const stored = this.#createStoredThought(session.store, input);
|
|
101
|
+
this.#commit(session.store, stored);
|
|
73
102
|
return this.#buildProcessResult(session, stored);
|
|
74
103
|
}
|
|
75
104
|
#processRevision(session, input) {
|
|
@@ -78,19 +107,31 @@ export class ThinkingEngine {
|
|
|
78
107
|
if (!resolved.ok)
|
|
79
108
|
return resolved.result;
|
|
80
109
|
const { targetNumber } = resolved;
|
|
81
|
-
const
|
|
110
|
+
const stored = this.#createStoredThought(store, input, {
|
|
82
111
|
revisionOf: targetNumber,
|
|
83
112
|
fallbackTotalThoughts: resolved.target.totalThoughts,
|
|
84
113
|
});
|
|
85
|
-
const { supersedes, supersedesTotal } = store.supersedeFrom(targetNumber,
|
|
114
|
+
const { supersedes, supersedesTotal } = store.supersedeFrom(targetNumber, stored.thoughtNumber, MAX_SUPERSEDES);
|
|
86
115
|
session.hasRevisions = true;
|
|
87
|
-
this.#
|
|
116
|
+
this.#commit(store, stored);
|
|
88
117
|
return this.#buildProcessResult(session, stored, {
|
|
89
118
|
revises: targetNumber,
|
|
90
119
|
supersedes,
|
|
91
120
|
supersedesTotal,
|
|
92
121
|
});
|
|
93
122
|
}
|
|
123
|
+
#commit(store, stored) {
|
|
124
|
+
store.storeThought(stored);
|
|
125
|
+
store.pruneHistoryIfNeeded();
|
|
126
|
+
}
|
|
127
|
+
#createStoredThought(store, input, extras = {}) {
|
|
128
|
+
const totalThoughts = this.#resolveEffectiveTotalThoughts(store, input, extras.fallbackTotalThoughts);
|
|
129
|
+
const numbers = store.nextThoughtNumbers(totalThoughts);
|
|
130
|
+
return this.#buildStoredThought(input, {
|
|
131
|
+
...numbers,
|
|
132
|
+
...(extras.revisionOf !== undefined && { revisionOf: extras.revisionOf }),
|
|
133
|
+
});
|
|
134
|
+
}
|
|
94
135
|
#buildStoredThought(input, details) {
|
|
95
136
|
return {
|
|
96
137
|
thought: input.thought,
|
|
@@ -107,19 +148,6 @@ export class ThinkingEngine {
|
|
|
107
148
|
byteLength: Buffer.byteLength(input.thought),
|
|
108
149
|
};
|
|
109
150
|
}
|
|
110
|
-
#createStoredThought(store, input, extras = {}) {
|
|
111
|
-
const effectiveTotalThoughts = this.#resolveEffectiveTotalThoughts(store, input, extras.fallbackTotalThoughts);
|
|
112
|
-
const numbers = store.nextThoughtNumbers(effectiveTotalThoughts);
|
|
113
|
-
const stored = this.#buildStoredThought(input, {
|
|
114
|
-
...numbers,
|
|
115
|
-
...(extras.revisionOf !== undefined && { revisionOf: extras.revisionOf }),
|
|
116
|
-
});
|
|
117
|
-
return { stored, numbers };
|
|
118
|
-
}
|
|
119
|
-
#commitThought(store, stored) {
|
|
120
|
-
store.storeThought(stored);
|
|
121
|
-
store.pruneHistoryIfNeeded();
|
|
122
|
-
}
|
|
123
151
|
#resolveEffectiveTotalThoughts(store, input, fallback) {
|
|
124
152
|
if (input.totalThoughts !== undefined) {
|
|
125
153
|
return normalizeInt(input.totalThoughts, _a.DEFAULT_TOTAL_THOUGHTS, { min: 1, max: MAX_THOUGHTS_CAP });
|
|
@@ -128,15 +156,12 @@ export class ThinkingEngine {
|
|
|
128
156
|
return fallback;
|
|
129
157
|
const activeThoughts = store.getActiveThoughts();
|
|
130
158
|
const lastActive = activeThoughts[activeThoughts.length - 1];
|
|
131
|
-
|
|
132
|
-
return lastActive.totalThoughts;
|
|
133
|
-
}
|
|
134
|
-
return _a.DEFAULT_TOTAL_THOUGHTS;
|
|
159
|
+
return lastActive?.totalThoughts ?? _a.DEFAULT_TOTAL_THOUGHTS;
|
|
135
160
|
}
|
|
136
161
|
#buildProcessResult(session, stored, revisionInfo) {
|
|
137
162
|
const activeThoughts = session.store.getActiveThoughts();
|
|
138
163
|
const activePathLength = activeThoughts.length;
|
|
139
|
-
const { progress, isComplete } = this.#
|
|
164
|
+
const { progress, isComplete } = this.#calculateProgress(activePathLength, stored.totalThoughts);
|
|
140
165
|
return {
|
|
141
166
|
ok: true,
|
|
142
167
|
result: {
|
|
@@ -153,11 +178,10 @@ export class ThinkingEngine {
|
|
|
153
178
|
},
|
|
154
179
|
};
|
|
155
180
|
}
|
|
156
|
-
#
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
};
|
|
181
|
+
#calculateProgress(active, total) {
|
|
182
|
+
const safeTotal = total > 0 ? total : 0;
|
|
183
|
+
const progress = safeTotal > 0 ? Math.min(1, active / safeTotal) : 1;
|
|
184
|
+
return { progress, isComplete: active >= safeTotal };
|
|
161
185
|
}
|
|
162
186
|
}
|
|
163
187
|
_a = ThinkingEngine;
|