Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 43 additions & 4 deletions plugin/scripts/post-tool-failure.mjs
Original file line number Diff line number Diff line change
@@ -1,7 +1,46 @@
#!/usr/bin/env node
import { execSync } from "node:child_process";
import { basename } from "node:path";

//#region src/hooks/_capture-filter.ts
const DEFAULT_DENY_PATTERNS = [
"memory_*",
"toolsearch",
"listmcpresources",
"fetchmcpresource"
];
function parseEnvList(raw) {
if (!raw?.trim()) return void 0;
return raw.split(/[,\s]+/).map((part) => part.trim()).filter(Boolean);
}
function bareToolName(toolName) {
const trimmed = toolName.trim();
if (/^mcp__/i.test(trimmed)) {
const parts = trimmed.split("__");
if (parts.length >= 3) return parts[parts.length - 1];
}
return trimmed;
}
function normalizePattern(pattern) {
return pattern.trim().toLowerCase();
}
function matchesPattern(toolName, pattern) {
const bare = bareToolName(toolName).toLowerCase();
const pat = normalizePattern(pattern);
if (!pat.includes("*")) return bare === pat;
const escaped = pat.replace(/[.+?^${}()|[\]\\]/g, "\\$&");
const re = new RegExp(`^${escaped.replace(/\*/g, ".*")}$`);
return re.test(bare) || re.test(toolName.toLowerCase());
}
function matchesAny(toolName, patterns) {
return patterns.some((pattern) => matchesPattern(toolName, pattern));
}
function shouldCaptureTool(toolName) {
if (typeof toolName !== "string" || !toolName.trim()) return true;
const allow = parseEnvList(process.env["AGENTMEMORY_CAPTURE_ALLOW"]);
if (allow) return matchesAny(toolName, allow);
return !matchesAny(toolName, [...DEFAULT_DENY_PATTERNS, ...parseEnvList(process.env["AGENTMEMORY_CAPTURE_DENY"]) ?? []]);
}
//#endregion
//#region src/hooks/_project.ts
function resolveProject(cwd) {
const explicit = process.env["AGENTMEMORY_PROJECT_NAME"];
Expand All @@ -21,7 +60,6 @@ function resolveProject(cwd) {
} catch {}
return basename(dir);
}

//#endregion
//#region src/hooks/post-tool-failure.ts
function isSdkChildContext(payload) {
Expand Down Expand Up @@ -49,6 +87,7 @@ async function main() {
if (data.is_interrupt || data.isInterrupt) return;
const sessionId = data.session_id || data.sessionId || "unknown";
const toolName = data.tool_name ?? data.toolName;
if (!shouldCaptureTool(toolName)) return;
const toolInput = data.tool_input ?? data.toolArgs;
const error = data.error ?? data.errorMessage;
fetch(`${REST_URL}/agentmemory/observe`, {
Expand All @@ -71,7 +110,7 @@ async function main() {
setTimeout(() => process.exit(0), 500).unref();
}
main();

//#endregion
export { };
export {};

//# sourceMappingURL=post-tool-failure.mjs.map
56 changes: 51 additions & 5 deletions plugin/scripts/post-tool-use.mjs
Original file line number Diff line number Diff line change
@@ -1,7 +1,52 @@
#!/usr/bin/env node
import { execSync } from "node:child_process";
import { basename } from "node:path";

//#region src/hooks/_capture-filter.ts
const DEFAULT_DENY_PATTERNS = [
"memory_*",
"toolsearch",
"listmcpresources",
"fetchmcpresource"
];
function parseEnvList(raw) {
if (!raw?.trim()) return void 0;
return raw.split(/[,\s]+/).map((part) => part.trim()).filter(Boolean);
}
function bareToolName(toolName) {
const trimmed = toolName.trim();
if (/^mcp__/i.test(trimmed)) {
const parts = trimmed.split("__");
if (parts.length >= 3) return parts[parts.length - 1];
}
return trimmed;
}
function normalizePattern(pattern) {
return pattern.trim().toLowerCase();
}
function matchesPattern(toolName, pattern) {
const bare = bareToolName(toolName).toLowerCase();
const pat = normalizePattern(pattern);
if (!pat.includes("*")) return bare === pat;
const escaped = pat.replace(/[.+?^${}()|[\]\\]/g, "\\$&");
const re = new RegExp(`^${escaped.replace(/\*/g, ".*")}$`);
return re.test(bare) || re.test(toolName.toLowerCase());
}
function matchesAny(toolName, patterns) {
return patterns.some((pattern) => matchesPattern(toolName, pattern));
}
function shouldCaptureTool(toolName) {
if (typeof toolName !== "string" || !toolName.trim()) return true;
const allow = parseEnvList(process.env["AGENTMEMORY_CAPTURE_ALLOW"]);
if (allow) return matchesAny(toolName, allow);
return !matchesAny(toolName, [...DEFAULT_DENY_PATTERNS, ...parseEnvList(process.env["AGENTMEMORY_CAPTURE_DENY"]) ?? []]);
}
function captureOutputMax() {
const raw = process.env["AGENTMEMORY_CAPTURE_OUTPUT_MAX"];
if (!raw?.trim()) return 8e3;
const parsed = Number.parseInt(raw, 10);
return Number.isFinite(parsed) && parsed > 0 ? parsed : 8e3;
}
//#endregion
//#region src/hooks/_project.ts
function resolveProject(cwd) {
const explicit = process.env["AGENTMEMORY_PROJECT_NAME"];
Expand All @@ -21,7 +66,6 @@ function resolveProject(cwd) {
} catch {}
return basename(dir);
}

//#endregion
//#region src/hooks/post-tool-use.ts
function isSdkChildContext(payload) {
Expand All @@ -48,8 +92,10 @@ async function main() {
if (isSdkChildContext(data)) return;
const sessionId = data.session_id || data.sessionId || "unknown";
const toolName = data.tool_name ?? data.toolName;
if (!shouldCaptureTool(toolName)) return;
const toolInput = data.tool_input ?? data.toolArgs;
const { imageData, cleanOutput } = extractImageData(toolOutput(data));
const outputMax = captureOutputMax();
fetch(`${REST_URL}/agentmemory/observe`, {
method: "POST",
headers: authHeaders(),
Expand All @@ -62,7 +108,7 @@ async function main() {
data: {
tool_name: toolName,
tool_input: toolInput,
tool_output: truncate(cleanOutput, 8e3),
tool_output: truncate(cleanOutput, outputMax),
...imageData ? { image_data: imageData } : {}
}
}),
Expand Down Expand Up @@ -116,7 +162,7 @@ function truncate(value, max) {
return value;
}
main();

//#endregion
export { };
export {};

//# sourceMappingURL=post-tool-use.mjs.map
19 changes: 14 additions & 5 deletions plugin/scripts/pre-compact.mjs
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
#!/usr/bin/env node
import { execSync } from "node:child_process";
import { basename } from "node:path";

//#region src/hooks/_capture-filter.ts
function preCompactBudget() {
const raw = process.env["AGENTMEMORY_PRE_COMPACT_BUDGET"];
if (raw?.trim() === "0") return 0;
if (!raw?.trim()) return 1500;
const parsed = Number.parseInt(raw, 10);
return Number.isFinite(parsed) && parsed >= 0 ? parsed : 1500;
}
//#endregion
//#region src/hooks/_project.ts
function resolveProject(cwd) {
const explicit = process.env["AGENTMEMORY_PROJECT_NAME"];
Expand All @@ -21,7 +29,6 @@ function resolveProject(cwd) {
} catch {}
return basename(dir);
}

//#endregion
//#region src/hooks/pre-compact.ts
function isSdkChildContext(payload) {
Expand Down Expand Up @@ -56,14 +63,16 @@ async function main() {
signal: AbortSignal.timeout(5e3)
});
} catch {}
const budget = preCompactBudget();
if (budget === 0) return;
try {
const res = await fetch(`${REST_URL}/agentmemory/context`, {
method: "POST",
headers: authHeaders(),
body: JSON.stringify({
sessionId,
project,
budget: 1500
budget
}),
signal: AbortSignal.timeout(5e3)
});
Expand All @@ -74,7 +83,7 @@ async function main() {
} catch {}
}
main();

//#endregion
export { };
export {};

//# sourceMappingURL=pre-compact.mjs.map
68 changes: 68 additions & 0 deletions src/hooks/_capture-filter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
const DEFAULT_DENY_PATTERNS = [
"memory_*",
"toolsearch",
"listmcpresources",
"fetchmcpresource",
];

function parseEnvList(raw: string | undefined): string[] | undefined {
if (!raw?.trim()) return undefined;
return raw
.split(/[,\s]+/)
.map((part) => part.trim())
.filter(Boolean);
}

export function bareToolName(toolName: string): string {
const trimmed = toolName.trim();
if (/^mcp__/i.test(trimmed)) {
const parts = trimmed.split("__");
if (parts.length >= 3) return parts[parts.length - 1]!;
}
return trimmed;
}

function normalizePattern(pattern: string): string {
return pattern.trim().toLowerCase();
}

function matchesPattern(toolName: string, pattern: string): boolean {
const bare = bareToolName(toolName).toLowerCase();
const pat = normalizePattern(pattern);
if (!pat.includes("*")) return bare === pat;
const escaped = pat.replace(/[.+?^${}()|[\]\\]/g, "\\$&");
const re = new RegExp(`^${escaped.replace(/\*/g, ".*")}$`);
return re.test(bare) || re.test(toolName.toLowerCase());
}

function matchesAny(toolName: string, patterns: string[]): boolean {
return patterns.some((pattern) => matchesPattern(toolName, pattern));
}

export function shouldCaptureTool(toolName: unknown): boolean {
if (typeof toolName !== "string" || !toolName.trim()) return true;

const allow = parseEnvList(process.env["AGENTMEMORY_CAPTURE_ALLOW"]);
if (allow) return matchesAny(toolName, allow);

const deny = [
...DEFAULT_DENY_PATTERNS,
...(parseEnvList(process.env["AGENTMEMORY_CAPTURE_DENY"]) ?? []),
];
return !matchesAny(toolName, deny);
}

export function captureOutputMax(): number {
const raw = process.env["AGENTMEMORY_CAPTURE_OUTPUT_MAX"];
if (!raw?.trim()) return 8000;
const parsed = Number.parseInt(raw, 10);
return Number.isFinite(parsed) && parsed > 0 ? parsed : 8000;
}

export function preCompactBudget(): number {
const raw = process.env["AGENTMEMORY_PRE_COMPACT_BUDGET"];
if (raw?.trim() === "0") return 0;
if (!raw?.trim()) return 1500;
const parsed = Number.parseInt(raw, 10);
return Number.isFinite(parsed) && parsed >= 0 ? parsed : 1500;
}
3 changes: 3 additions & 0 deletions src/hooks/post-tool-failure.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#!/usr/bin/env node
import { shouldCaptureTool } from "./_capture-filter.js";
import { resolveProject } from "./_project.js";

function isSdkChildContext(payload: unknown): boolean {
Expand Down Expand Up @@ -34,6 +35,8 @@ async function main() {

const sessionId = ((data.session_id || data.sessionId) as string) || "unknown";
const toolName = data.tool_name ?? data.toolName;
if (!shouldCaptureTool(toolName)) return;

const toolInput = data.tool_input ?? data.toolArgs;
const error = data.error ?? data.errorMessage;

Expand Down
6 changes: 5 additions & 1 deletion src/hooks/post-tool-use.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#!/usr/bin/env node
import { captureOutputMax, shouldCaptureTool } from "./_capture-filter.js";
import { resolveProject } from "./_project.js";

function isSdkChildContext(payload: unknown): boolean {
Expand Down Expand Up @@ -33,9 +34,12 @@ async function main() {

const sessionId = ((data.session_id || data.sessionId) as string) || "unknown";
const toolName = data.tool_name ?? data.toolName;
if (!shouldCaptureTool(toolName)) return;

const toolInput = data.tool_input ?? data.toolArgs;

const { imageData, cleanOutput } = extractImageData(toolOutput(data));
const outputMax = captureOutputMax();

fetch(`${REST_URL}/agentmemory/observe`, {
method: "POST",
Expand All @@ -49,7 +53,7 @@ async function main() {
data: {
tool_name: toolName,
tool_input: toolInput,
tool_output: truncate(cleanOutput, 8000),
tool_output: truncate(cleanOutput, outputMax),
...(imageData ? { image_data: imageData } : {}),
},
}),
Expand Down
6 changes: 5 additions & 1 deletion src/hooks/pre-compact.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#!/usr/bin/env node
import { preCompactBudget } from "./_capture-filter.js";
import { resolveProject } from "./_project.js";

function isSdkChildContext(payload: unknown): boolean {
Expand Down Expand Up @@ -47,11 +48,14 @@ async function main() {
}
}

const budget = preCompactBudget();
if (budget === 0) return;

try {
const res = await fetch(`${REST_URL}/agentmemory/context`, {
method: "POST",
headers: authHeaders(),
body: JSON.stringify({ sessionId, project, budget: 1500 }),
body: JSON.stringify({ sessionId, project, budget }),
signal: AbortSignal.timeout(5000),
});

Expand Down
Loading