opencode-conductor-plugin 1.15.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 +152 -63
- package/dist/prompts/agent/conductor.md +25 -22
- package/dist/prompts/agent/implementer.md +40 -0
- package/dist/prompts/agent.md +1 -1
- package/dist/prompts/implement.toml +2 -2
- package/dist/prompts/newTrack.toml +5 -5
- package/dist/prompts/revert.toml +1 -1
- package/dist/prompts/setup.toml +7 -7
- package/dist/prompts/status.toml +2 -2
- 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/dist/index.js
CHANGED
|
@@ -1,28 +1,24 @@
|
|
|
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);
|
|
9
|
+
const safeRead = async (path) => {
|
|
10
|
+
try {
|
|
11
|
+
if (existsSync(path))
|
|
12
|
+
return await readFile(path, "utf-8");
|
|
13
|
+
}
|
|
14
|
+
catch (e) { }
|
|
15
|
+
return null;
|
|
16
|
+
};
|
|
8
17
|
const ConductorPlugin = async (ctx) => {
|
|
9
18
|
try {
|
|
10
19
|
console.log("[Conductor] Initializing plugin...");
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
let isOMOActive = false;
|
|
14
|
-
try {
|
|
15
|
-
if (existsSync(configPath)) {
|
|
16
|
-
const config = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
17
|
-
isOMOActive = config.plugin?.some((p) => p.includes("oh-my-opencode"));
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
catch (e) {
|
|
21
|
-
const omoPath = join(homedir(), ".config", "opencode", "node_modules", "oh-my-opencode");
|
|
22
|
-
isOMOActive = existsSync(omoPath);
|
|
23
|
-
}
|
|
24
|
-
console.log(`[Conductor] Plugin environment detected. (OMO Synergy: ${isOMOActive ? "Enabled" : "Disabled"})`);
|
|
25
|
-
// 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)
|
|
26
22
|
const loadPrompt = async (filename, replacements = {}) => {
|
|
27
23
|
const promptPath = join(__dirname, "prompts", filename);
|
|
28
24
|
try {
|
|
@@ -34,8 +30,7 @@ const ConductorPlugin = async (ctx) => {
|
|
|
34
30
|
if (!promptText)
|
|
35
31
|
throw new Error(`Could not parse prompt text from ${filename}`);
|
|
36
32
|
const defaults = {
|
|
37
|
-
|
|
38
|
-
templatesDir: join(dirname(__dirname), "templates")
|
|
33
|
+
templatesDir: join(dirname(__dirname), "templates"),
|
|
39
34
|
};
|
|
40
35
|
const finalReplacements = { ...defaults, ...replacements };
|
|
41
36
|
for (const [key, value] of Object.entries(finalReplacements)) {
|
|
@@ -45,10 +40,13 @@ const ConductorPlugin = async (ctx) => {
|
|
|
45
40
|
}
|
|
46
41
|
catch (error) {
|
|
47
42
|
console.error(`[Conductor] Error loading prompt ${filename}:`, error);
|
|
48
|
-
return {
|
|
43
|
+
return {
|
|
44
|
+
prompt: `SYSTEM ERROR: Failed to load prompt ${filename}`,
|
|
45
|
+
description: "Error loading command",
|
|
46
|
+
};
|
|
49
47
|
}
|
|
50
48
|
};
|
|
51
|
-
//
|
|
49
|
+
// 2. Load Strategies
|
|
52
50
|
let strategySection = "";
|
|
53
51
|
try {
|
|
54
52
|
const strategyFile = "manual.md"; // Force manual strategy for now
|
|
@@ -58,74 +56,165 @@ const ConductorPlugin = async (ctx) => {
|
|
|
58
56
|
catch (e) {
|
|
59
57
|
strategySection = "SYSTEM ERROR: Could not load execution strategy.";
|
|
60
58
|
}
|
|
61
|
-
//
|
|
62
|
-
const [setup, newTrack, implement, status, revert] = await Promise.all([
|
|
59
|
+
// 3. Load all Command Prompts (Parallel)
|
|
60
|
+
const [setup, newTrack, implement, status, revert, workflowMd] = await Promise.all([
|
|
63
61
|
loadPrompt("setup.toml"),
|
|
64
62
|
loadPrompt("newTrack.toml", { args: "$ARGUMENTS" }),
|
|
65
|
-
loadPrompt("implement.toml", {
|
|
63
|
+
loadPrompt("implement.toml", {
|
|
64
|
+
track_name: "$ARGUMENTS",
|
|
65
|
+
strategy_section: strategySection,
|
|
66
|
+
}),
|
|
66
67
|
loadPrompt("status.toml"),
|
|
67
|
-
loadPrompt("revert.toml", { target: "$ARGUMENTS" })
|
|
68
|
+
loadPrompt("revert.toml", { target: "$ARGUMENTS" }),
|
|
69
|
+
safeRead(join(ctx.directory, "conductor", "workflow.md")),
|
|
68
70
|
]);
|
|
69
|
-
//
|
|
70
|
-
const
|
|
71
|
-
|
|
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() || "";
|
|
72
78
|
console.log("[Conductor] All components ready. Injecting config...");
|
|
73
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
|
+
},
|
|
74
86
|
config: async (config) => {
|
|
75
|
-
|
|
87
|
+
if (!config)
|
|
88
|
+
return;
|
|
89
|
+
console.log("[Conductor] config handler: Merging commands and agents...");
|
|
76
90
|
config.command = {
|
|
77
91
|
...(config.command || {}),
|
|
78
|
-
"conductor:setup": {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
92
|
+
"conductor:setup": {
|
|
93
|
+
template: setup.prompt,
|
|
94
|
+
description: setup.description,
|
|
95
|
+
agent: "conductor",
|
|
96
|
+
},
|
|
97
|
+
"conductor:newTrack": {
|
|
98
|
+
template: newTrack.prompt,
|
|
99
|
+
description: newTrack.description,
|
|
100
|
+
agent: "conductor",
|
|
101
|
+
},
|
|
102
|
+
"conductor:implement": {
|
|
103
|
+
template: implement.prompt,
|
|
104
|
+
description: implement.description,
|
|
105
|
+
agent: "conductor_implementer",
|
|
106
|
+
},
|
|
107
|
+
"conductor:status": {
|
|
108
|
+
template: status.prompt,
|
|
109
|
+
description: status.description,
|
|
110
|
+
agent: "conductor",
|
|
111
|
+
},
|
|
112
|
+
"conductor:revert": {
|
|
113
|
+
template: revert.prompt,
|
|
114
|
+
description: revert.description,
|
|
115
|
+
agent: "conductor",
|
|
116
|
+
},
|
|
83
117
|
};
|
|
84
118
|
config.agent = {
|
|
85
119
|
...(config.agent || {}),
|
|
86
|
-
|
|
87
|
-
description: "
|
|
120
|
+
conductor: {
|
|
121
|
+
description: "Conductor Protocol Steward.",
|
|
88
122
|
mode: "primary",
|
|
89
|
-
prompt:
|
|
123
|
+
prompt: conductorPrompt +
|
|
124
|
+
(workflowMd ? "\n\n### PROJECT WORKFLOW\n" + workflowMd : ""),
|
|
90
125
|
permission: {
|
|
126
|
+
bash: "allow",
|
|
91
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: {
|
|
92
153
|
bash: "allow",
|
|
154
|
+
edit: "allow",
|
|
93
155
|
webfetch: "allow",
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
156
|
+
external_directory: "deny",
|
|
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
|
+
},
|
|
177
|
+
},
|
|
98
178
|
};
|
|
99
179
|
},
|
|
100
180
|
"tool.execute.before": async (input, output) => {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
return null;
|
|
113
|
-
};
|
|
114
|
-
const workflowMd = await safeRead(join(conductorDir, "workflow.md"));
|
|
181
|
+
const delegationTools = [
|
|
182
|
+
"delegate_to_agent",
|
|
183
|
+
"task",
|
|
184
|
+
"background_task",
|
|
185
|
+
"conductor:delegate",
|
|
186
|
+
"conductor:background_task",
|
|
187
|
+
];
|
|
188
|
+
if (delegationTools.includes(input.tool)) {
|
|
189
|
+
const conductorDir = join(ctx.directory, "conductor");
|
|
190
|
+
const workflowMd = await safeRead(join(conductorDir, "workflow.md"));
|
|
191
|
+
if (workflowMd) {
|
|
115
192
|
let injection = "\n\n--- [SYSTEM INJECTION: CONDUCTOR CONTEXT PACKET] ---\n";
|
|
116
|
-
injection +=
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
193
|
+
injection +=
|
|
194
|
+
"You are receiving this task from the Conductor.\n";
|
|
195
|
+
injection +=
|
|
196
|
+
"You MUST adhere to the following project workflow rules:\n";
|
|
197
|
+
injection += "\n### DEVELOPMENT WORKFLOW\n" + workflowMd + "\n";
|
|
120
198
|
if (implement?.prompt) {
|
|
121
|
-
injection +=
|
|
199
|
+
injection +=
|
|
200
|
+
"\n### IMPLEMENTATION PROTOCOL\n" + implement.prompt + "\n";
|
|
122
201
|
}
|
|
123
|
-
injection +=
|
|
202
|
+
injection +=
|
|
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";
|
|
124
204
|
injection += "--- [END INJECTION] ---\n";
|
|
125
|
-
|
|
205
|
+
// Inject into the primary instruction field depending on the tool's schema
|
|
206
|
+
if (typeof output.args.objective === "string") {
|
|
207
|
+
output.args.objective += injection;
|
|
208
|
+
}
|
|
209
|
+
else if (typeof output.args.prompt === "string") {
|
|
210
|
+
output.args.prompt += injection;
|
|
211
|
+
}
|
|
212
|
+
else if (typeof output.args.instruction === "string") {
|
|
213
|
+
output.args.instruction += injection;
|
|
214
|
+
}
|
|
126
215
|
}
|
|
127
216
|
}
|
|
128
|
-
}
|
|
217
|
+
},
|
|
129
218
|
};
|
|
130
219
|
}
|
|
131
220
|
catch (err) {
|
|
@@ -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.
|
package/dist/prompts/agent.md
CHANGED
|
@@ -17,7 +17,7 @@ Your mission is to ensure that software development follows a rigorous, context-
|
|
|
17
17
|
- **Model Selection**: You prefer "flash" models for efficiency and speed during planning and tool orchestration.
|
|
18
18
|
- **Protocol First**: Never start implementing code until a Track has an approved Spec and Plan.
|
|
19
19
|
- **Collaboration**: You work alongside the user. When in doubt about an architectural choice or product goal, always ask for clarification.
|
|
20
|
-
- **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.
|
|
20
|
+
- **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
|
|
21
21
|
|
|
22
22
|
## Loop Protection Directive (CRITICAL)
|
|
23
23
|
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."
|
|
@@ -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:**
|
|
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
|
-
*
|
|
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:
|
package/dist/prompts/revert.toml
CHANGED
|
@@ -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
|
@@ -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:
|
package/dist/prompts/status.toml
CHANGED
|
@@ -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
|
+
}
|