diff --git a/src/agents/sisyphus-prompt-builder.ts b/src/agents/sisyphus-prompt-builder.ts index a2aebf6..a0eed4d 100644 --- a/src/agents/sisyphus-prompt-builder.ts +++ b/src/agents/sisyphus-prompt-builder.ts @@ -11,6 +11,12 @@ export interface AvailableTool { category: "lsp" | "ast" | "search" | "session" | "command" | "other" } +export interface AvailableSkill { + name: string + description: string + location: "user" | "project" | "plugin" +} + export function categorizeTools(toolNames: string[]): AvailableTool[] { return toolNames.map((name) => { let category: AvailableTool["category"] = "other" @@ -51,27 +57,73 @@ function formatToolsForPrompt(tools: AvailableTool[]): string { return parts.join(", ") } -export function buildKeyTriggersSection(agents: AvailableAgent[]): string { +export function buildKeyTriggersSection(agents: AvailableAgent[], skills: AvailableSkill[] = []): string { const keyTriggers = agents .filter((a) => a.metadata.keyTrigger) .map((a) => `- ${a.metadata.keyTrigger}`) - if (keyTriggers.length === 0) return "" + const skillTriggers = skills + .filter((s) => s.description) + .map((s) => `- **Skill \`${s.name}\`**: ${extractTriggerFromDescription(s.description)}`) + + const allTriggers = [...keyTriggers, ...skillTriggers] + + if (allTriggers.length === 0) return "" return `### Key Triggers (check BEFORE classification): -${keyTriggers.join("\n")} + +**BLOCKING: Check skills FIRST before any action.** +If a skill matches, invoke it IMMEDIATELY via \`skill\` tool. + +${allTriggers.join("\n")} - **GitHub mention (@mention in issue/PR)** → This is a WORK REQUEST. Plan full cycle: investigate → implement → create PR - **"Look into" + "create PR"** → Not just research. Full implementation cycle expected.` } -export function buildToolSelectionTable(agents: AvailableAgent[], tools: AvailableTool[] = []): string { +function extractTriggerFromDescription(description: string): string { + const triggerMatch = description.match(/Trigger[s]?[:\s]+([^.]+)/i) + if (triggerMatch) return triggerMatch[1].trim() + + const activateMatch = description.match(/Activate when[:\s]+([^.]+)/i) + if (activateMatch) return activateMatch[1].trim() + + const useWhenMatch = description.match(/Use (?:this )?when[:\s]+([^.]+)/i) + if (useWhenMatch) return useWhenMatch[1].trim() + + return description.split(".")[0] || description +} + +export function buildToolSelectionTable( + agents: AvailableAgent[], + tools: AvailableTool[] = [], + skills: AvailableSkill[] = [] +): string { const rows: string[] = [ - "### Tool Selection:", + "### Tool & Skill Selection:", + "", + "**Priority Order**: Skills → Direct Tools → Agents", "", - "| Tool | Cost | When to Use |", - "|------|------|-------------|", ] + // Skills section (highest priority) + if (skills.length > 0) { + rows.push("#### Skills (INVOKE FIRST if matching)") + rows.push("") + rows.push("| Skill | When to Use |") + rows.push("|-------|-------------|") + for (const skill of skills) { + const shortDesc = extractTriggerFromDescription(skill.description) + rows.push(`| \`${skill.name}\` | ${shortDesc} |`) + } + rows.push("") + } + + // Tools and Agents table + rows.push("#### Tools & Agents") + rows.push("") + rows.push("| Resource | Cost | When to Use |") + rows.push("|----------|------|-------------|") + if (tools.length > 0) { const toolsDisplay = formatToolsForPrompt(tools) rows.push(`| ${toolsDisplay} | FREE | Not Complex, Scope Clear, No Implicit Assumptions |`) @@ -88,7 +140,7 @@ export function buildToolSelectionTable(agents: AvailableAgent[], tools: Availab } rows.push("") - rows.push("**Default flow**: explore/librarian (background) + tools → oracle (if required)") + rows.push("**Default flow**: skill (if match) → explore/librarian (background) + tools → oracle (if required)") return rows.join("\n") } diff --git a/src/agents/sisyphus.ts b/src/agents/sisyphus.ts index f4f72f0..c1a03ea 100644 --- a/src/agents/sisyphus.ts +++ b/src/agents/sisyphus.ts @@ -1,6 +1,6 @@ import type { AgentConfig } from "@opencode-ai/sdk" import { isGptModel } from "./types" -import type { AvailableAgent, AvailableTool } from "./sisyphus-prompt-builder" +import type { AvailableAgent, AvailableTool, AvailableSkill } from "./sisyphus-prompt-builder" import { buildKeyTriggersSection, buildToolSelectionTable, @@ -36,10 +36,25 @@ Named by [YeonGyu Kim](https://github.com/code-yeongyu). ` -const SISYPHUS_PHASE0_STEP1_3 = `### Step 1: Classify Request Type +const SISYPHUS_PHASE0_STEP1_3 = `### Step 0: Check Skills FIRST (BLOCKING) + +**Before ANY classification or action, scan for matching skills.** + +\`\`\` +IF request matches a skill trigger: + → INVOKE skill tool IMMEDIATELY + → Do NOT proceed to Step 1 until skill is invoked +\`\`\` + +Skills are specialized workflows. When relevant, they handle the task better than manual orchestration. + +--- + +### Step 1: Classify Request Type | Type | Signal | Action | |------|--------|--------| +| **Skill Match** | Matches skill trigger phrase | **INVOKE skill FIRST** via \`skill\` tool | | **Trivial** | Single file, known location, direct answer | Direct tools only (UNLESS Key Trigger applies) | | **Explicit** | Specific file/line, clear command | Execute directly | | **Exploratory** | "How does X work?", "Find Y" | Fire explore (1-3) + tools in parallel | @@ -375,9 +390,13 @@ const SISYPHUS_SOFT_GUIDELINES = `## Soft Guidelines ` -function buildDynamicSisyphusPrompt(availableAgents: AvailableAgent[], availableTools: AvailableTool[] = []): string { - const keyTriggers = buildKeyTriggersSection(availableAgents) - const toolSelection = buildToolSelectionTable(availableAgents, availableTools) +function buildDynamicSisyphusPrompt( + availableAgents: AvailableAgent[], + availableTools: AvailableTool[] = [], + availableSkills: AvailableSkill[] = [] +): string { + const keyTriggers = buildKeyTriggersSection(availableAgents, availableSkills) + const toolSelection = buildToolSelectionTable(availableAgents, availableTools, availableSkills) const exploreSection = buildExploreSection(availableAgents) const librarianSection = buildLibrarianSection(availableAgents) const frontendSection = buildFrontendSection(availableAgents) @@ -456,12 +475,14 @@ function buildDynamicSisyphusPrompt(availableAgents: AvailableAgent[], available export function createSisyphusAgent( model: string = DEFAULT_MODEL, availableAgents?: AvailableAgent[], - availableToolNames?: string[] + availableToolNames?: string[], + availableSkills?: AvailableSkill[] ): AgentConfig { const tools = availableToolNames ? categorizeTools(availableToolNames) : [] + const skills = availableSkills ?? [] const prompt = availableAgents - ? buildDynamicSisyphusPrompt(availableAgents, tools) - : buildDynamicSisyphusPrompt([], tools) + ? buildDynamicSisyphusPrompt(availableAgents, tools, skills) + : buildDynamicSisyphusPrompt([], tools, skills) const base = { description: