@pi-agents/loop 0.1.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/LICENSE +21 -0
- package/README.md +93 -0
- package/config/default.json +11 -0
- package/dist/cron.d.ts +39 -0
- package/dist/cron.js +189 -0
- package/dist/cron.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +175 -0
- package/dist/index.js.map +1 -0
- package/dist/jitter.d.ts +28 -0
- package/dist/jitter.js +50 -0
- package/dist/jitter.js.map +1 -0
- package/dist/parse-args.d.ts +13 -0
- package/dist/parse-args.js +51 -0
- package/dist/parse-args.js.map +1 -0
- package/dist/scheduler.d.ts +31 -0
- package/dist/scheduler.js +142 -0
- package/dist/scheduler.js.map +1 -0
- package/dist/store.d.ts +17 -0
- package/dist/store.js +104 -0
- package/dist/store.js.map +1 -0
- package/dist/tools/cron-tools.d.ts +7 -0
- package/dist/tools/cron-tools.js +129 -0
- package/dist/tools/cron-tools.js.map +1 -0
- package/dist/types.d.ts +27 -0
- package/dist/types.js +24 -0
- package/dist/types.js.map +1 -0
- package/package.json +78 -0
- package/skills/loop/SKILL.md +52 -0
- package/skills/schedule/SKILL.md +42 -0
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* /loop argument parser.
|
|
3
|
+
*
|
|
4
|
+
* Priority:
|
|
5
|
+
* 1. Leading interval token: "5m check the deploy"
|
|
6
|
+
* 2. Trailing "every" clause: "check the deploy every 20m"
|
|
7
|
+
* 3. Default 10m
|
|
8
|
+
*/
|
|
9
|
+
const INTERVAL_RE = /^\d+[smhd]$/;
|
|
10
|
+
const TRAILING_EVERY_RE = /\s+every\s+(\d+)\s*([smhd]|seconds?|minutes?|hours?|days?)\s*$/i;
|
|
11
|
+
const UNIT_MAP = {
|
|
12
|
+
s: "s", second: "s", seconds: "s",
|
|
13
|
+
m: "m", minute: "m", minutes: "m",
|
|
14
|
+
h: "h", hour: "h", hours: "h",
|
|
15
|
+
d: "d", day: "d", days: "d",
|
|
16
|
+
};
|
|
17
|
+
export function parseLoopArgs(args) {
|
|
18
|
+
const trimmed = args.trim();
|
|
19
|
+
if (!trimmed)
|
|
20
|
+
return null;
|
|
21
|
+
const tokens = trimmed.split(/\s+/);
|
|
22
|
+
// Rule 1: Leading interval token
|
|
23
|
+
if (tokens.length >= 2 && INTERVAL_RE.test(tokens[0])) {
|
|
24
|
+
return {
|
|
25
|
+
interval: tokens[0],
|
|
26
|
+
prompt: tokens.slice(1).join(" "),
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
// Rule 2: Trailing "every" clause
|
|
30
|
+
const everyMatch = trimmed.match(TRAILING_EVERY_RE);
|
|
31
|
+
if (everyMatch) {
|
|
32
|
+
const n = everyMatch[1];
|
|
33
|
+
const unitRaw = everyMatch[2].toLowerCase();
|
|
34
|
+
const unit = UNIT_MAP[unitRaw];
|
|
35
|
+
if (unit) {
|
|
36
|
+
const prompt = trimmed.slice(0, everyMatch.index).trim();
|
|
37
|
+
if (prompt) {
|
|
38
|
+
return {
|
|
39
|
+
interval: `${n}${unit}`,
|
|
40
|
+
prompt,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
// Rule 3: Default 10m
|
|
46
|
+
return {
|
|
47
|
+
interval: "10m",
|
|
48
|
+
prompt: trimmed,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=parse-args.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parse-args.js","sourceRoot":"","sources":["../src/parse-args.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAOH,MAAM,WAAW,GAAG,aAAa,CAAC;AAClC,MAAM,iBAAiB,GAAG,iEAAiE,CAAC;AAE5F,MAAM,QAAQ,GAA2B;IACvC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG;IACjC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG;IACjC,CAAC,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE,KAAK,EAAE,GAAG;IAC7B,CAAC,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG;CAC5B,CAAC;AAEF,MAAM,UAAU,aAAa,CAAC,IAAY;IACxC,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;IAC5B,IAAI,CAAC,OAAO;QAAE,OAAO,IAAI,CAAC;IAE1B,MAAM,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IAEpC,iCAAiC;IACjC,IAAI,MAAM,CAAC,MAAM,IAAI,CAAC,IAAI,WAAW,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QACtD,OAAO;YACL,QAAQ,EAAE,MAAM,CAAC,CAAC,CAAC;YACnB,MAAM,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC;SAClC,CAAC;IACJ,CAAC;IAED,kCAAkC;IAClC,MAAM,UAAU,GAAG,OAAO,CAAC,KAAK,CAAC,iBAAiB,CAAC,CAAC;IACpD,IAAI,UAAU,EAAE,CAAC;QACf,MAAM,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC;QACxB,MAAM,OAAO,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;QAC5C,MAAM,IAAI,GAAG,QAAQ,CAAC,OAAO,CAAC,CAAC;QAC/B,IAAI,IAAI,EAAE,CAAC;YACT,MAAM,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,UAAU,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,CAAC;YACzD,IAAI,MAAM,EAAE,CAAC;gBACX,OAAO;oBACL,QAAQ,EAAE,GAAG,CAAC,GAAG,IAAI,EAAE;oBACvB,MAAM;iBACP,CAAC;YACJ,CAAC;QACH,CAAC;IACH,CAAC;IAED,sBAAsB;IACtB,OAAO;QACL,QAAQ,EAAE,KAAK;QACf,MAAM,EAAE,OAAO;KAChB,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core scheduler engine.
|
|
3
|
+
*
|
|
4
|
+
* 1-second tick loop that checks all tasks, gates on agent idle state,
|
|
5
|
+
* fires prompts via pi.sendUserMessage(), and handles auto-expiry.
|
|
6
|
+
*/
|
|
7
|
+
import type { ExtensionAPI, ExtensionContext } from "@mariozechner/pi-coding-agent";
|
|
8
|
+
import type { LoopConfig } from "./types.js";
|
|
9
|
+
export declare class LoopScheduler {
|
|
10
|
+
private interval;
|
|
11
|
+
private isAgentBusy;
|
|
12
|
+
private pendingFires;
|
|
13
|
+
private pi;
|
|
14
|
+
private config;
|
|
15
|
+
private cwd;
|
|
16
|
+
private ctx;
|
|
17
|
+
constructor(pi: ExtensionAPI, config: LoopConfig, cwd: string);
|
|
18
|
+
setContext(ctx: ExtensionContext): void;
|
|
19
|
+
start(): void;
|
|
20
|
+
stop(): void;
|
|
21
|
+
setBusy(): void;
|
|
22
|
+
setIdle(): void;
|
|
23
|
+
private check;
|
|
24
|
+
private shouldFire;
|
|
25
|
+
private fire;
|
|
26
|
+
private drainPendingFires;
|
|
27
|
+
private isAgedOut;
|
|
28
|
+
private updateStatus;
|
|
29
|
+
/** Refresh status bar (called externally after task changes) */
|
|
30
|
+
refreshStatus(): void;
|
|
31
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core scheduler engine.
|
|
3
|
+
*
|
|
4
|
+
* 1-second tick loop that checks all tasks, gates on agent idle state,
|
|
5
|
+
* fires prompts via pi.sendUserMessage(), and handles auto-expiry.
|
|
6
|
+
*/
|
|
7
|
+
import { nextCronRunMs, cronGapMs } from "./cron.js";
|
|
8
|
+
import { recurringJitterMs, oneShotJitterMs } from "./jitter.js";
|
|
9
|
+
import { getAllTasks, getTask, updateTask, removeTask, writeDurableTasks, } from "./store.js";
|
|
10
|
+
export class LoopScheduler {
|
|
11
|
+
interval = null;
|
|
12
|
+
isAgentBusy = false;
|
|
13
|
+
pendingFires = [];
|
|
14
|
+
pi;
|
|
15
|
+
config;
|
|
16
|
+
cwd;
|
|
17
|
+
ctx = null;
|
|
18
|
+
constructor(pi, config, cwd) {
|
|
19
|
+
this.pi = pi;
|
|
20
|
+
this.config = config;
|
|
21
|
+
this.cwd = cwd;
|
|
22
|
+
}
|
|
23
|
+
setContext(ctx) {
|
|
24
|
+
this.ctx = ctx;
|
|
25
|
+
}
|
|
26
|
+
start() {
|
|
27
|
+
if (this.interval)
|
|
28
|
+
return;
|
|
29
|
+
this.interval = setInterval(() => this.check(), this.config.checkIntervalMs);
|
|
30
|
+
}
|
|
31
|
+
stop() {
|
|
32
|
+
if (this.interval) {
|
|
33
|
+
clearInterval(this.interval);
|
|
34
|
+
this.interval = null;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
setBusy() {
|
|
38
|
+
this.isAgentBusy = true;
|
|
39
|
+
}
|
|
40
|
+
setIdle() {
|
|
41
|
+
this.isAgentBusy = false;
|
|
42
|
+
this.drainPendingFires();
|
|
43
|
+
}
|
|
44
|
+
check() {
|
|
45
|
+
const now = Date.now();
|
|
46
|
+
for (const task of getAllTasks()) {
|
|
47
|
+
if (this.shouldFire(task, now)) {
|
|
48
|
+
if (this.isAgentBusy) {
|
|
49
|
+
// Queue for later, but only if not already queued
|
|
50
|
+
if (!this.pendingFires.includes(task.id)) {
|
|
51
|
+
this.pendingFires.push(task.id);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
this.fire(task);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
shouldFire(task, now) {
|
|
61
|
+
// Compute the base next fire time
|
|
62
|
+
const anchor = task.lastFiredAt ?? task.createdAt;
|
|
63
|
+
const baseNext = nextCronRunMs(task.cron, anchor);
|
|
64
|
+
if (baseNext === null)
|
|
65
|
+
return false;
|
|
66
|
+
let fireTime;
|
|
67
|
+
if (task.recurring) {
|
|
68
|
+
// Forward jitter for recurring tasks
|
|
69
|
+
const gap = cronGapMs(task.cron, anchor);
|
|
70
|
+
const jitter = gap ? recurringJitterMs(task, gap, this.config) : 0;
|
|
71
|
+
fireTime = baseNext + jitter;
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
// Backward jitter for one-shots
|
|
75
|
+
const jitter = oneShotJitterMs(task, baseNext, this.config);
|
|
76
|
+
fireTime = baseNext - jitter;
|
|
77
|
+
}
|
|
78
|
+
return now >= fireTime;
|
|
79
|
+
}
|
|
80
|
+
fire(task) {
|
|
81
|
+
// Notify UI
|
|
82
|
+
if (this.ctx?.hasUI) {
|
|
83
|
+
const label = task.label || task.prompt.slice(0, 40);
|
|
84
|
+
this.ctx.ui.notify(`Loop firing: ${label}`, "info");
|
|
85
|
+
}
|
|
86
|
+
// Inject prompt as user message
|
|
87
|
+
this.pi.sendUserMessage(task.prompt);
|
|
88
|
+
// Update fire time
|
|
89
|
+
task.lastFiredAt = Date.now();
|
|
90
|
+
if (task.recurring) {
|
|
91
|
+
if (this.isAgedOut(task)) {
|
|
92
|
+
// Final fire — remove the task
|
|
93
|
+
removeTask(task.id);
|
|
94
|
+
if (this.ctx?.hasUI) {
|
|
95
|
+
this.ctx.ui.notify(`Loop ${task.id} expired after 7 days`, "warning");
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
updateTask(task);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
// One-shot: remove after firing
|
|
104
|
+
removeTask(task.id);
|
|
105
|
+
}
|
|
106
|
+
// Persist durable tasks
|
|
107
|
+
if (task.durable) {
|
|
108
|
+
writeDurableTasks(this.cwd, this.config).catch(() => { });
|
|
109
|
+
}
|
|
110
|
+
this.updateStatus();
|
|
111
|
+
}
|
|
112
|
+
drainPendingFires() {
|
|
113
|
+
const ids = this.pendingFires.splice(0);
|
|
114
|
+
for (const id of ids) {
|
|
115
|
+
const task = getTask(id);
|
|
116
|
+
if (task) {
|
|
117
|
+
this.fire(task);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
isAgedOut(task) {
|
|
122
|
+
if (this.config.recurringMaxAgeMs <= 0)
|
|
123
|
+
return false;
|
|
124
|
+
return Date.now() - task.createdAt >= this.config.recurringMaxAgeMs;
|
|
125
|
+
}
|
|
126
|
+
updateStatus() {
|
|
127
|
+
if (!this.ctx?.hasUI)
|
|
128
|
+
return;
|
|
129
|
+
const count = getAllTasks().length;
|
|
130
|
+
if (count > 0) {
|
|
131
|
+
this.ctx.ui.setStatus("pi-loop", `${count} loop${count === 1 ? "" : "s"} active`);
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
this.ctx.ui.setStatus("pi-loop", undefined);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
/** Refresh status bar (called externally after task changes) */
|
|
138
|
+
refreshStatus() {
|
|
139
|
+
this.updateStatus();
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
//# sourceMappingURL=scheduler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scheduler.js","sourceRoot":"","sources":["../src/scheduler.ts"],"names":[],"mappings":"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"}
|
package/dist/store.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LoopTask CRUD — in-memory Map + durable .pi-loop.json persistence.
|
|
3
|
+
* Includes file-based O_EXCL locking for multi-instance safety.
|
|
4
|
+
*/
|
|
5
|
+
import type { LoopConfig, LoopTask } from "./types.js";
|
|
6
|
+
export declare function generateTaskId(): string;
|
|
7
|
+
export declare function addTask(task: LoopTask): void;
|
|
8
|
+
export declare function removeTask(id: string): boolean;
|
|
9
|
+
export declare function getTask(id: string): LoopTask | undefined;
|
|
10
|
+
export declare function getAllTasks(): LoopTask[];
|
|
11
|
+
export declare function getTaskCount(): number;
|
|
12
|
+
export declare function updateTask(task: LoopTask): void;
|
|
13
|
+
export declare function clearAllTasks(): void;
|
|
14
|
+
export declare function loadDurableTasks(cwd: string, config: LoopConfig): Promise<LoopTask[]>;
|
|
15
|
+
export declare function writeDurableTasks(cwd: string, config: LoopConfig): Promise<void>;
|
|
16
|
+
export declare function acquireLock(cwd: string, config: LoopConfig): Promise<boolean>;
|
|
17
|
+
export declare function releaseLock(cwd: string, config: LoopConfig): Promise<void>;
|
package/dist/store.js
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LoopTask CRUD — in-memory Map + durable .pi-loop.json persistence.
|
|
3
|
+
* Includes file-based O_EXCL locking for multi-instance safety.
|
|
4
|
+
*/
|
|
5
|
+
import { readFile, writeFile, open, unlink } from "node:fs/promises";
|
|
6
|
+
import { join } from "node:path";
|
|
7
|
+
import { randomUUID } from "node:crypto";
|
|
8
|
+
// --- In-memory store ---
|
|
9
|
+
const tasks = new Map();
|
|
10
|
+
export function generateTaskId() {
|
|
11
|
+
return randomUUID().replace(/-/g, "").slice(0, 8);
|
|
12
|
+
}
|
|
13
|
+
export function addTask(task) {
|
|
14
|
+
tasks.set(task.id, task);
|
|
15
|
+
}
|
|
16
|
+
export function removeTask(id) {
|
|
17
|
+
return tasks.delete(id);
|
|
18
|
+
}
|
|
19
|
+
export function getTask(id) {
|
|
20
|
+
return tasks.get(id);
|
|
21
|
+
}
|
|
22
|
+
export function getAllTasks() {
|
|
23
|
+
return Array.from(tasks.values());
|
|
24
|
+
}
|
|
25
|
+
export function getTaskCount() {
|
|
26
|
+
return tasks.size;
|
|
27
|
+
}
|
|
28
|
+
export function updateTask(task) {
|
|
29
|
+
tasks.set(task.id, task);
|
|
30
|
+
}
|
|
31
|
+
export function clearAllTasks() {
|
|
32
|
+
tasks.clear();
|
|
33
|
+
}
|
|
34
|
+
// --- Durable file persistence ---
|
|
35
|
+
function durablePath(cwd, config) {
|
|
36
|
+
return join(cwd, config.durableFilePath);
|
|
37
|
+
}
|
|
38
|
+
function lockPath(cwd, config) {
|
|
39
|
+
return durablePath(cwd, config) + ".lock";
|
|
40
|
+
}
|
|
41
|
+
export async function loadDurableTasks(cwd, config) {
|
|
42
|
+
try {
|
|
43
|
+
const raw = await readFile(durablePath(cwd, config), "utf-8");
|
|
44
|
+
const data = JSON.parse(raw);
|
|
45
|
+
if (!Array.isArray(data.tasks))
|
|
46
|
+
return [];
|
|
47
|
+
return data.tasks;
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
return [];
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
export async function writeDurableTasks(cwd, config) {
|
|
54
|
+
const durableTasks = getAllTasks().filter((t) => t.durable);
|
|
55
|
+
const data = { tasks: durableTasks };
|
|
56
|
+
await writeFile(durablePath(cwd, config), JSON.stringify(data, null, 2) + "\n", "utf-8");
|
|
57
|
+
}
|
|
58
|
+
const STALE_LOCK_MS = 30_000;
|
|
59
|
+
export async function acquireLock(cwd, config) {
|
|
60
|
+
const path = lockPath(cwd, config);
|
|
61
|
+
const content = {
|
|
62
|
+
pid: process.pid,
|
|
63
|
+
acquiredAt: Date.now(),
|
|
64
|
+
};
|
|
65
|
+
try {
|
|
66
|
+
const fd = await open(path, "wx");
|
|
67
|
+
await fd.writeFile(JSON.stringify(content));
|
|
68
|
+
await fd.close();
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
catch (err) {
|
|
72
|
+
if (err.code !== "EEXIST")
|
|
73
|
+
return false;
|
|
74
|
+
// Check for stale lock
|
|
75
|
+
try {
|
|
76
|
+
const raw = await readFile(path, "utf-8");
|
|
77
|
+
const lock = JSON.parse(raw);
|
|
78
|
+
if (Date.now() - lock.acquiredAt > STALE_LOCK_MS) {
|
|
79
|
+
// Stale lock — remove and retry
|
|
80
|
+
await unlink(path);
|
|
81
|
+
return acquireLock(cwd, config);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
// Can't read lock file — someone else has it
|
|
86
|
+
}
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
export async function releaseLock(cwd, config) {
|
|
91
|
+
try {
|
|
92
|
+
const path = lockPath(cwd, config);
|
|
93
|
+
const raw = await readFile(path, "utf-8");
|
|
94
|
+
const lock = JSON.parse(raw);
|
|
95
|
+
// Only release if we own it
|
|
96
|
+
if (lock.pid === process.pid) {
|
|
97
|
+
await unlink(path);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
// Lock file gone or can't read — fine
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
//# sourceMappingURL=store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.js","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LLM-callable tools: cron_create, cron_delete, cron_list.
|
|
3
|
+
*/
|
|
4
|
+
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
5
|
+
import type { LoopConfig } from "../types.js";
|
|
6
|
+
import type { LoopScheduler } from "../scheduler.js";
|
|
7
|
+
export declare function registerCronTools(pi: ExtensionAPI, scheduler: LoopScheduler, config: LoopConfig, getCwd: () => string): void;
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LLM-callable tools: cron_create, cron_delete, cron_list.
|
|
3
|
+
*/
|
|
4
|
+
import { Type } from "@sinclair/typebox";
|
|
5
|
+
import { parseCronExpression, nextCronRunMs, cronToHuman } from "../cron.js";
|
|
6
|
+
import { addTask, removeTask, getAllTasks, getTaskCount, generateTaskId, writeDurableTasks, } from "../store.js";
|
|
7
|
+
function result(text) {
|
|
8
|
+
return { content: [{ type: "text", text }], details: undefined };
|
|
9
|
+
}
|
|
10
|
+
export function registerCronTools(pi, scheduler, config, getCwd) {
|
|
11
|
+
// --- CronCreate ---
|
|
12
|
+
pi.registerTool({
|
|
13
|
+
name: "cron_create",
|
|
14
|
+
label: "Create Scheduled Task",
|
|
15
|
+
description: "Schedule a prompt to run at a future time, either recurring on a cron schedule or once at a specific time. " +
|
|
16
|
+
"The cron field uses standard 5-field format (minute hour day-of-month month day-of-week) in local time. " +
|
|
17
|
+
"Examples: '*/5 * * * *' = every 5 minutes, '0 */2 * * *' = every 2 hours, '30 14 * * *' = daily at 2:30 PM.",
|
|
18
|
+
parameters: Type.Object({
|
|
19
|
+
cron: Type.String({
|
|
20
|
+
description: "5-field cron expression in local time (minute hour dom month dow)",
|
|
21
|
+
}),
|
|
22
|
+
prompt: Type.String({
|
|
23
|
+
description: "Prompt to enqueue at fire time",
|
|
24
|
+
}),
|
|
25
|
+
recurring: Type.Boolean({
|
|
26
|
+
default: true,
|
|
27
|
+
description: "true = fire on every match; false = one-shot then delete",
|
|
28
|
+
}),
|
|
29
|
+
durable: Type.Boolean({
|
|
30
|
+
default: false,
|
|
31
|
+
description: "true = persist to .pi-loop.json across sessions; false = session-only",
|
|
32
|
+
}),
|
|
33
|
+
}),
|
|
34
|
+
promptSnippet: "cron_create — schedule a prompt to run on a cron schedule (recurring or one-shot)",
|
|
35
|
+
async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
|
|
36
|
+
const parsed = parseCronExpression(params.cron);
|
|
37
|
+
if (!parsed) {
|
|
38
|
+
return result(`Invalid cron expression: "${params.cron}". Use 5-field format: minute hour day-of-month month day-of-week`);
|
|
39
|
+
}
|
|
40
|
+
const nextRun = nextCronRunMs(params.cron, Date.now());
|
|
41
|
+
if (nextRun === null) {
|
|
42
|
+
return result(`Cron expression "${params.cron}" does not match any date in the next year.`);
|
|
43
|
+
}
|
|
44
|
+
if (getTaskCount() >= config.maxJobs) {
|
|
45
|
+
return result(`Maximum of ${config.maxJobs} scheduled tasks reached. Delete some tasks first.`);
|
|
46
|
+
}
|
|
47
|
+
const task = {
|
|
48
|
+
id: generateTaskId(),
|
|
49
|
+
cron: params.cron,
|
|
50
|
+
prompt: params.prompt,
|
|
51
|
+
createdAt: Date.now(),
|
|
52
|
+
recurring: params.recurring,
|
|
53
|
+
durable: params.durable,
|
|
54
|
+
};
|
|
55
|
+
addTask(task);
|
|
56
|
+
if (task.durable) {
|
|
57
|
+
await writeDurableTasks(getCwd(), config);
|
|
58
|
+
}
|
|
59
|
+
scheduler.refreshStatus();
|
|
60
|
+
const human = cronToHuman(params.cron);
|
|
61
|
+
const nextDate = new Date(nextRun).toLocaleString();
|
|
62
|
+
const expiryNote = params.recurring
|
|
63
|
+
? ` Auto-expires after ${Math.round(config.recurringMaxAgeMs / (24 * 60 * 60 * 1000))} days.`
|
|
64
|
+
: " One-shot: will be deleted after firing.";
|
|
65
|
+
return result([
|
|
66
|
+
`Scheduled task created.`,
|
|
67
|
+
` ID: ${task.id}`,
|
|
68
|
+
` Schedule: ${human} (${params.cron})`,
|
|
69
|
+
` Next fire: ${nextDate}`,
|
|
70
|
+
` Prompt: ${task.prompt}`,
|
|
71
|
+
` Recurring: ${params.recurring}`,
|
|
72
|
+
` Durable: ${params.durable}`,
|
|
73
|
+
expiryNote,
|
|
74
|
+
`Cancel with: cron_delete { id: "${task.id}" } or /loop-kill ${task.id}`,
|
|
75
|
+
].join("\n"));
|
|
76
|
+
},
|
|
77
|
+
});
|
|
78
|
+
// --- CronDelete ---
|
|
79
|
+
pi.registerTool({
|
|
80
|
+
name: "cron_delete",
|
|
81
|
+
label: "Cancel Scheduled Task",
|
|
82
|
+
description: "Cancel a scheduled cron task by ID. Use cron_list to see active tasks.",
|
|
83
|
+
parameters: Type.Object({
|
|
84
|
+
id: Type.String({ description: "Task ID to cancel" }),
|
|
85
|
+
}),
|
|
86
|
+
promptSnippet: "cron_delete — cancel a scheduled task by ID",
|
|
87
|
+
async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
|
|
88
|
+
const removed = removeTask(params.id);
|
|
89
|
+
if (!removed) {
|
|
90
|
+
return result(`No task found with ID "${params.id}".`);
|
|
91
|
+
}
|
|
92
|
+
await writeDurableTasks(getCwd(), config).catch(() => { });
|
|
93
|
+
scheduler.refreshStatus();
|
|
94
|
+
return result(`Task ${params.id} cancelled.`);
|
|
95
|
+
},
|
|
96
|
+
});
|
|
97
|
+
// --- CronList ---
|
|
98
|
+
pi.registerTool({
|
|
99
|
+
name: "cron_list",
|
|
100
|
+
label: "List Scheduled Tasks",
|
|
101
|
+
description: "List all active scheduled cron tasks with their IDs, schedules, and next fire times.",
|
|
102
|
+
parameters: Type.Object({}),
|
|
103
|
+
promptSnippet: "cron_list — list all active scheduled tasks",
|
|
104
|
+
async execute(_toolCallId, _params, _signal, _onUpdate, _ctx) {
|
|
105
|
+
const tasks = getAllTasks();
|
|
106
|
+
if (tasks.length === 0) {
|
|
107
|
+
return result("No scheduled tasks.");
|
|
108
|
+
}
|
|
109
|
+
const now = Date.now();
|
|
110
|
+
const lines = tasks.map((t) => {
|
|
111
|
+
const human = cronToHuman(t.cron);
|
|
112
|
+
const next = nextCronRunMs(t.cron, t.lastFiredAt ?? t.createdAt);
|
|
113
|
+
const nextStr = next ? new Date(next).toLocaleString() : "unknown";
|
|
114
|
+
const age = Math.round((now - t.createdAt) / (60 * 1000));
|
|
115
|
+
const flags = [
|
|
116
|
+
t.recurring ? "recurring" : "one-shot",
|
|
117
|
+
t.durable ? "durable" : "session-only",
|
|
118
|
+
].join(", ");
|
|
119
|
+
return [
|
|
120
|
+
`[${t.id}] ${human} (${t.cron})`,
|
|
121
|
+
` Prompt: ${t.prompt}`,
|
|
122
|
+
` Next: ${nextStr} | Created: ${age}m ago | ${flags}`,
|
|
123
|
+
].join("\n");
|
|
124
|
+
});
|
|
125
|
+
return result(`${tasks.length} scheduled task${tasks.length === 1 ? "" : "s"}:\n\n${lines.join("\n\n")}`);
|
|
126
|
+
},
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
//# sourceMappingURL=cron-tools.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cron-tools.js","sourceRoot":"","sources":["../../src/tools/cron-tools.ts"],"names":[],"mappings":"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"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { type Static } from "@sinclair/typebox";
|
|
2
|
+
export declare const LoopTaskSchema: import("@sinclair/typebox").TObject<{
|
|
3
|
+
id: import("@sinclair/typebox").TString;
|
|
4
|
+
cron: import("@sinclair/typebox").TString;
|
|
5
|
+
prompt: import("@sinclair/typebox").TString;
|
|
6
|
+
createdAt: import("@sinclair/typebox").TNumber;
|
|
7
|
+
lastFiredAt: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TNumber>;
|
|
8
|
+
recurring: import("@sinclair/typebox").TBoolean;
|
|
9
|
+
durable: import("@sinclair/typebox").TBoolean;
|
|
10
|
+
label: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
11
|
+
}>;
|
|
12
|
+
export type LoopTask = Static<typeof LoopTaskSchema>;
|
|
13
|
+
export interface LoopConfig {
|
|
14
|
+
maxJobs: number;
|
|
15
|
+
recurringMaxAgeMs: number;
|
|
16
|
+
recurringJitterFrac: number;
|
|
17
|
+
recurringJitterCapMs: number;
|
|
18
|
+
oneShotJitterMaxMs: number;
|
|
19
|
+
oneShotJitterFloorMs: number;
|
|
20
|
+
oneShotJitterMinuteMod: number;
|
|
21
|
+
checkIntervalMs: number;
|
|
22
|
+
durableFilePath: string;
|
|
23
|
+
}
|
|
24
|
+
export declare const DEFAULT_CONFIG: LoopConfig;
|
|
25
|
+
export interface DurableFile {
|
|
26
|
+
tasks: LoopTask[];
|
|
27
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Type } from "@sinclair/typebox";
|
|
2
|
+
// --- LoopTask schema (TypeBox for tool parameters) ---
|
|
3
|
+
export const LoopTaskSchema = Type.Object({
|
|
4
|
+
id: Type.String(),
|
|
5
|
+
cron: Type.String(),
|
|
6
|
+
prompt: Type.String(),
|
|
7
|
+
createdAt: Type.Number(),
|
|
8
|
+
lastFiredAt: Type.Optional(Type.Number()),
|
|
9
|
+
recurring: Type.Boolean(),
|
|
10
|
+
durable: Type.Boolean(),
|
|
11
|
+
label: Type.Optional(Type.String()),
|
|
12
|
+
});
|
|
13
|
+
export const DEFAULT_CONFIG = {
|
|
14
|
+
maxJobs: 50,
|
|
15
|
+
recurringMaxAgeMs: 7 * 24 * 60 * 60 * 1000,
|
|
16
|
+
recurringJitterFrac: 0.1,
|
|
17
|
+
recurringJitterCapMs: 15 * 60 * 1000,
|
|
18
|
+
oneShotJitterMaxMs: 90 * 1000,
|
|
19
|
+
oneShotJitterFloorMs: 0,
|
|
20
|
+
oneShotJitterMinuteMod: 30,
|
|
21
|
+
checkIntervalMs: 1000,
|
|
22
|
+
durableFilePath: ".pi-loop.json",
|
|
23
|
+
};
|
|
24
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAe,MAAM,mBAAmB,CAAC;AAEtD,wDAAwD;AAExD,MAAM,CAAC,MAAM,cAAc,GAAG,IAAI,CAAC,MAAM,CAAC;IACxC,EAAE,EAAE,IAAI,CAAC,MAAM,EAAE;IACjB,IAAI,EAAE,IAAI,CAAC,MAAM,EAAE;IACnB,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE;IACrB,SAAS,EAAE,IAAI,CAAC,MAAM,EAAE;IACxB,WAAW,EAAE,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC;IACzC,SAAS,EAAE,IAAI,CAAC,OAAO,EAAE;IACzB,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE;IACvB,KAAK,EAAE,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC;CACpC,CAAC,CAAC;AAkBH,MAAM,CAAC,MAAM,cAAc,GAAe;IACxC,OAAO,EAAE,EAAE;IACX,iBAAiB,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI;IAC1C,mBAAmB,EAAE,GAAG;IACxB,oBAAoB,EAAE,EAAE,GAAG,EAAE,GAAG,IAAI;IACpC,kBAAkB,EAAE,EAAE,GAAG,IAAI;IAC7B,oBAAoB,EAAE,CAAC;IACvB,sBAAsB,EAAE,EAAE;IAC1B,eAAe,EAAE,IAAI;IACrB,eAAe,EAAE,eAAe;CACjC,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@pi-agents/loop",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Recurring prompt execution and cron scheduling for pi-agent — /loop, cron_create, cron_delete, cron_list",
|
|
5
|
+
"author": "ArtemisAI",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"main": "dist/index.js",
|
|
9
|
+
"types": "dist/index.d.ts",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"import": "./dist/index.js"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist/**",
|
|
18
|
+
"skills",
|
|
19
|
+
"config/default.json",
|
|
20
|
+
"README.md",
|
|
21
|
+
"LICENSE"
|
|
22
|
+
],
|
|
23
|
+
"pi": {
|
|
24
|
+
"extensions": [
|
|
25
|
+
"./dist/index.js"
|
|
26
|
+
],
|
|
27
|
+
"skills": [
|
|
28
|
+
"./skills"
|
|
29
|
+
]
|
|
30
|
+
},
|
|
31
|
+
"peerDependencies": {
|
|
32
|
+
"@mariozechner/pi-agent-core": "*",
|
|
33
|
+
"@mariozechner/pi-ai": "*",
|
|
34
|
+
"@mariozechner/pi-coding-agent": "*",
|
|
35
|
+
"@sinclair/typebox": "*"
|
|
36
|
+
},
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"@mariozechner/pi-agent-core": "*",
|
|
39
|
+
"@mariozechner/pi-ai": "*",
|
|
40
|
+
"@mariozechner/pi-coding-agent": "*",
|
|
41
|
+
"@sinclair/typebox": "^0.34.0",
|
|
42
|
+
"@types/node": "^22.0.0",
|
|
43
|
+
"typescript": "^5.7.0"
|
|
44
|
+
},
|
|
45
|
+
"publishConfig": {
|
|
46
|
+
"access": "public"
|
|
47
|
+
},
|
|
48
|
+
"engines": {
|
|
49
|
+
"node": ">=20"
|
|
50
|
+
},
|
|
51
|
+
"keywords": [
|
|
52
|
+
"pi",
|
|
53
|
+
"pi-package",
|
|
54
|
+
"pi-extension",
|
|
55
|
+
"pi-coding-agent",
|
|
56
|
+
"pi-agent",
|
|
57
|
+
"loop",
|
|
58
|
+
"cron",
|
|
59
|
+
"scheduler",
|
|
60
|
+
"recurring",
|
|
61
|
+
"automation"
|
|
62
|
+
],
|
|
63
|
+
"repository": {
|
|
64
|
+
"type": "git",
|
|
65
|
+
"url": "git+https://github.com/ArtemisAI/pi-loop.git"
|
|
66
|
+
},
|
|
67
|
+
"bugs": {
|
|
68
|
+
"url": "https://github.com/ArtemisAI/pi-loop/issues"
|
|
69
|
+
},
|
|
70
|
+
"homepage": "https://github.com/ArtemisAI/pi-loop#readme",
|
|
71
|
+
"scripts": {
|
|
72
|
+
"prebuild": "rm -rf dist",
|
|
73
|
+
"build": "tsc",
|
|
74
|
+
"lint": "tsc --noEmit",
|
|
75
|
+
"dev": "pi -e ./src/index.ts -s ./skills",
|
|
76
|
+
"prepublishOnly": "npm run build"
|
|
77
|
+
}
|
|
78
|
+
}
|