Merge pull request #3436 from code-yeongyu/fix/bug-batch-1
fix: numeric skill names, ZWSP agent lookups, ultrawork run_in_background
This commit is contained in:
@@ -87,7 +87,7 @@ export async function loadSkillFromPathAsync(
|
||||
const mcpJsonMcp = await loadMcpJsonFromDirAsync(resolvedPath)
|
||||
const mcpConfig = mcpJsonMcp || frontmatterMcp
|
||||
|
||||
const baseName = data.name || defaultName
|
||||
const baseName = String(data.name || defaultName)
|
||||
const skillName = namePrefix ? `${namePrefix}/${baseName}` : baseName
|
||||
const originalDescription = data.description || ""
|
||||
const isOpencodeSource = scope === "opencode" || scope === "opencode-project"
|
||||
|
||||
@@ -25,7 +25,7 @@ export async function loadSkillFromPath(options: {
|
||||
const mcpJsonMcp = await loadMcpJsonFromDir(options.resolvedPath)
|
||||
const mcpConfig = mcpJsonMcp || frontmatterMcp
|
||||
|
||||
const baseName = data.name || options.defaultName
|
||||
const baseName = String(data.name || options.defaultName)
|
||||
const skillName = namePrefix ? `${namePrefix}/${baseName}` : baseName
|
||||
const originalDescription = data.description || ""
|
||||
const isOpencodeSource = options.scope === "opencode" || options.scope === "opencode-project"
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { stripInvisibleAgentCharacters } from "../../shared/agent-display-names"
|
||||
import type { CompactionAgentConfigCheckpoint } from "../../shared/compaction-agent-config-checkpoint"
|
||||
|
||||
export type RecoveryPromptConfig = CompactionAgentConfigCheckpoint & {
|
||||
@@ -66,7 +67,7 @@ export function isPromptConfigRecovered(
|
||||
const agentMatches =
|
||||
typeof actualAgent === "string" &&
|
||||
!isCompactionAgent(actualAgent) &&
|
||||
actualAgent.toLowerCase() === expectedPromptConfig.agent.toLowerCase()
|
||||
stripInvisibleAgentCharacters(actualAgent).toLowerCase() === stripInvisibleAgentCharacters(expectedPromptConfig.agent).toLowerCase()
|
||||
|
||||
return (
|
||||
agentMatches &&
|
||||
|
||||
@@ -104,7 +104,7 @@ TELL THE USER WHAT AGENTS YOU WILL LEVERAGE NOW TO SATISFY USER'S REQUEST.
|
||||
| Architecture decision needed | MUST call plan agent |
|
||||
|
||||
\`\`\`
|
||||
task(subagent_type="plan", load_skills=[], prompt="<gathered context + user request>")
|
||||
task(subagent_type="plan", load_skills=[], run_in_background=false, prompt="<gathered context + user request>")
|
||||
\`\`\`
|
||||
|
||||
**WHY PLAN AGENT IS MANDATORY:**
|
||||
@@ -119,9 +119,9 @@ task(subagent_type="plan", load_skills=[], prompt="<gathered context + user requ
|
||||
|
||||
| Scenario | Action |
|
||||
|----------|--------|
|
||||
| Plan agent asks clarifying questions | \`task(session_id="{returned_session_id}", load_skills=[], prompt="<your answer>")\` |
|
||||
| Need to refine the plan | \`task(session_id="{returned_session_id}", load_skills=[], prompt="Please adjust: <feedback>")\` |
|
||||
| Plan needs more detail | \`task(session_id="{returned_session_id}", load_skills=[], prompt="Add more detail to Task N")\` |
|
||||
| Plan agent asks clarifying questions | \`task(session_id="{returned_session_id}", load_skills=[], run_in_background=false, prompt="<your answer>")\` |
|
||||
| Need to refine the plan | \`task(session_id="{returned_session_id}", load_skills=[], run_in_background=false, prompt="Please adjust: <feedback>")\` |
|
||||
| Plan needs more detail | \`task(session_id="{returned_session_id}", load_skills=[], run_in_background=false, prompt="Add more detail to Task N")\` |
|
||||
|
||||
**WHY SESSION_ID IS CRITICAL:**
|
||||
- Plan agent retains FULL conversation context
|
||||
@@ -131,10 +131,10 @@ task(subagent_type="plan", load_skills=[], prompt="<gathered context + user requ
|
||||
|
||||
\`\`\`
|
||||
// WRONG: Starting fresh loses all context
|
||||
task(subagent_type="plan", load_skills=[], prompt="Here's more info...")
|
||||
task(subagent_type="plan", load_skills=[], run_in_background=false, prompt="Here's more info...")
|
||||
|
||||
// CORRECT: Resume preserves everything
|
||||
task(session_id="ses_abc123", load_skills=[], prompt="Here's my answer to your question: ...")
|
||||
task(session_id="ses_abc123", load_skills=[], run_in_background=false, prompt="Here's my answer to your question: ...")
|
||||
\`\`\`
|
||||
|
||||
**FAILURE TO CALL PLAN AGENT = INCOMPLETE WORK.**
|
||||
@@ -149,21 +149,21 @@ task(session_id="ses_abc123", load_skills=[], prompt="Here's my answer to your q
|
||||
|-----------|--------|-----|
|
||||
| Codebase exploration | task(subagent_type="explore", load_skills=[], run_in_background=true) | Parallel, context-efficient |
|
||||
| Documentation lookup | task(subagent_type="librarian", load_skills=[], run_in_background=true) | Specialized knowledge |
|
||||
| Planning | task(subagent_type="plan", load_skills=[]) | Parallel task graph + structured TODO list |
|
||||
| Hard problem (conventional) | task(subagent_type="oracle", load_skills=[]) | Architecture, debugging, complex logic |
|
||||
| Hard problem (non-conventional) | task(category="artistry", load_skills=[...]) | Different approach needed |
|
||||
| Implementation | task(category="...", load_skills=[...]) | Domain-optimized models |
|
||||
| Planning | task(subagent_type="plan", load_skills=[], run_in_background=false) | Parallel task graph + structured TODO list |
|
||||
| Hard problem (conventional) | task(subagent_type="oracle", load_skills=[], run_in_background=false) | Architecture, debugging, complex logic |
|
||||
| Hard problem (non-conventional) | task(category="artistry", load_skills=[...], run_in_background=true) | Different approach needed |
|
||||
| Implementation | task(category="...", load_skills=[...], run_in_background=true) | Domain-optimized models |
|
||||
|
||||
**CATEGORY + SKILL DELEGATION:**
|
||||
\`\`\`
|
||||
// Frontend work
|
||||
task(category="visual-engineering", load_skills=["frontend-ui-ux"])
|
||||
task(category="visual-engineering", load_skills=["frontend-ui-ux"], run_in_background=true)
|
||||
|
||||
// Complex logic
|
||||
task(category="ultrabrain", load_skills=["typescript-programmer"])
|
||||
task(category="ultrabrain", load_skills=["typescript-programmer"], run_in_background=true)
|
||||
|
||||
// Quick fixes
|
||||
task(category="quick", load_skills=["git-master"])
|
||||
task(category="quick", load_skills=["git-master"], run_in_background=true)
|
||||
\`\`\`
|
||||
|
||||
**YOU SHOULD ONLY DO IT YOURSELF WHEN:**
|
||||
|
||||
@@ -156,7 +156,7 @@ TELL THE USER WHAT AGENTS YOU WILL LEVERAGE NOW TO SATISFY USER'S REQUEST.
|
||||
| Architecture decision needed | MUST call plan agent |
|
||||
|
||||
\`\`\`
|
||||
task(subagent_type="plan", load_skills=[], prompt="<gathered context + user request>")
|
||||
task(subagent_type="plan", load_skills=[], run_in_background=false, prompt="<gathered context + user request>")
|
||||
\`\`\`
|
||||
|
||||
### SESSION CONTINUITY WITH PLAN AGENT (CRITICAL)
|
||||
@@ -165,9 +165,9 @@ task(subagent_type="plan", load_skills=[], prompt="<gathered context + user requ
|
||||
|
||||
| Scenario | Action |
|
||||
|----------|--------|
|
||||
| Plan agent asks clarifying questions | \`task(session_id="{returned_session_id}", load_skills=[], prompt="<your answer>")\` |
|
||||
| Need to refine the plan | \`task(session_id="{returned_session_id}", load_skills=[], prompt="Please adjust: <feedback>")\` |
|
||||
| Plan needs more detail | \`task(session_id="{returned_session_id}", load_skills=[], prompt="Add more detail to Task N")\` |
|
||||
| Plan agent asks clarifying questions | \`task(session_id="{returned_session_id}", load_skills=[], run_in_background=false, prompt="<your answer>")\` |
|
||||
| Need to refine the plan | \`task(session_id="{returned_session_id}", load_skills=[], run_in_background=false, prompt="Please adjust: <feedback>")\` |
|
||||
| Plan needs more detail | \`task(session_id="{returned_session_id}", load_skills=[], run_in_background=false, prompt="Add more detail to Task N")\` |
|
||||
|
||||
**FAILURE TO CALL PLAN AGENT = INCOMPLETE WORK.**
|
||||
|
||||
@@ -183,10 +183,10 @@ task(subagent_type="plan", load_skills=[], prompt="<gathered context + user requ
|
||||
|-----------|--------|-----|
|
||||
| Codebase exploration | task(subagent_type="explore", load_skills=[], run_in_background=true) | Parallel, context-efficient |
|
||||
| Documentation lookup | task(subagent_type="librarian", load_skills=[], run_in_background=true) | Specialized knowledge |
|
||||
| Planning | task(subagent_type="plan", load_skills=[]) | Parallel task graph + structured TODO list |
|
||||
| Hard problem (conventional) | task(subagent_type="oracle", load_skills=[]) | Architecture, debugging, complex logic |
|
||||
| Hard problem (non-conventional) | task(category="artistry", load_skills=[...]) | Different approach needed |
|
||||
| Implementation | task(category="...", load_skills=[...]) | Domain-optimized models |
|
||||
| Planning | task(subagent_type="plan", load_skills=[], run_in_background=false) | Parallel task graph + structured TODO list |
|
||||
| Hard problem (conventional) | task(subagent_type="oracle", load_skills=[], run_in_background=false) | Architecture, debugging, complex logic |
|
||||
| Hard problem (non-conventional) | task(category="artistry", load_skills=[...], run_in_background=true) | Different approach needed |
|
||||
| Implementation | task(category="...", load_skills=[...], run_in_background=true) | Domain-optimized models |
|
||||
|
||||
**YOU SHOULD ONLY DO IT YOURSELF WHEN:**
|
||||
- Task is trivially simple (1-2 lines, obvious change)
|
||||
|
||||
@@ -71,9 +71,9 @@ Use these when they provide clear value based on the decision framework above:
|
||||
|----------|-------------|------------|
|
||||
| explore agent | Need codebase patterns you don't have | \`task(subagent_type="explore", load_skills=[], run_in_background=true, ...)\` |
|
||||
| librarian agent | External library docs, OSS examples | \`task(subagent_type="librarian", load_skills=[], run_in_background=true, ...)\` |
|
||||
| oracle agent | Stuck on architecture/debugging after 2+ attempts | \`task(subagent_type="oracle", load_skills=[], ...)\` |
|
||||
| plan agent | Complex multi-step with dependencies (5+ steps) | \`task(subagent_type="plan", load_skills=[], ...)\` |
|
||||
| task category | Specialized work matching a category | \`task(category="...", load_skills=[...])\` |
|
||||
| oracle agent | Stuck on architecture/debugging after 2+ attempts | \`task(subagent_type="oracle", load_skills=[], run_in_background=false, ...)\` |
|
||||
| plan agent | Complex multi-step with dependencies (5+ steps) | \`task(subagent_type="plan", load_skills=[], run_in_background=false, ...)\` |
|
||||
| task category | Specialized work matching a category | \`task(category="...", load_skills=[...], run_in_background=true)\` |
|
||||
|
||||
<tool_usage_rules>
|
||||
- Prefer tools over internal knowledge for fresh or user-specific data
|
||||
|
||||
@@ -117,7 +117,7 @@ Each TODO item MUST include:
|
||||
|
||||
| Wave | Tasks | Dispatch Command |
|
||||
|------|-------|------------------|
|
||||
| 1 | 1, 4 | \`task(category="...", load_skills=[...], run_in_background=false)\` × 2 |
|
||||
| 1 | 1, 4 | \`task(category="...", load_skills=[...], run_in_background=true)\` × 2 |
|
||||
| 2 | 2, 3, 5 | \`task(...)\` × 3 after Wave 1 completes |
|
||||
| 3 | 6 | \`task(...)\` final integration |
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { stripInvisibleAgentCharacters } from "../../shared/agent-display-names"
|
||||
import { ULTRAWORK_VERIFICATION_PROMISE } from "./constants"
|
||||
|
||||
export interface OracleVerificationEvidence {
|
||||
@@ -54,7 +55,7 @@ export function isOracleVerified(text: string): boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
const isOracleAgent = evidence.agent.toLowerCase() === "oracle"
|
||||
const isOracleAgent = stripInvisibleAgentCharacters(evidence.agent).toLowerCase() === "oracle"
|
||||
const isVerifiedPromise = evidence.promise === ULTRAWORK_VERIFICATION_PROMISE
|
||||
|
||||
return isOracleAgent && isVerifiedPromise
|
||||
@@ -62,7 +63,7 @@ export function isOracleVerified(text: string): boolean {
|
||||
|
||||
export function extractOracleSessionID(text: string): string | undefined {
|
||||
const evidence = parseOracleVerificationEvidence(text)
|
||||
if (!evidence || evidence.agent.toLowerCase() !== "oracle") {
|
||||
if (!evidence || stripInvisibleAgentCharacters(evidence.agent).toLowerCase() !== "oracle") {
|
||||
return undefined
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { consumeToolMetadata } from "../features/tool-metadata-store"
|
||||
import type { CreatedHooks } from "../create-hooks"
|
||||
import { log } from "../shared"
|
||||
import { stripInvisibleAgentCharacters } from "../shared/agent-display-names"
|
||||
import type { PluginContext } from "./types"
|
||||
import { readState, writeState } from "../hooks/ralph-loop/storage"
|
||||
|
||||
@@ -60,7 +61,7 @@ export function createToolExecuteAfterHandler(args: {
|
||||
const verificationAttemptId = prompt?.match(VERIFICATION_ATTEMPT_PATTERN)?.[1]?.trim()
|
||||
const loopState = directory ? readState(directory) : null
|
||||
const isVerificationContext =
|
||||
agent === "oracle"
|
||||
(agent ? stripInvisibleAgentCharacters(agent) : agent) === "oracle"
|
||||
&& !!sessionId
|
||||
&& !!directory
|
||||
&& loopState?.active === true
|
||||
|
||||
@@ -4,6 +4,7 @@ import { randomUUID } from "node:crypto"
|
||||
import { getMainSessionID } from "../features/claude-code-session-state"
|
||||
import { clearBoulderState } from "../features/boulder-state"
|
||||
import { log } from "../shared"
|
||||
import { stripInvisibleAgentCharacters } from "../shared/agent-display-names"
|
||||
import { resolveSessionAgent } from "./session-agent-resolver"
|
||||
import { parseRalphLoopArguments } from "../hooks/ralph-loop/command-arguments"
|
||||
import { ULTRAWORK_VERIFICATION_PROMISE } from "../hooks/ralph-loop/constants"
|
||||
@@ -109,7 +110,7 @@ export function createToolExecuteBeforeHandler(args: {
|
||||
}
|
||||
|
||||
const normalizedSubagentType =
|
||||
typeof argsObject.subagent_type === "string" ? argsObject.subagent_type : undefined
|
||||
typeof argsObject.subagent_type === "string" ? stripInvisibleAgentCharacters(argsObject.subagent_type) : undefined
|
||||
const prompt = typeof argsObject.prompt === "string" ? argsObject.prompt : ""
|
||||
const loopState = typeof ctx.directory === "string" ? readState(ctx.directory) : null
|
||||
const shouldInjectOracleVerification =
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { stripInvisibleAgentCharacters } from "./agent-display-names"
|
||||
|
||||
/**
|
||||
* Agent tool restrictions for session.prompt calls.
|
||||
* OpenCode SDK's session.prompt `tools` parameter expects boolean values.
|
||||
@@ -45,13 +47,15 @@ const AGENT_RESTRICTIONS: Record<string, Record<string, boolean>> = {
|
||||
}
|
||||
|
||||
export function getAgentToolRestrictions(agentName: string): Record<string, boolean> {
|
||||
return AGENT_RESTRICTIONS[agentName]
|
||||
?? Object.entries(AGENT_RESTRICTIONS).find(([key]) => key.toLowerCase() === agentName.toLowerCase())?.[1]
|
||||
const stripped = stripInvisibleAgentCharacters(agentName)
|
||||
return AGENT_RESTRICTIONS[stripped]
|
||||
?? Object.entries(AGENT_RESTRICTIONS).find(([key]) => key.toLowerCase() === stripped.toLowerCase())?.[1]
|
||||
?? {}
|
||||
}
|
||||
|
||||
export function hasAgentToolRestrictions(agentName: string): boolean {
|
||||
const restrictions = AGENT_RESTRICTIONS[agentName]
|
||||
?? Object.entries(AGENT_RESTRICTIONS).find(([key]) => key.toLowerCase() === agentName.toLowerCase())?.[1]
|
||||
const stripped = stripInvisibleAgentCharacters(agentName)
|
||||
const restrictions = AGENT_RESTRICTIONS[stripped]
|
||||
?? Object.entries(AGENT_RESTRICTIONS).find(([key]) => key.toLowerCase() === stripped.toLowerCase())?.[1]
|
||||
return restrictions !== undefined && Object.keys(restrictions).length > 0
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { OhMyOpenCodeConfig } from "../config"
|
||||
import { stripInvisibleAgentCharacters } from "./agent-display-names"
|
||||
import { AGENT_MODEL_REQUIREMENTS, CATEGORY_MODEL_REQUIREMENTS } from "./model-requirements"
|
||||
|
||||
export function resolveAgentVariant(
|
||||
@@ -9,12 +10,13 @@ export function resolveAgentVariant(
|
||||
return undefined
|
||||
}
|
||||
|
||||
const stripped = stripInvisibleAgentCharacters(agentName)
|
||||
const agentOverrides = config.agents as
|
||||
| Record<string, { variant?: string; category?: string }>
|
||||
| undefined
|
||||
const agentOverride = agentOverrides
|
||||
? agentOverrides[agentName]
|
||||
?? Object.entries(agentOverrides).find(([key]) => key.toLowerCase() === agentName.toLowerCase())?.[1]
|
||||
? agentOverrides[stripped]
|
||||
?? Object.entries(agentOverrides).find(([key]) => key.toLowerCase() === stripped.toLowerCase())?.[1]
|
||||
: undefined
|
||||
if (!agentOverride) {
|
||||
return undefined
|
||||
@@ -37,18 +39,19 @@ export function resolveVariantForModel(
|
||||
agentName: string,
|
||||
currentModel: { providerID: string; modelID: string },
|
||||
): string | undefined {
|
||||
const stripped = stripInvisibleAgentCharacters(agentName)
|
||||
const agentOverrides = config.agents as
|
||||
| Record<string, { variant?: string; category?: string }>
|
||||
| undefined
|
||||
const agentOverride = agentOverrides
|
||||
? agentOverrides[agentName]
|
||||
?? Object.entries(agentOverrides).find(([key]) => key.toLowerCase() === agentName.toLowerCase())?.[1]
|
||||
? agentOverrides[stripped]
|
||||
?? Object.entries(agentOverrides).find(([key]) => key.toLowerCase() === stripped.toLowerCase())?.[1]
|
||||
: undefined
|
||||
if (agentOverride?.variant) {
|
||||
return agentOverride.variant
|
||||
}
|
||||
|
||||
const agentRequirement = AGENT_MODEL_REQUIREMENTS[agentName]
|
||||
const agentRequirement = AGENT_MODEL_REQUIREMENTS[stripped]
|
||||
if (agentRequirement) {
|
||||
return findVariantInChain(agentRequirement.fallbackChain, currentModel)
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import type { CategoriesConfig, AgentOverrides } from "../../config/schema"
|
||||
import type { DelegatedModelConfig } from "../../shared/model-resolution-types"
|
||||
import type { FallbackEntry } from "../../shared/model-requirements"
|
||||
import { AGENT_MODEL_REQUIREMENTS } from "../../shared/model-requirements"
|
||||
import { getAgentConfigKey } from "../../shared/agent-display-names"
|
||||
import { getAgentConfigKey, stripInvisibleAgentCharacters } from "../../shared/agent-display-names"
|
||||
import { normalizeFallbackModels } from "../../shared/model-resolver"
|
||||
import { buildFallbackChainFromModels } from "../../shared/fallback-chain-from-models"
|
||||
import { log } from "../../shared"
|
||||
@@ -104,20 +104,21 @@ export function createCallOmoAgent(
|
||||
const toolCtx = toolContext as ToolContextWithMetadata
|
||||
log(`[call_omo_agent] Starting with agent: ${args.subagent_type}, background: ${args.run_in_background}`)
|
||||
|
||||
// Case-insensitive agent validation - allows "Explore", "EXPLORE", "explore" etc.
|
||||
// Strip ZWSP and case-insensitive agent validation - allows "Explore", "EXPLORE", "explore" etc.
|
||||
const strippedAgentType = stripInvisibleAgentCharacters(args.subagent_type)
|
||||
if (
|
||||
!ALLOWED_AGENTS.some(
|
||||
(name) => name.toLowerCase() === args.subagent_type.toLowerCase(),
|
||||
(name) => name.toLowerCase() === strippedAgentType.toLowerCase(),
|
||||
)
|
||||
) {
|
||||
return `Error: Invalid agent type "${args.subagent_type}". Only ${ALLOWED_AGENTS.join(", ")} are allowed.`
|
||||
}
|
||||
|
||||
const normalizedAgent = args.subagent_type.toLowerCase() as AllowedAgentType
|
||||
const normalizedAgent = strippedAgentType.toLowerCase() as AllowedAgentType
|
||||
args = { ...args, subagent_type: normalizedAgent }
|
||||
|
||||
// Check if agent is disabled
|
||||
if (disabledAgents.some((disabled) => disabled.toLowerCase() === normalizedAgent)) {
|
||||
if (disabledAgents.some((disabled) => stripInvisibleAgentCharacters(disabled).toLowerCase() === normalizedAgent)) {
|
||||
return `Error: Agent "${normalizedAgent}" is disabled via disabled_agents configuration. Remove it from disabled_agents in your ${CONFIG_BASENAME}.json to use it.`
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
} from "../../shared/model-suggestion-retry"
|
||||
import { formatDetailedError } from "./error-formatting"
|
||||
import { getAgentToolRestrictions } from "../../shared/agent-tool-restrictions"
|
||||
import { stripInvisibleAgentCharacters } from "../../shared/agent-display-names"
|
||||
import { applySessionPromptParams } from "../../shared/session-prompt-params-helpers"
|
||||
import { setSessionTools } from "../../shared/session-tools-store"
|
||||
import { createInternalAgentTextPart } from "../../shared/internal-initiator-marker"
|
||||
@@ -41,7 +42,7 @@ function buildPromptGenerationParams(model: DelegatedModelConfig | undefined): R
|
||||
}
|
||||
|
||||
function isOracleAgent(agentToUse: string): boolean {
|
||||
return agentToUse.toLowerCase() === "oracle"
|
||||
return stripInvisibleAgentCharacters(agentToUse).toLowerCase() === "oracle"
|
||||
}
|
||||
|
||||
function isUnexpectedEofError(error: unknown): boolean {
|
||||
@@ -80,7 +81,7 @@ export async function sendSyncPrompt(
|
||||
const promptArgs = {
|
||||
path: { id: input.sessionID },
|
||||
body: {
|
||||
agent: input.agentToUse.replace(/^\u200B+/, ""),
|
||||
agent: stripInvisibleAgentCharacters(input.agentToUse),
|
||||
system: input.systemContent,
|
||||
tools,
|
||||
parts: [createInternalAgentTextPart(effectivePrompt)],
|
||||
|
||||
Reference in New Issue
Block a user