opencodekit 0.14.6 → 0.15.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/README.md +2 -2
- package/dist/index.js +100 -58
- package/dist/template/.opencode/.env.example +1 -0
- package/dist/template/.opencode/AGENTS.md +13 -24
- package/dist/template/.opencode/README.md +8 -119
- package/dist/template/.opencode/agent/explore.md +2 -3
- package/dist/template/.opencode/agent/general.md +56 -0
- package/dist/template/.opencode/agent/plan.md +54 -0
- package/dist/template/.opencode/agent/scout.md +15 -5
- package/dist/template/.opencode/command/analyze-project.md +2 -2
- package/dist/template/.opencode/command/brainstorm.md +1 -1
- package/dist/template/.opencode/command/design-audit.md +4 -5
- package/dist/template/.opencode/command/design.md +4 -13
- package/dist/template/.opencode/command/generate-pattern.md +2 -9
- package/dist/template/.opencode/command/implement.md +4 -4
- package/dist/template/.opencode/command/init.md +1 -1
- package/dist/template/.opencode/command/new-feature.md +2 -3
- package/dist/template/.opencode/command/plan.md +1 -1
- package/dist/template/.opencode/command/pr.md +0 -1
- package/dist/template/.opencode/command/research.md +20 -6
- package/dist/template/.opencode/command/restore-image.md +1 -9
- package/dist/template/.opencode/command/revert-feature.md +1 -1
- package/dist/template/.opencode/command/review-codebase.md +4 -4
- package/dist/template/.opencode/command/status.md +1 -2
- package/dist/template/.opencode/command/summarize.md +1 -2
- package/dist/template/.opencode/command/triage.md +4 -32
- package/dist/template/.opencode/dcp.jsonc +68 -68
- package/dist/template/.opencode/memory/_templates/README.md +35 -0
- package/dist/template/.opencode/memory/_templates/project/architecture.md +60 -0
- package/dist/template/.opencode/memory/_templates/project/commands.md +72 -0
- package/dist/template/.opencode/memory/_templates/project/conventions.md +68 -0
- package/dist/template/.opencode/memory/_templates/project/gotchas.md +41 -0
- package/dist/template/.opencode/memory/beads-workflow.md +30 -29
- package/dist/template/.opencode/memory/project/architecture.md +31 -50
- package/dist/template/.opencode/memory/project/commands.md +41 -22
- package/dist/template/.opencode/memory/project/conventions.md +39 -177
- package/dist/template/.opencode/memory/project/gotchas.md +21 -177
- package/dist/template/.opencode/memory/user.example.md +5 -0
- package/dist/template/.opencode/opencode.json +644 -579
- package/dist/template/.opencode/package.json +18 -21
- package/dist/template/.opencode/plugin/compaction.ts +79 -85
- package/dist/template/.opencode/plugin/env-ctx.ts +19 -19
- package/dist/template/.opencode/plugin/lib/notify.ts +41 -45
- package/dist/template/.opencode/plugin/lsp.ts +197 -200
- package/dist/template/.opencode/plugin/memory.ts +14 -112
- package/dist/template/.opencode/plugin/package.json +5 -5
- package/dist/template/.opencode/plugin/sessions.ts +1 -1
- package/dist/template/.opencode/plugin/skill-mcp.ts +486 -521
- package/dist/template/.opencode/plugin/truncator.ts +47 -50
- package/dist/template/.opencode/plugin/tsconfig.json +14 -14
- package/dist/template/.opencode/skill/chrome-devtools/mcp.json +17 -17
- package/dist/template/.opencode/skill/condition-based-waiting/SKILL.md +17 -12
- package/dist/template/.opencode/skill/condition-based-waiting/example.ts +63 -69
- package/dist/template/.opencode/skill/defense-in-depth/SKILL.md +14 -8
- package/dist/template/.opencode/skill/dispatching-parallel-agents/SKILL.md +14 -3
- package/dist/template/.opencode/skill/playwright/mcp.json +14 -14
- package/dist/template/.opencode/skill/receiving-code-review/SKILL.md +21 -8
- package/dist/template/.opencode/skill/requesting-code-review/review.md +14 -0
- package/dist/template/.opencode/skill/root-cause-tracing/SKILL.md +18 -4
- package/dist/template/.opencode/skill/source-code-research/SKILL.md +9 -7
- package/dist/template/.opencode/skill/test-driven-development/SKILL.md +49 -32
- package/dist/template/.opencode/skill/testing-anti-patterns/SKILL.md +40 -22
- package/dist/template/.opencode/skill/testing-skills-with-subagents/SKILL.md +46 -26
- package/dist/template/.opencode/skill/tool-priority/SKILL.md +117 -44
- package/dist/template/.opencode/skill/v0/SKILL.md +1 -7
- package/dist/template/.opencode/skill/verification-before-completion/SKILL.md +27 -19
- package/dist/template/.opencode/skill/writing-skills/anthropic-best-practices.md +171 -148
- package/dist/template/.opencode/skill/writing-skills/persuasion-principles.md +39 -6
- package/dist/template/.opencode/tool/memory-read.ts +44 -56
- package/dist/template/.opencode/tool/memory-search.ts +8 -291
- package/dist/template/.opencode/tool/memory-update.ts +47 -51
- package/dist/template/.opencode/tool/observation.ts +6 -180
- package/dist/template/.opencode/tsconfig.json +19 -19
- package/package.json +19 -15
- package/dist/template/.opencode/.background-tasks.json +0 -114
- package/dist/template/.opencode/.ralph-state.json +0 -12
- package/dist/template/.opencode/agent/build.md +0 -327
- package/dist/template/.opencode/agent/ninja.md +0 -351
- package/dist/template/.opencode/agent/planner.md +0 -281
- package/dist/template/.opencode/agent/rush.md +0 -223
- package/dist/template/.opencode/memory/handoffs/README.md +0 -83
- package/dist/template/.opencode/memory/observations/.gitkeep +0 -0
- package/dist/template/.opencode/memory/observations/2026-01-09-pattern-ampcode-mcp-json-includetools-pattern.md +0 -42
- package/dist/template/.opencode/memory/vector_db/memories.lance/_transactions/0-0d25ba80-ba3b-4209-9046-b45d6093b4da.txn +0 -0
- package/dist/template/.opencode/memory/vector_db/memories.lance/_versions/1.manifest +0 -0
- package/dist/template/.opencode/memory/vector_db/memories.lance/data/1111100101010101011010004a9ef34df6b29f36a9a53a2892.lance +0 -0
- package/dist/template/.opencode/tool/ast-grep.ts +0 -245
- package/dist/template/.opencode/tool/background.ts +0 -509
- package/dist/template/.opencode/tool/bd-inbox.ts +0 -110
- package/dist/template/.opencode/tool/bd-msg.ts +0 -62
- package/dist/template/.opencode/tool/bd-release.ts +0 -71
- package/dist/template/.opencode/tool/bd-reserve.ts +0 -121
- package/dist/template/.opencode/tool/memory-embed.ts +0 -183
- package/dist/template/.opencode/tool/memory-index.ts +0 -769
- package/dist/template/.opencode/tool/repo-map.ts +0 -451
|
@@ -1,509 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @experimental Background Task Tool
|
|
3
|
-
*
|
|
4
|
-
* LIMITATION: Creates SEPARATE sessions, NOT child sessions of the parent.
|
|
5
|
-
* This means background tasks don't inherit context from the build agent's session.
|
|
6
|
-
*
|
|
7
|
-
* USE CASES:
|
|
8
|
-
* - True fire-and-forget async execution
|
|
9
|
-
* - When you need to continue working before results are ready
|
|
10
|
-
*
|
|
11
|
-
* FOR MOST CASES: Use the Task tool instead. Multiple Task calls in one message
|
|
12
|
-
* run in parallel AND create proper child sessions.
|
|
13
|
-
*
|
|
14
|
-
* Example with Task (recommended):
|
|
15
|
-
* Task({ subagent_type: "scout", prompt: "..." }) // ┐
|
|
16
|
-
* Task({ subagent_type: "explore", prompt: "..." }) // ┘ Parallel, proper sessions
|
|
17
|
-
*
|
|
18
|
-
* Example with Background (fire-and-forget):
|
|
19
|
-
* background_start({ agent: "scout", prompt: "..." }) // → taskId
|
|
20
|
-
* // ... continue other work ...
|
|
21
|
-
* background_output({ taskId }) // collect later
|
|
22
|
-
*/
|
|
23
|
-
|
|
24
|
-
import { type ChildProcess, execSync, spawn } from "node:child_process";
|
|
25
|
-
import fs from "node:fs/promises";
|
|
26
|
-
import path from "node:path";
|
|
27
|
-
import { tool } from "@opencode-ai/plugin";
|
|
28
|
-
|
|
29
|
-
const TASKS_FILE = ".opencode/.background-tasks.json";
|
|
30
|
-
const OUTPUT_DIR = ".opencode/.background-output";
|
|
31
|
-
|
|
32
|
-
interface BackgroundTask {
|
|
33
|
-
taskId: string;
|
|
34
|
-
pid: number;
|
|
35
|
-
outputFile: string;
|
|
36
|
-
agent: string;
|
|
37
|
-
prompt: string;
|
|
38
|
-
started: number;
|
|
39
|
-
status: "running" | "completed" | "cancelled" | "failed";
|
|
40
|
-
// Beads integration
|
|
41
|
-
beadId?: string;
|
|
42
|
-
autoCloseBead?: boolean;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
interface TasksStore {
|
|
46
|
-
tasks: Record<string, BackgroundTask>;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
async function loadTasks(): Promise<TasksStore> {
|
|
50
|
-
try {
|
|
51
|
-
const content = await fs.readFile(TASKS_FILE, "utf-8");
|
|
52
|
-
return JSON.parse(content);
|
|
53
|
-
} catch {
|
|
54
|
-
return { tasks: {} };
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
async function saveTasks(store: TasksStore): Promise<void> {
|
|
59
|
-
await fs.mkdir(path.dirname(TASKS_FILE), { recursive: true });
|
|
60
|
-
await fs.writeFile(TASKS_FILE, JSON.stringify(store, null, 2));
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
async function ensureOutputDir(): Promise<void> {
|
|
64
|
-
await fs.mkdir(OUTPUT_DIR, { recursive: true });
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* Find the bd binary path dynamically
|
|
69
|
-
*/
|
|
70
|
-
function findBdPath(): string {
|
|
71
|
-
try {
|
|
72
|
-
const result = execSync("which bd || command -v bd", {
|
|
73
|
-
encoding: "utf-8",
|
|
74
|
-
timeout: 5000,
|
|
75
|
-
shell: "/bin/sh",
|
|
76
|
-
}).trim();
|
|
77
|
-
if (result) return result;
|
|
78
|
-
} catch {
|
|
79
|
-
const commonPaths = [
|
|
80
|
-
`${process.env.HOME}/.local/bin/bd`,
|
|
81
|
-
`${process.env.HOME}/.bun/bin/bd`,
|
|
82
|
-
"/usr/local/bin/bd",
|
|
83
|
-
"/opt/homebrew/bin/bd",
|
|
84
|
-
];
|
|
85
|
-
for (const p of commonPaths) {
|
|
86
|
-
try {
|
|
87
|
-
execSync(`test -x "${p}"`, { timeout: 1000 });
|
|
88
|
-
return p;
|
|
89
|
-
} catch {
|
|
90
|
-
// Try next path
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
return "bd";
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
let bdPath: string | null = null;
|
|
98
|
-
function getBdPath(): string {
|
|
99
|
-
if (!bdPath) {
|
|
100
|
-
bdPath = findBdPath();
|
|
101
|
-
}
|
|
102
|
-
return bdPath;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* Helper to run beads CLI commands
|
|
107
|
-
*/
|
|
108
|
-
async function runBeadsCommand(
|
|
109
|
-
args: string[],
|
|
110
|
-
): Promise<{ success: boolean; output: string }> {
|
|
111
|
-
try {
|
|
112
|
-
const quotedArgs = args.map((arg) =>
|
|
113
|
-
arg.includes(" ") ? `"${arg}"` : arg,
|
|
114
|
-
);
|
|
115
|
-
const output = execSync(`${getBdPath()} ${quotedArgs.join(" ")}`, {
|
|
116
|
-
encoding: "utf-8",
|
|
117
|
-
timeout: 30000,
|
|
118
|
-
shell: "/bin/sh",
|
|
119
|
-
env: { ...process.env },
|
|
120
|
-
});
|
|
121
|
-
return { success: true, output };
|
|
122
|
-
} catch (e) {
|
|
123
|
-
const error = e as { stderr?: string; message?: string };
|
|
124
|
-
return {
|
|
125
|
-
success: false,
|
|
126
|
-
output: error.stderr || error.message || String(e),
|
|
127
|
-
};
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
/**
|
|
132
|
-
* Check if a process is still running by PID
|
|
133
|
-
*/
|
|
134
|
-
function isProcessRunning(pid: number): boolean {
|
|
135
|
-
try {
|
|
136
|
-
process.kill(pid, 0); // Signal 0 = check if process exists
|
|
137
|
-
return true;
|
|
138
|
-
} catch {
|
|
139
|
-
return false;
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
// Allowed agents for background delegation
|
|
144
|
-
const ALLOWED_AGENTS = [
|
|
145
|
-
"explore",
|
|
146
|
-
"scout",
|
|
147
|
-
"review",
|
|
148
|
-
"planner",
|
|
149
|
-
"vision",
|
|
150
|
-
"looker",
|
|
151
|
-
"rush",
|
|
152
|
-
] as const;
|
|
153
|
-
type AllowedAgent = (typeof ALLOWED_AGENTS)[number];
|
|
154
|
-
|
|
155
|
-
// Agents safe for autoCloseBead (pure research, no side effects)
|
|
156
|
-
const SAFE_AUTOCLOSE_AGENTS: readonly string[] = [
|
|
157
|
-
"explore",
|
|
158
|
-
"scout",
|
|
159
|
-
"looker",
|
|
160
|
-
] as const;
|
|
161
|
-
|
|
162
|
-
/**
|
|
163
|
-
* Start a background subagent task using `opencode run`.
|
|
164
|
-
* Spawns a separate process that runs independently.
|
|
165
|
-
*/
|
|
166
|
-
export const start = tool({
|
|
167
|
-
description:
|
|
168
|
-
"Start a background subagent task. Returns a task_id to collect results later. Use for parallel research/exploration or executing beads subtasks. NOTE: Cannot delegate to 'build' agent (build is the orchestrator).",
|
|
169
|
-
args: {
|
|
170
|
-
agent: tool.schema
|
|
171
|
-
.string()
|
|
172
|
-
.describe(
|
|
173
|
-
"Agent type: explore, scout, review, planner, vision, looker, rush (NOT build)",
|
|
174
|
-
),
|
|
175
|
-
prompt: tool.schema.string().describe("Task prompt for the agent"),
|
|
176
|
-
title: tool.schema
|
|
177
|
-
.string()
|
|
178
|
-
.optional()
|
|
179
|
-
.describe("Optional task title for identification"),
|
|
180
|
-
beadId: tool.schema
|
|
181
|
-
.string()
|
|
182
|
-
.optional()
|
|
183
|
-
.describe("Bead ID to associate with this task (e.g., bd-abc123)"),
|
|
184
|
-
autoCloseBead: tool.schema
|
|
185
|
-
.boolean()
|
|
186
|
-
.optional()
|
|
187
|
-
.describe(
|
|
188
|
-
"Auto-close bead on completion. Only allowed for safe agents (explore, scout, looker).",
|
|
189
|
-
),
|
|
190
|
-
},
|
|
191
|
-
execute: async (args) => {
|
|
192
|
-
// Validate agent type
|
|
193
|
-
if (args.agent === "build") {
|
|
194
|
-
return JSON.stringify({
|
|
195
|
-
error:
|
|
196
|
-
"Cannot delegate to 'build' agent. Build is the orchestrator. Use subagents instead.",
|
|
197
|
-
});
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
if (!ALLOWED_AGENTS.includes(args.agent as AllowedAgent)) {
|
|
201
|
-
return JSON.stringify({
|
|
202
|
-
error: `Invalid agent type: ${args.agent}. Allowed: ${ALLOWED_AGENTS.join(", ")}`,
|
|
203
|
-
});
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
// Validate autoCloseBead
|
|
207
|
-
if (args.autoCloseBead && !SAFE_AUTOCLOSE_AGENTS.includes(args.agent)) {
|
|
208
|
-
return JSON.stringify({
|
|
209
|
-
error: `autoCloseBead not allowed for '${args.agent}' agent. Only safe for: ${SAFE_AUTOCLOSE_AGENTS.join(", ")}`,
|
|
210
|
-
});
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
await ensureOutputDir();
|
|
214
|
-
|
|
215
|
-
const taskId = `bg_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
216
|
-
const title = args.title || `bg-${args.agent}-${taskId}`;
|
|
217
|
-
const outputFile = path.join(OUTPUT_DIR, `${taskId}.txt`);
|
|
218
|
-
|
|
219
|
-
try {
|
|
220
|
-
// Create output file to capture stdout
|
|
221
|
-
const outFd = await fs.open(outputFile, "w");
|
|
222
|
-
|
|
223
|
-
// Spawn opencode run with the agent and prompt
|
|
224
|
-
// Using --format default for readable output (json is too verbose for collection)
|
|
225
|
-
const proc = spawn(
|
|
226
|
-
"opencode",
|
|
227
|
-
[
|
|
228
|
-
"run",
|
|
229
|
-
"--agent",
|
|
230
|
-
args.agent,
|
|
231
|
-
"--title",
|
|
232
|
-
title,
|
|
233
|
-
"--format",
|
|
234
|
-
"default",
|
|
235
|
-
args.prompt,
|
|
236
|
-
],
|
|
237
|
-
{
|
|
238
|
-
detached: true, // Run independently of parent
|
|
239
|
-
stdio: ["ignore", outFd.fd, outFd.fd], // Redirect stdout/stderr to file
|
|
240
|
-
cwd: process.cwd(),
|
|
241
|
-
env: { ...process.env },
|
|
242
|
-
},
|
|
243
|
-
);
|
|
244
|
-
|
|
245
|
-
// Don't wait for completion - let it run in background
|
|
246
|
-
proc.unref();
|
|
247
|
-
|
|
248
|
-
// Close our handle to the output file (process keeps its own)
|
|
249
|
-
await outFd.close();
|
|
250
|
-
|
|
251
|
-
if (!proc.pid) {
|
|
252
|
-
return JSON.stringify({ error: "Failed to spawn opencode process" });
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
// Persist task info
|
|
256
|
-
const store = await loadTasks();
|
|
257
|
-
store.tasks[taskId] = {
|
|
258
|
-
taskId,
|
|
259
|
-
pid: proc.pid,
|
|
260
|
-
outputFile,
|
|
261
|
-
agent: args.agent,
|
|
262
|
-
prompt: args.prompt,
|
|
263
|
-
started: Date.now(),
|
|
264
|
-
status: "running",
|
|
265
|
-
beadId: args.beadId,
|
|
266
|
-
autoCloseBead: args.autoCloseBead,
|
|
267
|
-
};
|
|
268
|
-
await saveTasks(store);
|
|
269
|
-
|
|
270
|
-
// If beadId provided, mark it as in_progress
|
|
271
|
-
if (args.beadId) {
|
|
272
|
-
await runBeadsCommand([
|
|
273
|
-
"update",
|
|
274
|
-
args.beadId,
|
|
275
|
-
"--status",
|
|
276
|
-
"in_progress",
|
|
277
|
-
]);
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
return JSON.stringify({
|
|
281
|
-
taskId,
|
|
282
|
-
pid: proc.pid,
|
|
283
|
-
agent: args.agent,
|
|
284
|
-
beadId: args.beadId,
|
|
285
|
-
status: "started",
|
|
286
|
-
message: `Background task started. Use background_output(taskId="${taskId}") to get results.`,
|
|
287
|
-
});
|
|
288
|
-
} catch (e) {
|
|
289
|
-
const error = e instanceof Error ? e.message : String(e);
|
|
290
|
-
return JSON.stringify({
|
|
291
|
-
error: `Failed to start background task: ${error}`,
|
|
292
|
-
});
|
|
293
|
-
}
|
|
294
|
-
},
|
|
295
|
-
});
|
|
296
|
-
|
|
297
|
-
/**
|
|
298
|
-
* Get output from a background task.
|
|
299
|
-
* Reads the output file and checks if the process has completed.
|
|
300
|
-
*/
|
|
301
|
-
export const output = tool({
|
|
302
|
-
description:
|
|
303
|
-
"Get output from a background task. Returns the agent's response or 'still running' if not complete.",
|
|
304
|
-
args: {
|
|
305
|
-
taskId: tool.schema.string().describe("Task ID from background_start"),
|
|
306
|
-
},
|
|
307
|
-
execute: async (args) => {
|
|
308
|
-
const store = await loadTasks();
|
|
309
|
-
const task = store.tasks[args.taskId];
|
|
310
|
-
|
|
311
|
-
if (!task) {
|
|
312
|
-
return JSON.stringify({
|
|
313
|
-
error: `Task not found: ${args.taskId}`,
|
|
314
|
-
availableTasks: Object.keys(store.tasks),
|
|
315
|
-
});
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
try {
|
|
319
|
-
// Check if process is still running
|
|
320
|
-
const running = isProcessRunning(task.pid);
|
|
321
|
-
|
|
322
|
-
// Read output file
|
|
323
|
-
let output = "";
|
|
324
|
-
try {
|
|
325
|
-
output = await fs.readFile(task.outputFile, "utf-8");
|
|
326
|
-
} catch {
|
|
327
|
-
output = "(no output yet)";
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
// If still running and no substantial output
|
|
331
|
-
if (running && output.length < 100) {
|
|
332
|
-
return JSON.stringify({
|
|
333
|
-
taskId: args.taskId,
|
|
334
|
-
agent: task.agent,
|
|
335
|
-
status: "running",
|
|
336
|
-
message: "Task still running - no complete response yet",
|
|
337
|
-
partialOutput: output.slice(0, 500),
|
|
338
|
-
});
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
// Process completed or has substantial output
|
|
342
|
-
if (!running) {
|
|
343
|
-
// Update status
|
|
344
|
-
task.status = output.length > 0 ? "completed" : "failed";
|
|
345
|
-
await saveTasks(store);
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
// Build result object
|
|
349
|
-
const result: Record<string, unknown> = {
|
|
350
|
-
taskId: args.taskId,
|
|
351
|
-
agent: task.agent,
|
|
352
|
-
status: running ? "running" : task.status,
|
|
353
|
-
output: output || "(empty response)",
|
|
354
|
-
};
|
|
355
|
-
|
|
356
|
-
// Handle bead closing (only if completed)
|
|
357
|
-
if (task.beadId && !running && task.status === "completed") {
|
|
358
|
-
result.beadId = task.beadId;
|
|
359
|
-
|
|
360
|
-
if (task.autoCloseBead && SAFE_AUTOCLOSE_AGENTS.includes(task.agent)) {
|
|
361
|
-
const closeResult = await runBeadsCommand([
|
|
362
|
-
"close",
|
|
363
|
-
task.beadId,
|
|
364
|
-
"--reason",
|
|
365
|
-
`Auto-closed: ${task.agent} task completed (${task.taskId})`,
|
|
366
|
-
]);
|
|
367
|
-
result.beadClosed = closeResult.success;
|
|
368
|
-
if (!closeResult.success) {
|
|
369
|
-
result.beadCloseError = closeResult.output;
|
|
370
|
-
}
|
|
371
|
-
} else {
|
|
372
|
-
result.beadAction = `VERIFY output, then run: bd close ${task.beadId} --reason "..."`;
|
|
373
|
-
}
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
return JSON.stringify(result);
|
|
377
|
-
} catch (e) {
|
|
378
|
-
const error = e instanceof Error ? e.message : String(e);
|
|
379
|
-
return JSON.stringify({
|
|
380
|
-
taskId: args.taskId,
|
|
381
|
-
error: `Failed to get output: ${error}`,
|
|
382
|
-
});
|
|
383
|
-
}
|
|
384
|
-
},
|
|
385
|
-
});
|
|
386
|
-
|
|
387
|
-
/**
|
|
388
|
-
* Cancel background tasks by killing the process.
|
|
389
|
-
*/
|
|
390
|
-
export const cancel = tool({
|
|
391
|
-
description:
|
|
392
|
-
"Cancel background tasks. Use all=true to cancel all, or specify taskId.",
|
|
393
|
-
args: {
|
|
394
|
-
all: tool.schema
|
|
395
|
-
.boolean()
|
|
396
|
-
.optional()
|
|
397
|
-
.describe("Cancel all background tasks"),
|
|
398
|
-
taskId: tool.schema
|
|
399
|
-
.string()
|
|
400
|
-
.optional()
|
|
401
|
-
.describe("Specific task ID to cancel"),
|
|
402
|
-
},
|
|
403
|
-
execute: async (args) => {
|
|
404
|
-
const store = await loadTasks();
|
|
405
|
-
const cancelled: string[] = [];
|
|
406
|
-
const errors: string[] = [];
|
|
407
|
-
|
|
408
|
-
const tasksToCancel = args.all
|
|
409
|
-
? Object.values(store.tasks).filter((t) => t.status === "running")
|
|
410
|
-
: args.taskId && store.tasks[args.taskId]
|
|
411
|
-
? [store.tasks[args.taskId]]
|
|
412
|
-
: [];
|
|
413
|
-
|
|
414
|
-
if (!tasksToCancel.length) {
|
|
415
|
-
return JSON.stringify({
|
|
416
|
-
message: args.all
|
|
417
|
-
? "No running tasks to cancel"
|
|
418
|
-
: `Task not found: ${args.taskId}`,
|
|
419
|
-
activeTasks: Object.values(store.tasks)
|
|
420
|
-
.filter((t) => t.status === "running")
|
|
421
|
-
.map((t) => t.taskId),
|
|
422
|
-
});
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
for (const task of tasksToCancel) {
|
|
426
|
-
try {
|
|
427
|
-
// Kill the process and its children (negative PID kills process group)
|
|
428
|
-
try {
|
|
429
|
-
process.kill(-task.pid, "SIGTERM");
|
|
430
|
-
} catch {
|
|
431
|
-
// Try without process group
|
|
432
|
-
process.kill(task.pid, "SIGTERM");
|
|
433
|
-
}
|
|
434
|
-
task.status = "cancelled";
|
|
435
|
-
cancelled.push(task.taskId);
|
|
436
|
-
} catch (e) {
|
|
437
|
-
const error = e instanceof Error ? e.message : String(e);
|
|
438
|
-
// Process might already be dead
|
|
439
|
-
if (error.includes("ESRCH")) {
|
|
440
|
-
task.status = "cancelled";
|
|
441
|
-
cancelled.push(task.taskId);
|
|
442
|
-
} else {
|
|
443
|
-
errors.push(`${task.taskId}: ${error}`);
|
|
444
|
-
}
|
|
445
|
-
}
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
await saveTasks(store);
|
|
449
|
-
|
|
450
|
-
return JSON.stringify({
|
|
451
|
-
cancelled,
|
|
452
|
-
errors: errors.length ? errors : undefined,
|
|
453
|
-
remaining: Object.values(store.tasks)
|
|
454
|
-
.filter((t) => t.status === "running")
|
|
455
|
-
.map((t) => t.taskId),
|
|
456
|
-
});
|
|
457
|
-
},
|
|
458
|
-
});
|
|
459
|
-
|
|
460
|
-
/**
|
|
461
|
-
* List all background tasks with their status.
|
|
462
|
-
*/
|
|
463
|
-
export const list = tool({
|
|
464
|
-
description: "List all background tasks with their status.",
|
|
465
|
-
args: {
|
|
466
|
-
status: tool.schema
|
|
467
|
-
.enum(["running", "completed", "cancelled", "failed", "all"])
|
|
468
|
-
.optional()
|
|
469
|
-
.default("all")
|
|
470
|
-
.describe("Filter by status"),
|
|
471
|
-
},
|
|
472
|
-
execute: async (args) => {
|
|
473
|
-
const store = await loadTasks();
|
|
474
|
-
const tasks = Object.values(store.tasks);
|
|
475
|
-
|
|
476
|
-
// Update status of running tasks
|
|
477
|
-
for (const task of tasks) {
|
|
478
|
-
if (task.status === "running" && !isProcessRunning(task.pid)) {
|
|
479
|
-
// Process finished, check if it has output
|
|
480
|
-
try {
|
|
481
|
-
const output = await fs.readFile(task.outputFile, "utf-8");
|
|
482
|
-
task.status = output.length > 0 ? "completed" : "failed";
|
|
483
|
-
} catch {
|
|
484
|
-
task.status = "failed";
|
|
485
|
-
}
|
|
486
|
-
}
|
|
487
|
-
}
|
|
488
|
-
await saveTasks(store);
|
|
489
|
-
|
|
490
|
-
const filtered =
|
|
491
|
-
args.status === "all"
|
|
492
|
-
? tasks
|
|
493
|
-
: tasks.filter((t) => t.status === args.status);
|
|
494
|
-
|
|
495
|
-
return JSON.stringify({
|
|
496
|
-
total: filtered.length,
|
|
497
|
-
tasks: filtered.map((t) => ({
|
|
498
|
-
taskId: t.taskId,
|
|
499
|
-
agent: t.agent,
|
|
500
|
-
status: t.status,
|
|
501
|
-
pid: t.pid,
|
|
502
|
-
started: new Date(t.started).toISOString(),
|
|
503
|
-
running: t.status === "running" ? isProcessRunning(t.pid) : false,
|
|
504
|
-
prompt: t.prompt.slice(0, 100) + (t.prompt.length > 100 ? "..." : ""),
|
|
505
|
-
beadId: t.beadId,
|
|
506
|
-
})),
|
|
507
|
-
});
|
|
508
|
-
},
|
|
509
|
-
});
|
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
import fs from "node:fs/promises";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import { tool } from "@opencode-ai/plugin";
|
|
4
|
-
|
|
5
|
-
const RESERVATIONS_DIR = ".reservations";
|
|
6
|
-
const MESSAGES_FILE = "messages.jsonl";
|
|
7
|
-
|
|
8
|
-
interface Message {
|
|
9
|
-
id: string;
|
|
10
|
-
from: string;
|
|
11
|
-
to: string;
|
|
12
|
-
subj: string;
|
|
13
|
-
body?: string;
|
|
14
|
-
importance: string;
|
|
15
|
-
at: number;
|
|
16
|
-
read: boolean;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export default tool({
|
|
20
|
-
description:
|
|
21
|
-
"Read messages from other agents. Returns most recent messages addressed to you or broadcast to 'all'.",
|
|
22
|
-
args: {
|
|
23
|
-
n: tool.schema
|
|
24
|
-
.number()
|
|
25
|
-
.optional()
|
|
26
|
-
.default(5)
|
|
27
|
-
.describe("Max messages to return"),
|
|
28
|
-
unread: tool.schema
|
|
29
|
-
.boolean()
|
|
30
|
-
.optional()
|
|
31
|
-
.default(false)
|
|
32
|
-
.describe("Only show unread messages"),
|
|
33
|
-
ack: tool.schema
|
|
34
|
-
.array(tool.schema.string())
|
|
35
|
-
.optional()
|
|
36
|
-
.describe("Message IDs to mark as read"),
|
|
37
|
-
},
|
|
38
|
-
execute: async (args, context) => {
|
|
39
|
-
const cwd = process.cwd();
|
|
40
|
-
const agentId = context?.agent || `agent-${process.pid}`;
|
|
41
|
-
const messagesPath = path.join(cwd, RESERVATIONS_DIR, MESSAGES_FILE);
|
|
42
|
-
|
|
43
|
-
try {
|
|
44
|
-
const content = await fs.readFile(messagesPath, "utf-8");
|
|
45
|
-
if (!content.trim()) {
|
|
46
|
-
return JSON.stringify({ msgs: [], count: 0 });
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
const idsToAck = new Set(args.ack || []);
|
|
50
|
-
let messages: Message[] = [];
|
|
51
|
-
const lines = content.trim().split("\n");
|
|
52
|
-
|
|
53
|
-
for (const line of lines) {
|
|
54
|
-
if (!line.trim()) continue;
|
|
55
|
-
try {
|
|
56
|
-
const msg = JSON.parse(line) as Message;
|
|
57
|
-
// Filter to messages for this agent or broadcast
|
|
58
|
-
if (msg.to === "all" || msg.to === agentId) {
|
|
59
|
-
// Mark as read if in ack list
|
|
60
|
-
if (idsToAck.has(msg.id)) {
|
|
61
|
-
msg.read = true;
|
|
62
|
-
}
|
|
63
|
-
messages.push(msg);
|
|
64
|
-
}
|
|
65
|
-
} catch {
|
|
66
|
-
// Skip invalid lines
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// If acking, rewrite the file
|
|
71
|
-
if (idsToAck.size > 0) {
|
|
72
|
-
const allMsgs: Message[] = [];
|
|
73
|
-
for (const line of lines) {
|
|
74
|
-
if (!line.trim()) continue;
|
|
75
|
-
try {
|
|
76
|
-
const msg = JSON.parse(line) as Message;
|
|
77
|
-
if (idsToAck.has(msg.id)) {
|
|
78
|
-
msg.read = true;
|
|
79
|
-
}
|
|
80
|
-
allMsgs.push(msg);
|
|
81
|
-
} catch {
|
|
82
|
-
// Skip
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
await fs.writeFile(
|
|
86
|
-
messagesPath,
|
|
87
|
-
`${allMsgs.map((m) => JSON.stringify(m)).join("\n")}\n`,
|
|
88
|
-
"utf-8",
|
|
89
|
-
);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
// Filter unread if requested
|
|
93
|
-
if (args.unread) {
|
|
94
|
-
messages = messages.filter((m) => !m.read);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
// Return most recent N
|
|
98
|
-
const limit = args.n || 5;
|
|
99
|
-
messages = messages.slice(-limit).reverse();
|
|
100
|
-
|
|
101
|
-
return JSON.stringify({ msgs: messages, count: messages.length });
|
|
102
|
-
} catch (e) {
|
|
103
|
-
const err = e as NodeJS.ErrnoException;
|
|
104
|
-
if (err.code === "ENOENT") {
|
|
105
|
-
return JSON.stringify({ msgs: [], count: 0 });
|
|
106
|
-
}
|
|
107
|
-
return JSON.stringify({ error: (e as Error).message });
|
|
108
|
-
}
|
|
109
|
-
},
|
|
110
|
-
});
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
import fs from "node:fs/promises";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import { tool } from "@opencode-ai/plugin";
|
|
4
|
-
|
|
5
|
-
const RESERVATIONS_DIR = ".reservations";
|
|
6
|
-
const MESSAGES_FILE = "messages.jsonl";
|
|
7
|
-
|
|
8
|
-
interface Message {
|
|
9
|
-
id: string;
|
|
10
|
-
from: string;
|
|
11
|
-
to: string;
|
|
12
|
-
subj: string;
|
|
13
|
-
body?: string;
|
|
14
|
-
importance: string;
|
|
15
|
-
at: number;
|
|
16
|
-
read: boolean;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export default tool({
|
|
20
|
-
description:
|
|
21
|
-
"Send message to other agents or broadcast to all. Messages stored in .reservations/messages.jsonl.",
|
|
22
|
-
args: {
|
|
23
|
-
subj: tool.schema.string().describe("Message subject"),
|
|
24
|
-
body: tool.schema.string().optional().describe("Message body"),
|
|
25
|
-
to: tool.schema
|
|
26
|
-
.string()
|
|
27
|
-
.optional()
|
|
28
|
-
.default("all")
|
|
29
|
-
.describe("Recipient agent ID or 'all' for broadcast"),
|
|
30
|
-
importance: tool.schema
|
|
31
|
-
.string()
|
|
32
|
-
.optional()
|
|
33
|
-
.default("normal")
|
|
34
|
-
.describe("Priority: low | normal | high"),
|
|
35
|
-
},
|
|
36
|
-
execute: async (args, context) => {
|
|
37
|
-
if (!args.subj) {
|
|
38
|
-
return JSON.stringify({ error: "subj required" });
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
const cwd = process.cwd();
|
|
42
|
-
const agentId = context?.agent || `agent-${process.pid}`;
|
|
43
|
-
const messagesPath = path.join(cwd, RESERVATIONS_DIR, MESSAGES_FILE);
|
|
44
|
-
|
|
45
|
-
// Ensure dir exists
|
|
46
|
-
await fs.mkdir(path.join(cwd, RESERVATIONS_DIR), { recursive: true });
|
|
47
|
-
|
|
48
|
-
const msg: Message = {
|
|
49
|
-
id: `msg-${Date.now().toString(36)}`,
|
|
50
|
-
from: agentId,
|
|
51
|
-
to: args.to || "all",
|
|
52
|
-
subj: args.subj,
|
|
53
|
-
body: args.body,
|
|
54
|
-
importance: args.importance || "normal",
|
|
55
|
-
at: Date.now(),
|
|
56
|
-
read: false,
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
await fs.appendFile(messagesPath, `${JSON.stringify(msg)}\n`, "utf-8");
|
|
60
|
-
return JSON.stringify({ ok: 1, id: msg.id });
|
|
61
|
-
},
|
|
62
|
-
});
|