@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 +2 -0
- package/dist/index.js +21 -7
- package/package.json +1 -1
- package/src/index.ts +21 -7
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
|
-
|
|
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
|
-
"
|
|
1085
|
-
"
|
|
1086
|
-
"When
|
|
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
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
|
-
|
|
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
|
-
"
|
|
1170
|
-
"
|
|
1171
|
-
"When
|
|
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" }),
|