@alphacoder-v0/pi-loops 0.17.3
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/CHANGELOG.md +1989 -0
- package/LICENSE +21 -0
- package/README.md +370 -0
- package/README.zh-CN.md +606 -0
- package/docs/cli.md +252 -0
- package/docs/configuration.md +110 -0
- package/docs/design.md +155 -0
- package/docs/goal.md +59 -0
- package/docs/hooks.md +110 -0
- package/docs/loops.md +315 -0
- package/docs/mcp.md +90 -0
- package/docs/screenshot.png +0 -0
- package/docs/session-archive.md +64 -0
- package/docs/triggers.md +140 -0
- package/docs/troubleshooting.md +111 -0
- package/docs/web-ui-parity.md +305 -0
- package/examples/README.md +18 -0
- package/examples/mcp-notify-server.mjs +35 -0
- package/examples/mcp.toml +8 -0
- package/package.json +71 -0
- package/skills/pi-loops/SKILL.md +50 -0
- package/src/archive.ts +476 -0
- package/src/args.ts +217 -0
- package/src/cli-entry.mjs +23 -0
- package/src/cli.ts +610 -0
- package/src/config.ts +103 -0
- package/src/danger.ts +128 -0
- package/src/goal.ts +178 -0
- package/src/hooks.ts +436 -0
- package/src/host-control-channel.ts +243 -0
- package/src/host-control.ts +203 -0
- package/src/host-entry.mjs +14 -0
- package/src/host-runtime.ts +222 -0
- package/src/host.ts +329 -0
- package/src/inbox.ts +232 -0
- package/src/job-edit.ts +113 -0
- package/src/job-health.ts +29 -0
- package/src/lock.ts +193 -0
- package/src/log.ts +108 -0
- package/src/mcp-pool.ts +141 -0
- package/src/mcp.ts +885 -0
- package/src/paths.ts +33 -0
- package/src/pi-loops.ts +2292 -0
- package/src/pi-resolver.mjs +49 -0
- package/src/presence.ts +143 -0
- package/src/protocol.ts +198 -0
- package/src/redact.ts +61 -0
- package/src/register-pi.mjs +3 -0
- package/src/runner.ts +149 -0
- package/src/schedule.ts +431 -0
- package/src/scheduler.ts +981 -0
- package/src/sdk-runner.ts +536 -0
- package/src/share.ts +121 -0
- package/src/slots.ts +76 -0
- package/src/snapshot.ts +40 -0
- package/src/store.ts +599 -0
- package/src/subagent-guard.ts +47 -0
- package/src/thinking.ts +30 -0
- package/src/toml.ts +244 -0
- package/src/tools.ts +455 -0
- package/src/transcript.ts +84 -0
- package/src/trigger-runtime.ts +711 -0
- package/src/triggers.ts +558 -0
- package/src/trust.ts +47 -0
- package/src/ts-entry.mjs +67 -0
- package/src/ui-prefs.ts +84 -0
- package/src/version.ts +14 -0
- package/src/web.mjs +4484 -0
package/src/args.ts
ADDED
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Argument parsing for `/cron add`:
|
|
3
|
+
*
|
|
4
|
+
* /cron add [--stateful|--loop] [--inject] [--name <n>] [--cwd <dir>] [--model <m>] [--thinking <lvl>]
|
|
5
|
+
* [--tools a,b] [--timeout <dur>] [--catchup|--no-catchup] [--verify] [--checker-model <m>]
|
|
6
|
+
* <schedule> <prompt…>
|
|
7
|
+
*
|
|
8
|
+
* `--loop` is `--stateful` said the way the docs say it; `--inject` is the default (a plain job)
|
|
9
|
+
* said out loud, which is what a person reaches for when they are undoing a `--stateful` they typed.
|
|
10
|
+
*
|
|
11
|
+
* <schedule> is one quoted token ("0 9 * * *"), five bare cron tokens, an @alias,
|
|
12
|
+
* or `every <dur>` / `in <dur>` / `at <ISO>`. Everything after it is the prompt,
|
|
13
|
+
* taken verbatim from the original string (so quotes inside the prompt survive).
|
|
14
|
+
*/
|
|
15
|
+
import { normalizeScheduleAlias, parseDuration, parseSchedule, type Schedule } from "./schedule.ts";
|
|
16
|
+
import { requireThinkingLevel } from "./thinking.ts";
|
|
17
|
+
|
|
18
|
+
export interface Token {
|
|
19
|
+
value: string;
|
|
20
|
+
quoted: boolean;
|
|
21
|
+
end: number;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function tokenize(input: string): Token[] {
|
|
25
|
+
const tokens: Token[] = [];
|
|
26
|
+
let i = 0;
|
|
27
|
+
while (i < input.length) {
|
|
28
|
+
while (i < input.length && /\s/.test(input[i])) i++;
|
|
29
|
+
if (i >= input.length) break;
|
|
30
|
+
const quote = input[i] === '"' || input[i] === "'" ? input[i] : undefined;
|
|
31
|
+
if (quote) {
|
|
32
|
+
let j = i + 1;
|
|
33
|
+
let value = "";
|
|
34
|
+
while (j < input.length && input[j] !== quote) {
|
|
35
|
+
if (input[j] === "\\" && j + 1 < input.length) j++;
|
|
36
|
+
value += input[j++];
|
|
37
|
+
}
|
|
38
|
+
tokens.push({ value, quoted: true, end: Math.min(j + 1, input.length) });
|
|
39
|
+
i = j + 1;
|
|
40
|
+
} else {
|
|
41
|
+
let j = i;
|
|
42
|
+
while (j < input.length && !/\s/.test(input[j])) j++;
|
|
43
|
+
tokens.push({ value: input.slice(i, j), quoted: false, end: j });
|
|
44
|
+
i = j;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return tokens;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface AddArgs {
|
|
51
|
+
schedule: Schedule;
|
|
52
|
+
scheduleText: string;
|
|
53
|
+
prompt: string;
|
|
54
|
+
/** --stateful turns the job into a loop (sub-agent + notes + inbox); default is inject-and-run. */
|
|
55
|
+
stateful: boolean;
|
|
56
|
+
name?: string;
|
|
57
|
+
cwd?: string;
|
|
58
|
+
model?: string;
|
|
59
|
+
thinking?: string;
|
|
60
|
+
tools?: string[];
|
|
61
|
+
timeoutMs?: number;
|
|
62
|
+
/** undefined = default for the job kind (stateful: on, inject: off). */
|
|
63
|
+
catchUp?: boolean;
|
|
64
|
+
verify: boolean;
|
|
65
|
+
checkerModel?: string;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const VALUE_FLAGS = new Set(["--name", "--cwd", "--model", "--thinking", "--tools", "--timeout", "--checker-model"]);
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* The usage line for `/cron add`, printed by the errors below — one line, because it arrives as a
|
|
72
|
+
* notification and a line that wraps is read by nobody. Every flag the parser accepts is written
|
|
73
|
+
* down in the block at the top of this file and in `/cron help`; two are on this line, and they are
|
|
74
|
+
* the two that decide *what kind of job this is*: `--stateful` (a loop with notes and an /inbox,
|
|
75
|
+
* instead of a prompt injected into this chat) and `--verify` (a checker reviews its findings), and
|
|
76
|
+
* they are also the two `/cron set` cannot change afterwards. Everything else — `--name --cwd
|
|
77
|
+
* --model --thinking --tools --timeout --catchup|--no-catchup --checker-model` — tunes a job whose
|
|
78
|
+
* shape is already decided, and `--loop` / `--inject` are `--stateful` and its absence said out
|
|
79
|
+
* loud, so listing them here would be four spellings of one choice. `test/args.test.ts` holds that
|
|
80
|
+
* split to the parser.
|
|
81
|
+
*/
|
|
82
|
+
export const CRON_ADD_USAGE = 'usage: /cron add [--stateful] [--verify] "<minute hour dom month dow>" <prompt>; more flags: /cron help';
|
|
83
|
+
|
|
84
|
+
export function parseAddArgs(input: string, now: number = Date.now()): AddArgs {
|
|
85
|
+
const tokens = tokenize(input);
|
|
86
|
+
let idx = 0;
|
|
87
|
+
const out: Partial<AddArgs> & { stateful: boolean; catchUp?: boolean; verify: boolean } = { stateful: false, verify: false };
|
|
88
|
+
while (idx < tokens.length && tokens[idx].value.startsWith("--") && !tokens[idx].quoted) {
|
|
89
|
+
const flag = tokens[idx].value;
|
|
90
|
+
if (flag === "--inject") out.stateful = false;
|
|
91
|
+
else if (flag === "--no-catchup") out.catchUp = false;
|
|
92
|
+
else if (flag === "--catchup") out.catchUp = true;
|
|
93
|
+
else if (flag === "--verify") {
|
|
94
|
+
out.verify = true;
|
|
95
|
+
out.stateful = true; // a checker only makes sense for loop findings
|
|
96
|
+
}
|
|
97
|
+
else if (flag === "--stateful" || flag === "--loop") out.stateful = true;
|
|
98
|
+
else if (VALUE_FLAGS.has(flag)) {
|
|
99
|
+
const val = tokens[idx + 1]?.value;
|
|
100
|
+
if (val === undefined) throw new Error(`${flag} needs a value`);
|
|
101
|
+
idx++;
|
|
102
|
+
if (flag === "--name") out.name = val;
|
|
103
|
+
else if (flag === "--cwd") out.cwd = val;
|
|
104
|
+
else if (flag === "--model") out.model = val;
|
|
105
|
+
else if (flag === "--thinking") out.thinking = requireThinkingLevel(val);
|
|
106
|
+
else if (flag === "--tools") out.tools = val.split(",").map((s) => s.trim()).filter(Boolean);
|
|
107
|
+
else if (flag === "--timeout") out.timeoutMs = parseDuration(val);
|
|
108
|
+
else if (flag === "--checker-model") out.checkerModel = val;
|
|
109
|
+
} else throw new Error(`unknown flag ${flag}`);
|
|
110
|
+
idx++;
|
|
111
|
+
}
|
|
112
|
+
if (idx >= tokens.length) throw new Error(`missing schedule; ${CRON_ADD_USAGE}`);
|
|
113
|
+
|
|
114
|
+
let scheduleTokens: Token[];
|
|
115
|
+
const head = tokens[idx];
|
|
116
|
+
const lower = head.value.toLowerCase();
|
|
117
|
+
const singleAlias = normalizeScheduleAlias(head.value) !== undefined && !["every"].includes(lower);
|
|
118
|
+
if (head.quoted || lower.startsWith("@") || singleAlias) scheduleTokens = [head];
|
|
119
|
+
else if (lower === "every" || lower === "in" || lower === "at") scheduleTokens = tokens.slice(idx, idx + 2);
|
|
120
|
+
else scheduleTokens = tokens.slice(idx, idx + 5);
|
|
121
|
+
if (scheduleTokens.length < (head.quoted || lower.startsWith("@") || singleAlias ? 1 : lower === "every" || lower === "in" || lower === "at" ? 2 : 5)) {
|
|
122
|
+
throw new Error(`incomplete schedule (quote cron expressions: "0 9 * * *"); ${CRON_ADD_USAGE}`);
|
|
123
|
+
}
|
|
124
|
+
const scheduleText = scheduleTokens.map((t) => t.value).join(" ");
|
|
125
|
+
const schedule = parseSchedule(scheduleText, now);
|
|
126
|
+
const promptStart = scheduleTokens[scheduleTokens.length - 1].end;
|
|
127
|
+
const prompt = input.slice(promptStart).trim();
|
|
128
|
+
if (!prompt) throw new Error(`missing prompt after the schedule; ${CRON_ADD_USAGE}`);
|
|
129
|
+
return { ...out, schedule, scheduleText, prompt } as AddArgs;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** Split "<subcommand> <rest>" */
|
|
133
|
+
export function splitCommand(input: string): { sub: string; rest: string } {
|
|
134
|
+
const trimmed = input.trim();
|
|
135
|
+
const m = /^(\S+)\s*([\s\S]*)$/.exec(trimmed);
|
|
136
|
+
if (!m) return { sub: "", rest: "" };
|
|
137
|
+
return { sub: m[1].toLowerCase(), rest: m[2] };
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export interface SetArgs {
|
|
141
|
+
ref: string;
|
|
142
|
+
/** `null` = clear the pin (use the running session's value from now on). */
|
|
143
|
+
model?: string | null;
|
|
144
|
+
thinking?: string | null;
|
|
145
|
+
timeoutMs?: number | null;
|
|
146
|
+
name?: string | null;
|
|
147
|
+
/** `"here"` = this machine; `null` = any machine. */
|
|
148
|
+
host?: string | null;
|
|
149
|
+
/** Jobs only: the new prompt, verbatim. A loop's notes live under its id, so rewording it here keeps them. */
|
|
150
|
+
prompt?: string;
|
|
151
|
+
/** Jobs only: already parsed, so a typo is rejected before it can be stored. */
|
|
152
|
+
schedule?: Schedule;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export interface SetOptions {
|
|
156
|
+
/** `/cron set` only: a job has a prompt and a schedule, a trigger rule has neither. */
|
|
157
|
+
job?: boolean;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* `/cron set <id> …` and `/triggers set <id> …`: change what a job or rule runs with after it was
|
|
162
|
+
* created (re-reading the parent session's model every run is the alternative; here a pin is explicit and editable).
|
|
163
|
+
* `--model -` (or `current`) removes the pin.
|
|
164
|
+
*/
|
|
165
|
+
export function parseSetArgs(input: string, opts: SetOptions = {}): SetArgs {
|
|
166
|
+
const usage = `usage: /cron|/triggers set <id> [--model <provider/id>|-] [--thinking <level>|-] [--timeout <dur>|-] [--name <n>|-] [--host here|-]${opts.job ? ' [--prompt "<text>"] [--schedule "<expr>"]' : ""}`;
|
|
167
|
+
const tokens = tokenize(input);
|
|
168
|
+
const out: SetArgs = { ref: "" };
|
|
169
|
+
let touched = 0;
|
|
170
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
171
|
+
const t = tokens[i].value;
|
|
172
|
+
if (!t.startsWith("--")) {
|
|
173
|
+
// A prompt or a schedule with spaces has to be one quoted token; unquoted, its second word
|
|
174
|
+
// arrives here looking like a second job ref, and the bare usage line does not explain that.
|
|
175
|
+
if (out.ref && (out.prompt !== undefined || out.schedule !== undefined)) throw new Error(`quote a value that contains spaces: --prompt "check the CI and report only what changed"`);
|
|
176
|
+
if (out.ref) throw new Error(usage);
|
|
177
|
+
out.ref = t;
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
const arg = tokens[++i];
|
|
181
|
+
const val = arg?.value;
|
|
182
|
+
if (val === undefined) throw new Error(`${t} needs a value (or - to clear)`);
|
|
183
|
+
const clear = val === "-" || val.toLowerCase() === "current";
|
|
184
|
+
touched++;
|
|
185
|
+
if (t === "--model") out.model = clear ? null : val;
|
|
186
|
+
else if (t === "--thinking") out.thinking = clear ? null : requireThinkingLevel(val);
|
|
187
|
+
else if (t === "--timeout") out.timeoutMs = clear ? null : parseDuration(val);
|
|
188
|
+
else if (t === "--name") out.name = clear ? null : val;
|
|
189
|
+
// `--host here` re-homes a job stamped with a machine that no longer exists (a renamed box,
|
|
190
|
+
// a rebuilt container, or one half of a synced $HOME); `-` unpins it for any machine.
|
|
191
|
+
else if (t === "--host") {
|
|
192
|
+
if (!clear && val !== "here") throw new Error("--host takes `here` (this machine) or `-` (any machine)");
|
|
193
|
+
out.host = clear ? null : "here";
|
|
194
|
+
}
|
|
195
|
+
// Rewording a loop or moving it to another hour used to mean remove-and-re-add, which mints a
|
|
196
|
+
// new id — and the notes a stateful loop has been accumulating live at `state/<id>.md`.
|
|
197
|
+
else if (opts.job && (t === "--prompt" || t === "--schedule")) {
|
|
198
|
+
// `-` clears a *pin*, and there is nothing to fall back to here: a job always has a prompt
|
|
199
|
+
// and a schedule. Refusing beats erasing the prompt of a working loop; a quoted "-" is still
|
|
200
|
+
// available to anyone who really means that text.
|
|
201
|
+
if (clear && !arg.quoted) throw new Error(`${t} cannot be cleared: every job has one (quote the value to pass it literally)`);
|
|
202
|
+
if (t === "--prompt") {
|
|
203
|
+
if (!val.trim()) throw new Error("--prompt cannot be empty");
|
|
204
|
+
out.prompt = val;
|
|
205
|
+
} else {
|
|
206
|
+
const schedule = parseSchedule(val);
|
|
207
|
+
// A one-shot is spent the moment the job has any fired/due stamp, and the scheduler
|
|
208
|
+
// deletes a `once` job after it runs — with the notes this edit exists to preserve.
|
|
209
|
+
if (schedule.kind === "once") throw new Error("--schedule takes a recurring schedule (5-field cron, an alias, or `every <dur>`); `/cron run <id>` fires this job once");
|
|
210
|
+
out.schedule = schedule;
|
|
211
|
+
}
|
|
212
|
+
} else throw new Error(`unknown flag ${t}`);
|
|
213
|
+
}
|
|
214
|
+
if (!out.ref) throw new Error(usage);
|
|
215
|
+
if (!touched) throw new Error(`nothing to change; ${usage}`);
|
|
216
|
+
return out;
|
|
217
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// `pi-loops` — the session launcher and its subcommands (see src/cli.ts and docs/cli.md). Resolves
|
|
3
|
+
// pi's packages the way the headless host does (src/pi-resolver.mjs) and loads the TypeScript the
|
|
4
|
+
// way both entry points have to (src/ts-entry.mjs), so it runs outside pi with no build step and no
|
|
5
|
+
// dependencies — from a checkout, a `git:` package, or an npm package under node_modules.
|
|
6
|
+
import { register } from "node:module";
|
|
7
|
+
import { importTs } from "./ts-entry.mjs";
|
|
8
|
+
|
|
9
|
+
register("./pi-resolver.mjs", import.meta.url);
|
|
10
|
+
let cli;
|
|
11
|
+
try {
|
|
12
|
+
cli = await importTs(import.meta.url, "./cli.ts");
|
|
13
|
+
} catch (err) {
|
|
14
|
+
// Nothing has loaded yet, so there is no usage text to print underneath: the message is all of it.
|
|
15
|
+
process.stderr.write(`pi-loops: ${err?.message ?? err}\n`);
|
|
16
|
+
process.exit(1);
|
|
17
|
+
}
|
|
18
|
+
try {
|
|
19
|
+
process.exitCode = await cli.runCli(process.argv.slice(2));
|
|
20
|
+
} catch (err) {
|
|
21
|
+
process.stderr.write(`pi-loops: ${err?.message ?? err}\n\n${cli.CLI_USAGE}\n`);
|
|
22
|
+
process.exitCode = 1;
|
|
23
|
+
}
|