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-ultrawork combo + tuple refactor
Adjacent "hpp ulw" or "ulw hpp" (8 form combinations: short/long, both
orders) triggers a fused mode that suppresses the standalone ultrawork
and hyperplan banners and toasts in favor of one combo banner. The
combo banner explicitly preserves hyperplan's mandatory adversarial
workflow contract (do NOT improvise, do NOT skip rounds) instead of
silently downgrading it.
Suppression runs as a named pipeline step (suppressComboStandalones)
immediately after detection and before all consumers (planner filter,
session filters, toasts, message injection), so standalone toast checks
naturally see the already-suppressed list. Combo is allowed in non-main
sessions like ultrawork, filtered for planner agents like both
standalones, and blocked in subagent sessions via the existing gate.
disabled_keywords uses the intersection rule: disabling either
"ultrawork" or "hyperplan" also disables the combo, so no flavored
content leaks via the combo embedding when either base keyword is
disabled.
Includes a same-PR refactor of KEYWORD_DETECTORS from {pattern, message}
to {type, pattern, message} tuple shape, dropping the parallel hardcoded
types array in detector.ts that previously coupled type assignment to
registry index. Future detector additions can no longer silently corrupt
DetectedKeyword.type via reorder or insertion.
10 behavioral contract tests in hyperplan-ultrawork.test.ts cover both
trigger orders, non-adjacent rejection, suppression of injection and
toast, intersection-rule disable behavior, session/agent policy, and
ultrawork variant routing through the combo. The pre-existing combined
"ultrawork hyperplan" assertion in hyperplan.test.ts is removed in
favor of the new file.
Plan distilled from a hyperplan adversarial review (5 members,
3 rounds: skeptic, validator, researcher, architect, creative).
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { z } from "zod"
|
||||
|
||||
export const KeywordTypeSchema = z.enum(["ultrawork", "search", "analyze", "team", "hyperplan"])
|
||||
export const KeywordTypeSchema = z.enum(["ultrawork", "search", "analyze", "team", "hyperplan", "hyperplan-ultrawork"])
|
||||
export type KeywordType = z.infer<typeof KeywordTypeSchema>
|
||||
|
||||
export const KeywordDetectorConfigSchema = z.object({
|
||||
|
||||
@@ -7,26 +7,45 @@ export { ANALYZE_PATTERN, ANALYZE_MESSAGE } from "./analyze"
|
||||
export { TEAM_PATTERN, TEAM_MESSAGE } from "./team"
|
||||
export { HYPERPLAN_PATTERN, HYPERPLAN_MESSAGE } from "./hyperplan"
|
||||
|
||||
import type { KeywordType } from "../../config/schema/keyword-detector"
|
||||
import { getUltraworkMessage } from "./ultrawork"
|
||||
import { SEARCH_PATTERN, SEARCH_MESSAGE } from "./search"
|
||||
import { TEAM_PATTERN, TEAM_MESSAGE } from "./team"
|
||||
import { HYPERPLAN_PATTERN, HYPERPLAN_MESSAGE } from "./hyperplan"
|
||||
|
||||
// Hyperplan-ultrawork combo: strict adjacency, both word orders
|
||||
export const HYPERPLAN_ULTRAWORK_PATTERN =
|
||||
/\b(?:hpp|hyperplan)\s+(?:ulw|ultrawork)\b|\b(?:ulw|ultrawork)\s+(?:hpp|hyperplan)\b/i
|
||||
|
||||
const HYPERPLAN_ULTRAWORK_BANNER = `<hyperplan-ultrawork-mode>
|
||||
**MANDATORY**: Say "HYPERPLAN ULTRAWORK MODE ENABLED!" exactly once as your first response. Do NOT say the standalone "ULTRAWORK MODE ENABLED!" or "HYPERPLAN MODE ENABLED!" banners.
|
||||
|
||||
Apply the ultrawork protocol below as your execution framework. You MUST ALSO load the hyperplan skill immediately via \`skill(name="hyperplan")\` and follow its full adversarial workflow — do NOT improvise, do NOT skip rounds, do NOT write the plan yourself.
|
||||
</hyperplan-ultrawork-mode>`
|
||||
|
||||
export function getHyperplanUltraworkMessage(agentName?: string, modelID?: string): string {
|
||||
return `${HYPERPLAN_ULTRAWORK_BANNER}\n\n${getUltraworkMessage(agentName, modelID)}`
|
||||
}
|
||||
|
||||
export type KeywordDetector = {
|
||||
type: KeywordType
|
||||
pattern: RegExp
|
||||
message: string | ((agentName?: string, modelID?: string) => string)
|
||||
}
|
||||
|
||||
export const KEYWORD_DETECTORS: KeywordDetector[] = [
|
||||
{
|
||||
type: "ultrawork",
|
||||
pattern: /\b(ultrawork|ulw)\b/i,
|
||||
message: getUltraworkMessage,
|
||||
},
|
||||
{
|
||||
type: "search",
|
||||
pattern: SEARCH_PATTERN,
|
||||
message: SEARCH_MESSAGE,
|
||||
},
|
||||
{
|
||||
type: "analyze",
|
||||
pattern:
|
||||
/\b(analyze|analyse|investigate|examine|research|study|deep[\s-]?dive|inspect|audit|evaluate|assess|review|diagnose|scrutinize|dissect|debug|comprehend|interpret|breakdown|understand)\b|why\s+is|how\s+does|how\s+to|분석|조사|파악|연구|검토|진단|이해|설명|원인|이유|뜯어봐|따져봐|평가|해석|디버깅|디버그|어떻게|왜|살펴|分析|調査|解析|検討|研究|診断|理解|説明|検証|精査|究明|デバッグ|なぜ|どう|仕組み|调查|检查|剖析|深入|诊断|解释|调试|为什么|原理|搞清楚|弄明白|phân tích|điều tra|nghiên cứu|kiểm tra|xem xét|chẩn đoán|giải thích|tìm hiểu|gỡ lỗi|tại sao/i,
|
||||
message: `[analyze-mode]
|
||||
@@ -46,11 +65,18 @@ MANDATORY delegate_task params: ALWAYS include load_skills=[] and run_in_backgro
|
||||
Example: delegate_task(subagent_type="explore", prompt="...", run_in_background=true, load_skills=[])`,
|
||||
},
|
||||
{
|
||||
type: "team",
|
||||
pattern: TEAM_PATTERN,
|
||||
message: TEAM_MESSAGE,
|
||||
},
|
||||
{
|
||||
type: "hyperplan",
|
||||
pattern: HYPERPLAN_PATTERN,
|
||||
message: HYPERPLAN_MESSAGE,
|
||||
},
|
||||
{
|
||||
type: "hyperplan-ultrawork",
|
||||
pattern: HYPERPLAN_ULTRAWORK_PATTERN,
|
||||
message: getHyperplanUltraworkMessage,
|
||||
},
|
||||
]
|
||||
|
||||
@@ -40,11 +40,14 @@ export function detectKeywordsWithType(
|
||||
disabledKeywords?: ReadonlyArray<KeywordType>,
|
||||
): DetectedKeyword[] {
|
||||
const textWithoutCode = removeCodeBlocks(text)
|
||||
const types: Array<KeywordType> = ["ultrawork", "search", "analyze", "team", "hyperplan"]
|
||||
const disabled = new Set<KeywordType>(disabledKeywords ?? [])
|
||||
return KEYWORD_DETECTORS.map(({ pattern, message }, index) => ({
|
||||
// Intersection rule: combo requires BOTH base keywords enabled
|
||||
if (disabled.has("ultrawork") || disabled.has("hyperplan")) {
|
||||
disabled.add("hyperplan-ultrawork")
|
||||
}
|
||||
return KEYWORD_DETECTORS.map(({ type, pattern, message }) => ({
|
||||
matches: pattern.test(textWithoutCode),
|
||||
type: types[index],
|
||||
type,
|
||||
message: resolveMessage(message, agentName, modelID),
|
||||
}))
|
||||
.filter((result) => result.matches && !disabled.has(result.type))
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { PluginInput } from "@opencode-ai/plugin"
|
||||
import type { KeywordDetectorConfig } from "../../config/schema/keyword-detector"
|
||||
import type { DetectedKeyword } from "./detector"
|
||||
import { detectKeywordsWithType, extractPromptText } from "./detector"
|
||||
import { isPlannerAgent, isNonOmoAgent } from "./constants"
|
||||
import { log } from "../../shared"
|
||||
@@ -15,6 +16,12 @@ import {
|
||||
import type { ContextCollector } from "../../features/context-injector"
|
||||
import type { RalphLoopHook } from "../ralph-loop"
|
||||
|
||||
function suppressComboStandalones(detected: DetectedKeyword[]): DetectedKeyword[] {
|
||||
const hasCombo = detected.some((k) => k.type === "hyperplan-ultrawork")
|
||||
if (!hasCombo) return detected
|
||||
return detected.filter((k) => k.type !== "ultrawork" && k.type !== "hyperplan")
|
||||
}
|
||||
|
||||
export function createKeywordDetectorHook(
|
||||
ctx: PluginInput,
|
||||
_collector?: ContextCollector,
|
||||
@@ -63,10 +70,13 @@ export function createKeywordDetectorHook(
|
||||
const cleanText = removeSystemReminders(promptText)
|
||||
const modelID = input.model?.modelID
|
||||
let detectedKeywords = detectKeywordsWithType(cleanText, currentAgent, modelID, disabledKeywords)
|
||||
detectedKeywords = suppressComboStandalones(detectedKeywords)
|
||||
|
||||
if (isPlannerAgent(currentAgent)) {
|
||||
const preFilterCount = detectedKeywords.length
|
||||
detectedKeywords = detectedKeywords.filter((k) => k.type !== "ultrawork" && k.type !== "hyperplan")
|
||||
detectedKeywords = detectedKeywords.filter(
|
||||
(k) => k.type !== "ultrawork" && k.type !== "hyperplan" && k.type !== "hyperplan-ultrawork"
|
||||
)
|
||||
if (preFilterCount > detectedKeywords.length) {
|
||||
log(`[keyword-detector] Filtered ultrawork/hyperplan keywords for planner agent`, { sessionID: input.sessionID, agent: currentAgent })
|
||||
}
|
||||
@@ -86,7 +96,9 @@ export function createKeywordDetectorHook(
|
||||
const isNonMainSession = mainSessionID && input.sessionID !== mainSessionID
|
||||
|
||||
if (isNonMainSession) {
|
||||
detectedKeywords = detectedKeywords.filter((k) => k.type === "ultrawork")
|
||||
detectedKeywords = detectedKeywords.filter(
|
||||
(k) => k.type === "ultrawork" || k.type === "hyperplan-ultrawork"
|
||||
)
|
||||
if (detectedKeywords.length === 0) {
|
||||
log(`[keyword-detector] Skipping non-ultrawork keywords in non-main session`, {
|
||||
sessionID: input.sessionID,
|
||||
@@ -149,6 +161,21 @@ export function createKeywordDetectorHook(
|
||||
)
|
||||
}
|
||||
|
||||
const hasHyperplanUltrawork = detectedKeywords.some((k) => k.type === "hyperplan-ultrawork")
|
||||
if (hasHyperplanUltrawork) {
|
||||
log(`[keyword-detector] Hyperplan Ultrawork mode activated`, { sessionID: input.sessionID })
|
||||
ctx.client.tui
|
||||
.showToast({
|
||||
body: {
|
||||
title: "Hyperplan Ultrawork Mode Activated",
|
||||
message: "Ultrawork execution with adversarial hyperplan workflow.",
|
||||
variant: "success" as const,
|
||||
duration: 3000,
|
||||
},
|
||||
})
|
||||
.catch((err) => log(`[keyword-detector] Failed to show toast`, { error: err, sessionID: input.sessionID }))
|
||||
}
|
||||
|
||||
const textPartIndex = output.parts.findIndex((p) => p.type === "text" && p.text !== undefined)
|
||||
if (textPartIndex === -1) {
|
||||
log(`[keyword-detector] No text part found, skipping injection`, { sessionID: input.sessionID })
|
||||
|
||||
261
src/hooks/keyword-detector/hyperplan-ultrawork.test.ts
Normal file
261
src/hooks/keyword-detector/hyperplan-ultrawork.test.ts
Normal file
@@ -0,0 +1,261 @@
|
||||
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-ultrawork combo", () => {
|
||||
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 combo message when user types 'hpp ulw' (forward order)", async () => {
|
||||
// given - main session with adjacent forward-order combo keywords
|
||||
const sessionID = "combo-forward-session"
|
||||
getMainSessionSpy = spyOn(sessionState, "getMainSessionID").mockReturnValue(sessionID)
|
||||
const hook = createKeywordDetectorHook(createMockPluginInput())
|
||||
const output = {
|
||||
message: {} as Record<string, unknown>,
|
||||
parts: [{ type: "text", text: "hpp ulw refactor the auth module" }],
|
||||
}
|
||||
|
||||
// when - keyword detection runs
|
||||
await hook["chat.message"]({ sessionID }, output)
|
||||
|
||||
// then - combo banner and embedded ultrawork content both present
|
||||
const textPart = output.parts.find(p => p.type === "text")
|
||||
expect(textPart).toBeDefined()
|
||||
expect(textPart!.text).toContain("<hyperplan-ultrawork-mode>")
|
||||
expect(textPart!.text).toContain("<ultrawork-mode>")
|
||||
expect(textPart!.text).toContain("refactor the auth module")
|
||||
})
|
||||
|
||||
test("should inject combo message when user types 'ulw hpp' (reverse order)", async () => {
|
||||
// given - main session with adjacent reverse-order combo keywords
|
||||
const sessionID = "combo-reverse-session"
|
||||
getMainSessionSpy = spyOn(sessionState, "getMainSessionID").mockReturnValue(sessionID)
|
||||
const hook = createKeywordDetectorHook(createMockPluginInput())
|
||||
const output = {
|
||||
message: {} as Record<string, unknown>,
|
||||
parts: [{ type: "text", text: "ulw hpp ship this feature" }],
|
||||
}
|
||||
|
||||
// when - keyword detection runs
|
||||
await hook["chat.message"]({ sessionID }, output)
|
||||
|
||||
// then - combo fires identically regardless of word order
|
||||
const textPart = output.parts.find(p => p.type === "text")
|
||||
expect(textPart).toBeDefined()
|
||||
expect(textPart!.text).toContain("<hyperplan-ultrawork-mode>")
|
||||
expect(textPart!.text).toContain("<ultrawork-mode>")
|
||||
expect(textPart!.text).toContain("ship this feature")
|
||||
})
|
||||
|
||||
test("should NOT trigger combo on non-adjacent 'hpp do ulw' but fire both standalones instead", async () => {
|
||||
// given - keywords separated by another word block adjacency requirement
|
||||
const sessionID = "combo-non-adjacent-session"
|
||||
getMainSessionSpy = spyOn(sessionState, "getMainSessionID").mockReturnValue(sessionID)
|
||||
const hook = createKeywordDetectorHook(createMockPluginInput())
|
||||
const output = {
|
||||
message: {} as Record<string, unknown>,
|
||||
parts: [{ type: "text", text: "hpp do ulw stuff" }],
|
||||
}
|
||||
|
||||
// when - keyword detection runs
|
||||
await hook["chat.message"]({ sessionID }, output)
|
||||
|
||||
// then - combo absent, both standalone banners injected separately
|
||||
const textPart = output.parts.find(p => p.type === "text")
|
||||
expect(textPart).toBeDefined()
|
||||
expect(textPart!.text).not.toContain("<hyperplan-ultrawork-mode>")
|
||||
expect(textPart!.text).toContain("<hyperplan-mode>")
|
||||
expect(textPart!.text).toContain("<ultrawork-mode>")
|
||||
})
|
||||
|
||||
test("should suppress standalone messages when combo fires (only ONE banner injected)", async () => {
|
||||
// given - combo keywords that would also match standalone patterns
|
||||
const sessionID = "combo-suppress-session"
|
||||
getMainSessionSpy = spyOn(sessionState, "getMainSessionID").mockReturnValue(sessionID)
|
||||
const hook = createKeywordDetectorHook(createMockPluginInput())
|
||||
const output = {
|
||||
message: {} as Record<string, unknown>,
|
||||
parts: [{ type: "text", text: "hpp ulw build" }],
|
||||
}
|
||||
|
||||
// when - keyword detection runs
|
||||
await hook["chat.message"]({ sessionID }, output)
|
||||
|
||||
// then - only combo banner present, standalone hyperplan suppressed, ultrawork content appears once via embed
|
||||
const textPart = output.parts.find(p => p.type === "text")
|
||||
expect(textPart).toBeDefined()
|
||||
expect(textPart!.text).toContain("<hyperplan-ultrawork-mode>")
|
||||
expect(textPart!.text).not.toContain("<hyperplan-mode>")
|
||||
const ultraworkMatches = textPart!.text!.match(/<ultrawork-mode>/g) ?? []
|
||||
expect(ultraworkMatches).toHaveLength(1)
|
||||
})
|
||||
|
||||
test("should fire combo toast and suppress standalone toasts", async () => {
|
||||
// given - combo keywords with toast tracking
|
||||
const sessionID = "combo-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: "hpp ulw do it" }],
|
||||
}
|
||||
|
||||
// when - combo fires
|
||||
await hook["chat.message"]({ sessionID }, output)
|
||||
|
||||
// then - only combo toast title is shown, standalone toasts suppressed
|
||||
expect(toastCalls).toContain("Hyperplan Ultrawork Mode Activated")
|
||||
expect(toastCalls).not.toContain("Ultrawork Mode Activated")
|
||||
expect(toastCalls).not.toContain("Hyperplan Mode Activated")
|
||||
})
|
||||
|
||||
test("should disable combo only when disabled_keywords includes 'hyperplan-ultrawork' (standalones still fire)", async () => {
|
||||
// given - combo keyword disabled but standalones remain enabled
|
||||
const sessionID = "combo-disabled-session"
|
||||
getMainSessionSpy = spyOn(sessionState, "getMainSessionID").mockReturnValue(sessionID)
|
||||
const hook = createKeywordDetectorHook(
|
||||
createMockPluginInput(),
|
||||
undefined,
|
||||
undefined,
|
||||
{ disabled_keywords: ["hyperplan-ultrawork"] },
|
||||
)
|
||||
const output = {
|
||||
message: {} as Record<string, unknown>,
|
||||
parts: [{ type: "text", text: "hpp ulw work it" }],
|
||||
}
|
||||
|
||||
// when - keyword detection runs with combo disabled
|
||||
await hook["chat.message"]({ sessionID }, output)
|
||||
|
||||
// then - combo absent, both individual standalones still match and inject
|
||||
const textPart = output.parts.find(p => p.type === "text")
|
||||
expect(textPart).toBeDefined()
|
||||
expect(textPart!.text).not.toContain("<hyperplan-ultrawork-mode>")
|
||||
expect(textPart!.text).toContain("<hyperplan-mode>")
|
||||
expect(textPart!.text).toContain("<ultrawork-mode>")
|
||||
})
|
||||
|
||||
test("should block combo via intersection rule when disabled_keywords includes 'ultrawork'", async () => {
|
||||
// given - ultrawork standalone disabled, intersection rule cascades to combo
|
||||
const sessionID = "combo-intersection-session"
|
||||
getMainSessionSpy = spyOn(sessionState, "getMainSessionID").mockReturnValue(sessionID)
|
||||
const toastCalls: string[] = []
|
||||
const hook = createKeywordDetectorHook(
|
||||
createMockPluginInput({ toastCalls }),
|
||||
undefined,
|
||||
undefined,
|
||||
{ disabled_keywords: ["ultrawork"] },
|
||||
)
|
||||
const output = {
|
||||
message: {} as Record<string, unknown>,
|
||||
parts: [{ type: "text", text: "hpp ulw plan stuff" }],
|
||||
}
|
||||
|
||||
// when - combo would match but is blocked via intersection
|
||||
await hook["chat.message"]({ sessionID }, output)
|
||||
|
||||
// then - no combo, no ultrawork content leaks; standalone hyperplan still fires
|
||||
const textPart = output.parts.find(p => p.type === "text")
|
||||
expect(textPart).toBeDefined()
|
||||
expect(textPart!.text).not.toContain("<hyperplan-ultrawork-mode>")
|
||||
expect(textPart!.text).not.toContain("<ultrawork-mode>")
|
||||
expect(textPart!.text).toContain("<hyperplan-mode>")
|
||||
expect(toastCalls).not.toContain("Hyperplan Ultrawork Mode Activated")
|
||||
expect(toastCalls).not.toContain("Ultrawork Mode Activated")
|
||||
})
|
||||
|
||||
test("should allow combo in non-main session (passes through like standalone ultrawork)", async () => {
|
||||
// given - main session set, different (subagent) session triggers combo
|
||||
const mainSessionID = "main-combo"
|
||||
const subagentSessionID = "subagent-combo"
|
||||
setMainSession(mainSessionID)
|
||||
const hook = createKeywordDetectorHook(createMockPluginInput())
|
||||
const output = {
|
||||
message: {} as Record<string, unknown>,
|
||||
parts: [{ type: "text", text: "hpp ulw run this" }],
|
||||
}
|
||||
|
||||
// when - subagent session triggers combo
|
||||
await hook["chat.message"]({ sessionID: subagentSessionID }, output)
|
||||
|
||||
// then - combo banner reaches non-main session (whitelisted alongside standalone ultrawork)
|
||||
const textPart = output.parts.find(p => p.type === "text")
|
||||
expect(textPart).toBeDefined()
|
||||
expect(textPart!.text).toContain("<hyperplan-ultrawork-mode>")
|
||||
expect(textPart!.text).toContain("<ultrawork-mode>")
|
||||
expect(textPart!.text).toContain("run this")
|
||||
})
|
||||
|
||||
test("should filter combo when agent is prometheus (planner)", async () => {
|
||||
// given - planner agent receives a combo prompt
|
||||
const sessionID = "combo-prometheus-session"
|
||||
const hook = createKeywordDetectorHook(createMockPluginInput())
|
||||
const output = {
|
||||
message: {} as Record<string, unknown>,
|
||||
parts: [{ type: "text", text: "hpp ulw plan stuff" }],
|
||||
}
|
||||
|
||||
// when - planner-agent path filters all execution-mode keywords
|
||||
await hook["chat.message"]({ sessionID, agent: "prometheus" }, output)
|
||||
|
||||
// then - text untouched: combo, ultrawork, and hyperplan all filtered for planner
|
||||
const textPart = output.parts.find(p => p.type === "text")
|
||||
expect(textPart).toBeDefined()
|
||||
expect(textPart!.text).toBe("hpp ulw plan stuff")
|
||||
expect(textPart!.text).not.toContain("<hyperplan-ultrawork-mode>")
|
||||
expect(textPart!.text).not.toContain("<ultrawork-mode>")
|
||||
expect(textPart!.text).not.toContain("<hyperplan-mode>")
|
||||
})
|
||||
|
||||
test("should reuse ultrawork variant: combo with GPT model embeds GPT ultrawork content", async () => {
|
||||
// given - GPT-5.4 model selects the GPT ultrawork variant inside the combo banner
|
||||
const sessionID = "combo-gpt-variant-session"
|
||||
getMainSessionSpy = spyOn(sessionState, "getMainSessionID").mockReturnValue(sessionID)
|
||||
const hook = createKeywordDetectorHook(createMockPluginInput())
|
||||
const output = {
|
||||
message: {} as Record<string, unknown>,
|
||||
parts: [{ type: "text", text: "hpp ulw build feature" }],
|
||||
}
|
||||
|
||||
// when - combo fires with GPT model resolved
|
||||
await hook["chat.message"](
|
||||
{ sessionID, agent: "sisyphus", model: { providerID: "openai", modelID: "gpt-5.4" } },
|
||||
output,
|
||||
)
|
||||
|
||||
// then - combo banner present and GPT-variant ultrawork content embedded (output_verbosity_spec is GPT-only)
|
||||
const textPart = output.parts.find(p => p.type === "text")
|
||||
expect(textPart).toBeDefined()
|
||||
expect(textPart!.text).toContain("<hyperplan-ultrawork-mode>")
|
||||
expect(textPart!.text).toContain("<output_verbosity_spec>")
|
||||
})
|
||||
})
|
||||
@@ -220,25 +220,4 @@ describe("keyword-detector hyperplan keyword", () => {
|
||||
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")
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user