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.
- package/dist/index.js +119 -119
- 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
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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 (
|
|
46
|
-
|
|
47
|
-
|
|
21
|
+
catch (e) {
|
|
22
|
+
const omoPath = join(homedir(), ".config", "opencode", "node_modules", "oh-my-opencode");
|
|
23
|
+
isOMOActive = existsSync(omoPath);
|
|
48
24
|
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
"
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
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
|
-
"
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
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
|
-
|
|
116
|
-
|
|
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;
|