@trevonistrevon/pi-loop 0.4.6 → 0.4.8
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 +52 -8
- package/package.json +1 -1
- package/src/index.ts +56 -8
package/dist/index.js
CHANGED
|
@@ -271,11 +271,13 @@ export default function (pi) {
|
|
|
271
271
|
});
|
|
272
272
|
return true;
|
|
273
273
|
}
|
|
274
|
-
async function flushPendingNotifications() {
|
|
274
|
+
async function flushPendingNotifications(options) {
|
|
275
275
|
if (flushPromise)
|
|
276
276
|
return flushPromise;
|
|
277
277
|
flushPromise = (async () => {
|
|
278
|
-
if (agentRunning
|
|
278
|
+
if (agentRunning)
|
|
279
|
+
return;
|
|
280
|
+
if (!options?.ignorePendingMessages && _latestCtx?.hasPendingMessages())
|
|
279
281
|
return;
|
|
280
282
|
const entries = [...pendingNotifications.entries()]
|
|
281
283
|
.sort(([, left], [, right]) => left.timestamp - right.timestamp);
|
|
@@ -375,7 +377,7 @@ export default function (pi) {
|
|
|
375
377
|
agentRunning = false;
|
|
376
378
|
_latestCtx = ctx;
|
|
377
379
|
widget.setUICtx(ctx.ui);
|
|
378
|
-
await flushPendingNotifications();
|
|
380
|
+
await flushPendingNotifications({ ignorePendingMessages: true });
|
|
379
381
|
await pumpLoops();
|
|
380
382
|
});
|
|
381
383
|
pi.on("session_shutdown", async () => {
|
|
@@ -926,6 +928,34 @@ Use MonitorList to find the monitor ID, then stop it with this tool.`,
|
|
|
926
928
|
const active = loops.filter(l => l.status === "active").length;
|
|
927
929
|
ui.notify(`${active}/${loops.length} active loops (max 25)`, "info");
|
|
928
930
|
}
|
|
931
|
+
const AUTO_TASK_WORKER_THRESHOLD = 5;
|
|
932
|
+
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.";
|
|
933
|
+
function findAutoTaskWorkerLoop() {
|
|
934
|
+
return store.list().find(entry => entry.status === "active"
|
|
935
|
+
&& entry.prompt === AUTO_TASK_WORKER_PROMPT
|
|
936
|
+
&& triggerHasEventSource(entry.trigger, "tasks:created"));
|
|
937
|
+
}
|
|
938
|
+
async function ensureAutoTaskWorkerLoop(taskStore) {
|
|
939
|
+
if (taskStore.pendingCount() < AUTO_TASK_WORKER_THRESHOLD)
|
|
940
|
+
return { created: false };
|
|
941
|
+
const existing = findAutoTaskWorkerLoop();
|
|
942
|
+
if (existing)
|
|
943
|
+
return { entry: existing, created: false };
|
|
944
|
+
const trigger = {
|
|
945
|
+
type: "hybrid",
|
|
946
|
+
cron: "*/5 * * * *",
|
|
947
|
+
event: { source: "tasks:created" },
|
|
948
|
+
debounceMs: 30000,
|
|
949
|
+
};
|
|
950
|
+
const entry = store.create(trigger, AUTO_TASK_WORKER_PROMPT, {
|
|
951
|
+
recurring: true,
|
|
952
|
+
maxFires: 30,
|
|
953
|
+
});
|
|
954
|
+
triggerSystem.add(entry);
|
|
955
|
+
await maybeBootstrapTaskLoop(entry);
|
|
956
|
+
widget.update();
|
|
957
|
+
return { entry, created: true };
|
|
958
|
+
}
|
|
929
959
|
async function createNativeTaskInteractively(ui) {
|
|
930
960
|
if (!nativeTaskStore) {
|
|
931
961
|
ui.notify("Native tasks are unavailable while pi-tasks is active", "warning");
|
|
@@ -936,8 +966,18 @@ Use MonitorList to find the monitor ID, then stop it with this tool.`,
|
|
|
936
966
|
return;
|
|
937
967
|
const description = await ui.input("Task description") || subject;
|
|
938
968
|
const entry = nativeTaskStore.create(subject, description);
|
|
969
|
+
pi.events.emit("tasks:created", {
|
|
970
|
+
taskId: entry.id,
|
|
971
|
+
subject: entry.subject,
|
|
972
|
+
description: entry.description,
|
|
973
|
+
status: entry.status,
|
|
974
|
+
});
|
|
975
|
+
const worker = await ensureAutoTaskWorkerLoop(nativeTaskStore);
|
|
939
976
|
widget.update();
|
|
940
977
|
ui.notify(`Task #${entry.id} created`, "info");
|
|
978
|
+
if (worker.created && worker.entry) {
|
|
979
|
+
ui.notify(`Worker loop #${worker.entry.id} auto-created`, "info");
|
|
980
|
+
}
|
|
941
981
|
}
|
|
942
982
|
async function viewNativeTasks(ui) {
|
|
943
983
|
if (!nativeTaskStore) {
|
|
@@ -1022,8 +1062,12 @@ Use MonitorList to find the monitor ID, then stop it with this tool.`,
|
|
|
1022
1062
|
description: entry.description,
|
|
1023
1063
|
status: entry.status,
|
|
1024
1064
|
});
|
|
1065
|
+
const worker = await ensureAutoTaskWorkerLoop(nativeTaskStore);
|
|
1025
1066
|
widget.update();
|
|
1026
1067
|
ctx.ui.notify(`Task #${entry.id} created`, "info");
|
|
1068
|
+
if (worker.created && worker.entry) {
|
|
1069
|
+
ctx.ui.notify(`Worker loop #${worker.entry.id} auto-created`, "info");
|
|
1070
|
+
}
|
|
1027
1071
|
return;
|
|
1028
1072
|
}
|
|
1029
1073
|
await viewNativeTasks(ctx.ui);
|
|
@@ -1047,7 +1091,7 @@ Fields:
|
|
|
1047
1091
|
subject: Type.String({ description: "Brief actionable title for the task" }),
|
|
1048
1092
|
description: Type.String({ description: "Detailed description of what needs to be done" }),
|
|
1049
1093
|
}),
|
|
1050
|
-
execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
|
|
1094
|
+
async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
|
|
1051
1095
|
const entry = taskStore.create(params.subject, params.description);
|
|
1052
1096
|
pi.events.emit("tasks:created", {
|
|
1053
1097
|
taskId: entry.id,
|
|
@@ -1055,12 +1099,12 @@ Fields:
|
|
|
1055
1099
|
description: entry.description,
|
|
1056
1100
|
status: entry.status,
|
|
1057
1101
|
});
|
|
1102
|
+
const worker = await ensureAutoTaskWorkerLoop(taskStore);
|
|
1058
1103
|
widget.update();
|
|
1059
|
-
const
|
|
1060
|
-
|
|
1061
|
-
? `\n(${pending} pending tasks — consider creating a worker loop: LoopCreate trigger='tasks:created' recurring: true maxFires: 30 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.')`
|
|
1104
|
+
const autoLoopMsg = worker.created && worker.entry
|
|
1105
|
+
? `\nWorker loop #${worker.entry.id} auto-created`
|
|
1062
1106
|
: "";
|
|
1063
|
-
return Promise.resolve(textResult(`Task #${entry.id} created: ${entry.subject}${
|
|
1107
|
+
return Promise.resolve(textResult(`Task #${entry.id} created: ${entry.subject}${autoLoopMsg}`));
|
|
1064
1108
|
},
|
|
1065
1109
|
});
|
|
1066
1110
|
pi.registerTool({
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -292,11 +292,12 @@ export default function (pi: ExtensionAPI) {
|
|
|
292
292
|
return true;
|
|
293
293
|
}
|
|
294
294
|
|
|
295
|
-
async function flushPendingNotifications(): Promise<void> {
|
|
295
|
+
async function flushPendingNotifications(options?: { ignorePendingMessages?: boolean }): Promise<void> {
|
|
296
296
|
if (flushPromise) return flushPromise;
|
|
297
297
|
|
|
298
298
|
flushPromise = (async () => {
|
|
299
|
-
if (agentRunning
|
|
299
|
+
if (agentRunning) return;
|
|
300
|
+
if (!options?.ignorePendingMessages && _latestCtx?.hasPendingMessages()) return;
|
|
300
301
|
|
|
301
302
|
const entries = [...pendingNotifications.entries()]
|
|
302
303
|
.sort(([, left], [, right]) => left.timestamp - right.timestamp);
|
|
@@ -408,7 +409,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
408
409
|
agentRunning = false;
|
|
409
410
|
_latestCtx = ctx;
|
|
410
411
|
widget.setUICtx(ctx.ui);
|
|
411
|
-
await flushPendingNotifications();
|
|
412
|
+
await flushPendingNotifications({ ignorePendingMessages: true });
|
|
412
413
|
await pumpLoops();
|
|
413
414
|
});
|
|
414
415
|
|
|
@@ -1005,6 +1006,39 @@ Use MonitorList to find the monitor ID, then stop it with this tool.`,
|
|
|
1005
1006
|
ui.notify(`${active}/${loops.length} active loops (max 25)`, "info");
|
|
1006
1007
|
}
|
|
1007
1008
|
|
|
1009
|
+
const AUTO_TASK_WORKER_THRESHOLD = 5;
|
|
1010
|
+
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.";
|
|
1011
|
+
|
|
1012
|
+
function findAutoTaskWorkerLoop(): LoopEntry | undefined {
|
|
1013
|
+
return store.list().find(entry =>
|
|
1014
|
+
entry.status === "active"
|
|
1015
|
+
&& entry.prompt === AUTO_TASK_WORKER_PROMPT
|
|
1016
|
+
&& triggerHasEventSource(entry.trigger, "tasks:created")
|
|
1017
|
+
);
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
async function ensureAutoTaskWorkerLoop(taskStore: TaskStore): Promise<{ entry?: LoopEntry; created: boolean }> {
|
|
1021
|
+
if (taskStore.pendingCount() < AUTO_TASK_WORKER_THRESHOLD) return { created: false };
|
|
1022
|
+
|
|
1023
|
+
const existing = findAutoTaskWorkerLoop();
|
|
1024
|
+
if (existing) return { entry: existing, created: false };
|
|
1025
|
+
|
|
1026
|
+
const trigger: Trigger = {
|
|
1027
|
+
type: "hybrid",
|
|
1028
|
+
cron: "*/5 * * * *",
|
|
1029
|
+
event: { source: "tasks:created" },
|
|
1030
|
+
debounceMs: 30000,
|
|
1031
|
+
};
|
|
1032
|
+
const entry = store.create(trigger, AUTO_TASK_WORKER_PROMPT, {
|
|
1033
|
+
recurring: true,
|
|
1034
|
+
maxFires: 30,
|
|
1035
|
+
});
|
|
1036
|
+
triggerSystem.add(entry);
|
|
1037
|
+
await maybeBootstrapTaskLoop(entry);
|
|
1038
|
+
widget.update();
|
|
1039
|
+
return { entry, created: true };
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1008
1042
|
async function createNativeTaskInteractively(ui: ExtensionUIContext) {
|
|
1009
1043
|
if (!nativeTaskStore) {
|
|
1010
1044
|
ui.notify("Native tasks are unavailable while pi-tasks is active", "warning");
|
|
@@ -1015,8 +1049,18 @@ Use MonitorList to find the monitor ID, then stop it with this tool.`,
|
|
|
1015
1049
|
if (!subject) return;
|
|
1016
1050
|
const description = await ui.input("Task description") || subject;
|
|
1017
1051
|
const entry = nativeTaskStore.create(subject, description);
|
|
1052
|
+
pi.events.emit("tasks:created", {
|
|
1053
|
+
taskId: entry.id,
|
|
1054
|
+
subject: entry.subject,
|
|
1055
|
+
description: entry.description,
|
|
1056
|
+
status: entry.status,
|
|
1057
|
+
});
|
|
1058
|
+
const worker = await ensureAutoTaskWorkerLoop(nativeTaskStore);
|
|
1018
1059
|
widget.update();
|
|
1019
1060
|
ui.notify(`Task #${entry.id} created`, "info");
|
|
1061
|
+
if (worker.created && worker.entry) {
|
|
1062
|
+
ui.notify(`Worker loop #${worker.entry.id} auto-created`, "info");
|
|
1063
|
+
}
|
|
1020
1064
|
}
|
|
1021
1065
|
|
|
1022
1066
|
async function viewNativeTasks(ui: ExtensionUIContext): Promise<void> {
|
|
@@ -1103,8 +1147,12 @@ Use MonitorList to find the monitor ID, then stop it with this tool.`,
|
|
|
1103
1147
|
description: entry.description,
|
|
1104
1148
|
status: entry.status,
|
|
1105
1149
|
});
|
|
1150
|
+
const worker = await ensureAutoTaskWorkerLoop(nativeTaskStore);
|
|
1106
1151
|
widget.update();
|
|
1107
1152
|
ctx.ui.notify(`Task #${entry.id} created`, "info");
|
|
1153
|
+
if (worker.created && worker.entry) {
|
|
1154
|
+
ctx.ui.notify(`Worker loop #${worker.entry.id} auto-created`, "info");
|
|
1155
|
+
}
|
|
1108
1156
|
return;
|
|
1109
1157
|
}
|
|
1110
1158
|
await viewNativeTasks(ctx.ui);
|
|
@@ -1129,7 +1177,7 @@ Fields:
|
|
|
1129
1177
|
subject: Type.String({ description: "Brief actionable title for the task" }),
|
|
1130
1178
|
description: Type.String({ description: "Detailed description of what needs to be done" }),
|
|
1131
1179
|
}),
|
|
1132
|
-
execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
|
|
1180
|
+
async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
|
|
1133
1181
|
const entry = taskStore.create(params.subject, params.description);
|
|
1134
1182
|
pi.events.emit("tasks:created", {
|
|
1135
1183
|
taskId: entry.id,
|
|
@@ -1137,13 +1185,13 @@ Fields:
|
|
|
1137
1185
|
description: entry.description,
|
|
1138
1186
|
status: entry.status,
|
|
1139
1187
|
});
|
|
1188
|
+
const worker = await ensureAutoTaskWorkerLoop(taskStore);
|
|
1140
1189
|
widget.update();
|
|
1141
1190
|
|
|
1142
|
-
const
|
|
1143
|
-
|
|
1144
|
-
? `\n(${pending} pending tasks — consider creating a worker loop: LoopCreate trigger='tasks:created' recurring: true maxFires: 30 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.')`
|
|
1191
|
+
const autoLoopMsg = worker.created && worker.entry
|
|
1192
|
+
? `\nWorker loop #${worker.entry.id} auto-created`
|
|
1145
1193
|
: "";
|
|
1146
|
-
return Promise.resolve(textResult(`Task #${entry.id} created: ${entry.subject}${
|
|
1194
|
+
return Promise.resolve(textResult(`Task #${entry.id} created: ${entry.subject}${autoLoopMsg}`));
|
|
1147
1195
|
},
|
|
1148
1196
|
});
|
|
1149
1197
|
|