@trevonistrevon/pi-loop 0.4.4 → 0.4.6

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/README.md CHANGED
@@ -107,6 +107,8 @@ Only task counts and the single active/next task are shown in the widget so atte
107
107
  | `PI_LOOP_SCOPE` | `memory` (ephemeral), `session` (per-session file), `project` (shared) | `session` |
108
108
  | `PI_LOOP_DEBUG` | Debug logging to stderr | unset |
109
109
 
110
+ In `session` scope (default), loop and task files are saved per session ID (e.g. `.pi/tasks/tasks-<sessionId>.json`) so concurrent sessions and worktree agents do not share state. In `memory` scope nothing persists to disk.
111
+
110
112
  ## Limits
111
113
 
112
114
  25 active loops, 25 running monitors. Recurring loops expire after 7 days.
package/dist/index.js CHANGED
@@ -53,9 +53,14 @@ export default function (pi) {
53
53
  return undefined;
54
54
  return join(process.cwd(), ".pi", "loops", "loops.json");
55
55
  }
56
- function resolveTaskStorePath() {
56
+ function resolveTaskStorePath(sessionId) {
57
57
  if (loopScope === "memory")
58
58
  return undefined;
59
+ if (loopScope === "session" && sessionId) {
60
+ return join(process.cwd(), ".pi", "tasks", `tasks-${sessionId}.json`);
61
+ }
62
+ if (loopScope === "session")
63
+ return undefined;
59
64
  return join(process.cwd(), ".pi", "tasks", "tasks.json");
60
65
  }
61
66
  let store = new LoopStore(resolveStorePath());
@@ -319,6 +324,7 @@ export default function (pi) {
319
324
  let storeUpgraded = false;
320
325
  let persistedShown = false;
321
326
  let _latestCtx;
327
+ let _sessionId;
322
328
  function upgradeStoreIfNeeded(ctx) {
323
329
  if (storeUpgraded)
324
330
  return;
@@ -347,6 +353,7 @@ export default function (pi) {
347
353
  }
348
354
  pi.on("turn_start", async (_event, ctx) => {
349
355
  _latestCtx = ctx;
356
+ _sessionId = ctx.sessionManager.getSessionId();
350
357
  widget.setUICtx(ctx.ui);
351
358
  upgradeStoreIfNeeded(ctx);
352
359
  widget.update();
@@ -381,6 +388,7 @@ export default function (pi) {
381
388
  triggerSystem.stop();
382
389
  agentRunning = false;
383
390
  pendingNotifications.clear();
391
+ _sessionId = undefined;
384
392
  const isResume = event?.reason === "resume";
385
393
  storeUpgraded = false;
386
394
  persistedShown = false;
@@ -995,7 +1003,7 @@ Use MonitorList to find the monitor ID, then stop it with this tool.`,
995
1003
  setTimeout(async () => {
996
1004
  if (tasksAvailable || nativeTasksRegistered)
997
1005
  return;
998
- nativeTaskStore = new TaskStore(resolveTaskStorePath());
1006
+ nativeTaskStore = new TaskStore(resolveTaskStorePath(_sessionId));
999
1007
  nativeTasksRegistered = true;
1000
1008
  const taskStore = nativeTaskStore;
1001
1009
  pi.registerCommand("tasks", {
@@ -1032,6 +1040,7 @@ Fields:
1032
1040
  - metadata: optional tags/metadata`,
1033
1041
  promptGuidelines: [
1034
1042
  "Use TaskCreate to track complex multi-step work across turns.",
1043
+ "Break work into small, independently completable tasks. A task should be finishable in one focused session — if a task would take multiple turns, split it further.",
1035
1044
  "TaskCreate accepts `subject` and `description` parameters only — do not invent extra fields unless the schema explicitly adds them.",
1036
1045
  ],
1037
1046
  parameters: Type.Object({
@@ -1047,7 +1056,11 @@ Fields:
1047
1056
  status: entry.status,
1048
1057
  });
1049
1058
  widget.update();
1050
- return Promise.resolve(textResult(`Task #${entry.id} created: ${entry.subject}`));
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.')`
1062
+ : "";
1063
+ return Promise.resolve(textResult(`Task #${entry.id} created: ${entry.subject}${hint}`));
1051
1064
  },
1052
1065
  });
1053
1066
  pi.registerTool({
@@ -1079,11 +1092,12 @@ Fields:
1079
1092
  label: "TaskUpdate",
1080
1093
  description: `Update task status or details. Set status to "in_progress" before starting work, "completed" when done.
1081
1094
 
1082
- Statuses: pending → in_progress → completed`,
1095
+ Statuses: pending → in_progress → completed
1096
+ Parameters: id (required), status, subject, description`,
1083
1097
  promptGuidelines: [
1084
- "Use TaskUpdate with parameter `id`, not `taskId`.",
1085
- "TaskUpdate accepts only `id`, `status`, `subject`, and `description`.",
1086
- "When a tool validation error clearly indicates a recoverable schema mismatch, correct the arguments and retry without narrating the recovery unless the user needs to know.",
1098
+ "TaskUpdate uses parameter `id`, not `taskId`.",
1099
+ "Accepted parameters: `id` (required), `status`, `subject`, `description`.",
1100
+ "When validation fails with 'must have required properties id', you passed `taskId` instead of `id`. Correct silently and retry.",
1087
1101
  ],
1088
1102
  parameters: Type.Object({
1089
1103
  id: Type.String({ description: "Task ID to update" }),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trevonistrevon/pi-loop",
3
- "version": "0.4.4",
3
+ "version": "0.4.6",
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
@@ -73,8 +73,12 @@ export default function (pi: ExtensionAPI) {
73
73
  return join(process.cwd(), ".pi", "loops", "loops.json");
74
74
  }
75
75
 
76
- function resolveTaskStorePath(): string | undefined {
76
+ function resolveTaskStorePath(sessionId?: string): string | undefined {
77
77
  if (loopScope === "memory") return undefined;
78
+ if (loopScope === "session" && sessionId) {
79
+ return join(process.cwd(), ".pi", "tasks", `tasks-${sessionId}.json`);
80
+ }
81
+ if (loopScope === "session") return undefined;
78
82
  return join(process.cwd(), ".pi", "tasks", "tasks.json");
79
83
  }
80
84
 
@@ -349,6 +353,7 @@ export default function (pi: ExtensionAPI) {
349
353
  let storeUpgraded = false;
350
354
  let persistedShown = false;
351
355
  let _latestCtx: ExtensionContext | undefined;
356
+ let _sessionId: string | undefined;
352
357
 
353
358
  function upgradeStoreIfNeeded(ctx: ExtensionContext) {
354
359
  if (storeUpgraded) return;
@@ -378,6 +383,7 @@ export default function (pi: ExtensionAPI) {
378
383
 
379
384
  pi.on("turn_start", async (_event, ctx) => {
380
385
  _latestCtx = ctx;
386
+ _sessionId = ctx.sessionManager.getSessionId();
381
387
  widget.setUICtx(ctx.ui);
382
388
  upgradeStoreIfNeeded(ctx);
383
389
  widget.update();
@@ -417,6 +423,7 @@ export default function (pi: ExtensionAPI) {
417
423
  triggerSystem.stop();
418
424
  agentRunning = false;
419
425
  pendingNotifications.clear();
426
+ _sessionId = undefined;
420
427
 
421
428
  const isResume = event?.reason === "resume";
422
429
  storeUpgraded = false;
@@ -1076,7 +1083,7 @@ Use MonitorList to find the monitor ID, then stop it with this tool.`,
1076
1083
 
1077
1084
  setTimeout(async () => {
1078
1085
  if (tasksAvailable || nativeTasksRegistered) return;
1079
- nativeTaskStore = new TaskStore(resolveTaskStorePath());
1086
+ nativeTaskStore = new TaskStore(resolveTaskStorePath(_sessionId));
1080
1087
  nativeTasksRegistered = true;
1081
1088
  const taskStore = nativeTaskStore;
1082
1089
 
@@ -1115,6 +1122,7 @@ Fields:
1115
1122
  - metadata: optional tags/metadata`,
1116
1123
  promptGuidelines: [
1117
1124
  "Use TaskCreate to track complex multi-step work across turns.",
1125
+ "Break work into small, independently completable tasks. A task should be finishable in one focused session — if a task would take multiple turns, split it further.",
1118
1126
  "TaskCreate accepts `subject` and `description` parameters only — do not invent extra fields unless the schema explicitly adds them.",
1119
1127
  ],
1120
1128
  parameters: Type.Object({
@@ -1130,7 +1138,12 @@ Fields:
1130
1138
  status: entry.status,
1131
1139
  });
1132
1140
  widget.update();
1133
- return Promise.resolve(textResult(`Task #${entry.id} created: ${entry.subject}`));
1141
+
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.')`
1145
+ : "";
1146
+ return Promise.resolve(textResult(`Task #${entry.id} created: ${entry.subject}${hint}`));
1134
1147
  },
1135
1148
  });
1136
1149
 
@@ -1164,11 +1177,12 @@ Fields:
1164
1177
  label: "TaskUpdate",
1165
1178
  description: `Update task status or details. Set status to "in_progress" before starting work, "completed" when done.
1166
1179
 
1167
- Statuses: pending → in_progress → completed`,
1180
+ Statuses: pending → in_progress → completed
1181
+ Parameters: id (required), status, subject, description`,
1168
1182
  promptGuidelines: [
1169
- "Use TaskUpdate with parameter `id`, not `taskId`.",
1170
- "TaskUpdate accepts only `id`, `status`, `subject`, and `description`.",
1171
- "When a tool validation error clearly indicates a recoverable schema mismatch, correct the arguments and retry without narrating the recovery unless the user needs to know.",
1183
+ "TaskUpdate uses parameter `id`, not `taskId`.",
1184
+ "Accepted parameters: `id` (required), `status`, `subject`, `description`.",
1185
+ "When validation fails with 'must have required properties id', you passed `taskId` instead of `id`. Correct silently and retry.",
1172
1186
  ],
1173
1187
  parameters: Type.Object({
1174
1188
  id: Type.String({ description: "Task ID to update" }),