mirror of
https://mirror.skon.top/github.com/code-yeongyu/oh-my-opencode
synced 2026-04-30 18:50:29 +08:00
feat(keyword-detector): add hyperplan keyword pattern, message and test
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
244
src/hooks/keyword-detector/hyperplan.test.ts
Normal file
244
src/hooks/keyword-detector/hyperplan.test.ts
Normal file
@@ -0,0 +1,244 @@
|
||||
import { describe, expect, test, beforeEach, afterEach, spyOn } from "bun:test"
|
||||
import type { PluginInput } from "@opencode-ai/plugin"
|
||||
import { createKeywordDetectorHook } from "./index"
|
||||
import { setMainSession, _resetForTesting } from "../../features/claude-code-session-state"
|
||||
import * as sharedModule from "../../shared"
|
||||
import * as sessionState from "../../features/claude-code-session-state"
|
||||
|
||||
describe("keyword-detector hyperplan keyword", () => {
|
||||
let logSpy: ReturnType<typeof spyOn>
|
||||
let getMainSessionSpy: ReturnType<typeof spyOn>
|
||||
|
||||
beforeEach(() => {
|
||||
_resetForTesting()
|
||||
logSpy = spyOn(sharedModule, "log").mockImplementation(() => {})
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
logSpy?.mockRestore()
|
||||
getMainSessionSpy?.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 inject hyperplan message when user types 'hyperplan'", async () => {
|
||||
// given - main session typing the full keyword
|
||||
const sessionID = "hyperplan-full-session"
|
||||
getMainSessionSpy = spyOn(sessionState, "getMainSessionID").mockReturnValue(sessionID)
|
||||
const hook = createKeywordDetectorHook(createMockPluginInput())
|
||||
const output = {
|
||||
message: {} as Record<string, unknown>,
|
||||
parts: [{ type: "text", text: "hyperplan refactor the auth module" }],
|
||||
}
|
||||
|
||||
// when - keyword detection runs
|
||||
await hook["chat.message"]({ sessionID }, output)
|
||||
|
||||
// then - hyperplan-mode wrapper and skill-loading instruction should be present
|
||||
const textPart = output.parts.find(p => p.type === "text")
|
||||
expect(textPart).toBeDefined()
|
||||
expect(textPart!.text).toContain("<hyperplan-mode>")
|
||||
expect(textPart!.text).toContain('skill(name="hyperplan")')
|
||||
expect(textPart!.text).toContain("HYPERPLAN MODE ENABLED")
|
||||
expect(textPart!.text).toContain("refactor the auth module")
|
||||
expect(textPart!.text).toContain("---")
|
||||
})
|
||||
|
||||
test("should inject hyperplan message when user types 'hpp' shorthand", async () => {
|
||||
// given - main session typing the short keyword
|
||||
const sessionID = "hyperplan-short-session"
|
||||
getMainSessionSpy = spyOn(sessionState, "getMainSessionID").mockReturnValue(sessionID)
|
||||
const hook = createKeywordDetectorHook(createMockPluginInput())
|
||||
const output = {
|
||||
message: {} as Record<string, unknown>,
|
||||
parts: [{ type: "text", text: "hpp how should I structure this feature" }],
|
||||
}
|
||||
|
||||
// when - keyword detection runs
|
||||
await hook["chat.message"]({ sessionID }, output)
|
||||
|
||||
// then - hyperplan injection should fire
|
||||
const textPart = output.parts.find(p => p.type === "text")
|
||||
expect(textPart).toBeDefined()
|
||||
expect(textPart!.text).toContain("<hyperplan-mode>")
|
||||
expect(textPart!.text).toContain('skill(name="hyperplan")')
|
||||
})
|
||||
|
||||
test("should inject hyperplan message case-insensitively", async () => {
|
||||
// given - main session typing in mixed case
|
||||
const sessionID = "hyperplan-case-session"
|
||||
getMainSessionSpy = spyOn(sessionState, "getMainSessionID").mockReturnValue(sessionID)
|
||||
const hook = createKeywordDetectorHook(createMockPluginInput())
|
||||
const output = {
|
||||
message: {} as Record<string, unknown>,
|
||||
parts: [{ type: "text", text: "HyperPlan something now" }],
|
||||
}
|
||||
|
||||
// when - keyword detection runs with mixed case input
|
||||
await hook["chat.message"]({ sessionID }, output)
|
||||
|
||||
// then - hyperplan should still fire
|
||||
const textPart = output.parts.find(p => p.type === "text")
|
||||
expect(textPart).toBeDefined()
|
||||
expect(textPart!.text).toContain("<hyperplan-mode>")
|
||||
})
|
||||
|
||||
test("should NOT trigger hyperplan when 'hpp' is a substring of another word", async () => {
|
||||
// given - text contains 'hpp' only as part of larger string with no word boundary
|
||||
const sessionID = "hyperplan-substring-session"
|
||||
getMainSessionSpy = spyOn(sessionState, "getMainSessionID").mockReturnValue(sessionID)
|
||||
const hook = createKeywordDetectorHook(createMockPluginInput())
|
||||
const output = {
|
||||
message: {} as Record<string, unknown>,
|
||||
parts: [{ type: "text", text: "myhppvar = 1" }],
|
||||
}
|
||||
|
||||
// when - keyword detection runs
|
||||
await hook["chat.message"]({ sessionID }, output)
|
||||
|
||||
// then - hyperplan should NOT trigger because 'hpp' lacks word boundaries
|
||||
const textPart = output.parts.find(p => p.type === "text")
|
||||
expect(textPart).toBeDefined()
|
||||
expect(textPart!.text).toBe("myhppvar = 1")
|
||||
expect(textPart!.text).not.toContain("<hyperplan-mode>")
|
||||
})
|
||||
|
||||
test("should fire 'Hyperplan Mode Activated' toast when keyword detected", async () => {
|
||||
// given - main session and toast tracking
|
||||
const sessionID = "hyperplan-toast-session"
|
||||
getMainSessionSpy = spyOn(sessionState, "getMainSessionID").mockReturnValue(sessionID)
|
||||
const toastCalls: string[] = []
|
||||
const hook = createKeywordDetectorHook(createMockPluginInput({ toastCalls }))
|
||||
const output = {
|
||||
message: {} as Record<string, unknown>,
|
||||
parts: [{ type: "text", text: "hyperplan this task" }],
|
||||
}
|
||||
|
||||
// when - hyperplan keyword fires
|
||||
await hook["chat.message"]({ sessionID }, output)
|
||||
|
||||
// then - toast title should be present in tracked calls
|
||||
expect(toastCalls).toContain("Hyperplan Mode Activated")
|
||||
})
|
||||
|
||||
test("should NOT inject hyperplan when disabled_keywords includes 'hyperplan'", async () => {
|
||||
// given - keyword detector with hyperplan disabled
|
||||
const sessionID = "hyperplan-disabled-session"
|
||||
getMainSessionSpy = spyOn(sessionState, "getMainSessionID").mockReturnValue(sessionID)
|
||||
const toastCalls: string[] = []
|
||||
const hook = createKeywordDetectorHook(
|
||||
createMockPluginInput({ toastCalls }),
|
||||
undefined,
|
||||
undefined,
|
||||
{ disabled_keywords: ["hyperplan"] },
|
||||
)
|
||||
const output = {
|
||||
message: {} as Record<string, unknown>,
|
||||
parts: [{ type: "text", text: "hyperplan refactor this" }],
|
||||
}
|
||||
|
||||
// when - hyperplan keyword would normally fire
|
||||
await hook["chat.message"]({ sessionID }, output)
|
||||
|
||||
// then - neither injection nor toast should occur
|
||||
const textPart = output.parts.find(p => p.type === "text")
|
||||
expect(textPart).toBeDefined()
|
||||
expect(textPart!.text).toBe("hyperplan refactor this")
|
||||
expect(textPart!.text).not.toContain("<hyperplan-mode>")
|
||||
expect(toastCalls).not.toContain("Hyperplan Mode Activated")
|
||||
})
|
||||
|
||||
test("should filter hyperplan keyword in non-main session (only ultrawork allowed there)", async () => {
|
||||
// given - main session set, different (subagent) session triggers hyperplan
|
||||
const mainSessionID = "main-hyperplan"
|
||||
const subagentSessionID = "subagent-hyperplan"
|
||||
setMainSession(mainSessionID)
|
||||
const hook = createKeywordDetectorHook(createMockPluginInput())
|
||||
const output = {
|
||||
message: {} as Record<string, unknown>,
|
||||
parts: [{ type: "text", text: "hyperplan please" }],
|
||||
}
|
||||
|
||||
// when - subagent session triggers hyperplan keyword
|
||||
await hook["chat.message"]({ sessionID: subagentSessionID }, output)
|
||||
|
||||
// then - hyperplan injection should be skipped in non-main session
|
||||
const textPart = output.parts.find(p => p.type === "text")
|
||||
expect(textPart).toBeDefined()
|
||||
expect(textPart!.text).toBe("hyperplan please")
|
||||
expect(textPart!.text).not.toContain("<hyperplan-mode>")
|
||||
})
|
||||
|
||||
test("should skip hyperplan injection when agent is prometheus (planner)", async () => {
|
||||
// given - hook running with prometheus agent and a prompt that only triggers hyperplan
|
||||
const sessionID = "hyperplan-prometheus-session"
|
||||
const hook = createKeywordDetectorHook(createMockPluginInput())
|
||||
const output = {
|
||||
message: {} as Record<string, unknown>,
|
||||
parts: [{ type: "text", text: "hyperplan refactor stuff" }],
|
||||
}
|
||||
|
||||
// when - hyperplan keyword detected with prometheus agent
|
||||
await hook["chat.message"]({ sessionID, agent: "prometheus" }, output)
|
||||
|
||||
// then - hyperplan should be filtered out for planner agents
|
||||
const textPart = output.parts.find(p => p.type === "text")
|
||||
expect(textPart).toBeDefined()
|
||||
expect(textPart!.text).not.toContain("<hyperplan-mode>")
|
||||
expect(textPart!.text).not.toContain('skill(name="hyperplan")')
|
||||
expect(textPart!.text).toContain("hyperplan refactor stuff")
|
||||
})
|
||||
|
||||
test("should skip hyperplan injection when agent name contains 'planner' token", async () => {
|
||||
// given - hook running with planner-named agent and a prompt that only triggers hpp
|
||||
const sessionID = "hyperplan-planner-session"
|
||||
const hook = createKeywordDetectorHook(createMockPluginInput())
|
||||
const output = {
|
||||
message: {} as Record<string, unknown>,
|
||||
parts: [{ type: "text", text: "hpp build the feature" }],
|
||||
}
|
||||
|
||||
// when - hpp keyword detected with planner agent
|
||||
await hook["chat.message"]({ sessionID, agent: "Plan Agent" }, output)
|
||||
|
||||
// then - hyperplan should be filtered out
|
||||
const textPart = output.parts.find(p => p.type === "text")
|
||||
expect(textPart).toBeDefined()
|
||||
expect(textPart!.text).not.toContain("<hyperplan-mode>")
|
||||
expect(textPart!.text).not.toContain('skill(name="hyperplan")')
|
||||
expect(textPart!.text).toContain("hpp build the feature")
|
||||
})
|
||||
|
||||
test("should inject hyperplan AND ultrawork together when both keywords present", async () => {
|
||||
// given - main session typing both keywords in the same message
|
||||
const sessionID = "hyperplan-combo-session"
|
||||
getMainSessionSpy = spyOn(sessionState, "getMainSessionID").mockReturnValue(sessionID)
|
||||
const hook = createKeywordDetectorHook(createMockPluginInput())
|
||||
const output = {
|
||||
message: {} as Record<string, unknown>,
|
||||
parts: [{ type: "text", text: "ultrawork hyperplan ship this feature" }],
|
||||
}
|
||||
|
||||
// when - both keywords trigger
|
||||
await hook["chat.message"]({ sessionID }, output)
|
||||
|
||||
// then - both messages should be present
|
||||
const textPart = output.parts.find(p => p.type === "text")
|
||||
expect(textPart).toBeDefined()
|
||||
expect(textPart!.text).toContain("<hyperplan-mode>")
|
||||
expect(textPart!.text).toContain("YOU MUST LEVERAGE ALL AVAILABLE AGENTS")
|
||||
expect(textPart!.text).toContain("ship this feature")
|
||||
})
|
||||
})
|
||||
37
src/hooks/keyword-detector/hyperplan/default.ts
Normal file
37
src/hooks/keyword-detector/hyperplan/default.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
/**
|
||||
* Hyperplan keyword detector.
|
||||
*
|
||||
* Triggers when the user wants adversarial multi-agent planning via team-mode.
|
||||
*
|
||||
* Triggers (case-insensitive, word-bounded):
|
||||
* - English: hyperplan, hpp
|
||||
*
|
||||
* The detector injects a thin wrapper that loads the `hyperplan` skill, which
|
||||
* carries the full orchestration instructions for the 5-member adversarial team.
|
||||
*/
|
||||
|
||||
export const HYPERPLAN_PATTERN = /\b(hyperplan|hpp)\b/i
|
||||
|
||||
export const HYPERPLAN_MESSAGE = `<hyperplan-mode>
|
||||
**MANDATORY**: Say "HYPERPLAN MODE ENABLED!" as your first response, exactly once.
|
||||
|
||||
The user invoked **hyperplan mode** — adversarial multi-agent planning via team-mode.
|
||||
|
||||
LOAD THE HYPERPLAN SKILL IMMEDIATELY:
|
||||
|
||||
\`\`\`
|
||||
skill(name="hyperplan")
|
||||
\`\`\`
|
||||
|
||||
After loading, follow the skill's 6-phase workflow EXACTLY:
|
||||
1. Acknowledge and capture the planning request
|
||||
2. Spawn the 5-member adversarial team via team_create
|
||||
3. Round 1 — Independent analysis (each member produces findings)
|
||||
4. Round 2 — Cross-attack (each member ruthlessly attacks the other 4's findings)
|
||||
5. Round 3 — Defend, refine, or concede
|
||||
6. Synthesize defensible insights into a work plan + clean up the team
|
||||
|
||||
Do NOT improvise. Do NOT skip rounds. Be the lead orchestrator and let the adversarial members do the cross-critique.
|
||||
|
||||
If team-mode is unavailable (\`team_*\` tools missing), instruct the user to set \`team_mode.enabled: true\` in \`~/.config/opencode/oh-my-opencode.jsonc\` and restart opencode.
|
||||
</hyperplan-mode>`
|
||||
1
src/hooks/keyword-detector/hyperplan/index.ts
Normal file
1
src/hooks/keyword-detector/hyperplan/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { HYPERPLAN_PATTERN, HYPERPLAN_MESSAGE } from "./default"
|
||||
Reference in New Issue
Block a user