diff --git a/assets/oh-my-opencode.schema.json b/assets/oh-my-opencode.schema.json index 1d7c856ca..c4569442f 100644 --- a/assets/oh-my-opencode.schema.json +++ b/assets/oh-my-opencode.schema.json @@ -4261,6 +4261,10 @@ }, "replace_plan": { "type": "boolean" + }, + "tdd": { + "default": true, + "type": "boolean" } }, "additionalProperties": false diff --git a/src/config/schema/sisyphus-agent.ts b/src/config/schema/sisyphus-agent.ts index 76ee2c373..be08dda87 100644 --- a/src/config/schema/sisyphus-agent.ts +++ b/src/config/schema/sisyphus-agent.ts @@ -5,6 +5,7 @@ export const SisyphusAgentConfigSchema = z.object({ default_builder_enabled: z.boolean().optional(), planner_enabled: z.boolean().optional(), replace_plan: z.boolean().optional(), + tdd: z.boolean().default(true).optional(), }) export type SisyphusAgentConfig = z.infer diff --git a/src/plugin/tool-registry.ts b/src/plugin/tool-registry.ts index c29ecfce6..5d99cd0b2 100644 --- a/src/plugin/tool-registry.ts +++ b/src/plugin/tool-registry.ts @@ -132,6 +132,7 @@ export function createToolRegistry(args: { disabledSkills: skillContext.disabledSkills, availableCategories, availableSkills: skillContext.availableSkills, + sisyphusAgentConfig: pluginConfig.sisyphus_agent, syncPollTimeoutMs: pluginConfig.background_task?.syncPollTimeoutMs, onSyncSessionCreated: async (event) => { log("[index] onSyncSessionCreated callback", { diff --git a/src/tools/delegate-task/background-task.ts b/src/tools/delegate-task/background-task.ts index bc96423eb..be9b1f5b3 100644 --- a/src/tools/delegate-task/background-task.ts +++ b/src/tools/delegate-task/background-task.ts @@ -23,7 +23,8 @@ export async function executeBackgroundTask( const { manager } = executorCtx try { - const effectivePrompt = buildTaskPrompt(args.prompt, agentToUse) + const tddEnabled = executorCtx.sisyphusAgentConfig?.tdd + const effectivePrompt = buildTaskPrompt(args.prompt, agentToUse, tddEnabled) const task = await manager.launch({ description: args.description, prompt: effectivePrompt, diff --git a/src/tools/delegate-task/executor-types.ts b/src/tools/delegate-task/executor-types.ts index ad8c01879..bfa7fc70b 100644 --- a/src/tools/delegate-task/executor-types.ts +++ b/src/tools/delegate-task/executor-types.ts @@ -1,5 +1,5 @@ import type { BackgroundManager } from "../../features/background-agent" -import type { CategoriesConfig, GitMasterConfig, BrowserAutomationProvider, AgentOverrides } from "../../config/schema" +import type { CategoriesConfig, GitMasterConfig, BrowserAutomationProvider, AgentOverrides, SisyphusAgentConfig } from "../../config/schema" import type { OpencodeClient } from "./types" export interface ExecutorContext { @@ -11,6 +11,7 @@ export interface ExecutorContext { sisyphusJuniorModel?: string browserProvider?: BrowserAutomationProvider agentOverrides?: AgentOverrides + sisyphusAgentConfig?: SisyphusAgentConfig onSyncSessionCreated?: (event: { sessionID: string; parentID: string; title: string }) => Promise syncPollTimeoutMs?: number } diff --git a/src/tools/delegate-task/prompt-builder.ts b/src/tools/delegate-task/prompt-builder.ts index ddae1ce59..1672eea74 100644 --- a/src/tools/delegate-task/prompt-builder.ts +++ b/src/tools/delegate-task/prompt-builder.ts @@ -3,15 +3,24 @@ import { buildPlanAgentSystemPrepend, isPlanAgent } from "./constants" import { buildSystemContentWithTokenLimit } from "./token-limiter" const FREE_OR_LOCAL_PROMPT_TOKEN_LIMIT = 24000 -const PLAN_AGENT_PROMPT_APPEND = ` +const PLAN_AGENT_PROMPT_BASE = ` Additional requirements for this planning request: - Answer in English. - Write the plan in English. - Plan well for ultrawork execution. -- Use TDD-oriented planning. - Include a clear atomic commit strategy.` +const TDD_LINE = "- Use TDD-oriented planning." + +function buildPlanAgentPromptAppend(tddEnabled: boolean): string { + if (tddEnabled) { + return `${PLAN_AGENT_PROMPT_BASE} +${TDD_LINE}` + } + return PLAN_AGENT_PROMPT_BASE +} + function usesFreeOrLocalModel(model: { providerID: string; modelID: string; variant?: string } | undefined): boolean { if (!model) { return false @@ -61,10 +70,11 @@ export function buildSystemContent(input: BuildSystemContentInput): string | und ) } -export function buildTaskPrompt(prompt: string, agentName: string | undefined): string { +export function buildTaskPrompt(prompt: string, agentName: string | undefined, tddEnabled?: boolean): string { if (!isPlanAgent(agentName)) { return prompt } - return `${prompt}${PLAN_AGENT_PROMPT_APPEND}` + const effectiveTdd = tddEnabled ?? true + return `${prompt}${buildPlanAgentPromptAppend(effectiveTdd)}` } diff --git a/src/tools/delegate-task/sync-continuation.ts b/src/tools/delegate-task/sync-continuation.ts index 82618a289..5abe635c1 100644 --- a/src/tools/delegate-task/sync-continuation.ts +++ b/src/tools/delegate-task/sync-continuation.ts @@ -19,7 +19,7 @@ export async function executeSyncContinuation( executorCtx: ExecutorContext, deps: SyncContinuationDeps = syncContinuationDeps ): Promise { - const { client, syncPollTimeoutMs } = executorCtx + const { client, syncPollTimeoutMs, sisyphusAgentConfig } = executorCtx const toastManager = getTaskToastManager() const taskId = `resume_sync_${args.session_id!.slice(0, 8)}` const startTime = new Date() @@ -83,7 +83,8 @@ export async function executeSyncContinuation( } const allowTask = isPlanFamily(resumeAgent) - const effectivePrompt = buildTaskPrompt(args.prompt, resumeAgent) + const tddEnabled = sisyphusAgentConfig?.tdd + const effectivePrompt = buildTaskPrompt(args.prompt, resumeAgent, tddEnabled) const tools = { task: allowTask, call_omo_agent: true, diff --git a/src/tools/delegate-task/sync-prompt-sender.ts b/src/tools/delegate-task/sync-prompt-sender.ts index 502bb6dec..1140344d4 100644 --- a/src/tools/delegate-task/sync-prompt-sender.ts +++ b/src/tools/delegate-task/sync-prompt-sender.ts @@ -1,4 +1,5 @@ import type { DelegateTaskArgs, OpencodeClient, DelegatedModelConfig } from "./types" +import type { SisyphusAgentConfig } from "../../config/schema" import { isPlanFamily } from "./constants" import { buildTaskPrompt } from "./prompt-builder" import { @@ -41,11 +42,13 @@ export async function sendSyncPrompt( categoryModel: DelegatedModelConfig | undefined toastManager: { removeTask: (id: string) => void } | null | undefined taskId: string | undefined + sisyphusAgentConfig?: SisyphusAgentConfig }, deps: SendSyncPromptDeps = sendSyncPromptDeps ): Promise { const allowTask = isPlanFamily(input.agentToUse) - const effectivePrompt = buildTaskPrompt(input.args.prompt, input.agentToUse) + const tddEnabled = input.sisyphusAgentConfig?.tdd + const effectivePrompt = buildTaskPrompt(input.args.prompt, input.agentToUse, tddEnabled) const tools = { task: allowTask, call_omo_agent: true, diff --git a/src/tools/delegate-task/sync-task.ts b/src/tools/delegate-task/sync-task.ts index 554794188..b87001543 100644 --- a/src/tools/delegate-task/sync-task.ts +++ b/src/tools/delegate-task/sync-task.ts @@ -126,6 +126,7 @@ export async function executeSyncTask( categoryModel, toastManager, taskId, + sisyphusAgentConfig: executorCtx.sisyphusAgentConfig, }) if (promptError) { return promptError diff --git a/src/tools/delegate-task/tools.test.ts b/src/tools/delegate-task/tools.test.ts index 3a9b9e59e..2a18b0085 100644 --- a/src/tools/delegate-task/tools.test.ts +++ b/src/tools/delegate-task/tools.test.ts @@ -3132,6 +3132,35 @@ describe("sisyphus-task", () => { // then expect(result).toBe(prompt) }) + + test("excludes TDD line when tddEnabled is false", () => { + // given + const { buildTaskPrompt } = require("./tools") + const prompt = "Create a work plan for this feature" + + // when + const result = buildTaskPrompt(prompt, "plan", false) + + // then + expect(result).toContain(prompt) + expect(result).toContain("Answer in English.") + expect(result).toContain("Write the plan in English.") + expect(result).toContain("Plan well for ultrawork execution.") + expect(result).toContain("Include a clear atomic commit strategy.") + expect(result).not.toContain("Use TDD-oriented planning.") + }) + + test("includes TDD line when tddEnabled is true", () => { + // given + const { buildTaskPrompt } = require("./tools") + const prompt = "Create a work plan for this feature" + + // when + const result = buildTaskPrompt(prompt, "plan", true) + + // then + expect(result).toContain("Use TDD-oriented planning.") + }) }) describe("modelInfo detection via resolveCategoryConfig", () => { diff --git a/src/tools/delegate-task/types.ts b/src/tools/delegate-task/types.ts index fd1c2dd43..bbb0e8e21 100644 --- a/src/tools/delegate-task/types.ts +++ b/src/tools/delegate-task/types.ts @@ -1,6 +1,6 @@ import type { PluginInput } from "@opencode-ai/plugin" import type { BackgroundManager } from "../../features/background-agent" -import type { CategoriesConfig, GitMasterConfig, BrowserAutomationProvider, AgentOverrides } from "../../config/schema" +import type { CategoriesConfig, GitMasterConfig, BrowserAutomationProvider, AgentOverrides, SisyphusAgentConfig } from "../../config/schema" import type { AvailableCategory, AvailableSkill, @@ -67,6 +67,7 @@ export interface DelegateTaskToolOptions { availableCategories?: AvailableCategory[] availableSkills?: AvailableSkill[] agentOverrides?: AgentOverrides + sisyphusAgentConfig?: SisyphusAgentConfig onSyncSessionCreated?: (event: SyncSessionCreatedEvent) => Promise syncPollTimeoutMs?: number } diff --git a/src/tools/delegate-task/unstable-agent-task.ts b/src/tools/delegate-task/unstable-agent-task.ts index ba0ec6152..57f517e18 100644 --- a/src/tools/delegate-task/unstable-agent-task.ts +++ b/src/tools/delegate-task/unstable-agent-task.ts @@ -20,12 +20,13 @@ export async function executeUnstableAgentTask( systemContent: string | undefined, actualModel: string | undefined ): Promise { - const { manager, client, syncPollTimeoutMs } = executorCtx + const { manager, client, syncPollTimeoutMs, sisyphusAgentConfig } = executorCtx let cleanupReason: string | undefined let launchedTaskID: string | undefined try { - const effectivePrompt = buildTaskPrompt(args.prompt, agentToUse) + const tddEnabled = sisyphusAgentConfig?.tdd + const effectivePrompt = buildTaskPrompt(args.prompt, agentToUse, tddEnabled) const task = await manager.launch({ description: args.description, prompt: effectivePrompt,