72f4669c7f
The previous check used currentAgent?.toLowerCase() === 'athena' which failed after display name remapping stored the agent as 'Athena (Council)' in session state. Now uses getAgentConfigKey() to resolve display names back to config keys, matching the established pattern used by other hooks (atlas, todo-continuation, etc.). Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
906 lines
33 KiB
TypeScript
906 lines
33 KiB
TypeScript
import { describe, expect, test, beforeEach, afterEach, spyOn } from "bun:test"
|
|
import type { PluginInput } from "@opencode-ai/plugin"
|
|
import { createKeywordDetectorHook } from "./index"
|
|
import { setMainSession, updateSessionAgent, clearSessionAgent, _resetForTesting } from "../../features/claude-code-session-state"
|
|
import { ContextCollector } from "../../features/context-injector"
|
|
import * as sharedModule from "../../shared"
|
|
import * as sessionState from "../../features/claude-code-session-state"
|
|
|
|
describe("keyword-detector message transform", () => {
|
|
let logCalls: Array<{ msg: string; data?: unknown }>
|
|
let logSpy: ReturnType<typeof spyOn>
|
|
let getMainSessionSpy: ReturnType<typeof spyOn>
|
|
|
|
beforeEach(() => {
|
|
_resetForTesting()
|
|
logCalls = []
|
|
logSpy = spyOn(sharedModule, "log").mockImplementation((msg: string, data?: unknown) => {
|
|
logCalls.push({ msg, data })
|
|
})
|
|
})
|
|
|
|
afterEach(() => {
|
|
logSpy?.mockRestore()
|
|
getMainSessionSpy?.mockRestore()
|
|
_resetForTesting()
|
|
})
|
|
|
|
function createMockPluginInput() {
|
|
return {
|
|
client: {
|
|
tui: {
|
|
showToast: async () => {},
|
|
},
|
|
},
|
|
} as unknown as PluginInput
|
|
}
|
|
|
|
test("should prepend ultrawork message to text part", async () => {
|
|
// given - a fresh ContextCollector and keyword-detector hook
|
|
const collector = new ContextCollector()
|
|
const hook = createKeywordDetectorHook(createMockPluginInput(), collector)
|
|
const sessionID = "test-session-123"
|
|
const output = {
|
|
message: {} as Record<string, unknown>,
|
|
parts: [{ type: "text", text: "ultrawork do something" }],
|
|
}
|
|
|
|
// when - keyword detection runs
|
|
await hook["chat.message"]({ sessionID }, output)
|
|
|
|
// then - message should be prepended to text part with separator and original text
|
|
const textPart = output.parts.find(p => p.type === "text")
|
|
expect(textPart).toBeDefined()
|
|
expect(textPart!.text).toContain("---")
|
|
expect(textPart!.text).toContain("do something")
|
|
expect(textPart!.text).toContain("YOU MUST LEVERAGE ALL AVAILABLE AGENTS")
|
|
})
|
|
|
|
test("should prepend search message to text part", async () => {
|
|
// given - mock getMainSessionID to return our session (isolate from global state)
|
|
const collector = new ContextCollector()
|
|
const sessionID = "search-test-session"
|
|
getMainSessionSpy = spyOn(sessionState, "getMainSessionID").mockReturnValue(sessionID)
|
|
const hook = createKeywordDetectorHook(createMockPluginInput(), collector)
|
|
const output = {
|
|
message: {} as Record<string, unknown>,
|
|
parts: [{ type: "text", text: "search for the bug" }],
|
|
}
|
|
|
|
// when - keyword detection runs
|
|
await hook["chat.message"]({ sessionID }, output)
|
|
|
|
// then - search message should be prepended to text part
|
|
const textPart = output.parts.find(p => p.type === "text")
|
|
expect(textPart).toBeDefined()
|
|
expect(textPart!.text).toContain("---")
|
|
expect(textPart!.text).toContain("for the bug")
|
|
expect(textPart!.text).toContain("[search-mode]")
|
|
})
|
|
|
|
test("should NOT transform when no keywords detected", async () => {
|
|
// given - no keywords in message
|
|
const collector = new ContextCollector()
|
|
const hook = createKeywordDetectorHook(createMockPluginInput(), collector)
|
|
const sessionID = "test-session"
|
|
const output = {
|
|
message: {} as Record<string, unknown>,
|
|
parts: [{ type: "text", text: "just a normal message" }],
|
|
}
|
|
|
|
// when - keyword detection runs
|
|
await hook["chat.message"]({ sessionID }, output)
|
|
|
|
// then - text should remain unchanged
|
|
const textPart = output.parts.find(p => p.type === "text")
|
|
expect(textPart).toBeDefined()
|
|
expect(textPart!.text).toBe("just a normal message")
|
|
})
|
|
})
|
|
|
|
describe("keyword-detector session filtering", () => {
|
|
let logCalls: Array<{ msg: string; data?: unknown }>
|
|
let logSpy: ReturnType<typeof spyOn>
|
|
|
|
beforeEach(() => {
|
|
_resetForTesting()
|
|
logCalls = []
|
|
logSpy = spyOn(sharedModule, "log").mockImplementation((msg: string, data?: unknown) => {
|
|
logCalls.push({ msg, data })
|
|
})
|
|
})
|
|
|
|
afterEach(() => {
|
|
logSpy?.mockRestore()
|
|
_resetForTesting()
|
|
})
|
|
|
|
function createMockPluginInput(options: { toastCalls?: string[] } = {}) {
|
|
const toastCalls = options.toastCalls ?? []
|
|
return {
|
|
client: {
|
|
tui: {
|
|
showToast: async (opts: { body: { title: string } }) => {
|
|
toastCalls.push(opts.body.title)
|
|
},
|
|
},
|
|
},
|
|
} as unknown as PluginInput
|
|
}
|
|
|
|
test("should skip non-ultrawork keywords in non-main session (using mainSessionID check)", async () => {
|
|
// given - main session is set, different session submits search keyword
|
|
const mainSessionID = "main-123"
|
|
const subagentSessionID = "subagent-456"
|
|
setMainSession(mainSessionID)
|
|
|
|
const hook = createKeywordDetectorHook(createMockPluginInput())
|
|
const output = {
|
|
message: {} as Record<string, unknown>,
|
|
parts: [{ type: "text", text: "search mode 찾아줘" }],
|
|
}
|
|
|
|
// when - non-main session triggers keyword detection
|
|
await hook["chat.message"](
|
|
{ sessionID: subagentSessionID },
|
|
output
|
|
)
|
|
|
|
// then - search keyword should be filtered out based on mainSessionID comparison
|
|
expect(output.message.variant).toBeUndefined()
|
|
expect(output.parts[0]?.text).toBe("search mode 찾아줘")
|
|
})
|
|
|
|
test("should allow ultrawork keywords in non-main session", async () => {
|
|
// given - main session is set, different session submits ultrawork keyword
|
|
const mainSessionID = "main-123"
|
|
const subagentSessionID = "subagent-456"
|
|
setMainSession(mainSessionID)
|
|
|
|
const toastCalls: string[] = []
|
|
const hook = createKeywordDetectorHook(createMockPluginInput({ toastCalls }))
|
|
const output = {
|
|
message: {} as Record<string, unknown>,
|
|
parts: [{ type: "text", text: "ultrawork mode" }],
|
|
}
|
|
|
|
// when - non-main session triggers ultrawork keyword
|
|
await hook["chat.message"](
|
|
{ sessionID: subagentSessionID },
|
|
output
|
|
)
|
|
|
|
// then - ultrawork should still work without forcing a new variant
|
|
expect(output.message.variant).toBeUndefined()
|
|
expect(toastCalls).toContain("Ultrawork Mode Activated")
|
|
})
|
|
|
|
test("should allow all keywords in main session", async () => {
|
|
// given - main session submits search keyword
|
|
const mainSessionID = "main-123"
|
|
setMainSession(mainSessionID)
|
|
|
|
const hook = createKeywordDetectorHook(createMockPluginInput())
|
|
const output = {
|
|
message: {} as Record<string, unknown>,
|
|
parts: [{ type: "text", text: "search mode 찾아줘" }],
|
|
}
|
|
|
|
// when - main session triggers keyword detection
|
|
await hook["chat.message"](
|
|
{ sessionID: mainSessionID },
|
|
output
|
|
)
|
|
|
|
// then - search keyword should be detected (output unchanged but detection happens)
|
|
// Note: search keywords don't set variant, they inject messages via context-injector
|
|
// This test verifies the detection logic runs without filtering
|
|
expect(output.message.variant).toBeUndefined() // search doesn't set variant
|
|
})
|
|
|
|
test("should allow all keywords when mainSessionID is not set", async () => {
|
|
// given - no main session set (early startup or standalone mode)
|
|
setMainSession(undefined)
|
|
|
|
const toastCalls: string[] = []
|
|
const hook = createKeywordDetectorHook(createMockPluginInput({ toastCalls }))
|
|
const output = {
|
|
message: {} as Record<string, unknown>,
|
|
parts: [{ type: "text", text: "ultrawork search" }],
|
|
}
|
|
|
|
// when - any session triggers keyword detection
|
|
await hook["chat.message"](
|
|
{ sessionID: "any-session" },
|
|
output
|
|
)
|
|
|
|
// then - all keywords should work without forcing a new variant
|
|
expect(output.message.variant).toBeUndefined()
|
|
expect(toastCalls).toContain("Ultrawork Mode Activated")
|
|
})
|
|
|
|
test("should preserve existing runtime variant when ultrawork keyword is used", async () => {
|
|
// given - main session set with pre-existing variant from TUI
|
|
setMainSession("main-123")
|
|
|
|
const toastCalls: string[] = []
|
|
const hook = createKeywordDetectorHook(createMockPluginInput({ toastCalls }))
|
|
const output = {
|
|
message: { variant: "low" } as Record<string, unknown>,
|
|
parts: [{ type: "text", text: "ultrawork mode" }],
|
|
}
|
|
|
|
// when - ultrawork keyword triggers
|
|
await hook["chat.message"](
|
|
{ sessionID: "main-123" },
|
|
output
|
|
)
|
|
|
|
// then - ultrawork should preserve the already resolved runtime variant
|
|
expect(output.message.variant).toBe("low")
|
|
expect(toastCalls).toContain("Ultrawork Mode Activated")
|
|
})
|
|
})
|
|
|
|
describe("keyword-detector word boundary", () => {
|
|
let logCalls: Array<{ msg: string; data?: unknown }>
|
|
let logSpy: ReturnType<typeof spyOn>
|
|
|
|
beforeEach(() => {
|
|
_resetForTesting()
|
|
logCalls = []
|
|
logSpy = spyOn(sharedModule, "log").mockImplementation((msg: string, data?: unknown) => {
|
|
logCalls.push({ msg, data })
|
|
})
|
|
})
|
|
|
|
afterEach(() => {
|
|
logSpy?.mockRestore()
|
|
_resetForTesting()
|
|
})
|
|
|
|
function createMockPluginInput(options: { toastCalls?: string[] } = {}) {
|
|
const toastCalls = options.toastCalls ?? []
|
|
return {
|
|
client: {
|
|
tui: {
|
|
showToast: async (opts: { body: { title: string } }) => {
|
|
toastCalls.push(opts.body.title)
|
|
},
|
|
},
|
|
},
|
|
} as unknown as PluginInput
|
|
}
|
|
|
|
test("should NOT trigger ultrawork on partial matches like 'StatefulWidget' containing 'ulw'", async () => {
|
|
// given - text contains 'ulw' as part of another word (StatefulWidget)
|
|
setMainSession(undefined)
|
|
|
|
const toastCalls: string[] = []
|
|
const hook = createKeywordDetectorHook(createMockPluginInput({ toastCalls }))
|
|
const output = {
|
|
message: {} as Record<string, unknown>,
|
|
parts: [{ type: "text", text: "refactor the StatefulWidget component" }],
|
|
}
|
|
|
|
// when - message with partial 'ulw' match is processed
|
|
await hook["chat.message"](
|
|
{ sessionID: "any-session" },
|
|
output
|
|
)
|
|
|
|
// then - ultrawork should NOT be triggered
|
|
expect(output.message.variant).toBeUndefined()
|
|
expect(toastCalls).not.toContain("Ultrawork Mode Activated")
|
|
})
|
|
|
|
test("should trigger ultrawork on standalone 'ulw' keyword", async () => {
|
|
// given - text contains standalone 'ulw'
|
|
setMainSession(undefined)
|
|
|
|
const toastCalls: string[] = []
|
|
const hook = createKeywordDetectorHook(createMockPluginInput({ toastCalls }))
|
|
const output = {
|
|
message: {} as Record<string, unknown>,
|
|
parts: [{ type: "text", text: "ulw do this task" }],
|
|
}
|
|
|
|
// when - message with standalone 'ulw' is processed
|
|
await hook["chat.message"](
|
|
{ sessionID: "any-session" },
|
|
output
|
|
)
|
|
|
|
// then - ultrawork should be triggered without forcing max
|
|
expect(output.message.variant).toBeUndefined()
|
|
expect(toastCalls).toContain("Ultrawork Mode Activated")
|
|
})
|
|
|
|
test("should NOT trigger ultrawork on file references containing 'ulw' substring", async () => {
|
|
// given - file reference contains 'ulw' as substring
|
|
setMainSession(undefined)
|
|
|
|
const toastCalls: string[] = []
|
|
const hook = createKeywordDetectorHook(createMockPluginInput({ toastCalls }))
|
|
const output = {
|
|
message: {} as Record<string, unknown>,
|
|
parts: [{ type: "text", text: "@StatefulWidget.tsx please review this file" }],
|
|
}
|
|
|
|
// when - message referencing file with 'ulw' substring is processed
|
|
await hook["chat.message"](
|
|
{ sessionID: "any-session" },
|
|
output
|
|
)
|
|
|
|
// then - ultrawork should NOT be triggered
|
|
expect(output.message.variant).toBeUndefined()
|
|
expect(toastCalls).not.toContain("Ultrawork Mode Activated")
|
|
})
|
|
})
|
|
|
|
describe("keyword-detector system-reminder filtering", () => {
|
|
let logCalls: Array<{ msg: string; data?: unknown }>
|
|
let logSpy: ReturnType<typeof spyOn>
|
|
|
|
beforeEach(() => {
|
|
_resetForTesting()
|
|
logCalls = []
|
|
logSpy = spyOn(sharedModule, "log").mockImplementation((msg: string, data?: unknown) => {
|
|
logCalls.push({ msg, data })
|
|
})
|
|
})
|
|
|
|
afterEach(() => {
|
|
logSpy?.mockRestore()
|
|
_resetForTesting()
|
|
})
|
|
|
|
function createMockPluginInput() {
|
|
return {
|
|
client: {
|
|
tui: {
|
|
showToast: async () => {},
|
|
},
|
|
},
|
|
} as unknown as PluginInput
|
|
}
|
|
|
|
test("should NOT trigger search mode from keywords inside <system-reminder> tags", async () => {
|
|
// given - message contains search keywords only inside system-reminder tags
|
|
const collector = new ContextCollector()
|
|
const hook = createKeywordDetectorHook(createMockPluginInput(), collector)
|
|
const sessionID = "test-session"
|
|
const output = {
|
|
message: {} as Record<string, unknown>,
|
|
parts: [{
|
|
type: "text",
|
|
text: `<system-reminder>
|
|
The system will search for the file and find all occurrences.
|
|
Please locate and scan the directory.
|
|
</system-reminder>`
|
|
}],
|
|
}
|
|
|
|
// when - keyword detection runs on system-reminder content
|
|
await hook["chat.message"]({ sessionID }, output)
|
|
|
|
// then - should NOT trigger search mode (text should remain unchanged)
|
|
const textPart = output.parts.find(p => p.type === "text")
|
|
expect(textPart).toBeDefined()
|
|
expect(textPart!.text).not.toContain("[search-mode]")
|
|
expect(textPart!.text).toContain("<system-reminder>")
|
|
})
|
|
|
|
test("should NOT trigger analyze mode from keywords inside <system-reminder> tags", async () => {
|
|
// given - message contains analyze keywords only inside system-reminder tags
|
|
const collector = new ContextCollector()
|
|
const hook = createKeywordDetectorHook(createMockPluginInput(), collector)
|
|
const sessionID = "test-session"
|
|
const output = {
|
|
message: {} as Record<string, unknown>,
|
|
parts: [{
|
|
type: "text",
|
|
text: `<system-reminder>
|
|
You should investigate and examine the code carefully.
|
|
Research the implementation details.
|
|
</system-reminder>`
|
|
}],
|
|
}
|
|
|
|
// when - keyword detection runs on system-reminder content
|
|
await hook["chat.message"]({ sessionID }, output)
|
|
|
|
// then - should NOT trigger analyze mode
|
|
const textPart = output.parts.find(p => p.type === "text")
|
|
expect(textPart).toBeDefined()
|
|
expect(textPart!.text).not.toContain("[analyze-mode]")
|
|
expect(textPart!.text).toContain("<system-reminder>")
|
|
})
|
|
|
|
test("should detect keywords in user text even when system-reminder is present", async () => {
|
|
// given - message contains both system-reminder and user search keyword
|
|
const collector = new ContextCollector()
|
|
const hook = createKeywordDetectorHook(createMockPluginInput(), collector)
|
|
const sessionID = "test-session"
|
|
const output = {
|
|
message: {} as Record<string, unknown>,
|
|
parts: [{
|
|
type: "text",
|
|
text: `<system-reminder>
|
|
System will find and locate files.
|
|
</system-reminder>
|
|
|
|
Please search for the bug in the code.`
|
|
}],
|
|
}
|
|
|
|
// when - keyword detection runs on mixed content
|
|
await hook["chat.message"]({ sessionID }, output)
|
|
|
|
// then - should trigger search mode from user text only
|
|
const textPart = output.parts.find(p => p.type === "text")
|
|
expect(textPart).toBeDefined()
|
|
expect(textPart!.text).toContain("[search-mode]")
|
|
expect(textPart!.text).toContain("Please search for the bug in the code.")
|
|
})
|
|
|
|
test("should handle multiple system-reminder tags in message", async () => {
|
|
// given - message contains multiple system-reminder blocks with keywords
|
|
const collector = new ContextCollector()
|
|
const hook = createKeywordDetectorHook(createMockPluginInput(), collector)
|
|
const sessionID = "test-session"
|
|
const output = {
|
|
message: {} as Record<string, unknown>,
|
|
parts: [{
|
|
type: "text",
|
|
text: `<system-reminder>
|
|
First reminder with search and find keywords.
|
|
</system-reminder>
|
|
|
|
User message without keywords.
|
|
|
|
<system-reminder>
|
|
Second reminder with investigate and examine keywords.
|
|
</system-reminder>`
|
|
}],
|
|
}
|
|
|
|
// when - keyword detection runs on message with multiple system-reminders
|
|
await hook["chat.message"]({ sessionID }, output)
|
|
|
|
// then - should NOT trigger any mode (only user text exists, no keywords)
|
|
const textPart = output.parts.find(p => p.type === "text")
|
|
expect(textPart).toBeDefined()
|
|
expect(textPart!.text).not.toContain("[search-mode]")
|
|
expect(textPart!.text).not.toContain("[analyze-mode]")
|
|
})
|
|
|
|
test("should handle case-insensitive system-reminder tags", async () => {
|
|
// given - message contains system-reminder with different casing
|
|
const collector = new ContextCollector()
|
|
const hook = createKeywordDetectorHook(createMockPluginInput(), collector)
|
|
const sessionID = "test-session"
|
|
const output = {
|
|
message: {} as Record<string, unknown>,
|
|
parts: [{
|
|
type: "text",
|
|
text: `<SYSTEM-REMINDER>
|
|
System will search and find files.
|
|
</SYSTEM-REMINDER>`
|
|
}],
|
|
}
|
|
|
|
// when - keyword detection runs on uppercase system-reminder
|
|
await hook["chat.message"]({ sessionID }, output)
|
|
|
|
// then - should NOT trigger search mode
|
|
const textPart = output.parts.find(p => p.type === "text")
|
|
expect(textPart).toBeDefined()
|
|
expect(textPart!.text).not.toContain("[search-mode]")
|
|
})
|
|
|
|
test("should handle multiline system-reminder content with search keywords", async () => {
|
|
// given - system-reminder with multiline content containing various search keywords
|
|
const collector = new ContextCollector()
|
|
const hook = createKeywordDetectorHook(createMockPluginInput(), collector)
|
|
const sessionID = "test-session"
|
|
const output = {
|
|
message: {} as Record<string, unknown>,
|
|
parts: [{
|
|
type: "text",
|
|
text: `<system-reminder>
|
|
Commands executed:
|
|
- find: searched for pattern
|
|
- grep: located file
|
|
- scan: completed
|
|
|
|
Please explore the codebase and discover patterns.
|
|
</system-reminder>`
|
|
}],
|
|
}
|
|
|
|
// when - keyword detection runs on multiline system-reminder
|
|
await hook["chat.message"]({ sessionID }, output)
|
|
|
|
// then - should NOT trigger search mode
|
|
const textPart = output.parts.find(p => p.type === "text")
|
|
expect(textPart).toBeDefined()
|
|
expect(textPart!.text).not.toContain("[search-mode]")
|
|
})
|
|
})
|
|
|
|
describe("keyword-detector agent-specific ultrawork messages", () => {
|
|
let logCalls: Array<{ msg: string; data?: unknown }>
|
|
let logSpy: ReturnType<typeof spyOn>
|
|
|
|
beforeEach(() => {
|
|
_resetForTesting()
|
|
logCalls = []
|
|
logSpy = spyOn(sharedModule, "log").mockImplementation((msg: string, data?: unknown) => {
|
|
logCalls.push({ msg, data })
|
|
})
|
|
})
|
|
|
|
afterEach(() => {
|
|
logSpy?.mockRestore()
|
|
_resetForTesting()
|
|
})
|
|
|
|
function createMockPluginInput() {
|
|
return {
|
|
client: {
|
|
tui: {
|
|
showToast: async () => {},
|
|
},
|
|
},
|
|
} as unknown as PluginInput
|
|
}
|
|
|
|
test("should skip ultrawork injection when agent is prometheus", async () => {
|
|
// given - collector and prometheus agent
|
|
const collector = new ContextCollector()
|
|
const hook = createKeywordDetectorHook(createMockPluginInput(), collector)
|
|
const sessionID = "prometheus-session"
|
|
const output = {
|
|
message: {} as Record<string, unknown>,
|
|
parts: [{ type: "text", text: "ultrawork plan this feature" }],
|
|
}
|
|
|
|
// when - ultrawork keyword detected with prometheus agent
|
|
await hook["chat.message"]({ sessionID, agent: "prometheus" }, output)
|
|
|
|
// then - ultrawork should be skipped for planner agents, text unchanged
|
|
const textPart = output.parts.find(p => p.type === "text")
|
|
expect(textPart).toBeDefined()
|
|
expect(textPart!.text).toBe("ultrawork plan this feature")
|
|
expect(textPart!.text).not.toContain("YOU ARE A PLANNER, NOT AN IMPLEMENTER")
|
|
expect(textPart!.text).not.toContain("YOU MUST LEVERAGE ALL AVAILABLE AGENTS")
|
|
})
|
|
|
|
test("should skip ultrawork injection when agent name contains 'planner'", async () => {
|
|
// given - collector and agent with 'planner' in name
|
|
const collector = new ContextCollector()
|
|
const hook = createKeywordDetectorHook(createMockPluginInput(), collector)
|
|
const sessionID = "planner-session"
|
|
const output = {
|
|
message: {} as Record<string, unknown>,
|
|
parts: [{ type: "text", text: "ulw create a work plan" }],
|
|
}
|
|
|
|
// when - ultrawork keyword detected with planner agent
|
|
await hook["chat.message"]({ sessionID, agent: "Prometheus (Planner)" }, output)
|
|
|
|
// then - ultrawork should be skipped, text unchanged
|
|
const textPart = output.parts.find(p => p.type === "text")
|
|
expect(textPart).toBeDefined()
|
|
expect(textPart!.text).toBe("ulw create a work plan")
|
|
expect(textPart!.text).not.toContain("YOU ARE A PLANNER, NOT AN IMPLEMENTER")
|
|
})
|
|
|
|
test("should skip ultrawork injection when agent name contains 'plan' token", async () => {
|
|
//#given - collector and agent name that includes a plan token
|
|
const collector = new ContextCollector()
|
|
const hook = createKeywordDetectorHook(createMockPluginInput(), collector)
|
|
const sessionID = "plan-agent-session"
|
|
const output = {
|
|
message: {} as Record<string, unknown>,
|
|
parts: [{ type: "text", text: "ultrawork draft a plan" }],
|
|
}
|
|
|
|
//#when - ultrawork keyword detected with plan-like agent name
|
|
await hook["chat.message"]({ sessionID, agent: "Plan Agent" }, output)
|
|
|
|
//#then - ultrawork should be skipped, text unchanged
|
|
const textPart = output.parts.find(p => p.type === "text")
|
|
expect(textPart).toBeDefined()
|
|
expect(textPart!.text).toBe("ultrawork draft a plan")
|
|
expect(textPart!.text).not.toContain("YOU ARE A PLANNER, NOT AN IMPLEMENTER")
|
|
})
|
|
|
|
test("should use normal ultrawork message when agent is Sisyphus", async () => {
|
|
// given - collector and Sisyphus agent
|
|
const collector = new ContextCollector()
|
|
const hook = createKeywordDetectorHook(createMockPluginInput(), collector)
|
|
const sessionID = "sisyphus-session"
|
|
const output = {
|
|
message: {} as Record<string, unknown>,
|
|
parts: [{ type: "text", text: "ultrawork implement this feature" }],
|
|
}
|
|
|
|
// when - ultrawork keyword detected with Sisyphus agent
|
|
await hook["chat.message"]({ sessionID, agent: "sisyphus" }, output)
|
|
|
|
// then - should use normal ultrawork message with agent utilization instructions
|
|
const textPart = output.parts.find(p => p.type === "text")
|
|
expect(textPart).toBeDefined()
|
|
expect(textPart!.text).toContain("YOU MUST LEVERAGE ALL AVAILABLE AGENTS")
|
|
expect(textPart!.text).not.toContain("YOU ARE A PLANNER, NOT AN IMPLEMENTER")
|
|
expect(textPart!.text).toContain("---")
|
|
expect(textPart!.text).toContain("implement this feature")
|
|
})
|
|
|
|
test("should use normal ultrawork message when agent is undefined", async () => {
|
|
// given - collector with no agent specified
|
|
const collector = new ContextCollector()
|
|
const hook = createKeywordDetectorHook(createMockPluginInput(), collector)
|
|
const sessionID = "no-agent-session"
|
|
const output = {
|
|
message: {} as Record<string, unknown>,
|
|
parts: [{ type: "text", text: "ultrawork do something" }],
|
|
}
|
|
|
|
// when - ultrawork keyword detected without agent
|
|
await hook["chat.message"]({ sessionID }, output)
|
|
|
|
// then - should use normal ultrawork message (default behavior)
|
|
const textPart = output.parts.find(p => p.type === "text")
|
|
expect(textPart).toBeDefined()
|
|
expect(textPart!.text).toContain("YOU MUST LEVERAGE ALL AVAILABLE AGENTS")
|
|
expect(textPart!.text).not.toContain("YOU ARE A PLANNER, NOT AN IMPLEMENTER")
|
|
expect(textPart!.text).toContain("---")
|
|
expect(textPart!.text).toContain("do something")
|
|
})
|
|
|
|
test("should skip ultrawork for prometheus but inject for sisyphus", async () => {
|
|
// given - two sessions, one with prometheus, one with sisyphus
|
|
const collector = new ContextCollector()
|
|
const hook = createKeywordDetectorHook(createMockPluginInput(), collector)
|
|
|
|
// First session with prometheus
|
|
const prometheusSessionID = "prometheus-first"
|
|
const prometheusOutput = {
|
|
message: {} as Record<string, unknown>,
|
|
parts: [{ type: "text", text: "ultrawork plan" }],
|
|
}
|
|
await hook["chat.message"]({ sessionID: prometheusSessionID, agent: "prometheus" }, prometheusOutput)
|
|
|
|
// Second session with sisyphus
|
|
const sisyphusSessionID = "sisyphus-second"
|
|
const sisyphusOutput = {
|
|
message: {} as Record<string, unknown>,
|
|
parts: [{ type: "text", text: "ultrawork implement" }],
|
|
}
|
|
await hook["chat.message"]({ sessionID: sisyphusSessionID, agent: "sisyphus" }, sisyphusOutput)
|
|
|
|
// then - prometheus should have no injection, sisyphus should have normal ultrawork
|
|
const prometheusTextPart = prometheusOutput.parts.find(p => p.type === "text")
|
|
expect(prometheusTextPart!.text).toBe("ultrawork plan")
|
|
|
|
const sisyphusTextPart = sisyphusOutput.parts.find(p => p.type === "text")
|
|
expect(sisyphusTextPart!.text).toContain("YOU MUST LEVERAGE ALL AVAILABLE AGENTS")
|
|
expect(sisyphusTextPart!.text).toContain("---")
|
|
expect(sisyphusTextPart!.text).toContain("implement")
|
|
})
|
|
|
|
test("should use session state agent over stale input.agent (bug fix)", async () => {
|
|
// given - same session, agent switched from prometheus to sisyphus in session state
|
|
const collector = new ContextCollector()
|
|
const hook = createKeywordDetectorHook(createMockPluginInput(), collector)
|
|
const sessionID = "same-session-agent-switch"
|
|
|
|
// Simulate: session state was updated to sisyphus (by index.ts updateSessionAgent)
|
|
updateSessionAgent(sessionID, "sisyphus")
|
|
|
|
const output = {
|
|
message: {} as Record<string, unknown>,
|
|
parts: [{ type: "text", text: "ultrawork implement this" }],
|
|
}
|
|
|
|
// when - hook receives stale input.agent="prometheus" but session state says "Sisyphus"
|
|
await hook["chat.message"]({ sessionID, agent: "prometheus" }, output)
|
|
|
|
// then - should use Sisyphus from session state, NOT prometheus from stale input
|
|
const textPart = output.parts.find(p => p.type === "text")
|
|
expect(textPart).toBeDefined()
|
|
expect(textPart!.text).toContain("YOU MUST LEVERAGE ALL AVAILABLE AGENTS")
|
|
expect(textPart!.text).not.toContain("YOU ARE A PLANNER, NOT AN IMPLEMENTER")
|
|
expect(textPart!.text).toContain("---")
|
|
expect(textPart!.text).toContain("implement this")
|
|
|
|
// cleanup
|
|
clearSessionAgent(sessionID)
|
|
})
|
|
|
|
test("should fall back to input.agent when session state is empty and skip ultrawork for prometheus", async () => {
|
|
// given - no session state, only input.agent available
|
|
const collector = new ContextCollector()
|
|
const hook = createKeywordDetectorHook(createMockPluginInput(), collector)
|
|
const sessionID = "no-session-state"
|
|
|
|
// Ensure no session state
|
|
clearSessionAgent(sessionID)
|
|
|
|
const output = {
|
|
message: {} as Record<string, unknown>,
|
|
parts: [{ type: "text", text: "ultrawork plan this" }],
|
|
}
|
|
|
|
// when - hook receives input.agent="prometheus" with no session state
|
|
await hook["chat.message"]({ sessionID, agent: "prometheus" }, output)
|
|
|
|
// then - prometheus fallback from input.agent, ultrawork skipped
|
|
const textPart = output.parts.find(p => p.type === "text")
|
|
expect(textPart).toBeDefined()
|
|
expect(textPart!.text).toBe("ultrawork plan this")
|
|
expect(textPart!.text).not.toContain("YOU ARE A PLANNER, NOT AN IMPLEMENTER")
|
|
})
|
|
|
|
test("should skip ALL keyword injections for Athena with display name", async () => {
|
|
// given - session agent is stored as display name "Athena (Council)" after remapping
|
|
const collector = new ContextCollector()
|
|
const hook = createKeywordDetectorHook(createMockPluginInput(), collector)
|
|
const sessionID = "athena-display-name-session"
|
|
updateSessionAgent(sessionID, "Athena (Council)")
|
|
|
|
const output = {
|
|
message: {} as Record<string, unknown>,
|
|
parts: [{ type: "text", text: "ultrawork search for bugs in the code" }],
|
|
}
|
|
|
|
// when - keyword detection runs with Athena display name in session state
|
|
await hook["chat.message"]({ sessionID }, output)
|
|
|
|
// then - ALL keywords should be skipped (no injection)
|
|
const textPart = output.parts.find(p => p.type === "text")
|
|
expect(textPart!.text).toBe("ultrawork search for bugs in the code")
|
|
expect(textPart!.text).not.toContain("[search-mode]")
|
|
expect(textPart!.text).not.toContain("MAXIMIZE SEARCH EFFORT")
|
|
|
|
const skipLog = logCalls.find(c => c.msg.includes("Skipping all keywords for Athena"))
|
|
expect(skipLog).toBeDefined()
|
|
|
|
clearSessionAgent(sessionID)
|
|
})
|
|
|
|
test("should skip ALL keyword injections for Athena with lowercase config key", async () => {
|
|
// given - session agent is stored as lowercase "athena"
|
|
const collector = new ContextCollector()
|
|
const hook = createKeywordDetectorHook(createMockPluginInput(), collector)
|
|
const sessionID = "athena-lowercase-session"
|
|
|
|
const output = {
|
|
message: {} as Record<string, unknown>,
|
|
parts: [{ type: "text", text: "search for the implementation" }],
|
|
}
|
|
|
|
// when - keyword detection runs with athena as input.agent
|
|
await hook["chat.message"]({ sessionID, agent: "athena" }, output)
|
|
|
|
// then - ALL keywords should be skipped
|
|
const textPart = output.parts.find(p => p.type === "text")
|
|
expect(textPart!.text).toBe("search for the implementation")
|
|
expect(textPart!.text).not.toContain("[search-mode]")
|
|
|
|
const skipLog = logCalls.find(c => c.msg.includes("Skipping all keywords for Athena"))
|
|
expect(skipLog).toBeDefined()
|
|
})
|
|
})
|
|
|
|
describe("keyword-detector non-OMO agent skipping", () => {
|
|
let logCalls: Array<{ msg: string; data?: unknown }>
|
|
let logSpy: ReturnType<typeof spyOn>
|
|
|
|
beforeEach(() => {
|
|
_resetForTesting()
|
|
logCalls = []
|
|
logSpy = spyOn(sharedModule, "log").mockImplementation((msg: string, data?: unknown) => {
|
|
logCalls.push({ msg, data })
|
|
})
|
|
})
|
|
|
|
afterEach(() => {
|
|
logSpy?.mockRestore()
|
|
_resetForTesting()
|
|
})
|
|
|
|
function createMockPluginInput() {
|
|
return {
|
|
client: {
|
|
tui: {
|
|
showToast: async () => {},
|
|
},
|
|
},
|
|
} as unknown as PluginInput
|
|
}
|
|
|
|
test("should skip all keyword injection for OpenCode-Builder agent", async () => {
|
|
// given - keyword-detector hook with Builder agent
|
|
const collector = new ContextCollector()
|
|
const hook = createKeywordDetectorHook(createMockPluginInput(), collector)
|
|
const sessionID = "builder-session"
|
|
const output = {
|
|
message: {} as Record<string, unknown>,
|
|
parts: [{ type: "text", text: "ultrawork search and analyze this code" }],
|
|
}
|
|
|
|
// when - keyword detection runs with OpenCode-Builder agent
|
|
await hook["chat.message"]({ sessionID, agent: "OpenCode-Builder" }, output)
|
|
|
|
// then - no keywords should be injected
|
|
const textPart = output.parts.find(p => p.type === "text")
|
|
expect(textPart).toBeDefined()
|
|
expect(textPart!.text).toBe("ultrawork search and analyze this code")
|
|
})
|
|
|
|
test("should skip all keyword injection for Plan agent", async () => {
|
|
// given - keyword-detector hook with Plan agent
|
|
const collector = new ContextCollector()
|
|
const hook = createKeywordDetectorHook(createMockPluginInput(), collector)
|
|
const sessionID = "plan-session"
|
|
const output = {
|
|
message: {} as Record<string, unknown>,
|
|
parts: [{ type: "text", text: "search mode analyze mode ultrawork" }],
|
|
}
|
|
|
|
// when - keyword detection runs with Plan agent
|
|
await hook["chat.message"]({ sessionID, agent: "Plan" }, output)
|
|
|
|
// then - no keywords should be injected for non-OMO Plan agent
|
|
const textPart = output.parts.find(p => p.type === "text")
|
|
expect(textPart).toBeDefined()
|
|
expect(textPart!.text).toBe("search mode analyze mode ultrawork")
|
|
})
|
|
|
|
test("should still inject keywords for OMO agents like Sisyphus", async () => {
|
|
// given - keyword-detector hook with Sisyphus agent
|
|
const collector = new ContextCollector()
|
|
const hook = createKeywordDetectorHook(createMockPluginInput(), collector)
|
|
const sessionID = "sisyphus-session-omo"
|
|
const output = {
|
|
message: {} as Record<string, unknown>,
|
|
parts: [{ type: "text", text: "ultrawork implement this" }],
|
|
}
|
|
|
|
// when - keyword detection runs with Sisyphus (OMO agent)
|
|
await hook["chat.message"]({ sessionID, agent: "sisyphus" }, output)
|
|
|
|
// then - keywords should be injected normally
|
|
const textPart = output.parts.find(p => p.type === "text")
|
|
expect(textPart).toBeDefined()
|
|
expect(textPart!.text).toContain("YOU MUST LEVERAGE ALL AVAILABLE AGENTS")
|
|
expect(textPart!.text).toContain("implement this")
|
|
})
|
|
|
|
test("should skip keyword injection for agent names containing 'builder'", async () => {
|
|
// given - keyword-detector hook with a builder-variant agent name
|
|
const collector = new ContextCollector()
|
|
const hook = createKeywordDetectorHook(createMockPluginInput(), collector)
|
|
const sessionID = "custom-builder-session"
|
|
const output = {
|
|
message: {} as Record<string, unknown>,
|
|
parts: [{ type: "text", text: "search this codebase" }],
|
|
}
|
|
|
|
// when - keyword detection runs with a builder-type agent
|
|
await hook["chat.message"]({ sessionID, agent: "Custom-Builder" }, output)
|
|
|
|
// then - search-mode should NOT be injected
|
|
const textPart = output.parts.find(p => p.type === "text")
|
|
expect(textPart).toBeDefined()
|
|
expect(textPart!.text).toBe("search this codebase")
|
|
expect(textPart!.text).not.toContain("[search-mode]")
|
|
})
|
|
})
|