@seanhogg/builderforce-memory 2026.10.25 → 2026.10.27
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 +25 -0
- package/dist/working/codemapAreas.d.ts +49 -0
- package/dist/working/codemapAreas.d.ts.map +1 -0
- package/dist/working/codemapAreas.js +139 -0
- package/dist/working/codemapAreas.js.map +1 -0
- package/dist/working/consolidate.d.ts +17 -4
- package/dist/working/consolidate.d.ts.map +1 -1
- package/dist/working/consolidate.js +40 -7
- package/dist/working/consolidate.js.map +1 -1
- package/dist/working/consolidationMarker.d.ts +8 -2
- package/dist/working/consolidationMarker.d.ts.map +1 -1
- package/dist/working/consolidationMarker.js +13 -5
- package/dist/working/consolidationMarker.js.map +1 -1
- package/dist/working/index.d.ts +4 -3
- package/dist/working/index.d.ts.map +1 -1
- package/dist/working/index.js +4 -3
- package/dist/working/index.js.map +1 -1
- package/dist/working/summary.d.ts +18 -4
- package/dist/working/summary.d.ts.map +1 -1
- package/dist/working/summary.js +58 -14
- package/dist/working/summary.js.map +1 -1
- package/dist/working/transcript.d.ts +28 -10
- package/dist/working/transcript.d.ts.map +1 -1
- package/dist/working/transcript.js +72 -40
- package/dist/working/transcript.js.map +1 -1
- package/dist/working/types.d.ts +6 -0
- package/dist/working/types.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/working/codemap.test.ts +144 -0
- package/src/working/codemapAreas.ts +142 -0
- package/src/working/consolidate.ts +40 -6
- package/src/working/consolidationMarker.test.ts +12 -2
- package/src/working/consolidationMarker.ts +12 -5
- package/src/working/index.ts +19 -8
- package/src/working/summary.test.ts +35 -1
- package/src/working/summary.ts +58 -13
- package/src/working/transcript.ts +69 -38
- package/src/working/transcriptWindow.test.ts +84 -0
- package/src/working/types.ts +6 -0
- package/src/working/working.test.ts +7 -4
package/dist/working/summary.js
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* rendered for it, so a compaction note, a hand-off note and a "Consolidate" summary read
|
|
6
6
|
* the same everywhere.
|
|
7
7
|
*/
|
|
8
|
+
import { transcriptTokens } from './transcript.js';
|
|
8
9
|
/** The summarizer's instructions. */
|
|
9
10
|
export const TRANSCRIPT_SUMMARY_PROMPT = 'You compress an in-progress AI agent transcript into a concise MEMORY the agent keeps working from. Capture: the CURRENT outstanding instruction from the user (the most recent user message is authoritative — earlier requests it supersedes are history, not the active task), concrete facts/answers discovered, tool results that matter (ids, paths, values), decisions made, and what still remains to do. Keep every file path, symbol name and line number the remaining work depends on, with the specific facts learned from each file — the agent no longer sees those results, so what you leave out it must read again. Be information-dense; drop pleasantries. No preamble.';
|
|
10
11
|
/**
|
|
@@ -13,6 +14,13 @@ export const TRANSCRIPT_SUMMARY_PROMPT = 'You compress an in-progress AI agent t
|
|
|
13
14
|
* folds (paths, symbols, line numbers), and 1.2k was too little to carry them.
|
|
14
15
|
*/
|
|
15
16
|
export const TRANSCRIPT_SUMMARY_MAX_TOKENS = 2_500;
|
|
17
|
+
/**
|
|
18
|
+
* The most transcript one summarizer call reads (~tokens). A longer input — a whole long
|
|
19
|
+
* chat on "Consolidate", a first fold over a big seeded history — is summarized in
|
|
20
|
+
* CHUNKS of this size, each folded onto the note so far, so no single call can overrun
|
|
21
|
+
* a model's window.
|
|
22
|
+
*/
|
|
23
|
+
export const TRANSCRIPT_SUMMARY_INPUT_TOKENS = 48_000;
|
|
16
24
|
/** Render a slice of the transcript to the compact text the summarizer compresses. */
|
|
17
25
|
export function renderForSummary(msgs) {
|
|
18
26
|
return msgs
|
|
@@ -26,23 +34,59 @@ export function renderForSummary(msgs) {
|
|
|
26
34
|
.join('\n\n');
|
|
27
35
|
}
|
|
28
36
|
/**
|
|
29
|
-
*
|
|
30
|
-
*
|
|
37
|
+
* Split a transcript into consecutive chunks of at most `budget` tokens. A single message
|
|
38
|
+
* larger than the budget is a chunk of its own (its text is clipped when rendered).
|
|
31
39
|
*/
|
|
32
|
-
export
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
40
|
+
export function summaryChunks(msgs, budget = TRANSCRIPT_SUMMARY_INPUT_TOKENS) {
|
|
41
|
+
const chunks = [];
|
|
42
|
+
let chunk = [];
|
|
43
|
+
let used = 0;
|
|
44
|
+
for (const m of msgs) {
|
|
45
|
+
const cost = transcriptTokens([m]);
|
|
46
|
+
if (chunk.length > 0 && used + cost > budget) {
|
|
47
|
+
chunks.push(chunk);
|
|
48
|
+
chunk = [];
|
|
49
|
+
used = 0;
|
|
50
|
+
}
|
|
51
|
+
chunk.push(m);
|
|
52
|
+
used += cost;
|
|
43
53
|
}
|
|
44
|
-
|
|
54
|
+
if (chunk.length > 0)
|
|
55
|
+
chunks.push(chunk);
|
|
56
|
+
return chunks;
|
|
57
|
+
}
|
|
58
|
+
/** ~4 characters a token: the rendered text one call may carry. */
|
|
59
|
+
const clip = (text, budget) => {
|
|
60
|
+
const max = budget * 4;
|
|
61
|
+
return text.length > max ? `${text.slice(0, max)}\n[…clipped]` : text;
|
|
62
|
+
};
|
|
63
|
+
/**
|
|
64
|
+
* Compress `msgs` into a memory note — in one call, or chunk by chunk with each chunk
|
|
65
|
+
* folded onto the note so far when the transcript is longer than one call may read. Null
|
|
66
|
+
* on any failure or an empty answer, so the caller falls back (drop-oldest, elision,
|
|
67
|
+
* "nothing to consolidate").
|
|
68
|
+
*/
|
|
69
|
+
export async function summarizeTranscript(complete, msgs, signal, budget = TRANSCRIPT_SUMMARY_INPUT_TOKENS) {
|
|
70
|
+
if (msgs.length === 0)
|
|
45
71
|
return null;
|
|
72
|
+
let note = null;
|
|
73
|
+
for (const chunk of summaryChunks(msgs, budget)) {
|
|
74
|
+
const input = note ? [{ role: 'assistant', content: note }, ...chunk] : chunk;
|
|
75
|
+
try {
|
|
76
|
+
const out = ((await complete({
|
|
77
|
+
system: TRANSCRIPT_SUMMARY_PROMPT,
|
|
78
|
+
user: clip(renderForSummary(input), budget),
|
|
79
|
+
maxTokens: TRANSCRIPT_SUMMARY_MAX_TOKENS,
|
|
80
|
+
...(signal ? { signal } : {}),
|
|
81
|
+
})) ?? '').trim();
|
|
82
|
+
if (!out)
|
|
83
|
+
return null;
|
|
84
|
+
note = out;
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
46
89
|
}
|
|
90
|
+
return note;
|
|
47
91
|
}
|
|
48
92
|
//# sourceMappingURL=summary.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"summary.js","sourceRoot":"","sources":["../../src/working/summary.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;
|
|
1
|
+
{"version":3,"file":"summary.js","sourceRoot":"","sources":["../../src/working/summary.ts"],"names":[],"mappings":"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"}
|
|
@@ -58,17 +58,35 @@ export interface WorkingTranscriptState<M extends TranscriptMessage = Transcript
|
|
|
58
58
|
compactMemo: CompactMemo | null;
|
|
59
59
|
}
|
|
60
60
|
/**
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
*
|
|
67
|
-
*
|
|
61
|
+
* How far the message window's front moves at a time — a quarter of
|
|
62
|
+
* {@link HISTORY_WINDOW}. See {@link windowed}.
|
|
63
|
+
*/
|
|
64
|
+
export declare const HISTORY_WINDOW_DROP_CHUNK: number;
|
|
65
|
+
/**
|
|
66
|
+
* How far (in tokens) the token window's front moves at a time — a quarter of
|
|
67
|
+
* {@link HISTORY_TOKEN_BUDGET}, so a drop leaves the window at ~75% of the budget and
|
|
68
|
+
* the next one waits for ~16k tokens of new work. See {@link windowed}.
|
|
69
|
+
*/
|
|
70
|
+
export declare const HISTORY_TOKEN_DROP_CHUNK: number;
|
|
71
|
+
/**
|
|
72
|
+
* Trim the in-memory transcript to the history window — the message-count window
|
|
73
|
+
* ({@link HISTORY_WINDOW}) and the token budget ({@link HISTORY_TOKEN_BUDGET}) — before
|
|
74
|
+
* sending it to the model.
|
|
75
|
+
*
|
|
76
|
+
* The OLDEST turns go first, in CHUNKS with hysteresis: once over, the front jumps to
|
|
77
|
+
* the next fixed cut point ({@link HISTORY_WINDOW_DROP_CHUNK} messages /
|
|
78
|
+
* {@link HISTORY_TOKEN_DROP_CHUNK} tokens), leaving room the following turns fill before
|
|
79
|
+
* it moves again. Dropping exactly to the limit slid the front by a message or two on
|
|
80
|
+
* every turn, so the request's prefix changed every turn and no provider prefix cache
|
|
81
|
+
* could hit past the system prompt.
|
|
68
82
|
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
83
|
+
* The window is then anchored at a user turn: a window starting on an `assistant`
|
|
84
|
+
* tool-call turn or an orphaned `tool` result (whose owning call fell off the front) is
|
|
85
|
+
* invalid for strict vendors — Gemini rejects a request whose conversation does not
|
|
86
|
+
* begin with a user turn (the cascade `[googleai] 400 INVALID_ARGUMENT` after a long
|
|
87
|
+
* tool loop crossed the window boundary). The most recent user turn is never dropped:
|
|
88
|
+
* correctness over the size cap, when a tool loop outgrows the window or a single turn
|
|
89
|
+
* is itself oversized.
|
|
72
90
|
*/
|
|
73
91
|
export declare function windowed<M extends TranscriptMessage>(convo: M[]): M[];
|
|
74
92
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transcript.d.ts","sourceRoot":"","sources":["../../src/working/transcript.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAGH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAE1D,yFAAyF;AACzF,MAAM,MAAM,iBAAiB,GAAG,cAAc,CAAC;AAE/C,qEAAqE;AACrE,eAAO,MAAM,cAAc,KAAK,CAAC;AAEjC;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,oBAAoB,QAAS,CAAC;AAC3C;;;;GAIG;AACH,eAAO,MAAM,yBAAyB,QAAS,CAAC;AAShD;;;GAGG;AACH,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;;GAGG;AACH,MAAM,WAAW,sBAAsB,CAAC,CAAC,SAAS,iBAAiB,GAAG,iBAAiB;IACrF,UAAU,EAAE,CAAC,EAAE,CAAC;IAChB,WAAW,EAAE,WAAW,GAAG,IAAI,CAAC;CACjC;AAED
|
|
1
|
+
{"version":3,"file":"transcript.d.ts","sourceRoot":"","sources":["../../src/working/transcript.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAGH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAE1D,yFAAyF;AACzF,MAAM,MAAM,iBAAiB,GAAG,cAAc,CAAC;AAE/C,qEAAqE;AACrE,eAAO,MAAM,cAAc,KAAK,CAAC;AAEjC;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,oBAAoB,QAAS,CAAC;AAC3C;;;;GAIG;AACH,eAAO,MAAM,yBAAyB,QAAS,CAAC;AAShD;;;GAGG;AACH,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;;GAGG;AACH,MAAM,WAAW,sBAAsB,CAAC,CAAC,SAAS,iBAAiB,GAAG,iBAAiB;IACrF,UAAU,EAAE,CAAC,EAAE,CAAC;IAChB,WAAW,EAAE,WAAW,GAAG,IAAI,CAAC;CACjC;AAED;;;GAGG;AACH,eAAO,MAAM,yBAAyB,QAAqB,CAAC;AAE5D;;;;GAIG;AACH,eAAO,MAAM,wBAAwB,QAA2B,CAAC;AAoCjE;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,QAAQ,CAAC,CAAC,SAAS,iBAAiB,EAAE,KAAK,EAAE,CAAC,EAAE,GAAG,CAAC,EAAE,CAMrE;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,CAAC,SAAS,iBAAiB,EAAE,KAAK,EAAE,sBAAsB,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,OAAO,GAAG,OAAO,CAM7H;AAmBD,kFAAkF;AAClF,eAAO,MAAM,kBAAkB,IAAI,CAAC;AAMpC;4FAC4F;AAC5F,wBAAgB,aAAa,CAAC,KAAK,EAAE,SAAS,iBAAiB,EAAE,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAIvF;AAED,wFAAwF;AACxF,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,SAAS,iBAAiB,EAAE,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAE/F;AAED;;;;;;;GAOG;AACH,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,SAAS,iBAAiB,EAAE,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM,CAY3G;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,SAAS,iBAAiB,EAAE,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAGnG;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,SAAS,iBAAiB,EAC3D,YAAY,EAAE,MAAM,EACpB,KAAK,EAAE,CAAC,EAAE,EACV,IAAI,EAAE,MAAM,EACZ,UAAU,EAAE,MAAM,GACjB,CAAC,EAAE,CAQL;AAED;4EAC4E;AAC5E,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,SAAS,iBAAiB,EAAE,GAAG,MAAM,CAE3E;AAQD;kEACkE;AAClE,MAAM,WAAW,cAAc;IAC7B,eAAe,EAAE,MAAM,CAAC;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,sBAAsB,CAAC,CAAC,SAAS,iBAAiB,EACtE,KAAK,EAAE,sBAAsB,CAAC,CAAC,CAAC,EAChC,YAAY,EAAE,MAAM,EACpB,SAAS,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,EAChD,QAAQ,EAAE,CAAC,IAAI,EAAE,cAAc,KAAK,IAAI,GACvC,OAAO,CAAC,CAAC,EAAE,CAAC,CA2Bd"}
|
|
@@ -46,49 +46,81 @@ function messageTokens(m) {
|
|
|
46
46
|
return handoffMessageTokens(m, defaultEstimateTokens);
|
|
47
47
|
}
|
|
48
48
|
/**
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
* tool-call turn or an orphaned `tool` result (whose owning call fell off the
|
|
52
|
-
* front). That payload is invalid for strict vendors: Gemini rejects a request
|
|
53
|
-
* whose conversation does not begin with a user turn — surfaced as the cascade
|
|
54
|
-
* `[googleai] 400 INVALID_ARGUMENT` after a long tool-loop crossed the window
|
|
55
|
-
* boundary. So anchor the window at a user turn.
|
|
56
|
-
*
|
|
57
|
-
* If the last-N slice contains no user turn (a tool loop longer than the
|
|
58
|
-
* window), fall back to the most recent user turn in the FULL transcript and
|
|
59
|
-
* keep everything after it — correctness over the size cap.
|
|
49
|
+
* How far the message window's front moves at a time — a quarter of
|
|
50
|
+
* {@link HISTORY_WINDOW}. See {@link windowed}.
|
|
60
51
|
*/
|
|
61
|
-
export
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
52
|
+
export const HISTORY_WINDOW_DROP_CHUNK = HISTORY_WINDOW / 4;
|
|
53
|
+
/**
|
|
54
|
+
* How far (in tokens) the token window's front moves at a time — a quarter of
|
|
55
|
+
* {@link HISTORY_TOKEN_BUDGET}, so a drop leaves the window at ~75% of the budget and
|
|
56
|
+
* the next one waits for ~16k tokens of new work. See {@link windowed}.
|
|
57
|
+
*/
|
|
58
|
+
export const HISTORY_TOKEN_DROP_CHUNK = HISTORY_TOKEN_BUDGET / 4;
|
|
59
|
+
/**
|
|
60
|
+
* Where the message-count window begins: 0 while the transcript fits
|
|
61
|
+
* {@link HISTORY_WINDOW}, else the first multiple of {@link HISTORY_WINDOW_DROP_CHUNK}
|
|
62
|
+
* that brings it back under. The cut points are fixed positions in the transcript, so
|
|
63
|
+
* the front stays put while new messages arrive and jumps a whole chunk when it moves.
|
|
64
|
+
*/
|
|
65
|
+
function messageWindowStart(length) {
|
|
66
|
+
if (length <= HISTORY_WINDOW)
|
|
67
|
+
return 0;
|
|
68
|
+
return Math.ceil((length - HISTORY_WINDOW) / HISTORY_WINDOW_DROP_CHUNK) * HISTORY_WINDOW_DROP_CHUNK;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Where the token window begins: 0 while the transcript fits
|
|
72
|
+
* {@link HISTORY_TOKEN_BUDGET}, else the first cut point that brings it back under. A
|
|
73
|
+
* cut point is the message after which the transcript's running total crosses a multiple
|
|
74
|
+
* of {@link HISTORY_TOKEN_DROP_CHUNK} — counted from the transcript's own start, so a
|
|
75
|
+
* cut point never moves as the transcript grows at the end.
|
|
76
|
+
*/
|
|
77
|
+
function tokenWindowStart(convo) {
|
|
78
|
+
const sizes = convo.map(messageTokens);
|
|
79
|
+
let remaining = sizes.reduce((sum, n) => sum + n, 0);
|
|
80
|
+
if (remaining <= HISTORY_TOKEN_BUDGET)
|
|
81
|
+
return 0;
|
|
82
|
+
let dropped = 0;
|
|
83
|
+
let nextCut = HISTORY_TOKEN_DROP_CHUNK;
|
|
84
|
+
for (let i = 0; i < convo.length; i++) {
|
|
85
|
+
dropped += sizes[i];
|
|
86
|
+
remaining -= sizes[i];
|
|
87
|
+
if (dropped < nextCut)
|
|
88
|
+
continue;
|
|
89
|
+
while (dropped >= nextCut)
|
|
90
|
+
nextCut += HISTORY_TOKEN_DROP_CHUNK;
|
|
91
|
+
if (remaining <= HISTORY_TOKEN_BUDGET)
|
|
92
|
+
return i + 1;
|
|
68
93
|
}
|
|
69
|
-
return
|
|
94
|
+
return convo.length;
|
|
70
95
|
}
|
|
71
96
|
/**
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
*
|
|
97
|
+
* Trim the in-memory transcript to the history window — the message-count window
|
|
98
|
+
* ({@link HISTORY_WINDOW}) and the token budget ({@link HISTORY_TOKEN_BUDGET}) — before
|
|
99
|
+
* sending it to the model.
|
|
100
|
+
*
|
|
101
|
+
* The OLDEST turns go first, in CHUNKS with hysteresis: once over, the front jumps to
|
|
102
|
+
* the next fixed cut point ({@link HISTORY_WINDOW_DROP_CHUNK} messages /
|
|
103
|
+
* {@link HISTORY_TOKEN_DROP_CHUNK} tokens), leaving room the following turns fill before
|
|
104
|
+
* it moves again. Dropping exactly to the limit slid the front by a message or two on
|
|
105
|
+
* every turn, so the request's prefix changed every turn and no provider prefix cache
|
|
106
|
+
* could hit past the system prompt.
|
|
107
|
+
*
|
|
108
|
+
* The window is then anchored at a user turn: a window starting on an `assistant`
|
|
109
|
+
* tool-call turn or an orphaned `tool` result (whose owning call fell off the front) is
|
|
110
|
+
* invalid for strict vendors — Gemini rejects a request whose conversation does not
|
|
111
|
+
* begin with a user turn (the cascade `[googleai] 400 INVALID_ARGUMENT` after a long
|
|
112
|
+
* tool loop crossed the window boundary). The most recent user turn is never dropped:
|
|
113
|
+
* correctness over the size cap, when a tool loop outgrows the window or a single turn
|
|
114
|
+
* is itself oversized.
|
|
77
115
|
*/
|
|
78
|
-
function
|
|
79
|
-
|
|
80
|
-
if (
|
|
81
|
-
return
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
while (total > HISTORY_TOKEN_BUDGET && start < lastUser) {
|
|
85
|
-
total -= messageTokens(w[start]);
|
|
116
|
+
export function windowed(convo) {
|
|
117
|
+
const lastUser = convo.map((m) => m.role).lastIndexOf('user');
|
|
118
|
+
if (lastUser < 0)
|
|
119
|
+
return transcriptTokens(convo) <= HISTORY_TOKEN_BUDGET ? convo.slice() : convo.slice(-1);
|
|
120
|
+
let start = Math.min(Math.max(messageWindowStart(convo.length), tokenWindowStart(convo)), lastUser);
|
|
121
|
+
while (convo[start].role !== 'user')
|
|
86
122
|
start += 1;
|
|
87
|
-
|
|
88
|
-
let trimmed = w.slice(start);
|
|
89
|
-
while (trimmed.length > 1 && trimmed[0].role !== 'user')
|
|
90
|
-
trimmed = trimmed.slice(1);
|
|
91
|
-
return trimmed;
|
|
123
|
+
return convo.slice(start);
|
|
92
124
|
}
|
|
93
125
|
/**
|
|
94
126
|
* Is a transcript message still part of what the model sees? False once auto-compaction
|
|
@@ -109,11 +141,11 @@ export function stillInWorkingContext(state, anchor) {
|
|
|
109
141
|
// ---------------------------------------------------------------------------
|
|
110
142
|
// Auto-compaction — summarize the bulky MIDDLE instead of dropping it.
|
|
111
143
|
//
|
|
112
|
-
// `
|
|
144
|
+
// `windowed` keeps the request inside the window by DROPPING the oldest turns. That
|
|
113
145
|
// never 413s, but it silently LOSES context — which made a weak model re-read files and
|
|
114
146
|
// thrash ("LOOP EXHAUSTED", chat #50). When a summarizer is available the older turns are
|
|
115
147
|
// compressed into ONE memory note instead, so the model keeps working from a distilled
|
|
116
|
-
// memory. Falls back to `
|
|
148
|
+
// memory. Falls back to `windowed` when no summarizer is reachable.
|
|
117
149
|
//
|
|
118
150
|
// Why this fold is NOT `packHandoff` (`../handoff`): the packer is stateless and gives the
|
|
119
151
|
// verbatim tail priority, handing the note whatever room the tail leaves. The working
|
|
@@ -235,7 +267,7 @@ export async function buildWorkingTranscript(state, systemPrompt, summarize, onF
|
|
|
235
267
|
? assembleCompacted(systemPrompt, convo, memo.note, memo.coveredEnd)
|
|
236
268
|
: [{ role: 'system', content: systemPrompt }, ...windowed(convo)];
|
|
237
269
|
}
|
|
238
|
-
const note = `Compressed memory of the
|
|
270
|
+
const note = `Compressed memory of the earlier part of this conversation:\n${summary}`;
|
|
239
271
|
state.compactMemo = { note, coveredEnd: to };
|
|
240
272
|
onFolded({ droppedMessages: to - from, note, coveredEnd: to });
|
|
241
273
|
return assembleCompacted(systemPrompt, convo, note, to);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transcript.js","sourceRoot":"","sources":["../../src/working/transcript.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,qBAAqB,EAAE,aAAa,IAAI,oBAAoB,EAAE,QAAQ,IAAI,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAMjI,qEAAqE;AACrE,MAAM,CAAC,MAAM,cAAc,GAAG,EAAE,CAAC;AAEjC;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,MAAM,CAAC;AAC3C;;;;GAIG;AACH,MAAM,CAAC,MAAM,yBAAyB,GAAG,MAAM,CAAC;AAEhD;;0CAE0C;AAC1C,SAAS,aAAa,CAAC,CAAoB;IACzC,OAAO,oBAAoB,CAAC,CAAC,EAAE,qBAAqB,CAAC,CAAC;AACxD,CAAC;AAoBD
|
|
1
|
+
{"version":3,"file":"transcript.js","sourceRoot":"","sources":["../../src/working/transcript.ts"],"names":[],"mappings":"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"}
|
package/dist/working/types.d.ts
CHANGED
|
@@ -29,5 +29,11 @@ export interface ConsolidateOptions {
|
|
|
29
29
|
at?: Date;
|
|
30
30
|
/** The workspace root — absolute paths under it are kept, made relative. */
|
|
31
31
|
root?: string;
|
|
32
|
+
/**
|
|
33
|
+
* Distinct files (changed, deleted and read together) a run must touch for its map to
|
|
34
|
+
* be kept. Defaults to `MIN_CONSOLIDATED_FILES`; the same floor applies to a run that
|
|
35
|
+
* changed files as to one that only read.
|
|
36
|
+
*/
|
|
37
|
+
minFiles?: number;
|
|
32
38
|
}
|
|
33
39
|
//# sourceMappingURL=types.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/working/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,gFAAgF;AAChF,MAAM,MAAM,WAAW,GACnB;IAAE,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,QAAQ,GAAG,SAAS,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GACjE;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAExC,wFAAwF;AACxF,MAAM,WAAW,qBAAqB;IACpC,8BAA8B;IAC9B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3B,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;IAC1B,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3B,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;IACxB,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;CAC7B;AAED,wFAAwF;AACxF,MAAM,WAAW,kBAAkB;IACjC,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,kBAAkB;IACjC,sDAAsD;IACtD,EAAE,CAAC,EAAE,IAAI,CAAC;IACV,4EAA4E;IAC5E,IAAI,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/working/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,gFAAgF;AAChF,MAAM,MAAM,WAAW,GACnB;IAAE,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,QAAQ,GAAG,SAAS,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GACjE;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAExC,wFAAwF;AACxF,MAAM,WAAW,qBAAqB;IACpC,8BAA8B;IAC9B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3B,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;IAC1B,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3B,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;IACxB,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;CAC7B;AAED,wFAAwF;AACxF,MAAM,WAAW,kBAAkB;IACjC,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,kBAAkB;IACjC,sDAAsD;IACtD,EAAE,CAAC,EAAE,IAAI,CAAC;IACV,4EAA4E;IAC5E,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@seanhogg/builderforce-memory",
|
|
3
|
-
"version": "2026.10.
|
|
3
|
+
"version": "2026.10.27",
|
|
4
4
|
"description": "BuilderForce Agent Memory — runtime layer. SSM execution, Transformer orchestration, online distillation, enterprise RAG (ingestion pipelines, vector-store adapters, tenant/ACL-scoped hybrid retrieval), multi-agent graph orchestration, LLM telemetry and cost accounting, and persistent agent memory for BuilderForce.ai agents.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.js",
|
|
@@ -103,7 +103,7 @@
|
|
|
103
103
|
},
|
|
104
104
|
"homepage": "https://github.com/SeanHogg/builderforce-memory/tree/main/packages/memory#readme",
|
|
105
105
|
"peerDependencies": {
|
|
106
|
-
"@seanhogg/builderforce-memory-engine": "^2026.10.
|
|
106
|
+
"@seanhogg/builderforce-memory-engine": "^2026.10.27"
|
|
107
107
|
},
|
|
108
108
|
"devDependencies": {
|
|
109
109
|
"@jest/globals": "^29.7.0",
|
|
@@ -117,7 +117,7 @@
|
|
|
117
117
|
"ts-jest": "^29.2.0",
|
|
118
118
|
"typescript": "^6.0.3",
|
|
119
119
|
"@typescript/native-preview": "7.0.0-dev.20260627.2",
|
|
120
|
-
"@seanhogg/builderforce-memory-engine": "2026.10.
|
|
120
|
+
"@seanhogg/builderforce-memory-engine": "2026.10.27"
|
|
121
121
|
},
|
|
122
122
|
"jest": {
|
|
123
123
|
"preset": "ts-jest/presets/default-esm",
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/working/codemap.test.ts
|
|
3
|
+
* Code maps (2026.10.27): the 3-file floor for every run, the root area, reading a
|
|
4
|
+
* stored map back, and which maps a run is handed.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
CONSOLIDATED_ROOT_AREA,
|
|
9
|
+
areaOfPath,
|
|
10
|
+
areasNamedIn,
|
|
11
|
+
areasOfPathsIn,
|
|
12
|
+
consolidate,
|
|
13
|
+
consolidatedArea,
|
|
14
|
+
consolidatedFactsFor,
|
|
15
|
+
consolidatedFiles,
|
|
16
|
+
mergeConsolidated,
|
|
17
|
+
withoutConsolidatedFiles,
|
|
18
|
+
type WorkingMemorySnapshot,
|
|
19
|
+
} from './index.js';
|
|
20
|
+
|
|
21
|
+
const AT = new Date('2026-10-26T12:00:00Z');
|
|
22
|
+
|
|
23
|
+
function snap(partial: Partial<WorkingMemorySnapshot>): WorkingMemorySnapshot {
|
|
24
|
+
return { created: [], edited: [], deleted: [], read: [], searched: [], ...partial };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
describe('consolidate — the minimum holds for every run', () => {
|
|
28
|
+
it('skips a one- or two-file run that changed files', () => {
|
|
29
|
+
expect(consolidate(snap({ edited: ['api/src/r/x.ts'] }))).toBeNull();
|
|
30
|
+
expect(consolidate(snap({ edited: ['api/src/r/x.ts'], read: ['api/src/r/y.ts'] }))).toBeNull();
|
|
31
|
+
expect(consolidate(snap({ created: ['api/src/r/x.ts'], deleted: ['api/src/r/old.ts'] }))).toBeNull();
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('counts changed and read files together', () => {
|
|
35
|
+
const c = consolidate(snap({ edited: ['api/src/r/x.ts'], read: ['api/src/r/y.ts', 'api/src/r/z.ts'] }), { at: AT });
|
|
36
|
+
expect(c?.key).toBe('codemap:api/src/r');
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('counts a file once however it was touched', () => {
|
|
40
|
+
expect(consolidate(snap({ edited: ['a/b/x.ts'], read: ['a/b/x.ts', 'a/b/x.ts', 'a/b/y.ts'] }))).toBeNull();
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('does not count noise it drops', () => {
|
|
44
|
+
expect(consolidate(snap({ edited: ['a/b/x.ts'], read: ['pnpm-lock.yaml', 'node_modules/p/i.js', 'a/b/y.ts'] }))).toBeNull();
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
describe('consolidate — root files', () => {
|
|
49
|
+
it('files a root-only run under the root area', () => {
|
|
50
|
+
const c = consolidate(snap({ edited: ['DONE.md', 'ROADMAP.md'], read: ['package.json'] }), { at: AT })!;
|
|
51
|
+
expect(c.key).toBe(`codemap:${CONSOLIDATED_ROOT_AREA}`);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('maps a root file to the same area a reader resolves it to', () => {
|
|
55
|
+
const c = consolidate(snap({ read: ['README.md', 'package.json', 'tsconfig.json'] }), { at: AT })!;
|
|
56
|
+
expect(consolidatedArea(c.key)).toBe(areaOfPath('README.md'));
|
|
57
|
+
expect(consolidatedFactsFor([c], [areaOfPath('package.json')])).toEqual([c]);
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
describe('areaOfPath', () => {
|
|
62
|
+
it('is a file\'s directory, a directory itself, or the root area', () => {
|
|
63
|
+
expect(areaOfPath('web/src/a.ts')).toBe('web/src');
|
|
64
|
+
expect(areaOfPath('.\\web\\src\\a.ts')).toBe('web/src');
|
|
65
|
+
expect(areaOfPath('clients/vscode/src')).toBe('clients/vscode/src');
|
|
66
|
+
expect(areaOfPath('DONE.md')).toBe(CONSOLIDATED_ROOT_AREA);
|
|
67
|
+
expect(areaOfPath(' ')).toBe('');
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
describe('areasOfPathsIn', () => {
|
|
72
|
+
it('takes the areas of the repo-relative paths a text names', () => {
|
|
73
|
+
expect(areasOfPathsIn('Touches `brain-embedded/src/runStore/dispatchTool.ts` and C:/abs/x.ts, see (docs/specs/35.md).')).toEqual([
|
|
74
|
+
'brain-embedded/src/runStore',
|
|
75
|
+
'docs/specs',
|
|
76
|
+
]);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('maps a root file to the root area and skips what is not a repo path', () => {
|
|
80
|
+
expect(areasOfPathsIn('bump package.json, e.g. now')).toEqual([CONSOLIDATED_ROOT_AREA]);
|
|
81
|
+
expect(areasOfPathsIn('see https://x.dev/a/b.ts and /etc/hosts and ../up/x.ts')).toEqual([]);
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
describe('reading a stored map back', () => {
|
|
86
|
+
const map = consolidate(snap({ read: ['w/s/a.ts', 'w/s/b.ts'], edited: ['w/s/c.ts'] }), { at: AT })!;
|
|
87
|
+
|
|
88
|
+
it('lists the files a map names, never a deletion', () => {
|
|
89
|
+
expect(consolidatedFiles(map.content)).toEqual(['w/s/c.ts', 'w/s/a.ts', 'w/s/b.ts']);
|
|
90
|
+
expect(consolidatedFiles('Code map\n- x/y.ts (deleted)\n- x/z.ts')).toEqual(['x/z.ts']);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('drops a changed file from the map and keeps the rest', () => {
|
|
94
|
+
const out = withoutConsolidatedFiles(map.content, ['W\\S\\A.ts'])!;
|
|
95
|
+
expect(out).not.toContain('w/s/a.ts');
|
|
96
|
+
expect(out).toContain('- w/s/c.ts (changed)');
|
|
97
|
+
expect(out).toContain('- w/s/b.ts');
|
|
98
|
+
expect(out.split('\n')[0]).toBe(map.content.split('\n')[0]);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it('returns null once no file is left', () => {
|
|
102
|
+
expect(withoutConsolidatedFiles(map.content, ['w/s/a.ts', 'w/s/b.ts', 'w/s/c.ts'])).toBeNull();
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('leaves the map as it was when nothing named is in it', () => {
|
|
106
|
+
expect(withoutConsolidatedFiles(map.content, ['other.ts'])).toBe(map.content);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('reads a merged map the same way', () => {
|
|
110
|
+
const next = consolidate(snap({ edited: ['w/s/d.ts', 'w/s/e.ts', 'w/s/f.ts'] }), { at: AT })!;
|
|
111
|
+
expect(consolidatedFiles(mergeConsolidated(next.key, map.content, next.content))).toEqual([
|
|
112
|
+
'w/s/d.ts', 'w/s/e.ts', 'w/s/f.ts', 'w/s/c.ts', 'w/s/a.ts', 'w/s/b.ts',
|
|
113
|
+
]);
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
describe('which maps a run is handed', () => {
|
|
118
|
+
const facts = [
|
|
119
|
+
{ key: 'codemap:brain-embedded/src', content: 'wide' },
|
|
120
|
+
{ key: 'learned:x', content: 'not a map' },
|
|
121
|
+
{ key: 'codemap:brain-embedded/src/runStore', content: 'narrow' },
|
|
122
|
+
{ key: 'codemap:api/src', content: 'elsewhere' },
|
|
123
|
+
{ key: 'codemap:(root)', content: 'root' },
|
|
124
|
+
];
|
|
125
|
+
|
|
126
|
+
it('keeps overlapping maps only, most specific first, capped', () => {
|
|
127
|
+
expect(consolidatedFactsFor(facts, ['brain-embedded/src/runStore']).map((f) => f.content)).toEqual(['narrow', 'wide']);
|
|
128
|
+
expect(consolidatedFactsFor(facts, ['brain-embedded/src/runStore'], 1).map((f) => f.content)).toEqual(['narrow']);
|
|
129
|
+
expect(consolidatedFactsFor(facts, ['(root)']).map((f) => f.content)).toEqual(['root']);
|
|
130
|
+
expect(consolidatedFactsFor(facts, [])).toEqual([]);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it('finds the areas a request names by word', () => {
|
|
134
|
+
const areas = facts.map((f) => consolidatedArea(f.key)).filter((a): a is string => !!a);
|
|
135
|
+
expect(areasNamedIn('the run store drops a steer', areas)).toEqual(['brain-embedded/src/runStore']);
|
|
136
|
+
expect(areasNamedIn('Why does runStore retry?', areas)).toEqual(['brain-embedded/src/runStore']);
|
|
137
|
+
expect(areasNamedIn('brain-embedded build is slow', areas)).toEqual(['brain-embedded/src/runStore', 'brain-embedded/src']);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('never matches on a generic or short directory name', () => {
|
|
141
|
+
expect(areasNamedIn('fix the src api', ['api/src', 'web/src'])).toEqual([]);
|
|
142
|
+
expect(areasNamedIn('', ['api/src'])).toEqual([]);
|
|
143
|
+
});
|
|
144
|
+
});
|