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 +3 -1
- package/memory/index.ts +13 -9
- package/package.json +1 -1
- package/scheduler/index.ts +82 -14
- package/task-manager/index.ts +79 -28
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
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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:
|
|
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
package/scheduler/index.ts
CHANGED
|
@@ -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:
|
|
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 =
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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(
|
package/task-manager/index.ts
CHANGED
|
@@ -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)
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
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
|
|
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(
|
|
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(
|
|
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
|
|
254
|
-
|
|
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
|
|
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(
|
|
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:
|
|
332
|
+
description: `List tasks, optionally filtered by program/status/priority. ${OUTPUT_TAG_NOTE}`,
|
|
303
333
|
args: {
|
|
304
|
-
program: tool.schema
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
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:
|
|
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:
|
|
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
|
|
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
|
|
360
|
-
|
|
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:
|
|
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:
|
|
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:
|
|
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();
|