heartbeat-opencode-plugin 0.1.0 → 0.1.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.
package/index.ts CHANGED
@@ -4,6 +4,8 @@ import { SchedulerPlugin } from "./scheduler";
4
4
  import { TaskManagerPlugin, getTask, listTasks } from "./task-manager";
5
5
 
6
6
  type PluginOutput = Awaited<ReturnType<Plugin>>;
7
+ const OUTPUT_TAG_NOTE =
8
+ "Runtime contract: while running Heartbeat programs, every emitted status/output line must be prefixed with [program][taskId] for grepability.";
7
9
 
8
10
  function mergeTools(outputs: PluginOutput[]): Record<string, unknown> {
9
11
  const toolMap: Record<string, unknown> = {};
@@ -30,7 +32,7 @@ export const HeartbeatPlugin: Plugin = async (ctx) => {
30
32
 
31
33
  heartbeat_boot_context: tool({
32
34
  description:
33
- "Boot helper for a CPU cycle. Returns task state + prior memory for [program][taskId].",
35
+ `Boot helper for a CPU cycle. Returns task state + prior memory for [program][taskId]. ${OUTPUT_TAG_NOTE}`,
34
36
  args: {
35
37
  program: tool.schema.string().describe("Program name"),
36
38
  taskId: tool.schema.string().describe("Task identifier"),
package/memory/index.ts CHANGED
@@ -35,6 +35,8 @@ export function writeLog(input: {
35
35
 
36
36
  const LOG_ENTRY_PREFIX =
37
37
  /^\[\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z\]\s/;
38
+ const OUTPUT_TAG_NOTE =
39
+ "Runtime contract: while running Heartbeat programs, every emitted status/output line must be prefixed with [program][taskId] for grepability.";
38
40
 
39
41
  function readAllEntries(limit?: number): string[] {
40
42
  const path = getLogFilePath();
@@ -180,7 +182,8 @@ export const MemoryPlugin: Plugin = async (ctx) => {
180
182
  return {
181
183
  tool: {
182
184
  memory_write: tool({
183
- description: "Write a tagged log line as [program][taskId] message.",
185
+ description:
186
+ `Write a tagged log line as [program][taskId] message. ${OUTPUT_TAG_NOTE}`,
184
187
  args: {
185
188
  program: tool.schema.string().describe("Program name"),
186
189
  taskId: tool.schema.string().describe("Task identifier"),
@@ -193,7 +196,7 @@ export const MemoryPlugin: Plugin = async (ctx) => {
193
196
 
194
197
  write_log: tool({
195
198
  description:
196
- "Alias for memory_write. Writes tagged memory lines into the shared heartbeat log.",
199
+ `Alias for memory_write. Writes tagged memory lines into the shared heartbeat log. ${OUTPUT_TAG_NOTE}`,
197
200
  args: {
198
201
  program: tool.schema.string().describe("Program name"),
199
202
  taskId: tool.schema.string().describe("Task identifier"),
@@ -206,7 +209,7 @@ export const MemoryPlugin: Plugin = async (ctx) => {
206
209
 
207
210
  search_memory: tool({
208
211
  description:
209
- "Search memory by [program] and optional [taskId]. Use this on boot to restore previous cycle context.",
212
+ `Search memory by [program] and optional [taskId]. Use this on boot to restore previous cycle context. ${OUTPUT_TAG_NOTE}`,
210
213
  args: {
211
214
  program: tool.schema.string().describe("Program name"),
212
215
  taskId: tool.schema
@@ -227,7 +230,7 @@ export const MemoryPlugin: Plugin = async (ctx) => {
227
230
  }),
228
231
 
229
232
  read_program: tool({
230
- description: "Read all log lines containing [program].",
233
+ description: `Read all log lines containing [program]. ${OUTPUT_TAG_NOTE}`,
231
234
  args: {
232
235
  program: tool.schema.string().describe("Program name"),
233
236
  limit: tool.schema.number().optional().describe("Optional max lines"),
@@ -238,7 +241,8 @@ export const MemoryPlugin: Plugin = async (ctx) => {
238
241
  }),
239
242
 
240
243
  read_program_task: tool({
241
- description: "Read all log lines containing [program][taskId].",
244
+ description:
245
+ `Read all log lines containing [program][taskId]. ${OUTPUT_TAG_NOTE}`,
242
246
  args: {
243
247
  program: tool.schema.string().describe("Program name"),
244
248
  taskId: tool.schema.string().describe("Task identifier"),
@@ -251,7 +255,7 @@ export const MemoryPlugin: Plugin = async (ctx) => {
251
255
 
252
256
  memory_grep: tool({
253
257
  description:
254
- "Regex grep across the shared heartbeat log file. Useful for free-form memory queries.",
258
+ `Regex grep across the shared heartbeat log file. Useful for free-form memory queries. ${OUTPUT_TAG_NOTE}`,
255
259
  args: {
256
260
  pattern: tool.schema.string().describe("JavaScript regex pattern"),
257
261
  limit: tool.schema.number().optional().describe("Optional max lines"),
@@ -266,7 +270,7 @@ export const MemoryPlugin: Plugin = async (ctx) => {
266
270
  }),
267
271
 
268
272
  grep_logs: tool({
269
- description: "Alias for memory_grep.",
273
+ description: `Alias for memory_grep. ${OUTPUT_TAG_NOTE}`,
270
274
  args: {
271
275
  pattern: tool.schema.string().describe("JavaScript regex pattern"),
272
276
  limit: tool.schema.number().optional().describe("Optional max lines"),
@@ -281,7 +285,7 @@ export const MemoryPlugin: Plugin = async (ctx) => {
281
285
  }),
282
286
 
283
287
  tail_logs: tool({
284
- description: "Return recent lines from the shared heartbeat log.",
288
+ description: `Return recent lines from the shared heartbeat log. ${OUTPUT_TAG_NOTE}`,
285
289
  args: {
286
290
  lines: tool.schema
287
291
  .number()
@@ -294,7 +298,7 @@ export const MemoryPlugin: Plugin = async (ctx) => {
294
298
  }),
295
299
 
296
300
  log_info: tool({
297
- description: "Get shared log file path and stats.",
301
+ description: `Get shared log file path and stats. ${OUTPUT_TAG_NOTE}`,
298
302
  args: {},
299
303
  async execute() {
300
304
  return JSON.stringify(getLogInfo(), null, 2);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "heartbeat-opencode-plugin",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Heartbeat Runtime",
5
5
  "type": "module",
6
6
  "main": "./index.ts",
@@ -70,6 +70,13 @@ export interface RunResult {
70
70
  const IS_MAC = process.platform === "darwin";
71
71
  const LAUNCH_AGENTS_DIR = path.join(homedir(), "Library", "LaunchAgents");
72
72
  const SCHEDULER_ENTRY = fileURLToPath(import.meta.url);
73
+ const OPENCODE_COMMAND = "opencode";
74
+ const OPENCODE_FALLBACK_PATHS = [
75
+ "/opt/homebrew/bin/opencode",
76
+ "/usr/local/bin/opencode",
77
+ ];
78
+ const OUTPUT_TAG_NOTE =
79
+ "Runtime contract: while running Heartbeat programs, every emitted status/output line must be prefixed with [program][taskId] for grepability.";
73
80
 
74
81
  function ensureDir(dir: string): void {
75
82
  if (!fs.existsSync(dir)) {
@@ -103,6 +110,57 @@ function appendJobLog(job: Pick<Job, "id">, message: string): void {
103
110
  appendLog(`[Scheduler][job:${job.id}] ${message}`);
104
111
  }
105
112
 
113
+ function isExecutable(filePath: string): boolean {
114
+ try {
115
+ fs.accessSync(filePath, fs.constants.X_OK);
116
+ return true;
117
+ } catch {
118
+ return false;
119
+ }
120
+ }
121
+
122
+ function findExecutableInPath(command: string, envPath?: string): string | null {
123
+ if (!envPath) return null;
124
+
125
+ const pathEntries = envPath.split(path.delimiter).filter(Boolean);
126
+ for (const pathEntry of pathEntries) {
127
+ const candidate = path.join(pathEntry, command);
128
+ if (isExecutable(candidate)) {
129
+ return candidate;
130
+ }
131
+ }
132
+
133
+ return null;
134
+ }
135
+
136
+ function resolveOpencodeCommand(command: string): string {
137
+ if (path.basename(command) !== OPENCODE_COMMAND) {
138
+ return command;
139
+ }
140
+
141
+ if (isExecutable(command)) {
142
+ return command;
143
+ }
144
+
145
+ const envOverride = process.env.OPENCODE_BIN?.trim();
146
+ if (envOverride && isExecutable(envOverride)) {
147
+ return envOverride;
148
+ }
149
+
150
+ const fromPath = findExecutableInPath(OPENCODE_COMMAND, process.env.PATH);
151
+ if (fromPath) {
152
+ return fromPath;
153
+ }
154
+
155
+ for (const fallbackPath of OPENCODE_FALLBACK_PATHS) {
156
+ if (isExecutable(fallbackPath)) {
157
+ return fallbackPath;
158
+ }
159
+ }
160
+
161
+ return command;
162
+ }
163
+
106
164
  function readJob(jobId: string): Job | null {
107
165
  const file = jobFilePath(jobId);
108
166
  if (!fs.existsSync(file)) return null;
@@ -228,7 +286,7 @@ export function createOpencodeJob(input: CreateOpencodeJobInput): Job {
228
286
  kind: "opencode",
229
287
  schedule: input.schedule,
230
288
  prompt: input.prompt,
231
- command: "opencode",
289
+ command: resolveOpencodeCommand(OPENCODE_COMMAND),
232
290
  args: ["run", heartbeatPrompt],
233
291
  workdir: input.workdir ?? getDefaultWorkdir(),
234
292
  enabled: input.enabled ?? true,
@@ -261,7 +319,7 @@ export function updateJob(
261
319
  if (!nextPrompt) {
262
320
  throw new Error(`Opencode job ${existing.id} is missing prompt content.`);
263
321
  }
264
- nextCommand = "opencode";
322
+ nextCommand = resolveOpencodeCommand(OPENCODE_COMMAND);
265
323
  nextArgs = [
266
324
  "run",
267
325
  buildHeartbeatPrompt({
@@ -364,12 +422,19 @@ export function runJob(jobId: string): Promise<RunResult> {
364
422
  writeJob(job);
365
423
 
366
424
  appendJobLog(job, `Starting job "${job.name}"`);
367
- appendJobLog(job, `Command: ${job.command} ${job.args.join(" ")}`);
425
+ const resolvedCommand = job.kind === "opencode"
426
+ ? resolveOpencodeCommand(job.command)
427
+ : job.command;
428
+ if (resolvedCommand !== job.command) {
429
+ job.command = resolvedCommand;
430
+ appendJobLog(job, `Resolved opencode executable: ${resolvedCommand}`);
431
+ }
432
+ appendJobLog(job, `Command: ${resolvedCommand} ${job.args.join(" ")}`);
368
433
  appendJobLog(job, `Workdir: ${job.workdir}`);
369
434
 
370
435
  let child: ReturnType<typeof spawn>;
371
436
  try {
372
- child = spawn(job.command, job.args, {
437
+ child = spawn(resolvedCommand, job.args, {
373
438
  cwd: job.workdir,
374
439
  env: process.env,
375
440
  shell: false,
@@ -752,7 +817,7 @@ export const SchedulerPlugin: Plugin = async (ctx) => {
752
817
  tool: {
753
818
  scheduler_create_opencode_job: tool({
754
819
  description:
755
- "Create a human-managed cron job that runs `opencode run` with the provided prompt.",
820
+ `Create a human-managed cron job that runs \`opencode run\` with the provided prompt. ${OUTPUT_TAG_NOTE}`,
756
821
  args: {
757
822
  name: tool.schema.string().describe("Job name"),
758
823
  schedule: tool.schema.string().describe("Cron schedule (e.g. */5 * * * *)"),
@@ -803,7 +868,7 @@ export const SchedulerPlugin: Plugin = async (ctx) => {
803
868
 
804
869
  scheduler_create_command_job: tool({
805
870
  description:
806
- "Create a human-managed cron job for a generic command. Output streams to the shared heartbeat log.",
871
+ `Create a human-managed cron job for a generic command. Output streams to the shared heartbeat log. ${OUTPUT_TAG_NOTE}`,
807
872
  args: {
808
873
  name: tool.schema.string().describe("Job name"),
809
874
  schedule: tool.schema.string().describe("Cron schedule"),
@@ -830,7 +895,7 @@ export const SchedulerPlugin: Plugin = async (ctx) => {
830
895
  }),
831
896
 
832
897
  scheduler_list_jobs: tool({
833
- description: "List all heartbeat scheduler jobs.",
898
+ description: `List all heartbeat scheduler jobs. ${OUTPUT_TAG_NOTE}`,
834
899
  args: {},
835
900
  async execute() {
836
901
  const jobs = listJobs();
@@ -842,7 +907,7 @@ export const SchedulerPlugin: Plugin = async (ctx) => {
842
907
  }),
843
908
 
844
909
  scheduler_get_job: tool({
845
- description: "Get a scheduler job by id.",
910
+ description: `Get a scheduler job by id. ${OUTPUT_TAG_NOTE}`,
846
911
  args: {
847
912
  jobId: tool.schema.string().describe("Job ID"),
848
913
  },
@@ -856,7 +921,8 @@ export const SchedulerPlugin: Plugin = async (ctx) => {
856
921
  }),
857
922
 
858
923
  scheduler_update_job: tool({
859
- description: "Update schedule, prompt, or metadata of an existing job.",
924
+ description:
925
+ `Update schedule, prompt, or metadata of an existing job. ${OUTPUT_TAG_NOTE}`,
860
926
  args: {
861
927
  jobId: tool.schema.string().describe("Job ID"),
862
928
  name: tool.schema.string().optional().describe("Updated name"),
@@ -883,7 +949,8 @@ export const SchedulerPlugin: Plugin = async (ctx) => {
883
949
  }),
884
950
 
885
951
  scheduler_run_job: tool({
886
- description: "Run a job immediately. Output is appended to one shared log file.",
952
+ description:
953
+ `Run a job immediately. Output is appended to one shared log file. ${OUTPUT_TAG_NOTE}`,
887
954
  args: {
888
955
  jobId: tool.schema.string().describe("Job ID"),
889
956
  },
@@ -898,7 +965,7 @@ export const SchedulerPlugin: Plugin = async (ctx) => {
898
965
  }),
899
966
 
900
967
  scheduler_install_job: tool({
901
- description: "Install a job in macOS launchd.",
968
+ description: `Install a job in macOS launchd. ${OUTPUT_TAG_NOTE}`,
902
969
  args: {
903
970
  jobId: tool.schema.string().describe("Job ID"),
904
971
  },
@@ -916,7 +983,7 @@ export const SchedulerPlugin: Plugin = async (ctx) => {
916
983
  }),
917
984
 
918
985
  scheduler_uninstall_job: tool({
919
- description: "Uninstall a job from macOS launchd.",
986
+ description: `Uninstall a job from macOS launchd. ${OUTPUT_TAG_NOTE}`,
920
987
  args: {
921
988
  jobId: tool.schema.string().describe("Job ID"),
922
989
  },
@@ -934,7 +1001,8 @@ export const SchedulerPlugin: Plugin = async (ctx) => {
934
1001
  }),
935
1002
 
936
1003
  scheduler_delete_job: tool({
937
- description: "Delete a scheduler job and uninstall its launchd entry if present.",
1004
+ description:
1005
+ `Delete a scheduler job and uninstall its launchd entry if present. ${OUTPUT_TAG_NOTE}`,
938
1006
  args: {
939
1007
  jobId: tool.schema.string().describe("Job ID"),
940
1008
  },
@@ -945,7 +1013,7 @@ export const SchedulerPlugin: Plugin = async (ctx) => {
945
1013
  }),
946
1014
 
947
1015
  scheduler_info: tool({
948
- description: "Show scheduler storage paths and shared log path.",
1016
+ description: `Show scheduler storage paths and shared log path. ${OUTPUT_TAG_NOTE}`,
949
1017
  args: {},
950
1018
  async execute() {
951
1019
  return JSON.stringify(
@@ -40,6 +40,8 @@ const VALID_PRIORITIES = new Set<TaskPriority>([
40
40
  "high",
41
41
  "critical",
42
42
  ]);
43
+ const OUTPUT_TAG_NOTE =
44
+ "Runtime contract: while running Heartbeat programs, every emitted status/output line must be prefixed with [program][taskId] for grepability.";
43
45
 
44
46
  function nowIso(): string {
45
47
  return new Date().toISOString();
@@ -128,7 +130,8 @@ export function listTasks(filters?: TaskFilters): Task[] {
128
130
  const tasks = storeInstance().read();
129
131
  return tasks.filter((task) => {
130
132
  if (filters?.program && task.program !== filters.program) return false;
131
- if (filters?.parentTaskId && task.parentTaskId !== filters.parentTaskId) return false;
133
+ if (filters?.parentTaskId && task.parentTaskId !== filters.parentTaskId)
134
+ return false;
132
135
  if (filters?.status && task.status !== filters.status) return false;
133
136
  if (filters?.priority && task.priority !== filters.priority) return false;
134
137
  return true;
@@ -137,7 +140,9 @@ export function listTasks(filters?: TaskFilters): Task[] {
137
140
 
138
141
  export function getTask(program: string, taskId: string): Task | undefined {
139
142
  const tasks = storeInstance().read();
140
- return tasks.find((task) => taskKey(task.program, task.taskId) === taskKey(program, taskId));
143
+ return tasks.find(
144
+ (task) => taskKey(task.program, task.taskId) === taskKey(program, taskId),
145
+ );
141
146
  }
142
147
 
143
148
  export function createTask(input: {
@@ -152,7 +157,11 @@ export function createTask(input: {
152
157
  const instance = storeInstance();
153
158
  const tasks = instance.read();
154
159
 
155
- const duplicate = tasks.find((task) => taskKey(task.program, task.taskId) === taskKey(input.program, input.taskId));
160
+ const duplicate = tasks.find(
161
+ (task) =>
162
+ taskKey(task.program, task.taskId) ===
163
+ taskKey(input.program, input.taskId),
164
+ );
156
165
  if (duplicate) {
157
166
  throw new Error(`Task already exists: [${input.program}][${input.taskId}]`);
158
167
  }
@@ -172,7 +181,9 @@ export function createTask(input: {
172
181
 
173
182
  tasks.push(task);
174
183
  instance.write(tasks);
175
- appendLog(`[${task.program}][${task.taskId}] Task created with status=${task.status}`);
184
+ appendLog(
185
+ `[${task.program}][${task.taskId}] Task created with status=${task.status}`,
186
+ );
176
187
  return task;
177
188
  }
178
189
 
@@ -187,7 +198,11 @@ export function updateTask(input: {
187
198
  }): Task | null {
188
199
  const instance = storeInstance();
189
200
  const tasks = instance.read();
190
- const index = tasks.findIndex((task) => taskKey(task.program, task.taskId) === taskKey(input.program, input.taskId));
201
+ const index = tasks.findIndex(
202
+ (task) =>
203
+ taskKey(task.program, task.taskId) ===
204
+ taskKey(input.program, input.taskId),
205
+ );
191
206
 
192
207
  if (index === -1) return null;
193
208
 
@@ -195,9 +210,13 @@ export function updateTask(input: {
195
210
  const updated: Task = {
196
211
  ...existing,
197
212
  ...("parentTaskId" in input ? { parentTaskId: input.parentTaskId } : {}),
198
- ...("description" in input ? { description: input.description ?? existing.description } : {}),
213
+ ...("description" in input
214
+ ? { description: input.description ?? existing.description }
215
+ : {}),
199
216
  ...("status" in input ? { status: input.status ?? existing.status } : {}),
200
- ...("priority" in input ? { priority: input.priority ?? existing.priority } : {}),
217
+ ...("priority" in input
218
+ ? { priority: input.priority ?? existing.priority }
219
+ : {}),
201
220
  ...("references" in input ? { references: input.references } : {}),
202
221
  updatedAt: nowIso(),
203
222
  };
@@ -211,7 +230,9 @@ export function updateTask(input: {
211
230
 
212
231
  tasks[index] = updated;
213
232
  instance.write(tasks);
214
- appendLog(`[${updated.program}][${updated.taskId}] Task updated status=${updated.status}`);
233
+ appendLog(
234
+ `[${updated.program}][${updated.taskId}] Task updated status=${updated.status}`,
235
+ );
215
236
  return updated;
216
237
  }
217
238
 
@@ -226,7 +247,9 @@ export function markTaskDone(program: string, taskId: string): Task | null {
226
247
  export function deleteTask(program: string, taskId: string): boolean {
227
248
  const instance = storeInstance();
228
249
  const tasks = instance.read();
229
- const filtered = tasks.filter((task) => taskKey(task.program, task.taskId) !== taskKey(program, taskId));
250
+ const filtered = tasks.filter(
251
+ (task) => taskKey(task.program, task.taskId) !== taskKey(program, taskId),
252
+ );
230
253
 
231
254
  if (filtered.length === tasks.length) {
232
255
  return false;
@@ -247,20 +270,27 @@ export const TaskManagerPlugin: Plugin = async (ctx) => {
247
270
  return {
248
271
  tool: {
249
272
  task_create: tool({
250
- description:
251
- "Create a task tracked by [program][taskId]. Use this before running a cron cycle.",
273
+ description: `Create a task tracked by [program][taskId]. `,
252
274
  args: {
253
- program: tool.schema.string().describe("Program name used for task and memory tags"),
254
- taskId: tool.schema.string().describe("Task identifier in kebab-case"),
275
+ program: tool.schema
276
+ .string()
277
+ .describe("Program name used for task and memory tags"),
278
+ taskId: tool.schema
279
+ .string()
280
+ .describe("Task identifier in snake_case"),
255
281
  parentTaskId: tool.schema
256
282
  .string()
257
283
  .optional()
258
284
  .describe("Optional parent task ID to create task hierarchies"),
259
- description: tool.schema.string().describe("Human description of the task"),
285
+ description: tool.schema
286
+ .string()
287
+ .describe("Human description of the task"),
260
288
  status: tool.schema
261
289
  .string()
262
290
  .optional()
263
- .describe("Optional status: pending, running, blocked, raised, done"),
291
+ .describe(
292
+ "Optional status: pending, running, blocked, raised, done",
293
+ ),
264
294
  priority: tool.schema
265
295
  .string()
266
296
  .optional()
@@ -299,12 +329,24 @@ export const TaskManagerPlugin: Plugin = async (ctx) => {
299
329
  }),
300
330
 
301
331
  task_list: tool({
302
- description: "List tasks, optionally filtered by program/status/priority.",
332
+ description: `List tasks, optionally filtered by program/status/priority. ${OUTPUT_TAG_NOTE}`,
303
333
  args: {
304
- program: tool.schema.string().optional().describe("Optional program filter"),
305
- parentTaskId: tool.schema.string().optional().describe("Optional parent task filter"),
306
- status: tool.schema.string().optional().describe("Optional status filter"),
307
- priority: tool.schema.string().optional().describe("Optional priority filter"),
334
+ program: tool.schema
335
+ .string()
336
+ .optional()
337
+ .describe("Optional program filter"),
338
+ parentTaskId: tool.schema
339
+ .string()
340
+ .optional()
341
+ .describe("Optional parent task filter"),
342
+ status: tool.schema
343
+ .string()
344
+ .optional()
345
+ .describe("Optional status filter"),
346
+ priority: tool.schema
347
+ .string()
348
+ .optional()
349
+ .describe("Optional priority filter"),
308
350
  },
309
351
  async execute(args) {
310
352
  const status = normalizeStatus(args.status);
@@ -331,7 +373,7 @@ export const TaskManagerPlugin: Plugin = async (ctx) => {
331
373
  }),
332
374
 
333
375
  task_get: tool({
334
- description: "Get a single task by [program][taskId].",
376
+ description: `Get a single task by [program][taskId]. ${OUTPUT_TAG_NOTE}`,
335
377
  args: {
336
378
  program: tool.schema.string().describe("Program name"),
337
379
  taskId: tool.schema.string().describe("Task ID"),
@@ -346,7 +388,7 @@ export const TaskManagerPlugin: Plugin = async (ctx) => {
346
388
  }),
347
389
 
348
390
  task_update: tool({
349
- description: "Update task metadata or status.",
391
+ description: `Update task metadata or status. ${OUTPUT_TAG_NOTE}`,
350
392
  args: {
351
393
  program: tool.schema.string().describe("Program name"),
352
394
  taskId: tool.schema.string().describe("Task ID"),
@@ -354,10 +396,19 @@ export const TaskManagerPlugin: Plugin = async (ctx) => {
354
396
  .string()
355
397
  .optional()
356
398
  .describe("Updated parent task ID"),
357
- description: tool.schema.string().optional().describe("Updated description"),
399
+ description: tool.schema
400
+ .string()
401
+ .optional()
402
+ .describe("Updated description"),
358
403
  status: tool.schema.string().optional().describe("Updated status"),
359
- priority: tool.schema.string().optional().describe("Updated priority"),
360
- references: tool.schema.string().optional().describe("Updated references"),
404
+ priority: tool.schema
405
+ .string()
406
+ .optional()
407
+ .describe("Updated priority"),
408
+ references: tool.schema
409
+ .string()
410
+ .optional()
411
+ .describe("Updated references"),
361
412
  },
362
413
  async execute(args) {
363
414
  const status = normalizeStatus(args.status);
@@ -410,7 +461,7 @@ export const TaskManagerPlugin: Plugin = async (ctx) => {
410
461
  }),
411
462
 
412
463
  task_mark_done: tool({
413
- description: "Mark a task as done after it is resolved.",
464
+ description: `Mark a task as done after it is resolved. ${OUTPUT_TAG_NOTE}`,
414
465
  args: {
415
466
  program: tool.schema.string().describe("Program name"),
416
467
  taskId: tool.schema.string().describe("Task ID"),
@@ -425,7 +476,7 @@ export const TaskManagerPlugin: Plugin = async (ctx) => {
425
476
  }),
426
477
 
427
478
  task_delete: tool({
428
- description: "Delete a task from the task manager.",
479
+ description: `Delete a task from the task manager. ${OUTPUT_TAG_NOTE}`,
429
480
  args: {
430
481
  program: tool.schema.string().describe("Program name"),
431
482
  taskId: tool.schema.string().describe("Task ID"),
@@ -440,7 +491,7 @@ export const TaskManagerPlugin: Plugin = async (ctx) => {
440
491
  }),
441
492
 
442
493
  task_store_info: tool({
443
- description: "Get task store file path and current task count.",
494
+ description: `Get task store file path and current task count. ${OUTPUT_TAG_NOTE}`,
444
495
  args: {},
445
496
  async execute() {
446
497
  const tasks = listTasks();