Add skill support to sisyphus agent
🤖 Generated with assistance of OhMyOpenCode (https://github.com/code-yeongyu/oh-my-opencode)
This commit is contained in:
@@ -11,6 +11,12 @@ export interface AvailableTool {
|
|||||||
category: "lsp" | "ast" | "search" | "session" | "command" | "other"
|
category: "lsp" | "ast" | "search" | "session" | "command" | "other"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface AvailableSkill {
|
||||||
|
name: string
|
||||||
|
description: string
|
||||||
|
location: "user" | "project" | "plugin"
|
||||||
|
}
|
||||||
|
|
||||||
export function categorizeTools(toolNames: string[]): AvailableTool[] {
|
export function categorizeTools(toolNames: string[]): AvailableTool[] {
|
||||||
return toolNames.map((name) => {
|
return toolNames.map((name) => {
|
||||||
let category: AvailableTool["category"] = "other"
|
let category: AvailableTool["category"] = "other"
|
||||||
@@ -51,27 +57,73 @@ function formatToolsForPrompt(tools: AvailableTool[]): string {
|
|||||||
return parts.join(", ")
|
return parts.join(", ")
|
||||||
}
|
}
|
||||||
|
|
||||||
export function buildKeyTriggersSection(agents: AvailableAgent[]): string {
|
export function buildKeyTriggersSection(agents: AvailableAgent[], skills: AvailableSkill[] = []): string {
|
||||||
const keyTriggers = agents
|
const keyTriggers = agents
|
||||||
.filter((a) => a.metadata.keyTrigger)
|
.filter((a) => a.metadata.keyTrigger)
|
||||||
.map((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):
|
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
|
- **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.`
|
- **"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[] = [
|
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) {
|
if (tools.length > 0) {
|
||||||
const toolsDisplay = formatToolsForPrompt(tools)
|
const toolsDisplay = formatToolsForPrompt(tools)
|
||||||
rows.push(`| ${toolsDisplay} | FREE | Not Complex, Scope Clear, No Implicit Assumptions |`)
|
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("")
|
||||||
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")
|
return rows.join("\n")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import type { AgentConfig } from "@opencode-ai/sdk"
|
import type { AgentConfig } from "@opencode-ai/sdk"
|
||||||
import { isGptModel } from "./types"
|
import { isGptModel } from "./types"
|
||||||
import type { AvailableAgent, AvailableTool } from "./sisyphus-prompt-builder"
|
import type { AvailableAgent, AvailableTool, AvailableSkill } from "./sisyphus-prompt-builder"
|
||||||
import {
|
import {
|
||||||
buildKeyTriggersSection,
|
buildKeyTriggersSection,
|
||||||
buildToolSelectionTable,
|
buildToolSelectionTable,
|
||||||
@@ -36,10 +36,25 @@ Named by [YeonGyu Kim](https://github.com/code-yeongyu).
|
|||||||
|
|
||||||
</Role>`
|
</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 |
|
| 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) |
|
| **Trivial** | Single file, known location, direct answer | Direct tools only (UNLESS Key Trigger applies) |
|
||||||
| **Explicit** | Specific file/line, clear command | Execute directly |
|
| **Explicit** | Specific file/line, clear command | Execute directly |
|
||||||
| **Exploratory** | "How does X work?", "Find Y" | Fire explore (1-3) + tools in parallel |
|
| **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 {
|
function buildDynamicSisyphusPrompt(
|
||||||
const keyTriggers = buildKeyTriggersSection(availableAgents)
|
availableAgents: AvailableAgent[],
|
||||||
const toolSelection = buildToolSelectionTable(availableAgents, availableTools)
|
availableTools: AvailableTool[] = [],
|
||||||
|
availableSkills: AvailableSkill[] = []
|
||||||
|
): string {
|
||||||
|
const keyTriggers = buildKeyTriggersSection(availableAgents, availableSkills)
|
||||||
|
const toolSelection = buildToolSelectionTable(availableAgents, availableTools, availableSkills)
|
||||||
const exploreSection = buildExploreSection(availableAgents)
|
const exploreSection = buildExploreSection(availableAgents)
|
||||||
const librarianSection = buildLibrarianSection(availableAgents)
|
const librarianSection = buildLibrarianSection(availableAgents)
|
||||||
const frontendSection = buildFrontendSection(availableAgents)
|
const frontendSection = buildFrontendSection(availableAgents)
|
||||||
@@ -456,12 +475,14 @@ function buildDynamicSisyphusPrompt(availableAgents: AvailableAgent[], available
|
|||||||
export function createSisyphusAgent(
|
export function createSisyphusAgent(
|
||||||
model: string = DEFAULT_MODEL,
|
model: string = DEFAULT_MODEL,
|
||||||
availableAgents?: AvailableAgent[],
|
availableAgents?: AvailableAgent[],
|
||||||
availableToolNames?: string[]
|
availableToolNames?: string[],
|
||||||
|
availableSkills?: AvailableSkill[]
|
||||||
): AgentConfig {
|
): AgentConfig {
|
||||||
const tools = availableToolNames ? categorizeTools(availableToolNames) : []
|
const tools = availableToolNames ? categorizeTools(availableToolNames) : []
|
||||||
|
const skills = availableSkills ?? []
|
||||||
const prompt = availableAgents
|
const prompt = availableAgents
|
||||||
? buildDynamicSisyphusPrompt(availableAgents, tools)
|
? buildDynamicSisyphusPrompt(availableAgents, tools, skills)
|
||||||
: buildDynamicSisyphusPrompt([], tools)
|
: buildDynamicSisyphusPrompt([], tools, skills)
|
||||||
|
|
||||||
const base = {
|
const base = {
|
||||||
description:
|
description:
|
||||||
|
|||||||
Reference in New Issue
Block a user