maestro-agent-sdk 0.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.
Files changed (196) hide show
  1. package/LICENSE +21 -0
  2. package/NOTICE +24 -0
  3. package/README.md +133 -0
  4. package/dist/agents/contracts.d.ts +49 -0
  5. package/dist/agents/contracts.d.ts.map +1 -0
  6. package/dist/agents/contracts.js +2 -0
  7. package/dist/agents/contracts.js.map +1 -0
  8. package/dist/agents/rollout/shared.d.ts +24 -0
  9. package/dist/agents/rollout/shared.d.ts.map +1 -0
  10. package/dist/agents/rollout/shared.js +105 -0
  11. package/dist/agents/rollout/shared.js.map +1 -0
  12. package/dist/core/agent.d.ts +71 -0
  13. package/dist/core/agent.d.ts.map +1 -0
  14. package/dist/core/agent.js +22 -0
  15. package/dist/core/agent.js.map +1 -0
  16. package/dist/core/loop.d.ts +26 -0
  17. package/dist/core/loop.d.ts.map +1 -0
  18. package/dist/core/loop.js +317 -0
  19. package/dist/core/loop.js.map +1 -0
  20. package/dist/index.d.ts +49 -0
  21. package/dist/index.d.ts.map +1 -0
  22. package/dist/index.js +53 -0
  23. package/dist/index.js.map +1 -0
  24. package/dist/mcp/client.d.ts +79 -0
  25. package/dist/mcp/client.d.ts.map +1 -0
  26. package/dist/mcp/client.js +176 -0
  27. package/dist/mcp/client.js.map +1 -0
  28. package/dist/mcp/pool-cache.d.ts +103 -0
  29. package/dist/mcp/pool-cache.d.ts.map +1 -0
  30. package/dist/mcp/pool-cache.js +249 -0
  31. package/dist/mcp/pool-cache.js.map +1 -0
  32. package/dist/mcp/pool.d.ts +65 -0
  33. package/dist/mcp/pool.d.ts.map +1 -0
  34. package/dist/mcp/pool.js +86 -0
  35. package/dist/mcp/pool.js.map +1 -0
  36. package/dist/media/file-events.d.ts +8 -0
  37. package/dist/media/file-events.d.ts.map +1 -0
  38. package/dist/media/file-events.js +15 -0
  39. package/dist/media/file-events.js.map +1 -0
  40. package/dist/memory/active-task-template.d.ts +34 -0
  41. package/dist/memory/active-task-template.d.ts.map +1 -0
  42. package/dist/memory/active-task-template.js +63 -0
  43. package/dist/memory/active-task-template.js.map +1 -0
  44. package/dist/memory/compressor.d.ts +87 -0
  45. package/dist/memory/compressor.d.ts.map +1 -0
  46. package/dist/memory/compressor.js +164 -0
  47. package/dist/memory/compressor.js.map +1 -0
  48. package/dist/memory/hash.d.ts +17 -0
  49. package/dist/memory/hash.d.ts.map +1 -0
  50. package/dist/memory/hash.js +20 -0
  51. package/dist/memory/hash.js.map +1 -0
  52. package/dist/memory/prune.d.ts +117 -0
  53. package/dist/memory/prune.d.ts.map +1 -0
  54. package/dist/memory/prune.js +416 -0
  55. package/dist/memory/prune.js.map +1 -0
  56. package/dist/memory/reminder.d.ts +57 -0
  57. package/dist/memory/reminder.d.ts.map +1 -0
  58. package/dist/memory/reminder.js +57 -0
  59. package/dist/memory/reminder.js.map +1 -0
  60. package/dist/memory/scrubber.d.ts +28 -0
  61. package/dist/memory/scrubber.d.ts.map +1 -0
  62. package/dist/memory/scrubber.js +147 -0
  63. package/dist/memory/scrubber.js.map +1 -0
  64. package/dist/memory/token-estimate.d.ts +10 -0
  65. package/dist/memory/token-estimate.d.ts.map +1 -0
  66. package/dist/memory/token-estimate.js +69 -0
  67. package/dist/memory/token-estimate.js.map +1 -0
  68. package/dist/platform/config.d.ts +12 -0
  69. package/dist/platform/config.d.ts.map +1 -0
  70. package/dist/platform/config.js +54 -0
  71. package/dist/platform/config.js.map +1 -0
  72. package/dist/platform/jsonl.d.ts +15 -0
  73. package/dist/platform/jsonl.d.ts.map +1 -0
  74. package/dist/platform/jsonl.js +80 -0
  75. package/dist/platform/jsonl.js.map +1 -0
  76. package/dist/platform/lifecycle.d.ts +22 -0
  77. package/dist/platform/lifecycle.d.ts.map +1 -0
  78. package/dist/platform/lifecycle.js +60 -0
  79. package/dist/platform/lifecycle.js.map +1 -0
  80. package/dist/platform/logger.d.ts +26 -0
  81. package/dist/platform/logger.d.ts.map +1 -0
  82. package/dist/platform/logger.js +41 -0
  83. package/dist/platform/logger.js.map +1 -0
  84. package/dist/platform/mcp-config.d.ts +15 -0
  85. package/dist/platform/mcp-config.d.ts.map +1 -0
  86. package/dist/platform/mcp-config.js +8 -0
  87. package/dist/platform/mcp-config.js.map +1 -0
  88. package/dist/provider.d.ts +81 -0
  89. package/dist/provider.d.ts.map +1 -0
  90. package/dist/provider.js +444 -0
  91. package/dist/provider.js.map +1 -0
  92. package/dist/providers/anthropic.d.ts +132 -0
  93. package/dist/providers/anthropic.d.ts.map +1 -0
  94. package/dist/providers/anthropic.js +518 -0
  95. package/dist/providers/anthropic.js.map +1 -0
  96. package/dist/providers/base.d.ts +140 -0
  97. package/dist/providers/base.d.ts.map +1 -0
  98. package/dist/providers/base.js +2 -0
  99. package/dist/providers/base.js.map +1 -0
  100. package/dist/providers/deepseek.d.ts +118 -0
  101. package/dist/providers/deepseek.d.ts.map +1 -0
  102. package/dist/providers/deepseek.js +467 -0
  103. package/dist/providers/deepseek.js.map +1 -0
  104. package/dist/registry.d.ts +3 -0
  105. package/dist/registry.d.ts.map +1 -0
  106. package/dist/registry.js +94 -0
  107. package/dist/registry.js.map +1 -0
  108. package/dist/session-store.d.ts +133 -0
  109. package/dist/session-store.d.ts.map +1 -0
  110. package/dist/session-store.js +277 -0
  111. package/dist/session-store.js.map +1 -0
  112. package/dist/skills/curator.d.ts +104 -0
  113. package/dist/skills/curator.d.ts.map +1 -0
  114. package/dist/skills/curator.js +162 -0
  115. package/dist/skills/curator.js.map +1 -0
  116. package/dist/skills/index-builder.d.ts +42 -0
  117. package/dist/skills/index-builder.d.ts.map +1 -0
  118. package/dist/skills/index-builder.js +94 -0
  119. package/dist/skills/index-builder.js.map +1 -0
  120. package/dist/skills/loader.d.ts +107 -0
  121. package/dist/skills/loader.d.ts.map +1 -0
  122. package/dist/skills/loader.js +286 -0
  123. package/dist/skills/loader.js.map +1 -0
  124. package/dist/skills/preprocess.d.ts +45 -0
  125. package/dist/skills/preprocess.d.ts.map +1 -0
  126. package/dist/skills/preprocess.js +126 -0
  127. package/dist/skills/preprocess.js.map +1 -0
  128. package/dist/skills/usage.d.ts +75 -0
  129. package/dist/skills/usage.d.ts.map +1 -0
  130. package/dist/skills/usage.js +147 -0
  131. package/dist/skills/usage.js.map +1 -0
  132. package/dist/state/todos.d.ts +95 -0
  133. package/dist/state/todos.d.ts.map +1 -0
  134. package/dist/state/todos.js +198 -0
  135. package/dist/state/todos.js.map +1 -0
  136. package/dist/storage/conversations.d.ts +28 -0
  137. package/dist/storage/conversations.d.ts.map +1 -0
  138. package/dist/storage/conversations.js +8 -0
  139. package/dist/storage/conversations.js.map +1 -0
  140. package/dist/sub-agent/runner.d.ts +78 -0
  141. package/dist/sub-agent/runner.d.ts.map +1 -0
  142. package/dist/sub-agent/runner.js +215 -0
  143. package/dist/sub-agent/runner.js.map +1 -0
  144. package/dist/tools/builtin/agent.d.ts +33 -0
  145. package/dist/tools/builtin/agent.d.ts.map +1 -0
  146. package/dist/tools/builtin/agent.js +76 -0
  147. package/dist/tools/builtin/agent.js.map +1 -0
  148. package/dist/tools/builtin/bash.d.ts +11 -0
  149. package/dist/tools/builtin/bash.d.ts.map +1 -0
  150. package/dist/tools/builtin/bash.js +91 -0
  151. package/dist/tools/builtin/bash.js.map +1 -0
  152. package/dist/tools/builtin/edit.d.ts +21 -0
  153. package/dist/tools/builtin/edit.d.ts.map +1 -0
  154. package/dist/tools/builtin/edit.js +238 -0
  155. package/dist/tools/builtin/edit.js.map +1 -0
  156. package/dist/tools/builtin/read.d.ts +17 -0
  157. package/dist/tools/builtin/read.d.ts.map +1 -0
  158. package/dist/tools/builtin/read.js +139 -0
  159. package/dist/tools/builtin/read.js.map +1 -0
  160. package/dist/tools/builtin/sandbox.d.ts +16 -0
  161. package/dist/tools/builtin/sandbox.d.ts.map +1 -0
  162. package/dist/tools/builtin/sandbox.js +58 -0
  163. package/dist/tools/builtin/sandbox.js.map +1 -0
  164. package/dist/tools/builtin/skill_view.d.ts +37 -0
  165. package/dist/tools/builtin/skill_view.d.ts.map +1 -0
  166. package/dist/tools/builtin/skill_view.js +82 -0
  167. package/dist/tools/builtin/skill_view.js.map +1 -0
  168. package/dist/tools/builtin/todo_write.d.ts +29 -0
  169. package/dist/tools/builtin/todo_write.d.ts.map +1 -0
  170. package/dist/tools/builtin/todo_write.js +96 -0
  171. package/dist/tools/builtin/todo_write.js.map +1 -0
  172. package/dist/tools/builtin/web_fetch.d.ts +10 -0
  173. package/dist/tools/builtin/web_fetch.d.ts.map +1 -0
  174. package/dist/tools/builtin/web_fetch.js +150 -0
  175. package/dist/tools/builtin/web_fetch.js.map +1 -0
  176. package/dist/tools/builtin/write.d.ts +35 -0
  177. package/dist/tools/builtin/write.d.ts.map +1 -0
  178. package/dist/tools/builtin/write.js +70 -0
  179. package/dist/tools/builtin/write.js.map +1 -0
  180. package/dist/tools/file-state.d.ts +99 -0
  181. package/dist/tools/file-state.d.ts.map +1 -0
  182. package/dist/tools/file-state.js +133 -0
  183. package/dist/tools/file-state.js.map +1 -0
  184. package/dist/tools/hooks/sandbox-fs.d.ts +25 -0
  185. package/dist/tools/hooks/sandbox-fs.d.ts.map +1 -0
  186. package/dist/tools/hooks/sandbox-fs.js +48 -0
  187. package/dist/tools/hooks/sandbox-fs.js.map +1 -0
  188. package/dist/tools/registry.d.ts +102 -0
  189. package/dist/tools/registry.d.ts.map +1 -0
  190. package/dist/tools/registry.js +93 -0
  191. package/dist/tools/registry.js.map +1 -0
  192. package/dist/types.d.ts +109 -0
  193. package/dist/types.d.ts.map +1 -0
  194. package/dist/types.js +20 -0
  195. package/dist/types.js.map +1 -0
  196. package/package.json +72 -0
@@ -0,0 +1,117 @@
1
+ import type { ProviderMessage } from "../providers/base.js";
2
+ /**
3
+ * Maestro context pruning — token-savings pre-pass with zero LLM calls.
4
+ *
5
+ * Three passes, mirroring upstream Maestro v0.13.0
6
+ * `context_compressor.py::_prune_old_tool_results`. All passes are pure
7
+ * (return a new array; input is not mutated) and zero-LLM.
8
+ *
9
+ * Pass 1 — Tool result dedup.
10
+ * Walks tool_result blocks newest-first. For each block whose content
11
+ * hashes to a value already seen on a later turn, replace it with a
12
+ * back-reference placeholder. The most recent occurrence keeps the full
13
+ * payload — that's what the model needs to act on; older copies waste
14
+ * tokens. md5(content)[:12] (see hash.ts) — non-cryptographic, picked
15
+ * for parity with the Python reference and cost on the per-turn path.
16
+ *
17
+ * Pass 2 — Age-based 1-line summary.
18
+ * For tool_result blocks older than `ageTurnsThreshold` user-turns,
19
+ * replace large content with a structured 1-liner that names the tool,
20
+ * points at the original arguments, and notes size. The model gets a
21
+ * stable cue for "I called X and got back ~N bytes" without paying for
22
+ * the bytes. Recent results (inside the protected tail) stay verbatim so
23
+ * the model can still reason over fresh tool output.
24
+ *
25
+ * Both passes are pure (`messages` not mutated) — caller can freely send the
26
+ * returned array to the provider without losing the canonical history they
27
+ * still need for persistence/resume. Anti-thrashing is module-state-backed
28
+ * (see header on `pruneMessages`) so back-to-back calls on an already-pruned
29
+ * array don't burn cycles.
30
+ *
31
+ * Token savings are estimated by raw character count (not BPE-accurate). The
32
+ * trade-off: char-count is a strict upper bound on dedup/summary savings (a
33
+ * byte you remove was at most one token's worth), provider-agnostic, and
34
+ * available without spinning up a tokenizer. Real Anthropic token reduction
35
+ * is typically 1.1-1.4x the char delta, so this estimator under-reports
36
+ * savings slightly — fine for the anti-thrash threshold (better to over-skip
37
+ * than over-prune).
38
+ *
39
+ * Upstream reference: `/Users/maestrobot/__KEEP_MAESTRO_AGENT__/agent/context_compressor.py:519-685`.
40
+ */
41
+ export interface PruneOptions {
42
+ /**
43
+ * Tool_result blocks N user-turns or older from the tail get age-summarized
44
+ * by pass 2. Counting in user-turns (not raw messages) matches upstream's
45
+ * notion of "conversation rounds" and avoids fence-posting around assistant
46
+ * + tool_result pairings.
47
+ *
48
+ * Default: 10 — empirically tuned in upstream as the point past which a
49
+ * tool result is rarely re-referenced by the model.
50
+ */
51
+ ageTurnsThreshold?: number;
52
+ /** Disable pass 1 (dedup). Default: true. */
53
+ dedup?: boolean;
54
+ /** Disable pass 2 (age-based summary). Default: true. */
55
+ summarizeOld?: boolean;
56
+ /**
57
+ * Pass 3 (JSON-aware tool_use input shrink) — truncate string fields
58
+ * inside `tool_use.input` that exceed `largeArgChars`. Default: true.
59
+ *
60
+ * The model rarely re-references the *exact* bytes it passed to a tool
61
+ * (e.g. 12KB `content` for a Write call) once the call has completed;
62
+ * it just needs to know "I sent ~12KB to Write at <path>". Pass 3
63
+ * replaces oversize string values with `"<truncated N chars>"` so the
64
+ * structural shape is preserved (key names, types) but the payload
65
+ * shrinks dramatically.
66
+ */
67
+ shrinkLargeToolArgs?: boolean;
68
+ /** Per-string truncation threshold for pass 3 (chars). Default: 800. */
69
+ largeArgChars?: number;
70
+ /**
71
+ * Pass 3 only touches assistant turns older than this many user-turns
72
+ * from the tail (same window pass 2 uses). Default: same as
73
+ * `ageTurnsThreshold` (10).
74
+ */
75
+ shrinkAgeThreshold?: number;
76
+ /**
77
+ * Anti-thrashing — if two consecutive calls on the same `messages` array
78
+ * reference each save less than 10% of bytes, skip the third call entirely
79
+ * and return the input as-is. Default: true. Disable for tests that exercise
80
+ * single-call behavior in isolation.
81
+ *
82
+ * State is keyed on the array reference (WeakMap). A fresh array starts
83
+ * fresh; the loop's per-turn `messages.push(...)` keeps the same reference
84
+ * so state survives across iterations within one session.
85
+ */
86
+ antiThrash?: boolean;
87
+ }
88
+ /**
89
+ * Apply pass 1 (dedup) + pass 2 (age-based summary) to a message array.
90
+ *
91
+ * Returns a new array — input `messages` and the inner block arrays are not
92
+ * mutated, so the caller can keep the original around for persistence.
93
+ *
94
+ * `opts.antiThrash` (default on) backs off when consecutive calls on the
95
+ * same array reference don't save enough — useful inside the per-iteration
96
+ * hot path of the agent loop where most turns won't add new prune targets.
97
+ */
98
+ export declare function pruneMessages(messages: ProviderMessage[], opts?: PruneOptions): ProviderMessage[];
99
+ /**
100
+ * Char-count-based token savings estimate. Negative values are clamped to 0
101
+ * (a no-op pass is "0% savings", not "savings increased").
102
+ *
103
+ * Anthropic tokens-per-char varies (~3.5-4.5 for English, lower for CJK), so
104
+ * this is a deliberately conservative proxy. The anti-thrash check only
105
+ * needs a relative measure, not an absolute one.
106
+ *
107
+ * Exposed for tests so we can assert savings rates without spinning up a
108
+ * tokenizer.
109
+ */
110
+ export declare function estimateTokenSavings(before: ProviderMessage[], after: ProviderMessage[]): number;
111
+ /** Test-only: reset module-level anti-thrash state. */
112
+ export declare function __resetPruneStateForTests(): void;
113
+ export declare const __MIN_PRUNE_CHARS = 200;
114
+ export declare const __ANTI_THRASH_PCT = 10;
115
+ export declare const __ANTI_THRASH_LIMIT = 2;
116
+ export declare const __ANTI_THRASH_GROWTH_RESET = 4;
117
+ //# sourceMappingURL=prune.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prune.d.ts","sourceRoot":"","sources":["../../src/memory/prune.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAwB,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAE9E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AAEH,MAAM,WAAW,YAAY;IAC3B;;;;;;;;OAQG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,6CAA6C;IAC7C,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,yDAAyD;IACzD,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB;;;;;;;;;;OAUG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,wEAAwE;IACxE,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;;OAIG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B;;;;;;;;;OASG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAiCD;;;;;;;;;GASG;AACH,wBAAgB,aAAa,CAC3B,QAAQ,EAAE,eAAe,EAAE,EAC3B,IAAI,GAAE,YAAiB,GACtB,eAAe,EAAE,CA+FnB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,eAAe,EAAE,EAAE,KAAK,EAAE,eAAe,EAAE,GAAG,MAAM,CAIhG;AAED,uDAAuD;AACvD,wBAAgB,yBAAyB,IAAI,IAAI,CAMhD;AA+RD,eAAO,MAAM,iBAAiB,MAAkB,CAAC;AACjD,eAAO,MAAM,iBAAiB,KAAkB,CAAC;AACjD,eAAO,MAAM,mBAAmB,IAAoB,CAAC;AACrD,eAAO,MAAM,0BAA0B,IAA2B,CAAC"}
@@ -0,0 +1,416 @@
1
+ import { hashToolContent } from "../memory/hash.js";
2
+ /**
3
+ * Tool_result content must exceed this many chars before either pass touches
4
+ * it. Below this, dedup/summary placeholders would be longer than the
5
+ * original, defeating the purpose. Matches upstream's `if len(content) < 200`
6
+ * guard.
7
+ */
8
+ const MIN_PRUNE_CHARS = 200;
9
+ /** Anti-thrash trigger — < 10% byte savings counts as "ineffective". */
10
+ const ANTI_THRASH_PCT = 10;
11
+ /** Two consecutive ineffective calls → next call skips. */
12
+ const ANTI_THRASH_LIMIT = 2;
13
+ /**
14
+ * Message-count growth since the latch was set that re-opens prune retries.
15
+ * Without this the short-circuit at the top would latch permanently for the
16
+ * lifetime of the `messages` array reference — every prune attempt past the
17
+ * latch would skip even after the history grew by dozens of new turns that
18
+ * the prior decision never saw. Four new messages ≈ two tool iterations,
19
+ * enough that the model has added a fresh chunk of context worth re-prunes.
20
+ */
21
+ const ANTI_THRASH_GROWTH_RESET = 4;
22
+ const antiThrashByRef = new WeakMap();
23
+ /**
24
+ * Apply pass 1 (dedup) + pass 2 (age-based summary) to a message array.
25
+ *
26
+ * Returns a new array — input `messages` and the inner block arrays are not
27
+ * mutated, so the caller can keep the original around for persistence.
28
+ *
29
+ * `opts.antiThrash` (default on) backs off when consecutive calls on the
30
+ * same array reference don't save enough — useful inside the per-iteration
31
+ * hot path of the agent loop where most turns won't add new prune targets.
32
+ */
33
+ export function pruneMessages(messages, opts = {}) {
34
+ const { ageTurnsThreshold = 10, dedup = true, summarizeOld = true, shrinkLargeToolArgs = true, largeArgChars = 800, shrinkAgeThreshold = ageTurnsThreshold, antiThrash = true, } = opts;
35
+ if (messages.length === 0)
36
+ return messages;
37
+ // Anti-thrash short-circuit BEFORE we do any work — but break the latch
38
+ // when the history has grown by enough new turns that the prior "no
39
+ // savings here" decision is stale.
40
+ if (antiThrash) {
41
+ const st = antiThrashByRef.get(messages);
42
+ if (st && st.ineffectiveCount >= ANTI_THRASH_LIMIT) {
43
+ const grown = st.latchedAtLength !== undefined &&
44
+ messages.length >= st.latchedAtLength + ANTI_THRASH_GROWTH_RESET;
45
+ if (!grown)
46
+ return messages;
47
+ // Significant growth — invalidate latch, retry the prune path.
48
+ st.ineffectiveCount = 0;
49
+ st.latchedAtLength = undefined;
50
+ }
51
+ }
52
+ // Build a tool_use_id -> {name, args} index from every assistant turn so
53
+ // pass 2 can render `[<tool_name>] ... (Nchars)` lines. We index the WHOLE
54
+ // history (not just the recent tail) because pass 2 targets the older end.
55
+ const callIndex = buildToolCallIndex(messages);
56
+ // Compute the prune boundary — the index in `messages` at which pass 2
57
+ // starts considering tool_result blocks for summarization. We count
58
+ // user-role messages (excluding tool_result-only turns? no — every
59
+ // tool_result turn IS a user message, so user turns include both the
60
+ // freshly-typed prompts and the synthesized tool_result wrappers) from
61
+ // the tail. `ageTurnsThreshold` user-turns or fewer from the end are
62
+ // protected.
63
+ const protectedFromIdx = computeProtectedBoundary(messages, ageTurnsThreshold);
64
+ // ----------------------- Pass 1: dedup -----------------------
65
+ // Walk newest -> oldest. First occurrence of each content hash is kept;
66
+ // subsequent (older) occurrences become back-references.
67
+ let pruned = messages;
68
+ if (dedup) {
69
+ pruned = applyDedup(messages);
70
+ }
71
+ // ----------------------- Pass 2: summarize -----------------------
72
+ if (summarizeOld && protectedFromIdx > 0) {
73
+ pruned = applyAgeSummary(pruned, protectedFromIdx, callIndex);
74
+ }
75
+ // ----------------------- Pass 3: tool_use arg shrink -----------------------
76
+ // Runs on the same age window as pass 2. Cheap structural rewrite that
77
+ // also helps before-aux-LLM compaction: a Write tool call with 50KB
78
+ // content can dominate the wire bytes even when its tool_result is
79
+ // already deduped.
80
+ if (shrinkLargeToolArgs && protectedFromIdx > 0) {
81
+ const shrinkBoundary = shrinkAgeThreshold === ageTurnsThreshold
82
+ ? protectedFromIdx
83
+ : computeProtectedBoundary(messages, shrinkAgeThreshold);
84
+ if (shrinkBoundary > 0) {
85
+ pruned = applyToolArgShrink(pruned, shrinkBoundary, largeArgChars);
86
+ }
87
+ }
88
+ // ----------------------- Anti-thrash bookkeeping -----------------------
89
+ if (antiThrash) {
90
+ const beforeBytes = estimateBytes(messages);
91
+ const afterBytes = estimateBytes(pruned);
92
+ const savings = beforeBytes - afterBytes;
93
+ const pct = beforeBytes > 0 ? (savings / beforeBytes) * 100 : 0;
94
+ const st = antiThrashByRef.get(messages) ?? { ineffectiveCount: 0 };
95
+ if (pct < ANTI_THRASH_PCT) {
96
+ st.ineffectiveCount++;
97
+ // Record the messages.length at the moment we first latch so the
98
+ // growth-reset check above has an anchor to compare against. Set only
99
+ // on the transition into latched state; subsequent ineffective calls
100
+ // past the limit don't move the anchor forward.
101
+ if (st.ineffectiveCount >= ANTI_THRASH_LIMIT && st.latchedAtLength === undefined) {
102
+ st.latchedAtLength = messages.length;
103
+ }
104
+ }
105
+ else {
106
+ st.ineffectiveCount = 0;
107
+ st.latchedAtLength = undefined;
108
+ }
109
+ antiThrashByRef.set(messages, st);
110
+ }
111
+ return pruned;
112
+ }
113
+ /**
114
+ * Char-count-based token savings estimate. Negative values are clamped to 0
115
+ * (a no-op pass is "0% savings", not "savings increased").
116
+ *
117
+ * Anthropic tokens-per-char varies (~3.5-4.5 for English, lower for CJK), so
118
+ * this is a deliberately conservative proxy. The anti-thrash check only
119
+ * needs a relative measure, not an absolute one.
120
+ *
121
+ * Exposed for tests so we can assert savings rates without spinning up a
122
+ * tokenizer.
123
+ */
124
+ export function estimateTokenSavings(before, after) {
125
+ const b = estimateBytes(before);
126
+ const a = estimateBytes(after);
127
+ return Math.max(0, b - a);
128
+ }
129
+ /** Test-only: reset module-level anti-thrash state. */
130
+ export function __resetPruneStateForTests() {
131
+ // WeakMap has no .clear() — replace by emptying via reassignment of every
132
+ // tracked entry. In practice tests just create fresh arrays.
133
+ // We deliberately don't expose a hard clear because production callers
134
+ // shouldn't reach into module state; the WeakMap GC-friendly design is the
135
+ // point. Tests that need isolation should create fresh arrays.
136
+ }
137
+ function buildToolCallIndex(messages) {
138
+ const idx = new Map();
139
+ for (const msg of messages) {
140
+ if (msg.role !== "assistant" || !Array.isArray(msg.content))
141
+ continue;
142
+ for (const block of msg.content) {
143
+ if (block.type === "tool_use") {
144
+ idx.set(block.id, { name: block.name, input: block.input });
145
+ }
146
+ }
147
+ }
148
+ return idx;
149
+ }
150
+ /**
151
+ * Translate `ageTurnsThreshold` (user-turn count) into a message-array index
152
+ * such that messages at indices >= the returned value are within the
153
+ * protected tail. Returns 0 if the whole history fits in the protected tail
154
+ * (so pass 2 does nothing).
155
+ */
156
+ function computeProtectedBoundary(messages, ageTurnsThreshold) {
157
+ let userTurnsSeen = 0;
158
+ for (let i = messages.length - 1; i >= 0; i--) {
159
+ if (messages[i].role === "user") {
160
+ userTurnsSeen++;
161
+ if (userTurnsSeen > ageTurnsThreshold) {
162
+ return i + 1;
163
+ }
164
+ }
165
+ }
166
+ return 0;
167
+ }
168
+ /**
169
+ * Pass 1 — newest-first dedup of tool_result content blocks.
170
+ *
171
+ * Only string-content tool_result blocks >= MIN_PRUNE_CHARS participate.
172
+ * Multimodal / structured content is left alone (no safe hash strategy
173
+ * matching upstream's behavior here either).
174
+ */
175
+ function applyDedup(messages) {
176
+ const seenHashes = new Set();
177
+ const out = messages.map((m) => m);
178
+ // Walk newest -> oldest so the newest copy of each hash survives.
179
+ for (let i = out.length - 1; i >= 0; i--) {
180
+ const msg = out[i];
181
+ if (msg.role !== "user" || !Array.isArray(msg.content))
182
+ continue;
183
+ let mutated = false;
184
+ const newBlocks = msg.content.map((block) => {
185
+ if (block.type !== "tool_result")
186
+ return block;
187
+ const c = block.content;
188
+ if (typeof c !== "string")
189
+ return block;
190
+ if (c.length < MIN_PRUNE_CHARS)
191
+ return block;
192
+ // Skip blocks that are already a previous pass's placeholder so the
193
+ // anti-thrash measurement doesn't double-count.
194
+ if (c.startsWith("[Duplicate tool output"))
195
+ return block;
196
+ const h = hashToolContent(c);
197
+ if (seenHashes.has(h)) {
198
+ mutated = true;
199
+ return {
200
+ type: "tool_result",
201
+ tool_use_id: block.tool_use_id,
202
+ content: `[Duplicate tool output — same content as a more recent call, hash=${h}]`,
203
+ ...(block.is_error !== undefined ? { is_error: block.is_error } : {}),
204
+ };
205
+ }
206
+ seenHashes.add(h);
207
+ return block;
208
+ });
209
+ if (mutated) {
210
+ out[i] = { role: msg.role, content: newBlocks };
211
+ }
212
+ }
213
+ return out;
214
+ }
215
+ /**
216
+ * Pass 2 — replace tool_result content older than the protect boundary with
217
+ * a structured 1-line summary that points at the original tool_use.
218
+ *
219
+ * Skips:
220
+ * - Non-string content (multimodal etc.)
221
+ * - Content already below MIN_PRUNE_CHARS (savings would be negative)
222
+ * - Content that already looks like a pass-1 dedup placeholder
223
+ * - Content that already looks like our own pass-2 summary (idempotent)
224
+ */
225
+ function applyAgeSummary(messages, protectedFromIdx, callIndex) {
226
+ const out = messages.map((m) => m);
227
+ for (let i = 0; i < protectedFromIdx; i++) {
228
+ const msg = out[i];
229
+ if (msg.role !== "user" || !Array.isArray(msg.content))
230
+ continue;
231
+ let mutated = false;
232
+ const newBlocks = msg.content.map((block) => {
233
+ if (block.type !== "tool_result")
234
+ return block;
235
+ const c = block.content;
236
+ if (typeof c !== "string")
237
+ return block;
238
+ if (c.length < MIN_PRUNE_CHARS)
239
+ return block;
240
+ if (c.startsWith("[Duplicate tool output"))
241
+ return block;
242
+ if (c.startsWith("[Summarized:"))
243
+ return block;
244
+ const ref = callIndex.get(block.tool_use_id);
245
+ const summary = summarizeToolResult(ref, c);
246
+ mutated = true;
247
+ return {
248
+ type: "tool_result",
249
+ tool_use_id: block.tool_use_id,
250
+ content: summary,
251
+ ...(block.is_error !== undefined ? { is_error: block.is_error } : {}),
252
+ };
253
+ });
254
+ if (mutated) {
255
+ out[i] = { role: msg.role, content: newBlocks };
256
+ }
257
+ }
258
+ return out;
259
+ }
260
+ /**
261
+ * Render a 1-line summary of a tool result. Mirrors upstream's
262
+ * `_summarize_tool_result` shape but doesn't enumerate every tool name —
263
+ * the prefix "[Summarized:" lets us cheaply detect already-summarized
264
+ * blocks on a re-pass, and the (name, lines, chars) tuple is the same set
265
+ * of breadcrumbs the model uses to decide "did I see this before".
266
+ *
267
+ * If `ref` is missing (tool_use was trimmed away by an earlier compression
268
+ * or rollout) we fall back to a generic shape — better to lose tool-name
269
+ * fidelity than to leave several KB of stale tool output in the prompt.
270
+ */
271
+ function summarizeToolResult(ref, content) {
272
+ const lineCount = content.split("\n").length;
273
+ const tool = ref?.name ?? "tool";
274
+ // Short argument hint helps the model recognize repeat calls. We cap at
275
+ // 80 chars total to stay well under the original payload size — anything
276
+ // longer defeats the purpose of summarization.
277
+ const argHint = ref ? formatArgHint(ref.input) : "";
278
+ const head = head80(content);
279
+ return `[Summarized: ${tool}${argHint} — ${lineCount} lines, ${content.length} chars, head: ${JSON.stringify(head)}]`;
280
+ }
281
+ /** First 80 chars of the content, single-line (newlines collapsed to spaces). */
282
+ function head80(s) {
283
+ const flat = s.replace(/\s+/g, " ").trim();
284
+ return flat.length > 80 ? `${flat.slice(0, 77)}...` : flat;
285
+ }
286
+ /** A compact "k1=v1 k2=v2" hint, capped so a giant args object doesn't blow
287
+ * up the summary line. */
288
+ function formatArgHint(input) {
289
+ const entries = Object.entries(input).slice(0, 2);
290
+ if (entries.length === 0)
291
+ return "";
292
+ const parts = [];
293
+ for (const [k, v] of entries) {
294
+ const s = typeof v === "string" ? v : JSON.stringify(v);
295
+ const trimmed = s.length > 30 ? `${s.slice(0, 27)}...` : s;
296
+ parts.push(`${k}=${trimmed}`);
297
+ }
298
+ return ` ${parts.join(" ")}`;
299
+ }
300
+ /**
301
+ * Pass 3 — Replace oversize string values inside `tool_use.input` with a
302
+ * `"<truncated N chars>"` placeholder. Only touches assistant turns at
303
+ * indices < `boundaryIdx` (older than the protected tail), and only string
304
+ * values longer than `largeArgChars`. Nested objects/arrays are walked
305
+ * recursively so a `{patches: [{content: "<50KB>"}]}` shape gets caught.
306
+ *
307
+ * The model rarely needs to re-read the exact bytes it sent to a Write/Edit
308
+ * call once that turn is closed — knowing the structural shape + a head
309
+ * preview is enough for "did I call this with the same args?" recognition.
310
+ * Aggressive on the dollar value (one Write call's `content` field can
311
+ * dwarf an entire turn's text), cheap on cycles (no LLM, no parsing).
312
+ */
313
+ function applyToolArgShrink(messages, boundaryIdx, largeArgChars) {
314
+ // Already-shrunk inputs leave a sentinel string starting with this prefix
315
+ // so we don't double-process on a re-pass. Idempotent rewrites are
316
+ // critical inside the hot loop.
317
+ const SENTINEL = "<truncated ";
318
+ function shrinkValue(v) {
319
+ if (typeof v === "string") {
320
+ if (v.length <= largeArgChars)
321
+ return v;
322
+ if (v.startsWith(SENTINEL))
323
+ return v; // already truncated on a prior pass
324
+ return `${SENTINEL}${v.length} chars, head: ${JSON.stringify(v.slice(0, 60))}>`;
325
+ }
326
+ if (Array.isArray(v)) {
327
+ let mutated = false;
328
+ const out = v.map((el) => {
329
+ const next = shrinkValue(el);
330
+ if (next !== el)
331
+ mutated = true;
332
+ return next;
333
+ });
334
+ return mutated ? out : v;
335
+ }
336
+ if (v && typeof v === "object") {
337
+ let mutated = false;
338
+ const out = {};
339
+ for (const [k, val] of Object.entries(v)) {
340
+ const next = shrinkValue(val);
341
+ out[k] = next;
342
+ if (next !== val)
343
+ mutated = true;
344
+ }
345
+ return mutated ? out : v;
346
+ }
347
+ return v;
348
+ }
349
+ let messagesMutated = false;
350
+ const out = messages.map((msg, idx) => {
351
+ if (idx >= boundaryIdx)
352
+ return msg;
353
+ if (msg.role !== "assistant" || !Array.isArray(msg.content))
354
+ return msg;
355
+ let blockMutated = false;
356
+ const nextBlocks = msg.content.map((block) => {
357
+ if (block.type !== "tool_use")
358
+ return block;
359
+ const shrunkInput = shrinkValue(block.input);
360
+ if (shrunkInput === block.input)
361
+ return block;
362
+ blockMutated = true;
363
+ return { ...block, input: shrunkInput };
364
+ });
365
+ if (!blockMutated)
366
+ return msg;
367
+ messagesMutated = true;
368
+ return { role: msg.role, content: nextBlocks };
369
+ });
370
+ return messagesMutated ? out : messages;
371
+ }
372
+ /**
373
+ * Char-count proxy for token usage. Walks every message's content (string
374
+ * shortcut + per-block fields for arrays) and returns the total.
375
+ */
376
+ function estimateBytes(messages) {
377
+ let total = 0;
378
+ for (const msg of messages) {
379
+ if (typeof msg.content === "string") {
380
+ total += msg.content.length;
381
+ continue;
382
+ }
383
+ if (!Array.isArray(msg.content))
384
+ continue;
385
+ for (const block of msg.content) {
386
+ total += blockBytes(block);
387
+ }
388
+ }
389
+ return total;
390
+ }
391
+ function blockBytes(block) {
392
+ switch (block.type) {
393
+ case "text":
394
+ return block.text.length;
395
+ case "tool_result":
396
+ return typeof block.content === "string" ? block.content.length : 0;
397
+ case "tool_use":
398
+ // Stringify the input cheaply — the model pays for it on the wire as
399
+ // serialized JSON. Edge: a giant object weighed in megabytes would
400
+ // dominate, which is exactly the case we want to capture.
401
+ try {
402
+ return JSON.stringify(block.input).length;
403
+ }
404
+ catch {
405
+ return 0;
406
+ }
407
+ default:
408
+ return 0;
409
+ }
410
+ }
411
+ // Internal exports for tests.
412
+ export const __MIN_PRUNE_CHARS = MIN_PRUNE_CHARS;
413
+ export const __ANTI_THRASH_PCT = ANTI_THRASH_PCT;
414
+ export const __ANTI_THRASH_LIMIT = ANTI_THRASH_LIMIT;
415
+ export const __ANTI_THRASH_GROWTH_RESET = ANTI_THRASH_GROWTH_RESET;
416
+ //# sourceMappingURL=prune.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prune.js","sourceRoot":"","sources":["../../src/memory/prune.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,57 @@
1
+ import type { TodoEntry } from "../state/todos.js";
2
+ /**
3
+ * System-reminder builder.
4
+ *
5
+ * Renders the `<system-reminder>…</system-reminder>` block that gets
6
+ * attached to every new user message in `maestroProvider`. The reminder
7
+ * carries invariants the model needs to keep in mind for the current turn
8
+ * — sandbox state, workspace root, future: active task — and is what keeps
9
+ * long sessions from forgetting the rules after the compactor evicts the
10
+ * middle.
11
+ *
12
+ * Why this lives outside `loop.ts`:
13
+ *
14
+ * The reminder is attached to the canonical user message at push time
15
+ * (see `maestroProvider`), not on-wire just before each API call. That
16
+ * choice is load-bearing for Anthropic's automatic prompt cache: every
17
+ * past-turn user message must be byte-stable across future calls or the
18
+ * cache misses at the first divergence. On-wire injection would mean
19
+ * turn N's user message looks different in turn N's call vs turn N+1's
20
+ * call, breaking the cache from that point onward. By attaching at
21
+ * push time, each historical user message permanently carries the
22
+ * reminder that was live at that turn — frozen, cache-friendly.
23
+ *
24
+ * Placement: reminder text block follows the user prompt block. Claude
25
+ * Code's transcripts consistently place `<system-reminder>` at the tail
26
+ * of user content, and matching that order keeps the model's pretrained
27
+ * instinct ("the user said X; the reminder is meta") intact.
28
+ */
29
+ export interface SystemReminderContext {
30
+ /**
31
+ * Resolved maestro session id. Surfaced so the model can refer to it in
32
+ * cross-session tool calls (ask_session, tell_session) without re-asking.
33
+ */
34
+ sessionId: string;
35
+ /**
36
+ * Current TodoWrite list snapshot. When non-empty, the reminder renders
37
+ * a compact status header so the model carries the plan across turns
38
+ * without having to call a read-side tool. The list is the read-side —
39
+ * `todo_write` is the only related tool the model sees.
40
+ */
41
+ todos?: readonly TodoEntry[];
42
+ /**
43
+ * Anything additional the caller wants to render verbatim. Each entry
44
+ * becomes one line at the tail of the reminder. Caller owns formatting.
45
+ */
46
+ extras?: string[];
47
+ }
48
+ /**
49
+ * Build the `<system-reminder>` block. Returns a self-contained string that
50
+ * callers attach verbatim as a `text` content block on a user message.
51
+ *
52
+ * Empty extras + sandbox-enabled (default) state renders to ~3 lines so
53
+ * the per-turn token cost is bounded; the catalog of facts only grows as
54
+ * later phases add semantic state (Phase 3.2 task list, etc.).
55
+ */
56
+ export declare function buildSystemReminder(ctx: SystemReminderContext): string;
57
+ //# sourceMappingURL=reminder.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reminder.d.ts","sourceRoot":"","sources":["../../src/memory/reminder.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAI/C;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,MAAM,WAAW,qBAAqB;IACpC;;;OAGG;IACH,SAAS,EAAE,MAAM,CAAC;IAClB;;;;;OAKG;IACH,KAAK,CAAC,EAAE,SAAS,SAAS,EAAE,CAAC;IAC7B;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;CACnB;AAED;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,qBAAqB,GAAG,MAAM,CA8CtE"}