@trevonistrevon/pi-loop 0.5.7 → 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.
- package/.release-please-manifest.json +3 -0
- package/Dockerfile +17 -0
- package/coverage/coverage-final.json +28 -27
- package/coverage/index.html +47 -47
- package/coverage/src/commands/index.html +15 -15
- package/coverage/src/commands/loop-command.ts.html +3 -3
- package/coverage/src/commands/tasks-command.ts.html +23 -23
- package/coverage/src/coordinator.ts.html +20 -20
- package/coverage/src/goal-coordinator.ts.html +1 -1
- package/coverage/src/goal-reducer.ts.html +1 -1
- package/coverage/src/goal-store.ts.html +1 -1
- package/coverage/src/goal-verifier.ts.html +1 -1
- package/coverage/src/index.html +42 -42
- package/coverage/src/index.ts.html +122 -74
- package/coverage/src/loop-parse.ts.html +61 -61
- package/coverage/src/loop-reducer.ts.html +25 -25
- package/coverage/src/monitor-completion-coordinator.ts.html +3 -3
- package/coverage/src/monitor-manager.ts.html +73 -67
- package/coverage/src/monitor-reducer.ts.html +27 -27
- package/coverage/src/notification-reducer.ts.html +7 -7
- package/coverage/src/reducer-backed-store.ts.html +53 -53
- package/coverage/src/runtime/index.html +66 -51
- package/coverage/src/runtime/monitor-ondone-runtime.ts.html +39 -39
- package/coverage/src/runtime/notification-runtime.ts.html +14 -14
- package/coverage/src/runtime/scope.ts.html +34 -34
- package/coverage/src/runtime/session-runtime.ts.html +63 -57
- package/coverage/src/runtime/task-backlog-runtime.ts.html +48 -48
- package/coverage/src/runtime/task-events.ts.html +208 -0
- package/coverage/src/runtime/task-rpc.ts.html +88 -82
- package/coverage/src/scheduler.ts.html +34 -34
- package/coverage/src/store.ts.html +21 -21
- package/coverage/src/task-backlog-coordinator.ts.html +8 -8
- package/coverage/src/task-reducer.ts.html +39 -39
- package/coverage/src/task-store.ts.html +55 -55
- package/coverage/src/tools/index.html +43 -43
- package/coverage/src/tools/loop-tools.ts.html +94 -94
- package/coverage/src/tools/monitor-tools.ts.html +53 -53
- package/coverage/src/tools/native-task-tools.ts.html +99 -78
- package/coverage/src/trigger-system.ts.html +8 -8
- package/coverage/src/ui/index.html +1 -1
- package/coverage/src/ui/widget.ts.html +5 -5
- package/dist/commands/tasks-command.js +12 -9
- package/dist/index.js +34 -19
- package/dist/monitor-manager.d.ts +4 -1
- package/dist/monitor-manager.js +8 -4
- package/dist/runtime/session-runtime.js +3 -1
- package/dist/runtime/task-events.d.ts +15 -0
- package/dist/runtime/task-events.js +13 -0
- package/dist/runtime/task-rpc.js +2 -0
- package/dist/tools/native-task-tools.js +20 -9
- package/docker-compose.yml +12 -0
- package/package.json +1 -1
- package/release-please-config.json +9 -0
- package/src/commands/tasks-command.ts +9 -9
- package/src/index.ts +37 -21
- package/src/monitor-manager.ts +14 -4
- package/src/runtime/session-runtime.ts +3 -1
- package/src/runtime/task-events.ts +41 -0
- package/src/runtime/task-rpc.ts +2 -0
- package/src/tools/native-task-tools.ts +16 -9
- package/vitest.config.ts +11 -7
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Type } from "typebox";
|
|
2
|
+
import { emitNativeTaskEvent } from "../runtime/task-events.js";
|
|
2
3
|
function textResult(msg) {
|
|
3
4
|
return { content: [{ type: "text", text: msg }], details: undefined };
|
|
4
5
|
}
|
|
@@ -30,12 +31,7 @@ Fields:
|
|
|
30
31
|
}),
|
|
31
32
|
async execute(_toolCallId, params) {
|
|
32
33
|
const entry = taskStore.create(params.subject, params.description);
|
|
33
|
-
pi
|
|
34
|
-
taskId: entry.id,
|
|
35
|
-
subject: entry.subject,
|
|
36
|
-
description: entry.description,
|
|
37
|
-
status: entry.status,
|
|
38
|
-
});
|
|
34
|
+
emitNativeTaskEvent(pi, "tasks:created", entry);
|
|
39
35
|
const backlog = await evaluateTaskBacklog(taskStore, taskStore.pendingCount());
|
|
40
36
|
updateWidget();
|
|
41
37
|
const autoLoopMsg = backlog.created && backlog.entry
|
|
@@ -91,16 +87,28 @@ Parameters: id (required), status, subject, description`,
|
|
|
91
87
|
let entry = taskStore.get(id);
|
|
92
88
|
if (!entry)
|
|
93
89
|
return Promise.resolve(textResult(`Task #${id} not found`));
|
|
94
|
-
|
|
90
|
+
const previousStatus = entry.status;
|
|
91
|
+
if (status === "in_progress") {
|
|
95
92
|
entry = taskStore.start(id);
|
|
96
|
-
|
|
93
|
+
if (entry)
|
|
94
|
+
emitNativeTaskEvent(pi, "tasks:started", entry, previousStatus);
|
|
95
|
+
}
|
|
96
|
+
else if (status === "completed") {
|
|
97
97
|
entry = taskStore.complete(id);
|
|
98
|
-
|
|
98
|
+
if (entry)
|
|
99
|
+
emitNativeTaskEvent(pi, "tasks:completed", entry, previousStatus);
|
|
100
|
+
}
|
|
101
|
+
else if (status === "pending") {
|
|
99
102
|
entry = taskStore.reopen(id);
|
|
103
|
+
if (entry)
|
|
104
|
+
emitNativeTaskEvent(pi, "tasks:reopened", entry, previousStatus);
|
|
105
|
+
}
|
|
100
106
|
if (!entry)
|
|
101
107
|
return Promise.resolve(textResult(`Task #${id} not found`));
|
|
102
108
|
if (subject !== undefined || description !== undefined) {
|
|
103
109
|
entry = taskStore.updateDetails(id, { subject, description });
|
|
110
|
+
if (entry)
|
|
111
|
+
emitNativeTaskEvent(pi, "tasks:updated", entry, previousStatus);
|
|
104
112
|
}
|
|
105
113
|
if (!entry)
|
|
106
114
|
return Promise.resolve(textResult(`Task #${id} not found`));
|
|
@@ -121,9 +129,12 @@ Parameters: id (required), status, subject, description`,
|
|
|
121
129
|
id: Type.String({ description: "Task ID to delete" }),
|
|
122
130
|
}),
|
|
123
131
|
async execute(_toolCallId, params) {
|
|
132
|
+
const existing = taskStore.get(params.id);
|
|
124
133
|
const deleted = taskStore.delete(params.id);
|
|
125
134
|
updateWidget();
|
|
126
135
|
if (deleted) {
|
|
136
|
+
if (existing)
|
|
137
|
+
emitNativeTaskEvent(pi, "tasks:deleted", existing, existing.status);
|
|
127
138
|
await evaluateTaskBacklog(taskStore, taskStore.pendingCount());
|
|
128
139
|
return Promise.resolve(textResult(`Task #${params.id} deleted`));
|
|
129
140
|
}
|
|
@@ -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,4 +1,5 @@
|
|
|
1
1
|
import type { ExtensionAPI, ExtensionCommandContext, ExtensionUIContext } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { emitNativeTaskEvent } from "../runtime/task-events.js";
|
|
2
3
|
import { TaskStore } from "../task-store.js";
|
|
3
4
|
import type { TaskEntry } from "../task-types.js";
|
|
4
5
|
|
|
@@ -18,12 +19,7 @@ export function registerTasksCommand(options: TasksCommandOptions): void {
|
|
|
18
19
|
const { pi, getNativeTaskStore, evaluateTaskBacklog, updateWidget } = options;
|
|
19
20
|
|
|
20
21
|
async function emitCreated(entry: TaskEntry) {
|
|
21
|
-
pi
|
|
22
|
-
taskId: entry.id,
|
|
23
|
-
subject: entry.subject,
|
|
24
|
-
description: entry.description,
|
|
25
|
-
status: entry.status,
|
|
26
|
-
});
|
|
22
|
+
emitNativeTaskEvent(pi, "tasks:created", entry);
|
|
27
23
|
const taskStore = getNativeTaskStore();
|
|
28
24
|
if (!taskStore) return { created: false } satisfies TaskBacklogResult;
|
|
29
25
|
const backlog = await evaluateTaskBacklog(taskStore, taskStore.pendingCount());
|
|
@@ -94,15 +90,19 @@ export function registerTasksCommand(options: TasksCommandOptions): void {
|
|
|
94
90
|
|
|
95
91
|
if (action === "x Delete") {
|
|
96
92
|
taskStore.delete(task.id);
|
|
93
|
+
emitNativeTaskEvent(pi, "tasks:deleted", task, task.status);
|
|
97
94
|
ui.notify(`Task #${task.id} deleted`, "info");
|
|
98
95
|
} else if (action === "> Start") {
|
|
99
|
-
taskStore.start(task.id);
|
|
96
|
+
const next = taskStore.start(task.id);
|
|
97
|
+
if (next) emitNativeTaskEvent(pi, "tasks:started", next, task.status);
|
|
100
98
|
ui.notify(`Task #${task.id} started`, "info");
|
|
101
99
|
} else if (action === "ok Complete") {
|
|
102
|
-
taskStore.complete(task.id);
|
|
100
|
+
const next = taskStore.complete(task.id);
|
|
101
|
+
if (next) emitNativeTaskEvent(pi, "tasks:completed", next, task.status);
|
|
103
102
|
ui.notify(`Task #${task.id} completed`, "info");
|
|
104
103
|
} else if (action === "* Return to pending" || action === "* Reopen") {
|
|
105
|
-
taskStore.reopen(task.id);
|
|
104
|
+
const next = taskStore.reopen(task.id);
|
|
105
|
+
if (next) emitNativeTaskEvent(pi, "tasks:reopened", next, task.status);
|
|
106
106
|
ui.notify(`Task #${task.id} reopened`, "info");
|
|
107
107
|
}
|
|
108
108
|
|
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(
|
|
304
|
+
const nativeTaskFallbackTimer = setTimeout(() => {
|
|
301
305
|
if (tasksAvailable || nativeTasksRegistered) return;
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
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
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
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
|
}
|
package/src/monitor-manager.ts
CHANGED
|
@@ -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(
|
|
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
|
|
@@ -58,7 +66,8 @@ export class MonitorManager {
|
|
|
58
66
|
// finish() and stop() so every terminal status is pruned consistently — a
|
|
59
67
|
// stopped monitor that is never pruned lingers in list() and the widget count.
|
|
60
68
|
private schedulePrune(id: string): void {
|
|
61
|
-
|
|
69
|
+
// unref so a pending prune never keeps a one-shot (`pi -p`) process alive.
|
|
70
|
+
const timer = setTimeout(() => {
|
|
62
71
|
this.applyReducerEvent({
|
|
63
72
|
type: "MONITOR_PRUNED",
|
|
64
73
|
at: Date.now(),
|
|
@@ -68,6 +77,7 @@ export class MonitorManager {
|
|
|
68
77
|
payload: { id },
|
|
69
78
|
});
|
|
70
79
|
}, 30000);
|
|
80
|
+
timer.unref?.();
|
|
71
81
|
}
|
|
72
82
|
|
|
73
83
|
create(command: string, description?: string, timeout = 300000): MonitorEntry {
|
|
@@ -88,7 +98,7 @@ export class MonitorManager {
|
|
|
88
98
|
const entry = result.state.monitorsById[id]!;
|
|
89
99
|
|
|
90
100
|
const abortController = new AbortController();
|
|
91
|
-
const child =
|
|
101
|
+
const child = this.spawnFn("sh", ["-c", command], {
|
|
92
102
|
stdio: ["ignore", "pipe", "pipe"],
|
|
93
103
|
signal: abortController.signal,
|
|
94
104
|
env: { ...process.env },
|
|
@@ -61,7 +61,9 @@ export function registerSessionRuntimeHooks(options: SessionRuntimeOptions): voi
|
|
|
61
61
|
function ensureHeartbeat(): void {
|
|
62
62
|
if (heartbeatTimer) return;
|
|
63
63
|
heartbeatTimer = setInterval(() => {
|
|
64
|
-
|
|
64
|
+
// Swallow pump failures so a transient error never surfaces as an
|
|
65
|
+
// unhandled rejection; the next tick retries.
|
|
66
|
+
void pumpLoops().catch(() => {});
|
|
65
67
|
}, HEARTBEAT_MS);
|
|
66
68
|
heartbeatTimer.unref?.();
|
|
67
69
|
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import type { TaskEntry, TaskStatus } from "../task-types.js";
|
|
3
|
+
|
|
4
|
+
export type NativeTaskEventName =
|
|
5
|
+
| "tasks:created"
|
|
6
|
+
| "tasks:started"
|
|
7
|
+
| "tasks:completed"
|
|
8
|
+
| "tasks:reopened"
|
|
9
|
+
| "tasks:updated"
|
|
10
|
+
| "tasks:deleted";
|
|
11
|
+
|
|
12
|
+
export interface NativeTaskEventPayload {
|
|
13
|
+
taskId: string;
|
|
14
|
+
subject: string;
|
|
15
|
+
description: string;
|
|
16
|
+
status: TaskStatus;
|
|
17
|
+
previousStatus?: TaskStatus;
|
|
18
|
+
createdAt: number;
|
|
19
|
+
updatedAt: number;
|
|
20
|
+
completedAt?: number;
|
|
21
|
+
metadata?: Record<string, unknown>;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function emitNativeTaskEvent(
|
|
25
|
+
pi: ExtensionAPI,
|
|
26
|
+
name: NativeTaskEventName,
|
|
27
|
+
entry: TaskEntry,
|
|
28
|
+
previousStatus?: TaskStatus,
|
|
29
|
+
): void {
|
|
30
|
+
pi.events.emit(name, {
|
|
31
|
+
taskId: entry.id,
|
|
32
|
+
subject: entry.subject,
|
|
33
|
+
description: entry.description,
|
|
34
|
+
status: entry.status,
|
|
35
|
+
previousStatus,
|
|
36
|
+
createdAt: entry.createdAt,
|
|
37
|
+
updatedAt: entry.updatedAt,
|
|
38
|
+
completedAt: entry.completedAt,
|
|
39
|
+
metadata: entry.metadata,
|
|
40
|
+
} satisfies NativeTaskEventPayload);
|
|
41
|
+
}
|
package/src/runtime/task-rpc.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { randomUUID } from "node:crypto";
|
|
|
2
2
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
3
3
|
import type { TaskStore } from "../task-store.js";
|
|
4
4
|
import type { LoopEntry } from "../types.js";
|
|
5
|
+
import { emitNativeTaskEvent } from "./task-events.js";
|
|
5
6
|
|
|
6
7
|
export interface TaskRuntimeBridgeOptions {
|
|
7
8
|
pi: ExtensionAPI;
|
|
@@ -82,6 +83,7 @@ export function createTaskRuntimeBridge(options: TaskRuntimeBridgeOptions): Task
|
|
|
82
83
|
`Auto-created from loop #${entry.id}`,
|
|
83
84
|
{ loopId: entry.id, trigger: entry.trigger },
|
|
84
85
|
);
|
|
86
|
+
emitNativeTaskEvent(pi, "tasks:created", task);
|
|
85
87
|
onNativeTaskCreated?.(nativeTaskStore);
|
|
86
88
|
return task.id;
|
|
87
89
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import { Type } from "typebox";
|
|
3
|
+
import { emitNativeTaskEvent } from "../runtime/task-events.js";
|
|
3
4
|
import { TaskStore } from "../task-store.js";
|
|
4
5
|
|
|
5
6
|
export interface TaskBacklogResult {
|
|
@@ -47,12 +48,7 @@ Fields:
|
|
|
47
48
|
}),
|
|
48
49
|
async execute(_toolCallId, params) {
|
|
49
50
|
const entry = taskStore.create(params.subject, params.description);
|
|
50
|
-
pi
|
|
51
|
-
taskId: entry.id,
|
|
52
|
-
subject: entry.subject,
|
|
53
|
-
description: entry.description,
|
|
54
|
-
status: entry.status,
|
|
55
|
-
});
|
|
51
|
+
emitNativeTaskEvent(pi, "tasks:created", entry);
|
|
56
52
|
const backlog = await evaluateTaskBacklog(taskStore, taskStore.pendingCount());
|
|
57
53
|
updateWidget();
|
|
58
54
|
|
|
@@ -111,13 +107,22 @@ Parameters: id (required), status, subject, description`,
|
|
|
111
107
|
let entry = taskStore.get(id);
|
|
112
108
|
if (!entry) return Promise.resolve(textResult(`Task #${id} not found`));
|
|
113
109
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
110
|
+
const previousStatus = entry.status;
|
|
111
|
+
if (status === "in_progress") {
|
|
112
|
+
entry = taskStore.start(id);
|
|
113
|
+
if (entry) emitNativeTaskEvent(pi, "tasks:started", entry, previousStatus);
|
|
114
|
+
} else if (status === "completed") {
|
|
115
|
+
entry = taskStore.complete(id);
|
|
116
|
+
if (entry) emitNativeTaskEvent(pi, "tasks:completed", entry, previousStatus);
|
|
117
|
+
} else if (status === "pending") {
|
|
118
|
+
entry = taskStore.reopen(id);
|
|
119
|
+
if (entry) emitNativeTaskEvent(pi, "tasks:reopened", entry, previousStatus);
|
|
120
|
+
}
|
|
117
121
|
|
|
118
122
|
if (!entry) return Promise.resolve(textResult(`Task #${id} not found`));
|
|
119
123
|
if (subject !== undefined || description !== undefined) {
|
|
120
124
|
entry = taskStore.updateDetails(id, { subject, description });
|
|
125
|
+
if (entry) emitNativeTaskEvent(pi, "tasks:updated", entry, previousStatus);
|
|
121
126
|
}
|
|
122
127
|
if (!entry) return Promise.resolve(textResult(`Task #${id} not found`));
|
|
123
128
|
updateWidget();
|
|
@@ -138,9 +143,11 @@ Parameters: id (required), status, subject, description`,
|
|
|
138
143
|
id: Type.String({ description: "Task ID to delete" }),
|
|
139
144
|
}),
|
|
140
145
|
async execute(_toolCallId, params) {
|
|
146
|
+
const existing = taskStore.get(params.id);
|
|
141
147
|
const deleted = taskStore.delete(params.id);
|
|
142
148
|
updateWidget();
|
|
143
149
|
if (deleted) {
|
|
150
|
+
if (existing) emitNativeTaskEvent(pi, "tasks:deleted", existing, existing.status);
|
|
144
151
|
await evaluateTaskBacklog(taskStore, taskStore.pendingCount());
|
|
145
152
|
return Promise.resolve(textResult(`Task #${params.id} deleted`));
|
|
146
153
|
}
|
package/vitest.config.ts
CHANGED
|
@@ -5,20 +5,24 @@ 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"],
|
|
11
15
|
include: ["src/**/*.ts"],
|
|
12
16
|
// Type-only modules and test helpers carry no executable logic worth gating.
|
|
13
17
|
exclude: ["src/**/*-types.ts", "src/types.ts"],
|
|
14
|
-
// Floors set just below current actuals (stmts
|
|
15
|
-
// funcs
|
|
16
|
-
// runtime/
|
|
18
|
+
// Floors set just below current actuals (stmts 84%, branches 75%,
|
|
19
|
+
// funcs 95%, lines 86%) to catch regressions. Raised in Phase 4 after the
|
|
20
|
+
// runtime/ + tools/ suites landed.
|
|
17
21
|
thresholds: {
|
|
18
|
-
statements:
|
|
19
|
-
branches:
|
|
20
|
-
functions:
|
|
21
|
-
lines:
|
|
22
|
+
statements: 82,
|
|
23
|
+
branches: 73,
|
|
24
|
+
functions: 94,
|
|
25
|
+
lines: 84,
|
|
22
26
|
},
|
|
23
27
|
},
|
|
24
28
|
},
|