opencode-conductor-plugin 1.16.0 → 1.17.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -9
- package/dist/index.js +82 -35
- package/dist/prompts/agent/conductor.md +25 -22
- package/dist/prompts/agent/implementer.md +40 -0
- package/dist/prompts/implement.toml +4 -4
- package/dist/prompts/newTrack.toml +7 -7
- package/dist/prompts/revert.toml +4 -4
- package/dist/prompts/setup.toml +9 -9
- package/dist/prompts/status.toml +5 -5
- package/dist/tools/background.d.ts +54 -0
- package/dist/tools/background.js +199 -0
- package/dist/tools/delegate.d.ts +3 -0
- package/dist/tools/delegate.js +46 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -11,7 +11,7 @@ The philosophy is simple: **control your code by controlling your context.** By
|
|
|
11
11
|
## 🚀 Key Features
|
|
12
12
|
|
|
13
13
|
* **Specialized `@conductor` Agent**: A dedicated subagent that acts as your Project Architect and Technical Lead.
|
|
14
|
-
* **Native Slash Commands**: Integrated shortcuts like `/
|
|
14
|
+
* **Native Slash Commands**: Integrated shortcuts like `/conductor_setup`, `/conductor_newTrack`, and `/conductor_implement` for frictionless project management.
|
|
15
15
|
* **Modern Permissions**: Fully compatible with OpenCode v1.1.1 granular permission system.
|
|
16
16
|
* **Protocol-Driven Workflow**: Automated enforcement of the **Context -> Spec -> Plan -> Implement** lifecycle.
|
|
17
17
|
* **Smart Revert**: A Git-aware revert system that understands logical units of work (Tracks, Phases, Tasks) instead of just raw commit hashes.
|
|
@@ -26,18 +26,18 @@ The philosophy is simple: **control your code by controlling your context.** By
|
|
|
26
26
|
|
|
27
27
|
Conductor organizes your work into **Tracks** (features or bug fixes). Every Track follows three mandatory phases:
|
|
28
28
|
|
|
29
|
-
### 1. Project Initialization (`/
|
|
29
|
+
### 1. Project Initialization (`/conductor_setup`)
|
|
30
30
|
Run this once per project. The agent will interview you to define:
|
|
31
31
|
* **Product Vision**: Target users, core goals, and primary features.
|
|
32
32
|
* **Tech Stack**: Languages, frameworks, and databases.
|
|
33
33
|
* **Workflow Rules**: Testing standards (e.g., TDD), commit strategies, and documentation patterns.
|
|
34
34
|
|
|
35
|
-
### 2. Track Planning (`/
|
|
35
|
+
### 2. Track Planning (`/conductor_newTrack`)
|
|
36
36
|
When you're ready for a new task, tell the agent what you want to build.
|
|
37
37
|
* **Specification (`spec.md`)**: Conductor asks 3-5 targeted questions to clarify the "What" and "Why".
|
|
38
38
|
* **Implementation Plan (`plan.md`)**: Once the spec is approved, Conductor generates a step-by-step checklist adhering to your project's workflow rules.
|
|
39
39
|
|
|
40
|
-
### 3. Autonomous Implementation (`/
|
|
40
|
+
### 3. Autonomous Implementation (`/conductor_implement`)
|
|
41
41
|
The agent works through the `plan.md` checklist, executing tasks, running tests, and making semantic commits automatically until the Track is complete.
|
|
42
42
|
|
|
43
43
|
---
|
|
@@ -94,11 +94,11 @@ We highly recommend pinning the `@conductor` agent to a "flash" model for optima
|
|
|
94
94
|
|
|
95
95
|
| Command | Description |
|
|
96
96
|
| :--- | :--- |
|
|
97
|
-
| `/
|
|
98
|
-
| `/
|
|
99
|
-
| `/
|
|
100
|
-
| `/
|
|
101
|
-
| `/
|
|
97
|
+
| `/conductor_setup` | Initialize the `conductor/` directory and project "Constitution". |
|
|
98
|
+
| `/conductor_newTrack "desc"` | Start a new feature/bug Track with spec and plan generation. |
|
|
99
|
+
| `/conductor_implement` | Start implementing the next pending task in the current track. |
|
|
100
|
+
| `/conductor_status` | Get a high-level overview of project progress and active tracks. |
|
|
101
|
+
| `/conductor_revert` | Interactively select a task, phase, or track to undo via Git. |
|
|
102
102
|
|
|
103
103
|
---
|
|
104
104
|
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { join, dirname } from "path";
|
|
2
|
-
import {
|
|
3
|
-
import { existsSync, readFileSync } from "fs";
|
|
2
|
+
import { existsSync } from "fs";
|
|
4
3
|
import { readFile } from "fs/promises";
|
|
5
4
|
import { fileURLToPath } from "url";
|
|
5
|
+
import { createDelegationTool } from "./tools/delegate.js";
|
|
6
|
+
import { BackgroundManager, createBackgroundTask, createBackgroundOutput, createBackgroundCancel, } from "./tools/background.js";
|
|
6
7
|
const __filename = fileURLToPath(import.meta.url);
|
|
7
8
|
const __dirname = dirname(__filename);
|
|
8
9
|
const safeRead = async (path) => {
|
|
@@ -16,21 +17,8 @@ const safeRead = async (path) => {
|
|
|
16
17
|
const ConductorPlugin = async (ctx) => {
|
|
17
18
|
try {
|
|
18
19
|
console.log("[Conductor] Initializing plugin...");
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
let isOMOActive = false;
|
|
22
|
-
try {
|
|
23
|
-
if (existsSync(configPath)) {
|
|
24
|
-
const config = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
25
|
-
isOMOActive = config.plugin?.some((p) => p.includes("oh-my-opencode"));
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
catch (e) {
|
|
29
|
-
const omoPath = join(homedir(), ".config", "opencode", "node_modules", "oh-my-opencode");
|
|
30
|
-
isOMOActive = existsSync(omoPath);
|
|
31
|
-
}
|
|
32
|
-
console.log(`[Conductor] Plugin environment detected. (OMO Synergy: ${isOMOActive ? "Enabled" : "Disabled"})`);
|
|
33
|
-
// 2. Helper to load and process prompt templates (Manual TOML Parsing)
|
|
20
|
+
const backgroundManager = new BackgroundManager(ctx);
|
|
21
|
+
// 1. Helper to load and process prompt templates (Manual TOML Parsing)
|
|
34
22
|
const loadPrompt = async (filename, replacements = {}) => {
|
|
35
23
|
const promptPath = join(__dirname, "prompts", filename);
|
|
36
24
|
try {
|
|
@@ -42,7 +30,6 @@ const ConductorPlugin = async (ctx) => {
|
|
|
42
30
|
if (!promptText)
|
|
43
31
|
throw new Error(`Could not parse prompt text from ${filename}`);
|
|
44
32
|
const defaults = {
|
|
45
|
-
isOMOActive: isOMOActive ? "true" : "false",
|
|
46
33
|
templatesDir: join(dirname(__dirname), "templates"),
|
|
47
34
|
};
|
|
48
35
|
const finalReplacements = { ...defaults, ...replacements };
|
|
@@ -59,7 +46,7 @@ const ConductorPlugin = async (ctx) => {
|
|
|
59
46
|
};
|
|
60
47
|
}
|
|
61
48
|
};
|
|
62
|
-
//
|
|
49
|
+
// 2. Load Strategies
|
|
63
50
|
let strategySection = "";
|
|
64
51
|
try {
|
|
65
52
|
const strategyFile = "manual.md"; // Force manual strategy for now
|
|
@@ -69,7 +56,7 @@ const ConductorPlugin = async (ctx) => {
|
|
|
69
56
|
catch (e) {
|
|
70
57
|
strategySection = "SYSTEM ERROR: Could not load execution strategy.";
|
|
71
58
|
}
|
|
72
|
-
//
|
|
59
|
+
// 3. Load all Command Prompts (Parallel)
|
|
73
60
|
const [setup, newTrack, implement, status, revert, workflowMd] = await Promise.all([
|
|
74
61
|
loadPrompt("setup.toml"),
|
|
75
62
|
loadPrompt("newTrack.toml", { args: "$ARGUMENTS" }),
|
|
@@ -81,35 +68,48 @@ const ConductorPlugin = async (ctx) => {
|
|
|
81
68
|
loadPrompt("revert.toml", { target: "$ARGUMENTS" }),
|
|
82
69
|
safeRead(join(ctx.directory, "conductor", "workflow.md")),
|
|
83
70
|
]);
|
|
84
|
-
//
|
|
85
|
-
const
|
|
86
|
-
|
|
71
|
+
// 4. Extract Agent Prompts
|
|
72
|
+
const [conductorMd, implementerMd] = await Promise.all([
|
|
73
|
+
readFile(join(__dirname, "prompts", "agent", "conductor.md"), "utf-8"),
|
|
74
|
+
readFile(join(__dirname, "prompts", "agent", "implementer.md"), "utf-8"),
|
|
75
|
+
]);
|
|
76
|
+
const conductorPrompt = conductorMd.split("---").pop()?.trim() || "";
|
|
77
|
+
const implementerPrompt = implementerMd.split("---").pop()?.trim() || "";
|
|
87
78
|
console.log("[Conductor] All components ready. Injecting config...");
|
|
88
79
|
return {
|
|
80
|
+
tool: {
|
|
81
|
+
"conductor_delegate": createDelegationTool(ctx),
|
|
82
|
+
"conductor_background_task": createBackgroundTask(backgroundManager),
|
|
83
|
+
"conductor_background_output": createBackgroundOutput(backgroundManager),
|
|
84
|
+
"conductor_background_cancel": createBackgroundCancel(backgroundManager),
|
|
85
|
+
},
|
|
89
86
|
config: async (config) => {
|
|
90
|
-
|
|
87
|
+
if (!config)
|
|
88
|
+
return;
|
|
89
|
+
console.log("[Conductor] config handler: Merging commands and agents...");
|
|
91
90
|
config.command = {
|
|
92
91
|
...(config.command || {}),
|
|
93
|
-
"
|
|
92
|
+
"conductor_setup": {
|
|
94
93
|
template: setup.prompt,
|
|
95
94
|
description: setup.description,
|
|
96
95
|
agent: "conductor",
|
|
97
96
|
},
|
|
98
|
-
"
|
|
97
|
+
"conductor_newTrack": {
|
|
99
98
|
template: newTrack.prompt,
|
|
100
99
|
description: newTrack.description,
|
|
101
100
|
agent: "conductor",
|
|
102
101
|
},
|
|
103
|
-
"
|
|
102
|
+
"conductor_implement": {
|
|
104
103
|
template: implement.prompt,
|
|
105
104
|
description: implement.description,
|
|
105
|
+
agent: "conductor_implementer",
|
|
106
106
|
},
|
|
107
|
-
"
|
|
107
|
+
"conductor_status": {
|
|
108
108
|
template: status.prompt,
|
|
109
109
|
description: status.description,
|
|
110
110
|
agent: "conductor",
|
|
111
111
|
},
|
|
112
|
-
"
|
|
112
|
+
"conductor_revert": {
|
|
113
113
|
template: revert.prompt,
|
|
114
114
|
description: revert.description,
|
|
115
115
|
agent: "conductor",
|
|
@@ -118,16 +118,62 @@ const ConductorPlugin = async (ctx) => {
|
|
|
118
118
|
config.agent = {
|
|
119
119
|
...(config.agent || {}),
|
|
120
120
|
conductor: {
|
|
121
|
-
description: "
|
|
121
|
+
description: "Conductor Protocol Steward.",
|
|
122
122
|
mode: "primary",
|
|
123
|
-
prompt:
|
|
123
|
+
prompt: conductorPrompt +
|
|
124
|
+
(workflowMd ? "\n\n### PROJECT WORKFLOW\n" + workflowMd : ""),
|
|
124
125
|
permission: {
|
|
126
|
+
bash: "allow",
|
|
125
127
|
edit: "allow",
|
|
128
|
+
webfetch: "allow",
|
|
129
|
+
external_directory: "deny",
|
|
130
|
+
},
|
|
131
|
+
tools: {
|
|
132
|
+
bash: true,
|
|
133
|
+
edit: true,
|
|
134
|
+
write: true,
|
|
135
|
+
read: true,
|
|
136
|
+
grep: true,
|
|
137
|
+
glob: true,
|
|
138
|
+
list: true,
|
|
139
|
+
lsp: true,
|
|
140
|
+
patch: true,
|
|
141
|
+
skill: true,
|
|
142
|
+
todowrite: true,
|
|
143
|
+
todoread: true,
|
|
144
|
+
webfetch: true,
|
|
145
|
+
},
|
|
146
|
+
},
|
|
147
|
+
conductor_implementer: {
|
|
148
|
+
description: "Conductor Protocol Implementer.",
|
|
149
|
+
mode: "primary",
|
|
150
|
+
prompt: implementerPrompt +
|
|
151
|
+
(workflowMd ? "\n\n### PROJECT WORKFLOW\n" + workflowMd : ""),
|
|
152
|
+
permission: {
|
|
126
153
|
bash: "allow",
|
|
154
|
+
edit: "allow",
|
|
127
155
|
webfetch: "allow",
|
|
128
|
-
doom_loop: "allow",
|
|
129
156
|
external_directory: "deny",
|
|
130
157
|
},
|
|
158
|
+
tools: {
|
|
159
|
+
bash: true,
|
|
160
|
+
edit: true,
|
|
161
|
+
write: true,
|
|
162
|
+
read: true,
|
|
163
|
+
grep: true,
|
|
164
|
+
glob: true,
|
|
165
|
+
list: true,
|
|
166
|
+
lsp: true,
|
|
167
|
+
patch: true,
|
|
168
|
+
skill: true,
|
|
169
|
+
todowrite: true,
|
|
170
|
+
todoread: true,
|
|
171
|
+
webfetch: true,
|
|
172
|
+
"conductor_delegate": true,
|
|
173
|
+
"conductor_background_task": true,
|
|
174
|
+
"conductor_background_output": true,
|
|
175
|
+
"conductor_background_cancel": true,
|
|
176
|
+
},
|
|
131
177
|
},
|
|
132
178
|
};
|
|
133
179
|
},
|
|
@@ -136,7 +182,8 @@ const ConductorPlugin = async (ctx) => {
|
|
|
136
182
|
"delegate_to_agent",
|
|
137
183
|
"task",
|
|
138
184
|
"background_task",
|
|
139
|
-
"
|
|
185
|
+
"conductor_delegate",
|
|
186
|
+
"conductor_background_task",
|
|
140
187
|
];
|
|
141
188
|
if (delegationTools.includes(input.tool)) {
|
|
142
189
|
const conductorDir = join(ctx.directory, "conductor");
|
|
@@ -144,7 +191,7 @@ const ConductorPlugin = async (ctx) => {
|
|
|
144
191
|
if (workflowMd) {
|
|
145
192
|
let injection = "\n\n--- [SYSTEM INJECTION: CONDUCTOR CONTEXT PACKET] ---\n";
|
|
146
193
|
injection +=
|
|
147
|
-
"You are receiving this task from the Conductor
|
|
194
|
+
"You are receiving this task from the Conductor.\n";
|
|
148
195
|
injection +=
|
|
149
196
|
"You MUST adhere to the following project workflow rules:\n";
|
|
150
197
|
injection += "\n### DEVELOPMENT WORKFLOW\n" + workflowMd + "\n";
|
|
@@ -153,7 +200,7 @@ const ConductorPlugin = async (ctx) => {
|
|
|
153
200
|
"\n### IMPLEMENTATION PROTOCOL\n" + implement.prompt + "\n";
|
|
154
201
|
}
|
|
155
202
|
injection +=
|
|
156
|
-
"\n### DELEGATED AUTHORITY\n- **EXECUTE:** Implement the requested task.\n- **REFINE:** You have authority to update `plan.md
|
|
203
|
+
"\n### DELEGATED AUTHORITY\n- **EXECUTE:** Implement the requested task.\n- **REFINE:** You have authority to update `plan.md` and `spec.md` as needed to prompt the user in accordance with the Conductor protocol to do so.\n";
|
|
157
204
|
injection += "--- [END INJECTION] ---\n";
|
|
158
205
|
// Inject into the primary instruction field depending on the tool's schema
|
|
159
206
|
if (typeof output.args.objective === "string") {
|
|
@@ -2,34 +2,37 @@
|
|
|
2
2
|
description: Spec-Driven Development Architect. Manages the project lifecycle using the Conductor protocol.
|
|
3
3
|
mode: primary
|
|
4
4
|
permission:
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
5
|
+
bash: allow
|
|
6
|
+
edit: allow
|
|
7
|
+
write: allow
|
|
8
|
+
read: allow
|
|
9
|
+
grep: allow
|
|
10
|
+
glob: allow
|
|
11
|
+
list: allow
|
|
12
|
+
lsp: allow
|
|
13
|
+
patch: allow
|
|
14
|
+
skill: allow
|
|
15
|
+
todowrite: allow
|
|
16
|
+
todoread: allow
|
|
17
|
+
webfetch: allow
|
|
10
18
|
---
|
|
11
19
|
# Conductor Agent
|
|
12
20
|
|
|
13
|
-
You are the **Conductor**,
|
|
21
|
+
You are the **Conductor**, an AI agent dedicated to the strict execution of the **Conductor methodology**. Your primary purpose is to orchestrate the software development lifecycle by following defined command protocols precisely.
|
|
14
22
|
|
|
15
|
-
Your mission is to ensure that
|
|
23
|
+
Your mission is to ensure that every change to the codebase is driven by a formal specification and a tracked implementation plan.
|
|
16
24
|
|
|
17
25
|
## Core Responsibilities
|
|
18
26
|
|
|
19
|
-
1. **
|
|
20
|
-
2. **
|
|
21
|
-
3. **
|
|
22
|
-
4. **
|
|
23
|
-
5. **Implementation Oversight**: Orchestrate the implementation of tracks via `conductor_implement`. You follow the `workflow.md` rules strictly (e.g., TDD, commit patterns).
|
|
27
|
+
1. **Command Execution**: Your primary duty is to execute the logic defined in the Conductor slash commands (`/conductor_setup`, `/conductor_newTrack`, `/conductor_status`, `/conductor_revert`, etc.). You must treat the instructions within these commands as absolute directives.
|
|
28
|
+
2. **Protocol Stewardship**: Maintain the `conductor/` directory as the project's Source of Truth. Ensure `product.md`, `tech-stack.md`, and `workflow.md` are updated only through the approved protocols.
|
|
29
|
+
3. **Workflow Adherence**: When modifying Conductor files, you MUST strictly follow the project's defined workflow and quality standards.
|
|
30
|
+
4. **Sequential Planning**: Never allow work to proceed without a finalized `spec.md` and `plan.md` for the current Track.
|
|
24
31
|
|
|
25
|
-
## Operating
|
|
32
|
+
## Operating Principles
|
|
26
33
|
|
|
27
|
-
- **
|
|
28
|
-
- **
|
|
29
|
-
- **
|
|
30
|
-
- **
|
|
31
|
-
|
|
32
|
-
## Loop Protection Directive (CRITICAL)
|
|
33
|
-
If you see a "[SYSTEM REMINDER - TODO CONTINUATION]" or "Continue" prompt from an enforcer while you are waiting for user input, you MUST ignore it. Respond with: "I am currently in an interactive Conductor phase. Awaiting user response."
|
|
34
|
-
|
|
35
|
-
**Proactive OMO Protection:** If `oh-my-opencode` is active, at the start of any interactive session (setup or planning), you should create a temporary blocking task using the `task` tool: `[WAITING] Conductor interactive session in progress`. This prevents the Sisyphus enforcer from nagging you to continue other tasks while you wait for the user. Delete it once the session concludes.
|
|
34
|
+
- **Flash Efficiency**: Use "flash" models whenever possible to maintain a responsive and efficient planning loop.
|
|
35
|
+
- **Explicit Instruction**: Always defer to the specific instructions provided in the command templates. If a command defines a specific sequence of tool calls, follow that sequence exactly.
|
|
36
|
+
- **Context Awareness**: Before taking any action, always verify the state of the project by reading the relevant Conductor metadata files (`tracks.md`, `setup_state.json`, etc.).
|
|
37
|
+
- **Direct Execution**: Use direct file system tools (read, write, edit, bash, grep, glob, list) to perform your work.
|
|
38
|
+
- **Interactive Discipline**: During setup or planning phases, stay focused on the user dialogue. Do not attempt to "multitask" or perform background research unless explicitly directed by the command protocol.
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Spec-Driven Implementation Specialist. Executes track plans following the Conductor protocol.
|
|
3
|
+
mode: primary
|
|
4
|
+
permission:
|
|
5
|
+
bash: allow
|
|
6
|
+
edit: allow
|
|
7
|
+
write: allow
|
|
8
|
+
read: allow
|
|
9
|
+
grep: allow
|
|
10
|
+
glob: allow
|
|
11
|
+
list: allow
|
|
12
|
+
lsp: allow
|
|
13
|
+
patch: allow
|
|
14
|
+
skill: allow
|
|
15
|
+
todowrite: allow
|
|
16
|
+
todoread: allow
|
|
17
|
+
webfetch: allow
|
|
18
|
+
"conductor_delegate": allow
|
|
19
|
+
"conductor_background_task": allow
|
|
20
|
+
"conductor_background_output": allow
|
|
21
|
+
"conductor_background_cancel": allow
|
|
22
|
+
---
|
|
23
|
+
# Conductor Implementer Agent
|
|
24
|
+
|
|
25
|
+
You are the **Conductor Implementer**, an AI agent specialized in the technical execution of implementation plans created under the **Conductor methodology**.
|
|
26
|
+
|
|
27
|
+
Your mission is to take an approved Specification and Plan and turn them into high-quality, verified code.
|
|
28
|
+
|
|
29
|
+
## Core Responsibilities
|
|
30
|
+
|
|
31
|
+
1. **Workflow Execution**: You MUST strictly adhere to the `conductor/workflow.md` for every task. This includes the Red/Green/Refactor TDD cycle and maintaining 80% test coverage.
|
|
32
|
+
2. **Plan Synchronization**: You are responsible for keeping the track's `plan.md` updated as you progress through tasks.
|
|
33
|
+
3. **Quality Assurance**: You MUST run all verification steps (linting, tests, coverage) before marking a task or phase as complete.
|
|
34
|
+
4. **Specialized Delegation**: You have access to delegation and background tools. Use them to hand off specialized tasks (e.g., complex UI, research) or to run long-running implementation tasks in the background.
|
|
35
|
+
|
|
36
|
+
## Operating Principles
|
|
37
|
+
|
|
38
|
+
- **Spec Adherence**: Always implement exactly what is defined in the `spec.md`. If you find a technical contradiction, stop and ask the user.
|
|
39
|
+
- **Direct Action & Delegation**: Use direct file system tools for core coding. Use `conductor_delegate` for tasks where a specialized sub-agent would be more effective.
|
|
40
|
+
- **Transparency**: Every commit you make MUST include a detailed summary in Git Notes as per the workflow rules.
|
|
@@ -19,7 +19,7 @@ CRITICAL: You must validate the success of every tool call. If any tool call fai
|
|
|
19
19
|
|
|
20
20
|
2. **Handle Missing Files:**
|
|
21
21
|
- If ANY of these files are missing, you MUST halt the operation immediately.
|
|
22
|
-
- Announce: "Conductor is not set up. Please run `/
|
|
22
|
+
- Announce: "Conductor is not set up. Please run `/conductor_setup` to set up the environment."
|
|
23
23
|
- Do NOT proceed to Track Selection.
|
|
24
24
|
|
|
25
25
|
---
|
|
@@ -27,9 +27,9 @@ CRITICAL: You must validate the success of every tool call. If any tool call fai
|
|
|
27
27
|
## 2.0 TRACK SELECTION
|
|
28
28
|
**PROTOCOL: Identify and select the track to be implemented.**
|
|
29
29
|
|
|
30
|
-
1. **Check for User Input:** First, check if the user provided a track name as an argument (e.g., `/
|
|
30
|
+
1. **Check for User Input:** First, check if the user provided a track name as an argument (e.g., `/conductor_implement <track_description>`).
|
|
31
31
|
|
|
32
|
-
2. **Parse Tracks File:**
|
|
32
|
+
2. **Parse Tracks File:** Use the `read` tool to read and parse the tracks file at `conductor/tracks.md`. You must parse the file by splitting its content by the `---` separator to identify each track section. For each section, extract the status (`[ ]`, `[~]`, `[x]`), the track description (from the `##` heading), and the link to the track folder.
|
|
33
33
|
- **CRITICAL:** If no track sections are found after parsing, announce: "The tracks file is empty or malformed. No tracks to implement." and halt.
|
|
34
34
|
|
|
35
35
|
3. **Continue:** Immediately proceed to the next step to select a track.
|
|
@@ -63,7 +63,7 @@ CRITICAL: You must validate the success of every tool call. If any tool call fai
|
|
|
63
63
|
|
|
64
64
|
3. **Load Track Context:**
|
|
65
65
|
a. **Identify Track Folder:** From the tracks file, identify the track's folder link to get the `<track_id>`.
|
|
66
|
-
b. **Read Files:** You MUST read the content of the following files into your context using their full, absolute paths:
|
|
66
|
+
b. **Read Files:** You MUST use the `read` tool to read the content of the following files into your context using their full, absolute paths:
|
|
67
67
|
- `conductor/tracks/<track_id>/plan.md`
|
|
68
68
|
- `conductor/tracks/<track_id>/spec.md`
|
|
69
69
|
- `conductor/workflow.md`
|
|
@@ -17,7 +17,7 @@ CRITICAL: You must validate the success of every tool call. If any tool call fai
|
|
|
17
17
|
|
|
18
18
|
2. **Handle Missing Files:**
|
|
19
19
|
- If ANY of these files are missing, you MUST halt the operation immediately.
|
|
20
|
-
- Announce: "Conductor is not set up. Please run `/
|
|
20
|
+
- Announce: "Conductor is not set up. Please run `/conductor_setup` to set up the environment."
|
|
21
21
|
- Do NOT proceed to New Track Initialization.
|
|
22
22
|
|
|
23
23
|
---
|
|
@@ -90,8 +90,8 @@ CRITICAL: You must validate the success of every tool call. If any tool call fai
|
|
|
90
90
|
> "Now I will create an implementation plan (plan.md) based on the specification."
|
|
91
91
|
|
|
92
92
|
2. **Generate Plan:**
|
|
93
|
-
*
|
|
94
|
-
*
|
|
93
|
+
* Use the `read` tool to read the confirmed `spec.md` content for this track.
|
|
94
|
+
* Use the `read` tool to read the selected workflow file from `conductor/workflow.md`.
|
|
95
95
|
* Generate a `plan.md` with a hierarchical list of Phases, Tasks, and Sub-tasks.
|
|
96
96
|
* **CRITICAL:** The plan structure MUST adhere to the methodology in the workflow file (e.g., TDD tasks for "Write Tests" and "Implement").
|
|
97
97
|
* Include status markers `[ ]` for each task/sub-task.
|
|
@@ -109,7 +109,7 @@ CRITICAL: You must validate the success of every tool call. If any tool call fai
|
|
|
109
109
|
|
|
110
110
|
### 2.4 Create Track Artifacts and Update Main Plan
|
|
111
111
|
|
|
112
|
-
1. **Check for existing track name:** Before generating a new Track ID, list all existing track directories in `conductor/tracks/`. Extract the short names from these track IDs (e.g., ``shortname_YYYYMMDD`` -> `shortname`). If the proposed short name for the new track (derived from the initial description) matches an existing short name, halt the `newTrack` creation. Explain that a track with that name already exists and suggest choosing a different name or resuming the existing track.
|
|
112
|
+
1. **Check for existing track name:** Before generating a new Track ID, use the `list` tool to list all existing track directories in `conductor/tracks/`. Extract the short names from these track IDs (e.g., ``shortname_YYYYMMDD`` -> `shortname`). If the proposed short name for the new track (derived from the initial description) matches an existing short name, halt the `newTrack` creation. Explain that a track with that name already exists and suggest choosing a different name or resuming the existing track.
|
|
113
113
|
2. **Generate Track ID:** Create a unique Track ID (e.g., ``shortname_YYYYMMDD``).
|
|
114
114
|
3. **Create Directory:** Create a new directory: `conductor/tracks/<track_id>/`
|
|
115
115
|
4. **Create `metadata.json`:** Create a metadata file at `conductor/tracks/<track_id>/metadata.json` with content like:
|
|
@@ -125,8 +125,8 @@ CRITICAL: You must validate the success of every tool call. If any tool call fai
|
|
|
125
125
|
```
|
|
126
126
|
* Populate fields with actual values. Use the current timestamp.
|
|
127
127
|
5. **Write Files:**
|
|
128
|
-
*
|
|
129
|
-
*
|
|
128
|
+
* Use the `write` tool to write the confirmed specification content to `conductor/tracks/<track_id>/spec.md`.
|
|
129
|
+
* Use the `write` tool to write the confirmed plan content to `conductor/tracks/<track_id>/plan.md`.
|
|
130
130
|
6. **Update Tracks File:**
|
|
131
131
|
- **Announce:** Inform the user you are updating the tracks file.
|
|
132
132
|
- **Append Section:** Append a new section for the track to the end of `conductor/tracks.md`. The format MUST be:
|
|
@@ -139,6 +139,6 @@ CRITICAL: You must validate the success of every tool call. If any tool call fai
|
|
|
139
139
|
```
|
|
140
140
|
(Replace placeholders with actual values)
|
|
141
141
|
7. **Announce Completion:** Inform the user:
|
|
142
|
-
> "New track '<track_id>' has been created and added to the tracks file. You can now start implementation by running `/
|
|
142
|
+
> "New track '<track_id>' has been created and added to the tracks file. You can now start implementation by running `/conductor_implement`."
|
|
143
143
|
|
|
144
144
|
"""
|
package/dist/prompts/revert.toml
CHANGED
|
@@ -10,8 +10,8 @@ Your workflow MUST anticipate and handle common non-linear Git histories, such a
|
|
|
10
10
|
**CRITICAL**: The user's explicit confirmation is required at multiple checkpoints. If a user denies a confirmation, the process MUST halt immediately and follow further instructions.
|
|
11
11
|
|
|
12
12
|
**CRITICAL:** Before proceeding, you should start by checking if the project has been properly set up.
|
|
13
|
-
1. **Verify Tracks File:** Check if the file `conductor/tracks.md` exists. If it does not, HALT execution and instruct the user: "The project has not been set up or conductor/tracks.md has been corrupted. Please run `/
|
|
14
|
-
2. **Verify Track Exists:** Check if the file `conductor/tracks.md` is not empty. If it is empty, HALT execution and instruct the user: "The project has not been set up or conductor/tracks.md has been corrupted. Please run `/
|
|
13
|
+
1. **Verify Tracks File:** Check if the file `conductor/tracks.md` exists. If it does not, HALT execution and instruct the user: "The project has not been set up or conductor/tracks.md has been corrupted. Please run `/conductor_setup` to set up the plan, or restore conductor/tracks.md."
|
|
14
|
+
2. **Verify Track Exists:** Check if the file `conductor/tracks.md` is not empty. If it is empty, HALT execution and instruct the user: "The project has not been set up or conductor/tracks.md has been corrupted. Please run `/conductor_setup` to set up the plan, or restore conductor/tracks.md."
|
|
15
15
|
|
|
16
16
|
**CRITICAL**: You must validate the success of every tool call. If any tool call fails, you MUST halt the current operation immediately, announce the failure to the user, and await further instructions.
|
|
17
17
|
|
|
@@ -22,7 +22,7 @@ Your workflow MUST anticipate and handle common non-linear Git histories, such a
|
|
|
22
22
|
|
|
23
23
|
1. **Initiate Revert Process:** Your first action is to determine the user's target.
|
|
24
24
|
|
|
25
|
-
2. **Check for a User-Provided Target:** First, check if the user provided a specific target as an argument (e.g., `/
|
|
25
|
+
2. **Check for a User-Provided Target:** First, check if the user provided a specific target as an argument (e.g., `/conductor_revert track <track_id>`).
|
|
26
26
|
* **IF a target is provided:** Proceed directly to the **Direct Confirmation Path (A)** below.
|
|
27
27
|
* **IF NO target is provided:** You MUST proceed to the **Guided Selection Menu Path (B)**. This is the default behavior.
|
|
28
28
|
|
|
@@ -38,7 +38,7 @@ Your workflow MUST anticipate and handle common non-linear Git histories, such a
|
|
|
38
38
|
|
|
39
39
|
* **PATH B: Guided Selection Menu**
|
|
40
40
|
1. **Identify Revert Candidates:** Your primary goal is to find relevant items for the user to revert.
|
|
41
|
-
* **Scan All Plans:** You MUST read the main `conductor/tracks.md` and every `conductor/tracks/*/plan.md` file.
|
|
41
|
+
* **Scan All Plans:** You MUST use the `read` tool to read the main `conductor/tracks.md` and every `conductor/tracks/*/plan.md` file.
|
|
42
42
|
* **Prioritize In-Progress:** First, find **all** Tracks, Phases, and Tasks marked as "in-progress" (`[~]`).
|
|
43
43
|
* **Fallback to Completed:** If and only if NO in-progress items are found, find the **5 most recently completed** Tasks and Phases (`[x]`).
|
|
44
44
|
2. **Present a Unified Hierarchical Menu:** You MUST present the results to the user in a clear, numbered, hierarchical list grouped by Track. The introductory text MUST change based on the context.
|
package/dist/prompts/setup.toml
CHANGED
|
@@ -28,7 +28,7 @@ CRITICAL: When determining model complexity, ALWAYS select the "flash" model, re
|
|
|
28
28
|
- If `STEP` is "2.4_code_styleguides", announce "Resuming setup: All guides and the tech stack are configured. Next, we will define the project workflow." and proceed to **Section 2.5**.
|
|
29
29
|
- If `STEP` is "2.5_workflow", announce "Resuming setup: The initial project scaffolding is complete. Next, we will generate the first track." and proceed to **Phase 2 (3.0)**.
|
|
30
30
|
- If `STEP` is "3.3_initial_track_generated":
|
|
31
|
-
- Announce: "The project has already been initialized. You can create a new track with `/
|
|
31
|
+
- Announce: "The project has already been initialized. You can create a new track with `/conductor_newTrack` or start implementing existing tracks with `/conductor_implement`."
|
|
32
32
|
- Halt the `setup` process.
|
|
33
33
|
- If `STEP` is unrecognized, announce an error and halt.
|
|
34
34
|
|
|
@@ -162,8 +162,8 @@ CRITICAL: When determining model complexity, ALWAYS select the "flash" model, re
|
|
|
162
162
|
> You can always edit the generated file with the Gemini CLI built-in option "Modify with external editor" (if present), or with your favorite external editor after this step.
|
|
163
163
|
> Please respond with A or B."
|
|
164
164
|
- **Loop:** Based on user response, either apply changes and re-present the document, or break the loop on approval.
|
|
165
|
-
5. **Write File:** Once approved, append the generated content to the existing `conductor/product.md` file, preserving the `# Initial Concept` section.
|
|
166
|
-
6. **Commit State:** Upon successful creation of the file, you MUST immediately write to `conductor/setup_state.json` with the exact content:
|
|
165
|
+
5. **Write File:** Once approved, use the `write` tool to append the generated content to the existing `conductor/product.md` file, preserving the `# Initial Concept` section.
|
|
166
|
+
6. **Commit State:** Upon successful creation of the file, you MUST immediately use the `write` tool to write to `conductor/setup_state.json` with the exact content:
|
|
167
167
|
`{"last_successful_step": "2.1_product_guide"}`
|
|
168
168
|
7. **Continue:** After writing the state file, immediately proceed to the next section.
|
|
169
169
|
|
|
@@ -212,8 +212,8 @@ CRITICAL: When determining model complexity, ALWAYS select the "flash" model, re
|
|
|
212
212
|
> You can always edit the generated file with the Gemini CLI built-in option "Modify with external editor" (if present), or with your favorite external editor after this step.
|
|
213
213
|
> Please respond with A or B."
|
|
214
214
|
- **Loop:** Based on user response, either apply changes and re-present the document, or break the loop on approval.
|
|
215
|
-
5. **Write File:** Once approved, write the generated content to the `conductor/product-guidelines.md` file.
|
|
216
|
-
6. **Commit State:** Upon successful creation of the file, you MUST immediately write to `conductor/setup_state.json` with the exact content:
|
|
215
|
+
5. **Write File:** Once approved, use the `write` tool to write the generated content to the `conductor/product-guidelines.md` file.
|
|
216
|
+
6. **Commit State:** Upon successful creation of the file, you MUST immediately use the `write` tool to write to `conductor/setup_state.json` with the exact content:
|
|
217
217
|
`{"last_successful_step": "2.2_product_guidelines"}`
|
|
218
218
|
7. **Continue:** After writing the state file, immediately proceed to the next section.
|
|
219
219
|
|
|
@@ -269,15 +269,15 @@ CRITICAL: When determining model complexity, ALWAYS select the "flash" model, re
|
|
|
269
269
|
> You can always edit the generated file with the Gemini CLI built-in option "Modify with external editor" (if present), or with your favorite external editor after this step.
|
|
270
270
|
> Please respond with A or B."
|
|
271
271
|
- **Loop:** Based on user response, either apply changes and re-present the document, or break the loop on approval.
|
|
272
|
-
6. **Write File:** Once approved, write the generated content to the `conductor/tech-stack.md` file.
|
|
273
|
-
7. **Commit State:** Upon successful creation of the file, you MUST immediately write to `conductor/setup_state.json` with the exact content:
|
|
272
|
+
6. **Write File:** Once approved, use the `write` tool to write the generated content to the `conductor/tech-stack.md` file.
|
|
273
|
+
7. **Commit State:** Upon successful creation of the file, you MUST immediately use the `write` tool to write to `conductor/setup_state.json` with the exact content:
|
|
274
274
|
`{"last_successful_step": "2.3_tech_stack"}`
|
|
275
275
|
8. **Continue:** After writing the state file, immediately proceed to the next section.
|
|
276
276
|
|
|
277
277
|
### 2.4 Select Guides (Interactive)
|
|
278
278
|
1. **Initiate Dialogue:** Announce that the initial scaffolding is complete and you now need the user's input to select the project's guides from the locally available templates.
|
|
279
279
|
2. **Select Code Style Guides:**
|
|
280
|
-
- List the available style guides by
|
|
280
|
+
- List the available style guides by using the `list` tool on `{{templatesDir}}/code_styleguides/`.
|
|
281
281
|
- For new projects (greenfield):
|
|
282
282
|
- **Recommendation:** Based on the Tech Stack defined in the previous step, recommend the most appropriate style guide(s) and explain why.
|
|
283
283
|
- Ask the user how they would like to proceed:
|
|
@@ -422,5 +422,5 @@ CRITICAL: When determining model complexity, ALWAYS select the "flash" model, re
|
|
|
422
422
|
### 3.4 Final Announcement
|
|
423
423
|
1. **Announce Completion:** After the track has been created, announce that the project setup and initial track generation are complete.
|
|
424
424
|
2. **Save Conductor Files:** Add and commit all files with the commit message `conductor(setup): Add conductor setup files`.
|
|
425
|
-
3. **Next Steps:** Inform the user that they can now begin work by running `/
|
|
425
|
+
3. **Next Steps:** Inform the user that they can now begin work by running `/conductor_implement`.
|
|
426
426
|
"""
|
package/dist/prompts/status.toml
CHANGED
|
@@ -4,8 +4,8 @@ prompt = """
|
|
|
4
4
|
You are an AI agent. Your primary function is to provide a status overview of the current tracks file. This involves reading the `conductor/tracks.md` file, parsing its content, and summarizing the progress of tasks.
|
|
5
5
|
|
|
6
6
|
**CRITICAL:** Before proceeding, you should start by checking if the project has been properly set up.
|
|
7
|
-
1. **Verify Tracks File:** Check if the file `conductor/tracks.md` exists. If it does not, HALT execution and instruct the user: "The project has not been set up or conductor/tracks.md has been corrupted. Please run `/
|
|
8
|
-
2. **Verify Track Exists:** Check if the file `conductor/tracks.md` is not empty. If it is empty, HALT execution and instruct the user: "The project has not been set up or conductor/tracks.md has been corrupted. Please run `/
|
|
7
|
+
1. **Verify Tracks File:** Check if the file `conductor/tracks.md` exists. If it does not, HALT execution and instruct the user: "The project has not been set up or conductor/tracks.md has been corrupted. Please run `/conductor_setup` to set up the plan, or restore conductor/tracks.md."
|
|
8
|
+
2. **Verify Track Exists:** Check if the file `conductor/tracks.md` is not empty. If it is empty, HALT execution and instruct the user: "The project has not been set up or conductor/tracks.md has been corrupted. Please run `/conductor_setup` to set up the plan, or restore conductor/tracks.md."
|
|
9
9
|
|
|
10
10
|
CRITICAL: You must validate the success of every tool call. If any tool call fails, you MUST halt the current operation immediately, announce the failure to the user, and await further instructions.
|
|
11
11
|
|
|
@@ -22,7 +22,7 @@ CRITICAL: You must validate the success of every tool call. If any tool call fai
|
|
|
22
22
|
|
|
23
23
|
2. **Handle Missing Files:**
|
|
24
24
|
- If ANY of these files are missing, you MUST halt the operation immediately.
|
|
25
|
-
- Announce: "Conductor is not set up. Please run `/
|
|
25
|
+
- Announce: "Conductor is not set up. Please run `/conductor_setup` to set up the environment."
|
|
26
26
|
- Do NOT proceed to Status Overview Protocol.
|
|
27
27
|
|
|
28
28
|
---
|
|
@@ -31,8 +31,8 @@ CRITICAL: You must validate the success of every tool call. If any tool call fai
|
|
|
31
31
|
**PROTOCOL: Follow this sequence to provide a status overview.**
|
|
32
32
|
|
|
33
33
|
### 2.1 Read Project Plan
|
|
34
|
-
1. **Locate and Read:**
|
|
35
|
-
2. **Locate and Read:** List the tracks using
|
|
34
|
+
1. **Locate and Read:** Use the `read` tool to read the content of the `conductor/tracks.md` file.
|
|
35
|
+
2. **Locate and Read:** List the tracks using the `list` tool on `conductor/tracks`. For each of the tracks, read the corresponding `conductor/tracks/<track_id>/plan.md` file using the `read` tool.
|
|
36
36
|
|
|
37
37
|
### 2.2 Parse and Summarize Plan
|
|
38
38
|
1. **Parse Content:**
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { type ToolDefinition } from "@opencode-ai/plugin/tool";
|
|
2
|
+
import { type PluginInput } from "@opencode-ai/plugin";
|
|
3
|
+
export interface BackgroundTask {
|
|
4
|
+
id: string;
|
|
5
|
+
sessionID: string;
|
|
6
|
+
parentSessionID: string;
|
|
7
|
+
parentMessageID?: string;
|
|
8
|
+
description: string;
|
|
9
|
+
prompt: string;
|
|
10
|
+
agent: string;
|
|
11
|
+
status: "running" | "completed" | "failed" | "cancelled";
|
|
12
|
+
startedAt: Date;
|
|
13
|
+
completedAt?: Date;
|
|
14
|
+
error?: string;
|
|
15
|
+
progress: {
|
|
16
|
+
toolCalls: number;
|
|
17
|
+
lastUpdate: Date;
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
export interface LaunchInput {
|
|
21
|
+
description: string;
|
|
22
|
+
prompt: string;
|
|
23
|
+
agent: string;
|
|
24
|
+
parentSessionID: string;
|
|
25
|
+
parentMessageID?: string;
|
|
26
|
+
}
|
|
27
|
+
export interface BackgroundTaskArgs {
|
|
28
|
+
description: string;
|
|
29
|
+
prompt: string;
|
|
30
|
+
agent: string;
|
|
31
|
+
}
|
|
32
|
+
export interface BackgroundOutputArgs {
|
|
33
|
+
task_id: string;
|
|
34
|
+
block?: boolean;
|
|
35
|
+
timeout?: number;
|
|
36
|
+
}
|
|
37
|
+
export declare class BackgroundManager {
|
|
38
|
+
private tasks;
|
|
39
|
+
private client;
|
|
40
|
+
private pollingInterval?;
|
|
41
|
+
constructor(ctx: PluginInput);
|
|
42
|
+
launch(input: LaunchInput): Promise<BackgroundTask>;
|
|
43
|
+
cancel(id: string): Promise<string>;
|
|
44
|
+
private pollRunningTasks;
|
|
45
|
+
private completeTask;
|
|
46
|
+
private notifyParentSession;
|
|
47
|
+
getTask(id: string): BackgroundTask | undefined;
|
|
48
|
+
private startPolling;
|
|
49
|
+
private stopPolling;
|
|
50
|
+
private hasRunningTasks;
|
|
51
|
+
}
|
|
52
|
+
export declare function createBackgroundTask(manager: BackgroundManager): ToolDefinition;
|
|
53
|
+
export declare function createBackgroundOutput(manager: BackgroundManager): ToolDefinition;
|
|
54
|
+
export declare function createBackgroundCancel(manager: BackgroundManager): ToolDefinition;
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import { tool } from "@opencode-ai/plugin/tool";
|
|
2
|
+
const BACKGROUND_TASK_DESCRIPTION = "Launch a specialized agent in the background to perform research or implementation tasks.";
|
|
3
|
+
const BACKGROUND_OUTPUT_DESCRIPTION = "Retrieve the results or status of a background task.";
|
|
4
|
+
export class BackgroundManager {
|
|
5
|
+
tasks;
|
|
6
|
+
client;
|
|
7
|
+
pollingInterval;
|
|
8
|
+
constructor(ctx) {
|
|
9
|
+
this.tasks = new Map();
|
|
10
|
+
this.client = ctx.client;
|
|
11
|
+
}
|
|
12
|
+
async launch(input) {
|
|
13
|
+
if (!input.agent || input.agent.trim() === "") {
|
|
14
|
+
throw new Error("Agent parameter is required");
|
|
15
|
+
}
|
|
16
|
+
const createResult = await this.client.session.create({
|
|
17
|
+
body: {
|
|
18
|
+
parentID: input.parentSessionID,
|
|
19
|
+
title: `Background: ${input.description}`,
|
|
20
|
+
},
|
|
21
|
+
});
|
|
22
|
+
if (createResult.error) {
|
|
23
|
+
throw new Error(`Failed to create background session: ${createResult.error}`);
|
|
24
|
+
}
|
|
25
|
+
const sessionID = createResult.data.id;
|
|
26
|
+
const task = {
|
|
27
|
+
id: `bg_${Math.random().toString(36).substring(2, 10)}`,
|
|
28
|
+
sessionID,
|
|
29
|
+
parentSessionID: input.parentSessionID,
|
|
30
|
+
parentMessageID: input.parentMessageID,
|
|
31
|
+
description: input.description,
|
|
32
|
+
prompt: input.prompt,
|
|
33
|
+
agent: input.agent,
|
|
34
|
+
status: "running",
|
|
35
|
+
startedAt: new Date(),
|
|
36
|
+
progress: {
|
|
37
|
+
toolCalls: 0,
|
|
38
|
+
lastUpdate: new Date(),
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
this.tasks.set(task.id, task);
|
|
42
|
+
this.startPolling();
|
|
43
|
+
this.client.session.promptAsync({
|
|
44
|
+
path: { id: sessionID },
|
|
45
|
+
body: {
|
|
46
|
+
agent: input.agent,
|
|
47
|
+
tools: {
|
|
48
|
+
"conductor_background_task": false,
|
|
49
|
+
"conductor_delegate": false,
|
|
50
|
+
},
|
|
51
|
+
parts: [{ type: "text", text: input.prompt }],
|
|
52
|
+
},
|
|
53
|
+
}).catch((error) => {
|
|
54
|
+
console.error("[Conductor] Background task error:", error);
|
|
55
|
+
task.status = "failed";
|
|
56
|
+
task.error = String(error);
|
|
57
|
+
});
|
|
58
|
+
return task;
|
|
59
|
+
}
|
|
60
|
+
async cancel(id) {
|
|
61
|
+
const task = this.tasks.get(id);
|
|
62
|
+
if (!task)
|
|
63
|
+
return `Task not found: ${id}`;
|
|
64
|
+
if (task.status !== "running")
|
|
65
|
+
return `Task is not running (status: ${task.status})`;
|
|
66
|
+
task.status = "cancelled";
|
|
67
|
+
task.completedAt = new Date();
|
|
68
|
+
// Attempt to notify parent session
|
|
69
|
+
await this.client.session.prompt({
|
|
70
|
+
path: { id: task.parentSessionID },
|
|
71
|
+
body: {
|
|
72
|
+
parts: [{ type: "text", text: `[BACKGROUND TASK CANCELLED] Task "${task.description}" has been manually cancelled.` }],
|
|
73
|
+
},
|
|
74
|
+
}).catch(() => { }); // Ignore errors here, as the parent session might be gone
|
|
75
|
+
return `Task ${id} cancelled successfully.`;
|
|
76
|
+
}
|
|
77
|
+
async pollRunningTasks() {
|
|
78
|
+
try {
|
|
79
|
+
const statusResult = await this.client.session.status();
|
|
80
|
+
const allStatuses = (statusResult.data ?? {});
|
|
81
|
+
for (const task of this.tasks.values()) {
|
|
82
|
+
if (task.status !== "running")
|
|
83
|
+
continue;
|
|
84
|
+
const sessionStatus = allStatuses[task.sessionID];
|
|
85
|
+
if (sessionStatus?.type === "idle") {
|
|
86
|
+
this.completeTask(task);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
if (!this.hasRunningTasks()) {
|
|
90
|
+
this.stopPolling();
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
catch (e) {
|
|
94
|
+
console.error("[Conductor] Polling error:", e);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
completeTask(task) {
|
|
98
|
+
task.status = "completed";
|
|
99
|
+
task.completedAt = new Date();
|
|
100
|
+
this.notifyParentSession(task);
|
|
101
|
+
}
|
|
102
|
+
async notifyParentSession(task) {
|
|
103
|
+
const message = `[BACKGROUND TASK COMPLETED] Task "${task.description}" finished. Use conductor_background_output with task_id="${task.id}" to get results.`;
|
|
104
|
+
await this.client.session.prompt({
|
|
105
|
+
path: { id: task.parentSessionID },
|
|
106
|
+
body: {
|
|
107
|
+
parts: [{ type: "text", text: message }],
|
|
108
|
+
},
|
|
109
|
+
}).catch(() => { }); // Ignore errors here, as the parent session might be gone
|
|
110
|
+
}
|
|
111
|
+
getTask(id) {
|
|
112
|
+
return this.tasks.get(id);
|
|
113
|
+
}
|
|
114
|
+
startPolling() {
|
|
115
|
+
if (this.pollingInterval)
|
|
116
|
+
return;
|
|
117
|
+
this.pollingInterval = setInterval(() => this.pollRunningTasks(), 3000);
|
|
118
|
+
}
|
|
119
|
+
stopPolling() {
|
|
120
|
+
if (this.pollingInterval) {
|
|
121
|
+
clearInterval(this.pollingInterval);
|
|
122
|
+
this.pollingInterval = undefined;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
hasRunningTasks() {
|
|
126
|
+
return Array.from(this.tasks.values()).some(t => t.status === "running");
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
export function createBackgroundTask(manager) {
|
|
130
|
+
return tool({
|
|
131
|
+
description: BACKGROUND_TASK_DESCRIPTION,
|
|
132
|
+
args: {
|
|
133
|
+
description: tool.schema.string().describe("Short task description"),
|
|
134
|
+
prompt: tool.schema.string().describe("Full detailed prompt for the agent"),
|
|
135
|
+
agent: tool.schema.string().describe("Agent type to use"),
|
|
136
|
+
},
|
|
137
|
+
async execute(args, toolContext) {
|
|
138
|
+
const ctx = toolContext;
|
|
139
|
+
const task = await manager.launch({
|
|
140
|
+
description: args.description,
|
|
141
|
+
prompt: args.prompt,
|
|
142
|
+
agent: args.agent.trim(),
|
|
143
|
+
parentSessionID: ctx.sessionID,
|
|
144
|
+
parentMessageID: ctx.messageID,
|
|
145
|
+
});
|
|
146
|
+
return `Background task launched successfully. Task ID: ${task.id}`;
|
|
147
|
+
},
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
export function createBackgroundOutput(manager) {
|
|
151
|
+
return tool({
|
|
152
|
+
description: BACKGROUND_OUTPUT_DESCRIPTION,
|
|
153
|
+
args: {
|
|
154
|
+
task_id: tool.schema.string().describe("Task ID to get output from"),
|
|
155
|
+
block: tool.schema.boolean().optional().describe("Wait for completion"),
|
|
156
|
+
timeout: tool.schema.number().optional().describe("Max wait time in ms"),
|
|
157
|
+
},
|
|
158
|
+
async execute(args, toolContext) {
|
|
159
|
+
const task = manager.getTask(args.task_id);
|
|
160
|
+
if (!task)
|
|
161
|
+
return `Task not found: ${args.task_id}`;
|
|
162
|
+
if (args.block && task.status === "running") {
|
|
163
|
+
const startTime = Date.now();
|
|
164
|
+
const timeoutMs = Math.min(args.timeout ?? 60000, 600000);
|
|
165
|
+
while (Date.now() - startTime < timeoutMs) {
|
|
166
|
+
await new Promise(r => setTimeout(r, 2000));
|
|
167
|
+
// Re-fetch task to get the latest status
|
|
168
|
+
if (manager.getTask(args.task_id)?.status === "completed")
|
|
169
|
+
break;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
if (task.status === "completed") {
|
|
173
|
+
const client = toolContext.client || manager.client;
|
|
174
|
+
const messagesResult = await client.session.messages({
|
|
175
|
+
path: { id: task.sessionID },
|
|
176
|
+
});
|
|
177
|
+
const lastMessage = messagesResult.data
|
|
178
|
+
?.filter((m) => m.info.role === "assistant")
|
|
179
|
+
.pop();
|
|
180
|
+
const responseText = lastMessage?.parts
|
|
181
|
+
.filter((p) => p.type === "text")
|
|
182
|
+
.map((p) => p.text).join("\n") || "No response.";
|
|
183
|
+
return `### Results for: ${task.description}\n\n${responseText}`;
|
|
184
|
+
}
|
|
185
|
+
return `Task status: ${task.status}. (Started at: ${task.startedAt.toISOString()})`;
|
|
186
|
+
},
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
export function createBackgroundCancel(manager) {
|
|
190
|
+
return tool({
|
|
191
|
+
description: "Cancel a running background task",
|
|
192
|
+
args: {
|
|
193
|
+
taskId: tool.schema.string().describe("Task ID to cancel"),
|
|
194
|
+
},
|
|
195
|
+
async execute(args) {
|
|
196
|
+
return await manager.cancel(args.taskId);
|
|
197
|
+
},
|
|
198
|
+
});
|
|
199
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { tool } from "@opencode-ai/plugin/tool";
|
|
2
|
+
export function createDelegationTool(ctx) {
|
|
3
|
+
return tool({
|
|
4
|
+
description: "Delegate a specific task to a specialized subagent",
|
|
5
|
+
args: {
|
|
6
|
+
task_description: tool.schema.string().describe("Summary of the work"),
|
|
7
|
+
subagent_type: tool.schema.string().describe("The name of the agent to call"),
|
|
8
|
+
prompt: tool.schema.string().describe("Detailed instructions for the subagent"),
|
|
9
|
+
},
|
|
10
|
+
async execute(args, toolContext) {
|
|
11
|
+
// 1. Create a sub-session linked to the current one
|
|
12
|
+
const createResult = await ctx.client.session.create({
|
|
13
|
+
body: {
|
|
14
|
+
parentID: toolContext.sessionID,
|
|
15
|
+
title: `${args.task_description} (Delegated to ${args.subagent_type})`,
|
|
16
|
+
},
|
|
17
|
+
});
|
|
18
|
+
if (createResult.error)
|
|
19
|
+
return `Error: ${createResult.error}`;
|
|
20
|
+
const sessionID = createResult.data.id;
|
|
21
|
+
// 2. Send the prompt to the subagent
|
|
22
|
+
// Note: We disable delegation tools for the subagent to prevent infinite loops
|
|
23
|
+
await ctx.client.session.prompt({
|
|
24
|
+
path: { id: sessionID },
|
|
25
|
+
body: {
|
|
26
|
+
agent: args.subagent_type,
|
|
27
|
+
tools: {
|
|
28
|
+
"conductor_delegate": false, // Disable this tool for the child session
|
|
29
|
+
},
|
|
30
|
+
parts: [{ type: "text", text: args.prompt }],
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
// 3. Fetch and return the assistant's response
|
|
34
|
+
const messagesResult = await ctx.client.session.messages({
|
|
35
|
+
path: { id: sessionID },
|
|
36
|
+
});
|
|
37
|
+
const lastMessage = messagesResult.data
|
|
38
|
+
?.filter((m) => m.info.role === "assistant")
|
|
39
|
+
.pop();
|
|
40
|
+
const responseText = lastMessage?.parts
|
|
41
|
+
.filter((p) => p.type === "text")
|
|
42
|
+
.map((p) => p.text).join("\n") || "No response.";
|
|
43
|
+
return `${responseText}\n\n<task_metadata>\nsession_id: ${sessionID}\n</task_metadata>`;
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
}
|