opencode-conductor-plugin 1.10.0 → 1.12.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.
Files changed (2) hide show
  1. package/dist/index.js +119 -119
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1,140 +1,140 @@
1
+ import { tool } from "@opencode-ai/plugin/tool";
1
2
  import { join, dirname } from "path";
2
3
  import { homedir } from "os";
3
4
  import { existsSync, readFileSync } from "fs";
4
5
  import { readFile } from "fs/promises";
5
6
  import { fileURLToPath } from "url";
6
- import { parse } from "smol-toml";
7
7
  const __filename = fileURLToPath(import.meta.url);
8
8
  const __dirname = dirname(__filename);
9
9
  const ConductorPlugin = async (ctx) => {
10
- // 1. Detect oh-my-opencode for synergy features
11
- const configPath = join(homedir(), ".config", "opencode", "opencode.json");
12
- let isOMOActive = false;
13
10
  try {
14
- if (existsSync(configPath)) {
15
- const config = JSON.parse(readFileSync(configPath, "utf-8"));
16
- isOMOActive = config.plugin?.some((p) => p.includes("oh-my-opencode"));
17
- }
18
- }
19
- catch (e) {
20
- const omoPath = join(homedir(), ".config", "opencode", "node_modules", "oh-my-opencode");
21
- isOMOActive = existsSync(omoPath);
22
- }
23
- console.log(`[Conductor] Plugin loaded. (OMO Synergy: ${isOMOActive ? "Enabled" : "Disabled"})`);
24
- // 2. Helper to load and process prompt templates
25
- const loadPrompt = async (filename, replacements = {}) => {
26
- const promptPath = join(__dirname, "prompts", filename);
11
+ console.log("[Conductor] Initializing plugin...");
12
+ // 1. Detect oh-my-opencode for synergy features
13
+ const configPath = join(homedir(), ".config", "opencode", "opencode.json");
14
+ let isOMOActive = false;
27
15
  try {
28
- const content = await readFile(promptPath, "utf-8");
29
- const parsed = parse(content);
30
- let promptText = parsed.prompt;
31
- // Default Replacements
32
- const defaults = {
33
- isOMOActive: isOMOActive ? "true" : "false",
34
- templatesDir: join(dirname(__dirname), "templates")
35
- };
36
- const finalReplacements = { ...defaults, ...replacements };
37
- for (const [key, value] of Object.entries(finalReplacements)) {
38
- promptText = promptText.replaceAll(`{{${key}}}`, value || "");
16
+ if (existsSync(configPath)) {
17
+ const config = JSON.parse(readFileSync(configPath, "utf-8"));
18
+ isOMOActive = config.plugin?.some((p) => p.includes("oh-my-opencode"));
39
19
  }
40
- return {
41
- prompt: promptText,
42
- description: parsed.description
43
- };
44
20
  }
45
- catch (error) {
46
- console.error(`[Conductor] Failed to load prompt ${filename}:`, error);
47
- return { prompt: `SYSTEM ERROR: Failed to load prompt ${filename}`, description: "Error loading command" };
21
+ catch (e) {
22
+ const omoPath = join(homedir(), ".config", "opencode", "node_modules", "oh-my-opencode");
23
+ isOMOActive = existsSync(omoPath);
48
24
  }
49
- };
50
- // 3. Load Strategies for Implement Command
51
- let strategySection = "";
52
- try {
53
- const strategyFile = isOMOActive ? "delegate.md" : "manual.md";
54
- strategySection = await readFile(join(__dirname, "prompts", "strategies", strategyFile), "utf-8");
55
- }
56
- catch (e) {
57
- strategySection = "SYSTEM ERROR: Could not load execution strategy.";
58
- }
59
- // 4. Load all Command Prompts
60
- // conductor:setup
61
- const setup = await loadPrompt("setup.toml");
62
- // conductor:newTrack
63
- // Note: Arguments ($ARGUMENTS) are handled natively by OpenCode commands via variable injection in the template string?
64
- // Actually, for OpenCode commands, we put the placeholder directly in the string passed to 'template'.
65
- // Our TOML files use {{args}}, so we need to map that to "$ARGUMENTS" or "$1".
66
- const newTrack = await loadPrompt("newTrack.toml", { args: "$ARGUMENTS" });
67
- // conductor:implement
68
- const implement = await loadPrompt("implement.toml", {
69
- track_name: "$ARGUMENTS", // Map command arg to the TOML variable
70
- strategy_section: strategySection
71
- });
72
- // conductor:status
73
- const status = await loadPrompt("status.toml");
74
- // conductor:revert
75
- const revert = await loadPrompt("revert.toml", { target: "$ARGUMENTS" });
76
- return {
77
- command: {
78
- "conductor:setup": {
79
- template: setup.prompt,
80
- description: setup.description,
81
- agent: "conductor",
82
- },
83
- "conductor:newTrack": {
84
- template: newTrack.prompt,
85
- description: newTrack.description,
86
- agent: "conductor",
87
- },
88
- "conductor:implement": {
89
- template: implement.prompt,
90
- description: implement.description,
91
- agent: "sisyphus",
25
+ console.log(`[Conductor] Plugin environment detected. (OMO Synergy: ${isOMOActive ? "Enabled" : "Disabled"})`);
26
+ // 2. Helper to load and process prompt templates (Manual TOML Parsing)
27
+ const loadPrompt = async (filename, replacements = {}) => {
28
+ const promptPath = join(__dirname, "prompts", filename);
29
+ try {
30
+ const content = await readFile(promptPath, "utf-8");
31
+ const descMatch = content.match(/description\s*=\s*"([^"]+)"/);
32
+ const description = descMatch ? descMatch[1] : "Conductor Command";
33
+ const promptMatch = content.match(/prompt\s*=\s*"""([\s\S]*?)"""/);
34
+ let promptText = promptMatch ? promptMatch[1] : "";
35
+ if (!promptText)
36
+ throw new Error(`Could not parse prompt text from ${filename}`);
37
+ const defaults = {
38
+ isOMOActive: isOMOActive ? "true" : "false",
39
+ templatesDir: join(dirname(__dirname), "templates")
40
+ };
41
+ const finalReplacements = { ...defaults, ...replacements };
42
+ for (const [key, value] of Object.entries(finalReplacements)) {
43
+ promptText = promptText.replaceAll(`{{${key}}}`, value || "");
44
+ }
45
+ return { prompt: promptText, description: description };
46
+ }
47
+ catch (error) {
48
+ console.error(`[Conductor] Error loading prompt ${filename}:`, error);
49
+ return { prompt: `SYSTEM ERROR: Failed to load prompt ${filename}`, description: "Error loading command" };
50
+ }
51
+ };
52
+ // 3. Load Strategies
53
+ let strategySection = "";
54
+ try {
55
+ const strategyFile = isOMOActive ? "delegate.md" : "manual.md";
56
+ const strategyPath = join(__dirname, "prompts", "strategies", strategyFile);
57
+ strategySection = await readFile(strategyPath, "utf-8");
58
+ }
59
+ catch (e) {
60
+ strategySection = "SYSTEM ERROR: Could not load execution strategy.";
61
+ }
62
+ // 4. Load all Command Prompts (Parallel)
63
+ const [setup, newTrack, implement, status, revert] = await Promise.all([
64
+ loadPrompt("setup.toml"),
65
+ loadPrompt("newTrack.toml", { args: "$ARGUMENTS" }),
66
+ loadPrompt("implement.toml", { track_name: "$ARGUMENTS", strategy_section: strategySection }),
67
+ loadPrompt("status.toml"),
68
+ loadPrompt("revert.toml", { target: "$ARGUMENTS" })
69
+ ]);
70
+ // 5. Extract Agent Prompt
71
+ const agentMd = await readFile(join(__dirname, "prompts", "agent", "conductor.md"), "utf-8");
72
+ const agentPrompt = agentMd.split("---").pop()?.trim() || "";
73
+ console.log("[Conductor] All components ready. Injecting config...");
74
+ return {
75
+ tool: {
76
+ "conductor_health": tool({
77
+ description: "Health check",
78
+ args: {},
79
+ async execute() { return "Conductor is active."; }
80
+ })
92
81
  },
93
- "conductor:status": {
94
- template: status.prompt,
95
- description: status.description,
96
- agent: "conductor",
82
+ config: async (config) => {
83
+ console.log("[Conductor] config handler: Merging commands and agent...");
84
+ config.command = {
85
+ ...(config.command || {}),
86
+ "conductor:setup": { template: setup.prompt, description: setup.description, agent: "conductor", subtask: true },
87
+ "conductor:newTrack": { template: newTrack.prompt, description: newTrack.description, agent: "conductor", subtask: true },
88
+ "conductor:implement": { template: implement.prompt, description: implement.description, agent: isOMOActive ? "Sisyphus" : "conductor", subtask: true },
89
+ "conductor:status": { template: status.prompt, description: status.description, agent: "conductor", subtask: true },
90
+ "conductor:revert": { template: revert.prompt, description: revert.description, agent: "conductor", subtask: true }
91
+ };
92
+ config.agent = {
93
+ ...(config.agent || {}),
94
+ "conductor": {
95
+ description: "Spec-Driven Development Architect.",
96
+ mode: "primary",
97
+ prompt: agentPrompt,
98
+ permission: {
99
+ conductor_setup: "allow",
100
+ conductor_new_track: "allow",
101
+ conductor_implement: "allow",
102
+ conductor_status: "allow",
103
+ conductor_revert: "allow"
104
+ }
105
+ }
106
+ };
97
107
  },
98
- "conductor:revert": {
99
- template: revert.prompt,
100
- description: revert.description,
101
- agent: "conductor",
102
- }
103
- },
104
- // Keep the Hook for Sisyphus Synergy
105
- "tool.execute.before": async (input, output) => {
106
- if (input.tool === "delegate_to_agent") {
107
- const agentName = (output.args.agent_name || output.args.agent || "").toLowerCase();
108
- if (agentName.includes("sisyphus")) {
109
- const conductorDir = join(ctx.directory, "conductor");
110
- const safeRead = async (path) => {
111
- try {
112
- if (existsSync(path))
113
- return await readFile(path, "utf-8");
108
+ "tool.execute.before": async (input, output) => {
109
+ if (input.tool === "delegate_to_agent") {
110
+ const agentName = (output.args.agent_name || output.args.agent || "").toLowerCase();
111
+ if (agentName.includes("sisyphus")) {
112
+ const conductorDir = join(ctx.directory, "conductor");
113
+ const safeRead = async (path) => {
114
+ try {
115
+ if (existsSync(path))
116
+ return await readFile(path, "utf-8");
117
+ }
118
+ catch (e) { }
119
+ return null;
120
+ };
121
+ const workflowMd = await safeRead(join(conductorDir, "workflow.md"));
122
+ let injection = "\n\n--- [SYSTEM INJECTION: CONDUCTOR CONTEXT PACKET] ---\n";
123
+ injection += "You are receiving this task from the Conductor Architect.\n";
124
+ if (workflowMd) {
125
+ injection += "\n### DEVELOPMENT WORKFLOW\n" + workflowMd + "\n";
114
126
  }
115
- catch (e) { /* ignore */ }
116
- return null;
117
- };
118
- // We load the raw TOML just to get the protocol text if needed, or just hardcode a reference.
119
- // Since we already loaded 'implement' above, we could potentially reuse it, but simplicity is better here.
120
- // Let's just grab the workflow.md
121
- const workflowMd = await safeRead(join(conductorDir, "workflow.md"));
122
- let injection = "\n\n--- [SYSTEM INJECTION: CONDUCTOR CONTEXT PACKET] ---\n";
123
- injection += "You are receiving this task from the Conductor Architect.\n";
124
- if (workflowMd) {
125
- injection += "\n### DEVELOPMENT WORKFLOW\n";
126
- injection += "Follow these TDD and Commit rules precisely.\n";
127
- injection += "```markdown\n" + workflowMd + "\n```\n";
127
+ injection += "\n### DELEGATED AUTHORITY\n- **EXECUTE:** Implement the requested task.\n- **REFINE:** You have authority to update `plan.md`.\n";
128
+ injection += "--- [END INJECTION] ---\n";
129
+ output.args.objective += injection;
128
130
  }
129
- injection += "\n### DELEGATED AUTHORITY\n";
130
- injection += "- **EXECUTE:** Implement the requested task using the Workflow.\n";
131
- injection += "- **REFINE:** You have authority to update `plan.md` if it is flawed.\n";
132
- injection += "- **ESCALATE:** If you modify the Plan or Spec, report 'PLAN_UPDATED' immediately.\n";
133
- injection += "--- [END INJECTION] ---\n";
134
- output.args.objective += injection;
135
131
  }
136
132
  }
137
- }
138
- };
133
+ };
134
+ }
135
+ catch (err) {
136
+ console.error("[Conductor] FATAL: Plugin initialization failed:", err);
137
+ throw err;
138
+ }
139
139
  };
140
140
  export default ConductorPlugin;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-conductor-plugin",
3
- "version": "1.10.0",
3
+ "version": "1.12.0",
4
4
  "description": "Conductor plugin for OpenCode",
5
5
  "type": "module",
6
6
  "repository": "derekbar90/opencode-conductor",