@trevonistrevon/pi-loop 0.5.8 → 0.6.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.
- package/.release-please-manifest.json +3 -0
- package/AGENTS.md +52 -12
- package/CHANGELOG.md +59 -0
- package/CONTRIBUTING.md +13 -3
- package/Dockerfile +17 -0
- package/README.md +47 -0
- package/coverage/coverage-final.json +35 -31
- package/coverage/index.html +64 -49
- package/coverage/src/api.ts.html +193 -0
- package/coverage/src/commands/index.html +32 -32
- package/coverage/src/commands/loop-command.ts.html +170 -170
- package/coverage/src/commands/tasks-command.ts.html +135 -135
- package/coverage/src/coordinator.ts.html +26 -26
- package/coverage/src/index.html +61 -106
- package/coverage/src/index.ts.html +280 -94
- package/coverage/src/loop-parse.ts.html +90 -57
- package/coverage/src/loop-reducer.ts.html +25 -25
- package/coverage/src/monitor-completion-coordinator.ts.html +1 -1
- package/coverage/src/monitor-manager.ts.html +95 -71
- package/coverage/src/monitor-reducer.ts.html +15 -15
- package/coverage/src/notification-reducer.ts.html +7 -7
- package/coverage/src/reducer-backed-store.ts.html +57 -57
- package/coverage/src/rpc/channels.ts.html +376 -0
- package/coverage/src/rpc/cross-extension-rpc.ts.html +541 -0
- package/coverage/src/rpc/index.html +131 -0
- package/coverage/src/runtime/index.html +89 -29
- package/coverage/src/{goal-coordinator.ts.html → runtime/loop-events.ts.html} +79 -73
- package/coverage/src/runtime/monitor-ondone-runtime.ts.html +5 -5
- package/coverage/src/runtime/native-task-rpc.ts.html +502 -0
- package/coverage/src/runtime/notification-runtime.ts.html +14 -14
- package/coverage/src/runtime/scope.ts.html +17 -17
- package/coverage/src/runtime/session-runtime.ts.html +40 -40
- package/coverage/src/runtime/task-backlog-runtime.ts.html +66 -24
- package/coverage/src/runtime/task-events.ts.html +208 -0
- package/coverage/src/runtime/task-mutations.ts.html +394 -0
- package/coverage/src/runtime/task-rpc.ts.html +106 -118
- package/coverage/src/scheduler.ts.html +12 -12
- package/coverage/src/store.ts.html +20 -20
- package/coverage/src/task-backlog-coordinator.ts.html +8 -8
- package/coverage/src/task-reducer.ts.html +42 -42
- package/coverage/src/task-store.ts.html +49 -49
- package/coverage/src/tools/index.html +39 -24
- package/coverage/src/tools/loop-tools.ts.html +12 -21
- package/coverage/src/tools/monitor-tools.ts.html +12 -21
- package/coverage/src/tools/native-task-tools.ts.html +52 -94
- package/coverage/src/tools/tool-result.ts.html +97 -0
- package/coverage/src/trigger-system.ts.html +12 -12
- package/coverage/src/ui/index.html +1 -1
- package/coverage/src/ui/widget.ts.html +23 -23
- package/dist/api.d.ts +11 -0
- package/dist/api.js +10 -0
- package/dist/commands/loop-command.js +2 -2
- package/dist/commands/tasks-command.js +3 -3
- package/dist/coordinator.d.ts +1 -1
- package/dist/index.js +80 -20
- package/dist/loop-parse.js +14 -5
- package/dist/monitor-manager.d.ts +4 -1
- package/dist/monitor-manager.js +5 -3
- package/dist/reducer-backed-store.d.ts +1 -1
- package/dist/reducer-backed-store.js +1 -1
- package/dist/rpc/channels.d.ts +73 -0
- package/dist/rpc/channels.js +30 -0
- package/dist/rpc/cross-extension-rpc.d.ts +59 -0
- package/dist/rpc/cross-extension-rpc.js +117 -0
- package/dist/runtime/loop-events.d.ts +26 -0
- package/dist/runtime/loop-events.js +26 -0
- package/dist/runtime/native-task-rpc.d.ts +22 -0
- package/dist/runtime/native-task-rpc.js +50 -0
- package/dist/runtime/task-backlog-runtime.d.ts +3 -0
- package/dist/runtime/task-backlog-runtime.js +4 -1
- package/dist/runtime/task-mutations.d.ts +39 -0
- package/dist/runtime/task-mutations.js +68 -0
- package/dist/runtime/task-rpc.d.ts +4 -0
- package/dist/runtime/task-rpc.js +40 -58
- package/dist/scheduler.js +1 -1
- package/dist/tools/loop-tools.js +1 -3
- package/dist/tools/monitor-tools.js +1 -3
- package/dist/tools/native-task-tools.d.ts +2 -6
- package/dist/tools/native-task-tools.js +25 -55
- package/dist/tools/tool-result.d.ts +8 -0
- package/dist/tools/tool-result.js +4 -0
- package/docker-compose.yml +12 -0
- package/docs/architecture/state-machine-migration.md +0 -44
- package/docs/architecture/state-machine-reducer-event-model.md +2 -3
- package/package.json +6 -1
- package/release-please-config.json +9 -0
- package/src/api.ts +36 -0
- package/src/commands/loop-command.ts +2 -2
- package/src/commands/tasks-command.ts +3 -3
- package/src/coordinator.ts +1 -1
- package/src/index.ts +84 -22
- package/src/loop-parse.ts +18 -7
- package/src/monitor-manager.ts +11 -3
- package/src/reducer-backed-store.ts +3 -3
- package/src/rpc/channels.ts +97 -0
- package/src/rpc/cross-extension-rpc.ts +152 -0
- package/src/runtime/loop-events.ts +60 -0
- package/src/runtime/native-task-rpc.ts +139 -0
- package/src/runtime/task-backlog-runtime.ts +14 -0
- package/src/runtime/task-mutations.ts +103 -0
- package/src/runtime/task-rpc.ts +48 -54
- package/src/scheduler.ts +1 -1
- package/src/tools/loop-tools.ts +1 -4
- package/src/tools/monitor-tools.ts +1 -4
- package/src/tools/native-task-tools.ts +31 -52
- package/src/tools/tool-result.ts +4 -0
- package/src/trigger-system.ts +4 -4
- package/vitest.config.ts +11 -7
- package/coverage/src/goal-reducer.ts.html +0 -982
- package/coverage/src/goal-store.ts.html +0 -742
- package/coverage/src/goal-verifier.ts.html +0 -808
- package/dist/goal-coordinator.d.ts +0 -22
- package/dist/goal-coordinator.js +0 -28
- package/dist/goal-reducer.d.ts +0 -98
- package/dist/goal-reducer.js +0 -190
- package/dist/goal-store.d.ts +0 -22
- package/dist/goal-store.js +0 -188
- package/dist/goal-types.d.ts +0 -82
- package/dist/goal-types.js +0 -1
- package/dist/goal-verifier.d.ts +0 -20
- package/dist/goal-verifier.js +0 -198
- package/docs/architecture/goal-state-schema.md +0 -505
- package/src/goal-coordinator.ts +0 -58
- package/src/goal-reducer.ts +0 -299
- package/src/goal-store.ts +0 -219
- package/src/goal-types.ts +0 -104
- package/src/goal-verifier.ts +0 -241
package/src/runtime/task-rpc.ts
CHANGED
|
@@ -1,7 +1,17 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
3
|
+
import {
|
|
4
|
+
type CleanReply,
|
|
5
|
+
type CreateTaskReply,
|
|
6
|
+
type PendingReply,
|
|
7
|
+
type PingReply,
|
|
8
|
+
replyChannel,
|
|
9
|
+
TASKS_RPC,
|
|
10
|
+
} from "../rpc/channels.js";
|
|
11
|
+
import { type RpcReply, rpcCall } from "../rpc/cross-extension-rpc.js";
|
|
3
12
|
import type { TaskStore } from "../task-store.js";
|
|
4
13
|
import type { LoopEntry } from "../types.js";
|
|
14
|
+
import { NATIVE_TASKS_PROVIDER } from "./native-task-rpc.js";
|
|
5
15
|
import { emitNativeTaskEvent } from "./task-events.js";
|
|
6
16
|
|
|
7
17
|
export interface TaskRuntimeBridgeOptions {
|
|
@@ -11,6 +21,10 @@ export interface TaskRuntimeBridgeOptions {
|
|
|
11
21
|
getNativeTaskStore: () => TaskStore | undefined;
|
|
12
22
|
onNativeTaskCreated?: (taskStore: TaskStore) => void;
|
|
13
23
|
onNativeTasksPruned?: (taskStore: TaskStore) => Promise<void> | void;
|
|
24
|
+
/** Called when a detection window opens. */
|
|
25
|
+
onDetectionStarted?: () => void;
|
|
26
|
+
/** Called when a detection window closes (provider found or probe timed out). */
|
|
27
|
+
onDetectionSettled?: () => void;
|
|
14
28
|
debug?: (...args: unknown[]) => void;
|
|
15
29
|
}
|
|
16
30
|
|
|
@@ -29,48 +43,51 @@ export function createTaskRuntimeBridge(options: TaskRuntimeBridgeOptions): Task
|
|
|
29
43
|
getNativeTaskStore,
|
|
30
44
|
onNativeTaskCreated,
|
|
31
45
|
onNativeTasksPruned,
|
|
46
|
+
onDetectionStarted,
|
|
47
|
+
onDetectionSettled,
|
|
32
48
|
debug,
|
|
33
49
|
} = options;
|
|
50
|
+
let detectionEpoch = 0;
|
|
34
51
|
|
|
35
52
|
function checkTasksVersion() {
|
|
53
|
+
// Not rpcProbe: pi-loop's own native server also answers this ping, and a
|
|
54
|
+
// first-reply-wins probe would always settle on that self-reply. Keep the
|
|
55
|
+
// listener open for the whole window and skip self-replies so a slower
|
|
56
|
+
// external provider (pi-tasks) is still detected.
|
|
57
|
+
const epoch = ++detectionEpoch;
|
|
58
|
+
onDetectionStarted?.();
|
|
59
|
+
const settleCurrentDetection = () => {
|
|
60
|
+
if (epoch !== detectionEpoch) return;
|
|
61
|
+
onDetectionSettled?.();
|
|
62
|
+
};
|
|
36
63
|
const requestId = randomUUID();
|
|
37
64
|
const timer = setTimeout(() => {
|
|
38
65
|
unsub();
|
|
66
|
+
settleCurrentDetection();
|
|
39
67
|
}, 5000);
|
|
40
|
-
const unsub = pi.events.on(
|
|
68
|
+
const unsub = pi.events.on(replyChannel(TASKS_RPC.ping, requestId), (raw: unknown) => {
|
|
69
|
+
const reply = raw as RpcReply<PingReply> | undefined;
|
|
70
|
+
if (!reply?.success || !reply.data) return;
|
|
71
|
+
if (reply.data.provider === NATIVE_TASKS_PROVIDER) return;
|
|
72
|
+
if (reply.data.version === undefined) return;
|
|
41
73
|
unsub();
|
|
42
74
|
clearTimeout(timer);
|
|
43
|
-
|
|
44
|
-
|
|
75
|
+
setTasksAvailable(true);
|
|
76
|
+
settleCurrentDetection();
|
|
45
77
|
});
|
|
46
|
-
pi.events.emit(
|
|
78
|
+
pi.events.emit(TASKS_RPC.ping, { requestId });
|
|
47
79
|
}
|
|
48
80
|
|
|
49
81
|
async function autoCreateTask(entry: LoopEntry): Promise<string | undefined> {
|
|
50
82
|
if (!entry.autoTask) return undefined;
|
|
51
83
|
if (isTasksAvailable()) {
|
|
52
84
|
try {
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
const unsub = pi.events.on(`tasks:rpc:create:reply:${requestId}`, (raw: unknown) => {
|
|
60
|
-
unsub();
|
|
61
|
-
clearTimeout(timer);
|
|
62
|
-
const reply = raw as { success: boolean; data?: { id: string } };
|
|
63
|
-
if (reply.success && reply.data) resolve(reply.data.id);
|
|
64
|
-
else resolve(undefined);
|
|
65
|
-
});
|
|
66
|
-
pi.events.emit("tasks:rpc:create", {
|
|
67
|
-
requestId,
|
|
68
|
-
subject: entry.prompt.slice(0, 80),
|
|
69
|
-
description: `Auto-created from loop #${entry.id}`,
|
|
70
|
-
metadata: { loopId: entry.id, trigger: entry.trigger },
|
|
71
|
-
});
|
|
72
|
-
});
|
|
73
|
-
return taskId;
|
|
85
|
+
const reply = await rpcCall<CreateTaskReply>(pi.events, TASKS_RPC.create, {
|
|
86
|
+
subject: entry.prompt.slice(0, 80),
|
|
87
|
+
description: `Auto-created from loop #${entry.id}`,
|
|
88
|
+
metadata: { loopId: entry.id, trigger: entry.trigger },
|
|
89
|
+
}, 5000);
|
|
90
|
+
return reply.id;
|
|
74
91
|
} catch {
|
|
75
92
|
return undefined;
|
|
76
93
|
}
|
|
@@ -90,22 +107,11 @@ export function createTaskRuntimeBridge(options: TaskRuntimeBridgeOptions): Task
|
|
|
90
107
|
|
|
91
108
|
async function hasPendingTasks(): Promise<number> {
|
|
92
109
|
if (isTasksAvailable()) {
|
|
110
|
+
// -1 is this bridge's "unknown" sentinel, consumed by notification-runtime;
|
|
111
|
+
// the RPC layer itself rejects on failure.
|
|
93
112
|
try {
|
|
94
|
-
const
|
|
95
|
-
|
|
96
|
-
const timer = setTimeout(() => {
|
|
97
|
-
unsub();
|
|
98
|
-
resolve(-1);
|
|
99
|
-
}, 3000);
|
|
100
|
-
const unsub = pi.events.on(`tasks:rpc:pending:reply:${requestId}`, (raw: unknown) => {
|
|
101
|
-
unsub();
|
|
102
|
-
clearTimeout(timer);
|
|
103
|
-
const reply = raw as { success: boolean; data?: { pending: number } };
|
|
104
|
-
resolve(reply.success && reply.data ? reply.data.pending : -1);
|
|
105
|
-
});
|
|
106
|
-
pi.events.emit("tasks:rpc:pending", { requestId });
|
|
107
|
-
});
|
|
108
|
-
return count;
|
|
113
|
+
const reply = await rpcCall<PendingReply>(pi.events, TASKS_RPC.pending, {}, 3000);
|
|
114
|
+
return reply.pending;
|
|
109
115
|
} catch {
|
|
110
116
|
return -1;
|
|
111
117
|
}
|
|
@@ -117,20 +123,8 @@ export function createTaskRuntimeBridge(options: TaskRuntimeBridgeOptions): Task
|
|
|
117
123
|
async function cleanDoneTasks(): Promise<void> {
|
|
118
124
|
if (isTasksAvailable()) {
|
|
119
125
|
try {
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
const timer = setTimeout(() => {
|
|
123
|
-
unsub();
|
|
124
|
-
resolve();
|
|
125
|
-
}, 3000);
|
|
126
|
-
const unsub = pi.events.on(`tasks:rpc:clean:reply:${requestId}`, () => {
|
|
127
|
-
unsub();
|
|
128
|
-
clearTimeout(timer);
|
|
129
|
-
debug?.("tasks:rpc:clean — done tasks swept");
|
|
130
|
-
resolve();
|
|
131
|
-
});
|
|
132
|
-
pi.events.emit("tasks:rpc:clean", { requestId });
|
|
133
|
-
});
|
|
126
|
+
await rpcCall<CleanReply>(pi.events, TASKS_RPC.clean, {}, 3000);
|
|
127
|
+
debug?.(`${TASKS_RPC.clean} — done tasks swept`);
|
|
134
128
|
} catch {
|
|
135
129
|
// timeout or error, ignore
|
|
136
130
|
}
|
package/src/scheduler.ts
CHANGED
|
@@ -50,7 +50,7 @@ export class CronScheduler {
|
|
|
50
50
|
const _scheduleExpr = entry.trigger.type === "hybrid" ? entry.trigger.cron : (entry.trigger as { schedule: string }).schedule;
|
|
51
51
|
|
|
52
52
|
const nextFire = computeNextFire(entry);
|
|
53
|
-
const minuteField = _scheduleExpr.trim().split(/\s+/)[0];
|
|
53
|
+
const minuteField = _scheduleExpr.trim().split(/\s+/)[0] ?? "";
|
|
54
54
|
const minuteStep = minuteField.startsWith("*/") ? parseInt(minuteField.slice(2), 10) || 30 : 30;
|
|
55
55
|
const jitter = computeJitter(entry.id, entry.recurring, minuteStep);
|
|
56
56
|
const fireTime = nextFire.getTime() + jitter;
|
package/src/tools/loop-tools.ts
CHANGED
|
@@ -2,6 +2,7 @@ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
|
2
2
|
import { Type } from "typebox";
|
|
3
3
|
import { parseInterval } from "../loop-parse.js";
|
|
4
4
|
import type { LoopEntry, Trigger } from "../types.js";
|
|
5
|
+
import { textResult } from "./tool-result.js";
|
|
5
6
|
|
|
6
7
|
interface LoopStoreLike {
|
|
7
8
|
list(): LoopEntry[];
|
|
@@ -46,10 +47,6 @@ export interface LoopToolsOptions {
|
|
|
46
47
|
isTaskSystemReady: () => boolean;
|
|
47
48
|
}
|
|
48
49
|
|
|
49
|
-
function textResult(msg: string) {
|
|
50
|
-
return { content: [{ type: "text" as const, text: msg }], details: undefined as any };
|
|
51
|
-
}
|
|
52
|
-
|
|
53
50
|
function validateTrigger(trigger: Trigger): string | null {
|
|
54
51
|
if (trigger.type === "cron") {
|
|
55
52
|
const parts = trigger.schedule.trim().split(/\s+/);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import { Type } from "typebox";
|
|
3
3
|
import type { LoopEntry, MonitorEntry, Trigger } from "../types.js";
|
|
4
|
+
import { textResult } from "./tool-result.js";
|
|
4
5
|
|
|
5
6
|
interface MonitorManagerLike {
|
|
6
7
|
list(): MonitorEntry[];
|
|
@@ -26,10 +27,6 @@ export interface MonitorToolsOptions {
|
|
|
26
27
|
handleMonitorDoneLoop: (doneLoop: LoopEntry, monitorId: string) => void;
|
|
27
28
|
}
|
|
28
29
|
|
|
29
|
-
function textResult(msg: string) {
|
|
30
|
-
return { content: [{ type: "text" as const, text: msg }], details: undefined as any };
|
|
31
|
-
}
|
|
32
|
-
|
|
33
30
|
function formatRemaining(ms: number): string {
|
|
34
31
|
if (ms < 60000) return `${Math.round(ms / 1000)}s`;
|
|
35
32
|
if (ms < 3600000) return `${Math.round(ms / 60000)}m`;
|
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import { Type } from "typebox";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
createTask,
|
|
5
|
+
deleteTask,
|
|
6
|
+
type TaskBacklogResult,
|
|
7
|
+
type TaskMutationContext,
|
|
8
|
+
updateTask,
|
|
9
|
+
} from "../runtime/task-mutations.js";
|
|
4
10
|
import { TaskStore } from "../task-store.js";
|
|
11
|
+
import type { TaskStatus } from "../task-types.js";
|
|
12
|
+
import { textResult } from "./tool-result.js";
|
|
5
13
|
|
|
6
|
-
export
|
|
7
|
-
created: boolean;
|
|
8
|
-
entry?: { id: string };
|
|
9
|
-
}
|
|
14
|
+
export type { TaskBacklogResult };
|
|
10
15
|
|
|
11
16
|
export interface NativeTaskToolsOptions {
|
|
12
17
|
pi: ExtensionAPI;
|
|
@@ -15,12 +20,15 @@ export interface NativeTaskToolsOptions {
|
|
|
15
20
|
updateWidget: () => void;
|
|
16
21
|
}
|
|
17
22
|
|
|
18
|
-
function
|
|
19
|
-
return
|
|
23
|
+
function backlogSuffix(backlog: TaskBacklogResult): string {
|
|
24
|
+
return backlog.created && backlog.entry
|
|
25
|
+
? `\nBacklog worker loop #${backlog.entry.id} created`
|
|
26
|
+
: "";
|
|
20
27
|
}
|
|
21
28
|
|
|
22
29
|
export function registerNativeTaskTools(options: NativeTaskToolsOptions): void {
|
|
23
30
|
const { pi, taskStore, evaluateTaskBacklog, updateWidget } = options;
|
|
31
|
+
const mutationCtx: TaskMutationContext = { pi, taskStore, evaluateTaskBacklog, updateWidget };
|
|
24
32
|
|
|
25
33
|
pi.registerTool({
|
|
26
34
|
name: "TaskCreate",
|
|
@@ -47,15 +55,11 @@ Fields:
|
|
|
47
55
|
description: Type.String({ description: "Detailed description of what needs to be done" }),
|
|
48
56
|
}),
|
|
49
57
|
async execute(_toolCallId, params) {
|
|
50
|
-
const entry =
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
const autoLoopMsg = backlog.created && backlog.entry
|
|
56
|
-
? `\nBacklog worker loop #${backlog.entry.id} created`
|
|
57
|
-
: "";
|
|
58
|
-
return Promise.resolve(textResult(`Task #${entry.id} created: ${entry.subject}${autoLoopMsg}`));
|
|
58
|
+
const { entry, backlog } = await createTask(mutationCtx, {
|
|
59
|
+
subject: params.subject,
|
|
60
|
+
description: params.description,
|
|
61
|
+
});
|
|
62
|
+
return textResult(`Task #${entry.id} created: ${entry.subject}${backlogSuffix(backlog)}`);
|
|
59
63
|
},
|
|
60
64
|
});
|
|
61
65
|
|
|
@@ -104,34 +108,15 @@ Parameters: id (required), status, subject, description`,
|
|
|
104
108
|
}),
|
|
105
109
|
async execute(_toolCallId, params) {
|
|
106
110
|
const { id, status, subject, description } = params;
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
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
|
-
}
|
|
121
|
-
|
|
122
|
-
if (!entry) return Promise.resolve(textResult(`Task #${id} not found`));
|
|
123
|
-
if (subject !== undefined || description !== undefined) {
|
|
124
|
-
entry = taskStore.updateDetails(id, { subject, description });
|
|
125
|
-
if (entry) emitNativeTaskEvent(pi, "tasks:updated", entry, previousStatus);
|
|
126
|
-
}
|
|
127
|
-
if (!entry) return Promise.resolve(textResult(`Task #${id} not found`));
|
|
128
|
-
updateWidget();
|
|
129
|
-
const backlog = await evaluateTaskBacklog(taskStore, taskStore.pendingCount());
|
|
111
|
+
const result = await updateTask(mutationCtx, {
|
|
112
|
+
id,
|
|
113
|
+
status: status as TaskStatus | undefined,
|
|
114
|
+
subject,
|
|
115
|
+
description,
|
|
116
|
+
});
|
|
117
|
+
if (!result) return textResult(`Task #${id} not found`);
|
|
130
118
|
const statusMsg = status ? ` → ${status}` : "";
|
|
131
|
-
|
|
132
|
-
? `\nBacklog worker loop #${backlog.entry.id} created`
|
|
133
|
-
: "";
|
|
134
|
-
return Promise.resolve(textResult(`Task #${id} updated${statusMsg}${autoLoopMsg}`));
|
|
119
|
+
return textResult(`Task #${id} updated${statusMsg}${backlogSuffix(result.backlog)}`);
|
|
135
120
|
},
|
|
136
121
|
});
|
|
137
122
|
|
|
@@ -143,15 +128,9 @@ Parameters: id (required), status, subject, description`,
|
|
|
143
128
|
id: Type.String({ description: "Task ID to delete" }),
|
|
144
129
|
}),
|
|
145
130
|
async execute(_toolCallId, params) {
|
|
146
|
-
const
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
if (deleted) {
|
|
150
|
-
if (existing) emitNativeTaskEvent(pi, "tasks:deleted", existing, existing.status);
|
|
151
|
-
await evaluateTaskBacklog(taskStore, taskStore.pendingCount());
|
|
152
|
-
return Promise.resolve(textResult(`Task #${params.id} deleted`));
|
|
153
|
-
}
|
|
154
|
-
return Promise.resolve(textResult(`Task #${params.id} not found`));
|
|
131
|
+
const result = await deleteTask(mutationCtx, params.id);
|
|
132
|
+
if (!result) return textResult(`Task #${params.id} not found`);
|
|
133
|
+
return textResult(`Task #${params.id} deleted`);
|
|
155
134
|
},
|
|
156
135
|
});
|
|
157
136
|
}
|
package/src/trigger-system.ts
CHANGED
|
@@ -58,7 +58,7 @@ export class TriggerSystem {
|
|
|
58
58
|
}
|
|
59
59
|
const subs = this.eventSubscriptions.get(source)!;
|
|
60
60
|
|
|
61
|
-
const unsub = this.pi.events.on(source
|
|
61
|
+
const unsub = this.pi.events.on(source, (data: unknown) => {
|
|
62
62
|
if (entry.trigger.type === "hybrid") {
|
|
63
63
|
this.handleHybridFire(entry, data);
|
|
64
64
|
} else {
|
|
@@ -71,7 +71,7 @@ export class TriggerSystem {
|
|
|
71
71
|
subs.set(entry.id, unsub);
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
-
private handleHybridFire(entry: LoopEntry, _data:
|
|
74
|
+
private handleHybridFire(entry: LoopEntry, _data: unknown): void {
|
|
75
75
|
const now = Date.now();
|
|
76
76
|
const last = this.lastFireTime.get(entry.id) ?? 0;
|
|
77
77
|
const debounceMs = entry.trigger.type === "hybrid" ? entry.trigger.debounceMs : 0;
|
|
@@ -123,7 +123,7 @@ export class TriggerSystem {
|
|
|
123
123
|
}
|
|
124
124
|
}
|
|
125
125
|
|
|
126
|
-
private matchesFilter(data:
|
|
126
|
+
private matchesFilter(data: unknown, filter?: string): boolean {
|
|
127
127
|
if (!filter) return true;
|
|
128
128
|
|
|
129
129
|
if (filter.startsWith("regex:")) {
|
|
@@ -138,7 +138,7 @@ export class TriggerSystem {
|
|
|
138
138
|
try {
|
|
139
139
|
const parsed = JSON.parse(filter);
|
|
140
140
|
for (const [key, value] of Object.entries(parsed)) {
|
|
141
|
-
const dataValue = data?.[key];
|
|
141
|
+
const dataValue = (data as Record<string, unknown> | undefined)?.[key];
|
|
142
142
|
if (dataValue === undefined) return false;
|
|
143
143
|
if (typeof value === "object" && typeof dataValue === "object") {
|
|
144
144
|
if (JSON.stringify(value) !== JSON.stringify(dataValue)) return false;
|
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
|
-
//
|
|
18
|
+
// Floors set just below current actuals (stmts 91%, branches 83%,
|
|
19
|
+
// funcs 98%, lines 94%) to catch regressions. Re-anchored after the
|
|
20
|
+
// goal-subsystem removal and the rpc/native-task-rpc/command suites landed.
|
|
17
21
|
thresholds: {
|
|
18
|
-
statements:
|
|
19
|
-
branches:
|
|
20
|
-
functions:
|
|
21
|
-
lines:
|
|
22
|
+
statements: 89,
|
|
23
|
+
branches: 80,
|
|
24
|
+
functions: 96,
|
|
25
|
+
lines: 91,
|
|
22
26
|
},
|
|
23
27
|
},
|
|
24
28
|
},
|