@trevonistrevon/pi-loop 0.5.2 → 0.5.4

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.
@@ -36,7 +36,7 @@ export function registerLoopCommand(options) {
36
36
  async function viewLoops(ui) {
37
37
  const loops = getStore().list();
38
38
  if (loops.length === 0) {
39
- await ui.select("No active loops", ["< Back"]);
39
+ await ui.select("No loops configured", ["< Back"]);
40
40
  return;
41
41
  }
42
42
  const choices = loops.map((l) => {
@@ -49,7 +49,7 @@ export function registerLoopCommand(options) {
49
49
  return `${icon} #${l.id} [${l.status}] ${l.prompt.slice(0, 50)} (${triggerDesc})`;
50
50
  });
51
51
  choices.push("< Back");
52
- const selected = await ui.select("Active Loops", choices);
52
+ const selected = await ui.select("Loops", choices);
53
53
  if (!selected || selected === "< Back")
54
54
  return;
55
55
  const match = selected.match(/#(\d+)/);
@@ -99,7 +99,7 @@ export function registerLoopCommand(options) {
99
99
  const choice = await ui.select("Loop", [
100
100
  "Create scheduled loop",
101
101
  "Create event-triggered loop",
102
- "View active loops",
102
+ "View loops",
103
103
  "Settings",
104
104
  ]);
105
105
  if (!choice)
@@ -108,7 +108,7 @@ export function registerLoopCommand(options) {
108
108
  return scheduleLoop(ui);
109
109
  if (choice.startsWith("Create event"))
110
110
  return eventLoop(ui);
111
- if (choice.startsWith("View active"))
111
+ if (choice.startsWith("View loops"))
112
112
  return viewLoops(ui);
113
113
  return settings(ui);
114
114
  }
@@ -136,7 +136,6 @@ export function registerLoopCommand(options) {
136
136
  const choice = await ui.select("Loop mode", [
137
137
  `Scheduled: "${trimmed.slice(0, 50)}"`,
138
138
  `Event-triggered: "${trimmed.slice(0, 50)}"`,
139
- `Self-paced: "${trimmed.slice(0, 50)}"`,
140
139
  ]);
141
140
  if (!choice)
142
141
  return;
@@ -28,7 +28,7 @@ export function registerTasksCommand(options) {
28
28
  const backlog = await emitCreated(entry);
29
29
  ui.notify(`Task #${entry.id} created`, "info");
30
30
  if (backlog.created && backlog.entry) {
31
- ui.notify(`Worker loop #${backlog.entry.id} auto-created`, "info");
31
+ ui.notify(`Backlog worker loop #${backlog.entry.id} created`, "info");
32
32
  }
33
33
  }
34
34
  async function viewNativeTasks(ui) {
@@ -44,7 +44,7 @@ export function registerTasksCommand(options) {
44
44
  });
45
45
  choices.unshift("+ Create task");
46
46
  choices.push("< Back");
47
- const selected = await ui.select("Native Tasks", choices);
47
+ const selected = await ui.select("Tasks", choices);
48
48
  if (!selected || selected === "< Back")
49
49
  return;
50
50
  if (selected === "+ Create task") {
@@ -107,7 +107,7 @@ export function registerTasksCommand(options) {
107
107
  const backlog = await emitCreated(entry);
108
108
  ctx.ui.notify(`Task #${entry.id} created`, "info");
109
109
  if (backlog.created && backlog.entry) {
110
- ctx.ui.notify(`Worker loop #${backlog.entry.id} auto-created`, "info");
110
+ ctx.ui.notify(`Backlog worker loop #${backlog.entry.id} created`, "info");
111
111
  }
112
112
  return;
113
113
  }
@@ -7,6 +7,7 @@ const LOCK_RETRY_MS = 50;
7
7
  const LOCK_MAX_RETRIES = 100;
8
8
  const MAX_GOALS = 200;
9
9
  function acquireLock(lockPath) {
10
+ mkdirSync(dirname(lockPath), { recursive: true });
10
11
  for (let i = 0; i < LOCK_MAX_RETRIES; i++) {
11
12
  try {
12
13
  writeFileSync(lockPath, `${process.pid}`, { flag: "wx" });
package/dist/index.d.ts CHANGED
@@ -10,8 +10,8 @@
10
10
  * MonitorStop — Stop a running monitor
11
11
  *
12
12
  * Commands:
13
- * /loop — Schedule a re-wake loop: /loop [interval] [prompt]
14
- * /loopsInteractive menu: view, create, cancel, settings
13
+ * /loop — Schedule or manage re-wake loops: /loop [interval] [prompt]
14
+ * /tasksView or manage native fallback tasks when pi-tasks is absent
15
15
  */
16
16
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
17
17
  export default function (pi: ExtensionAPI): void;
package/dist/index.js CHANGED
@@ -10,8 +10,8 @@
10
10
  * MonitorStop — Stop a running monitor
11
11
  *
12
12
  * Commands:
13
- * /loop — Schedule a re-wake loop: /loop [interval] [prompt]
14
- * /loopsInteractive menu: view, create, cancel, settings
13
+ * /loop — Schedule or manage re-wake loops: /loop [interval] [prompt]
14
+ * /tasksView or manage native fallback tasks when pi-tasks is absent
15
15
  */
16
16
  import { registerLoopCommand } from "./commands/loop-command.js";
17
17
  import { registerTasksCommand } from "./commands/tasks-command.js";
@@ -114,10 +114,8 @@ export class MonitorManager {
114
114
  exitCode: code,
115
115
  outputLines: current.outputLines,
116
116
  });
117
- if (status === "completed") {
118
- for (const callback of bp.completionCallbacks)
119
- callback();
120
- }
117
+ for (const callback of bp.completionCallbacks)
118
+ callback();
121
119
  bp.completionCallbacks = [];
122
120
  for (const resolve of bp.waiters)
123
121
  resolve();
@@ -157,6 +155,8 @@ export class MonitorManager {
157
155
  monitorId: id,
158
156
  error: err.message,
159
157
  });
158
+ for (const callback of bp.completionCallbacks)
159
+ callback();
160
160
  bp.completionCallbacks = [];
161
161
  for (const resolve of bp.waiters)
162
162
  resolve();
package/dist/store.js CHANGED
@@ -7,6 +7,7 @@ const LOCK_RETRY_MS = 50;
7
7
  const LOCK_MAX_RETRIES = 100;
8
8
  const MAX_LOOPS = 25;
9
9
  function acquireLock(lockPath) {
10
+ mkdirSync(dirname(lockPath), { recursive: true });
10
11
  for (let i = 0; i < LOCK_MAX_RETRIES; i++) {
11
12
  try {
12
13
  writeFileSync(lockPath, `${process.pid}`, { flag: "wx" });
@@ -7,6 +7,7 @@ const LOCK_RETRY_MS = 50;
7
7
  const LOCK_MAX_RETRIES = 100;
8
8
  const MAX_TASKS = 200;
9
9
  function acquireLock(lockPath) {
10
+ mkdirSync(dirname(lockPath), { recursive: true });
10
11
  for (let i = 0; i < LOCK_MAX_RETRIES; i++) {
11
12
  try {
12
13
  writeFileSync(lockPath, `${process.pid}`, { flag: "wx" });
@@ -96,7 +96,7 @@ Skip this tool when the task is a one-off check (just do it directly) or when th
96
96
  "## Task-driven workflows",
97
97
  "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.",
98
98
  "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.",
99
- "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.",
99
+ "Set taskBacklog: true for backlog worker loops that process the existing pending queue. Backlog worker loops bootstrap against existing pending tasks and auto-delete when the queue reaches zero.",
100
100
  "When no tasks are pending, the loop should stop itself or skip the wake entirely — no tokens burned on empty polls.",
101
101
  "After creating a loop, tell the user the loop ID so they can cancel it with LoopDelete.",
102
102
  ],
@@ -170,10 +170,10 @@ Skip this tool when the task is a one-off check (just do it directly) or when th
170
170
  return Promise.resolve(textResult(`Loop #${entry.id} created: ${entry.prompt.slice(0, 60)}\n` +
171
171
  `Trigger: ${triggerDesc}\n` +
172
172
  `Recurring: ${entry.recurring}\n` +
173
- (entry.autoTask ? "Auto-task: enabled\n" : "") +
174
- (entry.taskBacklog ? "Task-backlog: enabled\n" : "") +
175
- (bootstrapped ? "Bootstrap: queued initial wake for existing pending tasks\n" : "") +
176
- (isTaskSystemReady() ? "" : "(task system not ready yet — autoTask may not fire until native fallback or pi-tasks becomes available)\n") +
173
+ (entry.autoTask ? "Auto-create task: enabled\n" : "") +
174
+ (entry.taskBacklog ? "Backlog worker: enabled\n" : "") +
175
+ (bootstrapped ? "Backlog: initial wake queued for existing pending tasks\n" : "") +
176
+ (isTaskSystemReady() ? "" : "Task system: not ready yet — autoTask may not fire until native fallback or pi-tasks becomes available\n") +
177
177
  `ID: ${entry.id} (use LoopDelete to cancel)`));
178
178
  },
179
179
  });
@@ -205,6 +205,8 @@ Use this before creating new loops to avoid duplicates, or to find IDs for LoopD
205
205
  }
206
206
  if (entry.autoTask)
207
207
  line += " [auto-task]";
208
+ if (entry.taskBacklog)
209
+ line += " [backlog-worker]";
208
210
  lines.push(line);
209
211
  }
210
212
  return Promise.resolve(textResult(lines.join("\n")));
@@ -55,7 +55,7 @@ Pass onDone with a prompt and the monitor auto-creates a one-shot loop that fire
55
55
  const doneTrigger = { type: "event", source: "monitor:done", filter: JSON.stringify({ monitorId: entry.id }) };
56
56
  const doneLoop = getStore().create(doneTrigger, params.onDone, { recurring: false });
57
57
  handleMonitorDoneLoop(doneLoop, entry.id);
58
- onDoneMsg = `\nonDone loop #${doneLoop.id}: fires when monitor completes — no polling needed`;
58
+ onDoneMsg = `\nCompletion wake loop #${doneLoop.id}: fires when the monitor completes — no polling needed`;
59
59
  }
60
60
  return Promise.resolve(textResult(`Monitor #${entry.id} started: ${entry.command.slice(0, 60)}\n` +
61
61
  `Output stream: monitor:output (monitorId: ${entry.id})\n` +
@@ -70,10 +70,10 @@ Pass onDone with a prompt and the monitor auto-creates a one-shot loop that fire
70
70
  execute() {
71
71
  const monitors = getMonitorManager().list();
72
72
  if (monitors.length === 0)
73
- return Promise.resolve(textResult("No monitors running."));
73
+ return Promise.resolve(textResult("No monitors."));
74
74
  const lines = [];
75
75
  for (const m of monitors) {
76
- const icon = m.status === "running" ? ">" : m.status === "completed" ? "ok" : "!!";
76
+ const icon = m.status === "running" ? ">" : m.status === "completed" ? "ok" : "x";
77
77
  const age = Date.now() - m.startedAt;
78
78
  const ageStr = formatRemaining(age);
79
79
  let line = `${icon} #${m.id} [${m.status}] ${m.command.slice(0, 60)} — ${m.outputLines} lines (${ageStr})`;
@@ -15,7 +15,10 @@ Fields:
15
15
  promptGuidelines: [
16
16
  "Use TaskCreate to track complex multi-step work across turns.",
17
17
  "When the user gives a broad goal, use multiple TaskCreate calls to decompose it into a small backlog of concrete tasks rather than one oversized task.",
18
+ "If the user supplies a shared goal or meta-goal, preserve it explicitly using the user's wording and tie each created task back to that goal in its description.",
19
+ "If several tasks share one goal, keep subjects short and put the shared goal in the first sentence of each description or as an equivalent explicit framing.",
18
20
  "Prefer 2-5 tasks that separate investigation, implementation, validation, and reporting or commit-prep when those phases are distinct.",
21
+ "When the user asks to break work into tasks, create the backlog directly and do not pivot to loops, monitors, or other automation unless the user also asked for ongoing automation.",
19
22
  "Make each `subject` a short verb-object action.",
20
23
  "Make each `description` include the expected artifact, outcome, or done condition so another turn can pick the task up cleanly.",
21
24
  "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.",
@@ -36,7 +39,7 @@ Fields:
36
39
  const backlog = await evaluateTaskBacklog(taskStore, taskStore.pendingCount());
37
40
  updateWidget();
38
41
  const autoLoopMsg = backlog.created && backlog.entry
39
- ? `\nWorker loop #${backlog.entry.id} auto-created`
42
+ ? `\nBacklog worker loop #${backlog.entry.id} created`
40
43
  : "";
41
44
  return Promise.resolve(textResult(`Task #${entry.id} created: ${entry.subject}${autoLoopMsg}`));
42
45
  },
@@ -105,7 +108,7 @@ Parameters: id (required), status, subject, description`,
105
108
  const backlog = await evaluateTaskBacklog(taskStore, taskStore.pendingCount());
106
109
  const statusMsg = status ? ` → ${status}` : "";
107
110
  const autoLoopMsg = backlog.created && backlog.entry
108
- ? `\nWorker loop #${backlog.entry.id} auto-created`
111
+ ? `\nBacklog worker loop #${backlog.entry.id} created`
109
112
  : "";
110
113
  return Promise.resolve(textResult(`Task #${id} updated${statusMsg}${autoLoopMsg}`));
111
114
  },
package/dist/ui/widget.js CHANGED
@@ -22,7 +22,7 @@ export class LoopWidget {
22
22
  this.uiCtx.setStatus("loops", this.computeStatus());
23
23
  }
24
24
  computeStatus() {
25
- const loops = this.store.list().filter(l => l.status === "active");
25
+ const loops = this.store.list().filter(isStatusVisibleLoop);
26
26
  const monitors = this.monitorManager.list();
27
27
  const taskSummary = this.taskSummaryProvider?.() ?? { count: 0 };
28
28
  if (loops.length === 0 && monitors.length === 0 && taskSummary.count === 0) {
@@ -47,3 +47,10 @@ export class LoopWidget {
47
47
  function formatCount(count, noun) {
48
48
  return `${count} ${noun}${count === 1 ? "" : "s"}`;
49
49
  }
50
+ function isStatusVisibleLoop(loop) {
51
+ if (loop.status !== "active")
52
+ return false;
53
+ if (loop.recurring)
54
+ return true;
55
+ return !(loop.trigger.type === "event" && loop.trigger.source === "monitor:done");
56
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trevonistrevon/pi-loop",
3
- "version": "0.5.2",
3
+ "version": "0.5.4",
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",
@@ -66,7 +66,7 @@ export function registerLoopCommand(options: LoopCommandOptions): void {
66
66
  async function viewLoops(ui: ExtensionUIContext) {
67
67
  const loops = getStore().list();
68
68
  if (loops.length === 0) {
69
- await ui.select("No active loops", ["< Back"]);
69
+ await ui.select("No loops configured", ["< Back"]);
70
70
  return;
71
71
  }
72
72
 
@@ -81,7 +81,7 @@ export function registerLoopCommand(options: LoopCommandOptions): void {
81
81
  });
82
82
  choices.push("< Back");
83
83
 
84
- const selected = await ui.select("Active Loops", choices);
84
+ const selected = await ui.select("Loops", choices);
85
85
  if (!selected || selected === "< Back") return;
86
86
 
87
87
  const match = selected.match(/#(\d+)/);
@@ -136,14 +136,14 @@ export function registerLoopCommand(options: LoopCommandOptions): void {
136
136
  const choice = await ui.select("Loop", [
137
137
  "Create scheduled loop",
138
138
  "Create event-triggered loop",
139
- "View active loops",
139
+ "View loops",
140
140
  "Settings",
141
141
  ]);
142
142
 
143
143
  if (!choice) return;
144
144
  if (choice.startsWith("Create scheduled")) return scheduleLoop(ui);
145
145
  if (choice.startsWith("Create event")) return eventLoop(ui);
146
- if (choice.startsWith("View active")) return viewLoops(ui);
146
+ if (choice.startsWith("View loops")) return viewLoops(ui);
147
147
  return settings(ui);
148
148
  }
149
149
 
@@ -173,7 +173,6 @@ export function registerLoopCommand(options: LoopCommandOptions): void {
173
173
  const choice = await ui.select("Loop mode", [
174
174
  `Scheduled: "${trimmed.slice(0, 50)}"`,
175
175
  `Event-triggered: "${trimmed.slice(0, 50)}"`,
176
- `Self-paced: "${trimmed.slice(0, 50)}"`,
177
176
  ]);
178
177
 
179
178
  if (!choice) return;
@@ -45,7 +45,7 @@ export function registerTasksCommand(options: TasksCommandOptions): void {
45
45
  const backlog = await emitCreated(entry);
46
46
  ui.notify(`Task #${entry.id} created`, "info");
47
47
  if (backlog.created && backlog.entry) {
48
- ui.notify(`Worker loop #${backlog.entry.id} auto-created`, "info");
48
+ ui.notify(`Backlog worker loop #${backlog.entry.id} created`, "info");
49
49
  }
50
50
  }
51
51
 
@@ -64,7 +64,7 @@ export function registerTasksCommand(options: TasksCommandOptions): void {
64
64
  choices.unshift("+ Create task");
65
65
  choices.push("< Back");
66
66
 
67
- const selected = await ui.select("Native Tasks", choices);
67
+ const selected = await ui.select("Tasks", choices);
68
68
  if (!selected || selected === "< Back") return;
69
69
  if (selected === "+ Create task") {
70
70
  await createNativeTaskInteractively(ui);
@@ -125,7 +125,7 @@ export function registerTasksCommand(options: TasksCommandOptions): void {
125
125
  const backlog = await emitCreated(entry);
126
126
  ctx.ui.notify(`Task #${entry.id} created`, "info");
127
127
  if (backlog.created && backlog.entry) {
128
- ctx.ui.notify(`Worker loop #${backlog.entry.id} auto-created`, "info");
128
+ ctx.ui.notify(`Backlog worker loop #${backlog.entry.id} created`, "info");
129
129
  }
130
130
  return;
131
131
  }
package/src/goal-store.ts CHANGED
@@ -17,6 +17,7 @@ const LOCK_MAX_RETRIES = 100;
17
17
  const MAX_GOALS = 200;
18
18
 
19
19
  function acquireLock(lockPath: string): void {
20
+ mkdirSync(dirname(lockPath), { recursive: true });
20
21
  for (let i = 0; i < LOCK_MAX_RETRIES; i++) {
21
22
  try {
22
23
  writeFileSync(lockPath, `${process.pid}`, { flag: "wx" });
package/src/index.ts CHANGED
@@ -10,8 +10,8 @@
10
10
  * MonitorStop — Stop a running monitor
11
11
  *
12
12
  * Commands:
13
- * /loop — Schedule a re-wake loop: /loop [interval] [prompt]
14
- * /loopsInteractive menu: view, create, cancel, settings
13
+ * /loop — Schedule or manage re-wake loops: /loop [interval] [prompt]
14
+ * /tasksView or manage native fallback tasks when pi-tasks is absent
15
15
  */
16
16
 
17
17
  import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
@@ -128,9 +128,7 @@ export class MonitorManager {
128
128
  exitCode: code,
129
129
  outputLines: current.outputLines,
130
130
  });
131
- if (status === "completed") {
132
- for (const callback of bp.completionCallbacks) callback();
133
- }
131
+ for (const callback of bp.completionCallbacks) callback();
134
132
  bp.completionCallbacks = [];
135
133
  for (const resolve of bp.waiters) resolve();
136
134
  bp.waiters = [];
@@ -171,6 +169,7 @@ export class MonitorManager {
171
169
  monitorId: id,
172
170
  error: err.message,
173
171
  });
172
+ for (const callback of bp.completionCallbacks) callback();
174
173
  bp.completionCallbacks = [];
175
174
  for (const resolve of bp.waiters) resolve();
176
175
  bp.waiters = [];
package/src/store.ts CHANGED
@@ -10,6 +10,7 @@ const LOCK_MAX_RETRIES = 100;
10
10
  const MAX_LOOPS = 25;
11
11
 
12
12
  function acquireLock(lockPath: string): void {
13
+ mkdirSync(dirname(lockPath), { recursive: true });
13
14
  for (let i = 0; i < LOCK_MAX_RETRIES; i++) {
14
15
  try {
15
16
  writeFileSync(lockPath, `${process.pid}`, { flag: "wx" });
package/src/task-store.ts CHANGED
@@ -10,6 +10,7 @@ const LOCK_MAX_RETRIES = 100;
10
10
  const MAX_TASKS = 200;
11
11
 
12
12
  function acquireLock(lockPath: string): void {
13
+ mkdirSync(dirname(lockPath), { recursive: true });
13
14
  for (let i = 0; i < LOCK_MAX_RETRIES; i++) {
14
15
  try {
15
16
  writeFileSync(lockPath, `${process.pid}`, { flag: "wx" });
@@ -149,7 +149,7 @@ Skip this tool when the task is a one-off check (just do it directly) or when th
149
149
  "## Task-driven workflows",
150
150
  "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.",
151
151
  "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.",
152
- "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.",
152
+ "Set taskBacklog: true for backlog worker loops that process the existing pending queue. Backlog worker loops bootstrap against existing pending tasks and auto-delete when the queue reaches zero.",
153
153
  "When no tasks are pending, the loop should stop itself or skip the wake entirely — no tokens burned on empty polls.",
154
154
  "After creating a loop, tell the user the loop ID so they can cancel it with LoopDelete.",
155
155
  ],
@@ -229,10 +229,10 @@ Skip this tool when the task is a one-off check (just do it directly) or when th
229
229
  `Loop #${entry.id} created: ${entry.prompt.slice(0, 60)}\n` +
230
230
  `Trigger: ${triggerDesc}\n` +
231
231
  `Recurring: ${entry.recurring}\n` +
232
- (entry.autoTask ? "Auto-task: enabled\n" : "") +
233
- (entry.taskBacklog ? "Task-backlog: enabled\n" : "") +
234
- (bootstrapped ? "Bootstrap: queued initial wake for existing pending tasks\n" : "") +
235
- (isTaskSystemReady() ? "" : "(task system not ready yet — autoTask may not fire until native fallback or pi-tasks becomes available)\n") +
232
+ (entry.autoTask ? "Auto-create task: enabled\n" : "") +
233
+ (entry.taskBacklog ? "Backlog worker: enabled\n" : "") +
234
+ (bootstrapped ? "Backlog: initial wake queued for existing pending tasks\n" : "") +
235
+ (isTaskSystemReady() ? "" : "Task system: not ready yet — autoTask may not fire until native fallback or pi-tasks becomes available\n") +
236
236
  `ID: ${entry.id} (use LoopDelete to cancel)`
237
237
  ));
238
238
  },
@@ -266,6 +266,7 @@ Use this before creating new loops to avoid duplicates, or to find IDs for LoopD
266
266
  line += ` next: ${formatRemaining(remaining)}`;
267
267
  }
268
268
  if (entry.autoTask) line += " [auto-task]";
269
+ if (entry.taskBacklog) line += " [backlog-worker]";
269
270
  lines.push(line);
270
271
  }
271
272
 
@@ -85,7 +85,7 @@ Pass onDone with a prompt and the monitor auto-creates a one-shot loop that fire
85
85
  const doneTrigger: Trigger = { type: "event", source: "monitor:done", filter: JSON.stringify({ monitorId: entry.id }) };
86
86
  const doneLoop = getStore().create(doneTrigger, params.onDone, { recurring: false });
87
87
  handleMonitorDoneLoop(doneLoop, entry.id);
88
- onDoneMsg = `\nonDone loop #${doneLoop.id}: fires when monitor completes — no polling needed`;
88
+ onDoneMsg = `\nCompletion wake loop #${doneLoop.id}: fires when the monitor completes — no polling needed`;
89
89
  }
90
90
 
91
91
  return Promise.resolve(textResult(
@@ -103,11 +103,11 @@ Pass onDone with a prompt and the monitor auto-creates a one-shot loop that fire
103
103
  parameters: Type.Object({}),
104
104
  execute() {
105
105
  const monitors = getMonitorManager().list();
106
- if (monitors.length === 0) return Promise.resolve(textResult("No monitors running."));
106
+ if (monitors.length === 0) return Promise.resolve(textResult("No monitors."));
107
107
 
108
108
  const lines: string[] = [];
109
109
  for (const m of monitors) {
110
- const icon = m.status === "running" ? ">" : m.status === "completed" ? "ok" : "!!";
110
+ const icon = m.status === "running" ? ">" : m.status === "completed" ? "ok" : "x";
111
111
  const age = Date.now() - m.startedAt;
112
112
  const ageStr = formatRemaining(age);
113
113
  let line = `${icon} #${m.id} [${m.status}] ${m.command.slice(0, 60)} — ${m.outputLines} lines (${ageStr})`;
@@ -32,7 +32,10 @@ Fields:
32
32
  promptGuidelines: [
33
33
  "Use TaskCreate to track complex multi-step work across turns.",
34
34
  "When the user gives a broad goal, use multiple TaskCreate calls to decompose it into a small backlog of concrete tasks rather than one oversized task.",
35
+ "If the user supplies a shared goal or meta-goal, preserve it explicitly using the user's wording and tie each created task back to that goal in its description.",
36
+ "If several tasks share one goal, keep subjects short and put the shared goal in the first sentence of each description or as an equivalent explicit framing.",
35
37
  "Prefer 2-5 tasks that separate investigation, implementation, validation, and reporting or commit-prep when those phases are distinct.",
38
+ "When the user asks to break work into tasks, create the backlog directly and do not pivot to loops, monitors, or other automation unless the user also asked for ongoing automation.",
36
39
  "Make each `subject` a short verb-object action.",
37
40
  "Make each `description` include the expected artifact, outcome, or done condition so another turn can pick the task up cleanly.",
38
41
  "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.",
@@ -54,7 +57,7 @@ Fields:
54
57
  updateWidget();
55
58
 
56
59
  const autoLoopMsg = backlog.created && backlog.entry
57
- ? `\nWorker loop #${backlog.entry.id} auto-created`
60
+ ? `\nBacklog worker loop #${backlog.entry.id} created`
58
61
  : "";
59
62
  return Promise.resolve(textResult(`Task #${entry.id} created: ${entry.subject}${autoLoopMsg}`));
60
63
  },
@@ -121,7 +124,7 @@ Parameters: id (required), status, subject, description`,
121
124
  const backlog = await evaluateTaskBacklog(taskStore, taskStore.pendingCount());
122
125
  const statusMsg = status ? ` → ${status}` : "";
123
126
  const autoLoopMsg = backlog.created && backlog.entry
124
- ? `\nWorker loop #${backlog.entry.id} auto-created`
127
+ ? `\nBacklog worker loop #${backlog.entry.id} created`
125
128
  : "";
126
129
  return Promise.resolve(textResult(`Task #${id} updated${statusMsg}${autoLoopMsg}`));
127
130
  },
package/src/ui/widget.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import type { ExtensionUIContext } from "@earendil-works/pi-coding-agent";
2
2
  import type { MonitorManager } from "../monitor-manager.js";
3
3
  import type { LoopStore } from "../store.js";
4
+ import type { LoopEntry } from "../types.js";
4
5
 
5
6
  interface TaskSummary {
6
7
  count: number;
@@ -34,7 +35,7 @@ export class LoopWidget {
34
35
  }
35
36
 
36
37
  private computeStatus(): string | undefined {
37
- const loops = this.store.list().filter(l => l.status === "active");
38
+ const loops = this.store.list().filter(isStatusVisibleLoop);
38
39
  const monitors = this.monitorManager.list();
39
40
  const taskSummary = this.taskSummaryProvider?.() ?? { count: 0 };
40
41
 
@@ -60,3 +61,9 @@ export class LoopWidget {
60
61
  function formatCount(count: number, noun: string): string {
61
62
  return `${count} ${noun}${count === 1 ? "" : "s"}`;
62
63
  }
64
+
65
+ function isStatusVisibleLoop(loop: LoopEntry): boolean {
66
+ if (loop.status !== "active") return false;
67
+ if (loop.recurring) return true;
68
+ return !(loop.trigger.type === "event" && loop.trigger.source === "monitor:done");
69
+ }