Skip to content

Commit cce7c6a

Browse files
committed
fix(routing): surface deny guidance
1 parent 2b67949 commit cce7c6a

2 files changed

Lines changed: 46 additions & 8 deletions

File tree

src/handlers/tool-before.test.ts

Lines changed: 45 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ describe("tool execute before handler", () => {
3434
routingOutcomes.clearAll();
3535
});
3636

37-
it("throws on denied WebFetch calls", async () => {
37+
it("throws guidance on denied WebFetch calls", async () => {
3838
const canonicalizer = new MockSessionCanonicalizer();
3939
canonicalizer.cached.set("root-session", "root-session");
4040
const handler = createToolBeforeHandler({
@@ -55,7 +55,7 @@ describe("tool execute before handler", () => {
5555
{ args: { url: "https://example.com" } } as never,
5656
),
5757
Error,
58-
"Tool denied (WebFetch)",
58+
"WebFetch is blocked",
5959
);
6060

6161
assertEquals(routingOutcomes.take("call-1"), {
@@ -65,7 +65,7 @@ describe("tool execute before handler", () => {
6565
});
6666
});
6767

68-
it("throws on denied WebFetch calls from a child session after first-call canonical lookup", async () => {
68+
it("throws guidance on denied WebFetch calls from a child session after first-call canonical lookup", async () => {
6969
const canonicalizer = new MockSessionCanonicalizer();
7070
canonicalizer.resolved.set("child-session", "root-session");
7171
const handler = createToolBeforeHandler({
@@ -86,7 +86,7 @@ describe("tool execute before handler", () => {
8686
{ args: { url: "https://example.com" } } as never,
8787
),
8888
Error,
89-
"Tool denied (WebFetch)",
89+
"WebFetch is blocked",
9090
);
9191

9292
assertEquals(canonicalizer.cachedCalls, ["child-session"]);
@@ -98,7 +98,7 @@ describe("tool execute before handler", () => {
9898
});
9999
});
100100

101-
it("throws a stable denial message without embedding guidance text", async () => {
101+
it("throws the deny guidance text when provided", async () => {
102102
const canonicalizer = new MockSessionCanonicalizer();
103103
canonicalizer.cached.set("root-session", "root-session");
104104
const handler = createToolBeforeHandler({
@@ -124,16 +124,54 @@ describe("tool execute before handler", () => {
124124
{ args: { command: "curl https://example.com" } } as never,
125125
),
126126
Error,
127-
"Tool denied (Bash)",
127+
"Dynamic guidance details that should stay out of the thrown error.",
128128
);
129129

130-
assertEquals(error.message, "Tool denied (Bash)");
130+
assertEquals(
131+
error.message,
132+
"Dynamic guidance details that should stay out of the thrown error.",
133+
);
131134
assertStringIncludes(
132135
String(routingOutcomes.take("call-stable-deny")?.reason),
133136
"test-deny",
134137
);
135138
});
136139

140+
it("falls back to the generic denial message when guidance is absent", async () => {
141+
const canonicalizer = new MockSessionCanonicalizer();
142+
canonicalizer.cached.set("root-session", "root-session");
143+
const handler = createToolBeforeHandler({
144+
sessionCanonicalizer: canonicalizer as never,
145+
guidanceThrottle: new ToolGuidanceCache(),
146+
routingOutcomes,
147+
routeToolCall: () => ({
148+
action: "deny",
149+
reason: "test-deny-no-guidance",
150+
guidance: "",
151+
}),
152+
});
153+
154+
const error = await assertRejects(
155+
() =>
156+
handler(
157+
{
158+
tool: "Bash",
159+
sessionID: "root-session",
160+
callID: "call-generic-deny",
161+
} as never,
162+
{ args: { command: "curl https://example.com" } } as never,
163+
),
164+
Error,
165+
"Tool denied (Bash)",
166+
);
167+
168+
assertEquals(error.message, "Tool denied (Bash)");
169+
assertStringIncludes(
170+
String(routingOutcomes.take("call-generic-deny")?.reason),
171+
"test-deny-no-guidance",
172+
);
173+
});
174+
137175
it("mutates args for Bash rewrite cases", async () => {
138176
const canonicalizer = new MockSessionCanonicalizer();
139177
canonicalizer.cached.set("root-session", "root-session");

src/handlers/tool-before.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ export function createToolBeforeHandler(
109109
action: "deny",
110110
reason: decision.reason,
111111
});
112-
throw new Error(`Tool denied (${tool})`);
112+
throw new Error(decision.guidance || `Tool denied (${tool})`);
113113
}
114114
};
115115
}

0 commit comments

Comments
 (0)