feat(hooks): add cleanup fields to HookMappingResolved, HookAction, HookTransformResult and propagate through mapping

This commit is contained in:
Trevin Chow 2026-01-29 09:05:35 -08:00 committed by Trevin Chow
parent bfde8e1213
commit fa5e8481ab
2 changed files with 104 additions and 10 deletions

View File

@ -3,6 +3,7 @@ import os from "node:os";
import path from "node:path";
import { describe, expect, it } from "vitest";
import type { HookAction, HookMappingResolved } from "./hooks-mapping.js";
import { applyHookMappings, resolveHookMappings } from "./hooks-mapping.js";
const baseUrl = new URL("http://127.0.0.1:18789/hooks/gmail");
@ -32,8 +33,7 @@ describe("hooks mapping", () => {
path: "gmail",
});
expect(result?.ok).toBe(true);
if (result?.ok) {
expect(result.action.kind).toBe("agent");
if (result?.ok && result.action?.kind === "agent") {
expect(result.action.message).toBe("Subject: Hello");
}
});
@ -57,7 +57,7 @@ describe("hooks mapping", () => {
path: "gmail",
});
expect(result?.ok).toBe(true);
if (result?.ok && result.action.kind === "agent") {
if (result?.ok && result.action?.kind === "agent") {
expect(result.action.model).toBe("openai/gpt-4.1-mini");
}
});
@ -90,11 +90,8 @@ describe("hooks mapping", () => {
});
expect(result?.ok).toBe(true);
if (result?.ok) {
expect(result.action.kind).toBe("wake");
if (result.action.kind === "wake") {
expect(result.action.text).toBe("Ping Ada");
}
if (result?.ok && result.action?.kind === "wake") {
expect(result.action.text).toBe("Ping Ada");
}
});
@ -147,8 +144,7 @@ describe("hooks mapping", () => {
path: "gmail",
});
expect(result?.ok).toBe(true);
if (result?.ok) {
expect(result.action.kind).toBe("agent");
if (result?.ok && result.action?.kind === "agent") {
expect(result.action.message).toBe("Override subject: Hello");
}
});
@ -166,3 +162,89 @@ describe("hooks mapping", () => {
expect(result?.ok).toBe(false);
});
});
describe("HookMappingResolved", () => {
it("includes cleanup fields", () => {
const resolved: HookMappingResolved = {
id: "test",
matchPath: "test",
action: "agent",
cleanup: "delete",
cleanupDelayMinutes: 5,
};
expect(resolved.cleanup).toBe("delete");
expect(resolved.cleanupDelayMinutes).toBe(5);
});
});
describe("HookAction", () => {
it("agent action includes cleanup fields", () => {
const action: HookAction = {
kind: "agent",
message: "test",
wakeMode: "now",
cleanup: "delete",
cleanupDelayMinutes: 5,
};
expect(action.kind).toBe("agent");
if (action.kind === "agent") {
expect(action.cleanup).toBe("delete");
expect(action.cleanupDelayMinutes).toBe(5);
}
});
});
describe("applyHookMappings cleanup propagation", () => {
it("propagates cleanup option from config to action", async () => {
const mappings = resolveHookMappings({
mappings: [
{
id: "test",
match: { path: "test" },
action: "agent",
messageTemplate: "hello",
cleanup: "delete",
cleanupDelayMinutes: 5,
},
],
});
const result = await applyHookMappings(mappings, {
payload: {},
headers: {},
url: new URL("http://localhost/hooks/test"),
path: "test",
});
expect(result?.ok).toBe(true);
if (result?.ok && result.action?.kind === "agent") {
expect(result.action.cleanup).toBe("delete");
expect(result.action.cleanupDelayMinutes).toBe(5);
}
});
it("defaults cleanup to undefined when not specified", async () => {
const mappings = resolveHookMappings({
mappings: [
{
id: "test",
match: { path: "test" },
action: "agent",
messageTemplate: "hello",
},
],
});
const result = await applyHookMappings(mappings, {
payload: {},
headers: {},
url: new URL("http://localhost/hooks/test"),
path: "test",
});
expect(result?.ok).toBe(true);
if (result?.ok && result.action?.kind === "agent") {
expect(result.action.cleanup).toBeUndefined();
}
});
});

View File

@ -21,6 +21,8 @@ export type HookMappingResolved = {
model?: string;
thinking?: string;
timeoutSeconds?: number;
cleanup?: "delete" | "keep";
cleanupDelayMinutes?: number;
transform?: HookMappingTransformResolved;
};
@ -55,6 +57,8 @@ export type HookAction =
model?: string;
thinking?: string;
timeoutSeconds?: number;
cleanup?: "delete" | "keep";
cleanupDelayMinutes?: number;
};
export type HookMappingResult =
@ -94,6 +98,8 @@ type HookTransformResult = Partial<{
model: string;
thinking: string;
timeoutSeconds: number;
cleanup: "delete" | "keep";
cleanupDelayMinutes: number;
}> | null;
type HookTransformFn = (
@ -191,6 +197,8 @@ function normalizeHookMapping(
model: mapping.model,
thinking: mapping.thinking,
timeoutSeconds: mapping.timeoutSeconds,
cleanup: mapping.cleanup,
cleanupDelayMinutes: mapping.cleanupDelayMinutes,
transform,
};
}
@ -237,6 +245,8 @@ function buildActionFromMapping(
model: renderOptional(mapping.model, ctx),
thinking: renderOptional(mapping.thinking, ctx),
timeoutSeconds: mapping.timeoutSeconds,
cleanup: mapping.cleanup,
cleanupDelayMinutes: mapping.cleanupDelayMinutes,
},
};
}
@ -277,6 +287,8 @@ function mergeAction(
model: override.model ?? baseAgent?.model,
thinking: override.thinking ?? baseAgent?.thinking,
timeoutSeconds: override.timeoutSeconds ?? baseAgent?.timeoutSeconds,
cleanup: override.cleanup ?? baseAgent?.cleanup,
cleanupDelayMinutes: override.cleanupDelayMinutes ?? baseAgent?.cleanupDelayMinutes,
});
}