Merge branch 'dev' into fix/ulw-boundary-and-skill-mcp-args

This commit is contained in:
Kenny
2026-01-14 09:27:59 -05:00
committed by GitHub
169 changed files with 19742 additions and 6171 deletions
+3 -3
View File
@@ -101,14 +101,14 @@ TELL THE USER WHAT AGENTS YOU WILL LEVERAGE NOW TO SATISFY USER'S REQUEST.
## EXECUTION RULES
- **TODO**: Track EVERY step. Mark complete IMMEDIATELY after each.
- **PARALLEL**: Fire independent agent calls simultaneously via background_task - NEVER wait sequentially.
- **BACKGROUND FIRST**: Use background_task for exploration/research agents (10+ concurrent if needed).
- **PARALLEL**: Fire independent agent calls simultaneously via sisyphus_task(background=true) - NEVER wait sequentially.
- **BACKGROUND FIRST**: Use sisyphus_task for exploration/research agents (10+ concurrent if needed).
- **VERIFY**: Re-read request after completion. Check ALL requirements met before reporting done.
- **DELEGATE**: Don't do everything yourself - orchestrate specialized agents for their strengths.
## WORKFLOW
1. Analyze the request and identify required capabilities
2. Spawn exploration/librarian agents via background_task in PARALLEL (10+ if needed)
2. Spawn exploration/librarian agents via sisyphus_task(background=true) in PARALLEL (10+ if needed)
3. Always Use Plan agent with gathered context to create detailed work breakdown
4. Execute with continuous verification against original requirements
+110
View File
@@ -1,7 +1,95 @@
import { describe, expect, test, beforeEach, afterEach, spyOn } from "bun:test"
import { createKeywordDetectorHook } from "./index"
import { setMainSession } 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 registers to ContextCollector", () => {
let logCalls: Array<{ msg: string; data?: unknown }>
let logSpy: ReturnType<typeof spyOn>
let getMainSessionSpy: ReturnType<typeof spyOn>
beforeEach(() => {
logCalls = []
logSpy = spyOn(sharedModule, "log").mockImplementation((msg: string, data?: unknown) => {
logCalls.push({ msg, data })
})
})
afterEach(() => {
logSpy?.mockRestore()
getMainSessionSpy?.mockRestore()
})
function createMockPluginInput() {
return {
client: {
tui: {
showToast: async () => {},
},
},
} as any
}
test("should register ultrawork keyword to ContextCollector", 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 - ultrawork context should be registered in collector
expect(collector.hasPending(sessionID)).toBe(true)
const pending = collector.getPending(sessionID)
expect(pending.entries.length).toBeGreaterThan(0)
expect(pending.entries[0].source).toBe("keyword-detector")
expect(pending.entries[0].id).toBe("keyword-ultrawork")
})
test("should register search keyword to ContextCollector", 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 context should be registered in collector
expect(collector.hasPending(sessionID)).toBe(true)
const pending = collector.getPending(sessionID)
expect(pending.entries.some((e) => e.id === "keyword-search")).toBe(true)
})
test("should NOT register to collector 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 - nothing should be registered
expect(collector.hasPending(sessionID)).toBe(false)
})
})
describe("keyword-detector session filtering", () => {
let logCalls: Array<{ msg: string; data?: unknown }>
@@ -122,6 +210,28 @@ describe("keyword-detector session filtering", () => {
expect(output.message.variant).toBe("max")
expect(toastCalls).toContain("Ultrawork Mode Activated")
})
test("should not override existing variant", async () => {
// #given - main session set with pre-existing variant
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 - existing variant should remain
expect(output.message.variant).toBe("low")
expect(toastCalls).toContain("Ultrawork Mode Activated")
})
})
describe("keyword-detector word boundary", () => {
+16 -4
View File
@@ -2,12 +2,13 @@ import type { PluginInput } from "@opencode-ai/plugin"
import { detectKeywordsWithType, extractPromptText, removeCodeBlocks } from "./detector"
import { log } from "../../shared"
import { getMainSessionID } from "../../features/claude-code-session-state"
import type { ContextCollector } from "../../features/context-injector"
export * from "./detector"
export * from "./constants"
export * from "./types"
export function createKeywordDetectorHook(ctx: PluginInput) {
export function createKeywordDetectorHook(ctx: PluginInput, collector?: ContextCollector) {
return {
"chat.message": async (
input: {
@@ -28,8 +29,6 @@ export function createKeywordDetectorHook(ctx: PluginInput) {
return
}
// Only ultrawork keywords work in non-main sessions
// Other keywords (search, analyze, etc.) only work in main sessions
const mainSessionID = getMainSessionID()
const isNonMainSession = mainSessionID && input.sessionID !== mainSessionID
@@ -48,7 +47,9 @@ export function createKeywordDetectorHook(ctx: PluginInput) {
if (hasUltrawork) {
log(`[keyword-detector] Ultrawork mode activated`, { sessionID: input.sessionID })
output.message.variant = "max"
if (output.message.variant === undefined) {
output.message.variant = "max"
}
ctx.client.tui
.showToast({
@@ -64,6 +65,17 @@ export function createKeywordDetectorHook(ctx: PluginInput) {
)
}
if (collector) {
for (const keyword of detectedKeywords) {
collector.register(input.sessionID, {
id: `keyword-${keyword.type}`,
source: "keyword-detector",
content: keyword.message,
priority: keyword.type === "ultrawork" ? "critical" : "high",
})
}
}
log(`[keyword-detector] Detected ${detectedKeywords.length} keywords`, {
sessionID: input.sessionID,
types: detectedKeywords.map((k) => k.type),