diff --git a/.opencode/command/remove-deadcode.md b/.opencode/command/remove-deadcode.md index b04093fb7..835ad6cbf 100644 --- a/.opencode/command/remove-deadcode.md +++ b/.opencode/command/remove-deadcode.md @@ -41,27 +41,27 @@ Fire ALL simultaneously: ``` // Agent 1: Find all exported symbols -delegate_task(subagent_type="explore", run_in_background=true, +task(subagent_type="explore", run_in_background=true, prompt="Find ALL exported functions, classes, types, interfaces, and constants across src/. List each with: file path, line number, symbol name, export type (named/default). EXCLUDE: src/index.ts root exports, test files. Return as structured list.") // Agent 2: Find potentially unused files -delegate_task(subagent_type="explore", run_in_background=true, +task(subagent_type="explore", run_in_background=true, prompt="Find files in src/ that are NOT imported by any other file. Check import/require statements across the entire codebase. EXCLUDE: index.ts files, test files, entry points, config files, .md files. Return list of potentially orphaned files.") // Agent 3: Find unused imports within files -delegate_task(subagent_type="explore", run_in_background=true, +task(subagent_type="explore", run_in_background=true, prompt="Find unused imports across src/**/*.ts files. Look for import statements where the imported symbol is never referenced in the file body. Return: file path, line number, imported symbol name.") // Agent 4: Find functions/variables only used in their own declaration -delegate_task(subagent_type="explore", run_in_background=true, +task(subagent_type="explore", run_in_background=true, prompt="Find private/non-exported functions, variables, and types in src/**/*.ts that appear to have zero usage beyond their declaration. Return: file path, line number, symbol name.") ``` diff --git a/.opencode/skills/github-issue-triage/SKILL.md b/.opencode/skills/github-issue-triage/SKILL.md index abf814404..1e503f576 100644 --- a/.opencode/skills/github-issue-triage/SKILL.md +++ b/.opencode/skills/github-issue-triage/SKILL.md @@ -21,7 +21,7 @@ You are a GitHub issue triage automation agent. Your job is to: | Aspect | Rule | |--------|------| -| **Task Granularity** | 1 Issue = Exactly 1 `delegate_task()` call | +| **Task Granularity** | 1 Issue = Exactly 1 `task()` call | | **Execution Mode** | `run_in_background=true` (Each issue runs independently) | | **Result Handling** | `background_output()` to collect results as they complete | | **Reporting** | IMMEDIATE streaming when each task finishes | @@ -67,7 +67,7 @@ for (let i = 0; i < allIssues.length; i++) { const issue = allIssues[i] const category = getCategory(i) - const taskId = await delegate_task( + const taskId = await task( category=category, load_skills=[], run_in_background=true, // ← CRITICAL: Each issue is independent background task @@ -195,7 +195,7 @@ for (let i = 0; i < allIssues.length; i++) { console.log(`🚀 Launching background task for Issue #${issue.number} (${category})...`) - const taskId = await delegate_task( + const taskId = await task( category=category, load_skills=[], run_in_background=true, // ← BACKGROUND TASK: Each issue runs independently @@ -480,7 +480,7 @@ When invoked, immediately: 4. Exhaustive pagination for issues 5. Exhaustive pagination for PRs 6. **LAUNCH**: For each issue: - - `delegate_task(run_in_background=true)` - 1 task per issue + - `task(run_in_background=true)` - 1 task per issue - Store taskId mapped to issue number 7. **STREAM**: Poll `background_output()` for each task: - As each completes, immediately report result diff --git a/.opencode/skills/github-pr-triage/SKILL.md b/.opencode/skills/github-pr-triage/SKILL.md index 3cf23bd01..092609c3a 100644 --- a/.opencode/skills/github-pr-triage/SKILL.md +++ b/.opencode/skills/github-pr-triage/SKILL.md @@ -22,7 +22,7 @@ You are a GitHub Pull Request triage automation agent. Your job is to: | Aspect | Rule | |--------|------| -| **Task Granularity** | 1 PR = Exactly 1 `delegate_task()` call | +| **Task Granularity** | 1 PR = Exactly 1 `task()` call | | **Execution Mode** | `run_in_background=true` (Each PR runs independently) | | **Result Handling** | `background_output()` to collect results as they complete | | **Reporting** | IMMEDIATE streaming when each task finishes | @@ -68,7 +68,7 @@ for (let i = 0; i < allPRs.length; i++) { const pr = allPRs[i] const category = getCategory(i) - const taskId = await delegate_task( + const taskId = await task( category=category, load_skills=[], run_in_background=true, // ← CRITICAL: Each PR is independent background task @@ -178,7 +178,7 @@ for (let i = 0; i < allPRs.length; i++) { console.log(`🚀 Launching background task for PR #${pr.number} (${category})...`) - const taskId = await delegate_task( + const taskId = await task( category=category, load_skills=[], run_in_background=true, // ← BACKGROUND TASK: Each PR runs independently @@ -474,7 +474,7 @@ When invoked, immediately: 2. `gh repo view --json nameWithOwner -q .nameWithOwner` 3. Exhaustive pagination for ALL open PRs 4. **LAUNCH**: For each PR: - - `delegate_task(run_in_background=true)` - 1 task per PR + - `task(run_in_background=true)` - 1 task per PR - Store taskId mapped to PR number 5. **STREAM**: Poll `background_output()` for each task: - As each completes, immediately report result diff --git a/AGENTS.md b/AGENTS.md index 5ebc9a186..1b539c2de 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -195,7 +195,7 @@ oh-my-opencode/ | Type Safety | `as any`, `@ts-ignore`, `@ts-expect-error` | | Error Handling | Empty catch blocks | | Testing | Deleting failing tests, writing implementation before test | -| Agent Calls | Sequential - use `delegate_task` parallel | +| Agent Calls | Sequential - use `task` parallel | | Hook Logic | Heavy PreToolUse - slows every call | | Commits | Giant (3+ files), separate test from impl | | Temperature | >0.3 for code agents | diff --git a/docs/category-skill-guide.md b/docs/category-skill-guide.md index b3de37a3d..7488d56b7 100644 --- a/docs/category-skill-guide.md +++ b/docs/category-skill-guide.md @@ -9,7 +9,7 @@ Instead of delegating everything to a single AI agent, it's far more efficient t - **Category**: "What kind of work is this?" (determines model, temperature, prompt mindset) - **Skill**: "What tools and knowledge are needed?" (injects specialized knowledge, MCP tools, workflows) -By combining these two concepts, you can generate optimal agents through `delegate_task`. +By combining these two concepts, you can generate optimal agents through `task`. --- @@ -32,10 +32,10 @@ A Category is an agent configuration preset optimized for specific domains. ### Usage -Specify the `category` parameter when invoking the `delegate_task` tool. +Specify the `category` parameter when invoking the `task` tool. ```typescript -delegate_task( +task( category="visual-engineering", prompt="Add a responsive chart component to the dashboard page" ) @@ -74,7 +74,7 @@ A Skill is a mechanism that injects **specialized knowledge (Context)** and **to Add desired skill names to the `load_skills` array. ```typescript -delegate_task( +task( category="quick", load_skills=["git-master"], prompt="Commit current changes. Follow commit message style." @@ -126,7 +126,7 @@ You can create powerful specialized agents by combining Categories and Skills. --- -## 5. delegate_task Prompt Guide +## 5. task Prompt Guide When delegating, **clear and specific** prompts are essential. Include these 7 elements: @@ -158,7 +158,7 @@ You can fine-tune categories in `oh-my-opencode.json`. | Field | Type | Description | |-------|------|-------------| -| `description` | string | Human-readable description of the category's purpose. Shown in delegate_task prompt. | +| `description` | string | Human-readable description of the category's purpose. Shown in task prompt. | | `model` | string | AI model ID to use (e.g., `anthropic/claude-opus-4-6`) | | `variant` | string | Model variant (e.g., `max`, `xhigh`) | | `temperature` | number | Creativity level (0.0 ~ 2.0). Lower is more deterministic. | diff --git a/docs/configurations.md b/docs/configurations.md index 648ee75ac..2fb67bd47 100644 --- a/docs/configurations.md +++ b/docs/configurations.md @@ -25,7 +25,7 @@ It asks about your providers (Claude, OpenAI, Gemini, etc.) and generates optima "explore": { "model": "opencode/gpt-5-nano" } // Free model for grep }, - // Override category models (used by delegate_task) + // Override category models (used by task) "categories": { "quick": { "model": "opencode/gpt-5-nano" }, // Fast/cheap for trivial tasks "visual-engineering": { "model": "google/gemini-3-pro" } // Gemini for UI @@ -252,7 +252,7 @@ Available agents: `sisyphus`, `prometheus`, `oracle`, `librarian`, `explore`, `m Oh My OpenCode includes built-in skills that provide additional capabilities: - **playwright** (default) / **agent-browser**: Browser automation for web scraping, testing, screenshots, and browser interactions. See [Browser Automation](#browser-automation) for switching between providers. -- **git-master**: Git expert for atomic commits, rebase/squash, and history search (blame, bisect, log -S). STRONGLY RECOMMENDED: Use with `delegate_task(category='quick', load_skills=['git-master'], ...)` to save context. +- **git-master**: Git expert for atomic commits, rebase/squash, and history search (blame, bisect, log -S). STRONGLY RECOMMENDED: Use with `task(category='quick', load_skills=['git-master'], ...)` to save context. Disable built-in skills via `disabled_skills` in `~/.config/opencode/oh-my-opencode.json` or `.opencode/oh-my-opencode.json`: @@ -455,7 +455,7 @@ Run background subagents in separate tmux panes for **visual multi-agent executi ### How It Works When `tmux.enabled` is `true` and you're inside a tmux session: -- Background agents (via `delegate_task(run_in_background=true)`) spawn in new tmux panes +- Background agents (via `task(run_in_background=true)`) spawn in new tmux panes - Each pane shows the subagent's real-time output - Panes are automatically closed when the subagent completes - Layout is automatically adjusted based on your configuration @@ -716,7 +716,7 @@ Configure concurrency limits for background agent tasks. This controls how many ## Categories -Categories enable domain-specific task delegation via the `delegate_task` tool. Each category applies runtime presets (model, temperature, prompt additions) when calling the `Sisyphus-Junior` agent. +Categories enable domain-specific task delegation via the `task` tool. Each category applies runtime presets (model, temperature, prompt additions) when calling the `Sisyphus-Junior` agent. ### Built-in Categories @@ -797,12 +797,12 @@ All 7 categories come with optimal model defaults, but **you must configure them ### Usage ```javascript -// Via delegate_task tool -delegate_task(category="visual-engineering", prompt="Create a responsive dashboard component") -delegate_task(category="ultrabrain", prompt="Design the payment processing flow") +// Via task tool +task(category="visual-engineering", prompt="Create a responsive dashboard component") +task(category="ultrabrain", prompt="Design the payment processing flow") // Or target a specific agent directly (bypasses categories) -delegate_task(agent="oracle", prompt="Review this architecture") +task(agent="oracle", prompt="Review this architecture") ``` ### Custom Categories @@ -831,7 +831,7 @@ Each category supports: `model`, `temperature`, `top_p`, `maxTokens`, `thinking` | Option | Type | Default | Description | | ------------------ | ------- | ------- | --------------------------------------------------------------------------------------------------- | -| `description` | string | - | Human-readable description of the category's purpose. Shown in delegate_task prompt. | +| `description` | string | - | Human-readable description of the category's purpose. Shown in task prompt. | | `is_unstable_agent`| boolean | `false` | Mark agent as unstable - forces background mode for monitoring. Auto-enabled for gemini models. | ## Model Resolution System diff --git a/docs/features.md b/docs/features.md index b750ce7d6..f73f7a656 100644 --- a/docs/features.md +++ b/docs/features.md @@ -54,7 +54,7 @@ Run agents in the background and continue working: ``` # Launch in background -delegate_task(subagent_type="explore", load_skills=[], prompt="Find auth implementations", run_in_background=true) +task(subagent_type="explore", load_skills=[], prompt="Find auth implementations", run_in_background=true) # Continue working... # System notifies on completion @@ -374,7 +374,7 @@ Hooks intercept and modify behavior at key points in the agent lifecycle. | Hook | Event | Description | |------|-------|-------------| | **task-resume-info** | PostToolUse | Provides task resume information for continuity. | -| **delegate-task-retry** | PostToolUse | Retries failed delegate_task calls. | +| **delegate-task-retry** | PostToolUse | Retries failed task calls. | #### Integration @@ -454,7 +454,7 @@ Disable specific hooks in config: | Tool | Description | |------|-------------| | **call_omo_agent** | Spawn explore/librarian agents. Supports `run_in_background`. | -| **delegate_task** | Category-based task delegation. Supports categories (visual, business-logic) or direct agent targeting. | +| **task** | Category-based task delegation. Supports categories (visual, business-logic) or direct agent targeting. | | **background_output** | Retrieve background task results | | **background_cancel** | Cancel running background tasks | diff --git a/docs/guide/understanding-orchestration-system.md b/docs/guide/understanding-orchestration-system.md index 148de716f..1263087c7 100644 --- a/docs/guide/understanding-orchestration-system.md +++ b/docs/guide/understanding-orchestration-system.md @@ -50,11 +50,11 @@ flowchart TB User -->|"/start-work"| Orchestrator Plan -->|"Read"| Orchestrator - Orchestrator -->|"delegate_task(category)"| Junior - Orchestrator -->|"delegate_task(agent)"| Oracle - Orchestrator -->|"delegate_task(agent)"| Explore - Orchestrator -->|"delegate_task(agent)"| Librarian - Orchestrator -->|"delegate_task(agent)"| Frontend + Orchestrator -->|"task(category)"| Junior + Orchestrator -->|"task(agent)"| Oracle + Orchestrator -->|"task(agent)"| Explore + Orchestrator -->|"task(agent)"| Librarian + Orchestrator -->|"task(agent)"| Frontend Junior -->|"Results + Learnings"| Orchestrator Oracle -->|"Advice"| Orchestrator @@ -220,9 +220,9 @@ Independent tasks run in parallel: ```typescript // Orchestrator identifies parallelizable groups from plan // Group A: Tasks 2, 3, 4 (no file conflicts) -delegate_task(category="ultrabrain", prompt="Task 2...") -delegate_task(category="visual-engineering", prompt="Task 3...") -delegate_task(category="general", prompt="Task 4...") +task(category="ultrabrain", prompt="Task 2...") +task(category="visual-engineering", prompt="Task 3...") +task(category="general", prompt="Task 4...") // All run simultaneously ``` @@ -234,7 +234,7 @@ delegate_task(category="general", prompt="Task 4...") Junior is the **workhorse** that actually writes code. Key characteristics: -- **Focused**: Cannot delegate (blocked from task/delegate_task tools) +- **Focused**: Cannot delegate (blocked from task tool) - **Disciplined**: Obsessive todo tracking - **Verified**: Must pass lsp_diagnostics before completion - **Constrained**: Cannot modify plan files (READ-ONLY) @@ -268,7 +268,7 @@ This "boulder pushing" mechanism is why the system is named after Sisyphus. --- -## The delegate_task Tool: Category + Skill System +## The task Tool: Category + Skill System ### Why Categories are Revolutionary @@ -276,17 +276,17 @@ This "boulder pushing" mechanism is why the system is named after Sisyphus. ```typescript // OLD: Model name creates distributional bias -delegate_task(agent="gpt-5.2", prompt="...") // Model knows its limitations -delegate_task(agent="claude-opus-4.6", prompt="...") // Different self-perception +task(agent="gpt-5.2", prompt="...") // Model knows its limitations +task(agent="claude-opus-4.6", prompt="...") // Different self-perception ``` **The Solution: Semantic Categories:** ```typescript // NEW: Category describes INTENT, not implementation -delegate_task(category="ultrabrain", prompt="...") // "Think strategically" -delegate_task(category="visual-engineering", prompt="...") // "Design beautifully" -delegate_task(category="quick", prompt="...") // "Just get it done fast" +task(category="ultrabrain", prompt="...") // "Think strategically" +task(category="visual-engineering", prompt="...") // "Design beautifully" +task(category="quick", prompt="...") // "Just get it done fast" ``` ### Built-in Categories @@ -324,13 +324,13 @@ Skills prepend specialized instructions to subagent prompts: ```typescript // Category + Skill combination -delegate_task( +task( category="visual-engineering", load_skills=["frontend-ui-ux"], // Adds UI/UX expertise prompt="..." ) -delegate_task( +task( category="general", load_skills=["playwright"], // Adds browser automation expertise prompt="..." @@ -365,7 +365,7 @@ sequenceDiagram Note over Orchestrator: Prompt Structure:
1. TASK (exact checkbox)
2. EXPECTED OUTCOME
3. REQUIRED SKILLS
4. REQUIRED TOOLS
5. MUST DO
6. MUST NOT DO
7. CONTEXT + Wisdom - Orchestrator->>Junior: delegate_task(category, load_skills, prompt) + Orchestrator->>Junior: task(category, load_skills, prompt) Junior->>Junior: Create todos, execute Junior->>Junior: Verify (lsp_diagnostics, tests) diff --git a/docs/orchestration-guide.md b/docs/orchestration-guide.md index 0eac77567..5235d3f7e 100644 --- a/docs/orchestration-guide.md +++ b/docs/orchestration-guide.md @@ -387,7 +387,7 @@ You can control related features in `oh-my-opencode.json`. 2. **Single Plan Principle**: No matter how large the task, contain all TODOs in one plan file (`.md`). This prevents context fragmentation. -3. **Active Delegation**: During execution, delegate to specialized agents via `delegate_task` rather than modifying code directly. +3. **Active Delegation**: During execution, delegate to specialized agents via `task` rather than modifying code directly. 4. **Trust /start-work Continuity**: Don't worry about session interruptions. `/start-work` will always resume your work from boulder.json. diff --git a/issue-1501-analysis.md b/issue-1501-analysis.md index 79bd02806..52295a941 100644 --- a/issue-1501-analysis.md +++ b/issue-1501-analysis.md @@ -288,7 +288,7 @@ src/tools/delegate-task/constants.ts ``` Sisyphus (ULW mode) ↓ -delegate_task(category="deep", ...) +task(category="deep", ...) ↓ executor.ts: executeBackgroundContinuation() ↓ diff --git a/sisyphus-prompt.md b/sisyphus-prompt.md index 177a97c8f..2d8779704 100644 --- a/sisyphus-prompt.md +++ b/sisyphus-prompt.md @@ -212,7 +212,7 @@ Search **external references** (docs, OSS, web). Fire proactively when unfamilia - "Working with unfamiliar npm/pip/cargo packages" ### Pre-Delegation Planning (MANDATORY) -**BEFORE every `delegate_task` call, EXPLICITLY declare your reasoning.** +**BEFORE every `task` call, EXPLICITLY declare your reasoning.** #### Step 1: Identify Task Requirements @@ -236,7 +236,7 @@ Ask yourself: **MANDATORY FORMAT:** ``` -I will use delegate_task with: +I will use task with: - **Category**: [selected-category-name] - **Why this category**: [how category description matches task domain] - **load_skills**: [list of selected skills] @@ -246,14 +246,14 @@ I will use delegate_task with: - **Expected Outcome**: [what success looks like] ``` -**Then** make the delegate_task call. +**Then** make the task call. #### Examples **CORRECT: Full Evaluation** ``` -I will use delegate_task with: +I will use task with: - **Category**: [category-name] - **Why this category**: Category description says "[quote description]" which matches this task's requirements - **load_skills**: ["skill-a", "skill-b"] @@ -263,9 +263,11 @@ I will use delegate_task with: - skill-c: OMITTED - description says "[quote]" which doesn't apply because [reason] - **Expected Outcome**: [concrete deliverable] -delegate_task( +task( category="[category-name]", load_skills=["skill-a", "skill-b"], + description="[short task description]", + run_in_background=false, prompt="..." ) ``` @@ -273,14 +275,16 @@ delegate_task( **CORRECT: Agent-Specific (for exploration/consultation)** ``` -I will use delegate_task with: +I will use task with: - **Agent**: [agent-name] - **Reason**: This requires [agent's specialty] based on agent description - **load_skills**: [] (agents have built-in expertise) - **Expected Outcome**: [what agent should return] -delegate_task( +task( subagent_type="[agent-name]", + description="[short task description]", + run_in_background=false, load_skills=[], prompt="..." ) @@ -289,14 +293,15 @@ delegate_task( **CORRECT: Background Exploration** ``` -I will use delegate_task with: +I will use task with: - **Agent**: explore - **Reason**: Need to find all authentication implementations across the codebase - this is contextual grep - **load_skills**: [] - **Expected Outcome**: List of files containing auth patterns -delegate_task( +task( subagent_type="explore", + description="Find auth implementations", run_in_background=true, load_skills=[], prompt="Find all authentication implementations in the codebase" @@ -306,7 +311,7 @@ delegate_task( **WRONG: No Skill Evaluation** ``` -delegate_task(category="...", load_skills=[], prompt="...") // Where's the justification? +task(category="...", load_skills=[], prompt="...") // Where's the justification? ``` **WRONG: Vague Category Selection** @@ -317,7 +322,7 @@ I'll use this category because it seems right. #### Enforcement -**BLOCKING VIOLATION**: If you call `delegate_task` without: +**BLOCKING VIOLATION**: If you call `task` without: 1. Explaining WHY category was selected (based on description) 2. Evaluating EACH available skill for relevance @@ -329,15 +334,15 @@ I'll use this category because it seems right. ```typescript // CORRECT: Always background, always parallel // Contextual Grep (internal) -delegate_task(subagent_type="explore", run_in_background=true, load_skills=[], prompt="Find auth implementations in our codebase...") -delegate_task(subagent_type="explore", run_in_background=true, load_skills=[], prompt="Find error handling patterns here...") +task(subagent_type="explore", description="Find auth implementations", run_in_background=true, load_skills=[], prompt="Find auth implementations in our codebase...") +task(subagent_type="explore", description="Find error handling patterns", run_in_background=true, load_skills=[], prompt="Find error handling patterns here...") // Reference Grep (external) -delegate_task(subagent_type="librarian", run_in_background=true, load_skills=[], prompt="Find JWT best practices in official docs...") -delegate_task(subagent_type="librarian", run_in_background=true, load_skills=[], prompt="Find how production apps handle auth in Express...") +task(subagent_type="librarian", description="Find JWT best practices", run_in_background=true, load_skills=[], prompt="Find JWT best practices in official docs...") +task(subagent_type="librarian", description="Find Express auth patterns", run_in_background=true, load_skills=[], prompt="Find how production apps handle auth in Express...") // Continue working immediately. Collect with background_output when needed. // WRONG: Sequential or blocking -result = delegate_task(...) // Never wait synchronously for explore/librarian +result = task(...) // Never wait synchronously for explore/librarian ``` ### Background Result Collection: @@ -347,16 +352,16 @@ result = delegate_task(...) // Never wait synchronously for explore/librarian 4. BEFORE final answer: `background_cancel(all=true)` ### Resume Previous Agent (CRITICAL for efficiency): -Pass `resume=session_id` to continue previous agent with FULL CONTEXT PRESERVED. +Pass `session_id` to continue previous agent with FULL CONTEXT PRESERVED. -**ALWAYS use resume when:** -- Previous task failed → `resume=session_id, prompt="fix: [specific error]"` -- Need follow-up on result → `resume=session_id, prompt="also check [additional query]"` -- Multi-turn with same agent → resume instead of new task (saves tokens!) +**ALWAYS use session_id when:** +- Previous task failed → `session_id="ses_xxx", prompt="fix: [specific error]"` +- Need follow-up on result → `session_id="ses_xxx", prompt="also check [additional query]"` +- Multi-turn with same agent → session_id instead of new task (saves tokens!) **Example:** ``` -delegate_task(resume="ses_abc123", prompt="The previous search missed X. Also look for Y.") +task(session_id="ses_abc123", description="Follow-up search", run_in_background=false, load_skills=[], prompt="The previous search missed X. Also look for Y.") ``` ### Search Stop Conditions @@ -377,7 +382,7 @@ STOP searching when: 3. Mark `completed` as soon as done (don't batch) - OBSESSIVELY TRACK YOUR WORK USING TODO TOOLS ### Category + Skills Delegation System -**delegate_task() combines categories and skills for optimal task execution.** +**task() combines categories and skills for optimal task execution.** #### Available Categories (Domain-Optimized Models) @@ -442,7 +447,7 @@ SKILL EVALUATION for "[skill-name]": ### Delegation Pattern ```typescript -delegate_task( +task( category="[selected-category]", load_skills=["skill-1", "skill-2"], // Include ALL relevant skills prompt="..." @@ -451,7 +456,7 @@ delegate_task( **ANTI-PATTERN (will produce poor results):** ```typescript -delegate_task(category="...", load_skills=[], prompt="...") // Empty load_skills without justification +task(category="...", load_skills=[], prompt="...") // Empty load_skills without justification ``` ### Delegation Table: diff --git a/src/agents/AGENTS.md b/src/agents/AGENTS.md index ff1dec185..1cbf91d37 100644 --- a/src/agents/AGENTS.md +++ b/src/agents/AGENTS.md @@ -68,11 +68,11 @@ agents/ ## TOOL RESTRICTIONS | Agent | Denied Tools | |-------|-------------| -| oracle | write, edit, task, delegate_task | -| librarian | write, edit, task, delegate_task, call_omo_agent | -| explore | write, edit, task, delegate_task, call_omo_agent | +| oracle | write, edit, task, task | +| librarian | write, edit, task, task, call_omo_agent | +| explore | write, edit, task, task, call_omo_agent | | multimodal-looker | Allowlist: read only | -| Sisyphus-Junior | task, delegate_task | +| Sisyphus-Junior | task, task | | Atlas | task, call_omo_agent | ## PATTERNS @@ -85,5 +85,5 @@ agents/ ## ANTI-PATTERNS - **Trust reports**: NEVER trust "I'm done" - verify outputs - **High temp**: Don't use >0.3 for code agents -- **Sequential calls**: Use `delegate_task` with `run_in_background` for exploration +- **Sequential calls**: Use `task` with `run_in_background` for exploration - **Prometheus writing code**: Planner only - never implements diff --git a/src/agents/atlas/default.ts b/src/agents/atlas/default.ts index 74cb218d4..dfe5cf5f0 100644 --- a/src/agents/atlas/default.ts +++ b/src/agents/atlas/default.ts @@ -19,18 +19,18 @@ You never write code yourself. You orchestrate specialists who do. -Complete ALL tasks in a work plan via \`delegate_task()\` until fully done. +Complete ALL tasks in a work plan via \`task()\` until fully done. One task per delegation. Parallel when independent. Verify everything. ## How to Delegate -Use \`delegate_task()\` with EITHER category OR agent (mutually exclusive): +Use \`task()\` with EITHER category OR agent (mutually exclusive): \`\`\`typescript // Option A: Category + Skills (spawns Sisyphus-Junior with domain config) -delegate_task( +task( category="[category-name]", load_skills=["skill-1", "skill-2"], run_in_background=false, @@ -38,7 +38,7 @@ delegate_task( ) // Option B: Specialized Agent (for specific expert tasks) -delegate_task( +task( subagent_type="[agent-name]", load_skills=[], run_in_background=false, @@ -58,7 +58,7 @@ delegate_task( ## 6-Section Prompt Structure (MANDATORY) -Every \`delegate_task()\` prompt MUST include ALL 6 sections: +Every \`task()\` prompt MUST include ALL 6 sections: \`\`\`markdown ## 1. TASK @@ -149,7 +149,7 @@ Structure: ### 3.1 Check Parallelization If tasks can run in parallel: - Prepare prompts for ALL parallelizable tasks -- Invoke multiple \`delegate_task()\` in ONE message +- Invoke multiple \`task()\` in ONE message - Wait for all to complete - Verify all, then continue @@ -167,10 +167,10 @@ Read(".sisyphus/notepads/{plan-name}/issues.md") Extract wisdom and include in prompt. -### 3.3 Invoke delegate_task() +### 3.3 Invoke task() \`\`\`typescript -delegate_task( +task( category="[category]", load_skills=["[relevant-skills]"], run_in_background=false, @@ -210,7 +210,7 @@ delegate_task( **If verification fails**: Resume the SAME session with the ACTUAL error output: \`\`\`typescript -delegate_task( +task( session_id="ses_xyz789", // ALWAYS use the session from the failed task load_skills=[...], prompt="Verification failed: {actual error}. Fix." @@ -221,13 +221,13 @@ delegate_task( **CRITICAL: When re-delegating, ALWAYS use \`session_id\` parameter.** -Every \`delegate_task()\` output includes a session_id. STORE IT. +Every \`task()\` output includes a session_id. STORE IT. If task fails: 1. Identify what went wrong 2. **Resume the SAME session** - subagent has full context already: \`\`\`typescript - delegate_task( + task( session_id="ses_xyz789", // Session from failed task load_skills=[...], prompt="FAILED: {error}. Fix by: {specific instruction}" @@ -274,21 +274,21 @@ ACCUMULATED WISDOM: **For exploration (explore/librarian)**: ALWAYS background \`\`\`typescript -delegate_task(subagent_type="explore", run_in_background=true, ...) -delegate_task(subagent_type="librarian", run_in_background=true, ...) +task(subagent_type="explore", run_in_background=true, ...) +task(subagent_type="librarian", run_in_background=true, ...) \`\`\` **For task execution**: NEVER background \`\`\`typescript -delegate_task(category="...", run_in_background=false, ...) +task(category="...", run_in_background=false, ...) \`\`\` **Parallel task groups**: Invoke multiple in ONE message \`\`\`typescript // Tasks 2, 3, 4 are independent - invoke together -delegate_task(category="quick", load_skills=[], run_in_background=false, prompt="Task 2...") -delegate_task(category="quick", load_skills=[], run_in_background=false, prompt="Task 3...") -delegate_task(category="quick", load_skills=[], run_in_background=false, prompt="Task 4...") +task(category="quick", load_skills=[], run_in_background=false, prompt="Task 2...") +task(category="quick", load_skills=[], run_in_background=false, prompt="Task 3...") +task(category="quick", load_skills=[], run_in_background=false, prompt="Task 4...") \`\`\` **Background management**: diff --git a/src/agents/atlas/gpt.ts b/src/agents/atlas/gpt.ts index b62b1acb7..d7d20fd70 100644 --- a/src/agents/atlas/gpt.ts +++ b/src/agents/atlas/gpt.ts @@ -24,7 +24,7 @@ You DELEGATE, COORDINATE, and VERIFY. You NEVER write code yourself. -Complete ALL tasks in a work plan via \`delegate_task()\` until fully done. +Complete ALL tasks in a work plan via \`task()\` until fully done. - One task per delegation - Parallel when independent - Verify everything @@ -71,14 +71,14 @@ Complete ALL tasks in a work plan via \`delegate_task()\` until fully done. ## Delegation API -Use \`delegate_task()\` with EITHER category OR agent (mutually exclusive): +Use \`task()\` with EITHER category OR agent (mutually exclusive): \`\`\`typescript // Category + Skills (spawns Sisyphus-Junior) -delegate_task(category="[name]", load_skills=["skill-1"], run_in_background=false, prompt="...") +task(category="[name]", load_skills=["skill-1"], run_in_background=false, prompt="...") // Specialized Agent -delegate_task(subagent_type="[agent]", load_skills=[], run_in_background=false, prompt="...") +task(subagent_type="[agent]", load_skills=[], run_in_background=false, prompt="...") \`\`\` {CATEGORY_SECTION} @@ -93,7 +93,7 @@ delegate_task(subagent_type="[agent]", load_skills=[], run_in_background=false, ## 6-Section Prompt Structure (MANDATORY) -Every \`delegate_task()\` prompt MUST include ALL 6 sections: +Every \`task()\` prompt MUST include ALL 6 sections: \`\`\`markdown ## 1. TASK @@ -166,7 +166,7 @@ Structure: learnings.md, decisions.md, issues.md, problems.md ## Step 3: Execute Tasks ### 3.1 Parallelization Check -- Parallel tasks → invoke multiple \`delegate_task()\` in ONE message +- Parallel tasks → invoke multiple \`task()\` in ONE message - Sequential → process one at a time ### 3.2 Pre-Delegation (MANDATORY) @@ -176,10 +176,10 @@ Read(".sisyphus/notepads/{plan-name}/issues.md") \`\`\` Extract wisdom → include in prompt. -### 3.3 Invoke delegate_task() +### 3.3 Invoke task() \`\`\`typescript -delegate_task(category="[cat]", load_skills=["[skills]"], run_in_background=false, prompt=\`[6-SECTION PROMPT]\`) +task(category="[cat]", load_skills=["[skills]"], run_in_background=false, prompt=\`[6-SECTION PROMPT]\`) \`\`\` ### 3.4 Verify (PROJECT-LEVEL QA) @@ -201,7 +201,7 @@ Checklist: **CRITICAL: Use \`session_id\` for retries.** \`\`\`typescript -delegate_task(session_id="ses_xyz789", load_skills=[...], prompt="FAILED: {error}. Fix by: {instruction}") +task(session_id="ses_xyz789", load_skills=[...], prompt="FAILED: {error}. Fix by: {instruction}") \`\`\` - Maximum 3 retries per task @@ -231,18 +231,18 @@ ACCUMULATED WISDOM: [from notepad] **Exploration (explore/librarian)**: ALWAYS background \`\`\`typescript -delegate_task(subagent_type="explore", run_in_background=true, ...) +task(subagent_type="explore", run_in_background=true, ...) \`\`\` **Task execution**: NEVER background \`\`\`typescript -delegate_task(category="...", run_in_background=false, ...) +task(category="...", run_in_background=false, ...) \`\`\` **Parallel task groups**: Invoke multiple in ONE message \`\`\`typescript -delegate_task(category="quick", load_skills=[], run_in_background=false, prompt="Task 2...") -delegate_task(category="quick", load_skills=[], run_in_background=false, prompt="Task 3...") +task(category="quick", load_skills=[], run_in_background=false, prompt="Task 2...") +task(category="quick", load_skills=[], run_in_background=false, prompt="Task 3...") \`\`\` **Background management**: diff --git a/src/agents/atlas/index.ts b/src/agents/atlas/index.ts index 7161525c9..77cfdda86 100644 --- a/src/agents/atlas/index.ts +++ b/src/agents/atlas/index.ts @@ -1,7 +1,7 @@ /** * Atlas - Master Orchestrator Agent * - * Orchestrates work via delegate_task() to complete ALL tasks in a todo list until fully done. + * Orchestrates work via task() to complete ALL tasks in a todo list until fully done. * You are the conductor of a symphony of specialized agents. * * Routing: @@ -111,7 +111,7 @@ export function createAtlasAgent(ctx: OrchestratorContext): AgentConfig { const baseConfig = { description: - "Orchestrates work via delegate_task() to complete ALL tasks in a todo list until fully done. (Atlas - OhMyOpenCode)", + "Orchestrates work via task() to complete ALL tasks in a todo list until fully done. (Atlas - OhMyOpenCode)", mode: MODE, ...(ctx.model ? { model: ctx.model } : {}), temperature: 0.1, diff --git a/src/agents/atlas/utils.ts b/src/agents/atlas/utils.ts index f912a0370..b83055671 100644 --- a/src/agents/atlas/utils.ts +++ b/src/agents/atlas/utils.ts @@ -47,7 +47,7 @@ Categories spawn \`Sisyphus-Junior-{category}\` with optimized settings: ${categoryRows.join("\n")} \`\`\`typescript -delegate_task(category="[category-name]", load_skills=[...], run_in_background=false, prompt="...") +task(category="[category-name]", load_skills=[...], run_in_background=false, prompt="...") \`\`\`` } @@ -105,7 +105,7 @@ Read each skill's description and ask: "Does this skill's domain overlap with my **Usage:** \`\`\`typescript -delegate_task(category="[category]", load_skills=["skill-1", "skill-2"], run_in_background=false, prompt="...") +task(category="[category]", load_skills=["skill-1", "skill-2"], run_in_background=false, prompt="...") \`\`\` **IMPORTANT:** diff --git a/src/agents/dynamic-agent-prompt-builder.ts b/src/agents/dynamic-agent-prompt-builder.ts index 7c7d35000..3221738fb 100644 --- a/src/agents/dynamic-agent-prompt-builder.ts +++ b/src/agents/dynamic-agent-prompt-builder.ts @@ -242,7 +242,7 @@ ${builtinRows.join("\n")}` return `### Category + Skills Delegation System -**delegate_task() combines categories and skills for optimal task execution.** +**task() combines categories and skills for optimal task execution.** #### Available Categories (Domain-Optimized Models) @@ -296,7 +296,7 @@ SKILL EVALUATION for "[skill-name]": ### Delegation Pattern \`\`\`typescript -delegate_task( +task( category="[selected-category]", load_skills=["skill-1", "skill-2"], // Include ALL relevant skills — ESPECIALLY user-installed ones prompt="..." @@ -305,7 +305,7 @@ delegate_task( **ANTI-PATTERN (will produce poor results):** \`\`\`typescript -delegate_task(category="...", load_skills=[], run_in_background=false, prompt="...") // Empty load_skills without justification +task(category="...", load_skills=[], run_in_background=false, prompt="...") // Empty load_skills without justification \`\`\`` } diff --git a/src/agents/explore.ts b/src/agents/explore.ts index 0e01e279f..1baae542d 100644 --- a/src/agents/explore.ts +++ b/src/agents/explore.ts @@ -29,7 +29,7 @@ export function createExploreAgent(model: string): AgentConfig { "write", "edit", "task", - "delegate_task", + "task", "call_omo_agent", ]) diff --git a/src/agents/hephaestus.ts b/src/agents/hephaestus.ts index bd69acdc4..2fb53148f 100644 --- a/src/agents/hephaestus.ts +++ b/src/agents/hephaestus.ts @@ -227,8 +227,8 @@ Agent: *runs gh pr list, gh pr view, searches recent commits* **Delegation Check (MANDATORY before acting directly):** 1. Is there a specialized agent that perfectly matches this request? -2. If not, is there a \`delegate_task\` category that best describes this task? What skills are available to equip the agent with? - - MUST FIND skills to use: \`delegate_task(load_skills=[{skill1}, ...])\` +2. If not, is there a \`task\` category that best describes this task? What skills are available to equip the agent with? + - MUST FIND skills to use: \`task(load_skills=[{skill1}, ...])\` 3. Can I do it myself for the best result, FOR SURE? **Default Bias: DELEGATE for complex tasks. Work yourself ONLY when trivial.** @@ -280,15 +280,15 @@ ${librarianSection} // CORRECT: Always background, always parallel // Prompt structure: [CONTEXT: what I'm doing] + [GOAL: what I'm trying to achieve] + [QUESTION: what I need to know] + [REQUEST: what to find] // Contextual Grep (internal) -delegate_task(subagent_type="explore", run_in_background=true, load_skills=[], prompt="I'm implementing user authentication for our API. I need to understand how auth is currently structured in this codebase. Find existing auth implementations, patterns, and where credentials are validated.") -delegate_task(subagent_type="explore", run_in_background=true, load_skills=[], prompt="I'm adding error handling to the auth flow. I want to follow existing project conventions for consistency. Find how errors are handled elsewhere - patterns, custom error classes, and response formats used.") +task(subagent_type="explore", run_in_background=true, load_skills=[], prompt="I'm implementing user authentication for our API. I need to understand how auth is currently structured in this codebase. Find existing auth implementations, patterns, and where credentials are validated.") +task(subagent_type="explore", run_in_background=true, load_skills=[], prompt="I'm adding error handling to the auth flow. I want to follow existing project conventions for consistency. Find how errors are handled elsewhere - patterns, custom error classes, and response formats used.") // Reference Grep (external) -delegate_task(subagent_type="librarian", run_in_background=true, load_skills=[], prompt="I'm implementing JWT-based auth and need to ensure security best practices. Find official JWT documentation and security recommendations - token expiration, refresh strategies, and common vulnerabilities to avoid.") -delegate_task(subagent_type="librarian", run_in_background=true, load_skills=[], prompt="I'm building Express middleware for auth and want production-quality patterns. Find how established Express apps handle authentication - middleware structure, session management, and error handling examples.") +task(subagent_type="librarian", run_in_background=true, load_skills=[], prompt="I'm implementing JWT-based auth and need to ensure security best practices. Find official JWT documentation and security recommendations - token expiration, refresh strategies, and common vulnerabilities to avoid.") +task(subagent_type="librarian", run_in_background=true, load_skills=[], prompt="I'm building Express middleware for auth and want production-quality patterns. Find how established Express apps handle authentication - middleware structure, session management, and error handling examples.") // Continue immediately - collect results when needed // WRONG: Sequential or blocking - NEVER DO THIS -result = delegate_task(..., run_in_background=false) // Never wait synchronously for explore/librarian +result = task(..., run_in_background=false) // Never wait synchronously for explore/librarian \`\`\` **Rules:** @@ -393,7 +393,7 @@ AFTER THE WORK YOU DELEGATED SEEMS DONE, ALWAYS VERIFY THE RESULTS AS FOLLOWING: ### Session Continuity (MANDATORY) -Every \`delegate_task()\` output includes a session_id. **USE IT.** +Every \`task()\` output includes a session_id. **USE IT.** **ALWAYS continue when:** | Scenario | Action | diff --git a/src/agents/librarian.ts b/src/agents/librarian.ts index 1588cfb3c..c13b9f10c 100644 --- a/src/agents/librarian.ts +++ b/src/agents/librarian.ts @@ -26,7 +26,7 @@ export function createLibrarianAgent(model: string): AgentConfig { "write", "edit", "task", - "delegate_task", + "task", "call_omo_agent", ]) diff --git a/src/agents/metis.ts b/src/agents/metis.ts index cdba1e303..f25c96c90 100644 --- a/src/agents/metis.ts +++ b/src/agents/metis.ts @@ -307,7 +307,6 @@ const metisRestrictions = createAgentToolRestrictions([ "write", "edit", "task", - "delegate_task", ]) export function createMetisAgent(model: string): AgentConfig { diff --git a/src/agents/momus.ts b/src/agents/momus.ts index 52b1176cc..457e354b5 100644 --- a/src/agents/momus.ts +++ b/src/agents/momus.ts @@ -193,7 +193,7 @@ export function createMomusAgent(model: string): AgentConfig { "write", "edit", "task", - "delegate_task", + "task", ]) const base = { diff --git a/src/agents/oracle.ts b/src/agents/oracle.ts index 9ba829be2..19713627d 100644 --- a/src/agents/oracle.ts +++ b/src/agents/oracle.ts @@ -147,7 +147,7 @@ export function createOracleAgent(model: string): AgentConfig { "write", "edit", "task", - "delegate_task", + "task", ]) const base = { diff --git a/src/agents/prometheus/high-accuracy-mode.ts b/src/agents/prometheus/high-accuracy-mode.ts index 4485924e4..d6ecc821f 100644 --- a/src/agents/prometheus/high-accuracy-mode.ts +++ b/src/agents/prometheus/high-accuracy-mode.ts @@ -15,7 +15,7 @@ export const PROMETHEUS_HIGH_ACCURACY_MODE = `# PHASE 3: PLAN GENERATION \`\`\`typescript // After generating initial plan while (true) { - const result = delegate_task( + const result = task( subagent_type="momus", prompt=".sisyphus/plans/{name}.md", run_in_background=false diff --git a/src/agents/prometheus/interview-mode.ts b/src/agents/prometheus/interview-mode.ts index d92df2c16..8692fd2f2 100644 --- a/src/agents/prometheus/interview-mode.ts +++ b/src/agents/prometheus/interview-mode.ts @@ -66,8 +66,8 @@ Or should I just note down this single fix?" **Research First:** \`\`\`typescript // Prompt structure: CONTEXT (what I'm doing) + GOAL (what I'm trying to achieve) + QUESTION (what I need to know) + REQUEST (what to find) -delegate_task(subagent_type="explore", prompt="I'm refactoring [target] and need to understand its impact scope before making changes. Find all usages via lsp_find_references - show calling code, patterns of use, and potential breaking points.", run_in_background=true) -delegate_task(subagent_type="explore", prompt="I'm about to modify [affected code] and need to ensure behavior preservation. Find existing test coverage - which tests exercise this code, what assertions exist, and any gaps in coverage.", run_in_background=true) +task(subagent_type="explore", prompt="I'm refactoring [target] and need to understand its impact scope before making changes. Find all usages via lsp_find_references - show calling code, patterns of use, and potential breaking points.", run_in_background=true) +task(subagent_type="explore", prompt="I'm about to modify [affected code] and need to ensure behavior preservation. Find existing test coverage - which tests exercise this code, what assertions exist, and any gaps in coverage.", run_in_background=true) \`\`\` **Interview Focus:** @@ -91,9 +91,9 @@ delegate_task(subagent_type="explore", prompt="I'm about to modify [affected cod \`\`\`typescript // Launch BEFORE asking user questions // Prompt structure: CONTEXT + GOAL + QUESTION + REQUEST -delegate_task(subagent_type="explore", prompt="I'm building a new [feature] and want to maintain codebase consistency. Find similar implementations in this project - their structure, patterns used, and conventions to follow.", run_in_background=true) -delegate_task(subagent_type="explore", prompt="I'm adding [feature type] to the project and need to understand existing conventions. Find how similar features are organized - file structure, naming patterns, and architectural approach.", run_in_background=true) -delegate_task(subagent_type="librarian", prompt="I'm implementing [technology] and want to follow established best practices. Find official documentation and community recommendations - setup patterns, common pitfalls, and production-ready examples.", run_in_background=true) +task(subagent_type="explore", prompt="I'm building a new [feature] and want to maintain codebase consistency. Find similar implementations in this project - their structure, patterns used, and conventions to follow.", run_in_background=true) +task(subagent_type="explore", prompt="I'm adding [feature type] to the project and need to understand existing conventions. Find how similar features are organized - file structure, naming patterns, and architectural approach.", run_in_background=true) +task(subagent_type="librarian", prompt="I'm implementing [technology] and want to follow established best practices. Find official documentation and community recommendations - setup patterns, common pitfalls, and production-ready examples.", run_in_background=true) \`\`\` **Interview Focus** (AFTER research): @@ -132,7 +132,7 @@ Based on your stack, I'd recommend NextAuth.js - it integrates well with Next.js Run this check: \`\`\`typescript -delegate_task(subagent_type="explore", prompt="I'm assessing this project's test setup before planning work that may require TDD. I need to understand what testing capabilities exist. Find test infrastructure: package.json test scripts, config files (jest.config, vitest.config, pytest.ini), and existing test files. Report: 1) Does test infra exist? 2) What framework? 3) Example test patterns.", run_in_background=true) +task(subagent_type="explore", prompt="I'm assessing this project's test setup before planning work that may require TDD. I need to understand what testing capabilities exist. Find test infrastructure: package.json test scripts, config files (jest.config, vitest.config, pytest.ini), and existing test files. Report: 1) Does test infra exist? 2) What framework? 3) Example test patterns.", run_in_background=true) \`\`\` #### Step 2: Ask the Test Question (MANDATORY) @@ -230,13 +230,13 @@ Add to draft immediately: **Research First:** \`\`\`typescript -delegate_task(subagent_type="explore", prompt="I'm planning architectural changes and need to understand the current system design. Find existing architecture: module boundaries, dependency patterns, data flow, and key abstractions used.", run_in_background=true) -delegate_task(subagent_type="librarian", prompt="I'm designing architecture for [domain] and want to make informed decisions. Find architectural best practices - proven patterns, trade-offs, and lessons learned from similar systems.", run_in_background=true) +task(subagent_type="explore", prompt="I'm planning architectural changes and need to understand the current system design. Find existing architecture: module boundaries, dependency patterns, data flow, and key abstractions used.", run_in_background=true) +task(subagent_type="librarian", prompt="I'm designing architecture for [domain] and want to make informed decisions. Find architectural best practices - proven patterns, trade-offs, and lessons learned from similar systems.", run_in_background=true) \`\`\` **Oracle Consultation** (recommend when stakes are high): \`\`\`typescript -delegate_task(subagent_type="oracle", prompt="Architecture consultation needed: [context]...", run_in_background=false) +task(subagent_type="oracle", prompt="Architecture consultation needed: [context]...", run_in_background=false) \`\`\` **Interview Focus:** @@ -253,9 +253,9 @@ delegate_task(subagent_type="oracle", prompt="Architecture consultation needed: **Parallel Investigation:** \`\`\`typescript -delegate_task(subagent_type="explore", prompt="I'm researching how to implement [feature] and need to understand current approach. Find how X is currently handled in this codebase - implementation details, edge cases covered, and any known limitations.", run_in_background=true) -delegate_task(subagent_type="librarian", prompt="I'm implementing Y and need authoritative guidance. Find official documentation - API reference, configuration options, and recommended usage patterns.", run_in_background=true) -delegate_task(subagent_type="librarian", prompt="I'm looking for battle-tested implementations of Z. Find open source projects that solve this - focus on production-quality code, how they handle edge cases, and any gotchas documented.", run_in_background=true) +task(subagent_type="explore", prompt="I'm researching how to implement [feature] and need to understand current approach. Find how X is currently handled in this codebase - implementation details, edge cases covered, and any known limitations.", run_in_background=true) +task(subagent_type="librarian", prompt="I'm implementing Y and need authoritative guidance. Find official documentation - API reference, configuration options, and recommended usage patterns.", run_in_background=true) +task(subagent_type="librarian", prompt="I'm looking for battle-tested implementations of Z. Find open source projects that solve this - focus on production-quality code, how they handle edge cases, and any gotchas documented.", run_in_background=true) \`\`\` **Interview Focus:** @@ -281,17 +281,17 @@ delegate_task(subagent_type="librarian", prompt="I'm looking for battle-tested i **For Understanding Codebase:** \`\`\`typescript -delegate_task(subagent_type="explore", prompt="I'm working on [topic] and need to understand how it's organized in this project. Find all related files - show the structure, patterns used, and conventions I should follow.", run_in_background=true) +task(subagent_type="explore", prompt="I'm working on [topic] and need to understand how it's organized in this project. Find all related files - show the structure, patterns used, and conventions I should follow.", run_in_background=true) \`\`\` **For External Knowledge:** \`\`\`typescript -delegate_task(subagent_type="librarian", prompt="I'm integrating [library] and need to understand [specific feature]. Find official documentation - API details, configuration options, and recommended best practices.", run_in_background=true) +task(subagent_type="librarian", prompt="I'm integrating [library] and need to understand [specific feature]. Find official documentation - API details, configuration options, and recommended best practices.", run_in_background=true) \`\`\` **For Implementation Examples:** \`\`\`typescript -delegate_task(subagent_type="librarian", prompt="I'm implementing [feature] and want to learn from existing solutions. Find open source implementations - focus on production-quality code, architecture decisions, and common patterns.", run_in_background=true) +task(subagent_type="librarian", prompt="I'm implementing [feature] and want to learn from existing solutions. Find open source implementations - focus on production-quality code, architecture decisions, and common patterns.", run_in_background=true) \`\`\` ## Interview Mode Anti-Patterns diff --git a/src/agents/prometheus/plan-generation.ts b/src/agents/prometheus/plan-generation.ts index d1edc19aa..3443d6888 100644 --- a/src/agents/prometheus/plan-generation.ts +++ b/src/agents/prometheus/plan-generation.ts @@ -59,7 +59,7 @@ todoWrite([ **BEFORE generating the plan**, summon Metis to catch what you might have missed: \`\`\`typescript -delegate_task( +task( subagent_type="metis", prompt=\`Review this planning session before I generate the work plan: diff --git a/src/agents/prometheus/plan-template.ts b/src/agents/prometheus/plan-template.ts index ce030d3e7..75c9ced9b 100644 --- a/src/agents/prometheus/plan-template.ts +++ b/src/agents/prometheus/plan-template.ts @@ -214,7 +214,7 @@ Parallel Speedup: ~40% faster than sequential | Wave | Tasks | Recommended Agents | |------|-------|-------------------| -| 1 | 1, 5 | delegate_task(category="...", load_skills=[...], run_in_background=false) | +| 1 | 1, 5 | task(category="...", load_skills=[...], run_in_background=false) | | 2 | 2, 3, 6 | dispatch parallel after Wave 1 completes | | 3 | 4 | final integration task | diff --git a/src/agents/sisyphus-junior/default.ts b/src/agents/sisyphus-junior/default.ts index a234a021b..95cce6078 100644 --- a/src/agents/sisyphus-junior/default.ts +++ b/src/agents/sisyphus-junior/default.ts @@ -24,7 +24,6 @@ Execute tasks directly. NEVER delegate or spawn other agents. BLOCKED ACTIONS (will fail if attempted): - task tool: BLOCKED -- delegate_task tool: BLOCKED ALLOWED: call_omo_agent - You CAN spawn explore/librarian agents for research. You work ALONE for implementation. No delegation of implementation tasks. diff --git a/src/agents/sisyphus-junior/gpt.ts b/src/agents/sisyphus-junior/gpt.ts index 0a26b2221..03653ba0a 100644 --- a/src/agents/sisyphus-junior/gpt.ts +++ b/src/agents/sisyphus-junior/gpt.ts @@ -50,7 +50,6 @@ BLOCKED (will fail if attempted): | Tool | Status | |------|--------| | task | BLOCKED | -| delegate_task | BLOCKED | ALLOWED: | Tool | Usage | diff --git a/src/agents/sisyphus-junior/index.test.ts b/src/agents/sisyphus-junior/index.test.ts index 7b9128a5e..64cf3de86 100644 --- a/src/agents/sisyphus-junior/index.test.ts +++ b/src/agents/sisyphus-junior/index.test.ts @@ -143,13 +143,12 @@ describe("createSisyphusJuniorAgentWithOverrides", () => { }) }) - describe("tool safety (task/delegate_task blocked, call_omo_agent allowed)", () => { - test("task and delegate_task remain blocked, call_omo_agent is allowed via tools format", () => { + describe("tool safety (task blocked, call_omo_agent allowed)", () => { + test("task remains blocked, call_omo_agent is allowed via tools format", () => { // given const override = { tools: { task: true, - delegate_task: true, call_omo_agent: true, read: true, }, @@ -163,25 +162,22 @@ describe("createSisyphusJuniorAgentWithOverrides", () => { const permission = result.permission as Record | undefined if (tools) { expect(tools.task).toBe(false) - expect(tools.delegate_task).toBe(false) // call_omo_agent is NOW ALLOWED for subagents to spawn explore/librarian expect(tools.call_omo_agent).toBe(true) expect(tools.read).toBe(true) } if (permission) { expect(permission.task).toBe("deny") - expect(permission.delegate_task).toBe("deny") // call_omo_agent is NOW ALLOWED for subagents to spawn explore/librarian expect(permission.call_omo_agent).toBe("allow") } }) - test("task and delegate_task remain blocked when using permission format override", () => { + test("task remains blocked when using permission format override", () => { // given const override = { permission: { task: "allow", - delegate_task: "allow", call_omo_agent: "allow", read: "allow", }, @@ -190,17 +186,15 @@ describe("createSisyphusJuniorAgentWithOverrides", () => { // when const result = createSisyphusJuniorAgentWithOverrides(override as Parameters[0]) - // then - task/delegate_task blocked, but call_omo_agent allowed for explore/librarian spawning + // then - task blocked, but call_omo_agent allowed for explore/librarian spawning const tools = result.tools as Record | undefined const permission = result.permission as Record | undefined if (tools) { expect(tools.task).toBe(false) - expect(tools.delegate_task).toBe(false) expect(tools.call_omo_agent).toBe(true) } if (permission) { expect(permission.task).toBe("deny") - expect(permission.delegate_task).toBe("deny") expect(permission.call_omo_agent).toBe("allow") } }) diff --git a/src/agents/sisyphus-junior/index.ts b/src/agents/sisyphus-junior/index.ts index 26c3f7538..d9c56bc9d 100644 --- a/src/agents/sisyphus-junior/index.ts +++ b/src/agents/sisyphus-junior/index.ts @@ -28,7 +28,7 @@ const MODE: AgentMode = "subagent" // Core tools that Sisyphus-Junior must NEVER have access to // Note: call_omo_agent is ALLOWED so subagents can spawn explore/librarian -const BLOCKED_TOOLS = ["task", "delegate_task"] +const BLOCKED_TOOLS = ["task"] export const SISYPHUS_JUNIOR_DEFAULTS = { model: "anthropic/claude-sonnet-4-5", diff --git a/src/agents/sisyphus.ts b/src/agents/sisyphus.ts index 024f86535..1adbcc8da 100644 --- a/src/agents/sisyphus.ts +++ b/src/agents/sisyphus.ts @@ -214,8 +214,8 @@ ${keyTriggers} **Delegation Check (MANDATORY before acting directly):** 1. Is there a specialized agent that perfectly matches this request? -2. If not, is there a \`delegate_task\` category best describes this task? (visual-engineering, ultrabrain, quick etc.) What skills are available to equip the agent with? - - MUST FIND skills to use, for: \`delegate_task(load_skills=[{skill1}, ...])\` MUST PASS SKILL AS DELEGATE TASK PARAMETER. +2. If not, is there a \`task\` category best describes this task? (visual-engineering, ultrabrain, quick etc.) What skills are available to equip the agent with? + - MUST FIND skills to use, for: \`task(load_skills=[{skill1}, ...])\` MUST PASS SKILL AS TASK PARAMETER. 3. Can I do it myself for the best result, FOR SURE? REALLY, REALLY, THERE IS NO APPROPRIATE CATEGORIES TO WORK WITH? **Default Bias: DELEGATE. WORK YOURSELF ONLY WHEN IT IS SUPER SIMPLE.** @@ -277,15 +277,15 @@ ${librarianSection} // CORRECT: Always background, always parallel // Prompt structure: [CONTEXT: what I'm doing] + [GOAL: what I'm trying to achieve] + [QUESTION: what I need to know] + [REQUEST: what to find] // Contextual Grep (internal) -delegate_task(subagent_type="explore", run_in_background=true, load_skills=[], prompt="I'm implementing user authentication for our API. I need to understand how auth is currently structured in this codebase. Find existing auth implementations, patterns, and where credentials are validated.") -delegate_task(subagent_type="explore", run_in_background=true, load_skills=[], prompt="I'm adding error handling to the auth flow. I want to follow existing project conventions for consistency. Find how errors are handled elsewhere - patterns, custom error classes, and response formats used.") +task(subagent_type="explore", run_in_background=true, load_skills=[], description="Find auth implementations", prompt="I'm implementing user authentication for our API. I need to understand how auth is currently structured in this codebase. Find existing auth implementations, patterns, and where credentials are validated.") +task(subagent_type="explore", run_in_background=true, load_skills=[], description="Find error handling patterns", prompt="I'm adding error handling to the auth flow. I want to follow existing project conventions for consistency. Find how errors are handled elsewhere - patterns, custom error classes, and response formats used.") // Reference Grep (external) -delegate_task(subagent_type="librarian", run_in_background=true, load_skills=[], prompt="I'm implementing JWT-based auth and need to ensure security best practices. Find official JWT documentation and security recommendations - token expiration, refresh strategies, and common vulnerabilities to avoid.") -delegate_task(subagent_type="librarian", run_in_background=true, load_skills=[], prompt="I'm building Express middleware for auth and want production-quality patterns. Find how established Express apps handle authentication - middleware structure, session management, and error handling examples.") +task(subagent_type="librarian", run_in_background=true, load_skills=[], description="Find JWT security docs", prompt="I'm implementing JWT-based auth and need to ensure security best practices. Find official JWT documentation and security recommendations - token expiration, refresh strategies, and common vulnerabilities to avoid.") +task(subagent_type="librarian", run_in_background=true, load_skills=[], description="Find Express auth patterns", prompt="I'm building Express middleware for auth and want production-quality patterns. Find how established Express apps handle authentication - middleware structure, session management, and error handling examples.") // Continue working immediately. Collect with background_output when needed. // WRONG: Sequential or blocking -result = delegate_task(..., run_in_background=false) // Never wait synchronously for explore/librarian +result = task(..., run_in_background=false) // Never wait synchronously for explore/librarian \`\`\` ### Background Result Collection: @@ -340,7 +340,7 @@ AFTER THE WORK YOU DELEGATED SEEMS DONE, ALWAYS VERIFY THE RESULTS AS FOLLOWING: ### Session Continuity (MANDATORY) -Every \`delegate_task()\` output includes a session_id. **USE IT.** +Every \`task()\` output includes a session_id. **USE IT.** **ALWAYS continue when:** | Scenario | Action | @@ -358,10 +358,10 @@ Every \`delegate_task()\` output includes a session_id. **USE IT.** \`\`\`typescript // WRONG: Starting fresh loses all context -delegate_task(category="quick", load_skills=[], run_in_background=false, prompt="Fix the type error in auth.ts...") +task(category="quick", load_skills=[], run_in_background=false, description="Fix type error", prompt="Fix the type error in auth.ts...") // CORRECT: Resume preserves everything -delegate_task(session_id="ses_abc123", prompt="Fix: Type error on line 42") +task(session_id="ses_abc123", load_skills=[], run_in_background=false, description="Fix type error", prompt="Fix: Type error on line 42") \`\`\` **After EVERY delegation, STORE the session_id for potential continuation.** diff --git a/src/config/schema.ts b/src/config/schema.ts index d317750d2..936c96a86 100644 --- a/src/config/schema.ts +++ b/src/config/schema.ts @@ -12,6 +12,7 @@ const AgentPermissionSchema = z.object({ edit: PermissionValue.optional(), bash: BashPermission.optional(), webfetch: PermissionValue.optional(), + task: PermissionValue.optional(), doom_loop: PermissionValue.optional(), external_directory: PermissionValue.optional(), }) @@ -183,7 +184,7 @@ export const SisyphusAgentConfigSchema = z.object({ }) export const CategoryConfigSchema = z.object({ - /** Human-readable description of the category's purpose. Shown in delegate_task prompt. */ + /** Human-readable description of the category's purpose. Shown in task prompt. */ description: z.string().optional(), model: z.string().optional(), variant: z.string().optional(), diff --git a/src/features/AGENTS.md b/src/features/AGENTS.md index 4cb9cc47b..59155c47f 100644 --- a/src/features/AGENTS.md +++ b/src/features/AGENTS.md @@ -56,7 +56,7 @@ features/ ## ANTI-PATTERNS -- **Sequential delegation**: Use `delegate_task` parallel +- **Sequential delegation**: Use `task` parallel - **Trust self-reports**: ALWAYS verify - **Main thread blocks**: No heavy I/O in loader init - **Direct state mutation**: Use managers for boulder/session state diff --git a/src/features/background-agent/manager.test.ts b/src/features/background-agent/manager.test.ts index 69dde4ddd..334e64178 100644 --- a/src/features/background-agent/manager.test.ts +++ b/src/features/background-agent/manager.test.ts @@ -1,8 +1,9 @@ -import { describe, test, expect, beforeEach } from "bun:test" -import { afterEach } from "bun:test" +declare const require: (name: string) => any +const { describe, test, expect, beforeEach, afterEach } = require("bun:test") import { tmpdir } from "node:os" import type { PluginInput } from "@opencode-ai/plugin" import type { BackgroundTask, ResumeInput } from "./types" +import { MIN_IDLE_TIME_MS } from "./constants" import { BackgroundManager } from "./manager" import { ConcurrencyManager } from "./concurrency" @@ -1088,6 +1089,34 @@ describe("BackgroundManager.tryCompleteTask", () => { // #then expect(abortedSessionIDs).toEqual(["session-1"]) }) + + test("should clean pendingByParent even when notifyParentSession throws", async () => { + // given + ;(manager as unknown as { notifyParentSession: () => Promise }).notifyParentSession = async () => { + throw new Error("notify failed") + } + + const task: BackgroundTask = { + id: "task-pending-cleanup", + sessionID: "session-pending-cleanup", + parentSessionID: "parent-pending-cleanup", + parentMessageID: "msg-1", + description: "pending cleanup task", + prompt: "test", + agent: "explore", + status: "running", + startedAt: new Date(), + } + getTaskMap(manager).set(task.id, task) + getPendingByParent(manager).set(task.parentSessionID, new Set([task.id])) + + // when + await tryCompleteTaskForTest(manager, task) + + // then + expect(task.status).toBe("completed") + expect(getPendingByParent(manager).get(task.parentSessionID)).toBeUndefined() + }) }) describe("BackgroundManager.trackTask", () => { @@ -1110,7 +1139,7 @@ describe("BackgroundManager.trackTask", () => { sessionID: "session-1", parentSessionID: "parent-session", description: "external task", - agent: "delegate_task", + agent: "task", concurrencyKey: "external-key", } @@ -1145,7 +1174,7 @@ describe("BackgroundManager.resume concurrency key", () => { sessionID: "session-1", parentSessionID: "parent-session", description: "external task", - agent: "delegate_task", + agent: "task", concurrencyKey: "external-key", }) @@ -2408,3 +2437,179 @@ describe("BackgroundManager.completionTimers - Memory Leak Fix", () => { expect(completionTimers.size).toBe(0) }) }) + +describe("BackgroundManager.handleEvent - early session.idle deferral", () => { + test("should defer and retry when session.idle fires before MIN_IDLE_TIME_MS", async () => { + //#given - a running task started less than MIN_IDLE_TIME_MS ago + const sessionID = "session-early-idle" + const messagesCalls: string[] = [] + const realDateNow = Date.now + const baseNow = realDateNow() + + const client = { + session: { + prompt: async () => ({}), + abort: async () => ({}), + messages: async (args: { path: { id: string } }) => { + messagesCalls.push(args.path.id) + return { + data: [ + { + info: { role: "assistant" }, + parts: [{ type: "text", text: "ok" }], + }, + ], + } + }, + todo: async () => ({ data: [] }), + }, + } + + const manager = new BackgroundManager({ client, directory: tmpdir() } as unknown as PluginInput) + stubNotifyParentSession(manager) + + const remainingMs = 1200 + const task: BackgroundTask = { + id: "task-early-idle", + sessionID, + parentSessionID: "parent-session", + parentMessageID: "msg-1", + description: "early idle task", + prompt: "test", + agent: "explore", + status: "running", + startedAt: new Date(baseNow), + } + + getTaskMap(manager).set(task.id, task) + + //#when - session.idle fires + try { + Date.now = () => baseNow + (MIN_IDLE_TIME_MS - 100) + manager.handleEvent({ type: "session.idle", properties: { sessionID } }) + + // Advance time so deferred callback (if any) sees elapsed >= MIN_IDLE_TIME_MS + Date.now = () => baseNow + (MIN_IDLE_TIME_MS + 10) + + //#then - idle should be deferred (not dropped), and task should eventually complete + expect(task.status).toBe("running") + await new Promise((resolve) => setTimeout(resolve, 220)) + expect(task.status).toBe("completed") + expect(messagesCalls).toEqual([sessionID]) + } finally { + Date.now = realDateNow + manager.shutdown() + } + }) + + test("should not defer when session.idle fires after MIN_IDLE_TIME_MS", async () => { + //#given - a running task started more than MIN_IDLE_TIME_MS ago + const sessionID = "session-late-idle" + const client = { + session: { + prompt: async () => ({}), + abort: async () => ({}), + messages: async () => ({ + data: [ + { + info: { role: "assistant" }, + parts: [{ type: "text", text: "ok" }], + }, + ], + }), + todo: async () => ({ data: [] }), + }, + } + + const manager = new BackgroundManager({ client, directory: tmpdir() } as unknown as PluginInput) + stubNotifyParentSession(manager) + + const task: BackgroundTask = { + id: "task-late-idle", + sessionID, + parentSessionID: "parent-session", + parentMessageID: "msg-1", + description: "late idle task", + prompt: "test", + agent: "explore", + status: "running", + startedAt: new Date(Date.now() - (MIN_IDLE_TIME_MS + 10)), + } + + getTaskMap(manager).set(task.id, task) + + //#when + manager.handleEvent({ type: "session.idle", properties: { sessionID } }) + + //#then - should be processed immediately + await new Promise((resolve) => setTimeout(resolve, 10)) + expect(task.status).toBe("completed") + + manager.shutdown() + }) + + test("should not process deferred idle if task already completed by other means", async () => { + //#given - a running task + const sessionID = "session-deferred-noop" + let messagesCallCount = 0 + const realDateNow = Date.now + const baseNow = realDateNow() + + const client = { + session: { + prompt: async () => ({}), + abort: async () => ({}), + messages: async () => { + messagesCallCount += 1 + return { + data: [ + { + info: { role: "assistant" }, + parts: [{ type: "text", text: "ok" }], + }, + ], + } + }, + todo: async () => ({ data: [] }), + }, + } + + const manager = new BackgroundManager({ client, directory: tmpdir() } as unknown as PluginInput) + stubNotifyParentSession(manager) + + const remainingMs = 120 + const task: BackgroundTask = { + id: "task-deferred-noop", + sessionID, + parentSessionID: "parent-session", + parentMessageID: "msg-1", + description: "deferred noop task", + prompt: "test", + agent: "explore", + status: "running", + startedAt: new Date(baseNow), + } + getTaskMap(manager).set(task.id, task) + + //#when - session.idle fires early, then task completes via another path before defer timer + try { + Date.now = () => baseNow + (MIN_IDLE_TIME_MS - remainingMs) + manager.handleEvent({ type: "session.idle", properties: { sessionID } }) + expect(messagesCallCount).toBe(0) + + await tryCompleteTaskForTest(manager, task) + expect(task.status).toBe("completed") + + // Advance time so deferred callback (if any) sees elapsed >= MIN_IDLE_TIME_MS + Date.now = () => baseNow + (MIN_IDLE_TIME_MS + 10) + + //#then - deferred callback should be a no-op + await new Promise((resolve) => setTimeout(resolve, remainingMs + 80)) + expect(task.status).toBe("completed") + expect(messagesCallCount).toBe(0) + } finally { + Date.now = realDateNow + manager.shutdown() + } + }) +}) diff --git a/src/features/background-agent/manager.ts b/src/features/background-agent/manager.ts index 0938a81b6..5b5bdbaf1 100644 --- a/src/features/background-agent/manager.ts +++ b/src/features/background-agent/manager.ts @@ -88,6 +88,7 @@ export class BackgroundManager { private queuesByKey: Map = new Map() private processingKeys: Set = new Set() private completionTimers: Map> = new Map() + private idleDeferralTimers: Map> = new Map() constructor( ctx: PluginInput, @@ -328,7 +329,6 @@ export class BackgroundManager { tools: { ...getAgentToolRestrictions(input.agent), task: false, - delegate_task: false, call_omo_agent: true, question: false, }, @@ -357,6 +357,7 @@ export class BackgroundManager { }).catch(() => {}) this.markForNotification(existingTask) + this.cleanupPendingByParent(existingTask) this.notifyParentSession(existingTask).catch(err => { log("[background-agent] Failed to notify on error:", err) }) @@ -410,7 +411,7 @@ export class BackgroundManager { } /** - * Track a task created elsewhere (e.g., from delegate_task) for notification tracking. + * Track a task created elsewhere (e.g., from task) for notification tracking. * This allows tasks created by other tools to receive the same toast/prompt notifications. */ async trackTask(input: { @@ -458,7 +459,7 @@ export class BackgroundManager { return existingTask } - const concurrencyGroup = input.concurrencyKey ?? input.agent ?? "delegate_task" + const concurrencyGroup = input.concurrencyKey ?? input.agent ?? "task" // Acquire concurrency slot if a key is provided if (input.concurrencyKey) { @@ -472,7 +473,7 @@ export class BackgroundManager { parentMessageID: "", description: input.description, prompt: "", - agent: input.agent || "delegate_task", + agent: input.agent || "task", status: "running", startedAt: new Date(), progress: { @@ -587,7 +588,6 @@ export class BackgroundManager { tools: { ...getAgentToolRestrictions(existingTask.agent), task: false, - delegate_task: false, call_omo_agent: true, question: false, }, @@ -614,6 +614,7 @@ export class BackgroundManager { } this.markForNotification(existingTask) + this.cleanupPendingByParent(existingTask) this.notifyParentSession(existingTask).catch(err => { log("[background-agent] Failed to notify on resume error:", err) }) @@ -651,6 +652,13 @@ export class BackgroundManager { const task = this.findBySession(sessionID) if (!task) return + // Clear any pending idle deferral timer since the task is still active + const existingTimer = this.idleDeferralTimers.get(task.id) + if (existingTimer) { + clearTimeout(existingTimer) + this.idleDeferralTimers.delete(task.id) + } + if (partInfo?.type === "tool" || partInfo?.tool) { if (!task.progress) { task.progress = { @@ -677,7 +685,17 @@ export class BackgroundManager { // Edge guard: Require minimum elapsed time (5 seconds) before accepting idle const elapsedMs = Date.now() - startedAt.getTime() if (elapsedMs < MIN_IDLE_TIME_MS) { - log("[background-agent] Ignoring early session.idle, elapsed:", { elapsedMs, taskId: task.id }) + const remainingMs = MIN_IDLE_TIME_MS - elapsedMs + if (!this.idleDeferralTimers.has(task.id)) { + log("[background-agent] Deferring early session.idle:", { elapsedMs, remainingMs, taskId: task.id }) + const timer = setTimeout(() => { + this.idleDeferralTimers.delete(task.id) + this.handleEvent({ type: "session.idle", properties: { sessionID } }) + }, remainingMs) + this.idleDeferralTimers.set(task.id, timer) + } else { + log("[background-agent] session.idle already deferred:", { elapsedMs, taskId: task.id }) + } return } @@ -736,6 +754,12 @@ export class BackgroundManager { clearTimeout(existingTimer) this.completionTimers.delete(task.id) } + + const idleTimer = this.idleDeferralTimers.get(task.id) + if (idleTimer) { + clearTimeout(idleTimer) + this.idleDeferralTimers.delete(task.id) + } this.cleanupPendingByParent(task) this.tasks.delete(task.id) this.clearNotificationsForTask(task.id) @@ -890,6 +914,12 @@ export class BackgroundManager { this.completionTimers.delete(task.id) } + const idleTimer = this.idleDeferralTimers.get(task.id) + if (idleTimer) { + clearTimeout(idleTimer) + this.idleDeferralTimers.delete(task.id) + } + this.cleanupPendingByParent(task) if (abortSession && task.sessionID) { @@ -1025,6 +1055,15 @@ export class BackgroundManager { this.markForNotification(task) + // Ensure pending tracking is cleaned up even if notification fails + this.cleanupPendingByParent(task) + + const idleTimer = this.idleDeferralTimers.get(task.id) + if (idleTimer) { + clearTimeout(idleTimer) + this.idleDeferralTimers.delete(task.id) + } + if (task.sessionID) { this.client.session.abort({ path: { id: task.sessionID }, @@ -1511,6 +1550,11 @@ Use \`background_output(task_id="${task.id}")\` to retrieve this result when rea } this.completionTimers.clear() + for (const timer of this.idleDeferralTimers.values()) { + clearTimeout(timer) + } + this.idleDeferralTimers.clear() + this.concurrencyManager.clear() this.tasks.clear() this.notifications.clear() diff --git a/src/features/background-agent/spawner.ts b/src/features/background-agent/spawner.ts index ef422795c..f9c9cae5e 100644 --- a/src/features/background-agent/spawner.ts +++ b/src/features/background-agent/spawner.ts @@ -146,7 +146,6 @@ export async function startTask( tools: { ...getAgentToolRestrictions(input.agent), task: false, - delegate_task: false, call_omo_agent: true, question: false, }, @@ -231,7 +230,6 @@ export async function resumeTask( tools: { ...getAgentToolRestrictions(task.agent), task: false, - delegate_task: false, call_omo_agent: true, question: false, }, diff --git a/src/features/builtin-commands/templates/init-deep.ts b/src/features/builtin-commands/templates/init-deep.ts index 808f489ec..28e18290b 100644 --- a/src/features/builtin-commands/templates/init-deep.ts +++ b/src/features/builtin-commands/templates/init-deep.ts @@ -45,12 +45,12 @@ Don't wait—these run async while main session works. \`\`\` // Fire all at once, collect results later -delegate_task(agent="explore", prompt="Project structure: PREDICT standard patterns for detected language → REPORT deviations only") -delegate_task(agent="explore", prompt="Entry points: FIND main files → REPORT non-standard organization") -delegate_task(agent="explore", prompt="Conventions: FIND config files (.eslintrc, pyproject.toml, .editorconfig) → REPORT project-specific rules") -delegate_task(agent="explore", prompt="Anti-patterns: FIND 'DO NOT', 'NEVER', 'ALWAYS', 'DEPRECATED' comments → LIST forbidden patterns") -delegate_task(agent="explore", prompt="Build/CI: FIND .github/workflows, Makefile → REPORT non-standard patterns") -delegate_task(agent="explore", prompt="Test patterns: FIND test configs, test structure → REPORT unique conventions") +task(subagent_type="explore", load_skills=[], description="Explore project structure", run_in_background=true, prompt="Project structure: PREDICT standard patterns for detected language → REPORT deviations only") +task(subagent_type="explore", load_skills=[], description="Find entry points", run_in_background=true, prompt="Entry points: FIND main files → REPORT non-standard organization") +task(subagent_type="explore", load_skills=[], description="Find conventions", run_in_background=true, prompt="Conventions: FIND config files (.eslintrc, pyproject.toml, .editorconfig) → REPORT project-specific rules") +task(subagent_type="explore", load_skills=[], description="Find anti-patterns", run_in_background=true, prompt="Anti-patterns: FIND 'DO NOT', 'NEVER', 'ALWAYS', 'DEPRECATED' comments → LIST forbidden patterns") +task(subagent_type="explore", load_skills=[], description="Explore build/CI", run_in_background=true, prompt="Build/CI: FIND .github/workflows, Makefile → REPORT non-standard patterns") +task(subagent_type="explore", load_skills=[], description="Find test patterns", run_in_background=true, prompt="Test patterns: FIND test configs, test structure → REPORT unique conventions") \`\`\` @@ -76,9 +76,9 @@ max_depth=$(find . -type d -not -path '*/node_modules/*' -not -path '*/.git/*' | Example spawning: \`\`\` // 500 files, 50k lines, depth 6, 15 large files → spawn 5+5+2+1 = 13 additional agents -delegate_task(agent="explore", prompt="Large file analysis: FIND files >500 lines, REPORT complexity hotspots") -delegate_task(agent="explore", prompt="Deep modules at depth 4+: FIND hidden patterns, internal conventions") -delegate_task(agent="explore", prompt="Cross-cutting concerns: FIND shared utilities across directories") +task(subagent_type="explore", load_skills=[], description="Analyze large files", run_in_background=true, prompt="Large file analysis: FIND files >500 lines, REPORT complexity hotspots") +task(subagent_type="explore", load_skills=[], description="Explore deep modules", run_in_background=true, prompt="Deep modules at depth 4+: FIND hidden patterns, internal conventions") +task(subagent_type="explore", load_skills=[], description="Find shared utilities", run_in_background=true, prompt="Cross-cutting concerns: FIND shared utilities across directories") // ... more based on calculation \`\`\` @@ -185,6 +185,11 @@ AGENTS_LOCATIONS = [ **Mark "generate" as in_progress.** + +**File Writing Rule**: If AGENTS.md already exists at the target path → use \`Edit\` tool. If it does NOT exist → use \`Write\` tool. +NEVER use Write to overwrite an existing file. ALWAYS check existence first via \`Read\` or discovery results. + + ### Root AGENTS.md (Full Treatment) \`\`\`markdown @@ -240,7 +245,7 @@ Launch writing tasks for each location: \`\`\` for loc in AGENTS_LOCATIONS (except root): - delegate_task(category="writing", load_skills=[], run_in_background=false, prompt=\\\` + task(category="writing", load_skills=[], run_in_background=false, description="Generate AGENTS.md", prompt=\\\` Generate AGENTS.md for: \${loc.path} - Reason: \${loc.reason} - 30-80 lines max diff --git a/src/features/builtin-skills/git-master/SKILL.md b/src/features/builtin-skills/git-master/SKILL.md index 39af06a65..fef28be88 100644 --- a/src/features/builtin-skills/git-master/SKILL.md +++ b/src/features/builtin-skills/git-master/SKILL.md @@ -1,6 +1,6 @@ --- name: git-master -description: "MUST USE for ANY git operations. Atomic commits, rebase/squash, history search (blame, bisect, log -S). STRONGLY RECOMMENDED: Use with delegate_task(category='quick', load_skills=['git-master'], ...) to save context. Triggers: 'commit', 'rebase', 'squash', 'who wrote', 'when was X added', 'find the commit that'." +description: "MUST USE for ANY git operations. Atomic commits, rebase/squash, history search (blame, bisect, log -S). STRONGLY RECOMMENDED: Use with task(category='quick', load_skills=['git-master'], ...) to save context. Triggers: 'commit', 'rebase', 'squash', 'who wrote', 'when was X added', 'find the commit that'." --- # Git Master Agent diff --git a/src/features/builtin-skills/skills/git-master.ts b/src/features/builtin-skills/skills/git-master.ts index e986a4730..d93f94d17 100644 --- a/src/features/builtin-skills/skills/git-master.ts +++ b/src/features/builtin-skills/skills/git-master.ts @@ -3,7 +3,7 @@ import type { BuiltinSkill } from "../types" export const gitMasterSkill: BuiltinSkill = { name: "git-master", description: - "MUST USE for ANY git operations. Atomic commits, rebase/squash, history search (blame, bisect, log -S). STRONGLY RECOMMENDED: Use with delegate_task(category='quick', load_skills=['git-master'], ...) to save context. Triggers: 'commit', 'rebase', 'squash', 'who wrote', 'when was X added', 'find the commit that'.", + "MUST USE for ANY git operations. Atomic commits, rebase/squash, history search (blame, bisect, log -S). STRONGLY RECOMMENDED: Use with task(category='quick', load_skills=['git-master'], ...) to save context. Triggers: 'commit', 'rebase', 'squash', 'who wrote', 'when was X added', 'find the commit that'.", template: `# Git Master Agent You are a Git expert combining three specializations: diff --git a/src/features/tool-metadata-store/index.test.ts b/src/features/tool-metadata-store/index.test.ts new file mode 100644 index 000000000..c76738eb5 --- /dev/null +++ b/src/features/tool-metadata-store/index.test.ts @@ -0,0 +1,111 @@ +import { describe, test, expect, beforeEach } from "bun:test" +import { + storeToolMetadata, + consumeToolMetadata, + getPendingStoreSize, + clearPendingStore, +} from "./index" + +describe("tool-metadata-store", () => { + beforeEach(() => { + clearPendingStore() + }) + + describe("storeToolMetadata", () => { + test("#given metadata with title and metadata, #when stored, #then store size increases", () => { + //#given + const sessionID = "ses_abc123" + const callID = "call_001" + const data = { + title: "Test Task", + metadata: { sessionId: "ses_child", agent: "oracle" }, + } + + //#when + storeToolMetadata(sessionID, callID, data) + + //#then + expect(getPendingStoreSize()).toBe(1) + }) + }) + + describe("consumeToolMetadata", () => { + test("#given stored metadata, #when consumed, #then returns the stored data", () => { + //#given + const sessionID = "ses_abc123" + const callID = "call_001" + const data = { + title: "My Task", + metadata: { sessionId: "ses_sub", run_in_background: true }, + } + storeToolMetadata(sessionID, callID, data) + + //#when + const result = consumeToolMetadata(sessionID, callID) + + //#then + expect(result).toEqual(data) + }) + + test("#given stored metadata, #when consumed twice, #then second call returns undefined", () => { + //#given + const sessionID = "ses_abc123" + const callID = "call_001" + storeToolMetadata(sessionID, callID, { title: "Task" }) + + //#when + consumeToolMetadata(sessionID, callID) + const second = consumeToolMetadata(sessionID, callID) + + //#then + expect(second).toBeUndefined() + expect(getPendingStoreSize()).toBe(0) + }) + + test("#given no stored metadata, #when consumed, #then returns undefined", () => { + //#given + const sessionID = "ses_nonexistent" + const callID = "call_999" + + //#when + const result = consumeToolMetadata(sessionID, callID) + + //#then + expect(result).toBeUndefined() + }) + }) + + describe("isolation", () => { + test("#given multiple entries, #when consuming one, #then others remain", () => { + //#given + storeToolMetadata("ses_1", "call_a", { title: "Task A" }) + storeToolMetadata("ses_1", "call_b", { title: "Task B" }) + storeToolMetadata("ses_2", "call_a", { title: "Task C" }) + + //#when + const resultA = consumeToolMetadata("ses_1", "call_a") + + //#then + expect(resultA?.title).toBe("Task A") + expect(getPendingStoreSize()).toBe(2) + expect(consumeToolMetadata("ses_1", "call_b")?.title).toBe("Task B") + expect(consumeToolMetadata("ses_2", "call_a")?.title).toBe("Task C") + expect(getPendingStoreSize()).toBe(0) + }) + }) + + describe("overwrite", () => { + test("#given existing entry, #when stored again with same key, #then overwrites", () => { + //#given + storeToolMetadata("ses_1", "call_a", { title: "Old" }) + + //#when + storeToolMetadata("ses_1", "call_a", { title: "New", metadata: { updated: true } }) + + //#then + const result = consumeToolMetadata("ses_1", "call_a") + expect(result?.title).toBe("New") + expect(result?.metadata).toEqual({ updated: true }) + }) + }) +}) diff --git a/src/features/tool-metadata-store/index.ts b/src/features/tool-metadata-store/index.ts new file mode 100644 index 000000000..906e1c2f5 --- /dev/null +++ b/src/features/tool-metadata-store/index.ts @@ -0,0 +1,84 @@ +/** + * Pending tool metadata store. + * + * OpenCode's `fromPlugin()` wrapper always replaces the metadata returned by + * plugin tools with `{ truncated, outputPath }`, discarding any sessionId, + * title, or custom metadata set during `execute()`. + * + * This store captures metadata written via `ctx.metadata()` inside execute(), + * then the `tool.execute.after` hook consumes it and merges it back into the + * result *before* the processor writes the final part to the session store. + * + * Flow: + * execute() → storeToolMetadata(sessionID, callID, data) + * fromPlugin() → overwrites metadata with { truncated } + * tool.execute.after → consumeToolMetadata(sessionID, callID) → merges back + * processor → Session.updatePart(status:"completed", metadata: result.metadata) + */ + +export interface PendingToolMetadata { + title?: string + metadata?: Record +} + +const pendingStore = new Map() + +const STALE_TIMEOUT_MS = 15 * 60 * 1000 + +function makeKey(sessionID: string, callID: string): string { + return `${sessionID}:${callID}` +} + +function cleanupStaleEntries(): void { + const now = Date.now() + for (const [key, entry] of pendingStore) { + if (now - entry.storedAt > STALE_TIMEOUT_MS) { + pendingStore.delete(key) + } + } +} + +/** + * Store metadata to be restored after fromPlugin() overwrites it. + * Called from tool execute() functions alongside ctx.metadata(). + */ +export function storeToolMetadata( + sessionID: string, + callID: string, + data: PendingToolMetadata, +): void { + cleanupStaleEntries() + pendingStore.set(makeKey(sessionID, callID), { ...data, storedAt: Date.now() }) +} + +/** + * Consume stored metadata (one-time read, removes from store). + * Called from tool.execute.after hook. + */ +export function consumeToolMetadata( + sessionID: string, + callID: string, +): PendingToolMetadata | undefined { + const key = makeKey(sessionID, callID) + const stored = pendingStore.get(key) + if (stored) { + pendingStore.delete(key) + const { storedAt: _, ...data } = stored + return data + } + return undefined +} + +/** + * Get current store size (for testing/debugging). + */ +export function getPendingStoreSize(): number { + return pendingStore.size +} + +/** + * Clear all pending metadata (for testing). + */ +export function clearPendingStore(): void { + pendingStore.clear() +} diff --git a/src/hooks/agent-usage-reminder/constants.ts b/src/hooks/agent-usage-reminder/constants.ts index a39ea04ad..17be086d2 100644 --- a/src/hooks/agent-usage-reminder/constants.ts +++ b/src/hooks/agent-usage-reminder/constants.ts @@ -24,7 +24,7 @@ export const TARGET_TOOLS = new Set([ export const AGENT_TOOLS = new Set([ "task", "call_omo_agent", - "delegate_task", + "task", ]); export const REMINDER_MESSAGE = ` @@ -32,13 +32,13 @@ export const REMINDER_MESSAGE = ` You called a search/fetch tool directly without leveraging specialized agents. -RECOMMENDED: Use delegate_task with explore/librarian agents for better results: +RECOMMENDED: Use task with explore/librarian agents for better results: \`\`\` // Parallel exploration - fire multiple agents simultaneously -delegate_task(agent="explore", prompt="Find all files matching pattern X") -delegate_task(agent="explore", prompt="Search for implementation of Y") -delegate_task(agent="librarian", prompt="Lookup documentation for Z") +task(agent="explore", prompt="Find all files matching pattern X") +task(agent="explore", prompt="Search for implementation of Y") +task(agent="librarian", prompt="Lookup documentation for Z") // Then continue your work while they run in background // System will notify you when each completes @@ -50,5 +50,5 @@ WHY: - Specialized agents have domain expertise - Reduces context window usage in main session -ALWAYS prefer: Multiple parallel delegate_task calls > Direct tool calls +ALWAYS prefer: Multiple parallel task calls > Direct tool calls `; diff --git a/src/hooks/atlas/index.test.ts b/src/hooks/atlas/index.test.ts index 23ebe0171..993984f1b 100644 --- a/src/hooks/atlas/index.test.ts +++ b/src/hooks/atlas/index.test.ts @@ -86,7 +86,7 @@ describe("atlas hook", () => { // when - calling with undefined output const result = await hook["tool.execute.after"]( - { tool: "delegate_task", sessionID: "session-123" }, + { tool: "task", sessionID: "session-123" }, undefined as unknown as { title: string; output: string; metadata: Record } ) @@ -94,8 +94,8 @@ describe("atlas hook", () => { expect(result).toBeUndefined() }) - test("should ignore non-delegate_task tools", async () => { - // given - hook and non-delegate_task tool + test("should ignore non-task tools", async () => { + // given - hook and non-task tool const hook = createAtlasHook(createMockPluginInput()) const output = { title: "Test Tool", @@ -138,7 +138,7 @@ describe("atlas hook", () => { // when await hook["tool.execute.after"]( - { tool: "delegate_task", sessionID }, + { tool: "task", sessionID }, output ) @@ -162,14 +162,14 @@ describe("atlas hook", () => { // when await hook["tool.execute.after"]( - { tool: "delegate_task", sessionID }, + { tool: "task", sessionID }, output ) // then - standalone verification reminder appended expect(output.output).toContain("Task completed successfully") expect(output.output).toContain("MANDATORY:") - expect(output.output).toContain("delegate_task(session_id=") + expect(output.output).toContain("task(session_id=") cleanupMessageStorage(sessionID) }) @@ -199,7 +199,7 @@ describe("atlas hook", () => { // when await hook["tool.execute.after"]( - { tool: "delegate_task", sessionID }, + { tool: "task", sessionID }, output ) @@ -208,7 +208,7 @@ describe("atlas hook", () => { expect(output.output).toContain("SUBAGENT WORK COMPLETED") expect(output.output).toContain("test-plan") expect(output.output).toContain("LIE") - expect(output.output).toContain("delegate_task(session_id=") + expect(output.output).toContain("task(session_id=") cleanupMessageStorage(sessionID) }) @@ -238,7 +238,7 @@ describe("atlas hook", () => { // when await hook["tool.execute.after"]( - { tool: "delegate_task", sessionID }, + { tool: "task", sessionID }, output ) @@ -275,7 +275,7 @@ describe("atlas hook", () => { // when await hook["tool.execute.after"]( - { tool: "delegate_task", sessionID }, + { tool: "task", sessionID }, output ) @@ -311,7 +311,7 @@ describe("atlas hook", () => { // when await hook["tool.execute.after"]( - { tool: "delegate_task", sessionID }, + { tool: "task", sessionID }, output ) @@ -348,7 +348,7 @@ describe("atlas hook", () => { // when await hook["tool.execute.after"]( - { tool: "delegate_task", sessionID }, + { tool: "task", sessionID }, output ) @@ -385,12 +385,12 @@ describe("atlas hook", () => { // when await hook["tool.execute.after"]( - { tool: "delegate_task", sessionID }, + { tool: "task", sessionID }, output ) // then - should include session_id instructions and verification - expect(output.output).toContain("delegate_task(session_id=") + expect(output.output).toContain("task(session_id=") expect(output.output).toContain("[x]") expect(output.output).toContain("MANDATORY:") @@ -425,8 +425,8 @@ describe("atlas hook", () => { // then expect(output.output).toContain("ORCHESTRATOR, not an IMPLEMENTER") - expect(output.output).toContain("delegate_task") - expect(output.output).toContain("delegate_task") + expect(output.output).toContain("task") + expect(output.output).toContain("task") }) test("should append delegation reminder when orchestrator edits outside .sisyphus/", async () => { diff --git a/src/hooks/atlas/index.ts b/src/hooks/atlas/index.ts index a7e232124..4878a9af3 100644 --- a/src/hooks/atlas/index.ts +++ b/src/hooks/atlas/index.ts @@ -44,7 +44,7 @@ You just performed direct file modifications outside \`.sisyphus/\`. **You are an ORCHESTRATOR, not an IMPLEMENTER.** As an orchestrator, you should: -- **DELEGATE** implementation work to subagents via \`delegate_task\` +- **DELEGATE** implementation work to subagents via \`task\` - **VERIFY** the work done by subagents - **COORDINATE** multiple tasks and ensure completion @@ -54,7 +54,7 @@ You should NOT: - Implement features yourself **If you need to make changes:** -1. Use \`delegate_task\` to delegate to an appropriate subagent +1. Use \`task\` to delegate to an appropriate subagent 2. Provide clear instructions in the prompt 3. Verify the subagent's work after completion @@ -128,7 +128,7 @@ You (Atlas) are attempting to directly modify a file outside \`.sisyphus/\`. **THIS IS FORBIDDEN** (except for VERIFICATION purposes) As an ORCHESTRATOR, you MUST: -1. **DELEGATE** all implementation work via \`delegate_task\` +1. **DELEGATE** all implementation work via \`task\` 2. **VERIFY** the work done by subagents (reading files is OK) 3. **COORDINATE** - you orchestrate, you don't implement @@ -146,11 +146,11 @@ As an ORCHESTRATOR, you MUST: **IF THIS IS FOR VERIFICATION:** Proceed if you are verifying subagent work by making a small fix. -But for any substantial changes, USE \`delegate_task\`. +But for any substantial changes, USE \`task\`. **CORRECT APPROACH:** \`\`\` -delegate_task( +task( category="...", prompt="[specific single task with clear acceptance criteria]" ) @@ -193,7 +193,7 @@ function buildVerificationReminder(sessionId: string): string { **If ANY verification fails, use this immediately:** \`\`\` -delegate_task(session_id="${sessionId}", prompt="fix: [describe the specific failure]") +task(session_id="${sessionId}", prompt="fix: [describe the specific failure]") \`\`\`` } @@ -688,12 +688,12 @@ export function createAtlasHook( return } - // Check delegate_task - inject single-task directive - if (input.tool === "delegate_task") { + // Check task - inject single-task directive + if (input.tool === "task") { const prompt = output.args.prompt as string | undefined if (prompt && !prompt.includes(SYSTEM_DIRECTIVE_PREFIX)) { output.args.prompt = `${SINGLE_TASK_DIRECTIVE}\n` + prompt - log(`[${HOOK_NAME}] Injected single-task directive to delegate_task`, { + log(`[${HOOK_NAME}] Injected single-task directive to task`, { sessionID: input.sessionID, }) } @@ -732,7 +732,7 @@ export function createAtlasHook( return } - if (input.tool !== "delegate_task") { + if (input.tool !== "task") { return } diff --git a/src/hooks/category-skill-reminder/index.test.ts b/src/hooks/category-skill-reminder/index.test.ts index 998085ea3..08d6118b8 100644 --- a/src/hooks/category-skill-reminder/index.test.ts +++ b/src/hooks/category-skill-reminder/index.test.ts @@ -50,7 +50,7 @@ describe("category-skill-reminder hook", () => { // then - reminder should be injected expect(output.output).toContain("[Category+Skill Reminder]") - expect(output.output).toContain("delegate_task") + expect(output.output).toContain("task") clearSessionAgent(sessionID) }) @@ -130,16 +130,16 @@ describe("category-skill-reminder hook", () => { }) describe("delegation tool tracking", () => { - test("should NOT inject reminder if delegate_task is used", async () => { - // given - sisyphus agent that uses delegate_task + test("should NOT inject reminder if task is used", async () => { + // given - sisyphus agent that uses task const hook = createHook() const sessionID = "delegation-session" updateSessionAgent(sessionID, "Sisyphus") const output = { title: "", output: "result", metadata: {} } - // when - delegate_task is used, then more tool calls - await hook["tool.execute.after"]({ tool: "delegate_task", sessionID, callID: "1" }, output) + // when - task is used, then more tool calls + await hook["tool.execute.after"]({ tool: "task", sessionID, callID: "1" }, output) await hook["tool.execute.after"]({ tool: "edit", sessionID, callID: "2" }, output) await hook["tool.execute.after"]({ tool: "edit", sessionID, callID: "3" }, output) await hook["tool.execute.after"]({ tool: "edit", sessionID, callID: "4" }, output) @@ -329,15 +329,15 @@ describe("category-skill-reminder hook", () => { }) test("should handle delegation tool names case-insensitively", async () => { - // given - sisyphus agent using DELEGATE_TASK in uppercase + // given - sisyphus agent using TASK in uppercase const hook = createHook() const sessionID = "case-delegate-session" updateSessionAgent(sessionID, "Sisyphus") const output = { title: "", output: "result", metadata: {} } - // when - DELEGATE_TASK in uppercase is used - await hook["tool.execute.after"]({ tool: "DELEGATE_TASK", sessionID, callID: "1" }, output) + // when - TASK in uppercase is used + await hook["tool.execute.after"]({ tool: "TASK", sessionID, callID: "1" }, output) await hook["tool.execute.after"]({ tool: "edit", sessionID, callID: "2" }, output) await hook["tool.execute.after"]({ tool: "edit", sessionID, callID: "3" }, output) await hook["tool.execute.after"]({ tool: "edit", sessionID, callID: "4" }, output) diff --git a/src/hooks/category-skill-reminder/index.ts b/src/hooks/category-skill-reminder/index.ts index a46b3ffb2..932219154 100644 --- a/src/hooks/category-skill-reminder/index.ts +++ b/src/hooks/category-skill-reminder/index.ts @@ -30,9 +30,8 @@ const DELEGATABLE_WORK_TOOLS = new Set([ * Tools that indicate the agent is already using delegation properly. */ const DELEGATION_TOOLS = new Set([ - "delegate_task", - "call_omo_agent", - "task", + "task", + "call_omo_agent", ]) function formatSkillNames(skills: AvailableSkill[], limit: number): string { @@ -63,7 +62,7 @@ function buildReminderMessage(availableSkills: AvailableSkill[]): string { "> User-installed skills OVERRIDE built-in defaults. ALWAYS prefer YOUR SKILLS when domain matches.", "", "```typescript", - `delegate_task(category=\"visual-engineering\", load_skills=${loadSkills}, run_in_background=true)`, + `task(category=\"visual-engineering\", load_skills=${loadSkills}, run_in_background=true)`, "```", "", ] diff --git a/src/hooks/claude-code-hooks/index.ts b/src/hooks/claude-code-hooks/index.ts index fd1f68efa..f4b2b1149 100644 --- a/src/hooks/claude-code-hooks/index.ts +++ b/src/hooks/claude-code-hooks/index.ts @@ -257,7 +257,7 @@ export function createClaudeCodeHooksHook( const cachedInput = getToolInput(input.sessionID, input.tool, input.callID) || {} // Use metadata if available and non-empty, otherwise wrap output.output in a structured object - // This ensures plugin tools (call_omo_agent, delegate_task, task) that return strings + // This ensures plugin tools (call_omo_agent, task) that return strings // get their results properly recorded in transcripts instead of empty {} const metadata = output.metadata as Record | undefined const hasMetadata = metadata && typeof metadata === "object" && Object.keys(metadata).length > 0 diff --git a/src/hooks/delegate-task-retry/index.test.ts b/src/hooks/delegate-task-retry/index.test.ts index 64f6692e5..3d536b49b 100644 --- a/src/hooks/delegate-task-retry/index.test.ts +++ b/src/hooks/delegate-task-retry/index.test.ts @@ -8,7 +8,7 @@ import { describe("sisyphus-task-retry", () => { describe("DELEGATE_TASK_ERROR_PATTERNS", () => { // given error patterns are defined - // then should include all known delegate_task error types + // then should include all known task error types it("should contain all known error patterns", () => { expect(DELEGATE_TASK_ERROR_PATTERNS.length).toBeGreaterThan(5) diff --git a/src/hooks/delegate-task-retry/index.ts b/src/hooks/delegate-task-retry/index.ts index c7b64d346..75927033d 100644 --- a/src/hooks/delegate-task-retry/index.ts +++ b/src/hooks/delegate-task-retry/index.ts @@ -45,7 +45,7 @@ export const DELEGATE_TASK_ERROR_PATTERNS: DelegateTaskErrorPattern[] = [ { pattern: "Cannot call primary agent", errorType: "primary_agent", - fixHint: "Primary agents cannot be called via delegate_task. Use a subagent like 'explore', 'oracle', or 'librarian'", + fixHint: "Primary agents cannot be called via task. Use a subagent like 'explore', 'oracle', or 'librarian'", }, { pattern: "Skills not found", @@ -85,11 +85,11 @@ export function buildRetryGuidance(errorInfo: DetectedError): string { ) if (!pattern) { - return `[delegate_task ERROR] Fix the error and retry with correct parameters.` + return `[task ERROR] Fix the error and retry with correct parameters.` } let guidance = ` -[delegate_task CALL FAILED - IMMEDIATE RETRY REQUIRED] +[task CALL FAILED - IMMEDIATE RETRY REQUIRED] **Error Type**: ${errorInfo.errorType} **Fix**: ${pattern.fixHint} @@ -101,11 +101,11 @@ export function buildRetryGuidance(errorInfo: DetectedError): string { } guidance += ` -**Action**: Retry delegate_task NOW with corrected parameters. +**Action**: Retry task NOW with corrected parameters. Example of CORRECT call: \`\`\` -delegate_task( +task( description="Task description", prompt="Detailed prompt...", category="unspecified-low", // OR subagent_type="explore" @@ -124,7 +124,7 @@ export function createDelegateTaskRetryHook(_ctx: PluginInput) { input: { tool: string; sessionID: string; callID: string }, output: { title: string; output: string; metadata: unknown } ) => { - if (input.tool.toLowerCase() !== "delegate_task") return + if (input.tool.toLowerCase() !== "task") return const errorInfo = detectDelegateTaskError(output.output) if (errorInfo) { diff --git a/src/hooks/empty-task-response-detector.ts b/src/hooks/empty-task-response-detector.ts index 01ea0bc3c..1d840e3d4 100644 --- a/src/hooks/empty-task-response-detector.ts +++ b/src/hooks/empty-task-response-detector.ts @@ -15,7 +15,7 @@ export function createEmptyTaskResponseDetectorHook(_ctx: PluginInput) { input: { tool: string; sessionID: string; callID: string }, output: { title: string; output: string; metadata: unknown } ) => { - if (input.tool !== "Task") return + if (input.tool !== "Task" && input.tool !== "task") return const responseText = output.output?.trim() ?? "" diff --git a/src/hooks/keyword-detector/ultrawork/default.ts b/src/hooks/keyword-detector/ultrawork/default.ts index e1f0303ac..fb7fce31d 100644 --- a/src/hooks/keyword-detector/ultrawork/default.ts +++ b/src/hooks/keyword-detector/ultrawork/default.ts @@ -2,7 +2,7 @@ * Default ultrawork message optimized for Claude series models. * * Key characteristics: - * - Natural tool-like usage of explore/librarian agents (background=true) + * - Natural tool-like usage of explore/librarian agents (run_in_background=true) * - Parallel execution emphasized - fire agents and continue working * - Simple workflow: EXPLORES → GATHER → PLAN → DELEGATE */ @@ -44,9 +44,9 @@ export const ULTRAWORK_DEFAULT_MESSAGE = ` **WHEN IN DOUBT:** \`\`\` -delegate_task(subagent_type="explore", load_skills=[], prompt="Find [X] patterns in codebase", run_in_background=true) -delegate_task(subagent_type="librarian", load_skills=[], prompt="Find docs/examples for [Y]", run_in_background=true) -delegate_task(subagent_type="oracle", load_skills=[], prompt="Review my approach: [describe plan]", run_in_background=false) +task(subagent_type="explore", load_skills=[], prompt="Find [X] patterns in codebase", run_in_background=true) +task(subagent_type="librarian", load_skills=[], prompt="Find docs/examples for [Y]", run_in_background=true) +task(subagent_type="oracle", load_skills=[], prompt="Review my approach: [describe plan]", run_in_background=false) \`\`\` **ONLY AFTER YOU HAVE:** @@ -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 | \`\`\` -delegate_task(subagent_type="plan", prompt="") +task(subagent_type="plan", prompt="") \`\`\` **WHY PLAN AGENT IS MANDATORY:** @@ -119,9 +119,9 @@ delegate_task(subagent_type="plan", prompt="") | Scenario | Action | |----------|--------| -| Plan agent asks clarifying questions | \`delegate_task(session_id="{returned_session_id}", prompt="")\` | -| Need to refine the plan | \`delegate_task(session_id="{returned_session_id}", prompt="Please adjust: ")\` | -| Plan needs more detail | \`delegate_task(session_id="{returned_session_id}", prompt="Add more detail to Task N")\` | +| Plan agent asks clarifying questions | \`task(session_id="{returned_session_id}", prompt="")\` | +| Need to refine the plan | \`task(session_id="{returned_session_id}", prompt="Please adjust: ")\` | +| Plan needs more detail | \`task(session_id="{returned_session_id}", prompt="Add more detail to Task N")\` | **WHY SESSION_ID IS CRITICAL:** - Plan agent retains FULL conversation context @@ -131,10 +131,10 @@ delegate_task(subagent_type="plan", prompt="") \`\`\` // WRONG: Starting fresh loses all context -delegate_task(subagent_type="plan", prompt="Here's more info...") +task(subagent_type="plan", prompt="Here's more info...") // CORRECT: Resume preserves everything -delegate_task(session_id="ses_abc123", prompt="Here's my answer to your question: ...") +task(session_id="ses_abc123", prompt="Here's my answer to your question: ...") \`\`\` **FAILURE TO CALL PLAN AGENT = INCOMPLETE WORK.** @@ -147,23 +147,23 @@ delegate_task(session_id="ses_abc123", prompt="Here's my answer to your question | Task Type | Action | Why | |-----------|--------|-----| -| Codebase exploration | delegate_task(subagent_type="explore", run_in_background=true) | Parallel, context-efficient | -| Documentation lookup | delegate_task(subagent_type="librarian", run_in_background=true) | Specialized knowledge | -| Planning | delegate_task(subagent_type="plan") | Parallel task graph + structured TODO list | -| Hard problem (conventional) | delegate_task(subagent_type="oracle") | Architecture, debugging, complex logic | -| Hard problem (non-conventional) | delegate_task(category="artistry", load_skills=[...]) | Different approach needed | -| Implementation | delegate_task(category="...", load_skills=[...]) | Domain-optimized models | +| Codebase exploration | task(subagent_type="explore", run_in_background=true) | Parallel, context-efficient | +| Documentation lookup | task(subagent_type="librarian", run_in_background=true) | Specialized knowledge | +| Planning | task(subagent_type="plan") | Parallel task graph + structured TODO list | +| Hard problem (conventional) | task(subagent_type="oracle") | Architecture, debugging, complex logic | +| Hard problem (non-conventional) | task(category="artistry", load_skills=[...]) | Different approach needed | +| Implementation | task(category="...", load_skills=[...]) | Domain-optimized models | **CATEGORY + SKILL DELEGATION:** \`\`\` // Frontend work -delegate_task(category="visual-engineering", load_skills=["frontend-ui-ux"]) +task(category="visual-engineering", load_skills=["frontend-ui-ux"]) // Complex logic -delegate_task(category="ultrabrain", load_skills=["typescript-programmer"]) +task(category="ultrabrain", load_skills=["typescript-programmer"]) // Quick fixes -delegate_task(category="quick", load_skills=["git-master"]) +task(category="quick", load_skills=["git-master"]) \`\`\` **YOU SHOULD ONLY DO IT YOURSELF WHEN:** @@ -177,14 +177,14 @@ delegate_task(category="quick", load_skills=["git-master"]) ## EXECUTION RULES - **TODO**: Track EVERY step. Mark complete IMMEDIATELY after each. -- **PARALLEL**: Fire independent agent calls simultaneously via delegate_task(background=true) - NEVER wait sequentially. -- **BACKGROUND FIRST**: Use delegate_task for exploration/research agents (10+ concurrent if needed). +- **PARALLEL**: Fire independent agent calls simultaneously via task(run_in_background=true) - NEVER wait sequentially. +- **BACKGROUND FIRST**: Use task for exploration/research agents (10+ concurrent if needed). - **VERIFY**: Re-read request after completion. Check ALL requirements met before reporting done. - **DELEGATE**: Don't do everything yourself - orchestrate specialized agents for their strengths. ## WORKFLOW 1. Analyze the request and identify required capabilities -2. Spawn exploration/librarian agents via delegate_task(background=true) in PARALLEL (10+ if needed) +2. Spawn exploration/librarian agents via task(run_in_background=true) in PARALLEL (10+ if needed) 3. Use Plan agent with gathered context to create detailed work breakdown 4. Execute with continuous verification against original requirements diff --git a/src/hooks/keyword-detector/ultrawork/gpt5.2.ts b/src/hooks/keyword-detector/ultrawork/gpt5.2.ts index a75040777..9309f4294 100644 --- a/src/hooks/keyword-detector/ultrawork/gpt5.2.ts +++ b/src/hooks/keyword-detector/ultrawork/gpt5.2.ts @@ -73,11 +73,11 @@ Use these when they provide clear value based on the decision framework above: | Resource | When to Use | How to Use | |----------|-------------|------------| -| explore agent | Need codebase patterns you don't have | \`delegate_task(subagent_type="explore", run_in_background=true, ...)\` | -| librarian agent | External library docs, OSS examples | \`delegate_task(subagent_type="librarian", run_in_background=true, ...)\` | -| oracle agent | Stuck on architecture/debugging after 2+ attempts | \`delegate_task(subagent_type="oracle", ...)\` | -| plan agent | Complex multi-step with dependencies (5+ steps) | \`delegate_task(subagent_type="plan", ...)\` | -| delegate_task category | Specialized work matching a category | \`delegate_task(category="...", load_skills=[...])\` | +| explore agent | Need codebase patterns you don't have | \`task(subagent_type="explore", run_in_background=true, ...)\` | +| librarian agent | External library docs, OSS examples | \`task(subagent_type="librarian", run_in_background=true, ...)\` | +| oracle agent | Stuck on architecture/debugging after 2+ attempts | \`task(subagent_type="oracle", ...)\` | +| plan agent | Complex multi-step with dependencies (5+ steps) | \`task(subagent_type="plan", ...)\` | +| task category | Specialized work matching a category | \`task(category="...", load_skills=[...])\` | - Prefer tools over internal knowledge for fresh or user-specific data @@ -97,8 +97,8 @@ Use these when they provide clear value based on the decision framework above: **ALWAYS run both tracks in parallel:** \`\`\` // Fire background agents for deep exploration -delegate_task(subagent_type="explore", load_skills=[], prompt="Find X patterns...", run_in_background=true) -delegate_task(subagent_type="librarian", load_skills=[], prompt="Find docs for Y...", run_in_background=true) +task(subagent_type="explore", load_skills=[], prompt="Find X patterns...", run_in_background=true) +task(subagent_type="librarian", load_skills=[], prompt="Find docs for Y...", run_in_background=true) // WHILE THEY RUN - use direct tools for immediate context grep(pattern="relevant_pattern", path="src/") diff --git a/src/hooks/keyword-detector/ultrawork/planner.ts b/src/hooks/keyword-detector/ultrawork/planner.ts index aab3dcb05..426926f48 100644 --- a/src/hooks/keyword-detector/ultrawork/planner.ts +++ b/src/hooks/keyword-detector/ultrawork/planner.ts @@ -14,7 +14,7 @@ You ARE the planner. You ARE NOT an implementer. You DO NOT write code. You DO N | Write/Edit | \`.sisyphus/**/*.md\` ONLY | Everything else | | Read | All files | - | | Bash | Research commands only | Implementation commands | -| delegate_task | explore, librarian | - | +| task | explore, librarian | - | **IF YOU TRY TO WRITE/EDIT OUTSIDE \`.sisyphus/\`:** - System will BLOCK your action @@ -38,9 +38,9 @@ You ARE the planner. Your job: create bulletproof work plans. ### Research Protocol 1. **Fire parallel background agents** for comprehensive context: \`\`\` - delegate_task(agent="explore", prompt="Find existing patterns for [topic] in codebase", background=true) - delegate_task(agent="explore", prompt="Find test infrastructure and conventions", background=true) - delegate_task(agent="librarian", prompt="Find official docs and best practices for [technology]", background=true) + task(agent="explore", prompt="Find existing patterns for [topic] in codebase", background=true) + task(agent="explore", prompt="Find test infrastructure and conventions", background=true) + task(agent="librarian", prompt="Find official docs and best practices for [technology]", background=true) \`\`\` 2. **Wait for results** before planning - rushed plans fail 3. **Synthesize findings** into informed requirements @@ -117,9 +117,9 @@ Each TODO item MUST include: | Wave | Tasks | Dispatch Command | |------|-------|------------------| -| 1 | 1, 4 | \`delegate_task(category="...", load_skills=[...], run_in_background=false)\` × 2 | -| 2 | 2, 3, 5 | \`delegate_task(...)\` × 3 after Wave 1 completes | -| 3 | 6 | \`delegate_task(...)\` final integration | +| 1 | 1, 4 | \`task(category="...", load_skills=[...], run_in_background=false)\` × 2 | +| 2 | 2, 3, 5 | \`task(...)\` × 3 after Wave 1 completes | +| 3 | 6 | \`task(...)\` final integration | **WHY PARALLEL TASK GRAPH IS MANDATORY:** - Orchestrator (Sisyphus) executes tasks in parallel waves diff --git a/src/hooks/prometheus-md-only/constants.ts b/src/hooks/prometheus-md-only/constants.ts index 2656b5d86..447975a25 100644 --- a/src/hooks/prometheus-md-only/constants.ts +++ b/src/hooks/prometheus-md-only/constants.ts @@ -51,14 +51,14 @@ ${createSystemDirective(SystemDirectiveTypes.PROMETHEUS_READ_ONLY)} │ │ - Record decisions to .sisyphus/drafts/ │ ├──────┼──────────────────────────────────────────────────────────────┤ │ 2 │ METIS CONSULTATION: Pre-generation gap analysis │ -│ │ - delegate_task(agent="Metis (Plan Consultant)", ...) │ +│ │ - task(agent="Metis (Plan Consultant)", ...) │ │ │ - Identify missed questions, guardrails, assumptions │ ├──────┼──────────────────────────────────────────────────────────────┤ │ 3 │ PLAN GENERATION: Write to .sisyphus/plans/*.md │ │ │ <- YOU ARE HERE │ ├──────┼──────────────────────────────────────────────────────────────┤ │ 4 │ MOMUS REVIEW (if high accuracy requested) │ -│ │ - delegate_task(agent="Momus (Plan Reviewer)", ...) │ +│ │ - task(agent="Momus (Plan Reviewer)", ...) │ │ │ - Loop until OKAY verdict │ ├──────┼──────────────────────────────────────────────────────────────┤ │ 5 │ SUMMARY: Present to user │ diff --git a/src/hooks/prometheus-md-only/index.test.ts b/src/hooks/prometheus-md-only/index.test.ts index 07dff9bf5..e576eecf6 100644 --- a/src/hooks/prometheus-md-only/index.test.ts +++ b/src/hooks/prometheus-md-only/index.test.ts @@ -227,11 +227,11 @@ describe("prometheus-md-only", () => { ).resolves.toBeUndefined() }) - test("should inject read-only warning when Prometheus calls delegate_task", async () => { + test("should inject read-only warning when Prometheus calls task", async () => { // given const hook = createPrometheusMdOnlyHook(createMockPluginInput()) const input = { - tool: "delegate_task", + tool: "task", sessionID: TEST_SESSION_ID, callID: "call-1", } @@ -289,7 +289,7 @@ describe("prometheus-md-only", () => { // given const hook = createPrometheusMdOnlyHook(createMockPluginInput()) const input = { - tool: "delegate_task", + tool: "task", sessionID: TEST_SESSION_ID, callID: "call-1", } @@ -330,11 +330,11 @@ describe("prometheus-md-only", () => { ).resolves.toBeUndefined() }) - test("should not inject warning for non-Prometheus agents calling delegate_task", async () => { + test("should not inject warning for non-Prometheus agents calling task", async () => { // given const hook = createPrometheusMdOnlyHook(createMockPluginInput()) const input = { - tool: "delegate_task", + tool: "task", sessionID: TEST_SESSION_ID, callID: "call-1", } diff --git a/src/hooks/prometheus-md-only/index.ts b/src/hooks/prometheus-md-only/index.ts index 7c9131f98..410cc4a0b 100644 --- a/src/hooks/prometheus-md-only/index.ts +++ b/src/hooks/prometheus-md-only/index.ts @@ -63,7 +63,7 @@ function getMessageDir(sessionID: string): string | null { return null } -const TASK_TOOLS = ["delegate_task", "task", "call_omo_agent"] +const TASK_TOOLS = ["task", "call_omo_agent"] function getAgentFromMessageFiles(sessionID: string): string | undefined { const messageDir = getMessageDir(sessionID) diff --git a/src/hooks/sisyphus-junior-notepad/index.ts b/src/hooks/sisyphus-junior-notepad/index.ts index 588df5685..630de9070 100644 --- a/src/hooks/sisyphus-junior-notepad/index.ts +++ b/src/hooks/sisyphus-junior-notepad/index.ts @@ -12,8 +12,8 @@ export function createSisyphusJuniorNotepadHook(ctx: PluginInput) { input: { tool: string; sessionID: string; callID: string }, output: { args: Record; message?: string } ): Promise => { - // 1. Check if tool is delegate_task - if (input.tool !== "delegate_task") { + // 1. Check if tool is task + if (input.tool !== "task") { return } @@ -37,7 +37,7 @@ export function createSisyphusJuniorNotepadHook(ctx: PluginInput) { output.args.prompt = NOTEPAD_DIRECTIVE + prompt // 6. Log injection - log(`[${HOOK_NAME}] Injected notepad directive to delegate_task`, { + log(`[${HOOK_NAME}] Injected notepad directive to task`, { sessionID: input.sessionID, }) }, diff --git a/src/hooks/task-resume-info/index.ts b/src/hooks/task-resume-info/index.ts index f1194c088..650624c77 100644 --- a/src/hooks/task-resume-info/index.ts +++ b/src/hooks/task-resume-info/index.ts @@ -1,4 +1,4 @@ -const TARGET_TOOLS = ["task", "Task", "task_tool", "call_omo_agent", "delegate_task"] +const TARGET_TOOLS = ["task", "Task", "task_tool", "call_omo_agent"] const SESSION_ID_PATTERNS = [ /Session ID: (ses_[a-zA-Z0-9_-]+)/, @@ -27,7 +27,7 @@ export function createTaskResumeInfoHook() { const sessionId = extractSessionId(output.output) if (!sessionId) return - output.output = output.output.trimEnd() + `\n\nto continue: delegate_task(session_id="${sessionId}", prompt="...")` + output.output = output.output.trimEnd() + `\n\nto continue: task(session_id="${sessionId}", prompt="...")` } return { diff --git a/src/hooks/tasks-todowrite-disabler/constants.ts b/src/hooks/tasks-todowrite-disabler/constants.ts index 8e5aa2dca..03e5ef50b 100644 --- a/src/hooks/tasks-todowrite-disabler/constants.ts +++ b/src/hooks/tasks-todowrite-disabler/constants.ts @@ -16,7 +16,7 @@ export const REPLACEMENT_MESSAGE = `TodoRead/TodoWrite are DISABLED because expe 3. DO THE WORK 4. TaskUpdate({ id: "T-xxx", status: "completed" }) -CRITICAL: 1 task = 1 delegate_task. Fire independent tasks concurrently. +CRITICAL: 1 task = 1 task. Fire independent tasks concurrently. **STOP! DO NOT START WORKING DIRECTLY - NO MATTER HOW SMALL THE TASK!** Even if the task seems trivial (1 line fix, simple edit, quick change), you MUST: diff --git a/src/index.ts b/src/index.ts index 0dfeb291e..f1750aeed 100644 --- a/src/index.ts +++ b/src/index.ts @@ -111,6 +111,7 @@ import { filterDisabledTools } from "./shared/disabled-tools"; import { loadPluginConfig } from "./plugin-config"; import { createModelCacheState } from "./plugin-state"; import { createConfigHandler } from "./plugin-handlers"; +import { consumeToolMetadata } from "./features/tool-metadata-store"; const OhMyOpenCodePlugin: Plugin = async (ctx) => { log("[OhMyOpenCodePlugin] ENTRY - plugin loading", { @@ -533,7 +534,7 @@ const OhMyOpenCodePlugin: Plugin = async (ctx) => { ...backgroundTools, call_omo_agent: callOmoAgent, ...(lookAt ? { look_at: lookAt } : {}), - delegate_task: delegateTask, + task: delegateTask, skill: skillTool, skill_mcp: skillMcpTool, slashcommand: slashcommandTool, @@ -787,16 +788,11 @@ const OhMyOpenCodePlugin: Plugin = async (ctx) => { if (input.tool === "task") { const args = output.args as Record; - const subagentType = args.subagent_type as string; - const isExploreOrLibrarian = ["explore", "librarian"].some( - (name) => name.toLowerCase() === (subagentType ?? "").toLowerCase(), - ); - - args.tools = { - ...(args.tools as Record | undefined), - delegate_task: false, - ...(isExploreOrLibrarian ? { call_omo_agent: false } : {}), - }; + const category = typeof args.category === "string" ? args.category : undefined; + const subagentType = typeof args.subagent_type === "string" ? args.subagent_type : undefined; + if (category && !subagentType) { + args.subagent_type = "sisyphus-junior"; + } } if (ralphLoop && input.tool === "slashcommand") { @@ -872,6 +868,19 @@ const OhMyOpenCodePlugin: Plugin = async (ctx) => { if (!output) { return; } + + // Restore metadata that fromPlugin() overwrites with { truncated, outputPath }. + // This must run FIRST, before any hook reads output.metadata. + const stored = consumeToolMetadata(input.sessionID, input.callID) + if (stored) { + if (stored.title) { + output.title = stored.title + } + if (stored.metadata) { + output.metadata = { ...output.metadata, ...stored.metadata } + } + } + await claudeCodeHooks["tool.execute.after"](input, output); await toolOutputTruncator?.["tool.execute.after"](input, output); await preemptiveCompaction?.["tool.execute.after"](input, output); diff --git a/src/plugin-handlers/config-handler.test.ts b/src/plugin-handlers/config-handler.test.ts index c53f11da7..4839e5531 100644 --- a/src/plugin-handlers/config-handler.test.ts +++ b/src/plugin-handlers/config-handler.test.ts @@ -274,7 +274,7 @@ describe("Plan agent demote behavior", () => { expect(agents.plan.prompt).toBe("original plan prompt") }) - test("prometheus should have mode 'all' to be callable via delegate_task", async () => { + test("prometheus should have mode 'all' to be callable via task", async () => { // given const pluginConfig: OhMyOpenCodeConfig = { sisyphus_agent: { @@ -305,7 +305,7 @@ describe("Plan agent demote behavior", () => { }) describe("Agent permission defaults", () => { - test("hephaestus should allow delegate_task", async () => { + test("hephaestus should allow task", async () => { // #given const createBuiltinAgentsMock = agents.createBuiltinAgents as unknown as { mockResolvedValue: (value: Record) => void @@ -335,7 +335,7 @@ describe("Agent permission defaults", () => { // #then const agentConfig = config.agent as Record }> expect(agentConfig.hephaestus).toBeDefined() - expect(agentConfig.hephaestus.permission?.delegate_task).toBe("allow") + expect(agentConfig.hephaestus.permission?.task).toBe("allow") }) }) diff --git a/src/plugin-handlers/config-handler.ts b/src/plugin-handlers/config-handler.ts index f5c41bb28..36191fafa 100644 --- a/src/plugin-handlers/config-handler.ts +++ b/src/plugin-handlers/config-handler.ts @@ -419,30 +419,30 @@ export function createConfigHandler(deps: ConfigHandlerDeps) { } if (agentResult["atlas"]) { const agent = agentResult["atlas"] as AgentWithPermission; - agent.permission = { ...agent.permission, task: "deny", call_omo_agent: "deny", delegate_task: "allow", "task_*": "allow", teammate: "allow" }; + agent.permission = { ...agent.permission, task: "allow", call_omo_agent: "deny", "task_*": "allow", teammate: "allow" }; } if (agentResult.sisyphus) { const agent = agentResult.sisyphus as AgentWithPermission; - agent.permission = { ...agent.permission, call_omo_agent: "deny", delegate_task: "allow", question: questionPermission, "task_*": "allow", teammate: "allow" }; + agent.permission = { ...agent.permission, call_omo_agent: "deny", task: "allow", question: questionPermission, "task_*": "allow", teammate: "allow" }; } if (agentResult.hephaestus) { const agent = agentResult.hephaestus as AgentWithPermission; - agent.permission = { ...agent.permission, call_omo_agent: "deny", delegate_task: "allow", question: questionPermission }; + agent.permission = { ...agent.permission, call_omo_agent: "deny", task: "allow", question: questionPermission }; } if (agentResult["prometheus"]) { const agent = agentResult["prometheus"] as AgentWithPermission; - agent.permission = { ...agent.permission, call_omo_agent: "deny", delegate_task: "allow", question: questionPermission, "task_*": "allow", teammate: "allow" }; + agent.permission = { ...agent.permission, call_omo_agent: "deny", task: "allow", question: questionPermission, "task_*": "allow", teammate: "allow" }; } if (agentResult["sisyphus-junior"]) { const agent = agentResult["sisyphus-junior"] as AgentWithPermission; - agent.permission = { ...agent.permission, delegate_task: "allow", "task_*": "allow", teammate: "allow" }; + agent.permission = { ...agent.permission, task: "allow", "task_*": "allow", teammate: "allow" }; } config.permission = { ...(config.permission as Record), webfetch: "allow", external_directory: "allow", - delegate_task: "deny", + task: "deny", }; const mcpResult = (pluginConfig.claude_code?.mcp ?? true) diff --git a/src/shared/agent-tool-restrictions.ts b/src/shared/agent-tool-restrictions.ts index 0a34d8427..865251d6f 100644 --- a/src/shared/agent-tool-restrictions.ts +++ b/src/shared/agent-tool-restrictions.ts @@ -8,7 +8,6 @@ const EXPLORATION_AGENT_DENYLIST: Record = { write: false, edit: false, task: false, - delegate_task: false, call_omo_agent: false, } @@ -21,7 +20,6 @@ const AGENT_RESTRICTIONS: Record> = { write: false, edit: false, task: false, - delegate_task: false, call_omo_agent: false, }, @@ -29,14 +27,12 @@ const AGENT_RESTRICTIONS: Record> = { write: false, edit: false, task: false, - delegate_task: false, }, momus: { write: false, edit: false, task: false, - delegate_task: false, }, "multimodal-looker": { @@ -45,7 +41,6 @@ const AGENT_RESTRICTIONS: Record> = { "sisyphus-junior": { task: false, - delegate_task: false, }, } diff --git a/src/shared/permission-compat.test.ts b/src/shared/permission-compat.test.ts index 099cff29d..9fbfbb23b 100644 --- a/src/shared/permission-compat.test.ts +++ b/src/shared/permission-compat.test.ts @@ -130,5 +130,49 @@ describe("permission-compat", () => { // then returns unchanged expect(result).toEqual(config) }) + + test("migrates delegate_task permission to task", () => { + //#given config with delegate_task permission + const config = { + model: "test", + permission: { delegate_task: "allow" as const, write: "deny" as const }, + } + + //#when migrating + const result = migrateAgentConfig(config) + + //#then delegate_task is renamed to task + const perm = result.permission as Record + expect(perm["task"]).toBe("allow") + expect(perm["delegate_task"]).toBeUndefined() + expect(perm["write"]).toBe("deny") + }) + + test("does not overwrite existing task permission with delegate_task", () => { + //#given config with both task and delegate_task permissions + const config = { + permission: { delegate_task: "allow" as const, task: "deny" as const }, + } + + //#when migrating + const result = migrateAgentConfig(config) + + //#then existing task permission is preserved + const perm = result.permission as Record + expect(perm["task"]).toBe("deny") + expect(perm["delegate_task"]).toBe("allow") + }) + + test("does not mutate the original config permission object", () => { + //#given config with delegate_task permission + const originalPerm = { delegate_task: "allow" as const } + const config = { permission: originalPerm } + + //#when migrating + migrateAgentConfig(config) + + //#then original permission object is not mutated + expect(originalPerm).toEqual({ delegate_task: "allow" }) + }) }) }) diff --git a/src/shared/permission-compat.ts b/src/shared/permission-compat.ts index f582fd553..fd8253b77 100644 --- a/src/shared/permission-compat.ts +++ b/src/shared/permission-compat.ts @@ -73,5 +73,14 @@ export function migrateAgentConfig( delete result.tools } + if (result.permission && typeof result.permission === "object") { + const perm = { ...(result.permission as Record) } + if ("delegate_task" in perm && !("task" in perm)) { + perm["task"] = perm["delegate_task"] + delete perm["delegate_task"] + result.permission = perm + } + } + return result } diff --git a/src/tools/AGENTS.md b/src/tools/AGENTS.md index 5313b9389..e6319f353 100644 --- a/src/tools/AGENTS.md +++ b/src/tools/AGENTS.md @@ -39,7 +39,7 @@ tools/ | Search | ast_grep_search, ast_grep_replace, grep, glob | Direct | | Session | session_list, session_read, session_search, session_info | Direct | | Task | task_create, task_get, task_list, task_update | Factory | -| Agent | delegate_task, call_omo_agent | Factory | +| Agent | task, call_omo_agent | Factory | | Background | background_output, background_cancel | Factory | | System | interactive_bash, look_at | Mixed | | Skill | skill, skill_mcp, slashcommand | Factory | diff --git a/src/tools/ast-grep/tools.ts b/src/tools/ast-grep/tools.ts index ec3de1ee9..11de9184a 100644 --- a/src/tools/ast-grep/tools.ts +++ b/src/tools/ast-grep/tools.ts @@ -4,9 +4,11 @@ import { runSg } from "./cli" import { formatSearchResult, formatReplaceResult } from "./utils" import type { CliLanguage } from "./types" -function showOutputToUser(context: unknown, output: string): void { - const ctx = context as { metadata?: (input: { metadata: { output: string } }) => void } - ctx.metadata?.({ metadata: { output } }) +async function showOutputToUser(context: unknown, output: string): Promise { + const ctx = context as { + metadata?: (input: { metadata: { output: string } }) => void | Promise + } + await ctx.metadata?.({ metadata: { output } }) } function getEmptyResultHint(pattern: string, lang: CliLanguage): string | null { @@ -65,11 +67,11 @@ export const ast_grep_search: ToolDefinition = tool({ } } - showOutputToUser(context, output) + await showOutputToUser(context, output) return output } catch (e) { const output = `Error: ${e instanceof Error ? e.message : String(e)}` - showOutputToUser(context, output) + await showOutputToUser(context, output) return output } }, @@ -99,14 +101,13 @@ export const ast_grep_replace: ToolDefinition = tool({ updateAll: args.dryRun === false, }) const output = formatReplaceResult(result, args.dryRun !== false) - showOutputToUser(context, output) + await showOutputToUser(context, output) return output } catch (e) { const output = `Error: ${e instanceof Error ? e.message : String(e)}` - showOutputToUser(context, output) + await showOutputToUser(context, output) return output } }, }) - diff --git a/src/tools/background-task/tools.ts b/src/tools/background-task/tools.ts index 8d25282d5..ec12128cb 100644 --- a/src/tools/background-task/tools.ts +++ b/src/tools/background-task/tools.ts @@ -8,6 +8,7 @@ import { findNearestMessageWithFields, findFirstMessageWithAgent, MESSAGE_STORAG import { getSessionAgent } from "../../features/claude-code-session-state" import { log } from "../../shared/logger" import { consumeNewMessages } from "../../shared/session-cursor" +import { storeToolMetadata } from "../../features/tool-metadata-store" type BackgroundOutputMessage = { info?: { role?: string; time?: string | { created?: number }; agent?: string } @@ -140,15 +141,37 @@ export function createBackgroundTask(manager: BackgroundManager): ToolDefinition parentAgent, }) - ctx.metadata?.({ + const WAIT_FOR_SESSION_INTERVAL_MS = 50 + const WAIT_FOR_SESSION_TIMEOUT_MS = 30000 + const waitStart = Date.now() + let sessionId = task.sessionID + while (!sessionId && Date.now() - waitStart < WAIT_FOR_SESSION_TIMEOUT_MS) { + if (ctx.abort?.aborted) { + await manager.cancelTask(task.id) + return `Task aborted and cancelled while waiting for session to start.\n\nTask ID: ${task.id}` + } + await delay(WAIT_FOR_SESSION_INTERVAL_MS) + const updated = manager.getTask(task.id) + if (!updated || updated.status === "error") { + return `Task ${!updated ? "was deleted" : `entered error state`}.\n\nTask ID: ${task.id}` + } + sessionId = updated?.sessionID + } + + const bgMeta = { title: args.description, - metadata: { sessionId: task.sessionID }, - }) + metadata: { sessionId: sessionId ?? "pending" } as Record, + } + await ctx.metadata?.(bgMeta) + const callID = (ctx as any).callID as string | undefined + if (callID) { + storeToolMetadata(ctx.sessionID, callID, bgMeta) + } return `Background task launched successfully. Task ID: ${task.id} -Session ID: ${task.sessionID} +Session ID: ${sessionId ?? "pending"} Description: ${task.description} Agent: ${task.agent} Status: ${task.status} @@ -663,7 +686,7 @@ export function createBackgroundCancel(manager: BackgroundManager, client: Backg To continue a cancelled task, use: \`\`\` -delegate_task(session_id="", prompt="Continue: ") +task(session_id="", prompt="Continue: ") \`\`\` Continuable sessions: diff --git a/src/tools/call-omo-agent/tools.ts b/src/tools/call-omo-agent/tools.ts index bb00f1a28..f18a6eec9 100644 --- a/src/tools/call-omo-agent/tools.ts +++ b/src/tools/call-omo-agent/tools.ts @@ -10,6 +10,7 @@ import { findFirstMessageWithAgent, findNearestMessageWithFields, MESSAGE_STORAG import { getSessionAgent } from "../../features/claude-code-session-state" function getMessageDir(sessionID: string): string | null { + if (!sessionID.startsWith("ses_")) return null if (!existsSync(MESSAGE_STORAGE)) return null const directPath = join(MESSAGE_STORAGE, sessionID) @@ -110,15 +111,31 @@ async function executeBackground( parentAgent, }) - toolContext.metadata?.({ + const WAIT_FOR_SESSION_INTERVAL_MS = 50 + const WAIT_FOR_SESSION_TIMEOUT_MS = 30000 + const waitStart = Date.now() + let sessionId = task.sessionID + while (!sessionId && Date.now() - waitStart < WAIT_FOR_SESSION_TIMEOUT_MS) { + if (toolContext.abort?.aborted) { + return `Task aborted while waiting for session to start.\n\nTask ID: ${task.id}` + } + const updated = manager.getTask(task.id) + if (updated?.status === "error" || updated?.status === "cancelled") { + return `Task failed to start (status: ${updated.status}).\n\nTask ID: ${task.id}` + } + await new Promise(resolve => setTimeout(resolve, WAIT_FOR_SESSION_INTERVAL_MS)) + sessionId = manager.getTask(task.id)?.sessionID + } + + await toolContext.metadata?.({ title: args.description, - metadata: { sessionId: task.sessionID }, + metadata: { sessionId: sessionId ?? "pending" }, }) return `Background agent task launched successfully. Task ID: ${task.id} -Session ID: ${task.sessionID} +Session ID: ${sessionId ?? "pending"} Description: ${task.description} Agent: ${task.agent} (subagent) Status: ${task.status} @@ -194,7 +211,7 @@ Original error: ${createResult.error}` log(`[call_omo_agent] Created session: ${sessionID}`) } - toolContext.metadata?.({ + await toolContext.metadata?.({ title: args.description, metadata: { sessionId: sessionID }, }) @@ -210,7 +227,6 @@ Original error: ${createResult.error}` tools: { ...getAgentToolRestrictions(args.subagent_type), task: false, - delegate_task: false, }, parts: [{ type: "text", text: args.prompt }], }, diff --git a/src/tools/delegate-task/constants.ts b/src/tools/delegate-task/constants.ts index b97ff6b20..603a63c67 100644 --- a/src/tools/delegate-task/constants.ts +++ b/src/tools/delegate-task/constants.ts @@ -459,13 +459,13 @@ YOU MUST END YOUR RESPONSE WITH THIS SECTION. 1. **Wave 1**: Fire these tasks IN PARALLEL (no dependencies) \`\`\` - delegate_task(category="...", load_skills=[...], run_in_background=false, prompt="Task 1: ...") - delegate_task(category="...", load_skills=[...], run_in_background=false, prompt="Task N: ...") + task(category="...", load_skills=[...], run_in_background=false, prompt="Task 1: ...") + task(category="...", load_skills=[...], run_in_background=false, prompt="Task N: ...") \`\`\` 2. **Wave 2**: After Wave 1 completes, fire next wave IN PARALLEL \`\`\` - delegate_task(category="...", load_skills=[...], run_in_background=false, prompt="Task 2: ...") + task(category="...", load_skills=[...], run_in_background=false, prompt="Task 2: ...") \`\`\` 3. Continue until all waves complete @@ -476,7 +476,7 @@ YOU MUST END YOUR RESPONSE WITH THIS SECTION. WHY THIS FORMAT IS MANDATORY: - Caller can directly copy TODO items - Wave grouping enables parallel execution -- Each task has clear delegate_task parameters +- Each task has clear task parameters - QA criteria ensure verifiable completion diff --git a/src/tools/delegate-task/executor.ts b/src/tools/delegate-task/executor.ts index dd9651ef9..bc6fd1708 100644 --- a/src/tools/delegate-task/executor.ts +++ b/src/tools/delegate-task/executor.ts @@ -16,6 +16,7 @@ import { log, getAgentToolRestrictions, resolveModelPipeline, promptWithModelSug import { fetchAvailableModels, isModelAvailable } from "../../shared/model-availability" import { readConnectedProvidersCache } from "../../shared/connected-providers-cache" import { CATEGORY_MODEL_REQUIREMENTS } from "../../shared/model-requirements" +import { storeToolMetadata } from "../../features/tool-metadata-store" const SISYPHUS_JUNIOR_AGENT = "sisyphus-junior" @@ -67,7 +68,7 @@ export function resolveParentContext(ctx: ToolContextWithMetadata): ParentContex const sessionAgent = getSessionAgent(ctx.sessionID) const parentAgent = ctx.agent ?? sessionAgent ?? firstMessageAgent ?? prevMessage?.agent - log("[delegate_task] parentAgent resolution", { + log("[task] parentAgent resolution", { sessionID: ctx.sessionID, messageDir, ctxAgent: ctx.agent, @@ -111,7 +112,7 @@ export async function executeBackgroundContinuation( parentAgent: parentContext.agent, }) - ctx.metadata?.({ + const bgContMeta = { title: `Continue: ${task.description}`, metadata: { prompt: args.prompt, @@ -122,7 +123,11 @@ export async function executeBackgroundContinuation( sessionId: task.sessionID, command: args.command, }, - }) + } + await ctx.metadata?.(bgContMeta) + if (ctx.callID) { + storeToolMetadata(ctx.sessionID, ctx.callID, bgContMeta) + } return `Background task continued. @@ -165,7 +170,7 @@ export async function executeSyncContinuation( }) } - ctx.metadata?.({ + const syncContMeta = { title: `Continue: ${args.description}`, metadata: { prompt: args.prompt, @@ -176,7 +181,11 @@ export async function executeSyncContinuation( sync: true, command: args.command, }, - }) + } + await ctx.metadata?.(syncContMeta) + if (ctx.callID) { + storeToolMetadata(ctx.sessionID, ctx.callID, syncContMeta) + } try { let resumeAgent: string | undefined @@ -207,13 +216,12 @@ export async function executeSyncContinuation( body: { ...(resumeAgent !== undefined ? { agent: resumeAgent } : {}), ...(resumeModel !== undefined ? { model: resumeModel } : {}), - tools: { - ...(resumeAgent ? getAgentToolRestrictions(resumeAgent) : {}), - task: false, - delegate_task: false, - call_omo_agent: true, - question: false, - }, + tools: { + ...(resumeAgent ? getAgentToolRestrictions(resumeAgent) : {}), + task: false, + call_omo_agent: true, + question: false, + }, parts: [{ type: "text", text: args.prompt }], }, }) @@ -316,17 +324,17 @@ export async function executeUnstableAgentTask( category: args.category, }) - const WAIT_FOR_SESSION_INTERVAL_MS = 100 - const WAIT_FOR_SESSION_TIMEOUT_MS = 30000 + const timing = getTimingConfig() const waitStart = Date.now() - while (!task.sessionID && Date.now() - waitStart < WAIT_FOR_SESSION_TIMEOUT_MS) { + let sessionID = task.sessionID + while (!sessionID && Date.now() - waitStart < timing.WAIT_FOR_SESSION_TIMEOUT_MS) { if (ctx.abort?.aborted) { return `Task aborted while waiting for session to start.\n\nTask ID: ${task.id}` } - await new Promise(resolve => setTimeout(resolve, WAIT_FOR_SESSION_INTERVAL_MS)) + await new Promise(resolve => setTimeout(resolve, timing.WAIT_FOR_SESSION_INTERVAL_MS)) + const updated = manager.getTask(task.id) + sessionID = updated?.sessionID } - - const sessionID = task.sessionID if (!sessionID) { return formatDetailedError(new Error(`Task failed to start within timeout (30s). Task ID: ${task.id}, Status: ${task.status}`), { operation: "Launch monitored background task", @@ -336,7 +344,7 @@ export async function executeUnstableAgentTask( }) } - ctx.metadata?.({ + const bgTaskMeta = { title: args.description, metadata: { prompt: args.prompt, @@ -348,7 +356,11 @@ export async function executeUnstableAgentTask( sessionId: sessionID, command: args.command, }, - }) + } + await ctx.metadata?.(bgTaskMeta) + if (ctx.callID) { + storeToolMetadata(ctx.sessionID, ctx.callID, bgTaskMeta) + } const startTime = new Date() const timingCfg = getTimingConfig() @@ -463,7 +475,23 @@ export async function executeBackgroundTask( category: args.category, }) - ctx.metadata?.({ + // OpenCode TUI's `Task` tool UI calculates toolcalls by looking up + // `props.metadata.sessionId` and then counting tool parts in that session. + // BackgroundManager.launch() returns immediately (pending) before the session exists, + // so we must wait briefly for the session to be created to set metadata correctly. + const timing = getTimingConfig() + const waitStart = Date.now() + let sessionId = task.sessionID + while (!sessionId && Date.now() - waitStart < timing.WAIT_FOR_SESSION_TIMEOUT_MS) { + if (ctx.abort?.aborted) { + return `Task aborted while waiting for session to start.\n\nTask ID: ${task.id}` + } + await new Promise(resolve => setTimeout(resolve, timing.WAIT_FOR_SESSION_INTERVAL_MS)) + const updated = manager.getTask(task.id) + sessionId = updated?.sessionID + } + + const unstableMeta = { title: args.description, metadata: { prompt: args.prompt, @@ -472,10 +500,14 @@ export async function executeBackgroundTask( load_skills: args.load_skills, description: args.description, run_in_background: args.run_in_background, - sessionId: task.sessionID, + sessionId: sessionId ?? "pending", command: args.command, }, - }) + } + await ctx.metadata?.(unstableMeta) + if (ctx.callID) { + storeToolMetadata(ctx.sessionID, ctx.callID, unstableMeta) + } return `Background task launched. @@ -487,7 +519,7 @@ Status: ${task.status} System notifies on completion. Use \`background_output\` with task_id="${task.id}" to check. -session_id: ${task.sessionID} +session_id: ${sessionId} ` } catch (error) { return formatDetailedError(error, { @@ -542,13 +574,13 @@ export async function executeSyncTask( subagentSessions.add(sessionID) if (onSyncSessionCreated) { - log("[delegate_task] Invoking onSyncSessionCreated callback", { sessionID, parentID: parentContext.sessionID }) + log("[task] Invoking onSyncSessionCreated callback", { sessionID, parentID: parentContext.sessionID }) await onSyncSessionCreated({ sessionID, parentID: parentContext.sessionID, title: args.description, }).catch((err) => { - log("[delegate_task] onSyncSessionCreated callback failed", { error: String(err) }) + log("[task] onSyncSessionCreated callback failed", { error: String(err) }) }) await new Promise(r => setTimeout(r, 200)) } @@ -568,7 +600,7 @@ export async function executeSyncTask( }) } - ctx.metadata?.({ + const syncTaskMeta = { title: args.description, metadata: { prompt: args.prompt, @@ -581,18 +613,21 @@ export async function executeSyncTask( sync: true, command: args.command, }, - }) + } + await ctx.metadata?.(syncTaskMeta) + if (ctx.callID) { + storeToolMetadata(ctx.sessionID, ctx.callID, syncTaskMeta) + } try { - const allowDelegateTask = isPlanAgent(agentToUse) + const allowTask = isPlanAgent(agentToUse) await promptWithModelSuggestionRetry(client, { path: { id: sessionID }, body: { agent: agentToUse, system: systemContent, tools: { - task: false, - delegate_task: allowDelegateTask, + task: allowTask, call_omo_agent: true, question: false, }, @@ -630,11 +665,11 @@ export async function executeSyncTask( let stablePolls = 0 let pollCount = 0 - log("[delegate_task] Starting poll loop", { sessionID, agentToUse }) + log("[task] Starting poll loop", { sessionID, agentToUse }) while (Date.now() - pollStart < syncTiming.MAX_POLL_TIME_MS) { if (ctx.abort?.aborted) { - log("[delegate_task] Aborted by user", { sessionID }) + log("[task] Aborted by user", { sessionID }) if (toastManager && taskId) toastManager.removeTask(taskId) return `Task aborted.\n\nSession ID: ${sessionID}` } @@ -647,7 +682,7 @@ export async function executeSyncTask( const sessionStatus = allStatuses[sessionID] if (pollCount % 10 === 0) { - log("[delegate_task] Poll status", { + log("[task] Poll status", { sessionID, pollCount, elapsed: Math.floor((Date.now() - pollStart) / 1000) + "s", @@ -675,7 +710,7 @@ export async function executeSyncTask( if (currentMsgCount === lastMsgCount) { stablePolls++ if (stablePolls >= syncTiming.STABILITY_POLLS_REQUIRED) { - log("[delegate_task] Poll complete - messages stable", { sessionID, pollCount, currentMsgCount }) + log("[task] Poll complete - messages stable", { sessionID, pollCount, currentMsgCount }) break } } else { @@ -685,7 +720,7 @@ export async function executeSyncTask( } if (Date.now() - pollStart >= syncTiming.MAX_POLL_TIME_MS) { - log("[delegate_task] Poll timeout reached", { sessionID, pollCount, lastMsgCount, stablePolls }) + log("[task] Poll timeout reached", { sessionID, pollCount, lastMsgCount, stablePolls }) } const messagesResult = await client.session.messages({ @@ -928,7 +963,7 @@ Sisyphus-Junior is spawned automatically when you specify a category. Pick the a return { agentToUse: "", categoryModel: undefined, - error: `You are prometheus. You cannot delegate to prometheus via delegate_task. + error: `You are prometheus. You cannot delegate to prometheus via task. Create the work plan directly - that's your job as the planning agent.`, } @@ -955,7 +990,7 @@ Create the work plan directly - that's your job as the planning agent.`, return { agentToUse: "", categoryModel: undefined, - error: `Cannot call primary agent "${isPrimaryAgent.name}" via delegate_task. Primary agents are top-level orchestrators.`, + error: `Cannot call primary agent "${isPrimaryAgent.name}" via task. Primary agents are top-level orchestrators.`, } } diff --git a/src/tools/delegate-task/helpers.ts b/src/tools/delegate-task/helpers.ts index ecde350d7..05a26e875 100644 --- a/src/tools/delegate-task/helpers.ts +++ b/src/tools/delegate-task/helpers.ts @@ -18,6 +18,7 @@ export function parseModelString(model: string): { providerID: string; modelID: * Get the message directory for a session, checking both direct and nested paths. */ export function getMessageDir(sessionID: string): string | null { + if (!sessionID.startsWith("ses_")) return null if (!existsSync(MESSAGE_STORAGE)) return null const directPath = join(MESSAGE_STORAGE, sessionID) diff --git a/src/tools/delegate-task/metadata-await.test.ts b/src/tools/delegate-task/metadata-await.test.ts new file mode 100644 index 000000000..733970d88 --- /dev/null +++ b/src/tools/delegate-task/metadata-await.test.ts @@ -0,0 +1,65 @@ +const { describe, test, expect } = require("bun:test") + +import { executeBackgroundTask } from "./executor" +import type { DelegateTaskArgs, ToolContextWithMetadata } from "./types" + +describe("task tool metadata awaiting", () => { + test("executeBackgroundTask awaits ctx.metadata before returning", async () => { + // given + let metadataResolved = false + const abort = new AbortController() + + const ctx: ToolContextWithMetadata = { + sessionID: "ses_parent", + messageID: "msg_parent", + agent: "sisyphus", + abort: abort.signal, + metadata: async () => { + await new Promise((resolve) => setTimeout(resolve, 50)) + metadataResolved = true + }, + } + + const args: DelegateTaskArgs = { + load_skills: [], + description: "Test task", + prompt: "Do something", + run_in_background: true, + subagent_type: "explore", + } + + const executorCtx = { + manager: { + launch: async () => ({ + id: "task_1", + description: "Test task", + prompt: "Do something", + agent: "explore", + status: "pending", + sessionID: "ses_child", + }), + getTask: () => undefined, + }, + } as any + + const parentContext = { + sessionID: "ses_parent", + messageID: "msg_parent", + } + + // when + const result = await executeBackgroundTask( + args, + ctx, + executorCtx, + parentContext, + "explore", + undefined, + undefined, + ) + + // then + expect(result).toContain("Background task launched") + expect(metadataResolved).toBe(true) + }) +}) diff --git a/src/tools/delegate-task/tools.test.ts b/src/tools/delegate-task/tools.test.ts index 4a6a615bc..018303523 100644 --- a/src/tools/delegate-task/tools.test.ts +++ b/src/tools/delegate-task/tools.test.ts @@ -1,4 +1,5 @@ -import { describe, test, expect, beforeEach, afterEach, spyOn } from "bun:test" +declare const require: (name: string) => any +const { describe, test, expect, beforeEach, afterEach, spyOn } = require("bun:test") import { DEFAULT_CATEGORIES, CATEGORY_PROMPT_APPENDS, CATEGORY_DESCRIPTIONS, isPlanAgent, PLAN_AGENT_NAMES } from "./constants" import { resolveCategoryConfig } from "./tools" import type { CategoryConfig } from "../../config/schema" @@ -207,6 +208,66 @@ describe("sisyphus-task", () => { }) describe("category delegation config validation", () => { + test("fills subagent_type as sisyphus-junior when category is provided without subagent_type", async () => { + // given + const { createDelegateTask } = require("./tools") + + const mockManager = { + launch: async () => ({ + id: "task-123", + status: "pending", + description: "Test task", + agent: "sisyphus-junior", + sessionID: "test-session", + }), + } + const mockClient = { + app: { agents: async () => ({ data: [] }) }, + config: { get: async () => ({}) }, + provider: { list: async () => ({ data: { connected: ["openai"] } }) }, + model: { list: async () => ({ data: [{ provider: "openai", id: "gpt-5.3-codex" }] }) }, + session: { + create: async () => ({ data: { id: "test-session" } }), + prompt: async () => ({ data: {} }), + messages: async () => ({ data: [] }), + status: async () => ({ data: {} }), + }, + } + + const tool = createDelegateTask({ + manager: mockManager, + client: mockClient, + }) + + const toolContext = { + sessionID: "parent-session", + messageID: "parent-message", + agent: "sisyphus", + abort: new AbortController().signal, + } + + const args: { + description: string + prompt: string + category: string + run_in_background: boolean + load_skills: string[] + subagent_type?: string + } = { + description: "Quick category test", + prompt: "Do something", + category: "quick", + run_in_background: true, + load_skills: [], + } + + // when + await tool.execute(args, toolContext) + + // then + expect(args.subagent_type).toBe("sisyphus-junior") + }, { timeout: 10000 }) + test("proceeds without error when systemDefaultModel is undefined", async () => { // given a mock client with no model in config const { createDelegateTask } = require("./tools") @@ -304,6 +365,71 @@ describe("sisyphus-task", () => { }) }) + describe("background metadata sessionId", () => { + test("should wait for background sessionId and set metadata for TUI toolcall counting", async () => { + //#given - manager.launch returns before sessionID is available + const { createDelegateTask } = require("./tools") + + const tasks = new Map() + const mockManager = { + getTask: (id: string) => tasks.get(id), + launch: async () => { + const task = { id: "bg_1", status: "pending", description: "Test task", agent: "explore" } + tasks.set(task.id, task) + setTimeout(() => { + tasks.set(task.id, { ...task, status: "running", sessionID: "ses_child" }) + }, 20) + return task + }, + } + + const mockClient = { + app: { agents: async () => ({ data: [{ name: "explore", mode: "subagent" }] }) }, + config: { get: async () => ({}) }, + provider: { list: async () => ({ data: { connected: ["openai"] } }) }, + model: { list: async () => ({ data: [{ provider: "openai", id: "gpt-5.3-codex" }] }) }, + session: { + create: async () => ({ data: { id: "test-session" } }), + prompt: async () => ({ data: {} }), + messages: async () => ({ data: [] }), + status: async () => ({ data: {} }), + }, + } + + const tool = createDelegateTask({ + manager: mockManager, + client: mockClient, + }) + + const metadataCalls: Array<{ title?: string; metadata?: Record }> = [] + const toolContext = { + sessionID: "parent-session", + messageID: "parent-message", + agent: "sisyphus", + abort: new AbortController().signal, + metadata: (input: { title?: string; metadata?: Record }) => { + metadataCalls.push(input) + }, + } + + const args = { + description: "Explore task", + prompt: "Explore features directory deeply", + subagent_type: "explore", + run_in_background: true, + load_skills: [], + } + + //#when + const result = await tool.execute(args, toolContext) + + //#then - metadata should include sessionId (camelCase) once it's available + expect(String(result)).toContain("Background task launched") + const sessionIdCall = metadataCalls.find((c) => c.metadata?.sessionId === "ses_child") + expect(sessionIdCall).toBeDefined() + }) + }) + describe("resolveCategoryConfig", () => { test("returns null for unknown category without user config", () => { // given @@ -1894,7 +2020,7 @@ describe("sisyphus-task", () => { describe("browserProvider propagation", () => { test("should resolve agent-browser skill when browserProvider is passed", async () => { - // given - delegate_task configured with browserProvider: "agent-browser" + // given - task configured with browserProvider: "agent-browser" const { createDelegateTask } = require("./tools") let promptBody: any @@ -1949,7 +2075,7 @@ describe("sisyphus-task", () => { }, { timeout: 20000 }) test("should NOT resolve agent-browser skill when browserProvider is not set", async () => { - // given - delegate_task without browserProvider (defaults to playwright) + // given - task without browserProvider (defaults to playwright) const { createDelegateTask } = require("./tools") const mockManager = { launch: async () => ({}) } @@ -2720,8 +2846,8 @@ describe("sisyphus-task", () => { }, { timeout: 20000 }) }) - describe("prometheus subagent delegate_task permission", () => { - test("prometheus subagent should have delegate_task permission enabled", async () => { + describe("prometheus subagent task permission", () => { + test("prometheus subagent should have task permission enabled", async () => { // given - sisyphus delegates to prometheus const { createDelegateTask } = require("./tools") let promptBody: any @@ -2759,7 +2885,7 @@ describe("sisyphus-task", () => { // when - sisyphus delegates to prometheus await tool.execute( { - description: "Test prometheus delegate_task permission", + description: "Test prometheus task permission", prompt: "Create a plan", subagent_type: "prometheus", run_in_background: false, @@ -2768,11 +2894,11 @@ describe("sisyphus-task", () => { toolContext ) - // then - prometheus should have delegate_task permission - expect(promptBody.tools.delegate_task).toBe(true) + // then - prometheus should have task permission + expect(promptBody.tools.task).toBe(true) }, { timeout: 20000 }) - test("non-prometheus subagent should NOT have delegate_task permission", async () => { + test("non-prometheus subagent should NOT have task permission", async () => { // given - sisyphus delegates to oracle (non-prometheus) const { createDelegateTask } = require("./tools") let promptBody: any @@ -2810,7 +2936,7 @@ describe("sisyphus-task", () => { // when - sisyphus delegates to oracle await tool.execute( { - description: "Test oracle no delegate_task permission", + description: "Test oracle no task permission", prompt: "Consult on architecture", subagent_type: "oracle", run_in_background: false, @@ -2819,8 +2945,8 @@ describe("sisyphus-task", () => { toolContext ) - // then - oracle should NOT have delegate_task permission - expect(promptBody.tools.delegate_task).toBe(false) + // then - oracle should NOT have task permission + expect(promptBody.tools.task).toBe(false) }, { timeout: 20000 }) }) diff --git a/src/tools/delegate-task/tools.ts b/src/tools/delegate-task/tools.ts index b6b0ea543..1db72408c 100644 --- a/src/tools/delegate-task/tools.ts +++ b/src/tools/delegate-task/tools.ts @@ -86,6 +86,13 @@ Prompts MUST be in English.` async execute(args: DelegateTaskArgs, toolContext) { const ctx = toolContext as ToolContextWithMetadata + if (args.category && !args.subagent_type) { + args.subagent_type = "sisyphus-junior" + } + await ctx.metadata?.({ + title: args.description, + }) + if (args.run_in_background === undefined) { throw new Error(`Invalid arguments: 'run_in_background' parameter is REQUIRED. Use run_in_background=false for task delegation, run_in_background=true only for parallel exploration.`) } @@ -116,7 +123,7 @@ Prompts MUST be in English.` return executeSyncContinuation(args, ctx, options) } - if (args.category && args.subagent_type) { + if (args.category && args.subagent_type && args.subagent_type !== "sisyphus-junior") { return `Invalid arguments: Provide EITHER category OR subagent_type, not both.` } @@ -157,7 +164,7 @@ Prompts MUST be in English.` const isRunInBackgroundExplicitlyFalse = args.run_in_background === false || args.run_in_background === "false" as unknown as boolean - log("[delegate_task] unstable agent detection", { + log("[task] unstable agent detection", { category: args.category, actualModel, isUnstableAgent, diff --git a/src/tools/delegate-task/types.ts b/src/tools/delegate-task/types.ts index e7892fc8f..1fb4b4a6f 100644 --- a/src/tools/delegate-task/types.ts +++ b/src/tools/delegate-task/types.ts @@ -28,7 +28,12 @@ export interface ToolContextWithMetadata { messageID: string agent: string abort: AbortSignal - metadata?: (input: { title?: string; metadata?: Record }) => void + metadata?: (input: { title?: string; metadata?: Record }) => void | Promise + /** + * Tool call ID injected by OpenCode's internal context (not in plugin ToolContext type, + * but present at runtime via spread in fromPlugin()). Used for metadata store keying. + */ + callID?: string } export interface SyncSessionCreatedEvent { diff --git a/src/tools/task/task-list.ts b/src/tools/task/task-list.ts index 2fbdb55bc..6582d4721 100644 --- a/src/tools/task/task-list.ts +++ b/src/tools/task/task-list.ts @@ -70,7 +70,7 @@ Returns summary format: id, subject, status, owner, blockedBy (not full descript return JSON.stringify({ tasks: summaries, - reminder: "1 task = 1 delegate_task. Maximize parallel execution by running independent tasks (tasks with empty blockedBy) concurrently." + reminder: "1 task = 1 task. Maximize parallel execution by running independent tasks (tasks with empty blockedBy) concurrently." }) }, })