Merge pull request #1770 from code-yeongyu/fix/prometheus-md-only-agent-name-matching
fix: use case-insensitive matching for prometheus agent detection
This commit is contained in:
commit
306c7f4c8e
@ -202,7 +202,7 @@ export async function executeSlashCommand(parsed: ParsedSlashCommand, options?:
|
|||||||
if (!command) {
|
if (!command) {
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
error: `Command "/${parsed.command}" not found. Use the slashcommand tool to list available commands.`,
|
error: parsed.command.includes(":") ? `Marketplace plugin commands like "/${parsed.command}" are not supported. Use .claude/commands/ for custom commands.` : `Command "/${parsed.command}" not found. Use the slashcommand tool to list available commands.`,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
5
src/hooks/prometheus-md-only/agent-matcher.ts
Normal file
5
src/hooks/prometheus-md-only/agent-matcher.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import { PROMETHEUS_AGENT } from "./constants"
|
||||||
|
|
||||||
|
export function isPrometheusAgent(agentName: string | undefined): boolean {
|
||||||
|
return agentName?.toLowerCase().includes(PROMETHEUS_AGENT) ?? false
|
||||||
|
}
|
||||||
@ -1,9 +1,10 @@
|
|||||||
import type { PluginInput } from "@opencode-ai/plugin"
|
import type { PluginInput } from "@opencode-ai/plugin"
|
||||||
import { HOOK_NAME, PROMETHEUS_AGENT, BLOCKED_TOOLS, PLANNING_CONSULT_WARNING, PROMETHEUS_WORKFLOW_REMINDER } from "./constants"
|
import { HOOK_NAME, BLOCKED_TOOLS, PLANNING_CONSULT_WARNING, PROMETHEUS_WORKFLOW_REMINDER } from "./constants"
|
||||||
import { log } from "../../shared/logger"
|
import { log } from "../../shared/logger"
|
||||||
import { SYSTEM_DIRECTIVE_PREFIX } from "../../shared/system-directive"
|
import { SYSTEM_DIRECTIVE_PREFIX } from "../../shared/system-directive"
|
||||||
import { getAgentDisplayName } from "../../shared/agent-display-names"
|
import { getAgentDisplayName } from "../../shared/agent-display-names"
|
||||||
import { getAgentFromSession } from "./agent-resolution"
|
import { getAgentFromSession } from "./agent-resolution"
|
||||||
|
import { isPrometheusAgent } from "./agent-matcher"
|
||||||
import { isAllowedFile } from "./path-policy"
|
import { isAllowedFile } from "./path-policy"
|
||||||
|
|
||||||
const TASK_TOOLS = ["task", "call_omo_agent"]
|
const TASK_TOOLS = ["task", "call_omo_agent"]
|
||||||
@ -16,7 +17,7 @@ export function createPrometheusMdOnlyHook(ctx: PluginInput) {
|
|||||||
): Promise<void> => {
|
): Promise<void> => {
|
||||||
const agentName = getAgentFromSession(input.sessionID, ctx.directory)
|
const agentName = getAgentFromSession(input.sessionID, ctx.directory)
|
||||||
|
|
||||||
if (agentName !== PROMETHEUS_AGENT) {
|
if (!isPrometheusAgent(agentName)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -30,11 +30,11 @@ describe("prometheus-md-only", () => {
|
|||||||
} as never
|
} as never
|
||||||
}
|
}
|
||||||
|
|
||||||
function setupMessageStorage(sessionID: string, agent: string): void {
|
function setupMessageStorage(sessionID: string, agent: string | undefined): void {
|
||||||
testMessageDir = join(MESSAGE_STORAGE, sessionID)
|
testMessageDir = join(MESSAGE_STORAGE, sessionID)
|
||||||
mkdirSync(testMessageDir, { recursive: true })
|
mkdirSync(testMessageDir, { recursive: true })
|
||||||
const messageContent = {
|
const messageContent = {
|
||||||
agent,
|
...(agent ? { agent } : {}),
|
||||||
model: { providerID: "test", modelID: "test-model" },
|
model: { providerID: "test", modelID: "test-model" },
|
||||||
}
|
}
|
||||||
writeFileSync(
|
writeFileSync(
|
||||||
@ -55,6 +55,122 @@ describe("prometheus-md-only", () => {
|
|||||||
rmSync(TEST_STORAGE_ROOT, { recursive: true, force: true })
|
rmSync(TEST_STORAGE_ROOT, { recursive: true, force: true })
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe("agent name matching", () => {
|
||||||
|
test("should enforce md-only restriction for exact prometheus agent name", async () => {
|
||||||
|
//#given
|
||||||
|
setupMessageStorage(TEST_SESSION_ID, "prometheus")
|
||||||
|
const hook = createPrometheusMdOnlyHook(createMockPluginInput())
|
||||||
|
const input = {
|
||||||
|
tool: "Write",
|
||||||
|
sessionID: TEST_SESSION_ID,
|
||||||
|
callID: "call-1",
|
||||||
|
}
|
||||||
|
const output = {
|
||||||
|
args: { filePath: "/path/to/file.ts" },
|
||||||
|
}
|
||||||
|
|
||||||
|
//#when //#then
|
||||||
|
await expect(
|
||||||
|
hook["tool.execute.before"](input, output)
|
||||||
|
).rejects.toThrow("can only write/edit .md files")
|
||||||
|
})
|
||||||
|
|
||||||
|
test("should enforce md-only restriction for Prometheus display name Plan Builder", async () => {
|
||||||
|
//#given
|
||||||
|
setupMessageStorage(TEST_SESSION_ID, "Prometheus (Plan Builder)")
|
||||||
|
const hook = createPrometheusMdOnlyHook(createMockPluginInput())
|
||||||
|
const input = {
|
||||||
|
tool: "Write",
|
||||||
|
sessionID: TEST_SESSION_ID,
|
||||||
|
callID: "call-1",
|
||||||
|
}
|
||||||
|
const output = {
|
||||||
|
args: { filePath: "/path/to/file.ts" },
|
||||||
|
}
|
||||||
|
|
||||||
|
//#when //#then
|
||||||
|
await expect(
|
||||||
|
hook["tool.execute.before"](input, output)
|
||||||
|
).rejects.toThrow("can only write/edit .md files")
|
||||||
|
})
|
||||||
|
|
||||||
|
test("should enforce md-only restriction for Prometheus display name Planner", async () => {
|
||||||
|
//#given
|
||||||
|
setupMessageStorage(TEST_SESSION_ID, "Prometheus (Planner)")
|
||||||
|
const hook = createPrometheusMdOnlyHook(createMockPluginInput())
|
||||||
|
const input = {
|
||||||
|
tool: "Write",
|
||||||
|
sessionID: TEST_SESSION_ID,
|
||||||
|
callID: "call-1",
|
||||||
|
}
|
||||||
|
const output = {
|
||||||
|
args: { filePath: "/path/to/file.ts" },
|
||||||
|
}
|
||||||
|
|
||||||
|
//#when //#then
|
||||||
|
await expect(
|
||||||
|
hook["tool.execute.before"](input, output)
|
||||||
|
).rejects.toThrow("can only write/edit .md files")
|
||||||
|
})
|
||||||
|
|
||||||
|
test("should enforce md-only restriction for uppercase PROMETHEUS", async () => {
|
||||||
|
//#given
|
||||||
|
setupMessageStorage(TEST_SESSION_ID, "PROMETHEUS")
|
||||||
|
const hook = createPrometheusMdOnlyHook(createMockPluginInput())
|
||||||
|
const input = {
|
||||||
|
tool: "Write",
|
||||||
|
sessionID: TEST_SESSION_ID,
|
||||||
|
callID: "call-1",
|
||||||
|
}
|
||||||
|
const output = {
|
||||||
|
args: { filePath: "/path/to/file.ts" },
|
||||||
|
}
|
||||||
|
|
||||||
|
//#when //#then
|
||||||
|
await expect(
|
||||||
|
hook["tool.execute.before"](input, output)
|
||||||
|
).rejects.toThrow("can only write/edit .md files")
|
||||||
|
})
|
||||||
|
|
||||||
|
test("should not enforce restriction for non-Prometheus agent", async () => {
|
||||||
|
//#given
|
||||||
|
setupMessageStorage(TEST_SESSION_ID, "sisyphus")
|
||||||
|
const hook = createPrometheusMdOnlyHook(createMockPluginInput())
|
||||||
|
const input = {
|
||||||
|
tool: "Write",
|
||||||
|
sessionID: TEST_SESSION_ID,
|
||||||
|
callID: "call-1",
|
||||||
|
}
|
||||||
|
const output = {
|
||||||
|
args: { filePath: "/path/to/file.ts" },
|
||||||
|
}
|
||||||
|
|
||||||
|
//#when //#then
|
||||||
|
await expect(
|
||||||
|
hook["tool.execute.before"](input, output)
|
||||||
|
).resolves.toBeUndefined()
|
||||||
|
})
|
||||||
|
|
||||||
|
test("should not enforce restriction when agent name is undefined", async () => {
|
||||||
|
//#given
|
||||||
|
setupMessageStorage(TEST_SESSION_ID, undefined)
|
||||||
|
const hook = createPrometheusMdOnlyHook(createMockPluginInput())
|
||||||
|
const input = {
|
||||||
|
tool: "Write",
|
||||||
|
sessionID: TEST_SESSION_ID,
|
||||||
|
callID: "call-1",
|
||||||
|
}
|
||||||
|
const output = {
|
||||||
|
args: { filePath: "/path/to/file.ts" },
|
||||||
|
}
|
||||||
|
|
||||||
|
//#when //#then
|
||||||
|
await expect(
|
||||||
|
hook["tool.execute.before"](input, output)
|
||||||
|
).resolves.toBeUndefined()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
describe("with Prometheus agent in message storage", () => {
|
describe("with Prometheus agent in message storage", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
setupMessageStorage(TEST_SESSION_ID, "prometheus")
|
setupMessageStorage(TEST_SESSION_ID, "prometheus")
|
||||||
|
|||||||
@ -88,7 +88,9 @@ export function createSlashcommandTool(options: SlashcommandToolOptions = {}): T
|
|||||||
return `No exact match for "/${commandName}". Did you mean: ${matchList}?\n\n${formatCommandList(allItems)}`
|
return `No exact match for "/${commandName}". Did you mean: ${matchList}?\n\n${formatCommandList(allItems)}`
|
||||||
}
|
}
|
||||||
|
|
||||||
return `Command or skill "/${commandName}" not found.\n\n${formatCommandList(allItems)}\n\nTry a different name.`
|
return commandName.includes(":")
|
||||||
|
? `Marketplace plugin commands like "/${commandName}" are not supported. Use .claude/commands/ for custom commands.\n\n${formatCommandList(allItems)}`
|
||||||
|
: `Command or skill "/${commandName}" not found.\n\n${formatCommandList(allItems)}\n\nTry a different name.`
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user