2026-02-08 13:34:47 +09:00
import type { AgentPromptMetadata } from "./types"
2026-02-07 19:08:08 +09:00
import { truncateDescription } from "../shared/truncate-description"
2025-12-30 23:56:09 +09:00
export interface AvailableAgent {
2026-02-08 13:34:47 +09:00
name : string
2025-12-30 23:56:09 +09:00
description : string
metadata : AgentPromptMetadata
}
export interface AvailableTool {
name : string
category : "lsp" | "ast" | "search" | "session" | "command" | "other"
}
2026-01-01 15:37:24 +09:00
export interface AvailableSkill {
name : string
description : string
location : "user" | "project" | "plugin"
}
2026-01-20 16:53:46 +09:00
export interface AvailableCategory {
name : string
description : string
2026-02-06 17:31:13 +09:00
model? : string
2026-01-20 16:53:46 +09:00
}
2025-12-30 23:56:09 +09:00
export function categorizeTools ( toolNames : string [ ] ) : AvailableTool [ ] {
return toolNames . map ( ( name ) = > {
let category : AvailableTool [ "category" ] = "other"
if ( name . startsWith ( "lsp_" ) ) {
category = "lsp"
} else if ( name . startsWith ( "ast_grep" ) ) {
category = "ast"
} else if ( name === "grep" || name === "glob" ) {
category = "search"
} else if ( name . startsWith ( "session_" ) ) {
category = "session"
} else if ( name === "slashcommand" ) {
category = "command"
}
return { name , category }
} )
}
function formatToolsForPrompt ( tools : AvailableTool [ ] ) : string {
const lspTools = tools . filter ( ( t ) = > t . category === "lsp" )
const astTools = tools . filter ( ( t ) = > t . category === "ast" )
const searchTools = tools . filter ( ( t ) = > t . category === "search" )
const parts : string [ ] = [ ]
if ( searchTools . length > 0 ) {
parts . push ( . . . searchTools . map ( ( t ) = > ` \` ${ t . name } \` ` ) )
}
if ( lspTools . length > 0 ) {
parts . push ( "`lsp_*`" )
}
if ( astTools . length > 0 ) {
parts . push ( "`ast_grep`" )
}
return parts . join ( ", " )
}
2026-01-22 22:45:41 +09:00
export function buildKeyTriggersSection ( agents : AvailableAgent [ ] , _skills : AvailableSkill [ ] = [ ] ) : string {
2025-12-30 23:56:09 +09:00
const keyTriggers = agents
. filter ( ( a ) = > a . metadata . keyTrigger )
. map ( ( a ) = > ` - ${ a . metadata . keyTrigger } ` )
2026-01-22 22:45:41 +09:00
if ( keyTriggers . length === 0 ) return ""
2025-12-30 23:56:09 +09:00
return ` ### Key Triggers (check BEFORE classification):
2026-01-01 15:37:24 +09:00
2026-01-22 22:45:41 +09:00
${ keyTriggers . join ( "\n" ) }
2025-12-30 23:56:09 +09:00
- **"Look into" + "create PR"** → Not just research. Full implementation cycle expected. `
}
2026-01-01 15:37:24 +09:00
export function buildToolSelectionTable (
agents : AvailableAgent [ ] ,
tools : AvailableTool [ ] = [ ] ,
2026-01-22 22:45:41 +09:00
_skills : AvailableSkill [ ] = [ ]
2026-01-01 15:37:24 +09:00
) : string {
2025-12-30 23:56:09 +09:00
const rows : string [ ] = [
2026-01-22 22:45:41 +09:00
"### Tool & Agent Selection:" ,
2025-12-30 23:56:09 +09:00
"" ,
]
2026-01-01 15:37:24 +09:00
rows . push ( "| Resource | Cost | When to Use |" )
rows . push ( "|----------|------|-------------|" )
2025-12-30 23:56:09 +09:00
if ( tools . length > 0 ) {
const toolsDisplay = formatToolsForPrompt ( tools )
rows . push ( ` | ${ toolsDisplay } | FREE | Not Complex, Scope Clear, No Implicit Assumptions | ` )
}
const costOrder = { FREE : 0 , CHEAP : 1 , EXPENSIVE : 2 }
const sortedAgents = [ . . . agents ]
. filter ( ( a ) = > a . metadata . category !== "utility" )
. sort ( ( a , b ) = > costOrder [ a . metadata . cost ] - costOrder [ b . metadata . cost ] )
for ( const agent of sortedAgents ) {
const shortDesc = agent . description . split ( "." ) [ 0 ] || agent . description
rows . push ( ` | \` ${ agent . name } \` agent | ${ agent . metadata . cost } | ${ shortDesc } | ` )
}
rows . push ( "" )
2026-01-22 22:45:41 +09:00
rows . push ( "**Default flow**: explore/librarian (background) + tools → oracle (if required)" )
2025-12-30 23:56:09 +09:00
return rows . join ( "\n" )
}
export function buildExploreSection ( agents : AvailableAgent [ ] ) : string {
const exploreAgent = agents . find ( ( a ) = > a . name === "explore" )
if ( ! exploreAgent ) return ""
const useWhen = exploreAgent . metadata . useWhen || [ ]
const avoidWhen = exploreAgent . metadata . avoidWhen || [ ]
return ` ### Explore Agent = Contextual Grep
Use it as a **peer tool**, not a fallback. Fire liberally.
| Use Direct Tools | Use Explore Agent |
|------------------|-------------------|
${ avoidWhen . map ( ( w ) = > ` | ${ w } | | ` ) . join ( "\n" ) }
${ useWhen . map ( ( w ) = > ` | | ${ w } | ` ) . join ( "\n" ) } `
}
export function buildLibrarianSection ( agents : AvailableAgent [ ] ) : string {
const librarianAgent = agents . find ( ( a ) = > a . name === "librarian" )
if ( ! librarianAgent ) return ""
const useWhen = librarianAgent . metadata . useWhen || [ ]
return ` ### Librarian Agent = Reference Grep
Search **external references** (docs, OSS, web). Fire proactively when unfamiliar libraries are involved.
| Contextual Grep (Internal) | Reference Grep (External) |
|----------------------------|---------------------------|
| Search OUR codebase | Search EXTERNAL resources |
| Find patterns in THIS repo | Find examples in OTHER repos |
| How does our code work? | How does this library work? |
| Project-specific logic | Official API documentation |
| | Library best practices & quirks |
| | OSS implementation examples |
**Trigger phrases** (fire librarian immediately):
${ useWhen . map ( ( w ) = > ` - " ${ w } " ` ) . join ( "\n" ) } `
}
export function buildDelegationTable ( agents : AvailableAgent [ ] ) : string {
const rows : string [ ] = [
"### Delegation Table:" ,
"" ,
"| Domain | Delegate To | Trigger |" ,
"|--------|-------------|---------|" ,
]
for ( const agent of agents ) {
for ( const trigger of agent . metadata . triggers ) {
rows . push ( ` | ${ trigger . domain } | \` ${ agent . name } \` | ${ trigger . trigger } | ` )
}
}
return rows . join ( "\n" )
}
2026-02-04 17:47:08 +01:00
/**
* Renders the "User-Installed Skills (HIGH PRIORITY)" block used across multiple agent prompts.
* Extracted to avoid duplication between buildCategorySkillsDelegationGuide, buildSkillsSection, etc.
*/
export function formatCustomSkillsBlock (
customRows : string [ ] ,
customSkills : AvailableSkill [ ] ,
headerLevel : "####" | "**" = "####"
) : string {
const customSkillNames = customSkills . map ( ( s ) = > ` " ${ s . name } " ` ) . join ( ", " )
const header = headerLevel === "####"
? ` #### User-Installed Skills (HIGH PRIORITY) `
: ` **User-Installed Skills (HIGH PRIORITY):** `
return ` ${ header }
**The user has installed these custom skills. They MUST be evaluated for EVERY delegation.**
Subagents are STATELESS — they lose all custom knowledge unless you pass these skills via \` load_skills \` .
| Skill | Expertise Domain | Source |
|-------|------------------|--------|
${ customRows . join ( "\n" ) }
> **CRITICAL**: Ignoring user-installed skills when they match the task domain is a failure.
> The user installed ${ customSkillNames } for a reason — USE THEM when the task overlaps with their domain. `
}
2026-01-20 16:53:46 +09:00
export function buildCategorySkillsDelegationGuide ( categories : AvailableCategory [ ] , skills : AvailableSkill [ ] ) : string {
if ( categories . length === 0 && skills . length === 0 ) return ""
const categoryRows = categories . map ( ( c ) = > {
const desc = c . description || c . name
return ` | \` ${ c . name } \` | ${ desc } | `
} )
2026-02-04 17:27:32 +01:00
const builtinSkills = skills . filter ( ( s ) = > s . location === "plugin" )
const customSkills = skills . filter ( ( s ) = > s . location !== "plugin" )
2026-02-07 19:08:08 +09:00
const builtinRows = builtinSkills . map ( ( s ) = > {
const desc = truncateDescription ( s . description )
return ` | \` ${ s . name } \` | ${ desc } | `
} )
2026-01-20 16:53:46 +09:00
2026-02-07 19:08:08 +09:00
const customRows = customSkills . map ( ( s ) = > {
const desc = truncateDescription ( s . description )
const source = s . location === "project" ? "project" : "user"
return ` | \` ${ s . name } \` | ${ desc } | ${ source } | `
} )
2026-02-04 17:27:32 +01:00
2026-02-04 17:47:08 +01:00
const customSkillBlock = formatCustomSkillsBlock ( customRows , customSkills )
2026-02-04 17:27:32 +01:00
let skillsSection : string
if ( customSkills . length > 0 && builtinSkills . length > 0 ) {
skillsSection = ` #### Built-in Skills
| Skill | Expertise Domain |
|-------|------------------|
${ builtinRows . join ( "\n" ) }
2026-02-04 17:47:08 +01:00
${ customSkillBlock } `
2026-02-04 17:27:32 +01:00
} else if ( customSkills . length > 0 ) {
2026-02-04 17:47:08 +01:00
skillsSection = customSkillBlock
2026-02-04 17:27:32 +01:00
} else {
skillsSection = ` #### Available Skills (Domain Expertise Injection)
Skills inject specialized instructions into the subagent. Read the description to understand when each skill applies.
| Skill | Expertise Domain |
|-------|------------------|
${ builtinRows . join ( "\n" ) } `
}
2026-01-20 16:53:46 +09:00
return ` ### Category + Skills Delegation System
2026-02-06 16:01:54 +09:00
**task() combines categories and skills for optimal task execution.**
2026-01-20 16:53:46 +09:00
#### Available Categories (Domain-Optimized Models)
2025-12-30 23:56:09 +09:00
2026-01-20 16:53:46 +09:00
Each category is configured with a model optimized for that domain. Read the description to understand when to use it.
2025-12-30 23:56:09 +09:00
2026-01-20 16:53:46 +09:00
| Category | Domain / Best For |
|----------|-------------------|
${ categoryRows . join ( "\n" ) }
2025-12-30 23:56:09 +09:00
2026-02-04 17:27:32 +01:00
${ skillsSection }
2025-12-30 23:56:09 +09:00
2026-01-20 16:53:46 +09:00
---
### MANDATORY: Category + Skill Selection Protocol
**STEP 1: Select Category**
- Read each category's description
- Match task requirements to category domain
- Select the category whose domain BEST fits the task
2026-02-04 17:27:32 +01:00
**STEP 2: Evaluate ALL Skills (Built-in AND User-Installed)**
2026-01-20 16:53:46 +09:00
For EVERY skill listed above, ask yourself:
> "Does this skill's expertise domain overlap with my task?"
2026-01-22 22:45:41 +09:00
- If YES → INCLUDE in \` load_skills=[...] \`
2026-01-20 16:53:46 +09:00
- If NO → You MUST justify why (see below)
2026-02-04 17:27:32 +01:00
${ customSkills . length > 0 ? `
> **User-installed skills get PRIORITY.** The user explicitly installed them for their workflow.
> When in doubt about a user-installed skill, INCLUDE it rather than omit it. ` : "" }
2026-01-20 16:53:46 +09:00
**STEP 3: Justify Omissions**
If you choose NOT to include a skill that MIGHT be relevant, you MUST provide:
2025-12-30 23:56:09 +09:00
2026-01-15 16:32:14 +09:00
\` \` \`
2026-01-20 16:53:46 +09:00
SKILL EVALUATION for "[skill-name]":
- Skill domain: [what the skill description says]
- Task domain: [what your task is about]
- Decision: OMIT
- Reason: [specific explanation of why domains don't overlap]
2026-01-15 16:32:14 +09:00
\` \` \`
2025-12-30 23:56:09 +09:00
2026-01-20 16:53:46 +09:00
**WHY JUSTIFICATION IS MANDATORY:**
- Forces you to actually READ skill descriptions
- Prevents lazy omission of potentially useful skills
- Subagents are STATELESS - they only know what you tell them
- Missing a relevant skill = suboptimal output
2026-01-15 16:32:14 +09:00
2026-01-20 16:53:46 +09:00
---
2026-01-15 16:32:14 +09:00
2026-01-20 16:53:46 +09:00
### Delegation Pattern
2026-01-15 16:32:14 +09:00
2026-01-20 16:53:46 +09:00
\` \` \` typescript
2026-02-06 16:01:54 +09:00
task(
2026-01-20 16:53:46 +09:00
category="[selected-category]",
2026-02-04 17:27:32 +01:00
load_skills=["skill-1", "skill-2"], // Include ALL relevant skills — ESPECIALLY user-installed ones
2026-01-20 16:53:46 +09:00
prompt="..."
)
\` \` \`
2026-01-15 16:32:14 +09:00
2026-01-20 16:53:46 +09:00
**ANTI-PATTERN (will produce poor results):**
\` \` \` typescript
2026-02-06 16:01:54 +09:00
task(category="...", load_skills=[], run_in_background=false, prompt="...") // Empty load_skills without justification
2026-01-20 16:53:46 +09:00
\` \` \` `
2025-12-30 23:56:09 +09:00
}
export function buildOracleSection ( agents : AvailableAgent [ ] ) : string {
const oracleAgent = agents . find ( ( a ) = > a . name === "oracle" )
if ( ! oracleAgent ) return ""
const useWhen = oracleAgent . metadata . useWhen || [ ]
const avoidWhen = oracleAgent . metadata . avoidWhen || [ ]
return ` <Oracle_Usage>
2026-01-09 02:24:43 +09:00
## Oracle — Read-Only High-IQ Consultant
2025-12-30 23:56:09 +09:00
2026-01-09 02:24:43 +09:00
Oracle is a read-only, expensive, high-quality reasoning model for debugging and architecture. Consultation only.
2025-12-30 23:56:09 +09:00
### WHEN to Consult:
| Trigger | Action |
|---------|--------|
${ useWhen . map ( ( w ) = > ` | ${ w } | Oracle FIRST, then implement | ` ) . join ( "\n" ) }
### WHEN NOT to Consult:
${ avoidWhen . map ( ( w ) = > ` - ${ w } ` ) . join ( "\n" ) }
### Usage Pattern:
Briefly announce "Consulting Oracle for [reason]" before invocation.
**Exception**: This is the ONLY case where you announce before acting. For all other work, start immediately without status updates.
2026-02-17 03:53:02 +09:00
### Oracle Background Task Policy:
- Oracle takes 20+ min by design. Always wait for Oracle results via \` background_output \` before final answer.
- Oracle provides independent analysis from a different angle that catches blind spots — even when you believe you already have sufficient context, Oracle's perspective is worth the wait.
2025-12-30 23:56:09 +09:00
</Oracle_Usage> `
}
2026-01-20 16:53:46 +09:00
export function buildHardBlocksSection ( ) : string {
2025-12-30 23:56:09 +09:00
const blocks = [
"| Type error suppression (`as any`, `@ts-ignore`) | Never |" ,
"| Commit without explicit request | Never |" ,
"| Speculate about unread code | Never |" ,
"| Leave code in broken state after failures | Never |" ,
]
return ` ## Hard Blocks (NEVER violate)
| Constraint | No Exceptions |
|------------|---------------|
${ blocks . join ( "\n" ) } `
}
2026-01-20 16:53:46 +09:00
export function buildAntiPatternsSection ( ) : string {
2025-12-30 23:56:09 +09:00
const patterns = [
"| **Type Safety** | `as any`, `@ts-ignore`, `@ts-expect-error` |" ,
"| **Error Handling** | Empty catch blocks `catch(e) {}` |" ,
"| **Testing** | Deleting failing tests to \"pass\" |" ,
"| **Search** | Firing agents for single-line typos or obvious syntax errors |" ,
"| **Debugging** | Shotgun debugging, random changes |" ,
]
return ` ## Anti-Patterns (BLOCKING violations)
| Category | Forbidden |
|----------|-----------|
${ patterns . join ( "\n" ) } `
}
2026-01-03 12:45:18 +09:00
2026-01-20 16:53:46 +09:00
export function buildUltraworkSection (
agents : AvailableAgent [ ] ,
categories : AvailableCategory [ ] ,
skills : AvailableSkill [ ]
) : string {
2026-01-03 12:51:04 +09:00
const lines : string [ ] = [ ]
2026-01-20 16:53:46 +09:00
if ( categories . length > 0 ) {
lines . push ( "**Categories** (for implementation tasks):" )
for ( const cat of categories ) {
const shortDesc = cat . description || cat . name
lines . push ( ` - \` ${ cat . name } \` : ${ shortDesc } ` )
}
lines . push ( "" )
}
if ( skills . length > 0 ) {
2026-02-04 17:27:32 +01:00
const builtinSkills = skills . filter ( ( s ) = > s . location === "plugin" )
const customSkills = skills . filter ( ( s ) = > s . location !== "plugin" )
if ( builtinSkills . length > 0 ) {
lines . push ( "**Built-in Skills** (combine with categories):" )
for ( const skill of builtinSkills ) {
const shortDesc = skill . description . split ( "." ) [ 0 ] || skill . description
lines . push ( ` - \` ${ skill . name } \` : ${ shortDesc } ` )
}
lines . push ( "" )
}
if ( customSkills . length > 0 ) {
lines . push ( "**User-Installed Skills** (HIGH PRIORITY - user installed these for their workflow):" )
for ( const skill of customSkills ) {
const shortDesc = skill . description . split ( "." ) [ 0 ] || skill . description
lines . push ( ` - \` ${ skill . name } \` : ${ shortDesc } ` )
}
lines . push ( "" )
2026-01-20 16:53:46 +09:00
}
}
if ( agents . length > 0 ) {
const ultraworkAgentPriority = [ "explore" , "librarian" , "plan" , "oracle" ]
const sortedAgents = [ . . . agents ] . sort ( ( a , b ) = > {
const aIdx = ultraworkAgentPriority . indexOf ( a . name )
const bIdx = ultraworkAgentPriority . indexOf ( b . name )
if ( aIdx === - 1 && bIdx === - 1 ) return 0
if ( aIdx === - 1 ) return 1
if ( bIdx === - 1 ) return - 1
return aIdx - bIdx
} )
lines . push ( "**Agents** (for specialized consultation/exploration):" )
for ( const agent of sortedAgents ) {
2026-02-07 18:45:29 +09:00
const shortDesc = agent . description . length > 120 ? agent . description . slice ( 0 , 120 ) + "..." : agent . description
2026-01-20 16:53:46 +09:00
const suffix = agent . name === "explore" || agent . name === "librarian" ? " (multiple)" : ""
lines . push ( ` - \` ${ agent . name } ${ suffix } \` : ${ shortDesc } ` )
}
2026-01-03 12:45:18 +09:00
}
2026-01-03 12:51:04 +09:00
return lines . join ( "\n" )
2026-01-03 12:45:18 +09:00
}