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,86 @@
1
+ import { getOrStartClient, releaseClient } from "../mcp/pool-cache.js";
2
+ import { logger } from "../platform/logger.js";
3
+ /**
4
+ * Acquire a lease on every MCP server in `servers` from the cache and collect
5
+ * their tool schemas. On cache miss the server is spawned fresh; on hit the
6
+ * pre-warmed client is reused (no spawn cost, no handshake cost).
7
+ *
8
+ * Failure isolation: one server failing to start does not abort the others,
9
+ * we log and continue. Partial MCP availability is preferable to losing the
10
+ * whole toolset for a turn — same stance as the pre-cache implementation and
11
+ * the Playwright exit-propagation path.
12
+ *
13
+ * `ctx` scopes the cache key. Pass userId/session/groupId from `AgentQueryOptions`.
14
+ * Same spec across two contexts ⇒ two separate cached clients (correct: e.g.
15
+ * playwright stdio with `--user-data-dir` differs per user-session anyway, but
16
+ * scoping by context keeps the invariant even when specs happen to match).
17
+ */
18
+ export async function startMcpPool(servers, ctx = {}) {
19
+ const clients = [];
20
+ const tools = [];
21
+ const entries = Object.entries(servers ?? {});
22
+ const results = await Promise.allSettled(entries.map(async ([name, spec]) => {
23
+ const client = await getOrStartClient(ctx, name, spec);
24
+ const ts = await client.listTools();
25
+ return { client, tools: ts };
26
+ }));
27
+ for (let i = 0; i < results.length; i++) {
28
+ const r = results[i];
29
+ const name = entries[i][0];
30
+ if (r.status === "rejected") {
31
+ logger.warn({ err: r.reason, server: name }, "maestro mcp pool: server failed to start, skipping");
32
+ continue;
33
+ }
34
+ clients.push(r.value.client);
35
+ tools.push(...r.value.tools);
36
+ }
37
+ let closed = false;
38
+ return {
39
+ clients,
40
+ tools,
41
+ async close() {
42
+ if (closed)
43
+ return;
44
+ closed = true;
45
+ // Release leases — actual client.close() runs in pool-cache.ts when the
46
+ // idle TTL elapses or LRU cap is exceeded.
47
+ for (const c of clients)
48
+ releaseClient(c);
49
+ },
50
+ };
51
+ }
52
+ /**
53
+ * Register every MCP tool in the pool into the given Maestro ToolRegistry.
54
+ *
55
+ * Name collisions (e.g. a builtin already owns the same prefixed name) are
56
+ * logged and skipped rather than thrown — builtins win because they're more
57
+ * stable than MCP servers that come and go per turn.
58
+ *
59
+ * Optional `abortSignal` is forwarded into every MCP `tools/call` so a
60
+ * Maestro-level abort cancels in-flight JSON-RPC requests instead of leaving
61
+ * them blocked on a dead/slow server. The cached client itself stays alive —
62
+ * only the in-flight RPC is cancelled, so the next turn can reuse it.
63
+ */
64
+ export function registerMcpTools(registry, pool, abortSignal) {
65
+ const byName = new Map(pool.clients.map((c) => [c.name, c]));
66
+ for (const t of pool.tools) {
67
+ if (registry.has(t.schema.name)) {
68
+ logger.warn({ name: t.schema.name, server: t.serverName }, "maestro mcp pool: tool name collision — skipping");
69
+ continue;
70
+ }
71
+ const handler = {
72
+ schema: t.schema,
73
+ async execute(input) {
74
+ const client = byName.get(t.serverName);
75
+ if (!client) {
76
+ return JSON.stringify({
77
+ error: `mcp client '${t.serverName}' not available`,
78
+ });
79
+ }
80
+ return client.callTool(t.originalName, input, abortSignal);
81
+ },
82
+ };
83
+ registry.register(handler);
84
+ }
85
+ }
86
+ //# sourceMappingURL=pool.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pool.js","sourceRoot":"","sources":["../../src/mcp/pool.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ import type { UnifiedEvent } from "../types.js";
2
+ /**
3
+ * Yield `file` events for every `[FILE:/path]` tag found in `text`.
4
+ * Providers call this when the host needs to react to file references the
5
+ * model emits inline (preview, attachment upload, etc).
6
+ */
7
+ export declare function extractFileEvents(text: string, source: string): Generator<UnifiedEvent>;
8
+ //# sourceMappingURL=file-events.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-events.d.ts","sourceRoot":"","sources":["../../src/media/file-events.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAE5C;;;;GAIG;AACH,wBAAiB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,YAAY,CAAC,CAOxF"}
@@ -0,0 +1,15 @@
1
+ import { FILE_TAG_REGEX } from "../platform/config.js";
2
+ /**
3
+ * Yield `file` events for every `[FILE:/path]` tag found in `text`.
4
+ * Providers call this when the host needs to react to file references the
5
+ * model emits inline (preview, attachment upload, etc).
6
+ */
7
+ export function* extractFileEvents(text, source) {
8
+ const tagRegex = new RegExp(FILE_TAG_REGEX.source, "gi");
9
+ let match = tagRegex.exec(text);
10
+ while (match !== null) {
11
+ yield { type: "file", path: match[1], source, origin: "tag" };
12
+ match = tagRegex.exec(text);
13
+ }
14
+ }
15
+ //# sourceMappingURL=file-events.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-events.js","sourceRoot":"","sources":["../../src/media/file-events.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAGnD;;;;GAIG;AACH,MAAM,SAAS,CAAC,CAAC,iBAAiB,CAAC,IAAY,EAAE,MAAc;IAC7D,MAAM,QAAQ,GAAG,IAAI,MAAM,CAAC,cAAc,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IACzD,IAAI,KAAK,GAA2B,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACxD,OAAO,KAAK,KAAK,IAAI,EAAE,CAAC;QACtB,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;QAC9D,KAAK,GAAG,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC9B,CAAC;AACH,CAAC"}
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Active Task template — the system prompt fed to the auxiliary LLM that
3
+ * compresses prior conversation history into a single structured summary
4
+ * block.
5
+ *
6
+ * Why a structured template rather than a free-form "summarize this"?
7
+ * - Free-form summaries drift in shape between turns, which means the
8
+ * compressed prefix changes across compactions and breaks Anthropic
9
+ * prompt-cache hits on the cached body. Fixed headers stabilize the
10
+ * cacheable prefix.
11
+ * - The model needs to know not just what happened but **what's still
12
+ * pending**. Without an explicit "Pending" section the summary
13
+ * degenerates into a recap; the model loses track of the actual task
14
+ * it should be working on next.
15
+ * - The headers (Active Task / Goal / Pending / Files / Recent context)
16
+ * match the structure upstream uses, calibrated against many real
17
+ * compaction events on long sessions.
18
+ *
19
+ * Upstream reference: `/Users/maestrobot/__KEEP_MAESTRO_AGENT__/agent/context_compressor.py`
20
+ * (look for the "ACTIVE_TASK_SUMMARY_TEMPLATE" / "compression_system_prompt"
21
+ * constants). We keep the schema verbatim so summaries between agents stay
22
+ * mutually intelligible if the topic later switches via set_agent.
23
+ */
24
+ export declare const ACTIVE_TASK_TEMPLATE = "You are compressing a long agent conversation so the main agent can continue\nwithout losing context. Produce a single concise summary using EXACTLY these\nsection headers, in this order:\n\n## Active Task\nOne sentence: what is the agent currently working on?\n\n## Goal\nOne or two sentences: the user's overall objective in this session.\n\n## Pending\nBulleted list of unresolved items, decisions to make, or work explicitly\ndeferred. Use \"(blocked: <reason>)\" when applicable.\n\n## Files\nBulleted list of `absolute/paths` touched or referenced (read, written,\ninspected). Skip if none.\n\n## Recent context\n3\u20135 bullets capturing the most recent tool calls + their salient outputs.\nPrefer specifics (paths, line numbers, exit codes, key values) over generic\nrecaps. Skip details that have no bearing on the next step.\n\nRULES:\n- Output ONLY the five sections above, with no preamble or postscript.\n- Do NOT echo the user's words verbatim \u2014 paraphrase tightly.\n- Do NOT invent file paths or facts not present in the transcript.\n- Keep the entire summary under 1500 words.";
25
+ /** Header line for the summary user message that the main loop sees in
26
+ * place of the compressed history. Surrounded by visible markers so the
27
+ * main model recognizes this as a system-injected compaction, not normal
28
+ * user input. Matches upstream's fence convention so cross-agent rollouts
29
+ * reading our compacted maestro session see a familiar marker. */
30
+ export declare const COMPACTED_MARKER_OPEN = "<compacted-history>";
31
+ export declare const COMPACTED_MARKER_CLOSE = "</compacted-history>";
32
+ /** Wrap a raw summary in the fence the main loop expects on the next turn. */
33
+ export declare function wrapCompactedSummary(summary: string): string;
34
+ //# sourceMappingURL=active-task-template.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"active-task-template.d.ts","sourceRoot":"","sources":["../../src/memory/active-task-template.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,eAAO,MAAM,oBAAoB,+kCA2BW,CAAC;AAE7C;;;;mEAImE;AACnE,eAAO,MAAM,qBAAqB,wBAAwB,CAAC;AAC3D,eAAO,MAAM,sBAAsB,yBAAyB,CAAC;AAE7D,8EAA8E;AAC9E,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAE5D"}
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Active Task template — the system prompt fed to the auxiliary LLM that
3
+ * compresses prior conversation history into a single structured summary
4
+ * block.
5
+ *
6
+ * Why a structured template rather than a free-form "summarize this"?
7
+ * - Free-form summaries drift in shape between turns, which means the
8
+ * compressed prefix changes across compactions and breaks Anthropic
9
+ * prompt-cache hits on the cached body. Fixed headers stabilize the
10
+ * cacheable prefix.
11
+ * - The model needs to know not just what happened but **what's still
12
+ * pending**. Without an explicit "Pending" section the summary
13
+ * degenerates into a recap; the model loses track of the actual task
14
+ * it should be working on next.
15
+ * - The headers (Active Task / Goal / Pending / Files / Recent context)
16
+ * match the structure upstream uses, calibrated against many real
17
+ * compaction events on long sessions.
18
+ *
19
+ * Upstream reference: `/Users/maestrobot/__KEEP_MAESTRO_AGENT__/agent/context_compressor.py`
20
+ * (look for the "ACTIVE_TASK_SUMMARY_TEMPLATE" / "compression_system_prompt"
21
+ * constants). We keep the schema verbatim so summaries between agents stay
22
+ * mutually intelligible if the topic later switches via set_agent.
23
+ */
24
+ export const ACTIVE_TASK_TEMPLATE = `You are compressing a long agent conversation so the main agent can continue
25
+ without losing context. Produce a single concise summary using EXACTLY these
26
+ section headers, in this order:
27
+
28
+ ## Active Task
29
+ One sentence: what is the agent currently working on?
30
+
31
+ ## Goal
32
+ One or two sentences: the user's overall objective in this session.
33
+
34
+ ## Pending
35
+ Bulleted list of unresolved items, decisions to make, or work explicitly
36
+ deferred. Use "(blocked: <reason>)" when applicable.
37
+
38
+ ## Files
39
+ Bulleted list of \`absolute/paths\` touched or referenced (read, written,
40
+ inspected). Skip if none.
41
+
42
+ ## Recent context
43
+ 3–5 bullets capturing the most recent tool calls + their salient outputs.
44
+ Prefer specifics (paths, line numbers, exit codes, key values) over generic
45
+ recaps. Skip details that have no bearing on the next step.
46
+
47
+ RULES:
48
+ - Output ONLY the five sections above, with no preamble or postscript.
49
+ - Do NOT echo the user's words verbatim — paraphrase tightly.
50
+ - Do NOT invent file paths or facts not present in the transcript.
51
+ - Keep the entire summary under 1500 words.`;
52
+ /** Header line for the summary user message that the main loop sees in
53
+ * place of the compressed history. Surrounded by visible markers so the
54
+ * main model recognizes this as a system-injected compaction, not normal
55
+ * user input. Matches upstream's fence convention so cross-agent rollouts
56
+ * reading our compacted maestro session see a familiar marker. */
57
+ export const COMPACTED_MARKER_OPEN = "<compacted-history>";
58
+ export const COMPACTED_MARKER_CLOSE = "</compacted-history>";
59
+ /** Wrap a raw summary in the fence the main loop expects on the next turn. */
60
+ export function wrapCompactedSummary(summary) {
61
+ return `${COMPACTED_MARKER_OPEN}\n${summary.trim()}\n${COMPACTED_MARKER_CLOSE}`;
62
+ }
63
+ //# sourceMappingURL=active-task-template.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"active-task-template.js","sourceRoot":"","sources":["../../src/memory/active-task-template.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,MAAM,CAAC,MAAM,oBAAoB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;4CA2BQ,CAAC;AAE7C;;;;mEAImE;AACnE,MAAM,CAAC,MAAM,qBAAqB,GAAG,qBAAqB,CAAC;AAC3D,MAAM,CAAC,MAAM,sBAAsB,GAAG,sBAAsB,CAAC;AAE7D,8EAA8E;AAC9E,MAAM,UAAU,oBAAoB,CAAC,OAAe;IAClD,OAAO,GAAG,qBAAqB,KAAK,OAAO,CAAC,IAAI,EAAE,KAAK,sBAAsB,EAAE,CAAC;AAClF,CAAC"}
@@ -0,0 +1,87 @@
1
+ import type { Provider, ProviderMessage } from "../providers/base.js";
2
+ /**
3
+ * Maestro context auto-compaction.
4
+ *
5
+ * When estimated tokens exceed `triggerRatio` × `contextWindow`, dispatch a
6
+ * cheap aux LLM (default `claude-haiku-4-5-...`) to summarize the middle
7
+ * slice of the conversation into the Active Task template, then return a
8
+ * new message array shaped as:
9
+ *
10
+ * [ ...head_protected, { role: "user", content: "<compacted-history>..." },
11
+ * ...tail_protected ]
12
+ *
13
+ * Head + tail protection rationale:
14
+ * - Head: the first user prompt + first assistant turn anchor the user's
15
+ * actual ask. Losing them to summarization makes the next compaction
16
+ * produce a recap with no goal, which cascades into hallucination.
17
+ * - Tail: the last few turns are the model's working memory; folding them
18
+ * into a summary destroys the in-progress reasoning.
19
+ *
20
+ * The middle is what gets compressed. We feed the aux LLM the raw
21
+ * ProviderMessage[] slice (with the Active Task system prompt) and replace
22
+ * it in the returned array with a single user message containing the fenced
23
+ * summary. Anthropic's tool_use/tool_result pairing requirement is
24
+ * preserved by snapping head/tail boundaries to message boundaries (we
25
+ * never split a user/assistant pair).
26
+ *
27
+ * Fallbacks:
28
+ * - Already below threshold → return input as-is (no LLM call).
29
+ * - Aux LLM call fails → fall back to pruneMessages only and log a
30
+ * warning. The turn proceeds; we never throw because that would break
31
+ * the user's in-flight conversation over an optimization.
32
+ * - Compacted result is *larger* than the input (degenerate aux output)
33
+ * → discard compaction and return pruned-only.
34
+ *
35
+ * The compactor itself is pure with respect to the caller's array: it
36
+ * builds and returns a new array. Caller's persistence path keeps the
37
+ * canonical (uncompacted) history on disk so resume can replay the full
38
+ * thread if desired.
39
+ *
40
+ * Upstream reference: `/Users/maestrobot/__KEEP_MAESTRO_AGENT__/agent/context_compressor.py`
41
+ * — the `should_compress(real_tokens)` → `compress_messages()` path, minus
42
+ * the multi-provider tokenizer plumbing (we estimate; see token-estimate.ts).
43
+ */
44
+ export interface CompressOptions {
45
+ /** Model context window in tokens. Default reads `MAESTRO_CONTEXT_WINDOW`
46
+ * env (Sonnet 4.6 default = 200_000). */
47
+ contextWindow?: number;
48
+ /** Compaction triggers when estimated tokens / window ≥ this ratio.
49
+ * Default 0.8 — matches upstream and leaves enough headroom for the
50
+ * current turn's prompt + response to fit inside the cap. */
51
+ triggerRatio?: number;
52
+ /** Number of HEAD messages preserved verbatim. Default 2 (first user
53
+ * prompt + first assistant turn). */
54
+ headProtect?: number;
55
+ /** Number of TAIL messages preserved verbatim. Default 6 (~ last 3 turns
56
+ * of user/assistant alternation). */
57
+ tailProtect?: number;
58
+ /** Aux model id for the summarization call. Default `MODEL_HAIKU`. */
59
+ auxModel?: string;
60
+ /** Inject a different provider for tests. Defaults to a fresh
61
+ * `AnthropicProvider.fromEnv()` reuse-of-the-main-provider via DI. */
62
+ auxProvider?: Provider;
63
+ /** Disable pruning fallback when aux LLM fails. Tests use this to verify
64
+ * the fallback path exits cleanly without re-pruning. */
65
+ disablePruneFallback?: boolean;
66
+ /** Abort signal for the aux summarization request. */
67
+ abortSignal?: AbortSignal;
68
+ }
69
+ /**
70
+ * Run the auto-compaction pipeline.
71
+ *
72
+ * Steps:
73
+ * 1. Apply `pruneMessages` first — pass 1+2 are cheap and frequently
74
+ * bring the wire size below the trigger ratio on their own.
75
+ * 2. Re-estimate tokens. If below threshold, return the pruned array.
76
+ * 3. Otherwise slice head + tail and dispatch the aux LLM to summarize
77
+ * the middle. Reconstruct as [head, summary user message, tail].
78
+ * 4. Anti-thrash: if two consecutive compactions on this array saved
79
+ * <10%, drop back to prune-only and stop calling the aux LLM until
80
+ * the caller hands us a different array reference.
81
+ *
82
+ * Returns a new array — caller is responsible for using the returned slice
83
+ * on the wire and keeping the unmodified canonical history for persistence.
84
+ */
85
+ export declare function compressIfNeeded(messages: ProviderMessage[], opts?: CompressOptions): Promise<ProviderMessage[]>;
86
+ export declare function __resetCompactorState(messages: ProviderMessage[]): void;
87
+ //# sourceMappingURL=compressor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compressor.d.ts","sourceRoot":"","sources":["../../src/memory/compressor.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,QAAQ,EAAwB,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAIxF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AAEH,MAAM,WAAW,eAAe;IAC9B;8CAC0C;IAC1C,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;kEAE8D;IAC9D,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;0CACsC;IACtC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;0CACsC;IACtC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,sEAAsE;IACtE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;2EACuE;IACvE,WAAW,CAAC,EAAE,QAAQ,CAAC;IACvB;8DAC0D;IAC1D,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,sDAAsD;IACtD,WAAW,CAAC,EAAE,WAAW,CAAC;CAC3B;AA0BD;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,gBAAgB,CACpC,QAAQ,EAAE,eAAe,EAAE,EAC3B,IAAI,GAAE,eAAoB,GACzB,OAAO,CAAC,eAAe,EAAE,CAAC,CA6G5B;AAoCD,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,eAAe,EAAE,GAAG,IAAI,CAEvE"}
@@ -0,0 +1,164 @@
1
+ import { ACTIVE_TASK_TEMPLATE, wrapCompactedSummary } from "../memory/active-task-template.js";
2
+ import { pruneMessages } from "../memory/prune.js";
3
+ import { estimateTokens } from "../memory/token-estimate.js";
4
+ import { MODEL_HAIKU } from "../platform/config.js";
5
+ import { logger } from "../platform/logger.js";
6
+ const compactorAntiThrash = new WeakMap();
7
+ /** A compaction that doesn't save at least this fraction of tokens is
8
+ * considered ineffective and counts toward backoff. */
9
+ const COMPACTOR_MIN_SAVINGS_RATIO = 0.1;
10
+ /** Two consecutive ineffective calls on the same array → bail out and just
11
+ * prune. */
12
+ const COMPACTOR_ANTI_THRASH_LIMIT = 2;
13
+ function defaultContextWindow() {
14
+ const env = process.env.MAESTRO_CONTEXT_WINDOW;
15
+ if (env) {
16
+ const n = Number.parseInt(env, 10);
17
+ if (Number.isFinite(n) && n > 0)
18
+ return n;
19
+ }
20
+ return 200_000;
21
+ }
22
+ /**
23
+ * Run the auto-compaction pipeline.
24
+ *
25
+ * Steps:
26
+ * 1. Apply `pruneMessages` first — pass 1+2 are cheap and frequently
27
+ * bring the wire size below the trigger ratio on their own.
28
+ * 2. Re-estimate tokens. If below threshold, return the pruned array.
29
+ * 3. Otherwise slice head + tail and dispatch the aux LLM to summarize
30
+ * the middle. Reconstruct as [head, summary user message, tail].
31
+ * 4. Anti-thrash: if two consecutive compactions on this array saved
32
+ * <10%, drop back to prune-only and stop calling the aux LLM until
33
+ * the caller hands us a different array reference.
34
+ *
35
+ * Returns a new array — caller is responsible for using the returned slice
36
+ * on the wire and keeping the unmodified canonical history for persistence.
37
+ */
38
+ export async function compressIfNeeded(messages, opts = {}) {
39
+ const contextWindow = opts.contextWindow ?? defaultContextWindow();
40
+ const triggerRatio = opts.triggerRatio ?? 0.8;
41
+ const headProtect = opts.headProtect ?? 2;
42
+ const tailProtect = opts.tailProtect ?? 6;
43
+ const auxModel = opts.auxModel ?? MODEL_HAIKU;
44
+ // Step 1: prune first. Cheap and often enough.
45
+ const pruned = pruneMessages(messages);
46
+ const prunedTokens = estimateTokens(pruned);
47
+ const threshold = contextWindow * triggerRatio;
48
+ if (prunedTokens < threshold) {
49
+ return pruned;
50
+ }
51
+ // Anti-thrash check — bail if we already tried twice and it didn't help.
52
+ const state = compactorAntiThrash.get(messages);
53
+ if (state && state.failedCompactions >= COMPACTOR_ANTI_THRASH_LIMIT) {
54
+ return pruned;
55
+ }
56
+ // Step 2: validate we have something to compress. Need at least
57
+ // headProtect + 1 + tailProtect messages, otherwise the middle slice is
58
+ // empty and there's nothing to summarize.
59
+ const minSize = headProtect + 1 + tailProtect;
60
+ if (pruned.length < minSize) {
61
+ return pruned;
62
+ }
63
+ // Snap head/tail boundaries to safe split points. Anthropic rejects a
64
+ // request whose first message after the head isn't a user turn, and
65
+ // requires every tool_use to be answered by a tool_result on the next
66
+ // user turn. We never split a user→assistant or assistant→user(tool_result)
67
+ // pairing.
68
+ const headEnd = snapHeadEnd(pruned, headProtect);
69
+ const tailStart = snapTailStart(pruned, pruned.length - tailProtect);
70
+ if (tailStart <= headEnd) {
71
+ // Snapping collapsed the middle — nothing left to compress.
72
+ return pruned;
73
+ }
74
+ const head = pruned.slice(0, headEnd);
75
+ const middle = pruned.slice(headEnd, tailStart);
76
+ const tail = pruned.slice(tailStart);
77
+ // Step 3: aux LLM call.
78
+ if (!opts.auxProvider) {
79
+ // No provider supplied AND no factory available in production callers
80
+ // (maestroProvider passes its own AnthropicProvider). Without one we
81
+ // can't summarize — drop to pruned and log so the operator sees why.
82
+ logger.warn({ prunedTokens, threshold }, "compressIfNeeded: over threshold but no auxProvider supplied — falling back to prune-only");
83
+ return pruned;
84
+ }
85
+ let summaryText;
86
+ try {
87
+ const auxResponse = await opts.auxProvider.complete({
88
+ model: auxModel,
89
+ messages: middle,
90
+ system: ACTIVE_TASK_TEMPLATE,
91
+ maxTokens: 2048,
92
+ ...(opts.abortSignal ? { abortSignal: opts.abortSignal } : {}),
93
+ });
94
+ summaryText = extractText(auxResponse.content).trim();
95
+ if (!summaryText) {
96
+ throw new Error("aux LLM returned empty summary");
97
+ }
98
+ }
99
+ catch (err) {
100
+ logger.warn({ err, prunedTokens, threshold }, "compressIfNeeded: aux LLM call failed — returning prune-only fallback");
101
+ if (opts.disablePruneFallback)
102
+ return messages;
103
+ return pruned;
104
+ }
105
+ const compacted = [
106
+ ...head,
107
+ { role: "user", content: wrapCompactedSummary(summaryText) },
108
+ ...tail,
109
+ ];
110
+ const compactedTokens = estimateTokens(compacted);
111
+ // Degenerate aux output: compaction made things bigger or barely smaller.
112
+ // Discard and fall back to prune-only.
113
+ const savings = prunedTokens - compactedTokens;
114
+ const ratio = savings / prunedTokens;
115
+ if (ratio < COMPACTOR_MIN_SAVINGS_RATIO) {
116
+ const next = state ?? { failedCompactions: 0 };
117
+ next.failedCompactions++;
118
+ compactorAntiThrash.set(messages, next);
119
+ logger.info({ prunedTokens, compactedTokens, ratio, failedCompactions: next.failedCompactions }, "compressIfNeeded: low-savings compaction discarded — anti-thrash counter incremented");
120
+ return pruned;
121
+ }
122
+ // Successful compaction resets the anti-thrash counter.
123
+ if (state)
124
+ compactorAntiThrash.delete(messages);
125
+ logger.info({ prunedTokens, compactedTokens, ratio, headProtect, tailProtect, middleSize: middle.length }, "compressIfNeeded: applied aux-LLM compaction");
126
+ return compacted;
127
+ }
128
+ /**
129
+ * Walk forward from `idealEnd` to land on the first boundary where the next
130
+ * message is `role:"user"` (so the post-head slice starts cleanly with a
131
+ * user turn — Anthropic's pairing rules require it). Caps at `idealEnd + 4`
132
+ * so a pathological "all assistant" run can't push the head past the tail.
133
+ */
134
+ function snapHeadEnd(messages, idealEnd) {
135
+ const cap = Math.min(messages.length, idealEnd + 4);
136
+ let i = Math.min(idealEnd, messages.length);
137
+ while (i < cap && messages[i] && messages[i].role !== "user")
138
+ i++;
139
+ return i;
140
+ }
141
+ /**
142
+ * Walk backward from `idealStart` until we land on a user turn — so the
143
+ * tail slice begins with a user turn (mirror of `snapHeadEnd`). Caps at
144
+ * `idealStart - 4` so the tail never grows unboundedly.
145
+ */
146
+ function snapTailStart(messages, idealStart) {
147
+ const floor = Math.max(0, idealStart - 4);
148
+ let i = Math.max(idealStart, 0);
149
+ while (i > floor && messages[i] && messages[i].role !== "user")
150
+ i--;
151
+ return i;
152
+ }
153
+ /** Pull the concatenated text out of an aux LLM ProviderResponse. */
154
+ function extractText(blocks) {
155
+ return blocks
156
+ .filter((b) => b.type === "text")
157
+ .map((b) => b.text)
158
+ .join("\n");
159
+ }
160
+ // Test-only: reset the WeakMap state for deterministic single-test runs.
161
+ export function __resetCompactorState(messages) {
162
+ compactorAntiThrash.delete(messages);
163
+ }
164
+ //# sourceMappingURL=compressor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compressor.js","sourceRoot":"","sources":["../../src/memory/compressor.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Maestro memory hashing — md5 helpers for tool_result deduplication.
3
+ *
4
+ * We use md5 (truncated to 12 hex chars) for parity with upstream Maestro
5
+ * v0.13.0 `context_compressor.py::_prune_old_tool_results`. Collision risk at
6
+ * 12 hex (48 bits) is ~1e-4 at 65K entries — well above the per-session
7
+ * tool_result count we ever see in practice (tens, maybe low hundreds).
8
+ *
9
+ * Not cryptographic — pure non-malicious dedup. md5 picked over SHA-256
10
+ * because:
11
+ * 1. ~3x cheaper to compute on the per-turn hot path
12
+ * 2. Wire compat with the Python reference makes future cross-runtime
13
+ * session-store inspection trivial
14
+ */
15
+ /** Truncated md5 hex digest of a string. 12 chars = 48 bits — see header. */
16
+ export declare function hashToolContent(content: string): string;
17
+ //# sourceMappingURL=hash.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hash.d.ts","sourceRoot":"","sources":["../../src/memory/hash.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;GAaG;AAEH,6EAA6E;AAC7E,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAEvD"}
@@ -0,0 +1,20 @@
1
+ import { createHash } from "node:crypto";
2
+ /**
3
+ * Maestro memory hashing — md5 helpers for tool_result deduplication.
4
+ *
5
+ * We use md5 (truncated to 12 hex chars) for parity with upstream Maestro
6
+ * v0.13.0 `context_compressor.py::_prune_old_tool_results`. Collision risk at
7
+ * 12 hex (48 bits) is ~1e-4 at 65K entries — well above the per-session
8
+ * tool_result count we ever see in practice (tens, maybe low hundreds).
9
+ *
10
+ * Not cryptographic — pure non-malicious dedup. md5 picked over SHA-256
11
+ * because:
12
+ * 1. ~3x cheaper to compute on the per-turn hot path
13
+ * 2. Wire compat with the Python reference makes future cross-runtime
14
+ * session-store inspection trivial
15
+ */
16
+ /** Truncated md5 hex digest of a string. 12 chars = 48 bits — see header. */
17
+ export function hashToolContent(content) {
18
+ return createHash("md5").update(content, "utf8").digest("hex").slice(0, 12);
19
+ }
20
+ //# sourceMappingURL=hash.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hash.js","sourceRoot":"","sources":["../../src/memory/hash.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC;;;;;;;;;;;;;GAaG;AAEH,6EAA6E;AAC7E,MAAM,UAAU,eAAe,CAAC,OAAe;IAC7C,OAAO,UAAU,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;AAC9E,CAAC"}