@trevonistrevon/pi-loop 0.5.5 → 0.5.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.
Files changed (68) hide show
  1. package/coverage/coverage-final.json +6 -6
  2. package/coverage/index.html +19 -19
  3. package/coverage/src/commands/index.html +1 -1
  4. package/coverage/src/commands/loop-command.ts.html +1 -1
  5. package/coverage/src/commands/tasks-command.ts.html +1 -1
  6. package/coverage/src/coordinator.ts.html +1 -1
  7. package/coverage/src/goal-coordinator.ts.html +1 -1
  8. package/coverage/src/goal-reducer.ts.html +1 -1
  9. package/coverage/src/goal-store.ts.html +1 -1
  10. package/coverage/src/goal-verifier.ts.html +1 -1
  11. package/coverage/src/index.html +25 -25
  12. package/coverage/src/index.ts.html +21 -12
  13. package/coverage/src/loop-parse.ts.html +22 -22
  14. package/coverage/src/loop-reducer.ts.html +1 -1
  15. package/coverage/src/monitor-completion-coordinator.ts.html +1 -1
  16. package/coverage/src/monitor-manager.ts.html +161 -92
  17. package/coverage/src/monitor-reducer.ts.html +31 -31
  18. package/coverage/src/notification-reducer.ts.html +1 -1
  19. package/coverage/src/reducer-backed-store.ts.html +10 -10
  20. package/coverage/src/runtime/index.html +1 -1
  21. package/coverage/src/runtime/monitor-ondone-runtime.ts.html +1 -1
  22. package/coverage/src/runtime/notification-runtime.ts.html +1 -1
  23. package/coverage/src/runtime/scope.ts.html +1 -1
  24. package/coverage/src/runtime/session-runtime.ts.html +1 -1
  25. package/coverage/src/runtime/task-backlog-runtime.ts.html +1 -1
  26. package/coverage/src/runtime/task-rpc.ts.html +1 -1
  27. package/coverage/src/scheduler.ts.html +1 -1
  28. package/coverage/src/store.ts.html +1 -1
  29. package/coverage/src/task-backlog-coordinator.ts.html +1 -1
  30. package/coverage/src/task-reducer.ts.html +1 -1
  31. package/coverage/src/task-store.ts.html +1 -1
  32. package/coverage/src/tools/index.html +1 -1
  33. package/coverage/src/tools/loop-tools.ts.html +1 -1
  34. package/coverage/src/tools/monitor-tools.ts.html +1 -1
  35. package/coverage/src/tools/native-task-tools.ts.html +1 -1
  36. package/coverage/src/trigger-system.ts.html +1 -1
  37. package/coverage/src/ui/index.html +1 -1
  38. package/coverage/src/ui/widget.ts.html +18 -18
  39. package/dist/index.js +3 -0
  40. package/dist/monitor-manager.d.ts +9 -0
  41. package/dist/monitor-manager.js +33 -12
  42. package/package.json +1 -1
  43. package/src/index.ts +3 -0
  44. package/src/monitor-manager.ts +35 -12
  45. package/tmp/perf-cap-data/loops.json +0 -455
  46. package/tmp/perf-cap-data/tasks.json +0 -1
  47. package/tmp/perf-data/loops-0.json +0 -1
  48. package/tmp/perf-data/loops-10.json +0 -1
  49. package/tmp/perf-data/loops-100.json +0 -1
  50. package/tmp/perf-data/loops-1000.json +0 -1
  51. package/tmp/perf-data/loops-500.json +0 -1
  52. package/tmp/perf-data/tasks-0.json +0 -1
  53. package/tmp/perf-data/tasks-10.json +0 -1
  54. package/tmp/perf-data/tasks-100.json +0 -1
  55. package/tmp/perf-data/tasks-1000.json +0 -1
  56. package/tmp/perf-data/tasks-500.json +0 -1
  57. package/tmp/perf-data-session/loops-0.json +0 -4
  58. package/tmp/perf-data-session/loops-100.json +0 -1805
  59. package/tmp/perf-data-session/loops-1000.json +0 -18005
  60. package/tmp/perf-data-session/loops-25.json +0 -455
  61. package/tmp/perf-data-session/loops-500.json +0 -9005
  62. package/tmp/perf-data-session/tasks-0.json +0 -1
  63. package/tmp/perf-data-session/tasks-100.json +0 -1
  64. package/tmp/perf-data-session/tasks-1000.json +0 -1
  65. package/tmp/perf-data-session/tasks-25.json +0 -1
  66. package/tmp/perf-data-session/tasks-500.json +0 -1
  67. package/tmp/perf-session-init.js +0 -98
  68. package/tmp/perf-startup-audit.js +0 -80
@@ -176,7 +176,7 @@
176
176
  <div class='footer quiet pad2 space-top1 center small'>
177
177
  Code coverage generated by
178
178
  <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
179
- at 2026-06-10T03:38:12.908Z
179
+ at 2026-06-18T14:16:07.591Z
180
180
  </div>
181
181
  <script src="../../prettify.js"></script>
182
182
  <script>
@@ -295,7 +295,7 @@ export function createMonitorOnDoneRuntime(options: MonitorOnDoneRuntimeOptions)
295
295
  <div class='footer quiet pad2 space-top1 center small'>
296
296
  Code coverage generated by
297
297
  <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
298
- at 2026-06-10T03:38:12.908Z
298
+ at 2026-06-18T14:16:07.591Z
299
299
  </div>
300
300
  <script src="../../prettify.js"></script>
301
301
  <script>
@@ -706,7 +706,7 @@ export function createNotificationRuntime(options: NotificationRuntimeOptions):
706
706
  <div class='footer quiet pad2 space-top1 center small'>
707
707
  Code coverage generated by
708
708
  <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
709
- at 2026-06-10T03:38:12.908Z
709
+ at 2026-06-18T14:16:07.591Z
710
710
  </div>
711
711
  <script src="../../prettify.js"></script>
712
712
  <script>
@@ -181,7 +181,7 @@ export function resolveTaskStorePath(options: ScopeOptions, sessionId?: string):
181
181
  <div class='footer quiet pad2 space-top1 center small'>
182
182
  Code coverage generated by
183
183
  <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
184
- at 2026-06-10T03:38:12.908Z
184
+ at 2026-06-18T14:16:07.591Z
185
185
  </div>
186
186
  <script src="../../prettify.js"></script>
187
187
  <script>
@@ -574,7 +574,7 @@ export function registerSessionRuntimeHooks(options: SessionRuntimeOptions): voi
574
574
  <div class='footer quiet pad2 space-top1 center small'>
575
575
  Code coverage generated by
576
576
  <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
577
- at 2026-06-10T03:38:12.908Z
577
+ at 2026-06-18T14:16:07.591Z
578
578
  </div>
579
579
  <script src="../../prettify.js"></script>
580
580
  <script>
@@ -559,7 +559,7 @@ export function createTaskBacklogRuntime(options: TaskBacklogRuntimeOptions): Ta
559
559
  <div class='footer quiet pad2 space-top1 center small'>
560
560
  Code coverage generated by
561
561
  <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
562
- at 2026-06-10T03:38:12.908Z
562
+ at 2026-06-18T14:16:07.591Z
563
563
  </div>
564
564
  <script src="../../prettify.js"></script>
565
565
  <script>
@@ -520,7 +520,7 @@ export function createTaskRuntimeBridge(options: TaskRuntimeBridgeOptions): Task
520
520
  <div class='footer quiet pad2 space-top1 center small'>
521
521
  Code coverage generated by
522
522
  <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
523
- at 2026-06-10T03:38:12.908Z
523
+ at 2026-06-18T14:16:07.591Z
524
524
  </div>
525
525
  <script src="../../prettify.js"></script>
526
526
  <script>
@@ -385,7 +385,7 @@ export class CronScheduler {
385
385
  <div class='footer quiet pad2 space-top1 center small'>
386
386
  Code coverage generated by
387
387
  <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
388
- at 2026-06-10T03:38:12.908Z
388
+ at 2026-06-18T14:16:07.591Z
389
389
  </div>
390
390
  <script src="../prettify.js"></script>
391
391
  <script>
@@ -652,7 +652,7 @@ export class LoopStore extends ReducerBackedStore&lt;LoopEntry, LoopReducerState
652
652
  <div class='footer quiet pad2 space-top1 center small'>
653
653
  Code coverage generated by
654
654
  <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
655
- at 2026-06-10T03:38:12.908Z
655
+ at 2026-06-18T14:16:07.591Z
656
656
  </div>
657
657
  <script src="../prettify.js"></script>
658
658
  <script>
@@ -166,7 +166,7 @@ export function reduceTaskBacklogEvent(event: TaskBacklogEvent): TaskBacklogEffe
166
166
  <div class='footer quiet pad2 space-top1 center small'>
167
167
  Code coverage generated by
168
168
  <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
169
- at 2026-06-10T03:38:12.908Z
169
+ at 2026-06-18T14:16:07.591Z
170
170
  </div>
171
171
  <script src="../prettify.js"></script>
172
172
  <script>
@@ -535,7 +535,7 @@ export function reduceTaskState(state: TaskReducerState, event: TaskReducerEvent
535
535
  <div class='footer quiet pad2 space-top1 center small'>
536
536
  Code coverage generated by
537
537
  <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
538
- at 2026-06-10T03:38:12.908Z
538
+ at 2026-06-18T14:16:07.591Z
539
539
  </div>
540
540
  <script src="../prettify.js"></script>
541
541
  <script>
@@ -511,7 +511,7 @@ export class TaskStore extends ReducerBackedStore&lt;TaskEntry, TaskReducerState
511
511
  <div class='footer quiet pad2 space-top1 center small'>
512
512
  Code coverage generated by
513
513
  <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
514
- at 2026-06-10T03:38:12.908Z
514
+ at 2026-06-18T14:16:07.591Z
515
515
  </div>
516
516
  <script src="../prettify.js"></script>
517
517
  <script>
@@ -131,7 +131,7 @@
131
131
  <div class='footer quiet pad2 space-top1 center small'>
132
132
  Code coverage generated by
133
133
  <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
134
- at 2026-06-10T03:38:12.908Z
134
+ at 2026-06-18T14:16:07.591Z
135
135
  </div>
136
136
  <script src="../../prettify.js"></script>
137
137
  <script>
@@ -985,7 +985,7 @@ Use "pause" to temporarily stop a loop without removing it. Use "delete" to perm
985
985
  <div class='footer quiet pad2 space-top1 center small'>
986
986
  Code coverage generated by
987
987
  <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
988
- at 2026-06-10T03:38:12.908Z
988
+ at 2026-06-18T14:16:07.591Z
989
989
  </div>
990
990
  <script src="../../prettify.js"></script>
991
991
  <script>
@@ -532,7 +532,7 @@ Use MonitorList to find the monitor ID, then stop it with this tool.`,
532
532
  <div class='footer quiet pad2 space-top1 center small'>
533
533
  Code coverage generated by
534
534
  <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
535
- at 2026-06-10T03:38:12.908Z
535
+ at 2026-06-18T14:16:07.591Z
536
536
  </div>
537
537
  <script src="../../prettify.js"></script>
538
538
  <script>
@@ -520,7 +520,7 @@ Parameters: id (required), status, subject, description`,
520
520
  <div class='footer quiet pad2 space-top1 center small'>
521
521
  Code coverage generated by
522
522
  <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
523
- at 2026-06-10T03:38:12.908Z
523
+ at 2026-06-18T14:16:07.591Z
524
524
  </div>
525
525
  <script src="../../prettify.js"></script>
526
526
  <script>
@@ -532,7 +532,7 @@ export class TriggerSystem {
532
532
  <div class='footer quiet pad2 space-top1 center small'>
533
533
  Code coverage generated by
534
534
  <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
535
- at 2026-06-10T03:38:12.908Z
535
+ at 2026-06-18T14:16:07.591Z
536
536
  </div>
537
537
  <script src="../prettify.js"></script>
538
538
  <script>
@@ -101,7 +101,7 @@
101
101
  <div class='footer quiet pad2 space-top1 center small'>
102
102
  Code coverage generated by
103
103
  <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
104
- at 2026-06-10T03:38:12.908Z
104
+ at 2026-06-18T14:16:07.591Z
105
105
  </div>
106
106
  <script src="../../prettify.js"></script>
107
107
  <script>
@@ -164,27 +164,27 @@
164
164
  <span class="cline-any cline-neutral">&nbsp;</span>
165
165
  <span class="cline-any cline-neutral">&nbsp;</span>
166
166
  <span class="cline-any cline-neutral">&nbsp;</span>
167
- <span class="cline-any cline-yes">174x</span>
168
- <span class="cline-any cline-yes">158x</span>
167
+ <span class="cline-any cline-yes">189x</span>
168
+ <span class="cline-any cline-yes">161x</span>
169
169
  <span class="cline-any cline-neutral">&nbsp;</span>
170
170
  <span class="cline-any cline-neutral">&nbsp;</span>
171
171
  <span class="cline-any cline-neutral">&nbsp;</span>
172
- <span class="cline-any cline-yes">158x</span>
173
- <span class="cline-any cline-yes">158x</span>
174
- <span class="cline-any cline-yes">158x</span>
172
+ <span class="cline-any cline-yes">161x</span>
173
+ <span class="cline-any cline-yes">161x</span>
174
+ <span class="cline-any cline-yes">161x</span>
175
175
  <span class="cline-any cline-neutral">&nbsp;</span>
176
- <span class="cline-any cline-yes">158x</span>
176
+ <span class="cline-any cline-yes">161x</span>
177
177
  <span class="cline-any cline-yes">66x</span>
178
178
  <span class="cline-any cline-neutral">&nbsp;</span>
179
179
  <span class="cline-any cline-neutral">&nbsp;</span>
180
- <span class="cline-any cline-yes">92x</span>
181
- <span class="cline-any cline-yes">92x</span>
182
- <span class="cline-any cline-yes">92x</span>
183
- <span class="cline-any cline-yes">92x</span>
180
+ <span class="cline-any cline-yes">95x</span>
181
+ <span class="cline-any cline-yes">95x</span>
182
+ <span class="cline-any cline-yes">95x</span>
183
+ <span class="cline-any cline-yes">95x</span>
184
184
  <span class="cline-any cline-neutral">&nbsp;</span>
185
- <span class="cline-any cline-yes">92x</span>
186
- <span class="cline-any cline-yes">92x</span>
187
- <span class="cline-any cline-yes">92x</span>
185
+ <span class="cline-any cline-yes">95x</span>
186
+ <span class="cline-any cline-yes">95x</span>
187
+ <span class="cline-any cline-yes">95x</span>
188
188
  <span class="cline-any cline-neutral">&nbsp;</span>
189
189
  <span class="cline-any cline-neutral">&nbsp;</span>
190
190
  <span class="cline-any cline-neutral">&nbsp;</span>
@@ -193,13 +193,13 @@
193
193
  <span class="cline-any cline-neutral">&nbsp;</span>
194
194
  <span class="cline-any cline-neutral">&nbsp;</span>
195
195
  <span class="cline-any cline-neutral">&nbsp;</span>
196
- <span class="cline-any cline-yes">116x</span>
196
+ <span class="cline-any cline-yes">119x</span>
197
197
  <span class="cline-any cline-neutral">&nbsp;</span>
198
198
  <span class="cline-any cline-neutral">&nbsp;</span>
199
199
  <span class="cline-any cline-neutral">&nbsp;</span>
200
- <span class="cline-any cline-yes">41x</span>
201
- <span class="cline-any cline-yes">40x</span>
202
- <span class="cline-any cline-yes">3x</span>
200
+ <span class="cline-any cline-yes">44x</span>
201
+ <span class="cline-any cline-yes">43x</span>
202
+ <span class="cline-any cline-yes">6x</span>
203
203
  <span class="cline-any cline-neutral">&nbsp;</span>
204
204
  <span class="cline-any cline-neutral">&nbsp;</span></td><td class="text"><pre class="prettyprint lang-js">import type { ExtensionUIContext } from "@earendil-works/pi-coding-agent";
205
205
  import type { MonitorManager } from "../monitor-manager.js";
@@ -277,7 +277,7 @@ function isStatusVisibleLoop(loop: LoopEntry): boolean {
277
277
  <div class='footer quiet pad2 space-top1 center small'>
278
278
  Code coverage generated by
279
279
  <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
280
- at 2026-06-10T03:38:12.908Z
280
+ at 2026-06-18T14:16:07.591Z
281
281
  </div>
282
282
  <script src="../../prettify.js"></script>
283
283
  <script>
package/dist/index.js CHANGED
@@ -46,6 +46,9 @@ export default function (pi) {
46
46
  let scheduler;
47
47
  let triggerSystem;
48
48
  const widget = new LoopWidget(store, monitorManager);
49
+ // Repaint the status bar when a monitor finishes/prunes on its own (no tool
50
+ // call), so stale monitors don't linger in the count between turns.
51
+ monitorManager.setOnChange(() => widget.update());
49
52
  widget.setTaskSummaryProvider(() => {
50
53
  if (!nativeTaskStore)
51
54
  return { count: 0 };
@@ -4,9 +4,18 @@ export declare class MonitorManager {
4
4
  private pi;
5
5
  private processes;
6
6
  private nextId;
7
+ private onChange;
7
8
  constructor(pi: ExtensionAPI);
9
+ /**
10
+ * Register a callback fired when a monitor's status changes or it is pruned
11
+ * (e.g. autonomous completion/error/stop/prune with no tool call). Used to
12
+ * repaint the status widget, which otherwise only refreshes on turn
13
+ * boundaries and explicit tool actions. Not fired for output lines.
14
+ */
15
+ setOnChange(cb: () => void): void;
8
16
  private toReducerState;
9
17
  private applyReducerEvent;
18
+ private schedulePrune;
10
19
  create(command: string, description?: string, timeout?: number): MonitorEntry;
11
20
  get(id: string): MonitorEntry | undefined;
12
21
  list(): MonitorEntry[];
@@ -4,9 +4,19 @@ export class MonitorManager {
4
4
  pi;
5
5
  processes = new Map();
6
6
  nextId = 1;
7
+ onChange;
7
8
  constructor(pi) {
8
9
  this.pi = pi;
9
10
  }
11
+ /**
12
+ * Register a callback fired when a monitor's status changes or it is pruned
13
+ * (e.g. autonomous completion/error/stop/prune with no tool call). Used to
14
+ * repaint the status widget, which otherwise only refreshes on turn
15
+ * boundaries and explicit tool actions. Not fired for output lines.
16
+ */
17
+ setOnChange(cb) {
18
+ this.onChange = cb;
19
+ }
10
20
  toReducerState() {
11
21
  return {
12
22
  nextId: this.nextId,
@@ -24,8 +34,29 @@ export class MonitorManager {
24
34
  }
25
35
  process.entry = updated;
26
36
  }
37
+ // Repaint on status/set transitions, but not on the frequent output-line
38
+ // events (which never change the visible count).
39
+ if (event.type !== "MONITOR_OUTPUT" && event.type !== "MONITOR_ONDONE_REGISTERED") {
40
+ this.onChange?.();
41
+ }
27
42
  return result;
28
43
  }
44
+ // Remove a finished monitor (completed/errored/stopped) after a brief delay so
45
+ // tool consumers can still read its final state via MonitorList. Shared by
46
+ // finish() and stop() so every terminal status is pruned consistently — a
47
+ // stopped monitor that is never pruned lingers in list() and the widget count.
48
+ schedulePrune(id) {
49
+ setTimeout(() => {
50
+ this.applyReducerEvent({
51
+ type: "MONITOR_PRUNED",
52
+ at: Date.now(),
53
+ source: "system",
54
+ entityType: "monitor",
55
+ entityId: id,
56
+ payload: { id },
57
+ });
58
+ }, 30000);
59
+ }
29
60
  create(command, description, timeout = 300000) {
30
61
  const now = Date.now();
31
62
  const result = reduceMonitorState(this.toReducerState(), {
@@ -120,18 +151,7 @@ export class MonitorManager {
120
151
  for (const resolve of bp.waiters)
121
152
  resolve();
122
153
  bp.waiters = [];
123
- // Remove completed/errored monitors after a brief delay so tool
124
- // consumers have time to read the final state via MonitorList.
125
- setTimeout(() => {
126
- this.applyReducerEvent({
127
- type: "MONITOR_PRUNED",
128
- at: Date.now(),
129
- source: "system",
130
- entityType: "monitor",
131
- entityId: id,
132
- payload: { id },
133
- });
134
- }, 30000);
154
+ this.schedulePrune(id);
135
155
  };
136
156
  child.on("close", (code) => {
137
157
  if (bp.entry.status === "running") {
@@ -197,6 +217,7 @@ export class MonitorManager {
197
217
  reason: "manual",
198
218
  },
199
219
  });
220
+ this.schedulePrune(id);
200
221
  bp.proc.kill("SIGTERM");
201
222
  await new Promise((resolve) => {
202
223
  const timer = setTimeout(() => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trevonistrevon/pi-loop",
3
- "version": "0.5.5",
3
+ "version": "0.5.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
@@ -55,6 +55,9 @@ export default function (pi: ExtensionAPI) {
55
55
  let scheduler: CronScheduler;
56
56
  let triggerSystem: TriggerSystem;
57
57
  const widget = new LoopWidget(store, monitorManager);
58
+ // Repaint the status bar when a monitor finishes/prunes on its own (no tool
59
+ // call), so stale monitors don't linger in the count between turns.
60
+ monitorManager.setOnChange(() => widget.update());
58
61
  widget.setTaskSummaryProvider(() => {
59
62
  if (!nativeTaskStore) return { count: 0 };
60
63
  const tasks = nativeTaskStore.list().filter(t => t.status === "pending" || t.status === "in_progress");
@@ -11,9 +11,20 @@ import type { MonitorEntry, MonitorProcess } from "./types.js";
11
11
  export class MonitorManager {
12
12
  private processes = new Map<string, MonitorProcess>();
13
13
  private nextId = 1;
14
+ private onChange: (() => void) | undefined;
14
15
 
15
16
  constructor(private pi: ExtensionAPI) {}
16
17
 
18
+ /**
19
+ * Register a callback fired when a monitor's status changes or it is pruned
20
+ * (e.g. autonomous completion/error/stop/prune with no tool call). Used to
21
+ * repaint the status widget, which otherwise only refreshes on turn
22
+ * boundaries and explicit tool actions. Not fired for output lines.
23
+ */
24
+ setOnChange(cb: () => void): void {
25
+ this.onChange = cb;
26
+ }
27
+
17
28
  private toReducerState(): MonitorReducerState {
18
29
  return {
19
30
  nextId: this.nextId,
@@ -34,9 +45,31 @@ export class MonitorManager {
34
45
  }
35
46
  process.entry = updated;
36
47
  }
48
+ // Repaint on status/set transitions, but not on the frequent output-line
49
+ // events (which never change the visible count).
50
+ if (event.type !== "MONITOR_OUTPUT" && event.type !== "MONITOR_ONDONE_REGISTERED") {
51
+ this.onChange?.();
52
+ }
37
53
  return result;
38
54
  }
39
55
 
56
+ // Remove a finished monitor (completed/errored/stopped) after a brief delay so
57
+ // tool consumers can still read its final state via MonitorList. Shared by
58
+ // finish() and stop() so every terminal status is pruned consistently — a
59
+ // stopped monitor that is never pruned lingers in list() and the widget count.
60
+ private schedulePrune(id: string): void {
61
+ setTimeout(() => {
62
+ this.applyReducerEvent({
63
+ type: "MONITOR_PRUNED",
64
+ at: Date.now(),
65
+ source: "system",
66
+ entityType: "monitor",
67
+ entityId: id,
68
+ payload: { id },
69
+ });
70
+ }, 30000);
71
+ }
72
+
40
73
  create(command: string, description?: string, timeout = 300000): MonitorEntry {
41
74
  const now = Date.now();
42
75
  const result = reduceMonitorState(this.toReducerState(), {
@@ -132,18 +165,7 @@ export class MonitorManager {
132
165
  bp.completionCallbacks = [];
133
166
  for (const resolve of bp.waiters) resolve();
134
167
  bp.waiters = [];
135
- // Remove completed/errored monitors after a brief delay so tool
136
- // consumers have time to read the final state via MonitorList.
137
- setTimeout(() => {
138
- this.applyReducerEvent({
139
- type: "MONITOR_PRUNED",
140
- at: Date.now(),
141
- source: "system",
142
- entityType: "monitor",
143
- entityId: id,
144
- payload: { id },
145
- });
146
- }, 30000);
168
+ this.schedulePrune(id);
147
169
  };
148
170
 
149
171
  child.on("close", (code) => {
@@ -214,6 +236,7 @@ export class MonitorManager {
214
236
  reason: "manual",
215
237
  },
216
238
  });
239
+ this.schedulePrune(id);
217
240
  bp.proc.kill("SIGTERM");
218
241
 
219
242
  await new Promise<void>((resolve) => {