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.
- package/LICENSE +21 -0
- package/NOTICE +24 -0
- package/README.md +133 -0
- package/dist/agents/contracts.d.ts +49 -0
- package/dist/agents/contracts.d.ts.map +1 -0
- package/dist/agents/contracts.js +2 -0
- package/dist/agents/contracts.js.map +1 -0
- package/dist/agents/rollout/shared.d.ts +24 -0
- package/dist/agents/rollout/shared.d.ts.map +1 -0
- package/dist/agents/rollout/shared.js +105 -0
- package/dist/agents/rollout/shared.js.map +1 -0
- package/dist/core/agent.d.ts +71 -0
- package/dist/core/agent.d.ts.map +1 -0
- package/dist/core/agent.js +22 -0
- package/dist/core/agent.js.map +1 -0
- package/dist/core/loop.d.ts +26 -0
- package/dist/core/loop.d.ts.map +1 -0
- package/dist/core/loop.js +317 -0
- package/dist/core/loop.js.map +1 -0
- package/dist/index.d.ts +49 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +53 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp/client.d.ts +79 -0
- package/dist/mcp/client.d.ts.map +1 -0
- package/dist/mcp/client.js +176 -0
- package/dist/mcp/client.js.map +1 -0
- package/dist/mcp/pool-cache.d.ts +103 -0
- package/dist/mcp/pool-cache.d.ts.map +1 -0
- package/dist/mcp/pool-cache.js +249 -0
- package/dist/mcp/pool-cache.js.map +1 -0
- package/dist/mcp/pool.d.ts +65 -0
- package/dist/mcp/pool.d.ts.map +1 -0
- package/dist/mcp/pool.js +86 -0
- package/dist/mcp/pool.js.map +1 -0
- package/dist/media/file-events.d.ts +8 -0
- package/dist/media/file-events.d.ts.map +1 -0
- package/dist/media/file-events.js +15 -0
- package/dist/media/file-events.js.map +1 -0
- package/dist/memory/active-task-template.d.ts +34 -0
- package/dist/memory/active-task-template.d.ts.map +1 -0
- package/dist/memory/active-task-template.js +63 -0
- package/dist/memory/active-task-template.js.map +1 -0
- package/dist/memory/compressor.d.ts +87 -0
- package/dist/memory/compressor.d.ts.map +1 -0
- package/dist/memory/compressor.js +164 -0
- package/dist/memory/compressor.js.map +1 -0
- package/dist/memory/hash.d.ts +17 -0
- package/dist/memory/hash.d.ts.map +1 -0
- package/dist/memory/hash.js +20 -0
- package/dist/memory/hash.js.map +1 -0
- package/dist/memory/prune.d.ts +117 -0
- package/dist/memory/prune.d.ts.map +1 -0
- package/dist/memory/prune.js +416 -0
- package/dist/memory/prune.js.map +1 -0
- package/dist/memory/reminder.d.ts +57 -0
- package/dist/memory/reminder.d.ts.map +1 -0
- package/dist/memory/reminder.js +57 -0
- package/dist/memory/reminder.js.map +1 -0
- package/dist/memory/scrubber.d.ts +28 -0
- package/dist/memory/scrubber.d.ts.map +1 -0
- package/dist/memory/scrubber.js +147 -0
- package/dist/memory/scrubber.js.map +1 -0
- package/dist/memory/token-estimate.d.ts +10 -0
- package/dist/memory/token-estimate.d.ts.map +1 -0
- package/dist/memory/token-estimate.js +69 -0
- package/dist/memory/token-estimate.js.map +1 -0
- package/dist/platform/config.d.ts +12 -0
- package/dist/platform/config.d.ts.map +1 -0
- package/dist/platform/config.js +54 -0
- package/dist/platform/config.js.map +1 -0
- package/dist/platform/jsonl.d.ts +15 -0
- package/dist/platform/jsonl.d.ts.map +1 -0
- package/dist/platform/jsonl.js +80 -0
- package/dist/platform/jsonl.js.map +1 -0
- package/dist/platform/lifecycle.d.ts +22 -0
- package/dist/platform/lifecycle.d.ts.map +1 -0
- package/dist/platform/lifecycle.js +60 -0
- package/dist/platform/lifecycle.js.map +1 -0
- package/dist/platform/logger.d.ts +26 -0
- package/dist/platform/logger.d.ts.map +1 -0
- package/dist/platform/logger.js +41 -0
- package/dist/platform/logger.js.map +1 -0
- package/dist/platform/mcp-config.d.ts +15 -0
- package/dist/platform/mcp-config.d.ts.map +1 -0
- package/dist/platform/mcp-config.js +8 -0
- package/dist/platform/mcp-config.js.map +1 -0
- package/dist/provider.d.ts +81 -0
- package/dist/provider.d.ts.map +1 -0
- package/dist/provider.js +444 -0
- package/dist/provider.js.map +1 -0
- package/dist/providers/anthropic.d.ts +132 -0
- package/dist/providers/anthropic.d.ts.map +1 -0
- package/dist/providers/anthropic.js +518 -0
- package/dist/providers/anthropic.js.map +1 -0
- package/dist/providers/base.d.ts +140 -0
- package/dist/providers/base.d.ts.map +1 -0
- package/dist/providers/base.js +2 -0
- package/dist/providers/base.js.map +1 -0
- package/dist/providers/deepseek.d.ts +118 -0
- package/dist/providers/deepseek.d.ts.map +1 -0
- package/dist/providers/deepseek.js +467 -0
- package/dist/providers/deepseek.js.map +1 -0
- package/dist/registry.d.ts +3 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +94 -0
- package/dist/registry.js.map +1 -0
- package/dist/session-store.d.ts +133 -0
- package/dist/session-store.d.ts.map +1 -0
- package/dist/session-store.js +277 -0
- package/dist/session-store.js.map +1 -0
- package/dist/skills/curator.d.ts +104 -0
- package/dist/skills/curator.d.ts.map +1 -0
- package/dist/skills/curator.js +162 -0
- package/dist/skills/curator.js.map +1 -0
- package/dist/skills/index-builder.d.ts +42 -0
- package/dist/skills/index-builder.d.ts.map +1 -0
- package/dist/skills/index-builder.js +94 -0
- package/dist/skills/index-builder.js.map +1 -0
- package/dist/skills/loader.d.ts +107 -0
- package/dist/skills/loader.d.ts.map +1 -0
- package/dist/skills/loader.js +286 -0
- package/dist/skills/loader.js.map +1 -0
- package/dist/skills/preprocess.d.ts +45 -0
- package/dist/skills/preprocess.d.ts.map +1 -0
- package/dist/skills/preprocess.js +126 -0
- package/dist/skills/preprocess.js.map +1 -0
- package/dist/skills/usage.d.ts +75 -0
- package/dist/skills/usage.d.ts.map +1 -0
- package/dist/skills/usage.js +147 -0
- package/dist/skills/usage.js.map +1 -0
- package/dist/state/todos.d.ts +95 -0
- package/dist/state/todos.d.ts.map +1 -0
- package/dist/state/todos.js +198 -0
- package/dist/state/todos.js.map +1 -0
- package/dist/storage/conversations.d.ts +28 -0
- package/dist/storage/conversations.d.ts.map +1 -0
- package/dist/storage/conversations.js +8 -0
- package/dist/storage/conversations.js.map +1 -0
- package/dist/sub-agent/runner.d.ts +78 -0
- package/dist/sub-agent/runner.d.ts.map +1 -0
- package/dist/sub-agent/runner.js +215 -0
- package/dist/sub-agent/runner.js.map +1 -0
- package/dist/tools/builtin/agent.d.ts +33 -0
- package/dist/tools/builtin/agent.d.ts.map +1 -0
- package/dist/tools/builtin/agent.js +76 -0
- package/dist/tools/builtin/agent.js.map +1 -0
- package/dist/tools/builtin/bash.d.ts +11 -0
- package/dist/tools/builtin/bash.d.ts.map +1 -0
- package/dist/tools/builtin/bash.js +91 -0
- package/dist/tools/builtin/bash.js.map +1 -0
- package/dist/tools/builtin/edit.d.ts +21 -0
- package/dist/tools/builtin/edit.d.ts.map +1 -0
- package/dist/tools/builtin/edit.js +238 -0
- package/dist/tools/builtin/edit.js.map +1 -0
- package/dist/tools/builtin/read.d.ts +17 -0
- package/dist/tools/builtin/read.d.ts.map +1 -0
- package/dist/tools/builtin/read.js +139 -0
- package/dist/tools/builtin/read.js.map +1 -0
- package/dist/tools/builtin/sandbox.d.ts +16 -0
- package/dist/tools/builtin/sandbox.d.ts.map +1 -0
- package/dist/tools/builtin/sandbox.js +58 -0
- package/dist/tools/builtin/sandbox.js.map +1 -0
- package/dist/tools/builtin/skill_view.d.ts +37 -0
- package/dist/tools/builtin/skill_view.d.ts.map +1 -0
- package/dist/tools/builtin/skill_view.js +82 -0
- package/dist/tools/builtin/skill_view.js.map +1 -0
- package/dist/tools/builtin/todo_write.d.ts +29 -0
- package/dist/tools/builtin/todo_write.d.ts.map +1 -0
- package/dist/tools/builtin/todo_write.js +96 -0
- package/dist/tools/builtin/todo_write.js.map +1 -0
- package/dist/tools/builtin/web_fetch.d.ts +10 -0
- package/dist/tools/builtin/web_fetch.d.ts.map +1 -0
- package/dist/tools/builtin/web_fetch.js +150 -0
- package/dist/tools/builtin/web_fetch.js.map +1 -0
- package/dist/tools/builtin/write.d.ts +35 -0
- package/dist/tools/builtin/write.d.ts.map +1 -0
- package/dist/tools/builtin/write.js +70 -0
- package/dist/tools/builtin/write.js.map +1 -0
- package/dist/tools/file-state.d.ts +99 -0
- package/dist/tools/file-state.d.ts.map +1 -0
- package/dist/tools/file-state.js +133 -0
- package/dist/tools/file-state.js.map +1 -0
- package/dist/tools/hooks/sandbox-fs.d.ts +25 -0
- package/dist/tools/hooks/sandbox-fs.d.ts.map +1 -0
- package/dist/tools/hooks/sandbox-fs.js +48 -0
- package/dist/tools/hooks/sandbox-fs.js.map +1 -0
- package/dist/tools/registry.d.ts +102 -0
- package/dist/tools/registry.d.ts.map +1 -0
- package/dist/tools/registry.js +93 -0
- package/dist/tools/registry.js.map +1 -0
- package/dist/types.d.ts +109 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +20 -0
- package/dist/types.js.map +1 -0
- package/package.json +72 -0
package/dist/mcp/pool.js
ADDED
|
@@ -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"}
|