@trevonistrevon/pi-loop 0.4.7 → 0.4.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +47 -10
- package/dist/store.d.ts +1 -0
- package/dist/store.js +1 -0
- package/dist/types.d.ts +1 -0
- package/package.json +1 -1
- package/src/index.ts +49 -11
- package/src/store.ts +2 -1
- package/src/types.ts +1 -0
package/dist/index.js
CHANGED
|
@@ -183,6 +183,7 @@ export default function (pi) {
|
|
|
183
183
|
if (nativeTaskStore) {
|
|
184
184
|
nativeTaskStore.sweepCompleted();
|
|
185
185
|
widget.update();
|
|
186
|
+
await cleanupTaskBacklogLoops();
|
|
186
187
|
}
|
|
187
188
|
}
|
|
188
189
|
let agentRunning = false;
|
|
@@ -271,11 +272,13 @@ export default function (pi) {
|
|
|
271
272
|
});
|
|
272
273
|
return true;
|
|
273
274
|
}
|
|
274
|
-
async function flushPendingNotifications() {
|
|
275
|
+
async function flushPendingNotifications(options) {
|
|
275
276
|
if (flushPromise)
|
|
276
277
|
return flushPromise;
|
|
277
278
|
flushPromise = (async () => {
|
|
278
|
-
if (agentRunning
|
|
279
|
+
if (agentRunning)
|
|
280
|
+
return;
|
|
281
|
+
if (!options?.ignorePendingMessages && _latestCtx?.hasPendingMessages())
|
|
279
282
|
return;
|
|
280
283
|
const entries = [...pendingNotifications.entries()]
|
|
281
284
|
.sort(([, left], [, right]) => left.timestamp - right.timestamp);
|
|
@@ -375,7 +378,8 @@ export default function (pi) {
|
|
|
375
378
|
agentRunning = false;
|
|
376
379
|
_latestCtx = ctx;
|
|
377
380
|
widget.setUICtx(ctx.ui);
|
|
378
|
-
await flushPendingNotifications();
|
|
381
|
+
await flushPendingNotifications({ ignorePendingMessages: true });
|
|
382
|
+
await cleanupTaskBacklogLoops();
|
|
379
383
|
await pumpLoops();
|
|
380
384
|
});
|
|
381
385
|
pi.on("session_shutdown", async () => {
|
|
@@ -466,6 +470,7 @@ Skip this tool when the task is a one-off check (just do it directly) or when th
|
|
|
466
470
|
- **prompt**: what to do when the loop fires (e.g., "check if the build passed")
|
|
467
471
|
- **recurring**: repeat or fire once (default: true)
|
|
468
472
|
- **autoTask**: when pi-tasks is loaded or native task fallback is active, auto-create a task on each fire
|
|
473
|
+
- **taskBacklog**: mark this as a task-backlog worker loop so it auto-deletes when pending tasks reach zero
|
|
469
474
|
- **readOnly**: restrict the agent to read-only tools when this loop fires (default: false)
|
|
470
475
|
- **maxFires**: auto-stop after N fires — prevents infinite token burn on polling loops`,
|
|
471
476
|
promptGuidelines: [
|
|
@@ -485,6 +490,7 @@ Skip this tool when the task is a one-off check (just do it directly) or when th
|
|
|
485
490
|
"## Task-driven workflows",
|
|
486
491
|
"Do not rely on a past 'tasks:created' event to replay. If tasks already exist, bootstrap the first pass in the current turn or use a hybrid/event loop that can catch future task creation and a cron safety-net.",
|
|
487
492
|
"Use autoTask only when you want the loop itself to create a task on each fire. For processing an existing task backlog, leave autoTask off and have the loop run TaskList to pick the next pending task.",
|
|
493
|
+
"Set taskBacklog: true for task-worker loops that process the existing pending queue. Task-backlog loops bootstrap against existing pending tasks and auto-delete when the queue reaches zero.",
|
|
488
494
|
"When no tasks are pending, the loop should stop itself or skip the wake entirely — no tokens burned on empty polls.",
|
|
489
495
|
"After creating a loop, tell the user the loop ID so they can cancel it with LoopDelete.",
|
|
490
496
|
],
|
|
@@ -493,13 +499,14 @@ Skip this tool when the task is a one-off check (just do it directly) or when th
|
|
|
493
499
|
prompt: Type.String({ description: "Prompt to run when the loop fires" }),
|
|
494
500
|
recurring: Type.Optional(Type.Boolean({ description: "Whether loop repeats (default: true)", default: true })),
|
|
495
501
|
autoTask: Type.Optional(Type.Boolean({ description: "Auto-create pi-tasks task on fire", default: false })),
|
|
502
|
+
taskBacklog: Type.Optional(Type.Boolean({ description: "Mark as a task-backlog worker loop that auto-deletes when pending tasks reach zero", default: false })),
|
|
496
503
|
triggerType: Type.Optional(Type.String({ description: "cron, event, or hybrid (inferred from trigger string if omitted)", enum: ["cron", "event", "hybrid"] })),
|
|
497
504
|
debounceMs: Type.Optional(Type.Number({ description: "Debounce for hybrid triggers (default: 30000)", default: 30000 })),
|
|
498
505
|
readOnly: Type.Optional(Type.Boolean({ description: "Restrict the agent to read-only tools when this loop fires (default: false)", default: false })),
|
|
499
506
|
maxFires: Type.Optional(Type.Number({ description: "Auto-stop after N fires. Prevents infinite token burn on polling loops." })),
|
|
500
507
|
}),
|
|
501
508
|
async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
|
|
502
|
-
const { trigger: triggerInput, prompt, recurring, autoTask, triggerType, debounceMs, readOnly, maxFires } = params;
|
|
509
|
+
const { trigger: triggerInput, prompt, recurring, autoTask, taskBacklog, triggerType, debounceMs, readOnly, maxFires } = params;
|
|
503
510
|
let trigger;
|
|
504
511
|
const inferred = triggerType ?? inferTriggerType(triggerInput);
|
|
505
512
|
if (inferred === "cron") {
|
|
@@ -526,6 +533,7 @@ Skip this tool when the task is a one-off check (just do it directly) or when th
|
|
|
526
533
|
const entry = store.create(trigger, prompt, {
|
|
527
534
|
recurring: recurring ?? (inferred !== "event"),
|
|
528
535
|
autoTask,
|
|
536
|
+
taskBacklog,
|
|
529
537
|
readOnly,
|
|
530
538
|
maxFires,
|
|
531
539
|
});
|
|
@@ -556,6 +564,7 @@ Skip this tool when the task is a one-off check (just do it directly) or when th
|
|
|
556
564
|
`Trigger: ${triggerDesc}\n` +
|
|
557
565
|
`Recurring: ${entry.recurring}\n` +
|
|
558
566
|
(entry.autoTask ? `Auto-task: enabled\n` : "") +
|
|
567
|
+
(entry.taskBacklog ? `Task-backlog: enabled\n` : "") +
|
|
559
568
|
(bootstrapped ? "Bootstrap: queued initial wake for existing pending tasks\n" : "") +
|
|
560
569
|
((tasksAvailable || nativeTasksRegistered) ? "" : "(task system not ready yet — autoTask may not fire until native fallback or pi-tasks becomes available)\n") +
|
|
561
570
|
`ID: ${entry.id} (use LoopDelete to cancel)`));
|
|
@@ -928,10 +937,33 @@ Use MonitorList to find the monitor ID, then stop it with this tool.`,
|
|
|
928
937
|
}
|
|
929
938
|
const AUTO_TASK_WORKER_THRESHOLD = 5;
|
|
930
939
|
const AUTO_TASK_WORKER_PROMPT = "Run TaskList, pick next pending task, mark it in_progress, implement it, run validation, complete it. If no pending tasks remain, call LoopDelete on your own loop ID.";
|
|
931
|
-
function
|
|
932
|
-
return
|
|
940
|
+
function isAutoTaskWorkerLoop(entry) {
|
|
941
|
+
return entry.status === "active"
|
|
933
942
|
&& entry.prompt === AUTO_TASK_WORKER_PROMPT
|
|
934
|
-
&& triggerHasEventSource(entry.trigger, "tasks:created")
|
|
943
|
+
&& triggerHasEventSource(entry.trigger, "tasks:created");
|
|
944
|
+
}
|
|
945
|
+
function isTaskBacklogLoop(entry) {
|
|
946
|
+
return entry.status === "active"
|
|
947
|
+
&& triggerHasEventSource(entry.trigger, "tasks:created")
|
|
948
|
+
&& (entry.taskBacklog === true || isAutoTaskWorkerLoop(entry));
|
|
949
|
+
}
|
|
950
|
+
function findAutoTaskWorkerLoop() {
|
|
951
|
+
return store.list().find(isAutoTaskWorkerLoop);
|
|
952
|
+
}
|
|
953
|
+
async function cleanupTaskBacklogLoops() {
|
|
954
|
+
const backlogLoops = store.list().filter(isTaskBacklogLoop);
|
|
955
|
+
if (backlogLoops.length === 0)
|
|
956
|
+
return 0;
|
|
957
|
+
const pending = await hasPendingTasks();
|
|
958
|
+
if (pending < 0 || pending > 0)
|
|
959
|
+
return 0;
|
|
960
|
+
for (const entry of backlogLoops) {
|
|
961
|
+
debug(`task backlog loop #${entry.id} — no pending tasks remain, deleting`);
|
|
962
|
+
triggerSystem.remove(entry.id);
|
|
963
|
+
store.delete(entry.id);
|
|
964
|
+
}
|
|
965
|
+
widget.update();
|
|
966
|
+
return backlogLoops.length;
|
|
935
967
|
}
|
|
936
968
|
async function ensureAutoTaskWorkerLoop(taskStore) {
|
|
937
969
|
if (taskStore.pendingCount() < AUTO_TASK_WORKER_THRESHOLD)
|
|
@@ -947,6 +979,7 @@ Use MonitorList to find the monitor ID, then stop it with this tool.`,
|
|
|
947
979
|
};
|
|
948
980
|
const entry = store.create(trigger, AUTO_TASK_WORKER_PROMPT, {
|
|
949
981
|
recurring: true,
|
|
982
|
+
taskBacklog: true,
|
|
950
983
|
maxFires: 30,
|
|
951
984
|
});
|
|
952
985
|
triggerSystem.add(entry);
|
|
@@ -1035,6 +1068,7 @@ Use MonitorList to find the monitor ID, then stop it with this tool.`,
|
|
|
1035
1068
|
ui.notify(`Task #${task.id} reopened`, "info");
|
|
1036
1069
|
}
|
|
1037
1070
|
widget.update();
|
|
1071
|
+
await cleanupTaskBacklogLoops();
|
|
1038
1072
|
return viewNativeTasks(ui);
|
|
1039
1073
|
}
|
|
1040
1074
|
// ── Native task tools (only when pi-tasks is absent) ──
|
|
@@ -1147,7 +1181,7 @@ Parameters: id (required), status, subject, description`,
|
|
|
1147
1181
|
subject: Type.Optional(Type.String({ description: "New title" })),
|
|
1148
1182
|
description: Type.Optional(Type.String({ description: "New description" })),
|
|
1149
1183
|
}),
|
|
1150
|
-
execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
|
|
1184
|
+
async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
|
|
1151
1185
|
const { id, status, subject, description } = params;
|
|
1152
1186
|
const entry = taskStore.update(id, {
|
|
1153
1187
|
status: status,
|
|
@@ -1157,6 +1191,7 @@ Parameters: id (required), status, subject, description`,
|
|
|
1157
1191
|
if (!entry)
|
|
1158
1192
|
return Promise.resolve(textResult(`Task #${id} not found`));
|
|
1159
1193
|
widget.update();
|
|
1194
|
+
await cleanupTaskBacklogLoops();
|
|
1160
1195
|
const statusMsg = status ? ` → ${status}` : "";
|
|
1161
1196
|
return Promise.resolve(textResult(`Task #${id} updated${statusMsg}`));
|
|
1162
1197
|
},
|
|
@@ -1168,11 +1203,13 @@ Parameters: id (required), status, subject, description`,
|
|
|
1168
1203
|
parameters: Type.Object({
|
|
1169
1204
|
id: Type.String({ description: "Task ID to delete" }),
|
|
1170
1205
|
}),
|
|
1171
|
-
execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
|
|
1206
|
+
async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
|
|
1172
1207
|
const deleted = taskStore.delete(params.id);
|
|
1173
1208
|
widget.update();
|
|
1174
|
-
if (deleted)
|
|
1209
|
+
if (deleted) {
|
|
1210
|
+
await cleanupTaskBacklogLoops();
|
|
1175
1211
|
return Promise.resolve(textResult(`Task #${params.id} deleted`));
|
|
1212
|
+
}
|
|
1176
1213
|
return Promise.resolve(textResult(`Task #${params.id} not found`));
|
|
1177
1214
|
},
|
|
1178
1215
|
});
|
package/dist/store.d.ts
CHANGED
package/dist/store.js
CHANGED
package/dist/types.d.ts
CHANGED
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -196,6 +196,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
196
196
|
if (nativeTaskStore) {
|
|
197
197
|
nativeTaskStore.sweepCompleted();
|
|
198
198
|
widget.update();
|
|
199
|
+
await cleanupTaskBacklogLoops();
|
|
199
200
|
}
|
|
200
201
|
}
|
|
201
202
|
|
|
@@ -292,11 +293,12 @@ export default function (pi: ExtensionAPI) {
|
|
|
292
293
|
return true;
|
|
293
294
|
}
|
|
294
295
|
|
|
295
|
-
async function flushPendingNotifications(): Promise<void> {
|
|
296
|
+
async function flushPendingNotifications(options?: { ignorePendingMessages?: boolean }): Promise<void> {
|
|
296
297
|
if (flushPromise) return flushPromise;
|
|
297
298
|
|
|
298
299
|
flushPromise = (async () => {
|
|
299
|
-
if (agentRunning
|
|
300
|
+
if (agentRunning) return;
|
|
301
|
+
if (!options?.ignorePendingMessages && _latestCtx?.hasPendingMessages()) return;
|
|
300
302
|
|
|
301
303
|
const entries = [...pendingNotifications.entries()]
|
|
302
304
|
.sort(([, left], [, right]) => left.timestamp - right.timestamp);
|
|
@@ -408,7 +410,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
408
410
|
agentRunning = false;
|
|
409
411
|
_latestCtx = ctx;
|
|
410
412
|
widget.setUICtx(ctx.ui);
|
|
411
|
-
await flushPendingNotifications();
|
|
413
|
+
await flushPendingNotifications({ ignorePendingMessages: true });
|
|
414
|
+
await cleanupTaskBacklogLoops();
|
|
412
415
|
await pumpLoops();
|
|
413
416
|
});
|
|
414
417
|
|
|
@@ -507,6 +510,7 @@ Skip this tool when the task is a one-off check (just do it directly) or when th
|
|
|
507
510
|
- **prompt**: what to do when the loop fires (e.g., "check if the build passed")
|
|
508
511
|
- **recurring**: repeat or fire once (default: true)
|
|
509
512
|
- **autoTask**: when pi-tasks is loaded or native task fallback is active, auto-create a task on each fire
|
|
513
|
+
- **taskBacklog**: mark this as a task-backlog worker loop so it auto-deletes when pending tasks reach zero
|
|
510
514
|
- **readOnly**: restrict the agent to read-only tools when this loop fires (default: false)
|
|
511
515
|
- **maxFires**: auto-stop after N fires — prevents infinite token burn on polling loops`,
|
|
512
516
|
promptGuidelines: [
|
|
@@ -526,6 +530,7 @@ Skip this tool when the task is a one-off check (just do it directly) or when th
|
|
|
526
530
|
"## Task-driven workflows",
|
|
527
531
|
"Do not rely on a past 'tasks:created' event to replay. If tasks already exist, bootstrap the first pass in the current turn or use a hybrid/event loop that can catch future task creation and a cron safety-net.",
|
|
528
532
|
"Use autoTask only when you want the loop itself to create a task on each fire. For processing an existing task backlog, leave autoTask off and have the loop run TaskList to pick the next pending task.",
|
|
533
|
+
"Set taskBacklog: true for task-worker loops that process the existing pending queue. Task-backlog loops bootstrap against existing pending tasks and auto-delete when the queue reaches zero.",
|
|
529
534
|
"When no tasks are pending, the loop should stop itself or skip the wake entirely — no tokens burned on empty polls.",
|
|
530
535
|
"After creating a loop, tell the user the loop ID so they can cancel it with LoopDelete.",
|
|
531
536
|
],
|
|
@@ -534,6 +539,7 @@ Skip this tool when the task is a one-off check (just do it directly) or when th
|
|
|
534
539
|
prompt: Type.String({ description: "Prompt to run when the loop fires" }),
|
|
535
540
|
recurring: Type.Optional(Type.Boolean({ description: "Whether loop repeats (default: true)", default: true })),
|
|
536
541
|
autoTask: Type.Optional(Type.Boolean({ description: "Auto-create pi-tasks task on fire", default: false })),
|
|
542
|
+
taskBacklog: Type.Optional(Type.Boolean({ description: "Mark as a task-backlog worker loop that auto-deletes when pending tasks reach zero", default: false })),
|
|
537
543
|
triggerType: Type.Optional(Type.String({ description: "cron, event, or hybrid (inferred from trigger string if omitted)", enum: ["cron", "event", "hybrid"] })),
|
|
538
544
|
debounceMs: Type.Optional(Type.Number({ description: "Debounce for hybrid triggers (default: 30000)", default: 30000 })),
|
|
539
545
|
readOnly: Type.Optional(Type.Boolean({ description: "Restrict the agent to read-only tools when this loop fires (default: false)", default: false })),
|
|
@@ -541,7 +547,7 @@ Skip this tool when the task is a one-off check (just do it directly) or when th
|
|
|
541
547
|
}),
|
|
542
548
|
|
|
543
549
|
async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
|
|
544
|
-
const { trigger: triggerInput, prompt, recurring, autoTask, triggerType, debounceMs, readOnly, maxFires } = params;
|
|
550
|
+
const { trigger: triggerInput, prompt, recurring, autoTask, taskBacklog, triggerType, debounceMs, readOnly, maxFires } = params;
|
|
545
551
|
|
|
546
552
|
let trigger: Trigger;
|
|
547
553
|
const inferred = triggerType ?? inferTriggerType(triggerInput);
|
|
@@ -569,6 +575,7 @@ Skip this tool when the task is a one-off check (just do it directly) or when th
|
|
|
569
575
|
const entry = store.create(trigger, prompt, {
|
|
570
576
|
recurring: recurring ?? (inferred !== "event"),
|
|
571
577
|
autoTask,
|
|
578
|
+
taskBacklog,
|
|
572
579
|
readOnly,
|
|
573
580
|
maxFires,
|
|
574
581
|
});
|
|
@@ -605,6 +612,7 @@ Skip this tool when the task is a one-off check (just do it directly) or when th
|
|
|
605
612
|
`Trigger: ${triggerDesc}\n` +
|
|
606
613
|
`Recurring: ${entry.recurring}\n` +
|
|
607
614
|
(entry.autoTask ? `Auto-task: enabled\n` : "") +
|
|
615
|
+
(entry.taskBacklog ? `Task-backlog: enabled\n` : "") +
|
|
608
616
|
(bootstrapped ? "Bootstrap: queued initial wake for existing pending tasks\n" : "") +
|
|
609
617
|
((tasksAvailable || nativeTasksRegistered) ? "" : "(task system not ready yet — autoTask may not fire until native fallback or pi-tasks becomes available)\n") +
|
|
610
618
|
`ID: ${entry.id} (use LoopDelete to cancel)`
|
|
@@ -1008,12 +1016,36 @@ Use MonitorList to find the monitor ID, then stop it with this tool.`,
|
|
|
1008
1016
|
const AUTO_TASK_WORKER_THRESHOLD = 5;
|
|
1009
1017
|
const AUTO_TASK_WORKER_PROMPT = "Run TaskList, pick next pending task, mark it in_progress, implement it, run validation, complete it. If no pending tasks remain, call LoopDelete on your own loop ID.";
|
|
1010
1018
|
|
|
1011
|
-
function
|
|
1012
|
-
return
|
|
1013
|
-
entry.status === "active"
|
|
1019
|
+
function isAutoTaskWorkerLoop(entry: LoopEntry): boolean {
|
|
1020
|
+
return entry.status === "active"
|
|
1014
1021
|
&& entry.prompt === AUTO_TASK_WORKER_PROMPT
|
|
1022
|
+
&& triggerHasEventSource(entry.trigger, "tasks:created");
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
function isTaskBacklogLoop(entry: LoopEntry): boolean {
|
|
1026
|
+
return entry.status === "active"
|
|
1015
1027
|
&& triggerHasEventSource(entry.trigger, "tasks:created")
|
|
1016
|
-
|
|
1028
|
+
&& (entry.taskBacklog === true || isAutoTaskWorkerLoop(entry));
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
function findAutoTaskWorkerLoop(): LoopEntry | undefined {
|
|
1032
|
+
return store.list().find(isAutoTaskWorkerLoop);
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
async function cleanupTaskBacklogLoops(): Promise<number> {
|
|
1036
|
+
const backlogLoops = store.list().filter(isTaskBacklogLoop);
|
|
1037
|
+
if (backlogLoops.length === 0) return 0;
|
|
1038
|
+
|
|
1039
|
+
const pending = await hasPendingTasks();
|
|
1040
|
+
if (pending < 0 || pending > 0) return 0;
|
|
1041
|
+
|
|
1042
|
+
for (const entry of backlogLoops) {
|
|
1043
|
+
debug(`task backlog loop #${entry.id} — no pending tasks remain, deleting`);
|
|
1044
|
+
triggerSystem.remove(entry.id);
|
|
1045
|
+
store.delete(entry.id);
|
|
1046
|
+
}
|
|
1047
|
+
widget.update();
|
|
1048
|
+
return backlogLoops.length;
|
|
1017
1049
|
}
|
|
1018
1050
|
|
|
1019
1051
|
async function ensureAutoTaskWorkerLoop(taskStore: TaskStore): Promise<{ entry?: LoopEntry; created: boolean }> {
|
|
@@ -1030,6 +1062,7 @@ Use MonitorList to find the monitor ID, then stop it with this tool.`,
|
|
|
1030
1062
|
};
|
|
1031
1063
|
const entry = store.create(trigger, AUTO_TASK_WORKER_PROMPT, {
|
|
1032
1064
|
recurring: true,
|
|
1065
|
+
taskBacklog: true,
|
|
1033
1066
|
maxFires: 30,
|
|
1034
1067
|
});
|
|
1035
1068
|
triggerSystem.add(entry);
|
|
@@ -1119,6 +1152,7 @@ Use MonitorList to find the monitor ID, then stop it with this tool.`,
|
|
|
1119
1152
|
}
|
|
1120
1153
|
|
|
1121
1154
|
widget.update();
|
|
1155
|
+
await cleanupTaskBacklogLoops();
|
|
1122
1156
|
return viewNativeTasks(ui);
|
|
1123
1157
|
}
|
|
1124
1158
|
|
|
@@ -1237,7 +1271,7 @@ Parameters: id (required), status, subject, description`,
|
|
|
1237
1271
|
subject: Type.Optional(Type.String({ description: "New title" })),
|
|
1238
1272
|
description: Type.Optional(Type.String({ description: "New description" })),
|
|
1239
1273
|
}),
|
|
1240
|
-
execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
|
|
1274
|
+
async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
|
|
1241
1275
|
const { id, status, subject, description } = params;
|
|
1242
1276
|
const entry = taskStore.update(id, {
|
|
1243
1277
|
status: status as "pending" | "in_progress" | "completed" | undefined,
|
|
@@ -1246,6 +1280,7 @@ Parameters: id (required), status, subject, description`,
|
|
|
1246
1280
|
});
|
|
1247
1281
|
if (!entry) return Promise.resolve(textResult(`Task #${id} not found`));
|
|
1248
1282
|
widget.update();
|
|
1283
|
+
await cleanupTaskBacklogLoops();
|
|
1249
1284
|
const statusMsg = status ? ` → ${status}` : "";
|
|
1250
1285
|
return Promise.resolve(textResult(`Task #${id} updated${statusMsg}`));
|
|
1251
1286
|
},
|
|
@@ -1258,10 +1293,13 @@ Parameters: id (required), status, subject, description`,
|
|
|
1258
1293
|
parameters: Type.Object({
|
|
1259
1294
|
id: Type.String({ description: "Task ID to delete" }),
|
|
1260
1295
|
}),
|
|
1261
|
-
execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
|
|
1296
|
+
async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
|
|
1262
1297
|
const deleted = taskStore.delete(params.id);
|
|
1263
1298
|
widget.update();
|
|
1264
|
-
if (deleted)
|
|
1299
|
+
if (deleted) {
|
|
1300
|
+
await cleanupTaskBacklogLoops();
|
|
1301
|
+
return Promise.resolve(textResult(`Task #${params.id} deleted`));
|
|
1302
|
+
}
|
|
1265
1303
|
return Promise.resolve(textResult(`Task #${params.id} not found`));
|
|
1266
1304
|
},
|
|
1267
1305
|
});
|
package/src/store.ts
CHANGED
|
@@ -95,7 +95,7 @@ export class LoopStore {
|
|
|
95
95
|
}
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
-
create(trigger: Trigger, prompt: string, opts: { recurring: boolean; autoTask?: boolean; readOnly?: boolean; maxFires?: number }): LoopEntry {
|
|
98
|
+
create(trigger: Trigger, prompt: string, opts: { recurring: boolean; autoTask?: boolean; taskBacklog?: boolean; readOnly?: boolean; maxFires?: number }): LoopEntry {
|
|
99
99
|
return this.withLock(() => {
|
|
100
100
|
if (this.loops.size >= MAX_LOOPS) {
|
|
101
101
|
throw new Error(`Maximum of ${MAX_LOOPS} loops reached. Delete some before creating new ones.`);
|
|
@@ -108,6 +108,7 @@ export class LoopStore {
|
|
|
108
108
|
status: "active",
|
|
109
109
|
recurring: opts.recurring,
|
|
110
110
|
autoTask: opts.autoTask,
|
|
111
|
+
taskBacklog: opts.taskBacklog,
|
|
111
112
|
readOnly: opts.readOnly,
|
|
112
113
|
maxFires: opts.maxFires,
|
|
113
114
|
fireCount: 0,
|