mirror of
https://github.com/mksglu/context-mode.git
synced 2026-10-03 04:38:25 +08:00
PostToolUse hook `matcher: ""` fired on ALL tools — 13 tools produced zero events but caused false "hook error" display (Claude Code #41868/#35587). PreToolUse `"Task"` matcher caught TaskCreate/TaskUpdate via substring matching, corrupting todo tool inputs (#241). Changes: - PostToolUse matcher narrowed to 17 tools that extractEvents() handles - "Task" removed from PreToolUse matchers (no standalone Task tool exists) - Task branch removed from routing.mjs (Agent handles subagents) - .claude-plugin/hooks/hooks.json deleted (duplicate, not in manifest) - POST_TOOL_USE_MATCHERS constant added to hooks.ts - Tests updated: Task passthrough verified, matcher whitelist tested Closes #229 Closes #241 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
04569b12a1
commit
ece3abb1d3
@@ -1,132 +0,0 @@
|
||||
{
|
||||
"description": "Context-mode hooks — PreToolUse routing, PostToolUse session capture, PreCompact snapshot, SessionStart context injection",
|
||||
"hooks": {
|
||||
"PostToolUse": [
|
||||
{
|
||||
"matcher": "",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "node ${CLAUDE_PLUGIN_ROOT}/hooks/posttooluse.mjs"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"PreCompact": [
|
||||
{
|
||||
"matcher": "",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "node ${CLAUDE_PLUGIN_ROOT}/hooks/precompact.mjs"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"PreToolUse": [
|
||||
{
|
||||
"matcher": "Bash",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "node ${CLAUDE_PLUGIN_ROOT}/hooks/pretooluse.mjs"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"matcher": "WebFetch",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "node ${CLAUDE_PLUGIN_ROOT}/hooks/pretooluse.mjs"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"matcher": "Read",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "node ${CLAUDE_PLUGIN_ROOT}/hooks/pretooluse.mjs"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"matcher": "Grep",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "node ${CLAUDE_PLUGIN_ROOT}/hooks/pretooluse.mjs"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"matcher": "Agent",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "node ${CLAUDE_PLUGIN_ROOT}/hooks/pretooluse.mjs"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"matcher": "Task",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "node ${CLAUDE_PLUGIN_ROOT}/hooks/pretooluse.mjs"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"matcher": "mcp__plugin_context-mode_context-mode__ctx_execute",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "node ${CLAUDE_PLUGIN_ROOT}/hooks/pretooluse.mjs"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"matcher": "mcp__plugin_context-mode_context-mode__ctx_execute_file",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "node ${CLAUDE_PLUGIN_ROOT}/hooks/pretooluse.mjs"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"matcher": "mcp__plugin_context-mode_context-mode__ctx_batch_execute",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "node ${CLAUDE_PLUGIN_ROOT}/hooks/pretooluse.mjs"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"UserPromptSubmit": [
|
||||
{
|
||||
"matcher": "",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "node ${CLAUDE_PLUGIN_ROOT}/hooks/userpromptsubmit.mjs"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"SessionStart": [
|
||||
{
|
||||
"matcher": "",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "node ${CLAUDE_PLUGIN_ROOT}/hooks/sessionstart.mjs"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -133,7 +133,7 @@ The symlink in step 2 ensures `hooks.json` (which registers PostToolUse, PreComp
|
||||
"hooks": {
|
||||
"PreToolUse": [
|
||||
{
|
||||
"matcher": "Bash|Read|Grep|WebFetch|Agent|Task|mcp__plugin_context-mode_context-mode__ctx_execute|mcp__plugin_context-mode_context-mode__ctx_execute_file|mcp__plugin_context-mode_context-mode__ctx_batch_execute",
|
||||
"matcher": "Bash|Read|Grep|WebFetch|Agent|mcp__plugin_context-mode_context-mode__ctx_execute|mcp__plugin_context-mode_context-mode__ctx_execute_file|mcp__plugin_context-mode_context-mode__ctx_batch_execute",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
|
||||
@@ -294,8 +294,8 @@ export function routePreToolUse(toolName, toolInput, projectDir, platform) {
|
||||
});
|
||||
}
|
||||
|
||||
// ─── Agent/Task: inject context-mode routing into subagent prompts ───
|
||||
if (canonical === "Agent" || canonical === "Task") {
|
||||
// ─── Agent: inject context-mode routing into subagent prompts ───
|
||||
if (canonical === "Agent") {
|
||||
const subagentType = toolInput.subagent_type ?? "";
|
||||
// Detect the correct field name for the prompt/request/objective/question/query
|
||||
const fieldName = ["prompt", "request", "objective", "question", "query", "task"].find(f => f in toolInput) ?? "prompt";
|
||||
|
||||
+1
-10
@@ -3,7 +3,7 @@
|
||||
"hooks": {
|
||||
"PostToolUse": [
|
||||
{
|
||||
"matcher": "",
|
||||
"matcher": "Bash|Read|Write|Edit|NotebookEdit|Glob|Grep|TodoWrite|TaskCreate|TaskUpdate|EnterPlanMode|ExitPlanMode|Skill|Agent|AskUserQuestion|EnterWorktree|mcp__",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
@@ -69,15 +69,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"matcher": "Task",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "node ${CLAUDE_PLUGIN_ROOT}/hooks/pretooluse.mjs"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"matcher": "mcp__plugin_context-mode_context-mode__ctx_execute",
|
||||
"hooks": [
|
||||
|
||||
@@ -42,7 +42,6 @@ export const PRE_TOOL_USE_MATCHERS = [
|
||||
"Read",
|
||||
"Grep",
|
||||
"Agent",
|
||||
"Task",
|
||||
"mcp__plugin_context-mode_context-mode__ctx_execute",
|
||||
"mcp__plugin_context-mode_context-mode__ctx_execute_file",
|
||||
"mcp__plugin_context-mode_context-mode__ctx_batch_execute",
|
||||
@@ -54,6 +53,40 @@ export const PRE_TOOL_USE_MATCHERS = [
|
||||
*/
|
||||
export const PRE_TOOL_USE_MATCHER_PATTERN = PRE_TOOL_USE_MATCHERS.join("|");
|
||||
|
||||
// ─────────────────────────────────────────────────────────
|
||||
// PostToolUse matchers (#229)
|
||||
// ─────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Tools that context-mode's PostToolUse hook should fire on.
|
||||
* Only tools that extractEvents() actually handles — all others
|
||||
* produce zero events and cause false "hook error" display.
|
||||
*/
|
||||
export const POST_TOOL_USE_MATCHERS = [
|
||||
"Bash",
|
||||
"Read",
|
||||
"Write",
|
||||
"Edit",
|
||||
"NotebookEdit",
|
||||
"Glob",
|
||||
"Grep",
|
||||
"TodoWrite",
|
||||
"TaskCreate",
|
||||
"TaskUpdate",
|
||||
"EnterPlanMode",
|
||||
"ExitPlanMode",
|
||||
"Skill",
|
||||
"Agent",
|
||||
"AskUserQuestion",
|
||||
"EnterWorktree",
|
||||
"mcp__",
|
||||
] as const;
|
||||
|
||||
/**
|
||||
* Combined matcher pattern for PostToolUse in hooks.json / settings.json.
|
||||
*/
|
||||
export const POST_TOOL_USE_MATCHER_PATTERN = POST_TOOL_USE_MATCHERS.join("|");
|
||||
|
||||
// ─────────────────────────────────────────────────────────
|
||||
// Hook script file names
|
||||
// ─────────────────────────────────────────────────────────
|
||||
|
||||
@@ -6,6 +6,11 @@ import { join, resolve } from "node:path";
|
||||
import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
||||
import { ClaudeCodeAdapter } from "../../src/adapters/claude-code/index.js";
|
||||
import { fakeHome, realHome } from "../setup-home";
|
||||
import {
|
||||
PRE_TOOL_USE_MATCHERS,
|
||||
POST_TOOL_USE_MATCHERS,
|
||||
POST_TOOL_USE_MATCHER_PATTERN,
|
||||
} from "../../src/adapters/claude-code/hooks.js";
|
||||
|
||||
describe("ClaudeCodeAdapter", () => {
|
||||
let adapter: ClaudeCodeAdapter;
|
||||
@@ -557,6 +562,46 @@ describe("ClaudeCodeAdapter", () => {
|
||||
});
|
||||
});
|
||||
|
||||
// ── Hook matchers (#229, #241) ────────────────────────
|
||||
|
||||
describe("hook matchers (#229, #241)", () => {
|
||||
it("PRE_TOOL_USE_MATCHERS does NOT contain 'Task' (#241)", () => {
|
||||
expect(PRE_TOOL_USE_MATCHERS).not.toContain("Task");
|
||||
});
|
||||
|
||||
it("PRE_TOOL_USE_MATCHERS contains 'Agent' for subagent routing", () => {
|
||||
expect(PRE_TOOL_USE_MATCHERS).toContain("Agent");
|
||||
});
|
||||
|
||||
it("POST_TOOL_USE_MATCHERS contains all tools that extractEvents handles", () => {
|
||||
const required = [
|
||||
"Bash", "Read", "Write", "Edit", "NotebookEdit", "Glob", "Grep",
|
||||
"TodoWrite", "TaskCreate", "TaskUpdate",
|
||||
"EnterPlanMode", "ExitPlanMode",
|
||||
"Skill", "Agent", "AskUserQuestion", "EnterWorktree",
|
||||
"mcp__",
|
||||
];
|
||||
for (const tool of required) {
|
||||
expect(POST_TOOL_USE_MATCHERS).toContain(tool);
|
||||
}
|
||||
});
|
||||
|
||||
it("POST_TOOL_USE_MATCHERS does NOT contain tools that produce zero events (#229)", () => {
|
||||
const excluded = [
|
||||
"TaskGet", "TaskList", "TaskStop", "TaskOutput",
|
||||
"ExitWorktree", "WebFetch", "WebSearch",
|
||||
"RemoteTrigger", "CronCreate", "CronDelete", "CronList",
|
||||
];
|
||||
for (const tool of excluded) {
|
||||
expect(POST_TOOL_USE_MATCHERS).not.toContain(tool);
|
||||
}
|
||||
});
|
||||
|
||||
it("POST_TOOL_USE_MATCHER_PATTERN is pipe-separated string", () => {
|
||||
expect(POST_TOOL_USE_MATCHER_PATTERN).toBe(POST_TOOL_USE_MATCHERS.join("|"));
|
||||
});
|
||||
});
|
||||
|
||||
// ── parseSessionStartInput ────────────────────────────
|
||||
|
||||
describe("parseSessionStartInput", () => {
|
||||
|
||||
+30
-12
@@ -2,50 +2,68 @@ import { describe, it, expect } from "vitest";
|
||||
import { routePreToolUse } from "../../hooks/core/routing.mjs";
|
||||
import { ROUTING_BLOCK } from "../../hooks/routing-block.mjs";
|
||||
|
||||
describe("Routing: Subagents (Agent/Task)", () => {
|
||||
it("detects prompt in different field names (request, objective, etc.)", () => {
|
||||
describe("Routing: Subagents (Agent only — Task removed per #241)", () => {
|
||||
it("Agent tool injects routing block into prompt field", () => {
|
||||
const fields = ["prompt", "request", "objective", "question", "query", "task"];
|
||||
|
||||
for (const field of fields) {
|
||||
const toolInput = { [field]: "hello" };
|
||||
const decision = routePreToolUse("Agent", toolInput, "/test");
|
||||
|
||||
|
||||
expect(decision.action).toBe("modify");
|
||||
expect(decision.updatedInput[field]).toBe("hello" + ROUTING_BLOCK);
|
||||
}
|
||||
});
|
||||
|
||||
it("falls back to 'prompt' field if no known field is present", () => {
|
||||
it("Agent falls back to 'prompt' field if no known field is present", () => {
|
||||
const toolInput = { unknown_field: "content" };
|
||||
const decision = routePreToolUse("Agent", toolInput, "/test");
|
||||
|
||||
|
||||
expect(decision.action).toBe("modify");
|
||||
expect(decision.updatedInput.prompt).toBe(ROUTING_BLOCK);
|
||||
});
|
||||
|
||||
it("converts subagent_type='Bash' to 'general-purpose' and injects routing", () => {
|
||||
const toolInput = {
|
||||
it("Agent converts subagent_type='Bash' to 'general-purpose'", () => {
|
||||
const toolInput = {
|
||||
prompt: "do something",
|
||||
subagent_type: "Bash"
|
||||
};
|
||||
const decision = routePreToolUse("Agent", toolInput, "/test");
|
||||
|
||||
|
||||
expect(decision.action).toBe("modify");
|
||||
expect(decision.updatedInput.prompt).toBe("do something" + ROUTING_BLOCK);
|
||||
expect(decision.updatedInput.subagent_type).toBe("general-purpose");
|
||||
});
|
||||
|
||||
it("preserves other fields when modifying", () => {
|
||||
const toolInput = {
|
||||
it("Agent preserves other fields when modifying", () => {
|
||||
const toolInput = {
|
||||
request: "analyze this",
|
||||
other_param: 123,
|
||||
nested: { a: 1 }
|
||||
};
|
||||
const decision = routePreToolUse("Task", toolInput, "/test");
|
||||
|
||||
const decision = routePreToolUse("Agent", toolInput, "/test");
|
||||
|
||||
expect(decision.action).toBe("modify");
|
||||
expect(decision.updatedInput.request).toBe("analyze this" + ROUTING_BLOCK);
|
||||
expect(decision.updatedInput.other_param).toBe(123);
|
||||
expect(decision.updatedInput.nested).toEqual({ a: 1 });
|
||||
});
|
||||
|
||||
it("Task tool is NOT routed — returns null (passthrough) (#241)", () => {
|
||||
const toolInput = { prompt: "create a task" };
|
||||
const decision = routePreToolUse("Task", toolInput, "/test");
|
||||
|
||||
// Task should not be intercepted — it matches TaskCreate/TaskUpdate via substring
|
||||
expect(decision).toBeNull();
|
||||
});
|
||||
|
||||
it("TaskCreate is NOT routed — returns null (passthrough)", () => {
|
||||
const decision = routePreToolUse("TaskCreate", { title: "my task" }, "/test");
|
||||
expect(decision).toBeNull();
|
||||
});
|
||||
|
||||
it("TaskUpdate is NOT routed — returns null (passthrough)", () => {
|
||||
const decision = routePreToolUse("TaskUpdate", { id: "123", status: "done" }, "/test");
|
||||
expect(decision).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -332,47 +332,30 @@ describe("routePreToolUse", () => {
|
||||
});
|
||||
});
|
||||
|
||||
// ─── Task routing ──────────────────────────────────────
|
||||
// ─── Task routing (#241: removed — substring matching catches TaskCreate etc.) ──
|
||||
|
||||
describe("Task tool", () => {
|
||||
it("injects ROUTING_BLOCK into prompt", () => {
|
||||
describe("Task tool (#241)", () => {
|
||||
it("returns null (passthrough) — no longer intercepted", () => {
|
||||
const result = routePreToolUse("Task", {
|
||||
prompt: "Analyze the codebase",
|
||||
subagent_type: "general-purpose",
|
||||
});
|
||||
expect(result).not.toBeNull();
|
||||
expect(result!.action).toBe("modify");
|
||||
expect(result!.updatedInput).toBeDefined();
|
||||
expect((result!.updatedInput as Record<string, string>).prompt).toContain(
|
||||
"Analyze the codebase",
|
||||
);
|
||||
expect((result!.updatedInput as Record<string, string>).prompt).toContain(
|
||||
"context_window_protection",
|
||||
);
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it("upgrades Bash subagent to general-purpose", () => {
|
||||
const result = routePreToolUse("Task", {
|
||||
prompt: "Run some commands",
|
||||
subagent_type: "Bash",
|
||||
it("TaskCreate returns null (passthrough)", () => {
|
||||
const result = routePreToolUse("TaskCreate", {
|
||||
title: "my task",
|
||||
});
|
||||
expect(result).not.toBeNull();
|
||||
expect(result!.action).toBe("modify");
|
||||
expect(
|
||||
(result!.updatedInput as Record<string, string>).subagent_type,
|
||||
).toBe("general-purpose");
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it("keeps non-Bash subagent type unchanged", () => {
|
||||
const result = routePreToolUse("Task", {
|
||||
prompt: "Do research",
|
||||
subagent_type: "general-purpose",
|
||||
it("TaskUpdate returns null (passthrough)", () => {
|
||||
const result = routePreToolUse("TaskUpdate", {
|
||||
id: "123",
|
||||
status: "done",
|
||||
});
|
||||
expect(result).not.toBeNull();
|
||||
expect(result!.action).toBe("modify");
|
||||
expect(
|
||||
(result!.updatedInput as Record<string, string>).subagent_type,
|
||||
).toBe("general-purpose");
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -197,89 +197,33 @@ describe("WebFetch", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("Task", () => {
|
||||
test("Task + prompt: hookSpecificOutput with updatedInput containing routing block", () => {
|
||||
describe("Task (#241: no longer routed)", () => {
|
||||
test("Task tool returns empty stdout (passthrough, no routing)", () => {
|
||||
const result = runHook({
|
||||
tool_name: "Task",
|
||||
tool_input: { prompt: "Analyze this codebase and summarize the architecture." },
|
||||
});
|
||||
assert.equal(result.exitCode, 0, `Expected exit 0, got ${result.exitCode}`);
|
||||
assert.ok(result.stdout.length > 0, "Expected non-empty stdout");
|
||||
const parsed = JSON.parse(result.stdout);
|
||||
assert.ok(parsed.hookSpecificOutput, "Expected hookSpecificOutput");
|
||||
assert.equal(parsed.hookSpecificOutput.hookEventName, "PreToolUse");
|
||||
assert.ok(parsed.hookSpecificOutput.updatedInput, "Expected updatedInput");
|
||||
assert.ok(
|
||||
parsed.hookSpecificOutput.updatedInput.prompt.includes("<context_window_protection>"),
|
||||
"Expected <context_window_protection> XML tag in updatedInput.prompt",
|
||||
);
|
||||
assert.ok(
|
||||
parsed.hookSpecificOutput.updatedInput.prompt.includes("</context_window_protection>"),
|
||||
"Expected </context_window_protection> closing tag in updatedInput.prompt",
|
||||
);
|
||||
assert.ok(
|
||||
parsed.hookSpecificOutput.updatedInput.prompt.includes("<tool_selection_hierarchy>"),
|
||||
"Expected <tool_selection_hierarchy> tag in updatedInput.prompt",
|
||||
);
|
||||
assert.ok(
|
||||
parsed.hookSpecificOutput.updatedInput.prompt.includes("<forbidden_actions>"),
|
||||
"Expected <forbidden_actions> tag in updatedInput.prompt",
|
||||
);
|
||||
assert.ok(
|
||||
parsed.hookSpecificOutput.updatedInput.prompt.includes(
|
||||
"Analyze this codebase and summarize the architecture.",
|
||||
),
|
||||
"Expected original prompt preserved in updatedInput.prompt",
|
||||
);
|
||||
// Task is no longer intercepted — hook produces no hookSpecificOutput
|
||||
assert.equal(result.stdout.trim(), "", "Expected empty stdout for passthrough");
|
||||
});
|
||||
|
||||
test("Task + Bash subagent: upgraded to general-purpose for MCP access", () => {
|
||||
test("TaskCreate returns empty stdout (passthrough)", () => {
|
||||
const result = runHook({
|
||||
tool_name: "Task",
|
||||
tool_input: {
|
||||
prompt: "Research this GitHub repository.",
|
||||
subagent_type: "Bash",
|
||||
description: "Research repo",
|
||||
},
|
||||
tool_name: "TaskCreate",
|
||||
tool_input: { title: "my task" },
|
||||
});
|
||||
assert.equal(result.exitCode, 0);
|
||||
const parsed = JSON.parse(result.stdout);
|
||||
const updated = parsed.hookSpecificOutput.updatedInput;
|
||||
assert.equal(
|
||||
updated.subagent_type,
|
||||
"general-purpose",
|
||||
`Expected subagent_type upgraded to general-purpose, got: ${updated.subagent_type}`,
|
||||
);
|
||||
assert.ok(
|
||||
updated.prompt.includes("<context_window_protection>"),
|
||||
"Expected XML routing block in prompt",
|
||||
);
|
||||
assert.ok(
|
||||
updated.prompt.includes("Research this GitHub repository."),
|
||||
"Expected original prompt preserved",
|
||||
);
|
||||
assert.equal(
|
||||
updated.description,
|
||||
"Research repo",
|
||||
"Expected other fields preserved",
|
||||
);
|
||||
assert.equal(result.stdout.trim(), "", "Expected empty stdout for passthrough");
|
||||
});
|
||||
|
||||
test("Task + Explore subagent: keeps original subagent_type", () => {
|
||||
test("TaskUpdate returns empty stdout (passthrough)", () => {
|
||||
const result = runHook({
|
||||
tool_name: "Task",
|
||||
tool_input: {
|
||||
prompt: "Find all TypeScript files.",
|
||||
subagent_type: "Explore",
|
||||
},
|
||||
tool_name: "TaskUpdate",
|
||||
tool_input: { id: "123", status: "done" },
|
||||
});
|
||||
assert.equal(result.exitCode, 0);
|
||||
const parsed = JSON.parse(result.stdout);
|
||||
const updated = parsed.hookSpecificOutput.updatedInput;
|
||||
assert.ok(
|
||||
updated.subagent_type === undefined || updated.subagent_type === "Explore",
|
||||
`Expected subagent_type to remain Explore or undefined, got: ${updated.subagent_type}`,
|
||||
);
|
||||
assert.equal(result.stdout.trim(), "", "Expected empty stdout for passthrough");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -515,8 +459,8 @@ describe("Plugin Tool Name Format in ROUTING_BLOCK", () => {
|
||||
const PLUGIN_PREFIX = "mcp__plugin_context-mode_context-mode__";
|
||||
const SHORT_PREFIX = "mcp__context-mode__";
|
||||
|
||||
test("Task routing block uses plugin-format tool names", () => {
|
||||
const result = runHook({ tool_name: "Task", tool_input: { prompt: "Do something." } });
|
||||
test("Agent routing block uses plugin-format tool names", () => {
|
||||
const result = runHook({ tool_name: "Agent", tool_input: { prompt: "Do something." } });
|
||||
assert.equal(result.exitCode, 0);
|
||||
const parsed = JSON.parse(result.stdout);
|
||||
const prompt = parsed.hookSpecificOutput.updatedInput.prompt;
|
||||
|
||||
@@ -271,15 +271,11 @@ describe("routePreToolUse with platform parameter", () => {
|
||||
expect(result!.reason).toContain("@context-mode/ctx_search");
|
||||
});
|
||||
|
||||
it("Task routing block uses opencode tool names when platform=opencode", () => {
|
||||
it("Task is no longer routed — returns null (#241)", () => {
|
||||
const result = routePreToolUse("Task", {
|
||||
prompt: "Analyze the code",
|
||||
}, "/tmp", "opencode");
|
||||
expect(result).not.toBeNull();
|
||||
const prompt = (result!.updatedInput as Record<string, string>).prompt;
|
||||
expect(prompt).toContain("context-mode_ctx_batch_execute");
|
||||
expect(prompt).toContain("context-mode_ctx_search");
|
||||
expect(prompt).not.toContain("mcp__plugin_context-mode_context-mode__");
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it("Read guidance uses vscode-copilot tool names when platform=vscode-copilot", () => {
|
||||
|
||||
Reference in New Issue
Block a user