opencode-conductor-plugin 1.16.0 → 1.17.0

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/dist/index.js CHANGED
@@ -1,8 +1,9 @@
1
1
  import { join, dirname } from "path";
2
- import { homedir } from "os";
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
- // 1. Detect oh-my-opencode for synergy features
20
- const configPath = join(homedir(), ".config", "opencode", "opencode.json");
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
- // 3. Load Strategies
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
- // 4. Load all Command Prompts (Parallel)
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,13 +68,25 @@ const ConductorPlugin = async (ctx) => {
81
68
  loadPrompt("revert.toml", { target: "$ARGUMENTS" }),
82
69
  safeRead(join(ctx.directory, "conductor", "workflow.md")),
83
70
  ]);
84
- // 5. Extract Agent Prompt
85
- const agentMd = await readFile(join(__dirname, "prompts", "agent", "conductor.md"), "utf-8");
86
- const agentPrompt = agentMd.split("---").pop()?.trim() || "";
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
- console.log("[Conductor] config handler: Merging commands and agent...");
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": {
@@ -103,6 +102,7 @@ const ConductorPlugin = async (ctx) => {
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,
@@ -118,16 +118,62 @@ const ConductorPlugin = async (ctx) => {
118
118
  config.agent = {
119
119
  ...(config.agent || {}),
120
120
  conductor: {
121
- description: "Spec-Driven Development Architect.",
121
+ description: "Conductor Protocol Steward.",
122
122
  mode: "primary",
123
- prompt: agentPrompt + workflowMd,
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
- "call_omo_agent",
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 Architect.\n";
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`.\n";
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
- conductor_setup: allow
6
- conductor_new_track: allow
7
- conductor_implement: allow
8
- conductor_status: allow
9
- conductor_revert: allow
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**, a specialized AI agent for project management and architectural planning using the **Conductor methodology**.
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 software development follows a rigorous, context-driven lifecycle: **Context -> Spec & Plan -> Implement**.
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. **Project Stewardship**: Maintain the `conductor/` directory as the "Source of Truth" for the project's product vision, technology stack, and development workflow.
20
- 2. **Interactive Scaffolding**: Guide the user through the `conductor_setup` process to define project foundations.
21
- 3. **Meticulous Planning**: Help the user create new "Tracks" (features or bug fixes) using `conductor_new_track`. You must ask clarifying questions to build a high-quality `spec.md` before generating a `plan.md`.
22
- 4. **Loop Protection**: When in an interactive questioning phase, you MUST NOT create OpenCode todos or background tasks that could trigger infinite continuation loops. Your focus is on the current dialogue.
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 Protocol
32
+ ## Operating Principles
26
33
 
27
- - **Model Selection**: You prefer "flash" models for efficiency and speed during planning and tool orchestration.
28
- - **Protocol First**: Never start implementing code until a Track has an approved Spec and Plan.
29
- - **Collaboration**: You work alongside the user. When in doubt about an architectural choice or product goal, always ask for clarification.
30
- - **Synergy with Sisyphus**: If the user is using `oh-my-opencode`, you act as the Technical Lead/Architect. You can delegate UI work to `@frontend-ui-ux-engineer` or documentation to `@document-writer` if their roles are available. There are plenty of agents to choose from to help in different regards
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.
@@ -29,7 +29,7 @@ CRITICAL: You must validate the success of every tool call. If any tool call fai
29
29
 
30
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:** 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.
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`
@@ -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
- * Read the confirmed `spec.md` content for this track.
94
- * Read the selected workflow file from `conductor/workflow.md`.
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
- * Write the confirmed specification content to `conductor/tracks/<track_id>/spec.md`.
129
- * Write the confirmed plan content to `conductor/tracks/<track_id>/plan.md`.
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:
@@ -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.
@@ -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 running `ls {{templatesDir}}/code_styleguides/`.
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:
@@ -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:** Read the content of the `conductor/tracks.md` file.
35
- 2. **Locate and Read:** List the tracks using shell command `ls conductor/tracks`. For each of the tracks, read the corresponding `conductor/tracks/<track_id>/plan.md` file.
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,3 @@
1
+ import { type PluginInput } from "@opencode-ai/plugin";
2
+ import { type ToolDefinition } from "@opencode-ai/plugin/tool";
3
+ export declare function createDelegationTool(ctx: PluginInput): ToolDefinition;
@@ -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
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-conductor-plugin",
3
- "version": "1.16.0",
3
+ "version": "1.17.0",
4
4
  "description": "Conductor plugin for OpenCode",
5
5
  "type": "module",
6
6
  "repository": "derekbar90/opencode-conductor",