Add skill support to sisyphus agent

🤖 Generated with assistance of OhMyOpenCode (https://github.com/code-yeongyu/oh-my-opencode)
This commit is contained in:
YeonGyu-Kim
2026-01-01 15:37:24 +09:00
parent fbae3aeb6b
commit 4efa58616f
2 changed files with 89 additions and 16 deletions

View File

@@ -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")
}

View File

@@ -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).
</Role>`
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: