feat(hooks): surface fsync-skip warnings to AI agent via tool output
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
@@ -0,0 +1,98 @@
|
||||
import { beforeEach, describe, expect, it } from "bun:test"
|
||||
|
||||
import { classifyPathEnvironment } from "../../shared/classify-path-environment"
|
||||
import { clearAllSkips, recordFsyncSkip } from "../../shared/fsync-skip-tracker"
|
||||
import { createFsyncSkipWarningHook } from "./index"
|
||||
|
||||
describe("createFsyncSkipWarningHook", () => {
|
||||
beforeEach(() => {
|
||||
clearAllSkips()
|
||||
})
|
||||
|
||||
it("records callID start timestamp in tool.execute.before", async () => {
|
||||
const hook = createFsyncSkipWarningHook()
|
||||
const input = { tool: "bash", sessionID: "ses1", callID: "call-1" }
|
||||
const output = { args: {} as Record<string, unknown> }
|
||||
|
||||
await hook["tool.execute.before"](input, output)
|
||||
await Bun.sleep(2)
|
||||
|
||||
recordFsyncSkip({
|
||||
filePath: "/tmp/a",
|
||||
contextLabel: "atomicWrite:/tmp/a",
|
||||
errorCode: "EPERM",
|
||||
message: "operation not permitted",
|
||||
pathClassification: classifyPathEnvironment("/tmp/a"),
|
||||
})
|
||||
|
||||
const afterOutput = { title: "ok", output: "done", metadata: {} as Record<string, unknown> }
|
||||
await hook["tool.execute.after"](input, afterOutput)
|
||||
|
||||
expect(afterOutput.output).toContain("[fsync-skipped]")
|
||||
})
|
||||
|
||||
it("drains skips after start time and appends warning to output text", async () => {
|
||||
const hook = createFsyncSkipWarningHook()
|
||||
const input = { tool: "write", sessionID: "ses1", callID: "call-2" }
|
||||
const beforeOutput = { args: {} as Record<string, unknown> }
|
||||
const afterOutput = { title: "ok", output: "base", metadata: {} as Record<string, unknown> }
|
||||
|
||||
await hook["tool.execute.before"](input, beforeOutput)
|
||||
await Bun.sleep(2)
|
||||
|
||||
recordFsyncSkip({
|
||||
filePath: "/Users/x/OneDrive/a",
|
||||
contextLabel: "atomicWrite:/Users/x/OneDrive/a",
|
||||
errorCode: "EPERM",
|
||||
message: "operation not permitted",
|
||||
pathClassification: classifyPathEnvironment("/Users/x/OneDrive/a"),
|
||||
})
|
||||
|
||||
await hook["tool.execute.after"](input, afterOutput)
|
||||
|
||||
expect(afterOutput.output).toContain("base\n\n---")
|
||||
expect(afterOutput.output).toContain("OneDrive")
|
||||
})
|
||||
|
||||
it("leaves output unchanged when no skips happen during window", async () => {
|
||||
const hook = createFsyncSkipWarningHook()
|
||||
const input = { tool: "write", sessionID: "ses1", callID: "call-3" }
|
||||
const beforeOutput = { args: {} as Record<string, unknown> }
|
||||
const afterOutput = { title: "ok", output: "base", metadata: {} as Record<string, unknown> }
|
||||
|
||||
await hook["tool.execute.before"](input, beforeOutput)
|
||||
await hook["tool.execute.after"](input, afterOutput)
|
||||
|
||||
expect(afterOutput.output).toBe("base")
|
||||
})
|
||||
|
||||
it("isolates multiple parallel calls by callID watermark", async () => {
|
||||
const hook = createFsyncSkipWarningHook()
|
||||
const beforeOutput = { args: {} as Record<string, unknown> }
|
||||
|
||||
const inputA = { tool: "write", sessionID: "ses1", callID: "call-A" }
|
||||
const inputB = { tool: "write", sessionID: "ses1", callID: "call-B" }
|
||||
|
||||
await hook["tool.execute.before"](inputA, beforeOutput)
|
||||
await Bun.sleep(2)
|
||||
await hook["tool.execute.before"](inputB, beforeOutput)
|
||||
await Bun.sleep(2)
|
||||
|
||||
recordFsyncSkip({
|
||||
filePath: "/tmp/a",
|
||||
contextLabel: "atomicWrite:/tmp/a",
|
||||
errorCode: "EPERM",
|
||||
message: "operation not permitted",
|
||||
pathClassification: classifyPathEnvironment("/tmp/a"),
|
||||
})
|
||||
|
||||
const outputA = { title: "ok", output: "A", metadata: {} as Record<string, unknown> }
|
||||
const outputB = { title: "ok", output: "B", metadata: {} as Record<string, unknown> }
|
||||
|
||||
await hook["tool.execute.after"](inputA, outputA)
|
||||
await hook["tool.execute.after"](inputB, outputB)
|
||||
|
||||
expect(outputA.output).toContain("[fsync-skipped]")
|
||||
expect(outputB.output).toBe("B")
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,50 @@
|
||||
import { drainSkipsAfter } from "../../shared/fsync-skip-tracker"
|
||||
import { formatFsyncSkipWarning } from "../../shared/fsync-skip-warning-formatter"
|
||||
|
||||
type ToolExecuteInput = {
|
||||
tool: string
|
||||
sessionID: string
|
||||
callID: string
|
||||
}
|
||||
|
||||
type ToolBeforeOutput = {
|
||||
args: Record<string, unknown>
|
||||
}
|
||||
|
||||
type ToolAfterOutput = {
|
||||
title: string
|
||||
output: string
|
||||
metadata: unknown
|
||||
}
|
||||
|
||||
export function createFsyncSkipWarningHook() {
|
||||
const startTimesByCallId = new Map<string, number>()
|
||||
|
||||
const toolExecuteBefore = async (
|
||||
input: ToolExecuteInput,
|
||||
_output: ToolBeforeOutput,
|
||||
): Promise<void> => {
|
||||
startTimesByCallId.set(input.callID, Date.now())
|
||||
}
|
||||
|
||||
const toolExecuteAfter = async (
|
||||
input: ToolExecuteInput,
|
||||
output: ToolAfterOutput,
|
||||
): Promise<void> => {
|
||||
if (typeof output.output !== "string") return
|
||||
|
||||
const startTimestamp = startTimesByCallId.get(input.callID) ?? 0
|
||||
startTimesByCallId.delete(input.callID)
|
||||
|
||||
const skips = drainSkipsAfter(startTimestamp)
|
||||
const warning = formatFsyncSkipWarning(skips)
|
||||
if (warning.length === 0) return
|
||||
|
||||
output.output = `${output.output}\n\n${warning}`
|
||||
}
|
||||
|
||||
return {
|
||||
"tool.execute.before": toolExecuteBefore,
|
||||
"tool.execute.after": toolExecuteAfter,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user