@trevonistrevon/pi-loop 0.5.0 → 0.5.2

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 (81) hide show
  1. package/README.md +11 -10
  2. package/dist/commands/loop-command.d.ts +22 -0
  3. package/dist/commands/loop-command.js +148 -0
  4. package/dist/commands/tasks-command.d.ts +15 -0
  5. package/dist/commands/tasks-command.js +117 -0
  6. package/dist/goal-coordinator.d.ts +22 -0
  7. package/dist/goal-coordinator.js +28 -0
  8. package/dist/goal-reducer.d.ts +89 -0
  9. package/dist/goal-reducer.js +181 -0
  10. package/dist/goal-store.d.ts +33 -0
  11. package/dist/goal-store.js +310 -0
  12. package/dist/goal-types.d.ts +4 -0
  13. package/dist/index.js +125 -1212
  14. package/dist/runtime/monitor-ondone-runtime.d.ts +13 -0
  15. package/dist/runtime/monitor-ondone-runtime.js +49 -0
  16. package/dist/runtime/notification-runtime.d.ts +34 -0
  17. package/dist/runtime/notification-runtime.js +152 -0
  18. package/dist/runtime/scope.d.ts +8 -0
  19. package/dist/runtime/scope.js +33 -0
  20. package/dist/runtime/session-runtime.d.ts +39 -0
  21. package/dist/runtime/session-runtime.js +110 -0
  22. package/dist/runtime/task-backlog-runtime.d.ts +36 -0
  23. package/dist/runtime/task-backlog-runtime.js +105 -0
  24. package/dist/runtime/task-rpc.d.ts +19 -0
  25. package/dist/runtime/task-rpc.js +118 -0
  26. package/dist/store.d.ts +7 -4
  27. package/dist/store.js +66 -48
  28. package/dist/task-store.d.ts +8 -4
  29. package/dist/task-store.js +80 -51
  30. package/dist/tools/loop-tools.d.ts +41 -0
  31. package/dist/tools/loop-tools.js +241 -0
  32. package/dist/tools/monitor-tools.d.ts +25 -0
  33. package/dist/tools/monitor-tools.js +110 -0
  34. package/dist/tools/native-task-tools.d.ts +15 -0
  35. package/dist/tools/native-task-tools.js +130 -0
  36. package/dist/ui/widget.d.ts +0 -1
  37. package/dist/ui/widget.js +1 -14
  38. package/package.json +1 -1
  39. package/src/commands/loop-command.ts +184 -0
  40. package/src/commands/tasks-command.ts +135 -0
  41. package/src/goal-coordinator.ts +58 -0
  42. package/src/goal-reducer.ts +280 -0
  43. package/src/goal-store.ts +326 -0
  44. package/src/goal-types.ts +5 -0
  45. package/src/index.ts +129 -1299
  46. package/src/runtime/monitor-ondone-runtime.ts +75 -0
  47. package/src/runtime/notification-runtime.ts +212 -0
  48. package/src/runtime/scope.ts +37 -0
  49. package/src/runtime/session-runtime.ts +168 -0
  50. package/src/runtime/task-backlog-runtime.ts +163 -0
  51. package/src/runtime/task-rpc.ts +150 -0
  52. package/src/store.ts +66 -49
  53. package/src/task-store.ts +76 -50
  54. package/src/tools/loop-tools.ts +304 -0
  55. package/src/tools/monitor-tools.ts +145 -0
  56. package/src/tools/native-task-tools.ts +147 -0
  57. package/src/ui/widget.ts +1 -16
  58. package/tmp/perf-cap-data/loops.json +455 -0
  59. package/tmp/perf-cap-data/tasks.json +1 -0
  60. package/tmp/perf-data/loops-0.json +1 -0
  61. package/tmp/perf-data/loops-10.json +1 -0
  62. package/tmp/perf-data/loops-100.json +1 -0
  63. package/tmp/perf-data/loops-1000.json +1 -0
  64. package/tmp/perf-data/loops-500.json +1 -0
  65. package/tmp/perf-data/tasks-0.json +1 -0
  66. package/tmp/perf-data/tasks-10.json +1 -0
  67. package/tmp/perf-data/tasks-100.json +1 -0
  68. package/tmp/perf-data/tasks-1000.json +1 -0
  69. package/tmp/perf-data/tasks-500.json +1 -0
  70. package/tmp/perf-data-session/loops-0.json +4 -0
  71. package/tmp/perf-data-session/loops-100.json +1805 -0
  72. package/tmp/perf-data-session/loops-1000.json +18005 -0
  73. package/tmp/perf-data-session/loops-25.json +455 -0
  74. package/tmp/perf-data-session/loops-500.json +9005 -0
  75. package/tmp/perf-data-session/tasks-0.json +1 -0
  76. package/tmp/perf-data-session/tasks-100.json +1 -0
  77. package/tmp/perf-data-session/tasks-1000.json +1 -0
  78. package/tmp/perf-data-session/tasks-25.json +1 -0
  79. package/tmp/perf-data-session/tasks-500.json +1 -0
  80. package/tmp/perf-session-init.js +98 -0
  81. package/tmp/perf-startup-audit.js +80 -0
@@ -0,0 +1,150 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
3
+ import type { TaskStore } from "../task-store.js";
4
+ import type { LoopEntry } from "../types.js";
5
+
6
+ export interface TaskRuntimeBridgeOptions {
7
+ pi: ExtensionAPI;
8
+ isTasksAvailable: () => boolean;
9
+ setTasksAvailable: (available: boolean) => void;
10
+ getNativeTaskStore: () => TaskStore | undefined;
11
+ onNativeTaskCreated?: (taskStore: TaskStore) => void;
12
+ onNativeTasksPruned?: (taskStore: TaskStore) => Promise<void> | void;
13
+ debug?: (...args: unknown[]) => void;
14
+ }
15
+
16
+ export interface TaskRuntimeBridge {
17
+ checkTasksVersion(): void;
18
+ autoCreateTask(entry: LoopEntry): Promise<string | undefined>;
19
+ hasPendingTasks(): Promise<number>;
20
+ cleanDoneTasks(): Promise<void>;
21
+ }
22
+
23
+ export function createTaskRuntimeBridge(options: TaskRuntimeBridgeOptions): TaskRuntimeBridge {
24
+ const {
25
+ pi,
26
+ isTasksAvailable,
27
+ setTasksAvailable,
28
+ getNativeTaskStore,
29
+ onNativeTaskCreated,
30
+ onNativeTasksPruned,
31
+ debug,
32
+ } = options;
33
+
34
+ function checkTasksVersion() {
35
+ const requestId = randomUUID();
36
+ const timer = setTimeout(() => {
37
+ unsub();
38
+ }, 5000);
39
+ const unsub = pi.events.on(`tasks:rpc:ping:reply:${requestId}`, (raw: unknown) => {
40
+ unsub();
41
+ clearTimeout(timer);
42
+ const remoteVersion = (raw as { data?: { version?: number } })?.data?.version;
43
+ if (remoteVersion !== undefined) setTasksAvailable(true);
44
+ });
45
+ pi.events.emit("tasks:rpc:ping", { requestId });
46
+ }
47
+
48
+ async function autoCreateTask(entry: LoopEntry): Promise<string | undefined> {
49
+ if (!entry.autoTask) return undefined;
50
+ if (isTasksAvailable()) {
51
+ try {
52
+ const requestId = randomUUID();
53
+ const taskId = await new Promise<string | undefined>((resolve) => {
54
+ const timer = setTimeout(() => {
55
+ unsub();
56
+ resolve(undefined);
57
+ }, 5000);
58
+ const unsub = pi.events.on(`tasks:rpc:create:reply:${requestId}`, (raw: unknown) => {
59
+ unsub();
60
+ clearTimeout(timer);
61
+ const reply = raw as { success: boolean; data?: { id: string } };
62
+ if (reply.success && reply.data) resolve(reply.data.id);
63
+ else resolve(undefined);
64
+ });
65
+ pi.events.emit("tasks:rpc:create", {
66
+ requestId,
67
+ subject: entry.prompt.slice(0, 80),
68
+ description: `Auto-created from loop #${entry.id}`,
69
+ metadata: { loopId: entry.id, trigger: entry.trigger },
70
+ });
71
+ });
72
+ return taskId;
73
+ } catch {
74
+ return undefined;
75
+ }
76
+ }
77
+
78
+ const nativeTaskStore = getNativeTaskStore();
79
+ if (!nativeTaskStore) return undefined;
80
+ const task = nativeTaskStore.create(
81
+ entry.prompt.slice(0, 80),
82
+ `Auto-created from loop #${entry.id}`,
83
+ { loopId: entry.id, trigger: entry.trigger },
84
+ );
85
+ onNativeTaskCreated?.(nativeTaskStore);
86
+ return task.id;
87
+ }
88
+
89
+ async function hasPendingTasks(): Promise<number> {
90
+ if (isTasksAvailable()) {
91
+ try {
92
+ const requestId = randomUUID();
93
+ const count = await new Promise<number>((resolve) => {
94
+ const timer = setTimeout(() => {
95
+ unsub();
96
+ resolve(-1);
97
+ }, 3000);
98
+ const unsub = pi.events.on(`tasks:rpc:pending:reply:${requestId}`, (raw: unknown) => {
99
+ unsub();
100
+ clearTimeout(timer);
101
+ const reply = raw as { success: boolean; data?: { pending: number } };
102
+ resolve(reply.success && reply.data ? reply.data.pending : -1);
103
+ });
104
+ pi.events.emit("tasks:rpc:pending", { requestId });
105
+ });
106
+ return count;
107
+ } catch {
108
+ return -1;
109
+ }
110
+ }
111
+
112
+ return getNativeTaskStore()?.pendingCount() ?? -1;
113
+ }
114
+
115
+ async function cleanDoneTasks(): Promise<void> {
116
+ if (isTasksAvailable()) {
117
+ try {
118
+ const requestId = randomUUID();
119
+ await new Promise<void>((resolve) => {
120
+ const timer = setTimeout(() => {
121
+ unsub();
122
+ resolve();
123
+ }, 3000);
124
+ const unsub = pi.events.on(`tasks:rpc:clean:reply:${requestId}`, () => {
125
+ unsub();
126
+ clearTimeout(timer);
127
+ debug?.("tasks:rpc:clean — done tasks swept");
128
+ resolve();
129
+ });
130
+ pi.events.emit("tasks:rpc:clean", { requestId });
131
+ });
132
+ } catch {
133
+ // timeout or error, ignore
134
+ }
135
+ return;
136
+ }
137
+
138
+ const nativeTaskStore = getNativeTaskStore();
139
+ if (!nativeTaskStore) return;
140
+ nativeTaskStore.pruneCompleted();
141
+ await onNativeTasksPruned?.(nativeTaskStore);
142
+ }
143
+
144
+ return {
145
+ checkTasksVersion,
146
+ autoCreateTask,
147
+ hasPendingTasks,
148
+ cleanDoneTasks,
149
+ };
150
+ }
package/src/store.ts CHANGED
@@ -1,8 +1,8 @@
1
- import { existsSync, mkdirSync, readFileSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
1
+ import { existsSync, mkdirSync, readFileSync, renameSync, statSync, unlinkSync, writeFileSync } from "node:fs";
2
2
  import { homedir } from "node:os";
3
3
  import { dirname, isAbsolute, join } from "node:path";
4
4
  import { type LoopReducerEvent, type LoopReducerState, reduceLoopState } from "./loop-reducer.js";
5
- import type { LoopEntry, LoopStatus, LoopStoreData, Trigger } from "./types.js";
5
+ import type { LoopEntry, LoopStoreData, Trigger } from "./types.js";
6
6
 
7
7
  const LOOPS_DIR = join(homedir(), ".pi", "loops");
8
8
  const LOCK_RETRY_MS = 50;
@@ -44,6 +44,7 @@ function isProcessRunning(pid: number): boolean {
44
44
  export class LoopStore {
45
45
  private filePath: string | undefined;
46
46
  private lockPath: string | undefined;
47
+ private lastLoadedSignature: string | undefined;
47
48
 
48
49
  private nextId = 1;
49
50
  private loops = new Map<string, LoopEntry>();
@@ -58,9 +59,17 @@ export class LoopStore {
58
59
  this.load();
59
60
  }
60
61
 
61
- private load(): void {
62
+ private getFileSignature(): string | undefined {
63
+ if (!this.filePath || !existsSync(this.filePath)) return undefined;
64
+ const stat = statSync(this.filePath);
65
+ return `${stat.mtimeMs}:${stat.size}`;
66
+ }
67
+
68
+ private load(force = false): void {
62
69
  if (!this.filePath) return;
63
- if (!existsSync(this.filePath)) return;
70
+ const signature = this.getFileSignature();
71
+ if (!signature) return;
72
+ if (!force && signature === this.lastLoadedSignature) return;
64
73
  try {
65
74
  const data: LoopStoreData = JSON.parse(readFileSync(this.filePath, "utf-8"));
66
75
  this.nextId = data.nextId;
@@ -68,6 +77,7 @@ export class LoopStore {
68
77
  for (const loop of data.loops) {
69
78
  this.loops.set(loop.id, loop);
70
79
  }
80
+ this.lastLoadedSignature = signature;
71
81
  } catch { /* corrupt file — start fresh */ }
72
82
  }
73
83
 
@@ -80,13 +90,14 @@ export class LoopStore {
80
90
  const tmpPath = this.filePath + ".tmp";
81
91
  writeFileSync(tmpPath, JSON.stringify(data, null, 2));
82
92
  renameSync(tmpPath, this.filePath);
93
+ this.lastLoadedSignature = this.getFileSignature();
83
94
  }
84
95
 
85
96
  private withLock<T>(fn: () => T): T {
86
97
  if (!this.lockPath) return fn();
87
98
  acquireLock(this.lockPath);
88
99
  try {
89
- this.load();
100
+ this.load(true);
90
101
  const result = fn();
91
102
  this.save();
92
103
  return result;
@@ -143,38 +154,61 @@ export class LoopStore {
143
154
  return Array.from(this.loops.values()).sort((a, b) => Number(a.id) - Number(b.id));
144
155
  }
145
156
 
146
- update(id: string, fields: { status?: LoopStatus; trigger?: Trigger; prompt?: string; fireCount?: number }): { entry: LoopEntry | undefined; changedFields: string[] } {
157
+ pause(id: string): LoopEntry | undefined {
147
158
  return this.withLock(() => {
148
159
  const entry = this.loops.get(id);
149
- if (!entry) return { entry: undefined, changedFields: [] };
160
+ if (!entry) return undefined;
161
+ this.applyReducerEvent({
162
+ type: "LOOP_PAUSED",
163
+ at: Date.now(),
164
+ source: "tool",
165
+ entityType: "loop",
166
+ entityId: id,
167
+ payload: { id },
168
+ });
169
+ return this.loops.get(id);
170
+ });
171
+ }
150
172
 
151
- const changedFields: string[] = [];
152
- const now = Date.now();
173
+ resume(id: string): LoopEntry | undefined {
174
+ return this.withLock(() => {
175
+ const entry = this.loops.get(id);
176
+ if (!entry) return undefined;
177
+ this.applyReducerEvent({
178
+ type: "LOOP_RESUMED",
179
+ at: Date.now(),
180
+ source: "tool",
181
+ entityType: "loop",
182
+ entityId: id,
183
+ payload: { id },
184
+ });
185
+ return this.loops.get(id);
186
+ });
187
+ }
153
188
 
154
- if (fields.status === "paused") {
155
- this.applyReducerEvent({
156
- type: "LOOP_PAUSED",
157
- at: now,
158
- source: "tool",
159
- entityType: "loop",
160
- entityId: id,
161
- payload: { id },
162
- });
163
- changedFields.push("status");
164
- } else if (fields.status === "active") {
165
- this.applyReducerEvent({
166
- type: "LOOP_RESUMED",
167
- at: now,
168
- source: "tool",
169
- entityType: "loop",
170
- entityId: id,
171
- payload: { id },
172
- });
173
- changedFields.push("status");
174
- }
189
+ fire(id: string): LoopEntry | undefined {
190
+ return this.withLock(() => {
191
+ const entry = this.loops.get(id);
192
+ if (!entry) return undefined;
193
+ this.applyReducerEvent({
194
+ type: "LOOP_FIRED",
195
+ at: Date.now(),
196
+ source: "system",
197
+ entityType: "loop",
198
+ entityId: id,
199
+ payload: { id },
200
+ });
201
+ return this.loops.get(id);
202
+ });
203
+ }
175
204
 
205
+ updateMetadata(id: string, fields: { trigger?: Trigger; prompt?: string }): { entry: LoopEntry | undefined; changedFields: string[] } {
206
+ return this.withLock(() => {
176
207
  const current = this.loops.get(id);
177
- if (!current) return { entry: undefined, changedFields };
208
+ if (!current) return { entry: undefined, changedFields: [] };
209
+
210
+ const changedFields: string[] = [];
211
+ const now = Date.now();
178
212
 
179
213
  if (fields.trigger !== undefined) {
180
214
  current.trigger = fields.trigger;
@@ -184,24 +218,7 @@ export class LoopStore {
184
218
  current.prompt = fields.prompt;
185
219
  changedFields.push("prompt");
186
220
  }
187
- if (fields.fireCount !== undefined) {
188
- if (fields.fireCount === (current.fireCount ?? 0) + 1) {
189
- this.applyReducerEvent({
190
- type: "LOOP_FIRED",
191
- at: now,
192
- source: "system",
193
- entityType: "loop",
194
- entityId: id,
195
- payload: { id },
196
- });
197
- } else {
198
- current.fireCount = fields.fireCount;
199
- current.updatedAt = now;
200
- }
201
- changedFields.push("fireCount");
202
- }
203
-
204
- if (fields.trigger !== undefined || fields.prompt !== undefined) {
221
+ if (changedFields.length > 0) {
205
222
  current.updatedAt = now;
206
223
  }
207
224
 
package/src/task-store.ts CHANGED
@@ -1,8 +1,8 @@
1
- import { existsSync, mkdirSync, readFileSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
1
+ import { existsSync, mkdirSync, readFileSync, renameSync, statSync, unlinkSync, writeFileSync } from "node:fs";
2
2
  import { homedir } from "node:os";
3
3
  import { dirname, isAbsolute, join } from "node:path";
4
4
  import { reduceTaskState, type TaskReducerEvent, type TaskReducerState } from "./task-reducer.js";
5
- import type { TaskEntry, TaskStatus, TaskStoreData } from "./task-types.js";
5
+ import type { TaskEntry, TaskStoreData } from "./task-types.js";
6
6
 
7
7
  const TASKS_DIR = join(homedir(), ".pi", "tasks");
8
8
  const LOCK_RETRY_MS = 50;
@@ -44,6 +44,7 @@ function isProcessRunning(pid: number): boolean {
44
44
  export class TaskStore {
45
45
  private filePath: string | undefined;
46
46
  private lockPath: string | undefined;
47
+ private lastLoadedSignature: string | undefined;
47
48
 
48
49
  private nextId = 1;
49
50
  private tasks = new Map<string, TaskEntry>();
@@ -58,9 +59,17 @@ export class TaskStore {
58
59
  this.load();
59
60
  }
60
61
 
61
- private load(): void {
62
+ private getFileSignature(): string | undefined {
63
+ if (!this.filePath || !existsSync(this.filePath)) return undefined;
64
+ const stat = statSync(this.filePath);
65
+ return `${stat.mtimeMs}:${stat.size}`;
66
+ }
67
+
68
+ private load(force = false): void {
62
69
  if (!this.filePath) return;
63
- if (!existsSync(this.filePath)) return;
70
+ const signature = this.getFileSignature();
71
+ if (!signature) return;
72
+ if (!force && signature === this.lastLoadedSignature) return;
64
73
  try {
65
74
  const data: TaskStoreData = JSON.parse(readFileSync(this.filePath, "utf-8"));
66
75
  this.nextId = data.nextId;
@@ -68,6 +77,7 @@ export class TaskStore {
68
77
  for (const task of data.tasks) {
69
78
  this.tasks.set(task.id, task);
70
79
  }
80
+ this.lastLoadedSignature = signature;
71
81
  } catch { /* corrupt file — start fresh */ }
72
82
  }
73
83
 
@@ -80,13 +90,14 @@ export class TaskStore {
80
90
  const tmpPath = this.filePath + ".tmp";
81
91
  writeFileSync(tmpPath, JSON.stringify(data, null, 2));
82
92
  renameSync(tmpPath, this.filePath);
93
+ this.lastLoadedSignature = this.getFileSignature();
83
94
  }
84
95
 
85
96
  private withLock<T>(fn: () => T): T {
86
97
  if (!this.lockPath) return fn();
87
98
  acquireLock(this.lockPath);
88
99
  try {
89
- this.load();
100
+ this.load(true);
90
101
  const result = fn();
91
102
  this.save();
92
103
  return result;
@@ -135,56 +146,71 @@ export class TaskStore {
135
146
  return Array.from(this.tasks.values()).sort((a, b) => Number(a.id) - Number(b.id));
136
147
  }
137
148
 
138
- update(id: string, fields: { status?: TaskStatus; subject?: string; description?: string }): TaskEntry | undefined {
149
+ start(id: string): TaskEntry | undefined {
139
150
  return this.withLock(() => {
140
151
  const entry = this.tasks.get(id);
141
152
  if (!entry) return undefined;
153
+ this.applyReducerEvent({
154
+ type: "TASK_STARTED",
155
+ at: Date.now(),
156
+ source: "tool",
157
+ entityType: "task",
158
+ entityId: id,
159
+ payload: { id },
160
+ });
161
+ return this.tasks.get(id);
162
+ });
163
+ }
142
164
 
143
- const now = Date.now();
144
- if (fields.status === "in_progress") {
145
- this.applyReducerEvent({
146
- type: "TASK_STARTED",
147
- at: now,
148
- source: "tool",
149
- entityType: "task",
150
- entityId: id,
151
- payload: { id },
152
- });
153
- } else if (fields.status === "completed") {
154
- this.applyReducerEvent({
155
- type: "TASK_COMPLETED",
156
- at: now,
157
- source: "tool",
158
- entityType: "task",
159
- entityId: id,
160
- payload: { id },
161
- });
162
- } else if (fields.status === "pending") {
163
- this.applyReducerEvent({
164
- type: "TASK_REOPENED",
165
- at: now,
166
- source: "tool",
167
- entityType: "task",
168
- entityId: id,
169
- payload: { id },
170
- });
171
- }
165
+ complete(id: string): TaskEntry | undefined {
166
+ return this.withLock(() => {
167
+ const entry = this.tasks.get(id);
168
+ if (!entry) return undefined;
169
+ this.applyReducerEvent({
170
+ type: "TASK_COMPLETED",
171
+ at: Date.now(),
172
+ source: "tool",
173
+ entityType: "task",
174
+ entityId: id,
175
+ payload: { id },
176
+ });
177
+ return this.tasks.get(id);
178
+ });
179
+ }
172
180
 
173
- if (fields.subject !== undefined || fields.description !== undefined) {
174
- this.applyReducerEvent({
175
- type: "TASK_UPDATED",
176
- at: now,
177
- source: "tool",
178
- entityType: "task",
179
- entityId: id,
180
- payload: {
181
- id,
182
- subject: fields.subject,
183
- description: fields.description,
184
- },
185
- });
186
- }
181
+ reopen(id: string): TaskEntry | undefined {
182
+ return this.withLock(() => {
183
+ const entry = this.tasks.get(id);
184
+ if (!entry) return undefined;
185
+ this.applyReducerEvent({
186
+ type: "TASK_REOPENED",
187
+ at: Date.now(),
188
+ source: "tool",
189
+ entityType: "task",
190
+ entityId: id,
191
+ payload: { id },
192
+ });
193
+ return this.tasks.get(id);
194
+ });
195
+ }
187
196
 
197
+ updateDetails(id: string, fields: { subject?: string; description?: string }): TaskEntry | undefined {
198
+ return this.withLock(() => {
199
+ const entry = this.tasks.get(id);
200
+ if (!entry) return undefined;
201
+ if (fields.subject === undefined && fields.description === undefined) return entry;
202
+ this.applyReducerEvent({
203
+ type: "TASK_UPDATED",
204
+ at: Date.now(),
205
+ source: "tool",
206
+ entityType: "task",
207
+ entityId: id,
208
+ payload: {
209
+ id,
210
+ subject: fields.subject,
211
+ description: fields.description,
212
+ },
213
+ });
188
214
  return this.tasks.get(id);
189
215
  });
190
216
  }
@@ -212,7 +238,7 @@ export class TaskStore {
212
238
  return count;
213
239
  }
214
240
 
215
- sweepCompleted(): number {
241
+ pruneCompleted(): number {
216
242
  return this.withLock(() => {
217
243
  const before = this.tasks.size;
218
244
  this.applyReducerEvent({