opencodekit 0.14.6 → 0.15.1

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 (95) hide show
  1. package/README.md +2 -2
  2. package/dist/index.js +435 -57
  3. package/dist/template/.opencode/.env.example +1 -0
  4. package/dist/template/.opencode/AGENTS.md +13 -24
  5. package/dist/template/.opencode/README.md +8 -119
  6. package/dist/template/.opencode/agent/explore.md +2 -3
  7. package/dist/template/.opencode/agent/general.md +56 -0
  8. package/dist/template/.opencode/agent/plan.md +54 -0
  9. package/dist/template/.opencode/agent/scout.md +15 -5
  10. package/dist/template/.opencode/command/analyze-project.md +2 -2
  11. package/dist/template/.opencode/command/brainstorm.md +1 -1
  12. package/dist/template/.opencode/command/design-audit.md +4 -5
  13. package/dist/template/.opencode/command/design.md +4 -13
  14. package/dist/template/.opencode/command/generate-pattern.md +2 -9
  15. package/dist/template/.opencode/command/implement.md +4 -4
  16. package/dist/template/.opencode/command/init.md +1 -1
  17. package/dist/template/.opencode/command/new-feature.md +2 -3
  18. package/dist/template/.opencode/command/plan.md +1 -1
  19. package/dist/template/.opencode/command/pr.md +0 -1
  20. package/dist/template/.opencode/command/research.md +20 -6
  21. package/dist/template/.opencode/command/restore-image.md +1 -9
  22. package/dist/template/.opencode/command/revert-feature.md +1 -1
  23. package/dist/template/.opencode/command/review-codebase.md +4 -4
  24. package/dist/template/.opencode/command/status.md +1 -2
  25. package/dist/template/.opencode/command/summarize.md +1 -2
  26. package/dist/template/.opencode/command/triage.md +4 -32
  27. package/dist/template/.opencode/dcp.jsonc +68 -68
  28. package/dist/template/.opencode/memory/_templates/README.md +35 -0
  29. package/dist/template/.opencode/memory/_templates/project/architecture.md +60 -0
  30. package/dist/template/.opencode/memory/_templates/project/commands.md +72 -0
  31. package/dist/template/.opencode/memory/_templates/project/conventions.md +68 -0
  32. package/dist/template/.opencode/memory/_templates/project/gotchas.md +41 -0
  33. package/dist/template/.opencode/memory/beads-workflow.md +30 -29
  34. package/dist/template/.opencode/memory/project/architecture.md +31 -50
  35. package/dist/template/.opencode/memory/project/commands.md +41 -22
  36. package/dist/template/.opencode/memory/project/conventions.md +39 -177
  37. package/dist/template/.opencode/memory/project/gotchas.md +21 -177
  38. package/dist/template/.opencode/memory/user.example.md +5 -0
  39. package/dist/template/.opencode/opencode.json +628 -579
  40. package/dist/template/.opencode/package.json +18 -21
  41. package/dist/template/.opencode/plugin/compaction.ts +79 -85
  42. package/dist/template/.opencode/plugin/env-ctx.ts +19 -19
  43. package/dist/template/.opencode/plugin/lib/notify.ts +41 -45
  44. package/dist/template/.opencode/plugin/lsp.ts +197 -200
  45. package/dist/template/.opencode/plugin/memory.ts +14 -112
  46. package/dist/template/.opencode/plugin/package.json +5 -5
  47. package/dist/template/.opencode/plugin/sessions.ts +1 -1
  48. package/dist/template/.opencode/plugin/skill-mcp.ts +486 -521
  49. package/dist/template/.opencode/plugin/truncator.ts +47 -50
  50. package/dist/template/.opencode/plugin/tsconfig.json +14 -14
  51. package/dist/template/.opencode/skill/chrome-devtools/mcp.json +17 -17
  52. package/dist/template/.opencode/skill/condition-based-waiting/SKILL.md +17 -12
  53. package/dist/template/.opencode/skill/condition-based-waiting/example.ts +63 -69
  54. package/dist/template/.opencode/skill/defense-in-depth/SKILL.md +14 -8
  55. package/dist/template/.opencode/skill/dispatching-parallel-agents/SKILL.md +14 -3
  56. package/dist/template/.opencode/skill/playwright/mcp.json +14 -14
  57. package/dist/template/.opencode/skill/receiving-code-review/SKILL.md +21 -8
  58. package/dist/template/.opencode/skill/requesting-code-review/review.md +14 -0
  59. package/dist/template/.opencode/skill/root-cause-tracing/SKILL.md +18 -4
  60. package/dist/template/.opencode/skill/source-code-research/SKILL.md +9 -7
  61. package/dist/template/.opencode/skill/test-driven-development/SKILL.md +49 -32
  62. package/dist/template/.opencode/skill/testing-anti-patterns/SKILL.md +40 -22
  63. package/dist/template/.opencode/skill/testing-skills-with-subagents/SKILL.md +46 -26
  64. package/dist/template/.opencode/skill/tool-priority/SKILL.md +117 -44
  65. package/dist/template/.opencode/skill/v0/SKILL.md +1 -7
  66. package/dist/template/.opencode/skill/verification-before-completion/SKILL.md +27 -19
  67. package/dist/template/.opencode/skill/writing-skills/anthropic-best-practices.md +171 -148
  68. package/dist/template/.opencode/skill/writing-skills/persuasion-principles.md +39 -6
  69. package/dist/template/.opencode/tool/memory-read.ts +44 -56
  70. package/dist/template/.opencode/tool/memory-search.ts +8 -291
  71. package/dist/template/.opencode/tool/memory-update.ts +47 -51
  72. package/dist/template/.opencode/tool/observation.ts +6 -180
  73. package/dist/template/.opencode/tsconfig.json +19 -19
  74. package/package.json +19 -15
  75. package/dist/template/.opencode/.background-tasks.json +0 -114
  76. package/dist/template/.opencode/.ralph-state.json +0 -12
  77. package/dist/template/.opencode/agent/build.md +0 -327
  78. package/dist/template/.opencode/agent/ninja.md +0 -351
  79. package/dist/template/.opencode/agent/planner.md +0 -281
  80. package/dist/template/.opencode/agent/rush.md +0 -223
  81. package/dist/template/.opencode/memory/handoffs/README.md +0 -83
  82. package/dist/template/.opencode/memory/observations/.gitkeep +0 -0
  83. package/dist/template/.opencode/memory/observations/2026-01-09-pattern-ampcode-mcp-json-includetools-pattern.md +0 -42
  84. package/dist/template/.opencode/memory/vector_db/memories.lance/_transactions/0-0d25ba80-ba3b-4209-9046-b45d6093b4da.txn +0 -0
  85. package/dist/template/.opencode/memory/vector_db/memories.lance/_versions/1.manifest +0 -0
  86. package/dist/template/.opencode/memory/vector_db/memories.lance/data/1111100101010101011010004a9ef34df6b29f36a9a53a2892.lance +0 -0
  87. package/dist/template/.opencode/tool/ast-grep.ts +0 -245
  88. package/dist/template/.opencode/tool/background.ts +0 -509
  89. package/dist/template/.opencode/tool/bd-inbox.ts +0 -110
  90. package/dist/template/.opencode/tool/bd-msg.ts +0 -62
  91. package/dist/template/.opencode/tool/bd-release.ts +0 -71
  92. package/dist/template/.opencode/tool/bd-reserve.ts +0 -121
  93. package/dist/template/.opencode/tool/memory-embed.ts +0 -183
  94. package/dist/template/.opencode/tool/memory-index.ts +0 -769
  95. 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
- });