@trevonistrevon/pi-loop 0.4.11 → 0.5.0

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 (45) hide show
  1. package/README.md +10 -0
  2. package/dist/coordinator.d.ts +35 -0
  3. package/dist/coordinator.js +56 -0
  4. package/dist/goal-types.d.ts +78 -0
  5. package/dist/goal-types.js +1 -0
  6. package/dist/goal-verifier.d.ts +20 -0
  7. package/dist/goal-verifier.js +198 -0
  8. package/dist/index.js +170 -40
  9. package/dist/loop-reducer.d.ts +63 -0
  10. package/dist/loop-reducer.js +67 -0
  11. package/dist/monitor-completion-coordinator.d.ts +10 -0
  12. package/dist/monitor-completion-coordinator.js +13 -0
  13. package/dist/monitor-manager.d.ts +2 -0
  14. package/dist/monitor-manager.js +107 -29
  15. package/dist/monitor-reducer.d.ts +82 -0
  16. package/dist/monitor-reducer.js +69 -0
  17. package/dist/notification-reducer.d.ts +81 -0
  18. package/dist/notification-reducer.js +65 -0
  19. package/dist/store.d.ts +2 -0
  20. package/dist/store.js +118 -44
  21. package/dist/task-backlog-coordinator.d.ts +12 -0
  22. package/dist/task-backlog-coordinator.js +22 -0
  23. package/dist/task-reducer.d.ts +66 -0
  24. package/dist/task-reducer.js +76 -0
  25. package/dist/task-store.d.ts +2 -0
  26. package/dist/task-store.js +82 -30
  27. package/docs/architecture/goal-state-schema.md +505 -0
  28. package/docs/architecture/state-machine-migration.md +546 -0
  29. package/docs/architecture/state-machine-reducer-event-model.md +823 -0
  30. package/docs/architecture/state-machine-test-matrix.md +249 -0
  31. package/docs/architecture/state-machine-transition-map.md +436 -0
  32. package/package.json +1 -1
  33. package/src/coordinator.ts +115 -0
  34. package/src/goal-types.ts +99 -0
  35. package/src/goal-verifier.ts +241 -0
  36. package/src/index.ts +196 -39
  37. package/src/loop-reducer.ts +148 -0
  38. package/src/monitor-completion-coordinator.ts +24 -0
  39. package/src/monitor-manager.ts +115 -27
  40. package/src/monitor-reducer.ts +166 -0
  41. package/src/notification-reducer.ts +155 -0
  42. package/src/store.ts +119 -43
  43. package/src/task-backlog-coordinator.ts +32 -0
  44. package/src/task-reducer.ts +152 -0
  45. package/src/task-store.ts +84 -27
package/dist/index.js CHANGED
@@ -16,10 +16,14 @@
16
16
  import { randomUUID } from "node:crypto";
17
17
  import { join, resolve } from "node:path";
18
18
  import { Type } from "typebox";
19
+ import { createCoordinator, } from "./coordinator.js";
19
20
  import { parseInterval } from "./loop-parse.js";
21
+ import { reduceMonitorCompletionEvent, } from "./monitor-completion-coordinator.js";
20
22
  import { MonitorManager } from "./monitor-manager.js";
23
+ import { reduceNotificationState, } from "./notification-reducer.js";
21
24
  import { CronScheduler } from "./scheduler.js";
22
25
  import { LoopStore } from "./store.js";
26
+ import { reduceTaskBacklogEvent, } from "./task-backlog-coordinator.js";
23
27
  import { TaskStore } from "./task-store.js";
24
28
  import { TriggerSystem } from "./trigger-system.js";
25
29
  import { LoopWidget } from "./ui/widget.js";
@@ -183,12 +187,89 @@ export default function (pi) {
183
187
  if (nativeTaskStore) {
184
188
  nativeTaskStore.sweepCompleted();
185
189
  widget.update();
186
- await cleanupTaskBacklogLoops();
190
+ await evaluateTaskBacklog(nativeTaskStore, nativeTaskStore.pendingCount());
187
191
  }
188
192
  }
189
- let agentRunning = false;
190
- const pendingNotifications = new Map();
193
+ let notificationState = {
194
+ notificationsByKey: {},
195
+ agentRunning: false,
196
+ hasPendingMessages: false,
197
+ };
191
198
  let flushPromise;
199
+ let notificationCoordinatorDelivered = false;
200
+ let notificationCoordinatorDeliveredSuccessfully = false;
201
+ const notificationReducerHandler = (incoming) => {
202
+ const result = reduceNotificationState(notificationState, incoming);
203
+ notificationState = result.state;
204
+ return result.effects;
205
+ };
206
+ const notificationCoordinator = createCoordinator({
207
+ reducers: [notificationReducerHandler],
208
+ effectHandlers: {
209
+ REQUEST_NOTIFICATION_FLUSH: () => { },
210
+ DELIVER_NOTIFICATION: async (effect) => {
211
+ notificationCoordinatorDelivered = true;
212
+ notificationCoordinatorDeliveredSuccessfully = await deliverNotification(effect.payload.notification);
213
+ },
214
+ },
215
+ });
216
+ const monitorCompletionReducerHandler = (incoming) => {
217
+ if (incoming.type !== "MONITOR_ONDONE_TRIGGERED")
218
+ return [];
219
+ return reduceMonitorCompletionEvent(incoming);
220
+ };
221
+ const monitorCompletionCoordinator = createCoordinator({
222
+ reducers: [monitorCompletionReducerHandler],
223
+ effectHandlers: {
224
+ DELIVER_MONITOR_ONDONE_WAKE: (effect) => {
225
+ const { loopId, monitorId } = effect.payload;
226
+ const current = store.get(loopId);
227
+ if (!current)
228
+ return;
229
+ debug(`onDone loop #${loopId} — monitor #${monitorId} completed, delivering through coordinator`);
230
+ onLoopFire(current);
231
+ store.delete(loopId);
232
+ },
233
+ },
234
+ });
235
+ let taskBacklogCoordinatorStore;
236
+ let taskBacklogCoordinatorWorker = { created: false };
237
+ let taskBacklogCoordinatorCleanupCount = 0;
238
+ const taskBacklogReducerHandler = (incoming) => {
239
+ if (incoming.type !== "TASK_BACKLOG_EVALUATED")
240
+ return [];
241
+ return reduceTaskBacklogEvent(incoming);
242
+ };
243
+ const taskBacklogCoordinator = createCoordinator({
244
+ reducers: [taskBacklogReducerHandler],
245
+ effectHandlers: {
246
+ ENSURE_AUTO_TASK_WORKER: async () => {
247
+ if (!taskBacklogCoordinatorStore)
248
+ return;
249
+ taskBacklogCoordinatorWorker = await ensureAutoTaskWorkerLoop(taskBacklogCoordinatorStore);
250
+ },
251
+ CLEANUP_TASK_BACKLOG_LOOPS: async () => {
252
+ taskBacklogCoordinatorCleanupCount = await cleanupTaskBacklogLoops();
253
+ },
254
+ },
255
+ });
256
+ function applyNotificationEvent(event) {
257
+ const result = reduceNotificationState(notificationState, event);
258
+ notificationState = result.state;
259
+ return result;
260
+ }
261
+ function syncNotificationRuntimeState(options) {
262
+ applyNotificationEvent({
263
+ type: "NOTIFICATION_RUNTIME_UPDATED",
264
+ at: Date.now(),
265
+ source: "system",
266
+ entityType: "notification",
267
+ payload: {
268
+ agentRunning: options?.agentRunning ?? notificationState.agentRunning,
269
+ hasPendingMessages: options?.hasPendingMessages ?? (_latestCtx?.hasPendingMessages() ?? false),
270
+ },
271
+ });
272
+ }
192
273
  function buildLoopFireMessage(data) {
193
274
  const triggerInfo = typeof data.trigger === "string"
194
275
  ? data.trigger
@@ -253,7 +334,7 @@ export default function (pi) {
253
334
  return false;
254
335
  }
255
336
  }
256
- agentRunning = true;
337
+ syncNotificationRuntimeState({ agentRunning: true });
257
338
  pi.sendMessage({
258
339
  customType: "pi-loop",
259
340
  content: notification.message,
@@ -276,16 +357,22 @@ export default function (pi) {
276
357
  if (flushPromise)
277
358
  return flushPromise;
278
359
  flushPromise = (async () => {
279
- if (agentRunning)
280
- return;
281
- if (!options?.ignorePendingMessages && _latestCtx?.hasPendingMessages())
282
- return;
283
- const entries = [...pendingNotifications.entries()]
284
- .sort(([, left], [, right]) => left.timestamp - right.timestamp);
285
- for (const [key, notification] of entries) {
286
- pendingNotifications.delete(key);
287
- const delivered = await deliverNotification(notification);
288
- if (delivered)
360
+ syncNotificationRuntimeState({
361
+ hasPendingMessages: _latestCtx?.hasPendingMessages() ?? false,
362
+ });
363
+ while (true) {
364
+ notificationCoordinatorDelivered = false;
365
+ notificationCoordinatorDeliveredSuccessfully = false;
366
+ await notificationCoordinator.dispatch({
367
+ type: "NOTIFICATION_FLUSH_REQUESTED",
368
+ at: Date.now(),
369
+ source: "system",
370
+ entityType: "notification",
371
+ payload: { ignorePendingMessages: options?.ignorePendingMessages },
372
+ });
373
+ if (!notificationCoordinatorDelivered)
374
+ return;
375
+ if (notificationCoordinatorDeliveredSuccessfully)
289
376
  return;
290
377
  }
291
378
  })().finally(() => {
@@ -295,7 +382,14 @@ export default function (pi) {
295
382
  }
296
383
  async function queueOrDeliverNotification(data) {
297
384
  const notification = buildPendingNotification(data);
298
- pendingNotifications.set(notification.key, notification);
385
+ applyNotificationEvent({
386
+ type: "NOTIFICATION_QUEUED",
387
+ at: notification.timestamp,
388
+ source: "system",
389
+ entityType: "notification",
390
+ entityId: notification.key,
391
+ payload: { notification },
392
+ });
299
393
  await flushPendingNotifications();
300
394
  }
301
395
  // ── Loop fire handler ──
@@ -370,28 +464,40 @@ export default function (pi) {
370
464
  widget.update();
371
465
  });
372
466
  pi.on("agent_start", async (_event, ctx) => {
373
- agentRunning = true;
467
+ syncNotificationRuntimeState({ agentRunning: true, hasPendingMessages: ctx.hasPendingMessages() });
374
468
  _latestCtx = ctx;
375
469
  widget.setUICtx(ctx.ui);
376
470
  });
377
471
  pi.on("agent_end", async (_event, ctx) => {
378
- agentRunning = false;
379
472
  _latestCtx = ctx;
380
473
  widget.setUICtx(ctx.ui);
474
+ syncNotificationRuntimeState({ agentRunning: false, hasPendingMessages: ctx.hasPendingMessages() });
381
475
  await flushPendingNotifications({ ignorePendingMessages: true });
382
476
  await cleanupTaskBacklogLoops();
383
477
  await pumpLoops();
384
478
  });
385
479
  pi.on("session_shutdown", async () => {
386
- agentRunning = false;
387
- pendingNotifications.clear();
480
+ syncNotificationRuntimeState({ agentRunning: false, hasPendingMessages: false });
481
+ applyNotificationEvent({
482
+ type: "NOTIFICATION_CLEARED",
483
+ at: Date.now(),
484
+ source: "session",
485
+ entityType: "notification",
486
+ payload: { reason: "session_shutdown" },
487
+ });
388
488
  });
389
489
  pi.on("session_switch", async (event, ctx) => {
390
490
  _latestCtx = ctx;
391
491
  widget.setUICtx(ctx.ui);
392
492
  triggerSystem.stop();
393
- agentRunning = false;
394
- pendingNotifications.clear();
493
+ syncNotificationRuntimeState({ agentRunning: false, hasPendingMessages: false });
494
+ applyNotificationEvent({
495
+ type: "NOTIFICATION_CLEARED",
496
+ at: Date.now(),
497
+ source: "session",
498
+ entityType: "notification",
499
+ payload: { reason: "session_switch" },
500
+ });
395
501
  _sessionId = undefined;
396
502
  const isResume = event?.reason === "resume";
397
503
  storeUpgraded = false;
@@ -584,12 +690,14 @@ Skip this tool when the task is a one-off check (just do it directly) or when th
584
690
  });
585
691
  function handleMonitorDoneLoop(doneLoop, monitorId) {
586
692
  const deliver = () => {
587
- const current = store.get(doneLoop.id);
588
- if (!current)
589
- return;
590
- debug(`onDone loop #${doneLoop.id} — monitor #${monitorId} completed, delivering directly`);
591
- onLoopFire(current);
592
- store.delete(doneLoop.id);
693
+ void monitorCompletionCoordinator.dispatch({
694
+ type: "MONITOR_ONDONE_TRIGGERED",
695
+ at: Date.now(),
696
+ source: "monitor",
697
+ entityType: "monitor",
698
+ entityId: monitorId,
699
+ payload: { loopId: doneLoop.id, monitorId },
700
+ });
593
701
  };
594
702
  const registered = monitorManager.onComplete(monitorId, deliver);
595
703
  if (registered)
@@ -1012,6 +1120,25 @@ Use MonitorList to find the monitor ID, then stop it with this tool.`,
1012
1120
  widget.update();
1013
1121
  return { entry, created: true };
1014
1122
  }
1123
+ async function evaluateTaskBacklog(taskStore, pendingCount) {
1124
+ const resolvedPending = pendingCount ?? (taskStore ? taskStore.pendingCount() : await hasPendingTasks());
1125
+ taskBacklogCoordinatorStore = taskStore;
1126
+ taskBacklogCoordinatorWorker = { created: false };
1127
+ taskBacklogCoordinatorCleanupCount = 0;
1128
+ await taskBacklogCoordinator.dispatch({
1129
+ type: "TASK_BACKLOG_EVALUATED",
1130
+ at: Date.now(),
1131
+ source: "system",
1132
+ entityType: "task",
1133
+ payload: { pendingCount: resolvedPending, threshold: AUTO_TASK_WORKER_THRESHOLD },
1134
+ });
1135
+ taskBacklogCoordinatorStore = undefined;
1136
+ return {
1137
+ entry: taskBacklogCoordinatorWorker.entry,
1138
+ created: taskBacklogCoordinatorWorker.created,
1139
+ cleaned: taskBacklogCoordinatorCleanupCount,
1140
+ };
1141
+ }
1015
1142
  async function createNativeTaskInteractively(ui) {
1016
1143
  if (!nativeTaskStore) {
1017
1144
  ui.notify("Native tasks are unavailable while pi-tasks is active", "warning");
@@ -1028,11 +1155,11 @@ Use MonitorList to find the monitor ID, then stop it with this tool.`,
1028
1155
  description: entry.description,
1029
1156
  status: entry.status,
1030
1157
  });
1031
- const worker = await ensureAutoTaskWorkerLoop(nativeTaskStore);
1158
+ const backlog = await evaluateTaskBacklog(nativeTaskStore, nativeTaskStore.pendingCount());
1032
1159
  widget.update();
1033
1160
  ui.notify(`Task #${entry.id} created`, "info");
1034
- if (worker.created && worker.entry) {
1035
- ui.notify(`Worker loop #${worker.entry.id} auto-created`, "info");
1161
+ if (backlog.created && backlog.entry) {
1162
+ ui.notify(`Worker loop #${backlog.entry.id} auto-created`, "info");
1036
1163
  }
1037
1164
  }
1038
1165
  async function viewNativeTasks(ui) {
@@ -1093,7 +1220,7 @@ Use MonitorList to find the monitor ID, then stop it with this tool.`,
1093
1220
  ui.notify(`Task #${task.id} reopened`, "info");
1094
1221
  }
1095
1222
  widget.update();
1096
- await cleanupTaskBacklogLoops();
1223
+ await evaluateTaskBacklog(nativeTaskStore, nativeTaskStore.pendingCount());
1097
1224
  return viewNativeTasks(ui);
1098
1225
  }
1099
1226
  // ── Native task tools (only when pi-tasks is absent) ──
@@ -1119,11 +1246,11 @@ Use MonitorList to find the monitor ID, then stop it with this tool.`,
1119
1246
  description: entry.description,
1120
1247
  status: entry.status,
1121
1248
  });
1122
- const worker = await ensureAutoTaskWorkerLoop(nativeTaskStore);
1249
+ const backlog = await evaluateTaskBacklog(nativeTaskStore, nativeTaskStore.pendingCount());
1123
1250
  widget.update();
1124
1251
  ctx.ui.notify(`Task #${entry.id} created`, "info");
1125
- if (worker.created && worker.entry) {
1126
- ctx.ui.notify(`Worker loop #${worker.entry.id} auto-created`, "info");
1252
+ if (backlog.created && backlog.entry) {
1253
+ ctx.ui.notify(`Worker loop #${backlog.entry.id} auto-created`, "info");
1127
1254
  }
1128
1255
  return;
1129
1256
  }
@@ -1156,10 +1283,10 @@ Fields:
1156
1283
  description: entry.description,
1157
1284
  status: entry.status,
1158
1285
  });
1159
- const worker = await ensureAutoTaskWorkerLoop(taskStore);
1286
+ const backlog = await evaluateTaskBacklog(taskStore, taskStore.pendingCount());
1160
1287
  widget.update();
1161
- const autoLoopMsg = worker.created && worker.entry
1162
- ? `\nWorker loop #${worker.entry.id} auto-created`
1288
+ const autoLoopMsg = backlog.created && backlog.entry
1289
+ ? `\nWorker loop #${backlog.entry.id} auto-created`
1163
1290
  : "";
1164
1291
  return Promise.resolve(textResult(`Task #${entry.id} created: ${entry.subject}${autoLoopMsg}`));
1165
1292
  },
@@ -1216,9 +1343,12 @@ Parameters: id (required), status, subject, description`,
1216
1343
  if (!entry)
1217
1344
  return Promise.resolve(textResult(`Task #${id} not found`));
1218
1345
  widget.update();
1219
- await cleanupTaskBacklogLoops();
1346
+ const backlog = await evaluateTaskBacklog(taskStore, taskStore.pendingCount());
1220
1347
  const statusMsg = status ? ` → ${status}` : "";
1221
- return Promise.resolve(textResult(`Task #${id} updated${statusMsg}`));
1348
+ const autoLoopMsg = backlog.created && backlog.entry
1349
+ ? `\nWorker loop #${backlog.entry.id} auto-created`
1350
+ : "";
1351
+ return Promise.resolve(textResult(`Task #${id} updated${statusMsg}${autoLoopMsg}`));
1222
1352
  },
1223
1353
  });
1224
1354
  pi.registerTool({
@@ -1232,7 +1362,7 @@ Parameters: id (required), status, subject, description`,
1232
1362
  const deleted = taskStore.delete(params.id);
1233
1363
  widget.update();
1234
1364
  if (deleted) {
1235
- await cleanupTaskBacklogLoops();
1365
+ await evaluateTaskBacklog(taskStore, taskStore.pendingCount());
1236
1366
  return Promise.resolve(textResult(`Task #${params.id} deleted`));
1237
1367
  }
1238
1368
  return Promise.resolve(textResult(`Task #${params.id} not found`));
@@ -0,0 +1,63 @@
1
+ import type { LoopEntry, Trigger } from "./types.js";
2
+ export declare const MAX_LOOP_EXPIRY_MS: number;
3
+ type ReducerSource = "tool" | "command" | "scheduler" | "eventbus" | "monitor" | "session" | "coordinator" | "system";
4
+ export interface LoopReducerState {
5
+ nextId: number;
6
+ loopsById: Record<string, LoopEntry>;
7
+ }
8
+ export type LoopReducerEvent = {
9
+ type: "LOOP_CREATED";
10
+ at: number;
11
+ source: ReducerSource;
12
+ entityType?: "loop";
13
+ entityId?: string;
14
+ payload: {
15
+ prompt: string;
16
+ trigger: Trigger;
17
+ recurring: boolean;
18
+ autoTask?: boolean;
19
+ taskBacklog?: boolean;
20
+ readOnly?: boolean;
21
+ maxFires?: number;
22
+ };
23
+ } | {
24
+ type: "LOOP_PAUSED" | "LOOP_RESUMED" | "LOOP_FIRED" | "LOOP_DELETED" | "LOOP_MAX_FIRES_REACHED" | "LOOP_BACKLOG_EMPTY";
25
+ at: number;
26
+ source: ReducerSource;
27
+ entityType?: "loop";
28
+ entityId?: string;
29
+ payload: {
30
+ id: string;
31
+ };
32
+ } | {
33
+ type: "LOOP_EXPIRED";
34
+ at: number;
35
+ source: ReducerSource;
36
+ entityType?: "loop";
37
+ entityId?: string;
38
+ payload: {
39
+ id: string;
40
+ reason: "expires_at" | "resume_event_stale" | "already_completed_monitor";
41
+ };
42
+ };
43
+ export type LoopReducerEffect = {
44
+ type: "PERSIST_LOOP";
45
+ entityType: "loop";
46
+ entityId: string;
47
+ payload: {
48
+ loop: LoopEntry;
49
+ };
50
+ } | {
51
+ type: "DELETE_LOOP";
52
+ entityType: "loop";
53
+ entityId: string;
54
+ payload: {
55
+ id: string;
56
+ };
57
+ };
58
+ export interface LoopReduceResult {
59
+ state: LoopReducerState;
60
+ effects: LoopReducerEffect[];
61
+ }
62
+ export declare function reduceLoopState(state: LoopReducerState, event: LoopReducerEvent): LoopReduceResult;
63
+ export {};
@@ -0,0 +1,67 @@
1
+ export const MAX_LOOP_EXPIRY_MS = 7 * 24 * 60 * 60 * 1000;
2
+ function cloneState(state) {
3
+ return {
4
+ nextId: state.nextId,
5
+ loopsById: { ...state.loopsById },
6
+ };
7
+ }
8
+ export function reduceLoopState(state, event) {
9
+ if (event.type === "LOOP_CREATED") {
10
+ const next = cloneState(state);
11
+ const id = String(next.nextId++);
12
+ const loop = {
13
+ id,
14
+ prompt: event.payload.prompt,
15
+ trigger: event.payload.trigger,
16
+ status: "active",
17
+ recurring: event.payload.recurring,
18
+ createdAt: event.at,
19
+ updatedAt: event.at,
20
+ expiresAt: event.at + MAX_LOOP_EXPIRY_MS,
21
+ autoTask: event.payload.autoTask,
22
+ taskBacklog: event.payload.taskBacklog,
23
+ readOnly: event.payload.readOnly,
24
+ maxFires: event.payload.maxFires,
25
+ fireCount: 0,
26
+ };
27
+ next.loopsById[id] = loop;
28
+ return {
29
+ state: next,
30
+ effects: [{ type: "PERSIST_LOOP", entityType: "loop", entityId: id, payload: { loop } }],
31
+ };
32
+ }
33
+ const id = event.payload.id;
34
+ const current = state.loopsById[id];
35
+ if (!current)
36
+ return { state, effects: [] };
37
+ if (event.type === "LOOP_DELETED"
38
+ || event.type === "LOOP_MAX_FIRES_REACHED"
39
+ || event.type === "LOOP_EXPIRED"
40
+ || event.type === "LOOP_BACKLOG_EMPTY") {
41
+ const next = cloneState(state);
42
+ delete next.loopsById[id];
43
+ return {
44
+ state: next,
45
+ effects: [{ type: "DELETE_LOOP", entityType: "loop", entityId: id, payload: { id } }],
46
+ };
47
+ }
48
+ const next = cloneState(state);
49
+ const loop = { ...current };
50
+ if (event.type === "LOOP_PAUSED") {
51
+ loop.status = "paused";
52
+ loop.updatedAt = event.at;
53
+ }
54
+ if (event.type === "LOOP_RESUMED") {
55
+ loop.status = "active";
56
+ loop.updatedAt = event.at;
57
+ }
58
+ if (event.type === "LOOP_FIRED") {
59
+ loop.fireCount = (loop.fireCount ?? 0) + 1;
60
+ loop.updatedAt = event.at;
61
+ }
62
+ next.loopsById[id] = loop;
63
+ return {
64
+ state: next,
65
+ effects: [{ type: "PERSIST_LOOP", entityType: "loop", entityId: id, payload: { loop } }],
66
+ };
67
+ }
@@ -0,0 +1,10 @@
1
+ import type { ReducerEffect, ReducerEvent } from "./coordinator.js";
2
+ export type MonitorCompletionEvent = ReducerEvent<"MONITOR_ONDONE_TRIGGERED", {
3
+ loopId: string;
4
+ monitorId: string;
5
+ }>;
6
+ export type MonitorCompletionEffect = ReducerEffect<"DELIVER_MONITOR_ONDONE_WAKE", {
7
+ loopId: string;
8
+ monitorId: string;
9
+ }>;
10
+ export declare function reduceMonitorCompletionEvent(event: MonitorCompletionEvent): MonitorCompletionEffect[];
@@ -0,0 +1,13 @@
1
+ export function reduceMonitorCompletionEvent(event) {
2
+ if (event.type !== "MONITOR_ONDONE_TRIGGERED")
3
+ return [];
4
+ return [{
5
+ type: "DELIVER_MONITOR_ONDONE_WAKE",
6
+ entityType: "monitor",
7
+ entityId: event.payload.monitorId,
8
+ payload: {
9
+ loopId: event.payload.loopId,
10
+ monitorId: event.payload.monitorId,
11
+ },
12
+ }];
13
+ }
@@ -5,6 +5,8 @@ export declare class MonitorManager {
5
5
  private processes;
6
6
  private nextId;
7
7
  constructor(pi: ExtensionAPI);
8
+ private toReducerState;
9
+ private applyReducerEvent;
8
10
  create(command: string, description?: string, timeout?: number): MonitorEntry;
9
11
  get(id: string): MonitorEntry | undefined;
10
12
  list(): MonitorEntry[];