@trevonistrevon/pi-loop 0.5.9 → 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 +1 -1
- package/AGENTS.md +52 -12
- package/CHANGELOG.md +59 -0
- package/CONTRIBUTING.md +13 -3
- package/README.md +47 -0
- package/coverage/coverage-final.json +35 -32
- 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 +129 -129
- package/coverage/src/coordinator.ts.html +26 -26
- package/coverage/src/index.html +61 -106
- package/coverage/src/index.ts.html +224 -86
- package/coverage/src/loop-parse.ts.html +106 -73
- package/coverage/src/loop-reducer.ts.html +21 -21
- 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 +6 -6
- 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 +68 -23
- 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 +22 -22
- package/coverage/src/runtime/scope.ts.html +14 -14
- package/coverage/src/runtime/session-runtime.ts.html +32 -32
- package/coverage/src/runtime/task-backlog-runtime.ts.html +65 -23
- package/coverage/src/runtime/task-events.ts.html +2 -2
- package/coverage/src/runtime/task-mutations.ts.html +394 -0
- package/coverage/src/runtime/task-rpc.ts.html +102 -120
- package/coverage/src/scheduler.ts.html +29 -29
- package/coverage/src/store.ts.html +27 -27
- 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 +57 -120
- 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 +48 -3
- package/dist/loop-parse.js +14 -5
- 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/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/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 +49 -3
- package/src/loop-parse.ts +18 -7
- 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 +7 -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
|
@@ -224,15 +224,15 @@
|
|
|
224
224
|
<span class="cline-any cline-neutral"> </span>
|
|
225
225
|
<span class="cline-any cline-neutral"> </span>
|
|
226
226
|
<span class="cline-any cline-neutral"> </span>
|
|
227
|
-
<span class="cline-any cline-yes">
|
|
228
|
-
<span class="cline-any cline-yes">
|
|
229
|
-
<span class="cline-any cline-yes">
|
|
227
|
+
<span class="cline-any cline-yes">126x</span>
|
|
228
|
+
<span class="cline-any cline-yes">126x</span>
|
|
229
|
+
<span class="cline-any cline-yes">126x</span>
|
|
230
230
|
<span class="cline-any cline-neutral"> </span>
|
|
231
231
|
<span class="cline-any cline-neutral"> </span>
|
|
232
|
-
<span class="cline-any cline-yes">
|
|
233
|
-
<span class="cline-any cline-yes">
|
|
234
|
-
<span class="cline-any cline-yes">
|
|
235
|
-
<span class="cline-any cline-yes">
|
|
232
|
+
<span class="cline-any cline-yes">126x</span>
|
|
233
|
+
<span class="cline-any cline-yes">126x</span>
|
|
234
|
+
<span class="cline-any cline-yes">126x</span>
|
|
235
|
+
<span class="cline-any cline-yes">126x</span>
|
|
236
236
|
<span class="cline-any cline-neutral"> </span>
|
|
237
237
|
<span class="cline-any cline-neutral"> </span>
|
|
238
238
|
<span class="cline-any cline-neutral"> </span>
|
|
@@ -431,7 +431,7 @@ export class TriggerSystem {
|
|
|
431
431
|
}
|
|
432
432
|
const subs = this.eventSubscriptions.get(source)!;
|
|
433
433
|
|
|
434
|
-
const unsub = this.pi.events.on(source
|
|
434
|
+
const unsub = this.pi.events.on(source, (data: unknown) => {
|
|
435
435
|
if (entry.trigger.type === "hybrid") {
|
|
436
436
|
this.handleHybridFire(entry, data);
|
|
437
437
|
} else {
|
|
@@ -444,7 +444,7 @@ export class TriggerSystem {
|
|
|
444
444
|
subs.set(entry.id, unsub);
|
|
445
445
|
}
|
|
446
446
|
|
|
447
|
-
private handleHybridFire(entry: LoopEntry, _data:
|
|
447
|
+
private handleHybridFire(entry: LoopEntry, _data: unknown): void {
|
|
448
448
|
const now = Date.now();
|
|
449
449
|
const last = this.lastFireTime.get(entry.id) ?? 0;
|
|
450
450
|
const debounceMs = entry.trigger.type === "hybrid" ? entry.trigger.debounceMs : <span class="branch-1 cbranch-no" title="branch not covered" >0;</span>
|
|
@@ -496,7 +496,7 @@ export class TriggerSystem {
|
|
|
496
496
|
}
|
|
497
497
|
}
|
|
498
498
|
|
|
499
|
-
private matchesFilter(data:
|
|
499
|
+
private matchesFilter(data: unknown, filter?: string): boolean {
|
|
500
500
|
if (!filter) return true;
|
|
501
501
|
|
|
502
502
|
if (filter.startsWith("regex:")) {
|
|
@@ -511,7 +511,7 @@ export class TriggerSystem {
|
|
|
511
511
|
try {
|
|
512
512
|
const parsed = JSON.parse(filter);
|
|
513
513
|
for (const [key, value] of Object.entries(parsed)) {
|
|
514
|
-
const dataValue = <span class="cstat-no" title="statement not covered" >data?.[key];</span>
|
|
514
|
+
const dataValue = (<span class="cstat-no" title="statement not covered" >data as Record<string, unknown> | undefined)?.[key];</span>
|
|
515
515
|
<span class="cstat-no" title="statement not covered" > if (dataValue === undefined) <span class="cstat-no" title="statement not covered" >return false;</span></span>
|
|
516
516
|
<span class="cstat-no" title="statement not covered" > if (typeof value === "object" && typeof dataValue === "object") {</span>
|
|
517
517
|
<span class="cstat-no" title="statement not covered" > if (JSON.stringify(value) !== JSON.stringify(dataValue)) <span class="cstat-no" title="statement not covered" >return false;</span></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-
|
|
535
|
+
at 2026-07-02T19:31:50.067Z
|
|
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-
|
|
104
|
+
at 2026-07-02T19:31:50.067Z
|
|
105
105
|
</div>
|
|
106
106
|
<script src="../../prettify.js"></script>
|
|
107
107
|
<script>
|
|
@@ -147,44 +147,44 @@
|
|
|
147
147
|
<span class="cline-any cline-neutral"> </span>
|
|
148
148
|
<span class="cline-any cline-neutral"> </span>
|
|
149
149
|
<span class="cline-any cline-neutral"> </span>
|
|
150
|
-
<span class="cline-any cline-yes">
|
|
151
|
-
<span class="cline-any cline-yes">
|
|
150
|
+
<span class="cline-any cline-yes">72x</span>
|
|
151
|
+
<span class="cline-any cline-yes">72x</span>
|
|
152
152
|
<span class="cline-any cline-neutral"> </span>
|
|
153
153
|
<span class="cline-any cline-neutral"> </span>
|
|
154
154
|
<span class="cline-any cline-neutral"> </span>
|
|
155
|
-
<span class="cline-any cline-yes">
|
|
155
|
+
<span class="cline-any cline-yes">108x</span>
|
|
156
156
|
<span class="cline-any cline-neutral"> </span>
|
|
157
157
|
<span class="cline-any cline-neutral"> </span>
|
|
158
158
|
<span class="cline-any cline-neutral"> </span>
|
|
159
|
-
<span class="cline-any cline-yes">
|
|
159
|
+
<span class="cline-any cline-yes">47x</span>
|
|
160
160
|
<span class="cline-any cline-neutral"> </span>
|
|
161
161
|
<span class="cline-any cline-neutral"> </span>
|
|
162
162
|
<span class="cline-any cline-neutral"> </span>
|
|
163
|
-
<span class="cline-any cline-yes">
|
|
163
|
+
<span class="cline-any cline-yes">67x</span>
|
|
164
164
|
<span class="cline-any cline-neutral"> </span>
|
|
165
165
|
<span class="cline-any cline-neutral"> </span>
|
|
166
166
|
<span class="cline-any cline-neutral"> </span>
|
|
167
|
-
<span class="cline-any cline-yes">
|
|
168
|
-
<span class="cline-any cline-yes">
|
|
167
|
+
<span class="cline-any cline-yes">191x</span>
|
|
168
|
+
<span class="cline-any cline-yes">168x</span>
|
|
169
169
|
<span class="cline-any cline-neutral"> </span>
|
|
170
170
|
<span class="cline-any cline-neutral"> </span>
|
|
171
171
|
<span class="cline-any cline-neutral"> </span>
|
|
172
|
-
<span class="cline-any cline-yes">
|
|
173
|
-
<span class="cline-any cline-yes">
|
|
174
|
-
<span class="cline-any cline-yes">
|
|
172
|
+
<span class="cline-any cline-yes">168x</span>
|
|
173
|
+
<span class="cline-any cline-yes">168x</span>
|
|
174
|
+
<span class="cline-any cline-yes">168x</span>
|
|
175
175
|
<span class="cline-any cline-neutral"> </span>
|
|
176
|
-
<span class="cline-any cline-yes">
|
|
177
|
-
<span class="cline-any cline-yes">
|
|
176
|
+
<span class="cline-any cline-yes">168x</span>
|
|
177
|
+
<span class="cline-any cline-yes">70x</span>
|
|
178
178
|
<span class="cline-any cline-neutral"> </span>
|
|
179
179
|
<span class="cline-any cline-neutral"> </span>
|
|
180
|
-
<span class="cline-any cline-yes">
|
|
181
|
-
<span class="cline-any cline-yes">
|
|
182
|
-
<span class="cline-any cline-yes">
|
|
183
|
-
<span class="cline-any cline-yes">
|
|
180
|
+
<span class="cline-any cline-yes">98x</span>
|
|
181
|
+
<span class="cline-any cline-yes">98x</span>
|
|
182
|
+
<span class="cline-any cline-yes">98x</span>
|
|
183
|
+
<span class="cline-any cline-yes">98x</span>
|
|
184
184
|
<span class="cline-any cline-neutral"> </span>
|
|
185
|
-
<span class="cline-any cline-yes">
|
|
186
|
-
<span class="cline-any cline-yes">
|
|
187
|
-
<span class="cline-any cline-yes">
|
|
185
|
+
<span class="cline-any cline-yes">98x</span>
|
|
186
|
+
<span class="cline-any cline-yes">98x</span>
|
|
187
|
+
<span class="cline-any cline-yes">98x</span>
|
|
188
188
|
<span class="cline-any cline-neutral"> </span>
|
|
189
189
|
<span class="cline-any cline-neutral"> </span>
|
|
190
190
|
<span class="cline-any cline-neutral"> </span>
|
|
@@ -193,12 +193,12 @@
|
|
|
193
193
|
<span class="cline-any cline-neutral"> </span>
|
|
194
194
|
<span class="cline-any cline-neutral"> </span>
|
|
195
195
|
<span class="cline-any cline-neutral"> </span>
|
|
196
|
-
<span class="cline-any cline-yes">
|
|
196
|
+
<span class="cline-any cline-yes">117x</span>
|
|
197
197
|
<span class="cline-any cline-neutral"> </span>
|
|
198
198
|
<span class="cline-any cline-neutral"> </span>
|
|
199
199
|
<span class="cline-any cline-neutral"> </span>
|
|
200
|
-
<span class="cline-any cline-yes">
|
|
201
|
-
<span class="cline-any cline-yes">
|
|
200
|
+
<span class="cline-any cline-yes">40x</span>
|
|
201
|
+
<span class="cline-any cline-yes">39x</span>
|
|
202
202
|
<span class="cline-any cline-yes">6x</span>
|
|
203
203
|
<span class="cline-any cline-neutral"> </span>
|
|
204
204
|
<span class="cline-any cline-neutral"> </span></td><td class="text"><pre class="prettyprint lang-js">import type { ExtensionUIContext } from "@earendil-works/pi-coding-agent";
|
|
@@ -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-
|
|
280
|
+
at 2026-07-02T19:31:50.067Z
|
|
281
281
|
</div>
|
|
282
282
|
<script src="../../prettify.js"></script>
|
|
283
283
|
<script>
|
package/dist/api.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public API surface for sibling extensions (imported as
|
|
3
|
+
* `@trevonistrevon/pi-loop/api`). Everything else under src/ is internal —
|
|
4
|
+
* the package `exports` map deliberately blocks deep imports.
|
|
5
|
+
*/
|
|
6
|
+
export { type CleanReply, type CreateTaskParams, type CreateTaskReply, type PendingReply, type PingReply, replyChannel, type SpawnParams, type SpawnReply, SUBAGENTS_RPC, TASK_EVENTS, TASKS_RPC, type TaskEntryWire, type TaskStatusWire, type UpdateTaskParams, type UpdateTaskReply, } from "./rpc/channels.js";
|
|
7
|
+
export { type HandleRpcOptions, handleRpc, PROTOCOL_VERSION, RpcError, type RpcEventBus, type RpcReply, rpcCall, rpcProbe, } from "./rpc/cross-extension-rpc.js";
|
|
8
|
+
export { NATIVE_TASKS_PROVIDER } from "./runtime/native-task-rpc.js";
|
|
9
|
+
export { resolveLoopStorePath, resolveTaskStorePath } from "./runtime/scope.js";
|
|
10
|
+
export { TaskStore } from "./task-store.js";
|
|
11
|
+
export type { TaskEntry, TaskStatus, TaskStoreData } from "./task-types.js";
|
package/dist/api.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public API surface for sibling extensions (imported as
|
|
3
|
+
* `@trevonistrevon/pi-loop/api`). Everything else under src/ is internal —
|
|
4
|
+
* the package `exports` map deliberately blocks deep imports.
|
|
5
|
+
*/
|
|
6
|
+
export { replyChannel, SUBAGENTS_RPC, TASK_EVENTS, TASKS_RPC, } from "./rpc/channels.js";
|
|
7
|
+
export { handleRpc, PROTOCOL_VERSION, RpcError, rpcCall, rpcProbe, } from "./rpc/cross-extension-rpc.js";
|
|
8
|
+
export { NATIVE_TASKS_PROVIDER } from "./runtime/native-task-rpc.js";
|
|
9
|
+
export { resolveLoopStorePath, resolveTaskStorePath } from "./runtime/scope.js";
|
|
10
|
+
export { TaskStore } from "./task-store.js";
|
|
@@ -53,7 +53,7 @@ export function registerLoopCommand(options) {
|
|
|
53
53
|
if (!selected || selected === "< Back")
|
|
54
54
|
return;
|
|
55
55
|
const match = selected.match(/#(\d+)/);
|
|
56
|
-
if (match) {
|
|
56
|
+
if (match?.[1]) {
|
|
57
57
|
const entry = getStore().get(match[1]);
|
|
58
58
|
if (entry) {
|
|
59
59
|
const actions = ["x Delete"];
|
|
@@ -114,7 +114,7 @@ export function registerLoopCommand(options) {
|
|
|
114
114
|
}
|
|
115
115
|
const intervalMatch = trimmed.match(/^(\d+\s*[smhdS]\b)/i);
|
|
116
116
|
if (intervalMatch) {
|
|
117
|
-
const interval = intervalMatch[1];
|
|
117
|
+
const interval = intervalMatch[1] ?? intervalMatch[0];
|
|
118
118
|
const prompt = trimmed.slice(intervalMatch[0].length).trim();
|
|
119
119
|
if (!prompt) {
|
|
120
120
|
ui.notify("Provide a prompt after the interval, e.g., /loop 5m check the deploy", "warning");
|
|
@@ -47,10 +47,10 @@ export function registerTasksCommand(options) {
|
|
|
47
47
|
await createNativeTaskInteractively(ui);
|
|
48
48
|
return viewNativeTasks(ui);
|
|
49
49
|
}
|
|
50
|
-
const
|
|
51
|
-
if (!
|
|
50
|
+
const taskId = selected.match(/#(\d+)/)?.[1];
|
|
51
|
+
if (!taskId)
|
|
52
52
|
return viewNativeTasks(ui);
|
|
53
|
-
const task = taskStore.get(
|
|
53
|
+
const task = taskStore.get(taskId);
|
|
54
54
|
if (!task)
|
|
55
55
|
return viewNativeTasks(ui);
|
|
56
56
|
const actions = ["x Delete"];
|
package/dist/coordinator.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export type ReducerSource = "tool" | "command" | "scheduler" | "eventbus" | "monitor" | "session" | "coordinator" | "system";
|
|
2
|
-
export type ReducerEntityType = "task" | "loop" | "monitor" | "notification"
|
|
2
|
+
export type ReducerEntityType = "task" | "loop" | "monitor" | "notification";
|
|
3
3
|
export interface ReducerEvent<TType extends string = string, TPayload = unknown> {
|
|
4
4
|
type: TType;
|
|
5
5
|
at: number;
|
package/dist/index.js
CHANGED
|
@@ -18,6 +18,7 @@ import { registerTasksCommand } from "./commands/tasks-command.js";
|
|
|
18
18
|
import { atMaxFires } from "./loop-reducer.js";
|
|
19
19
|
import { MonitorManager } from "./monitor-manager.js";
|
|
20
20
|
import { createMonitorOnDoneRuntime } from "./runtime/monitor-ondone-runtime.js";
|
|
21
|
+
import { registerNativeTaskRpc } from "./runtime/native-task-rpc.js";
|
|
21
22
|
import { createNotificationRuntime, } from "./runtime/notification-runtime.js";
|
|
22
23
|
import { resolveLoopStorePath, resolveTaskStorePath } from "./runtime/scope.js";
|
|
23
24
|
import { registerSessionRuntimeHooks } from "./runtime/session-runtime.js";
|
|
@@ -53,7 +54,9 @@ export default function (pi) {
|
|
|
53
54
|
// call), so stale monitors don't linger in the count between turns.
|
|
54
55
|
monitorManager.setOnChange(() => widget.update());
|
|
55
56
|
widget.setTaskSummaryProvider(() => {
|
|
56
|
-
|
|
57
|
+
// Once pi-tasks owns the channels, any native store created during the
|
|
58
|
+
// detection window is a shadow — never surface it in the status line.
|
|
59
|
+
if (tasksAvailable || !nativeTaskStore)
|
|
57
60
|
return { count: 0 };
|
|
58
61
|
const tasks = nativeTaskStore.list().filter(t => t.status === "pending" || t.status === "in_progress");
|
|
59
62
|
const active = tasks.find(t => t.status === "in_progress");
|
|
@@ -69,8 +72,18 @@ export default function (pi) {
|
|
|
69
72
|
triggerSystem = new TriggerSystem(pi, scheduler, store, onLoopFire);
|
|
70
73
|
// ── pi-tasks integration ──
|
|
71
74
|
let tasksAvailable = false;
|
|
75
|
+
let tasksDetectionSettled = false;
|
|
72
76
|
let nativeTaskStore;
|
|
73
77
|
let nativeTasksRegistered = false;
|
|
78
|
+
function getOrCreateNativeTaskStore() {
|
|
79
|
+
// pi-tasks owns the task channels; don't create a shadow store.
|
|
80
|
+
if (tasksAvailable)
|
|
81
|
+
return undefined;
|
|
82
|
+
if (!nativeTaskStore) {
|
|
83
|
+
nativeTaskStore = new TaskStore(resolveTaskStorePath(getScopeOptions(), _sessionId));
|
|
84
|
+
}
|
|
85
|
+
return nativeTaskStore;
|
|
86
|
+
}
|
|
74
87
|
const taskRuntime = createTaskRuntimeBridge({
|
|
75
88
|
pi,
|
|
76
89
|
isTasksAvailable: () => tasksAvailable,
|
|
@@ -86,6 +99,29 @@ export default function (pi) {
|
|
|
86
99
|
widget.update();
|
|
87
100
|
await evaluateTaskBacklog(taskStore, taskStore.pendingCount());
|
|
88
101
|
},
|
|
102
|
+
onDetectionStarted: () => {
|
|
103
|
+
tasksDetectionSettled = false;
|
|
104
|
+
},
|
|
105
|
+
onDetectionSettled: () => {
|
|
106
|
+
tasksDetectionSettled = true;
|
|
107
|
+
},
|
|
108
|
+
debug,
|
|
109
|
+
});
|
|
110
|
+
// The RPC server registers at init (not behind the 6s tool-fallback timer) so
|
|
111
|
+
// early cross-extension calls never race the timer; it stands down via
|
|
112
|
+
// isEnabled once an external pi-tasks is detected. checkTasksVersion ignores
|
|
113
|
+
// this server's own ping reply via its provider field. Mutating verbs stay
|
|
114
|
+
// silent until the detection probe settles, so a co-resident pi-tasks can
|
|
115
|
+
// never race the native server into creating divergent task state.
|
|
116
|
+
registerNativeTaskRpc({
|
|
117
|
+
pi,
|
|
118
|
+
getNativeTaskStore: getOrCreateNativeTaskStore,
|
|
119
|
+
isEnabled: () => !tasksAvailable,
|
|
120
|
+
isDetectionSettled: () => tasksDetectionSettled,
|
|
121
|
+
evaluateTaskBacklog: (taskStore, pendingCount) => evaluateTaskBacklog(taskStore, pendingCount),
|
|
122
|
+
updateWidget: () => {
|
|
123
|
+
widget.update();
|
|
124
|
+
},
|
|
89
125
|
debug,
|
|
90
126
|
});
|
|
91
127
|
taskRuntime.checkTasksVersion();
|
|
@@ -156,6 +192,12 @@ export default function (pi) {
|
|
|
156
192
|
hasPendingTasks: () => hasPendingTasks(),
|
|
157
193
|
bootstrapTaskLoop: (entry) => maybeBootstrapTaskLoop(entry),
|
|
158
194
|
triggerHasEventSource,
|
|
195
|
+
emitLoopAutodeleted: (payload) => {
|
|
196
|
+
pi.events.emit("loops:autodeleted", payload);
|
|
197
|
+
},
|
|
198
|
+
emitTaskBacklogEmpty: (payload) => {
|
|
199
|
+
pi.events.emit("tasks:backlog_empty", payload);
|
|
200
|
+
},
|
|
159
201
|
debug,
|
|
160
202
|
});
|
|
161
203
|
const flushPendingNotifications = notificationRuntime.flushPendingNotifications;
|
|
@@ -266,10 +308,14 @@ export default function (pi) {
|
|
|
266
308
|
},
|
|
267
309
|
});
|
|
268
310
|
// ── Native task tools (only when pi-tasks is absent) ──
|
|
311
|
+
// Only tool/command registration stays deferred: the tool names collide with
|
|
312
|
+
// pi-tasks and cannot be unregistered. The RPC server is already live (above).
|
|
269
313
|
const nativeTaskFallbackTimer = setTimeout(() => {
|
|
270
314
|
if (tasksAvailable || nativeTasksRegistered)
|
|
271
315
|
return;
|
|
272
|
-
const taskStore =
|
|
316
|
+
const taskStore = getOrCreateNativeTaskStore();
|
|
317
|
+
if (!taskStore)
|
|
318
|
+
return;
|
|
273
319
|
try {
|
|
274
320
|
registerTasksCommand({
|
|
275
321
|
pi,
|
|
@@ -295,7 +341,6 @@ export default function (pi) {
|
|
|
295
341
|
}
|
|
296
342
|
throw error;
|
|
297
343
|
}
|
|
298
|
-
nativeTaskStore = taskStore;
|
|
299
344
|
nativeTasksRegistered = true;
|
|
300
345
|
debug("native task tools registered (pi-tasks not detected)");
|
|
301
346
|
}, 6000);
|
package/dist/loop-parse.js
CHANGED
|
@@ -21,6 +21,8 @@ const COMMON_INTERVALS = {
|
|
|
21
21
|
86400: "0 0 * * *",
|
|
22
22
|
};
|
|
23
23
|
function roundToNearestCommon(seconds) {
|
|
24
|
+
// COMMON_INTERVALS is a non-empty const table, so keys[0] and
|
|
25
|
+
// COMMON_INTERVALS[best] below are true invariants, not runtime fallbacks.
|
|
24
26
|
const keys = Object.keys(COMMON_INTERVALS).map(Number).sort((a, b) => a - b);
|
|
25
27
|
let best = keys[0];
|
|
26
28
|
for (const k of keys) {
|
|
@@ -55,8 +57,8 @@ export function parseInterval(input) {
|
|
|
55
57
|
}
|
|
56
58
|
const match = trimmed.match(/^(\d+)\s*(s|m|h|d)$/i);
|
|
57
59
|
if (match) {
|
|
58
|
-
const value = parseInt(match[1], 10);
|
|
59
|
-
const unit = match[2].toLowerCase();
|
|
60
|
+
const value = parseInt(match[1] ?? "", 10);
|
|
61
|
+
const unit = (match[2] ?? "").toLowerCase();
|
|
60
62
|
const totalSec = value * (UNIT_TO_CRON[unit] ?? 60);
|
|
61
63
|
if (totalSec < 60) {
|
|
62
64
|
return { cron: `*/1 * * * *`, description: `${totalSec} seconds (rounded to 1 minute)` };
|
|
@@ -70,6 +72,13 @@ export function cronToNextFire(cronExpr, fromDate = new Date()) {
|
|
|
70
72
|
if (parts.length !== 5)
|
|
71
73
|
throw new Error(`Invalid cron expression: ${cronExpr}`);
|
|
72
74
|
const [minF, hourF, dayF, monthF, dowF] = parts;
|
|
75
|
+
if (minF === undefined ||
|
|
76
|
+
hourF === undefined ||
|
|
77
|
+
dayF === undefined ||
|
|
78
|
+
monthF === undefined ||
|
|
79
|
+
dowF === undefined) {
|
|
80
|
+
throw new Error(`Invalid cron expression: ${cronExpr}`);
|
|
81
|
+
}
|
|
73
82
|
const now = new Date(fromDate);
|
|
74
83
|
now.setSeconds(0, 0);
|
|
75
84
|
for (let minutesAdvanced = 1; minutesAdvanced < 525600; minutesAdvanced++) {
|
|
@@ -96,7 +105,7 @@ function cronFieldMatches(field, value) {
|
|
|
96
105
|
if (part === "*")
|
|
97
106
|
return true;
|
|
98
107
|
if (part.includes("/")) {
|
|
99
|
-
const [range, stepStr] = part.split("/");
|
|
108
|
+
const [range = "", stepStr = ""] = part.split("/");
|
|
100
109
|
const step = parseInt(stepStr, 10);
|
|
101
110
|
let rangeMin;
|
|
102
111
|
let rangeMax;
|
|
@@ -105,7 +114,7 @@ function cronFieldMatches(field, value) {
|
|
|
105
114
|
rangeMax = 59;
|
|
106
115
|
}
|
|
107
116
|
else if (range.includes("-")) {
|
|
108
|
-
const [minS, maxS] = range.split("-");
|
|
117
|
+
const [minS = "", maxS = ""] = range.split("-");
|
|
109
118
|
rangeMin = parseInt(minS, 10);
|
|
110
119
|
rangeMax = parseInt(maxS, 10);
|
|
111
120
|
}
|
|
@@ -121,7 +130,7 @@ function cronFieldMatches(field, value) {
|
|
|
121
130
|
continue;
|
|
122
131
|
}
|
|
123
132
|
if (part.includes("-")) {
|
|
124
|
-
const [minS, maxS] = part.split("-");
|
|
133
|
+
const [minS = "", maxS = ""] = part.split("-");
|
|
125
134
|
const min = parseInt(minS, 10);
|
|
126
135
|
const max = parseInt(maxS, 10);
|
|
127
136
|
if (value >= min && value <= max)
|
|
@@ -25,7 +25,7 @@ export interface ReducerBackedStoreConfig<TEntry, TState, TEvent, TData> {
|
|
|
25
25
|
}
|
|
26
26
|
/**
|
|
27
27
|
* Shared persistence + reducer-dispatch machinery for the file-backed entity
|
|
28
|
-
* stores (loops, tasks
|
|
28
|
+
* stores (loops, tasks). Owns file locking, signature-gated load, atomic
|
|
29
29
|
* save, and reducer application; subclasses add only their entity-specific
|
|
30
30
|
* command methods.
|
|
31
31
|
*
|
|
@@ -48,7 +48,7 @@ function isProcessRunning(pid) {
|
|
|
48
48
|
}
|
|
49
49
|
/**
|
|
50
50
|
* Shared persistence + reducer-dispatch machinery for the file-backed entity
|
|
51
|
-
* stores (loops, tasks
|
|
51
|
+
* stores (loops, tasks). Owns file locking, signature-gated load, atomic
|
|
52
52
|
* save, and reducer application; subclasses add only their entity-specific
|
|
53
53
|
* command methods.
|
|
54
54
|
*
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/** Cross-extension RPC channels served by a tasks provider (pi-tasks or pi-loop native). */
|
|
2
|
+
export declare const TASKS_RPC: {
|
|
3
|
+
readonly ping: "tasks:rpc:ping";
|
|
4
|
+
readonly create: "tasks:rpc:create";
|
|
5
|
+
readonly update: "tasks:rpc:update";
|
|
6
|
+
readonly pending: "tasks:rpc:pending";
|
|
7
|
+
readonly clean: "tasks:rpc:clean";
|
|
8
|
+
};
|
|
9
|
+
/** Cross-extension RPC channels served by @tintinweb/pi-subagents. */
|
|
10
|
+
export declare const SUBAGENTS_RPC: {
|
|
11
|
+
readonly ping: "subagents:rpc:ping";
|
|
12
|
+
readonly spawn: "subagents:rpc:spawn";
|
|
13
|
+
readonly stop: "subagents:rpc:stop";
|
|
14
|
+
};
|
|
15
|
+
/** Broadcast (fire-and-forget) task lifecycle events. */
|
|
16
|
+
export declare const TASK_EVENTS: {
|
|
17
|
+
readonly ready: "tasks:ready";
|
|
18
|
+
readonly created: "tasks:created";
|
|
19
|
+
readonly started: "tasks:started";
|
|
20
|
+
readonly completed: "tasks:completed";
|
|
21
|
+
readonly reopened: "tasks:reopened";
|
|
22
|
+
readonly updated: "tasks:updated";
|
|
23
|
+
readonly deleted: "tasks:deleted";
|
|
24
|
+
};
|
|
25
|
+
export declare function replyChannel(channel: string, requestId: string): string;
|
|
26
|
+
export type TaskStatusWire = "pending" | "in_progress" | "completed";
|
|
27
|
+
export interface TaskEntryWire {
|
|
28
|
+
id: string;
|
|
29
|
+
subject: string;
|
|
30
|
+
description: string;
|
|
31
|
+
status: TaskStatusWire;
|
|
32
|
+
createdAt: number;
|
|
33
|
+
updatedAt: number;
|
|
34
|
+
completedAt?: number;
|
|
35
|
+
metadata?: Record<string, unknown>;
|
|
36
|
+
}
|
|
37
|
+
export interface CreateTaskParams {
|
|
38
|
+
subject: string;
|
|
39
|
+
description: string;
|
|
40
|
+
metadata?: Record<string, unknown>;
|
|
41
|
+
}
|
|
42
|
+
export interface CreateTaskReply {
|
|
43
|
+
id: string;
|
|
44
|
+
task: TaskEntryWire;
|
|
45
|
+
}
|
|
46
|
+
export interface UpdateTaskParams {
|
|
47
|
+
id: string;
|
|
48
|
+
status?: TaskStatusWire;
|
|
49
|
+
subject?: string;
|
|
50
|
+
description?: string;
|
|
51
|
+
}
|
|
52
|
+
export interface UpdateTaskReply {
|
|
53
|
+
task: TaskEntryWire;
|
|
54
|
+
}
|
|
55
|
+
export interface PendingReply {
|
|
56
|
+
pending: number;
|
|
57
|
+
}
|
|
58
|
+
export interface CleanReply {
|
|
59
|
+
pruned: number;
|
|
60
|
+
}
|
|
61
|
+
export interface PingReply {
|
|
62
|
+
version: number;
|
|
63
|
+
/** Identifies which extension answered; lets a provider ignore its own reply. */
|
|
64
|
+
provider?: string;
|
|
65
|
+
}
|
|
66
|
+
export interface SpawnParams {
|
|
67
|
+
type: string;
|
|
68
|
+
prompt: string;
|
|
69
|
+
options?: Record<string, unknown>;
|
|
70
|
+
}
|
|
71
|
+
export interface SpawnReply {
|
|
72
|
+
id: string;
|
|
73
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// VENDORED MODULE — canonical copy shared verbatim by pi-loop and pi-orca.
|
|
2
|
+
// If you edit this file, copy it to the sibling repo and bump VENDOR_REV.
|
|
3
|
+
// VENDOR_REV: 1
|
|
4
|
+
/** Cross-extension RPC channels served by a tasks provider (pi-tasks or pi-loop native). */
|
|
5
|
+
export const TASKS_RPC = {
|
|
6
|
+
ping: "tasks:rpc:ping",
|
|
7
|
+
create: "tasks:rpc:create",
|
|
8
|
+
update: "tasks:rpc:update",
|
|
9
|
+
pending: "tasks:rpc:pending",
|
|
10
|
+
clean: "tasks:rpc:clean",
|
|
11
|
+
};
|
|
12
|
+
/** Cross-extension RPC channels served by @tintinweb/pi-subagents. */
|
|
13
|
+
export const SUBAGENTS_RPC = {
|
|
14
|
+
ping: "subagents:rpc:ping",
|
|
15
|
+
spawn: "subagents:rpc:spawn",
|
|
16
|
+
stop: "subagents:rpc:stop",
|
|
17
|
+
};
|
|
18
|
+
/** Broadcast (fire-and-forget) task lifecycle events. */
|
|
19
|
+
export const TASK_EVENTS = {
|
|
20
|
+
ready: "tasks:ready",
|
|
21
|
+
created: "tasks:created",
|
|
22
|
+
started: "tasks:started",
|
|
23
|
+
completed: "tasks:completed",
|
|
24
|
+
reopened: "tasks:reopened",
|
|
25
|
+
updated: "tasks:updated",
|
|
26
|
+
deleted: "tasks:deleted",
|
|
27
|
+
};
|
|
28
|
+
export function replyChannel(channel, requestId) {
|
|
29
|
+
return `${channel}:reply:${requestId}`;
|
|
30
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { type PingReply } from "./channels.js";
|
|
2
|
+
/**
|
|
3
|
+
* Cross-extension request/reply over the in-process pi event bus.
|
|
4
|
+
*
|
|
5
|
+
* Wire contract (matches the pi-mono convention used by @tintinweb/pi-subagents):
|
|
6
|
+
* request: emit(channel, { requestId, ...params })
|
|
7
|
+
* reply: emit(`${channel}:reply:${requestId}`, RpcReply)
|
|
8
|
+
*/
|
|
9
|
+
export declare const PROTOCOL_VERSION = 2;
|
|
10
|
+
export type RpcReply<T = void> = {
|
|
11
|
+
success: true;
|
|
12
|
+
data?: T;
|
|
13
|
+
} | {
|
|
14
|
+
success: false;
|
|
15
|
+
error: string;
|
|
16
|
+
};
|
|
17
|
+
export declare class RpcError extends Error {
|
|
18
|
+
readonly channel: string;
|
|
19
|
+
readonly timedOut: boolean;
|
|
20
|
+
constructor(channel: string, message: string, timedOut?: boolean);
|
|
21
|
+
}
|
|
22
|
+
/** Minimal event-bus surface — satisfied by pi.events. */
|
|
23
|
+
export interface RpcEventBus {
|
|
24
|
+
on(event: string, handler: (data: unknown) => void): () => void;
|
|
25
|
+
emit(event: string, data: unknown): void;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Call a remote handler and await its reply.
|
|
29
|
+
*
|
|
30
|
+
* Rejects with RpcError on a failure envelope or on timeout (timedOut=true).
|
|
31
|
+
* Callers that treat failure as "fall back" wrap this in try/catch at the
|
|
32
|
+
* layer that knows the fallback — the RPC layer itself never returns sentinels.
|
|
33
|
+
*/
|
|
34
|
+
export declare function rpcCall<T = void>(bus: RpcEventBus, channel: string, params?: Record<string, unknown>, timeoutMs?: number): Promise<T>;
|
|
35
|
+
/**
|
|
36
|
+
* Detection probe: resolves undefined when nobody answers the ping channel.
|
|
37
|
+
* Absence of a provider is expected, not an error.
|
|
38
|
+
*
|
|
39
|
+
* First reply wins. If the probing extension also serves this channel, its
|
|
40
|
+
* own reply will settle the probe — in that case listen on the reply channel
|
|
41
|
+
* directly for the full window and filter by PingReply.provider instead.
|
|
42
|
+
*/
|
|
43
|
+
export declare function rpcProbe(bus: RpcEventBus, pingChannel: string, timeoutMs?: number): Promise<PingReply | undefined>;
|
|
44
|
+
export interface HandleRpcOptions {
|
|
45
|
+
/**
|
|
46
|
+
* When false, the handler is a silent no-op: another extension owns the
|
|
47
|
+
* channel and will reply; emitting a failure here would race its reply.
|
|
48
|
+
*/
|
|
49
|
+
enabled?: () => boolean;
|
|
50
|
+
debug?: (...args: unknown[]) => void;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Register a server handler for one RPC channel. Returns the unsubscribe fn.
|
|
54
|
+
*
|
|
55
|
+
* Malformed requests get a failure reply, never a silent drop — the only
|
|
56
|
+
* silent case is a missing requestId, which leaves no reply address.
|
|
57
|
+
* A thrown/rejected fn becomes a failure envelope.
|
|
58
|
+
*/
|
|
59
|
+
export declare function handleRpc<P extends object, R = unknown>(bus: RpcEventBus, channel: string, fn: (params: P) => R | Promise<R>, opts?: HandleRpcOptions): () => void;
|