opencode-conductor-plugin 1.27.0 → 1.28.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,94 +1,206 @@
1
- import { setupCommand } from "./commands/setup.js";
2
- import { newTrackCommand } from "./commands/newTrack.js";
3
- import { implementCommand } from "./commands/implement.js";
4
- import { statusCommand } from "./commands/status.js";
5
- import { revertCommand } from "./commands/revert.js";
6
1
  import { join, dirname } from "path";
7
- import { homedir } from "os";
8
- import { existsSync, readFileSync } from "fs";
2
+ import { existsSync } from "fs";
9
3
  import { readFile } from "fs/promises";
10
4
  import { fileURLToPath } from "url";
5
+ import { createDelegationTool } from "./tools/delegate.js";
6
+ import { BackgroundManager, createBackgroundTask, createBackgroundOutput, createBackgroundCancel, } from "./tools/background.js";
11
7
  const __filename = fileURLToPath(import.meta.url);
12
8
  const __dirname = dirname(__filename);
13
- const ConductorPlugin = async (ctx) => {
14
- // Detect oh-my-opencode for synergy features
15
- const configPath = join(homedir(), ".config", "opencode", "opencode.json");
16
- let isOMOActive = false;
9
+ const safeRead = async (path) => {
17
10
  try {
18
- if (existsSync(configPath)) {
19
- const config = JSON.parse(readFileSync(configPath, "utf-8"));
20
- isOMOActive = config.plugin?.some((p) => p.includes("oh-my-opencode"));
21
- }
22
- }
23
- catch (e) {
24
- // Fallback to filesystem check if config read fails
25
- const omoPath = join(homedir(), ".config", "opencode", "node_modules", "oh-my-opencode");
26
- isOMOActive = existsSync(omoPath);
11
+ if (existsSync(path))
12
+ return await readFile(path, "utf-8");
27
13
  }
28
- console.log(`[Conductor] Plugin tools loaded. (OMO Synergy: ${isOMOActive ? "Enabled" : "Disabled"})`);
29
- const extendedCtx = { ...ctx, isOMOActive };
30
- return {
31
- commands: {
32
- setup: setupCommand(extendedCtx),
33
- newTrack: newTrackCommand(extendedCtx),
34
- implement: implementCommand(extendedCtx),
35
- status: statusCommand(extendedCtx),
36
- revert: revertCommand(extendedCtx),
37
- },
38
- "tool.execute.before": async (input, output) => {
39
- // INTERCEPT: Sisyphus Delegation Hook
40
- // Purpose: Automatically inject the full Conductor context (Plan, Spec, Workflow, Protocol)
41
- // whenever the Conductor delegates a task to Sisyphus. This ensures Sisyphus has "Engineering Authority"
42
- // without needing the LLM to manually copy-paste huge context blocks.
43
- if (input.tool === "delegate_to_agent") {
44
- const agentName = (output.args.agent_name || output.args.agent || "").toLowerCase();
45
- if (agentName.includes("sisyphus")) {
46
- console.log("[Conductor] Intercepting Sisyphus delegation. Injecting Context Packet...");
14
+ catch (e) { }
15
+ return null;
16
+ };
17
+ const ConductorPlugin = async (ctx) => {
18
+ try {
19
+ console.log("[Conductor] Initializing plugin...");
20
+ const backgroundManager = new BackgroundManager(ctx);
21
+ // 1. Helper to load and process prompt templates (Manual TOML Parsing)
22
+ const loadPrompt = async (filename, replacements = {}) => {
23
+ const promptPath = join(__dirname, "prompts", filename);
24
+ try {
25
+ const content = await readFile(promptPath, "utf-8");
26
+ const descMatch = content.match(/description\s*=\s*\"([^\"]+)\"/);
27
+ const description = descMatch ? descMatch[1] : "Conductor Command";
28
+ const promptMatch = content.match(/prompt\s*=\s*\"\"\"([\s\S]*?)\"\"\"/);
29
+ let promptText = promptMatch ? promptMatch[1] : "";
30
+ if (!promptText)
31
+ throw new Error(`Could not parse prompt text from ${filename}`);
32
+ const defaults = {
33
+ templatesDir: join(dirname(__dirname), "templates"),
34
+ };
35
+ const finalReplacements = { ...defaults, ...replacements };
36
+ for (const [key, value] of Object.entries(finalReplacements)) {
37
+ promptText = promptText.replaceAll(`{{${key}}}`, value || "");
38
+ }
39
+ return { prompt: promptText, description: description };
40
+ }
41
+ catch (error) {
42
+ console.error(`[Conductor] Error loading prompt ${filename}:`, error);
43
+ return {
44
+ prompt: `SYSTEM ERROR: Failed to load prompt ${filename}`,
45
+ description: "Error loading command",
46
+ };
47
+ }
48
+ };
49
+ // 2. Load all Command Prompts (Parallel)
50
+ const [setup, newTrack, implement, status, revert] = await Promise.all([
51
+ loadPrompt("setup.toml"),
52
+ loadPrompt("newTrack.toml", { args: "$ARGUMENTS" }),
53
+ loadPrompt("implement.toml", {
54
+ track_name: "$ARGUMENTS",
55
+ }),
56
+ loadPrompt("status.toml"),
57
+ loadPrompt("revert.toml", { target: "$ARGUMENTS" }),
58
+ ]);
59
+ // 3. Extract Agent Prompts
60
+ const [conductorMd, implementerMd] = await Promise.all([
61
+ readFile(join(__dirname, "prompts", "agent", "conductor.md"), "utf-8"),
62
+ readFile(join(__dirname, "prompts", "agent", "implementer.md"), "utf-8"),
63
+ ]);
64
+ const conductorPrompt = conductorMd.split("---").pop()?.trim() || "";
65
+ const implementerPrompt = implementerMd.split("---").pop()?.trim() || "";
66
+ console.log("[Conductor] All components ready. Injecting config...");
67
+ return {
68
+ tool: {
69
+ ...(ctx.client.tool || {}),
70
+ "conductor_delegate": createDelegationTool(ctx),
71
+ "conductor_bg_task": createBackgroundTask(backgroundManager),
72
+ "conductor_bg_output": createBackgroundOutput(backgroundManager),
73
+ "conductor_bg_cancel": createBackgroundCancel(backgroundManager),
74
+ },
75
+ config: async (config) => {
76
+ if (!config)
77
+ return;
78
+ console.log("[Conductor] config handler: Merging commands and agents...");
79
+ config.command = {
80
+ ...(config.command || {}),
81
+ "conductor_setup": {
82
+ template: setup.prompt,
83
+ description: setup.description,
84
+ agent: "conductor",
85
+ },
86
+ "conductor_newTrack": {
87
+ template: newTrack.prompt,
88
+ description: newTrack.description,
89
+ agent: "conductor",
90
+ },
91
+ "conductor_implement": {
92
+ template: implement.prompt,
93
+ description: implement.description,
94
+ agent: "conductor_implementer",
95
+ },
96
+ "conductor_status": {
97
+ template: status.prompt,
98
+ description: status.description,
99
+ agent: "conductor",
100
+ },
101
+ "conductor_revert": {
102
+ template: revert.prompt,
103
+ description: revert.description,
104
+ agent: "conductor",
105
+ },
106
+ };
107
+ config.agent = {
108
+ ...(config.agent || {}),
109
+ conductor: {
110
+ description: "Conductor Protocol Steward.",
111
+ mode: "primary",
112
+ prompt: conductorPrompt,
113
+ permission: {
114
+ '*': 'allow',
115
+ read: {
116
+ "*": "allow",
117
+ "*.env": "deny",
118
+ "*.env.*": "deny",
119
+ "*.env.example": "allow",
120
+ },
121
+ edit: "allow",
122
+ bash: "allow",
123
+ grep: "allow",
124
+ glob: "allow",
125
+ list: "allow",
126
+ lsp: "allow",
127
+ todoread: "allow",
128
+ todowrite: "allow",
129
+ webfetch: "allow",
130
+ external_directory: "deny",
131
+ doom_loop: "ask",
132
+ },
133
+ },
134
+ conductor_implementer: {
135
+ description: "Conductor Protocol Implementer.",
136
+ mode: "primary",
137
+ prompt: implementerPrompt,
138
+ permission: {
139
+ '*': 'allow',
140
+ read: {
141
+ "*": "allow",
142
+ "*.env": "deny",
143
+ "*.env.*": "deny",
144
+ "*.env.example": "allow",
145
+ },
146
+ edit: "allow",
147
+ bash: "allow",
148
+ grep: "allow",
149
+ glob: "allow",
150
+ list: "allow",
151
+ lsp: "allow",
152
+ todoread: "allow",
153
+ todowrite: "allow",
154
+ webfetch: "allow",
155
+ "conductor_delegate": "allow",
156
+ "conductor_bg_task": "allow",
157
+ "conductor_bg_output": "allow",
158
+ "conductor_bg_cancel": "allow",
159
+ external_directory: "deny",
160
+ doom_loop: "ask",
161
+ },
162
+ },
163
+ };
164
+ },
165
+ "tool.execute.before": async (input, output) => {
166
+ const delegationTools = [
167
+ "delegate_to_agent",
168
+ "task",
169
+ "background_task",
170
+ "conductor_delegate",
171
+ "conductor_bg_task",
172
+ ];
173
+ if (delegationTools.includes(input.tool)) {
47
174
  const conductorDir = join(ctx.directory, "conductor");
48
- const promptsDir = join(__dirname, "prompts");
49
- // Helper to safely read file content
50
- const safeRead = async (path) => {
51
- try {
52
- if (existsSync(path))
53
- return await readFile(path, "utf-8");
54
- }
55
- catch (e) { /* ignore */ }
56
- return null;
57
- };
58
- // 1. Read Project Context Files
59
- // We need to find the active track to get the correct spec/plan.
60
- // Since we don't know the track ID easily here, we look for the 'plan.md' that might be in the args
61
- // OR we just rely on the Conductor having already done the setup.
62
- // WAIT: We can't easily guess the track ID here.
63
- // BETTER APPROACH: We rely on the generic 'conductor/workflow.md' and 'prompts/implement.toml'.
64
- // For 'spec.md' and 'plan.md', the Conductor usually puts the path in the message.
65
- // However, to be robust, we will read the GLOBAL workflow and the IMPLEMENT prompt.
66
- // We will explicitly inject the IMPLEMENT PROMPT as requested.
67
- const implementToml = await safeRead(join(promptsDir, "definitions", "implement.toml"));
68
175
  const workflowMd = await safeRead(join(conductorDir, "workflow.md"));
69
- // Construct the injection block
70
- let injection = "\n\n--- [SYSTEM INJECTION: CONDUCTOR CONTEXT PACKET] ---\n";
71
- injection += "You are receiving this task from the Conductor Architect.\n";
72
- if (implementToml) {
73
- injection += "\n### 1. ARCHITECTURAL PROTOCOL (Reference Only)\n";
74
- injection += "Use this protocol to understand the project's rigorous standards. DO NOT restart the project management lifecycle (e.g. track selection).\n";
75
- injection += "```toml\n" + implementToml + "\n```\n";
76
- }
77
176
  if (workflowMd) {
78
- injection += "\n### 2. DEVELOPMENT WORKFLOW\n";
79
- injection += "Follow these TDD and Commit rules precisely.\n";
80
- injection += "```markdown\n" + workflowMd + "\n```\n";
177
+ let injection = "\n\n--- [SYSTEM INJECTION: CONDUCTOR CONTEXT PACKET] ---\n";
178
+ injection += "You are receiving this task from the Conductor.\n";
179
+ injection += "You MUST adhere to the following project workflow rules:\n";
180
+ injection += "\n### DEVELOPMENT WORKFLOW\n" + workflowMd + "\n";
181
+ if (implement?.prompt) {
182
+ injection += "\n### IMPLEMENTATION PROTOCOL\n" + implement.prompt + "\n";
183
+ }
184
+ injection += "\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";
185
+ injection += "--- [END INJECTION] ---\n";
186
+ // Inject into the primary instruction field depending on the tool's schema
187
+ if (output.args && typeof output.args.objective === "string") {
188
+ output.args.objective += injection;
189
+ }
190
+ else if (output.args && typeof output.args.prompt === "string") {
191
+ output.args.prompt += injection;
192
+ }
193
+ else if (output.args && typeof output.args.instruction === "string") {
194
+ output.args.instruction += injection;
195
+ }
81
196
  }
82
- injection += "\n### 3. DELEGATED AUTHORITY\n";
83
- injection += "- **EXECUTE:** Implement the requested task using the Workflow.\n";
84
- injection += "- **REFINE:** You have authority to update `plan.md` if it is flawed.\n";
85
- injection += "- **ESCALATE:** If you modify the Plan or Spec, report 'PLAN_UPDATED' immediately.\n";
86
- injection += "--- [END INJECTION] ---\n";
87
- // Append to the objective
88
- output.args.objective += injection;
89
197
  }
90
- }
91
- }
92
- };
198
+ },
199
+ };
200
+ }
201
+ catch (err) {
202
+ console.error("[Conductor] FATAL: Plugin initialization failed:", err);
203
+ throw err;
204
+ }
93
205
  };
94
206
  export default ConductorPlugin;
@@ -6,12 +6,12 @@ import { readFile } from "fs/promises";
6
6
  const __filename = fileURLToPath(import.meta.url);
7
7
  const __dirname = dirname(__filename);
8
8
  export const setupCommand = createConductorCommand({
9
- name: "legacy/conductor/commands/conductor/setup.toml",
9
+ name: "setup.toml",
10
10
  description: "Directives lookup tool for scaffolding the project and setting up the Conductor environment",
11
11
  args: {},
12
12
  });
13
13
  export const newTrackCommand = createConductorCommand({
14
- name: "legacy/conductor/commands/conductor/newTrack.toml",
14
+ name: "newTrack.toml",
15
15
  description: "Directives lookup tool for planning a track, generating track-specific spec documents and updating the tracks file",
16
16
  args: {
17
17
  description: tool.schema.string().optional().describe("Brief description of the track (feature, bug fix, chore, etc.)"),
@@ -23,7 +23,7 @@ export const newTrackCommand = createConductorCommand({
23
23
  },
24
24
  });
25
25
  export const implementCommand = createConductorCommand({
26
- name: "legacy/conductor/commands/conductor/implement.toml",
26
+ name: "implement.toml",
27
27
  description: "Directives lookup tool for executing the tasks defined in the specified track's plan",
28
28
  args: {
29
29
  track_name: tool.schema.string().optional().describe("Name or description of the track to implement"),
@@ -47,12 +47,12 @@ export const implementCommand = createConductorCommand({
47
47
  },
48
48
  });
49
49
  export const statusCommand = createConductorCommand({
50
- name: "legacy/conductor/commands/conductor/status.toml",
50
+ name: "status.toml",
51
51
  description: "Directives lookup tool for displaying the current progress of the project",
52
52
  args: {},
53
53
  });
54
54
  export const revertCommand = createConductorCommand({
55
- name: "legacy/conductor/commands/conductor/revert.toml",
55
+ name: "revert.toml",
56
56
  description: "Directives lookup tool for reverting previous work",
57
57
  args: {
58
58
  target: tool.schema.string().optional().describe("Target to revert (e.g., 'track <track_id>', 'phase <phase_name>', 'task <task_name>')"),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-conductor-plugin",
3
- "version": "1.27.0",
3
+ "version": "1.28.0",
4
4
  "description": "Conductor plugin for OpenCode",
5
5
  "type": "module",
6
6
  "repository": "derekbar90/opencode-conductor",
@@ -31,9 +31,10 @@
31
31
  "test": "vitest run",
32
32
  "postinstall": "node scripts/postinstall.cjs",
33
33
  "build": "tsc && npm run copy-prompts && npm run copy-templates",
34
- "copy-prompts": "mkdir -p dist/prompts && cp src/prompts/*.md dist/prompts/ && mkdir -p dist/prompts/agent && cp src/prompts/agent/*.md dist/prompts/agent/ && mkdir -p dist/prompts/strategies && cp src/prompts/strategies/*.md dist/prompts/strategies/ && mkdir -p dist/prompts/legacy/conductor/commands/conductor && cp legacy/conductor/commands/conductor/*.toml dist/prompts/legacy/conductor/commands/conductor/",
34
+ "copy-prompts": "mkdir -p dist/prompts && cp src/prompts/*.md dist/prompts/ && cp legacy/conductor/commands/conductor/*.toml dist/prompts/ && mkdir -p dist/prompts/agent && cp src/prompts/agent/*.md dist/prompts/agent/ && mkdir -p dist/prompts/strategies && cp src/prompts/strategies/*.md dist/prompts/strategies/",
35
35
  "copy-templates": "mkdir -p dist/templates && cp -r src/templates/* dist/templates/",
36
- "prepublishOnly": "npm run build"
36
+ "update-submodule": "git submodule update --init --recursive --remote",
37
+ "prepublishOnly": "npm run update-submodule && npm run build"
37
38
  },
38
39
  "dependencies": {
39
40
  "@opencode-ai/plugin": "^1.0.209",
@@ -1 +0,0 @@
1
- export declare const newTrackCommand: (ctx: import("@opencode-ai/plugin").PluginInput) => import("@opencode-ai/plugin/tool").ToolDefinition;
@@ -1,12 +0,0 @@
1
- import { tool } from "@opencode-ai/plugin/tool";
2
- import { createConductorCommand } from "../utils/commandFactory.js";
3
- export const newTrackCommand = createConductorCommand({
4
- name: "legacy/conductor/commands/conductor/newTrack.toml",
5
- description: "Creates a new Development Track (feature/bug/chore) with proper scaffolding.",
6
- args: {
7
- description: tool.schema.string().optional().describe("Brief description of the track."),
8
- },
9
- additionalContext: async (_, args) => ({
10
- args: args.description || ""
11
- })
12
- });
@@ -1 +0,0 @@
1
- export declare const revertCommand: (ctx: import("@opencode-ai/plugin").PluginInput) => import("@opencode-ai/plugin/tool").ToolDefinition;
@@ -1,14 +0,0 @@
1
- import { createConductorCommand } from "../utils/commandFactory.js";
2
- import { tool } from "@opencode-ai/plugin/tool";
3
- export const revertCommand = createConductorCommand({
4
- name: "legacy/conductor/commands/conductor/revert.toml",
5
- description: "Reverts the last commit in the current track and updates the plan.",
6
- args: {
7
- target: tool.schema.string().optional().describe("ID or description of what to revert."),
8
- },
9
- additionalContext: async (ctx, args) => {
10
- return {
11
- target: args.target || "",
12
- };
13
- },
14
- });
@@ -1 +0,0 @@
1
- export declare const setupCommand: (ctx: import("@opencode-ai/plugin").PluginInput) => import("@opencode-ai/plugin/tool").ToolDefinition;
@@ -1,10 +0,0 @@
1
- import { tool } from "@opencode-ai/plugin/tool";
2
- import { createConductorCommand } from "../utils/commandFactory.js";
3
- export const setupCommand = createConductorCommand({
4
- name: "legacy/conductor/commands/conductor/setup.toml",
5
- description: "Initializes the Conductor in the current project, creating necessary directories and the workflow file.",
6
- args: {
7
- user_input: tool.schema.string().optional().describe("The user's response to a previous question, if applicable."),
8
- },
9
- requiresSetup: false // Setup command is what creates the setup
10
- });
@@ -1 +0,0 @@
1
- export declare const statusCommand: (ctx: import("@opencode-ai/plugin").PluginInput) => import("@opencode-ai/plugin/tool").ToolDefinition;
@@ -1,6 +0,0 @@
1
- import { createConductorCommand } from "../utils/commandFactory.js";
2
- export const statusCommand = createConductorCommand({
3
- name: "legacy/conductor/commands/conductor/status.toml",
4
- description: "Displays the current Conductor status, active tracks, and project health.",
5
- args: {}
6
- });