@seanhogg/builderforce-memory 2026.10.23 → 2026.10.25

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.
Files changed (60) hide show
  1. package/dist/handoff/index.d.ts +1 -0
  2. package/dist/handoff/index.d.ts.map +1 -1
  3. package/dist/handoff/index.js +1 -0
  4. package/dist/handoff/index.js.map +1 -1
  5. package/dist/handoff/pack.d.ts.map +1 -1
  6. package/dist/handoff/pack.js +30 -3
  7. package/dist/handoff/pack.js.map +1 -1
  8. package/dist/handoff/volatile.d.ts +32 -0
  9. package/dist/handoff/volatile.d.ts.map +1 -0
  10. package/dist/handoff/volatile.js +42 -0
  11. package/dist/handoff/volatile.js.map +1 -0
  12. package/dist/working/consolidate.d.ts +39 -0
  13. package/dist/working/consolidate.d.ts.map +1 -0
  14. package/dist/working/consolidate.js +179 -0
  15. package/dist/working/consolidate.js.map +1 -0
  16. package/dist/working/consolidationMarker.d.ts +75 -0
  17. package/dist/working/consolidationMarker.d.ts.map +1 -0
  18. package/dist/working/consolidationMarker.js +104 -0
  19. package/dist/working/consolidationMarker.js.map +1 -0
  20. package/dist/working/index.d.ts +35 -0
  21. package/dist/working/index.d.ts.map +1 -0
  22. package/dist/working/index.js +31 -0
  23. package/dist/working/index.js.map +1 -0
  24. package/dist/working/rank.d.ts +7 -0
  25. package/dist/working/rank.d.ts.map +1 -0
  26. package/dist/working/rank.js +12 -0
  27. package/dist/working/rank.js.map +1 -0
  28. package/dist/working/summary.d.ts +31 -0
  29. package/dist/working/summary.d.ts.map +1 -0
  30. package/dist/working/summary.js +48 -0
  31. package/dist/working/summary.js.map +1 -0
  32. package/dist/working/transcript.d.ts +137 -0
  33. package/dist/working/transcript.d.ts.map +1 -0
  34. package/dist/working/transcript.js +243 -0
  35. package/dist/working/transcript.js.map +1 -0
  36. package/dist/working/types.d.ts +33 -0
  37. package/dist/working/types.d.ts.map +1 -0
  38. package/dist/working/types.js +5 -0
  39. package/dist/working/types.js.map +1 -0
  40. package/dist/working/workingMemory.d.ts +45 -0
  41. package/dist/working/workingMemory.d.ts.map +1 -0
  42. package/dist/working/workingMemory.js +103 -0
  43. package/dist/working/workingMemory.js.map +1 -0
  44. package/package.json +7 -3
  45. package/src/handoff/handoff.test.ts +28 -1
  46. package/src/handoff/index.ts +1 -0
  47. package/src/handoff/pack.ts +30 -4
  48. package/src/handoff/volatile.ts +46 -0
  49. package/src/working/consolidate.ts +181 -0
  50. package/src/working/consolidationMarker.test.ts +95 -0
  51. package/src/working/consolidationMarker.ts +115 -0
  52. package/src/working/index.ts +59 -0
  53. package/src/working/rank.ts +10 -0
  54. package/src/working/summary.test.ts +51 -0
  55. package/src/working/summary.ts +68 -0
  56. package/src/working/transcript.test.ts +125 -0
  57. package/src/working/transcript.ts +291 -0
  58. package/src/working/types.ts +32 -0
  59. package/src/working/working.test.ts +120 -0
  60. package/src/working/workingMemory.ts +103 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"consolidationMarker.js","sourceRoot":"","sources":["../../src/working/consolidationMarker.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Working memory — the `@seanhogg/builderforce-memory/working` subpath.
3
+ *
4
+ * A run has two memories, and they do different jobs:
5
+ *
6
+ * - SHORT-TERM: {@link WorkingMemory}, what THIS run has read, searched and changed.
7
+ * Restated before every turn ({@link WorkingMemory.brief}), because a model's own
8
+ * reasoning is not carried into its next turn and old tool results are compacted
9
+ * out of its window — without it a long run re-derives the codebase every turn.
10
+ * - LONG-TERM: {@link consolidate}, what survives the run — a map of where the code
11
+ * for that area lives, keyed per area so the next run on it (any surface) opens the
12
+ * right files first. {@link mergeConsolidated} is the write-side rule that makes
13
+ * consolidations of one area accumulate instead of overwriting each other.
14
+ *
15
+ * The memory knows observations ("read this file", "changed that one"), never tool
16
+ * names — a host maps its own tool vocabulary onto {@link Observation}.
17
+ *
18
+ * And what the MODEL sees of a run each turn — the working TRANSCRIPT (`transcript.ts`):
19
+ * the drop-oldest window and the incremental auto-compaction fold, the ONE summarizer
20
+ * prompt it folds with (`summary.ts`), and the consolidation markers a fold or a
21
+ * "Consolidate" persists so the next run starts from the note (`consolidationMarker.ts`).
22
+ *
23
+ * Pure: no `node:*`, no I/O — runs in a Cloudflare Worker, a browser page and Node alike.
24
+ */
25
+ export { WorkingMemory } from './workingMemory.js';
26
+ export { consolidate, mergeConsolidated, consolidatedKey, CONSOLIDATED_KEY_PREFIX, MIN_CONSOLIDATED_FILES } from './consolidate.js';
27
+ export { rankByCount } from './rank.js';
28
+ export type { Observation, WorkingMemorySnapshot, ConsolidatedMemory, ConsolidateOptions } from './types.js';
29
+ export { HISTORY_WINDOW, HISTORY_TOKEN_BUDGET, COMPACT_TAIL_TOKEN_BUDGET, COMPACT_TAIL_TURNS, windowed, stillInWorkingContext, verbatimStart, compactTailStart, compactTailStartForBudget, pinnedDirectiveIndex, assembleCompacted, transcriptTokens, buildWorkingTranscript, } from './transcript.js';
30
+ export type { TranscriptMessage, CompactMemo, WorkingTranscriptState, TranscriptFold } from './transcript.js';
31
+ export { TRANSCRIPT_SUMMARY_PROMPT, TRANSCRIPT_SUMMARY_MAX_TOKENS, renderForSummary, summarizeTranscript } from './summary.js';
32
+ export type { SummaryCompletion } from './summary.js';
33
+ export { CONSOLIDATION_META, CONSOLIDATION_MARKER_PREFIX, consolidationMetadata, consolidationMarkerOf, consolidationMarkerContent, isConsolidationMarker, lastConsolidationIndex, scopeToConsolidation, foldKeepFromId, } from './consolidationMarker.js';
34
+ export type { ConsolidationMarkerMeta } from './consolidationMarker.js';
35
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/working/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,EAAE,WAAW,EAAE,iBAAiB,EAAE,eAAe,EAAE,uBAAuB,EAAE,sBAAsB,EAAE,MAAM,kBAAkB,CAAC;AACpI,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AACxC,YAAY,EAAE,WAAW,EAAE,qBAAqB,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAC7G,OAAO,EACL,cAAc,EACd,oBAAoB,EACpB,yBAAyB,EACzB,kBAAkB,EAClB,QAAQ,EACR,qBAAqB,EACrB,aAAa,EACb,gBAAgB,EAChB,yBAAyB,EACzB,oBAAoB,EACpB,iBAAiB,EACjB,gBAAgB,EAChB,sBAAsB,GACvB,MAAM,iBAAiB,CAAC;AACzB,YAAY,EAAE,iBAAiB,EAAE,WAAW,EAAE,sBAAsB,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAC9G,OAAO,EAAE,yBAAyB,EAAE,6BAA6B,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAC/H,YAAY,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AACtD,OAAO,EACL,kBAAkB,EAClB,2BAA2B,EAC3B,qBAAqB,EACrB,qBAAqB,EACrB,0BAA0B,EAC1B,qBAAqB,EACrB,sBAAsB,EACtB,oBAAoB,EACpB,cAAc,GACf,MAAM,0BAA0B,CAAC;AAClC,YAAY,EAAE,uBAAuB,EAAE,MAAM,0BAA0B,CAAC"}
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Working memory — the `@seanhogg/builderforce-memory/working` subpath.
3
+ *
4
+ * A run has two memories, and they do different jobs:
5
+ *
6
+ * - SHORT-TERM: {@link WorkingMemory}, what THIS run has read, searched and changed.
7
+ * Restated before every turn ({@link WorkingMemory.brief}), because a model's own
8
+ * reasoning is not carried into its next turn and old tool results are compacted
9
+ * out of its window — without it a long run re-derives the codebase every turn.
10
+ * - LONG-TERM: {@link consolidate}, what survives the run — a map of where the code
11
+ * for that area lives, keyed per area so the next run on it (any surface) opens the
12
+ * right files first. {@link mergeConsolidated} is the write-side rule that makes
13
+ * consolidations of one area accumulate instead of overwriting each other.
14
+ *
15
+ * The memory knows observations ("read this file", "changed that one"), never tool
16
+ * names — a host maps its own tool vocabulary onto {@link Observation}.
17
+ *
18
+ * And what the MODEL sees of a run each turn — the working TRANSCRIPT (`transcript.ts`):
19
+ * the drop-oldest window and the incremental auto-compaction fold, the ONE summarizer
20
+ * prompt it folds with (`summary.ts`), and the consolidation markers a fold or a
21
+ * "Consolidate" persists so the next run starts from the note (`consolidationMarker.ts`).
22
+ *
23
+ * Pure: no `node:*`, no I/O — runs in a Cloudflare Worker, a browser page and Node alike.
24
+ */
25
+ export { WorkingMemory } from './workingMemory.js';
26
+ export { consolidate, mergeConsolidated, consolidatedKey, CONSOLIDATED_KEY_PREFIX, MIN_CONSOLIDATED_FILES } from './consolidate.js';
27
+ export { rankByCount } from './rank.js';
28
+ export { HISTORY_WINDOW, HISTORY_TOKEN_BUDGET, COMPACT_TAIL_TOKEN_BUDGET, COMPACT_TAIL_TURNS, windowed, stillInWorkingContext, verbatimStart, compactTailStart, compactTailStartForBudget, pinnedDirectiveIndex, assembleCompacted, transcriptTokens, buildWorkingTranscript, } from './transcript.js';
29
+ export { TRANSCRIPT_SUMMARY_PROMPT, TRANSCRIPT_SUMMARY_MAX_TOKENS, renderForSummary, summarizeTranscript } from './summary.js';
30
+ export { CONSOLIDATION_META, CONSOLIDATION_MARKER_PREFIX, consolidationMetadata, consolidationMarkerOf, consolidationMarkerContent, isConsolidationMarker, lastConsolidationIndex, scopeToConsolidation, foldKeepFromId, } from './consolidationMarker.js';
31
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/working/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,EAAE,WAAW,EAAE,iBAAiB,EAAE,eAAe,EAAE,uBAAuB,EAAE,sBAAsB,EAAE,MAAM,kBAAkB,CAAC;AACpI,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAExC,OAAO,EACL,cAAc,EACd,oBAAoB,EACpB,yBAAyB,EACzB,kBAAkB,EAClB,QAAQ,EACR,qBAAqB,EACrB,aAAa,EACb,gBAAgB,EAChB,yBAAyB,EACzB,oBAAoB,EACpB,iBAAiB,EACjB,gBAAgB,EAChB,sBAAsB,GACvB,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EAAE,yBAAyB,EAAE,6BAA6B,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAE/H,OAAO,EACL,kBAAkB,EAClB,2BAA2B,EAC3B,qBAAqB,EACrB,qBAAqB,EACrB,0BAA0B,EAC1B,qBAAqB,EACrB,sBAAsB,EACtB,oBAAoB,EACpB,cAAc,GACf,MAAM,0BAA0B,CAAC"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Each distinct item with how often it occurs, most frequent first, ties by name — the
3
+ * one ranking the brief, the consolidation and its area all use (and that hosts reuse
4
+ * for their own renderings of a run, so a run reads the same everywhere).
5
+ */
6
+ export declare function rankByCount(items: readonly string[]): [string, number][];
7
+ //# sourceMappingURL=rank.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rank.d.ts","sourceRoot":"","sources":["../../src/working/rank.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,CAIxE"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Each distinct item with how often it occurs, most frequent first, ties by name — the
3
+ * one ranking the brief, the consolidation and its area all use (and that hosts reuse
4
+ * for their own renderings of a run, so a run reads the same everywhere).
5
+ */
6
+ export function rankByCount(items) {
7
+ const counts = new Map();
8
+ for (const i of items)
9
+ counts.set(i, (counts.get(i) ?? 0) + 1);
10
+ return [...counts.entries()].sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]));
11
+ }
12
+ //# sourceMappingURL=rank.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rank.js","sourceRoot":"","sources":["../../src/working/rank.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,UAAU,WAAW,CAAC,KAAwB;IAClD,MAAM,MAAM,GAAG,IAAI,GAAG,EAAkB,CAAC;IACzC,KAAK,MAAM,CAAC,IAAI,KAAK;QAAE,MAAM,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IAC/D,OAAO,CAAC,GAAG,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACvF,CAAC"}
@@ -0,0 +1,31 @@
1
+ /**
2
+ * The transcript summarizer — the ONE prompt every fold, hand-off note and chat
3
+ * consolidation compresses a conversation with. The model call is the host's
4
+ * ({@link SummaryCompletion}); this module owns what is asked and how the transcript is
5
+ * rendered for it, so a compaction note, a hand-off note and a "Consolidate" summary read
6
+ * the same everywhere.
7
+ */
8
+ import type { TranscriptMessage } from './transcript.js';
9
+ /** The summarizer's instructions. */
10
+ export declare 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 \u2014 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 \u2014 the agent no longer sees those results, so what you leave out it must read again. Be information-dense; drop pleasantries. No preamble.";
11
+ /**
12
+ * The note's output ceiling. A compaction note is a UTILITY completion: a bounded answer
13
+ * with no thinking. ~2.5k tokens: the note is the agent's only record of every file it
14
+ * folds (paths, symbols, line numbers), and 1.2k was too little to carry them.
15
+ */
16
+ export declare const TRANSCRIPT_SUMMARY_MAX_TOKENS = 2500;
17
+ /** One no-tools, no-reasoning completion — the host's model call. Resolves the text. */
18
+ export type SummaryCompletion = (request: {
19
+ system: string;
20
+ user: string;
21
+ maxTokens: number;
22
+ signal?: AbortSignal;
23
+ }) => Promise<string | null | undefined>;
24
+ /** Render a slice of the transcript to the compact text the summarizer compresses. */
25
+ export declare function renderForSummary(msgs: readonly TranscriptMessage[]): string;
26
+ /**
27
+ * Compress `msgs` into a memory note. Null on any failure or an empty answer, so the
28
+ * caller falls back (drop-oldest, elision, "nothing to consolidate").
29
+ */
30
+ export declare function summarizeTranscript(complete: SummaryCompletion, msgs: readonly TranscriptMessage[], signal?: AbortSignal): Promise<string | null>;
31
+ //# sourceMappingURL=summary.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"summary.d.ts","sourceRoot":"","sources":["../../src/working/summary.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAEzD,qCAAqC;AACrC,eAAO,MAAM,yBAAyB,0qBACynB,CAAC;AAEhqB;;;;GAIG;AACH,eAAO,MAAM,6BAA6B,OAAQ,CAAC;AAEnD,wFAAwF;AACxF,MAAM,MAAM,iBAAiB,GAAG,CAAC,OAAO,EAAE;IACxC,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,CAAC;AAMzC,sFAAsF;AACtF,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,SAAS,iBAAiB,EAAE,GAAG,MAAM,CAU3E;AAED;;;GAGG;AACH,wBAAsB,mBAAmB,CACvC,QAAQ,EAAE,iBAAiB,EAC3B,IAAI,EAAE,SAAS,iBAAiB,EAAE,EAClC,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAaxB"}
@@ -0,0 +1,48 @@
1
+ /**
2
+ * The transcript summarizer — the ONE prompt every fold, hand-off note and chat
3
+ * consolidation compresses a conversation with. The model call is the host's
4
+ * ({@link SummaryCompletion}); this module owns what is asked and how the transcript is
5
+ * rendered for it, so a compaction note, a hand-off note and a "Consolidate" summary read
6
+ * the same everywhere.
7
+ */
8
+ /** The summarizer's instructions. */
9
+ 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
+ * The note's output ceiling. A compaction note is a UTILITY completion: a bounded answer
12
+ * with no thinking. ~2.5k tokens: the note is the agent's only record of every file it
13
+ * folds (paths, symbols, line numbers), and 1.2k was too little to carry them.
14
+ */
15
+ export const TRANSCRIPT_SUMMARY_MAX_TOKENS = 2_500;
16
+ /** Render a slice of the transcript to the compact text the summarizer compresses. */
17
+ export function renderForSummary(msgs) {
18
+ return msgs
19
+ .map((m) => {
20
+ const content = typeof m.content === 'string' ? m.content : JSON.stringify(m.content ?? '');
21
+ const calls = Array.isArray(m.tool_calls) && m.tool_calls.length > 0
22
+ ? ` [called: ${m.tool_calls.map((t) => t.function?.name).filter((n) => typeof n === 'string' && n.length > 0).join(', ')}]`
23
+ : '';
24
+ return `${m.role}${calls}: ${content}`;
25
+ })
26
+ .join('\n\n');
27
+ }
28
+ /**
29
+ * Compress `msgs` into a memory note. Null on any failure or an empty answer, so the
30
+ * caller falls back (drop-oldest, elision, "nothing to consolidate").
31
+ */
32
+ export async function summarizeTranscript(complete, msgs, signal) {
33
+ if (msgs.length === 0)
34
+ return null;
35
+ try {
36
+ const out = ((await complete({
37
+ system: TRANSCRIPT_SUMMARY_PROMPT,
38
+ user: renderForSummary(msgs),
39
+ maxTokens: TRANSCRIPT_SUMMARY_MAX_TOKENS,
40
+ ...(signal ? { signal } : {}),
41
+ })) ?? '').trim();
42
+ return out.length > 0 ? out : null;
43
+ }
44
+ catch {
45
+ return null;
46
+ }
47
+ }
48
+ //# sourceMappingURL=summary.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"summary.js","sourceRoot":"","sources":["../../src/working/summary.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,qCAAqC;AACrC,MAAM,CAAC,MAAM,yBAAyB,GACpC,6pBAA6pB,CAAC;AAEhqB;;;;GAIG;AACH,MAAM,CAAC,MAAM,6BAA6B,GAAG,KAAK,CAAC;AAcnD,sFAAsF;AACtF,MAAM,UAAU,gBAAgB,CAAC,IAAkC;IACjE,OAAO,IAAI;SACR,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;QACT,MAAM,OAAO,GAAG,OAAO,CAAC,CAAC,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC;QAC5F,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,UAAU,CAAC,MAAM,GAAG,CAAC;YAClE,CAAC,CAAC,aAAc,CAAC,CAAC,UAA8B,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAe,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG;YAC7J,CAAC,CAAC,EAAE,CAAC;QACP,OAAO,GAAG,CAAC,CAAC,IAAI,GAAG,KAAK,KAAK,OAAO,EAAE,CAAC;IACzC,CAAC,CAAC;SACD,IAAI,CAAC,MAAM,CAAC,CAAC;AAClB,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,QAA2B,EAC3B,IAAkC,EAClC,MAAoB;IAEpB,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IACnC,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,CAAC,CAAC,MAAM,QAAQ,CAAC;YAC3B,MAAM,EAAE,yBAAyB;YACjC,IAAI,EAAE,gBAAgB,CAAC,IAAI,CAAC;YAC5B,SAAS,EAAE,6BAA6B;YACxC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAC9B,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;QAClB,OAAO,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC;IACrC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC"}
@@ -0,0 +1,137 @@
1
+ /**
2
+ * The working transcript — what of a run the model is SHOWN each turn.
3
+ *
4
+ * A run's full transcript grows without bound; the request sent to the model must not.
5
+ * This module owns the two ways it is kept in bounds: the message-count + token WINDOW
6
+ * (drop-oldest, anchored on a user turn) and AUTO-COMPACTION (fold the older part into a
7
+ * memory note, keep every later message verbatim). A host supplies only what is
8
+ * run-specific: the summarizer it calls (see `summary.ts`) and what a fold records.
9
+ *
10
+ * Pure except for {@link buildWorkingTranscript}, which awaits the injected summarizer and
11
+ * updates the caller's `compactMemo`. Generic over the host's message shape: anything with
12
+ * the OpenAI-style `role` / `content` / `tool_calls` fields (`HandoffMessage`).
13
+ */
14
+ import type { HandoffMessage } from '../handoff/types.js';
15
+ /** A transcript message — the hand-off packer's shape, so both count a message alike. */
16
+ export type TranscriptMessage = HandoffMessage;
17
+ /** How much history we send to the model (message-count ceiling). */
18
+ export declare const HISTORY_WINDOW = 80;
19
+ /**
20
+ * Token budget for the working transcript sent to the model each turn — how much of this
21
+ * run the model can SEE: the file windows it read, the searches it ran, what it decided.
22
+ *
23
+ * It bounds context, which message-count windowing (HISTORY_WINDOW) alone does not: one
24
+ * `tasks.list` result can be tens of thousands of tokens. It is NOT what keeps a request
25
+ * inside the serving model's window — the gateway fits every request to a model that can
26
+ * hold it.
27
+ *
28
+ * It was 24k, "sized under the smallest pool model's window". That protected nothing — the
29
+ * ~16k-token system prompt + tool catalog in front of it already put a full turn at ~40k,
30
+ * past any 32k window — and it cost every coding run its memory. Six 4k-token file windows
31
+ * filled it, compaction folded them into a 1.2k-token note, and the model went back for the
32
+ * files it had just read (chat #105: 55 turns, 63% of calls revisiting, zero edits, prompt
33
+ * peak 40,351). 64k holds a coding task's working set — a dozen-plus file windows —
34
+ * verbatim, while a full turn stays well inside the 128k window every coding-capable pool
35
+ * model has. See {@link windowed} and {@link buildWorkingTranscript}.
36
+ */
37
+ export declare const HISTORY_TOKEN_BUDGET = 64000;
38
+ /**
39
+ * How much of {@link HISTORY_TOKEN_BUDGET} the verbatim TAIL may keep when the older part
40
+ * of the transcript is compacted. The rest is headroom new work fills before the next
41
+ * fold, so the memo is re-folded once per ~20k tokens of progress, not on every turn.
42
+ */
43
+ export declare const COMPACT_TAIL_TOKEN_BUDGET = 40000;
44
+ /**
45
+ * The compressed memory of a transcript's older part: `note` summarizes
46
+ * `transcript[0, coveredEnd)`; every message from `coveredEnd` on is sent verbatim.
47
+ */
48
+ export interface CompactMemo {
49
+ note: string;
50
+ coveredEnd: number;
51
+ }
52
+ /**
53
+ * What the working transcript is built from — the run's full transcript and its cached
54
+ * compressed memory. {@link buildWorkingTranscript} updates `compactMemo` in place.
55
+ */
56
+ export interface WorkingTranscriptState<M extends TranscriptMessage = TranscriptMessage> {
57
+ transcript: M[];
58
+ compactMemo: CompactMemo | null;
59
+ }
60
+ /**
61
+ * Trim the in-memory transcript to the history window before sending it to the
62
+ * model. Slicing the last N can leave the window starting on an `assistant`
63
+ * tool-call turn or an orphaned `tool` result (whose owning call fell off the
64
+ * front). That payload is invalid for strict vendors: Gemini rejects a request
65
+ * whose conversation does not begin with a user turn — surfaced as the cascade
66
+ * `[googleai] 400 INVALID_ARGUMENT` after a long tool-loop crossed the window
67
+ * boundary. So anchor the window at a user turn.
68
+ *
69
+ * If the last-N slice contains no user turn (a tool loop longer than the
70
+ * window), fall back to the most recent user turn in the FULL transcript and
71
+ * keep everything after it — correctness over the size cap.
72
+ */
73
+ export declare function windowed<M extends TranscriptMessage>(convo: M[]): M[];
74
+ /**
75
+ * Is a transcript message still part of what the model sees? False once auto-compaction
76
+ * has folded it into the memory note, or the drop-oldest window has let it fall off the
77
+ * front. Deliberately conservative: it judges against the CURRENT transcript (which has
78
+ * grown since the last working set was built), so it can only ever call a message gone a
79
+ * turn early — a needless replay costs tokens, a stub for a vanished result costs the run.
80
+ */
81
+ export declare function stillInWorkingContext<M extends TranscriptMessage>(state: WorkingTranscriptState<M>, anchor: unknown): boolean;
82
+ /** The fewest recent messages the verbatim tail keeps, however large they are. */
83
+ export declare const COMPACT_TAIL_TURNS = 8;
84
+ /** The first index at or after `from` that is not a `tool` result: a tool row whose
85
+ * assistant call was folded into the memo would be orphaned (strict vendors 400 on it). */
86
+ export declare function verbatimStart(convo: readonly TranscriptMessage[], from: number): number;
87
+ /** Start index of the last `tailTurns` messages, walked off a leading `tool` result. */
88
+ export declare function compactTailStart(convo: readonly TranscriptMessage[], tailTurns: number): number;
89
+ /**
90
+ * Where the verbatim tail begins when the transcript is compacted: as many of the most
91
+ * recent messages as fit in `budgetTokens` — never fewer than {@link COMPACT_TAIL_TURNS},
92
+ * never more than {@link COMPACT_TAIL_MAX_MESSAGES} — walked off a leading tool result.
93
+ *
94
+ * Sized by TOKENS, not a message count: a fixed eight messages is four tool results, so
95
+ * after a fold the fifth file the model needed was always the one it had to read again.
96
+ */
97
+ export declare function compactTailStartForBudget(convo: readonly TranscriptMessage[], budgetTokens: number): number;
98
+ /**
99
+ * Index of the MOST RECENT user turn to re-inject verbatim ahead of the tail — the
100
+ * ACTIVE directive — or -1 when the latest user turn is already inside the tail. Pinning
101
+ * the FIRST user turn (the old behaviour) made a multi-instruction chat revert to its
102
+ * opening request (chat #55); the current instruction is always the LATEST user turn.
103
+ */
104
+ export declare function pinnedDirectiveIndex(convo: readonly TranscriptMessage[], tailStart: number): number;
105
+ /**
106
+ * Assemble the working transcript from a compressed-memory `note` that covers
107
+ * `convo[0, coveredEnd)`: system + the memory note + the ACTIVE user directive re-injected
108
+ * verbatim (when the note covers it) + EVERY message from `coveredEnd` on (tool-pairing
109
+ * safe via {@link verbatimStart}). Nothing is in neither the note nor the tail. The
110
+ * directive sits immediately before the tail so the model reads it as the CURRENT
111
+ * instruction.
112
+ */
113
+ export declare function assembleCompacted<M extends TranscriptMessage>(systemPrompt: string, convo: M[], note: string, coveredEnd: number): M[];
114
+ /** Estimated tokens of a message list — the ONE count compaction budgets by, exported so
115
+ * a run measures what compaction kept out of a call with the same rule. */
116
+ export declare function transcriptTokens(msgs: readonly TranscriptMessage[]): number;
117
+ /** What a fold reports: how many messages it newly compressed, the note it wrote, and
118
+ * the transcript index the note now covers up to (exclusive). */
119
+ export interface TranscriptFold {
120
+ droppedMessages: number;
121
+ note: string;
122
+ coveredEnd: number;
123
+ }
124
+ /**
125
+ * Build the working transcript for a turn. When the whole transcript fits → all of it,
126
+ * verbatim. Otherwise → a memory note of the older part plus EVERY message after it,
127
+ * verbatim; `onFolded` is told about each NEW fold, so the caller can record it (a
128
+ * visible step, a durable consolidation marker).
129
+ *
130
+ * The note is folded INCREMENTALLY, and only when it has to be. A note whose verbatim
131
+ * remainder still fits is reused as-is; once it no longer fits, the previous note plus
132
+ * exactly the messages since it are folded into a new one, up to a token-sized tail
133
+ * ({@link compactTailStartForBudget}). Falls back to drop-oldest when the summarizer is
134
+ * unavailable (`summarize` resolves null). A message-count overflow folds too.
135
+ */
136
+ export declare function buildWorkingTranscript<M extends TranscriptMessage>(state: WorkingTranscriptState<M>, systemPrompt: string, summarize: (msgs: M[]) => Promise<string | null>, onFolded: (fold: TranscriptFold) => void): Promise<M[]>;
137
+ //# sourceMappingURL=transcript.d.ts.map
@@ -0,0 +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;;;;;;;;;;;;GAYG;AACH,wBAAgB,QAAQ,CAAC,CAAC,SAAS,iBAAiB,EAAE,KAAK,EAAE,CAAC,EAAE,GAAG,CAAC,EAAE,CAQrE;AAuBD;;;;;;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"}
@@ -0,0 +1,243 @@
1
+ /**
2
+ * The working transcript — what of a run the model is SHOWN each turn.
3
+ *
4
+ * A run's full transcript grows without bound; the request sent to the model must not.
5
+ * This module owns the two ways it is kept in bounds: the message-count + token WINDOW
6
+ * (drop-oldest, anchored on a user turn) and AUTO-COMPACTION (fold the older part into a
7
+ * memory note, keep every later message verbatim). A host supplies only what is
8
+ * run-specific: the summarizer it calls (see `summary.ts`) and what a fold records.
9
+ *
10
+ * Pure except for {@link buildWorkingTranscript}, which awaits the injected summarizer and
11
+ * updates the caller's `compactMemo`. Generic over the host's message shape: anything with
12
+ * the OpenAI-style `role` / `content` / `tool_calls` fields (`HandoffMessage`).
13
+ */
14
+ import { defaultEstimateTokens, messageTokens as handoffMessageTokens, tokensOf as handoffTokensOf } from '../handoff/budget.js';
15
+ /** How much history we send to the model (message-count ceiling). */
16
+ export const HISTORY_WINDOW = 80;
17
+ /**
18
+ * Token budget for the working transcript sent to the model each turn — how much of this
19
+ * run the model can SEE: the file windows it read, the searches it ran, what it decided.
20
+ *
21
+ * It bounds context, which message-count windowing (HISTORY_WINDOW) alone does not: one
22
+ * `tasks.list` result can be tens of thousands of tokens. It is NOT what keeps a request
23
+ * inside the serving model's window — the gateway fits every request to a model that can
24
+ * hold it.
25
+ *
26
+ * It was 24k, "sized under the smallest pool model's window". That protected nothing — the
27
+ * ~16k-token system prompt + tool catalog in front of it already put a full turn at ~40k,
28
+ * past any 32k window — and it cost every coding run its memory. Six 4k-token file windows
29
+ * filled it, compaction folded them into a 1.2k-token note, and the model went back for the
30
+ * files it had just read (chat #105: 55 turns, 63% of calls revisiting, zero edits, prompt
31
+ * peak 40,351). 64k holds a coding task's working set — a dozen-plus file windows —
32
+ * verbatim, while a full turn stays well inside the 128k window every coding-capable pool
33
+ * model has. See {@link windowed} and {@link buildWorkingTranscript}.
34
+ */
35
+ export const HISTORY_TOKEN_BUDGET = 64_000;
36
+ /**
37
+ * How much of {@link HISTORY_TOKEN_BUDGET} the verbatim TAIL may keep when the older part
38
+ * of the transcript is compacted. The rest is headroom new work fills before the next
39
+ * fold, so the memo is re-folded once per ~20k tokens of progress, not on every turn.
40
+ */
41
+ export const COMPACT_TAIL_TOKEN_BUDGET = 40_000;
42
+ /** Estimated tokens for one message (content + tool-call payload + framing) — THE
43
+ * hand-off packer's estimate, so the working transcript and a hand-off to a smaller
44
+ * model count a message the same way. */
45
+ function messageTokens(m) {
46
+ return handoffMessageTokens(m, defaultEstimateTokens);
47
+ }
48
+ /**
49
+ * Trim the in-memory transcript to the history window before sending it to the
50
+ * model. Slicing the last N can leave the window starting on an `assistant`
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.
60
+ */
61
+ export function windowed(convo) {
62
+ let w = convo.slice(-HISTORY_WINDOW);
63
+ while (w.length > 0 && w[0].role !== 'user')
64
+ w = w.slice(1);
65
+ if (w.length === 0) {
66
+ const lastUser = convo.map((m) => m.role).lastIndexOf('user');
67
+ w = lastUser >= 0 ? convo.slice(lastUser) : convo.slice();
68
+ }
69
+ return tokenBounded(w);
70
+ }
71
+ /**
72
+ * Enforce the token budget on an already message-count-windowed slice. Drops the
73
+ * OLDEST turns first, then re-anchors on a user turn (a strict vendor rejects a window
74
+ * that doesn't start on `user`, and dropping a turn can orphan a `tool` result whose
75
+ * `assistant` tool-call fell off the front). The most recent user turn is never
76
+ * dropped: correctness over the budget when a single turn is itself oversized.
77
+ */
78
+ function tokenBounded(w) {
79
+ let total = transcriptTokens(w);
80
+ if (total <= HISTORY_TOKEN_BUDGET)
81
+ return w;
82
+ const lastUser = w.map((m) => m.role).lastIndexOf('user');
83
+ let start = 0;
84
+ while (total > HISTORY_TOKEN_BUDGET && start < lastUser) {
85
+ total -= messageTokens(w[start]);
86
+ 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;
92
+ }
93
+ /**
94
+ * Is a transcript message still part of what the model sees? False once auto-compaction
95
+ * has folded it into the memory note, or the drop-oldest window has let it fall off the
96
+ * front. Deliberately conservative: it judges against the CURRENT transcript (which has
97
+ * grown since the last working set was built), so it can only ever call a message gone a
98
+ * turn early — a needless replay costs tokens, a stub for a vanished result costs the run.
99
+ */
100
+ export function stillInWorkingContext(state, anchor) {
101
+ const convo = state.transcript;
102
+ const idx = convo.indexOf(anchor);
103
+ if (idx < 0)
104
+ return false;
105
+ if (state.compactMemo)
106
+ return idx >= verbatimStart(convo, state.compactMemo.coveredEnd);
107
+ return windowed(convo).includes(convo[idx]);
108
+ }
109
+ // ---------------------------------------------------------------------------
110
+ // Auto-compaction — summarize the bulky MIDDLE instead of dropping it.
111
+ //
112
+ // `tokenBounded` keeps the request inside the window by DROPPING the oldest turns. That
113
+ // never 413s, but it silently LOSES context — which made a weak model re-read files and
114
+ // thrash ("LOOP EXHAUSTED", chat #50). When a summarizer is available the older turns are
115
+ // compressed into ONE memory note instead, so the model keeps working from a distilled
116
+ // memory. Falls back to `tokenBounded` when no summarizer is reachable.
117
+ //
118
+ // Why this fold is NOT `packHandoff` (`../handoff`): the packer is stateless and gives the
119
+ // verbatim tail priority, handing the note whatever room the tail leaves. The working
120
+ // transcript needs the opposite on both counts — an INCREMENTAL memo reused across turns
121
+ // (one summarizer call per ~20k tokens of progress), a tail capped at
122
+ // COMPACT_TAIL_TOKEN_BUDGET so the note and the next turns have headroom, and a
123
+ // message-count window. What the two share — the token estimate — is the packer's.
124
+ // ---------------------------------------------------------------------------
125
+ /** The fewest recent messages the verbatim tail keeps, however large they are. */
126
+ export const COMPACT_TAIL_TURNS = 8;
127
+ /** The most recent messages the verbatim tail may hold — half the message window, so a
128
+ * compacted transcript stays inside {@link HISTORY_WINDOW} with room to grow. */
129
+ const COMPACT_TAIL_MAX_MESSAGES = HISTORY_WINDOW / 2;
130
+ /** The first index at or after `from` that is not a `tool` result: a tool row whose
131
+ * assistant call was folded into the memo would be orphaned (strict vendors 400 on it). */
132
+ export function verbatimStart(convo, from) {
133
+ let start = Math.max(0, Math.min(from, convo.length));
134
+ while (start < convo.length && convo[start].role === 'tool')
135
+ start += 1;
136
+ return start;
137
+ }
138
+ /** Start index of the last `tailTurns` messages, walked off a leading `tool` result. */
139
+ export function compactTailStart(convo, tailTurns) {
140
+ return verbatimStart(convo, convo.length - tailTurns);
141
+ }
142
+ /**
143
+ * Where the verbatim tail begins when the transcript is compacted: as many of the most
144
+ * recent messages as fit in `budgetTokens` — never fewer than {@link COMPACT_TAIL_TURNS},
145
+ * never more than {@link COMPACT_TAIL_MAX_MESSAGES} — walked off a leading tool result.
146
+ *
147
+ * Sized by TOKENS, not a message count: a fixed eight messages is four tool results, so
148
+ * after a fold the fifth file the model needed was always the one it had to read again.
149
+ */
150
+ export function compactTailStartForBudget(convo, budgetTokens) {
151
+ let start = convo.length;
152
+ let tokens = 0;
153
+ while (start > 0) {
154
+ const kept = convo.length - start;
155
+ if (kept >= COMPACT_TAIL_MAX_MESSAGES)
156
+ break;
157
+ const next = messageTokens(convo[start - 1]);
158
+ if (kept >= COMPACT_TAIL_TURNS && tokens + next > budgetTokens)
159
+ break;
160
+ tokens += next;
161
+ start -= 1;
162
+ }
163
+ return verbatimStart(convo, start);
164
+ }
165
+ /**
166
+ * Index of the MOST RECENT user turn to re-inject verbatim ahead of the tail — the
167
+ * ACTIVE directive — or -1 when the latest user turn is already inside the tail. Pinning
168
+ * the FIRST user turn (the old behaviour) made a multi-instruction chat revert to its
169
+ * opening request (chat #55); the current instruction is always the LATEST user turn.
170
+ */
171
+ export function pinnedDirectiveIndex(convo, tailStart) {
172
+ const lastUser = convo.map((m) => m.role).lastIndexOf('user');
173
+ return lastUser >= 0 && lastUser < tailStart ? lastUser : -1;
174
+ }
175
+ /**
176
+ * Assemble the working transcript from a compressed-memory `note` that covers
177
+ * `convo[0, coveredEnd)`: system + the memory note + the ACTIVE user directive re-injected
178
+ * verbatim (when the note covers it) + EVERY message from `coveredEnd` on (tool-pairing
179
+ * safe via {@link verbatimStart}). Nothing is in neither the note nor the tail. The
180
+ * directive sits immediately before the tail so the model reads it as the CURRENT
181
+ * instruction.
182
+ */
183
+ export function assembleCompacted(systemPrompt, convo, note, coveredEnd) {
184
+ const tailStart = verbatimStart(convo, coveredEnd);
185
+ const out = [{ role: 'system', content: systemPrompt }];
186
+ out.push({ role: 'assistant', content: note });
187
+ const directiveIdx = pinnedDirectiveIndex(convo, tailStart);
188
+ if (directiveIdx >= 0)
189
+ out.push(convo[directiveIdx]);
190
+ out.push(...convo.slice(tailStart));
191
+ return out;
192
+ }
193
+ /** Estimated tokens of a message list — the ONE count compaction budgets by, exported so
194
+ * a run measures what compaction kept out of a call with the same rule. */
195
+ export function transcriptTokens(msgs) {
196
+ return handoffTokensOf(msgs, defaultEstimateTokens);
197
+ }
198
+ /** Can these messages (plus `extraTokens` in front of them) go to the model verbatim —
199
+ * inside both the token budget and the message window? */
200
+ function fitsVerbatim(msgs, extraTokens = 0) {
201
+ return msgs.length <= HISTORY_WINDOW && extraTokens + transcriptTokens(msgs) <= HISTORY_TOKEN_BUDGET;
202
+ }
203
+ /**
204
+ * Build the working transcript for a turn. When the whole transcript fits → all of it,
205
+ * verbatim. Otherwise → a memory note of the older part plus EVERY message after it,
206
+ * verbatim; `onFolded` is told about each NEW fold, so the caller can record it (a
207
+ * visible step, a durable consolidation marker).
208
+ *
209
+ * The note is folded INCREMENTALLY, and only when it has to be. A note whose verbatim
210
+ * remainder still fits is reused as-is; once it no longer fits, the previous note plus
211
+ * exactly the messages since it are folded into a new one, up to a token-sized tail
212
+ * ({@link compactTailStartForBudget}). Falls back to drop-oldest when the summarizer is
213
+ * unavailable (`summarize` resolves null). A message-count overflow folds too.
214
+ */
215
+ export async function buildWorkingTranscript(state, systemPrompt, summarize, onFolded) {
216
+ const convo = state.transcript;
217
+ if (fitsVerbatim(convo)) {
218
+ state.compactMemo = null; // back under budget — a later overflow summarizes afresh
219
+ return [{ role: 'system', content: systemPrompt }, ...windowed(convo)];
220
+ }
221
+ // A note written against a transcript that has since been replaced covers nothing here.
222
+ const memo = state.compactMemo && state.compactMemo.coveredEnd <= convo.length ? state.compactMemo : null;
223
+ if (memo && fitsVerbatim(convo.slice(verbatimStart(convo, memo.coveredEnd)), defaultEstimateTokens(memo.note))) {
224
+ return assembleCompacted(systemPrompt, convo, memo.note, memo.coveredEnd);
225
+ }
226
+ const from = memo?.coveredEnd ?? 0;
227
+ const to = Math.max(from, compactTailStartForBudget(convo, COMPACT_TAIL_TOKEN_BUDGET));
228
+ const fold = memo
229
+ ? [{ role: 'assistant', content: memo.note }, ...convo.slice(from, to)]
230
+ : convo.slice(from, to);
231
+ const summary = to > from ? await summarize(fold) : null;
232
+ if (summary == null) {
233
+ // Nothing new to fold, or no summarizer: keep the note already held, else drop-oldest.
234
+ return memo
235
+ ? assembleCompacted(systemPrompt, convo, memo.note, memo.coveredEnd)
236
+ : [{ role: 'system', content: systemPrompt }, ...windowed(convo)];
237
+ }
238
+ const note = `Compressed memory of the first ${to} message(s) of this conversation:\n${summary}`;
239
+ state.compactMemo = { note, coveredEnd: to };
240
+ onFolded({ droppedMessages: to - from, note, coveredEnd: to });
241
+ return assembleCompacted(systemPrompt, convo, note, to);
242
+ }
243
+ //# sourceMappingURL=transcript.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transcript.js","sourceRoot":"","sources":["../../src/working/transcript.ts"],"names":[],"mappings":"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"}