@trevonistrevon/pi-loop 0.4.6 → 0.4.7

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 CHANGED
@@ -926,6 +926,34 @@ Use MonitorList to find the monitor ID, then stop it with this tool.`,
926
926
  const active = loops.filter(l => l.status === "active").length;
927
927
  ui.notify(`${active}/${loops.length} active loops (max 25)`, "info");
928
928
  }
929
+ const AUTO_TASK_WORKER_THRESHOLD = 5;
930
+ 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 findAutoTaskWorkerLoop() {
932
+ return store.list().find(entry => entry.status === "active"
933
+ && entry.prompt === AUTO_TASK_WORKER_PROMPT
934
+ && triggerHasEventSource(entry.trigger, "tasks:created"));
935
+ }
936
+ async function ensureAutoTaskWorkerLoop(taskStore) {
937
+ if (taskStore.pendingCount() < AUTO_TASK_WORKER_THRESHOLD)
938
+ return { created: false };
939
+ const existing = findAutoTaskWorkerLoop();
940
+ if (existing)
941
+ return { entry: existing, created: false };
942
+ const trigger = {
943
+ type: "hybrid",
944
+ cron: "*/5 * * * *",
945
+ event: { source: "tasks:created" },
946
+ debounceMs: 30000,
947
+ };
948
+ const entry = store.create(trigger, AUTO_TASK_WORKER_PROMPT, {
949
+ recurring: true,
950
+ maxFires: 30,
951
+ });
952
+ triggerSystem.add(entry);
953
+ await maybeBootstrapTaskLoop(entry);
954
+ widget.update();
955
+ return { entry, created: true };
956
+ }
929
957
  async function createNativeTaskInteractively(ui) {
930
958
  if (!nativeTaskStore) {
931
959
  ui.notify("Native tasks are unavailable while pi-tasks is active", "warning");
@@ -936,8 +964,18 @@ Use MonitorList to find the monitor ID, then stop it with this tool.`,
936
964
  return;
937
965
  const description = await ui.input("Task description") || subject;
938
966
  const entry = nativeTaskStore.create(subject, description);
967
+ pi.events.emit("tasks:created", {
968
+ taskId: entry.id,
969
+ subject: entry.subject,
970
+ description: entry.description,
971
+ status: entry.status,
972
+ });
973
+ const worker = await ensureAutoTaskWorkerLoop(nativeTaskStore);
939
974
  widget.update();
940
975
  ui.notify(`Task #${entry.id} created`, "info");
976
+ if (worker.created && worker.entry) {
977
+ ui.notify(`Worker loop #${worker.entry.id} auto-created`, "info");
978
+ }
941
979
  }
942
980
  async function viewNativeTasks(ui) {
943
981
  if (!nativeTaskStore) {
@@ -1022,8 +1060,12 @@ Use MonitorList to find the monitor ID, then stop it with this tool.`,
1022
1060
  description: entry.description,
1023
1061
  status: entry.status,
1024
1062
  });
1063
+ const worker = await ensureAutoTaskWorkerLoop(nativeTaskStore);
1025
1064
  widget.update();
1026
1065
  ctx.ui.notify(`Task #${entry.id} created`, "info");
1066
+ if (worker.created && worker.entry) {
1067
+ ctx.ui.notify(`Worker loop #${worker.entry.id} auto-created`, "info");
1068
+ }
1027
1069
  return;
1028
1070
  }
1029
1071
  await viewNativeTasks(ctx.ui);
@@ -1047,7 +1089,7 @@ Fields:
1047
1089
  subject: Type.String({ description: "Brief actionable title for the task" }),
1048
1090
  description: Type.String({ description: "Detailed description of what needs to be done" }),
1049
1091
  }),
1050
- execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
1092
+ async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
1051
1093
  const entry = taskStore.create(params.subject, params.description);
1052
1094
  pi.events.emit("tasks:created", {
1053
1095
  taskId: entry.id,
@@ -1055,12 +1097,12 @@ Fields:
1055
1097
  description: entry.description,
1056
1098
  status: entry.status,
1057
1099
  });
1100
+ const worker = await ensureAutoTaskWorkerLoop(taskStore);
1058
1101
  widget.update();
1059
- const pending = taskStore.pendingCount();
1060
- const hint = pending >= 5
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.')`
1102
+ const autoLoopMsg = worker.created && worker.entry
1103
+ ? `\nWorker loop #${worker.entry.id} auto-created`
1062
1104
  : "";
1063
- return Promise.resolve(textResult(`Task #${entry.id} created: ${entry.subject}${hint}`));
1105
+ return Promise.resolve(textResult(`Task #${entry.id} created: ${entry.subject}${autoLoopMsg}`));
1064
1106
  },
1065
1107
  });
1066
1108
  pi.registerTool({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trevonistrevon/pi-loop",
3
- "version": "0.4.6",
3
+ "version": "0.4.7",
4
4
  "description": "A pi extension for cron/event-based agent re-wake loops and background process monitoring.",
5
5
  "author": "trevonistrevon",
6
6
  "license": "MIT",
package/src/index.ts CHANGED
@@ -1005,6 +1005,39 @@ Use MonitorList to find the monitor ID, then stop it with this tool.`,
1005
1005
  ui.notify(`${active}/${loops.length} active loops (max 25)`, "info");
1006
1006
  }
1007
1007
 
1008
+ const AUTO_TASK_WORKER_THRESHOLD = 5;
1009
+ 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
+
1011
+ function findAutoTaskWorkerLoop(): LoopEntry | undefined {
1012
+ return store.list().find(entry =>
1013
+ entry.status === "active"
1014
+ && entry.prompt === AUTO_TASK_WORKER_PROMPT
1015
+ && triggerHasEventSource(entry.trigger, "tasks:created")
1016
+ );
1017
+ }
1018
+
1019
+ async function ensureAutoTaskWorkerLoop(taskStore: TaskStore): Promise<{ entry?: LoopEntry; created: boolean }> {
1020
+ if (taskStore.pendingCount() < AUTO_TASK_WORKER_THRESHOLD) return { created: false };
1021
+
1022
+ const existing = findAutoTaskWorkerLoop();
1023
+ if (existing) return { entry: existing, created: false };
1024
+
1025
+ const trigger: Trigger = {
1026
+ type: "hybrid",
1027
+ cron: "*/5 * * * *",
1028
+ event: { source: "tasks:created" },
1029
+ debounceMs: 30000,
1030
+ };
1031
+ const entry = store.create(trigger, AUTO_TASK_WORKER_PROMPT, {
1032
+ recurring: true,
1033
+ maxFires: 30,
1034
+ });
1035
+ triggerSystem.add(entry);
1036
+ await maybeBootstrapTaskLoop(entry);
1037
+ widget.update();
1038
+ return { entry, created: true };
1039
+ }
1040
+
1008
1041
  async function createNativeTaskInteractively(ui: ExtensionUIContext) {
1009
1042
  if (!nativeTaskStore) {
1010
1043
  ui.notify("Native tasks are unavailable while pi-tasks is active", "warning");
@@ -1015,8 +1048,18 @@ Use MonitorList to find the monitor ID, then stop it with this tool.`,
1015
1048
  if (!subject) return;
1016
1049
  const description = await ui.input("Task description") || subject;
1017
1050
  const entry = nativeTaskStore.create(subject, description);
1051
+ pi.events.emit("tasks:created", {
1052
+ taskId: entry.id,
1053
+ subject: entry.subject,
1054
+ description: entry.description,
1055
+ status: entry.status,
1056
+ });
1057
+ const worker = await ensureAutoTaskWorkerLoop(nativeTaskStore);
1018
1058
  widget.update();
1019
1059
  ui.notify(`Task #${entry.id} created`, "info");
1060
+ if (worker.created && worker.entry) {
1061
+ ui.notify(`Worker loop #${worker.entry.id} auto-created`, "info");
1062
+ }
1020
1063
  }
1021
1064
 
1022
1065
  async function viewNativeTasks(ui: ExtensionUIContext): Promise<void> {
@@ -1103,8 +1146,12 @@ Use MonitorList to find the monitor ID, then stop it with this tool.`,
1103
1146
  description: entry.description,
1104
1147
  status: entry.status,
1105
1148
  });
1149
+ const worker = await ensureAutoTaskWorkerLoop(nativeTaskStore);
1106
1150
  widget.update();
1107
1151
  ctx.ui.notify(`Task #${entry.id} created`, "info");
1152
+ if (worker.created && worker.entry) {
1153
+ ctx.ui.notify(`Worker loop #${worker.entry.id} auto-created`, "info");
1154
+ }
1108
1155
  return;
1109
1156
  }
1110
1157
  await viewNativeTasks(ctx.ui);
@@ -1129,7 +1176,7 @@ Fields:
1129
1176
  subject: Type.String({ description: "Brief actionable title for the task" }),
1130
1177
  description: Type.String({ description: "Detailed description of what needs to be done" }),
1131
1178
  }),
1132
- execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
1179
+ async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
1133
1180
  const entry = taskStore.create(params.subject, params.description);
1134
1181
  pi.events.emit("tasks:created", {
1135
1182
  taskId: entry.id,
@@ -1137,13 +1184,13 @@ Fields:
1137
1184
  description: entry.description,
1138
1185
  status: entry.status,
1139
1186
  });
1187
+ const worker = await ensureAutoTaskWorkerLoop(taskStore);
1140
1188
  widget.update();
1141
1189
 
1142
- const pending = taskStore.pendingCount();
1143
- const hint = pending >= 5
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.')`
1190
+ const autoLoopMsg = worker.created && worker.entry
1191
+ ? `\nWorker loop #${worker.entry.id} auto-created`
1145
1192
  : "";
1146
- return Promise.resolve(textResult(`Task #${entry.id} created: ${entry.subject}${hint}`));
1193
+ return Promise.resolve(textResult(`Task #${entry.id} created: ${entry.subject}${autoLoopMsg}`));
1147
1194
  },
1148
1195
  });
1149
1196