@trevonistrevon/pi-loop 0.5.8 → 0.5.9

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 (50) hide show
  1. package/.release-please-manifest.json +3 -0
  2. package/Dockerfile +17 -0
  3. package/coverage/coverage-final.json +25 -24
  4. package/coverage/index.html +42 -42
  5. package/coverage/src/commands/index.html +15 -15
  6. package/coverage/src/commands/loop-command.ts.html +3 -3
  7. package/coverage/src/commands/tasks-command.ts.html +23 -23
  8. package/coverage/src/coordinator.ts.html +3 -3
  9. package/coverage/src/goal-coordinator.ts.html +1 -1
  10. package/coverage/src/goal-reducer.ts.html +1 -1
  11. package/coverage/src/goal-store.ts.html +1 -1
  12. package/coverage/src/goal-verifier.ts.html +1 -1
  13. package/coverage/src/index.html +18 -18
  14. package/coverage/src/index.ts.html +126 -78
  15. package/coverage/src/loop-parse.ts.html +38 -38
  16. package/coverage/src/loop-reducer.ts.html +17 -17
  17. package/coverage/src/monitor-completion-coordinator.ts.html +1 -1
  18. package/coverage/src/monitor-manager.ts.html +16 -16
  19. package/coverage/src/monitor-reducer.ts.html +10 -10
  20. package/coverage/src/notification-reducer.ts.html +11 -11
  21. package/coverage/src/reducer-backed-store.ts.html +49 -49
  22. package/coverage/src/runtime/index.html +35 -20
  23. package/coverage/src/runtime/monitor-ondone-runtime.ts.html +5 -5
  24. package/coverage/src/runtime/notification-runtime.ts.html +25 -25
  25. package/coverage/src/runtime/scope.ts.html +12 -12
  26. package/coverage/src/runtime/session-runtime.ts.html +23 -23
  27. package/coverage/src/runtime/task-backlog-runtime.ts.html +7 -7
  28. package/coverage/src/runtime/task-events.ts.html +208 -0
  29. package/coverage/src/runtime/task-rpc.ts.html +21 -15
  30. package/coverage/src/scheduler.ts.html +25 -25
  31. package/coverage/src/store.ts.html +12 -12
  32. package/coverage/src/task-backlog-coordinator.ts.html +1 -1
  33. package/coverage/src/task-reducer.ts.html +1 -1
  34. package/coverage/src/task-store.ts.html +5 -5
  35. package/coverage/src/tools/index.html +11 -11
  36. package/coverage/src/tools/loop-tools.ts.html +5 -5
  37. package/coverage/src/tools/monitor-tools.ts.html +5 -5
  38. package/coverage/src/tools/native-task-tools.ts.html +41 -20
  39. package/coverage/src/trigger-system.ts.html +8 -8
  40. package/coverage/src/ui/index.html +1 -1
  41. package/coverage/src/ui/widget.ts.html +5 -5
  42. package/dist/index.js +34 -19
  43. package/dist/monitor-manager.d.ts +4 -1
  44. package/dist/monitor-manager.js +5 -3
  45. package/docker-compose.yml +12 -0
  46. package/package.json +1 -1
  47. package/release-please-config.json +9 -0
  48. package/src/index.ts +37 -21
  49. package/src/monitor-manager.ts +11 -3
  50. package/vitest.config.ts +4 -0
@@ -224,15 +224,15 @@
224
224
  <span class="cline-any cline-neutral">&nbsp;</span>
225
225
  <span class="cline-any cline-neutral">&nbsp;</span>
226
226
  <span class="cline-any cline-neutral">&nbsp;</span>
227
- <span class="cline-any cline-yes">121x</span>
228
- <span class="cline-any cline-yes">121x</span>
229
- <span class="cline-any cline-yes">121x</span>
227
+ <span class="cline-any cline-yes">123x</span>
228
+ <span class="cline-any cline-yes">123x</span>
229
+ <span class="cline-any cline-yes">123x</span>
230
230
  <span class="cline-any cline-neutral">&nbsp;</span>
231
231
  <span class="cline-any cline-neutral">&nbsp;</span>
232
- <span class="cline-any cline-yes">121x</span>
233
- <span class="cline-any cline-yes">121x</span>
234
- <span class="cline-any cline-yes">121x</span>
235
- <span class="cline-any cline-yes">121x</span>
232
+ <span class="cline-any cline-yes">123x</span>
233
+ <span class="cline-any cline-yes">123x</span>
234
+ <span class="cline-any cline-yes">123x</span>
235
+ <span class="cline-any cline-yes">123x</span>
236
236
  <span class="cline-any cline-neutral">&nbsp;</span>
237
237
  <span class="cline-any cline-neutral">&nbsp;</span>
238
238
  <span class="cline-any cline-neutral">&nbsp;</span>
@@ -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-19T00:56:38.236Z
535
+ at 2026-06-23T21:49:18.784Z
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-19T00:56:38.236Z
104
+ at 2026-06-23T21:49:18.784Z
105
105
  </div>
106
106
  <script src="../../prettify.js"></script>
107
107
  <script>
@@ -147,8 +147,8 @@
147
147
  <span class="cline-any cline-neutral">&nbsp;</span>
148
148
  <span class="cline-any cline-neutral">&nbsp;</span>
149
149
  <span class="cline-any cline-neutral">&nbsp;</span>
150
- <span class="cline-any cline-yes">68x</span>
151
- <span class="cline-any cline-yes">68x</span>
150
+ <span class="cline-any cline-yes">70x</span>
151
+ <span class="cline-any cline-yes">70x</span>
152
152
  <span class="cline-any cline-neutral">&nbsp;</span>
153
153
  <span class="cline-any cline-neutral">&nbsp;</span>
154
154
  <span class="cline-any cline-neutral">&nbsp;</span>
@@ -160,11 +160,11 @@
160
160
  <span class="cline-any cline-neutral">&nbsp;</span>
161
161
  <span class="cline-any cline-neutral">&nbsp;</span>
162
162
  <span class="cline-any cline-neutral">&nbsp;</span>
163
- <span class="cline-any cline-yes">63x</span>
163
+ <span class="cline-any cline-yes">65x</span>
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">192x</span>
167
+ <span class="cline-any cline-yes">189x</span>
168
168
  <span class="cline-any cline-yes">164x</span>
169
169
  <span class="cline-any cline-neutral">&nbsp;</span>
170
170
  <span class="cline-any cline-neutral">&nbsp;</span>
@@ -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-19T00:56:38.236Z
280
+ at 2026-06-23T21:49:18.784Z
281
281
  </div>
282
282
  <script src="../../prettify.js"></script>
283
283
  <script>
package/dist/index.js CHANGED
@@ -36,6 +36,9 @@ function debug(...args) {
36
36
  if (DEBUG)
37
37
  console.error("[pi-loop]", ...args);
38
38
  }
39
+ function isStaleExtensionContextError(error) {
40
+ return error instanceof Error && error.message.includes("extension ctx is stale");
41
+ }
39
42
  export default function (pi) {
40
43
  const piLoopEnv = process.env.PI_LOOP;
41
44
  const piLoopScope = process.env.PI_LOOP_SCOPE;
@@ -263,28 +266,40 @@ export default function (pi) {
263
266
  },
264
267
  });
265
268
  // ── Native task tools (only when pi-tasks is absent) ──
266
- setTimeout(async () => {
269
+ const nativeTaskFallbackTimer = setTimeout(() => {
267
270
  if (tasksAvailable || nativeTasksRegistered)
268
271
  return;
269
- nativeTaskStore = new TaskStore(resolveTaskStorePath(getScopeOptions(), _sessionId));
272
+ const taskStore = new TaskStore(resolveTaskStorePath(getScopeOptions(), _sessionId));
273
+ try {
274
+ registerTasksCommand({
275
+ pi,
276
+ getNativeTaskStore: () => nativeTaskStore,
277
+ evaluateTaskBacklog,
278
+ updateWidget: () => {
279
+ widget.update();
280
+ },
281
+ });
282
+ registerNativeTaskTools({
283
+ pi,
284
+ taskStore,
285
+ evaluateTaskBacklog,
286
+ updateWidget: () => {
287
+ widget.update();
288
+ },
289
+ });
290
+ }
291
+ catch (error) {
292
+ if (isStaleExtensionContextError(error)) {
293
+ debug("native task fallback skipped: extension context went stale");
294
+ return;
295
+ }
296
+ throw error;
297
+ }
298
+ nativeTaskStore = taskStore;
270
299
  nativeTasksRegistered = true;
271
- const taskStore = nativeTaskStore;
272
- registerTasksCommand({
273
- pi,
274
- getNativeTaskStore: () => nativeTaskStore,
275
- evaluateTaskBacklog,
276
- updateWidget: () => {
277
- widget.update();
278
- },
279
- });
280
- registerNativeTaskTools({
281
- pi,
282
- taskStore,
283
- evaluateTaskBacklog,
284
- updateWidget: () => {
285
- widget.update();
286
- },
287
- });
288
300
  debug("native task tools registered (pi-tasks not detected)");
289
301
  }, 6000);
302
+ pi.on("session_shutdown", () => {
303
+ clearTimeout(nativeTaskFallbackTimer);
304
+ });
290
305
  }
@@ -1,11 +1,14 @@
1
+ import { type ChildProcess, type SpawnOptions } from "node:child_process";
1
2
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
3
  import type { MonitorEntry, MonitorProcess } from "./types.js";
4
+ export type SpawnFn = (command: string, args: string[], options: SpawnOptions) => ChildProcess;
3
5
  export declare class MonitorManager {
4
6
  private pi;
5
7
  private processes;
6
8
  private nextId;
7
9
  private onChange;
8
- constructor(pi: ExtensionAPI);
10
+ private spawnFn;
11
+ constructor(pi: ExtensionAPI, spawnFn?: SpawnFn);
9
12
  /**
10
13
  * Register a callback fired when a monitor's status changes or it is pruned
11
14
  * (e.g. autonomous completion/error/stop/prune with no tool call). Used to
@@ -1,12 +1,14 @@
1
- import { spawn } from "node:child_process";
1
+ import { spawn as nodeSpawn } from "node:child_process";
2
2
  import { reduceMonitorState, } from "./monitor-reducer.js";
3
3
  export class MonitorManager {
4
4
  pi;
5
5
  processes = new Map();
6
6
  nextId = 1;
7
7
  onChange;
8
- constructor(pi) {
8
+ spawnFn;
9
+ constructor(pi, spawnFn) {
9
10
  this.pi = pi;
11
+ this.spawnFn = spawnFn ?? ((cmd, args, opts) => nodeSpawn(cmd, args, opts));
10
12
  }
11
13
  /**
12
14
  * Register a callback fired when a monitor's status changes or it is pruned
@@ -76,7 +78,7 @@ export class MonitorManager {
76
78
  const id = String(this.nextId - 1);
77
79
  const entry = result.state.monitorsById[id];
78
80
  const abortController = new AbortController();
79
- const child = spawn("sh", ["-c", command], {
81
+ const child = this.spawnFn("sh", ["-c", command], {
80
82
  stdio: ["ignore", "pipe", "pipe"],
81
83
  signal: abortController.signal,
82
84
  env: { ...process.env },
@@ -0,0 +1,12 @@
1
+ services:
2
+ ci-node20:
3
+ build:
4
+ context: .
5
+ target: node20
6
+ command: sh -c "npm run typecheck && npm run lint && npm run test:coverage && npm run build"
7
+
8
+ ci-node22:
9
+ build:
10
+ context: .
11
+ target: node22
12
+ command: sh -c "npm run typecheck && npm run lint && npm run test:coverage && npm run build"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trevonistrevon/pi-loop",
3
- "version": "0.5.8",
3
+ "version": "0.5.9",
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",
@@ -0,0 +1,9 @@
1
+ {
2
+ "packages": {
3
+ ".": {
4
+ "release-type": "node",
5
+ "bump-minor-pre-major": true,
6
+ "bump-patch-for-minor-pre-major": true
7
+ }
8
+ }
9
+ }
package/src/index.ts CHANGED
@@ -43,6 +43,10 @@ function debug(...args: unknown[]) {
43
43
  if (DEBUG) console.error("[pi-loop]", ...args);
44
44
  }
45
45
 
46
+ function isStaleExtensionContextError(error: unknown): boolean {
47
+ return error instanceof Error && error.message.includes("extension ctx is stale");
48
+ }
49
+
46
50
  export default function (pi: ExtensionAPI) {
47
51
  const piLoopEnv = process.env.PI_LOOP;
48
52
  const piLoopScope = process.env.PI_LOOP_SCOPE as "memory" | "session" | "project" | undefined;
@@ -297,30 +301,42 @@ export default function (pi: ExtensionAPI) {
297
301
 
298
302
  // ── Native task tools (only when pi-tasks is absent) ──
299
303
 
300
- setTimeout(async () => {
304
+ const nativeTaskFallbackTimer = setTimeout(() => {
301
305
  if (tasksAvailable || nativeTasksRegistered) return;
302
- nativeTaskStore = new TaskStore(resolveTaskStorePath(getScopeOptions(), _sessionId));
303
- nativeTasksRegistered = true;
304
- const taskStore = nativeTaskStore;
305
-
306
- registerTasksCommand({
307
- pi,
308
- getNativeTaskStore: () => nativeTaskStore,
309
- evaluateTaskBacklog,
310
- updateWidget: () => {
311
- widget.update();
312
- },
313
- });
306
+ const taskStore = new TaskStore(resolveTaskStorePath(getScopeOptions(), _sessionId));
307
+
308
+ try {
309
+ registerTasksCommand({
310
+ pi,
311
+ getNativeTaskStore: () => nativeTaskStore,
312
+ evaluateTaskBacklog,
313
+ updateWidget: () => {
314
+ widget.update();
315
+ },
316
+ });
314
317
 
315
- registerNativeTaskTools({
316
- pi,
317
- taskStore,
318
- evaluateTaskBacklog,
319
- updateWidget: () => {
320
- widget.update();
321
- },
322
- });
318
+ registerNativeTaskTools({
319
+ pi,
320
+ taskStore,
321
+ evaluateTaskBacklog,
322
+ updateWidget: () => {
323
+ widget.update();
324
+ },
325
+ });
326
+ } catch (error) {
327
+ if (isStaleExtensionContextError(error)) {
328
+ debug("native task fallback skipped: extension context went stale");
329
+ return;
330
+ }
331
+ throw error;
332
+ }
323
333
 
334
+ nativeTaskStore = taskStore;
335
+ nativeTasksRegistered = true;
324
336
  debug("native task tools registered (pi-tasks not detected)");
325
337
  }, 6000);
338
+
339
+ pi.on("session_shutdown", () => {
340
+ clearTimeout(nativeTaskFallbackTimer);
341
+ });
326
342
  }
@@ -1,5 +1,5 @@
1
1
 
2
- import { spawn } from "node:child_process";
2
+ import { type ChildProcess, spawn as nodeSpawn, type SpawnOptions } from "node:child_process";
3
3
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
4
4
  import {
5
5
  type MonitorReducerEvent,
@@ -8,12 +8,20 @@ import {
8
8
  } from "./monitor-reducer.js";
9
9
  import type { MonitorEntry, MonitorProcess } from "./types.js";
10
10
 
11
+ export type SpawnFn = (command: string, args: string[], options: SpawnOptions) => ChildProcess;
12
+
11
13
  export class MonitorManager {
12
14
  private processes = new Map<string, MonitorProcess>();
13
15
  private nextId = 1;
14
16
  private onChange: (() => void) | undefined;
17
+ private spawnFn: SpawnFn;
15
18
 
16
- constructor(private pi: ExtensionAPI) {}
19
+ constructor(
20
+ private pi: ExtensionAPI,
21
+ spawnFn?: SpawnFn,
22
+ ) {
23
+ this.spawnFn = spawnFn ?? ((cmd, args, opts) => nodeSpawn(cmd, args, opts));
24
+ }
17
25
 
18
26
  /**
19
27
  * Register a callback fired when a monitor's status changes or it is pruned
@@ -90,7 +98,7 @@ export class MonitorManager {
90
98
  const entry = result.state.monitorsById[id]!;
91
99
 
92
100
  const abortController = new AbortController();
93
- const child = spawn("sh", ["-c", command], {
101
+ const child = this.spawnFn("sh", ["-c", command], {
94
102
  stdio: ["ignore", "pipe", "pipe"],
95
103
  signal: abortController.signal,
96
104
  env: { ...process.env },
package/vitest.config.ts CHANGED
@@ -5,6 +5,10 @@ export default defineConfig({
5
5
  environment: "node",
6
6
  include: ["test/**/*.test.ts"],
7
7
  exclude: ["node_modules", "dist"],
8
+ // CI runners are slower than local; 15s gives real-child-process
9
+ // tests (monitor stop, lifecycle) enough headroom.
10
+ testTimeout: 15000,
11
+ hookTimeout: 15000,
8
12
  coverage: {
9
13
  provider: "v8",
10
14
  reporter: ["text-summary", "html", "json"],