@alphacoder-v0/pi-loops 0.22.8 → 0.24.1
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 +717 -0
- package/CONTRACT.md +7 -7
- package/README.md +114 -78
- package/README.zh-CN.md +62 -41
- package/docs/cli.md +352 -91
- package/docs/configuration.md +86 -24
- package/docs/design.md +404 -61
- package/docs/downstream.md +2 -2
- package/docs/goal.md +12 -3
- package/docs/hooks.md +6 -7
- package/docs/loops.md +288 -59
- package/docs/mcp.md +22 -14
- package/docs/recipes.md +6 -0
- package/docs/screenshot.png +0 -0
- package/docs/triggers.md +120 -26
- package/docs/troubleshooting.md +41 -28
- package/docs/web-ui-parity.md +702 -65
- package/package.json +4 -3
- package/recipes/autoresearch/recipe.toml +1 -0
- package/recipes/changelog-draft/recipe.toml +1 -0
- package/recipes/ci-sweeper/recipe.toml +1 -0
- package/recipes/daily-digest/recipe.toml +1 -0
- package/recipes/deps-sweeper/recipe.toml +1 -0
- package/recipes/ecosystem/recipe.toml +1 -0
- package/recipes/issue-loop/recipe.toml +1 -0
- package/recipes/pr-watch/recipe.toml +1 -0
- package/skills/pi-loops/SKILL.md +6 -4
- package/src/activity.ts +199 -0
- package/src/archive.ts +21 -9
- package/src/args.ts +6 -2
- package/src/browser-open.ts +169 -0
- package/src/cli-entry.mjs +12 -3
- package/src/cli.ts +960 -143
- package/src/config.ts +53 -5
- package/src/conversation-rows.ts +469 -0
- package/src/danger.ts +227 -6
- package/src/engine.ts +1240 -0
- package/src/goal.ts +71 -0
- package/src/held-read.ts +27 -0
- package/src/hooks.ts +49 -18
- package/src/host-control-channel.ts +455 -40
- package/src/host-control.ts +602 -111
- package/src/host-entry.mjs +5 -3
- package/src/host-runtime.ts +455 -42
- package/src/host.ts +659 -123
- package/src/inbox-card.ts +278 -0
- package/src/inbox.ts +139 -62
- package/src/job-edit.ts +7 -1
- package/src/job-health.ts +14 -2
- package/src/job-lines.ts +201 -13
- package/src/job-owner.ts +74 -13
- package/src/job-tune.ts +110 -0
- package/src/lease.ts +537 -0
- package/src/lock.ts +7 -2
- package/src/mcp-pool.ts +66 -14
- package/src/mcp.ts +1 -1
- package/src/open-file.ts +54 -0
- package/src/page-children.ts +1507 -0
- package/src/page-devices.ts +192 -0
- package/src/page-server.ts +3150 -0
- package/src/path-precheck.ts +215 -0
- package/src/paths.ts +24 -0
- package/src/pi-floor.ts +10 -0
- package/src/pi-loops.ts +1370 -473
- package/src/pi-resolver.d.mts +1 -0
- package/src/pi-resolver.mjs +110 -10
- package/src/presence.ts +12 -63
- package/src/protocol.ts +10 -1
- package/src/recipe.ts +43 -1
- package/src/requests.ts +354 -0
- package/src/rule-lines.ts +68 -0
- package/src/run-card.ts +105 -0
- package/src/runner.ts +18 -1
- package/src/schedule.ts +1 -2
- package/src/scheduler.ts +595 -245
- package/src/sdk-runner.ts +321 -27
- package/src/seen.ts +278 -0
- package/src/session-head.ts +21 -0
- package/src/share.ts +12 -2
- package/src/snapshot.ts +3 -2
- package/src/store.ts +81 -8
- package/src/tools.ts +196 -65
- package/src/trigger-runtime.ts +164 -127
- package/src/triggers.ts +39 -7
- package/src/trust.ts +46 -2
- package/src/ui-prefs.ts +22 -7
- package/src/watch-plan.ts +174 -0
- package/src/web.d.mts +3 -0
- package/src/web.mjs +2261 -2340
- package/src/window-executor.ts +459 -0
- package/src/window-guard.ts +158 -0
- package/docs/agents/issue-tracker.md +0 -34
package/docs/configuration.md
CHANGED
|
@@ -4,28 +4,37 @@ Everything lives under `~/.pi/agent/loops/` (override: `PI_LOOPS_DIR`).
|
|
|
4
4
|
|
|
5
5
|
| File | Purpose |
|
|
6
6
|
|---|---|
|
|
7
|
-
| `config.toml` | `allow_project_hooks`, `[triggers] poll_interval_secs = 600` / `run_timeout_secs = 900`, `[cron] catch_up = true` / `max_concurrent_runs = 3`, `[hooks] mode = "sync"`, `[host] auto = true`, `[limits] daily_budget_usd = 0`, `[danger] allow = []` |
|
|
7
|
+
| `config.toml` | `allow_project_hooks`, `[triggers] poll_interval_secs = 600` / `run_timeout_secs = 900`, `[cron] catch_up = true` / `max_concurrent_runs = 3`, `[hooks] mode = "sync"`, `[host] auto = true`, `[limits] daily_budget_usd = 0`, `[danger] allow = []`, `[engine] window_guard = true`, `[page] port = 4173` / `host = "127.0.0.1"` / `allow_host = []` |
|
|
8
8
|
| `logs/pi-<pid>.log` | what each pi process diagnosed: jobs disabled, writes that failed, sub-agent warnings. Rotated at 2 MB, newest 5 processes kept |
|
|
9
9
|
| `mcp.toml` | MCP servers — see [mcp.md](mcp.md) |
|
|
10
10
|
| `hooks.toml` | lifecycle hooks — see [hooks.md](hooks.md) |
|
|
11
|
-
| `ui.json` | preferences that outlive a session: `panel` (written by `/cron panel on\|off`)
|
|
11
|
+
| `ui.json` | preferences that outlive a session: `panel` (written by `/cron panel on\|off`); `model` / `thinking` — the last ones you chose in the browser page, applied to the next session that does not say otherwise; `host` (`"stopped"` after a manual stop); `launcher` (`"declined"` after `uninstall-launcher`, so the launcher is not written again; `"failed"`, with `launcherFailedIn`, the directory a write failed in, so it is retried only when that changes); `heldJobsNotice` (the one-time notice about plain jobs of closed conversations); `lastPage` (the conversation `GET /` opens) |
|
|
12
12
|
| `jobs.json`, `triggers.json` | cron jobs and trigger rules (machine-global, each with a `cwd`) |
|
|
13
|
+
| `jobs.json.bak` | the last `jobs.json` that read cleanly, kept so a damaged store can be restored |
|
|
13
14
|
| `state/<id>.md` | loop notes; plain Markdown, editable |
|
|
14
15
|
| `inbox.jsonl` | the inbox (past 1 MB the oldest triaged findings are dropped; nothing still new) |
|
|
15
16
|
| `runs.jsonl`, `triggers-audit.jsonl` | run log (halved past 1 MB) and trigger audit (2 MB) |
|
|
16
17
|
| `spend.json` | per-day totals of what rotation dropped from the run log, so a daily budget still counts it |
|
|
17
18
|
| `sessions/<job-id>/`, `sessions/triggers-<project>-<hash>/` | sub-agent transcripts (20 per job, 40 per project's checks; the hash of the project's path keeps two projects named alike apart) |
|
|
18
|
-
| `
|
|
19
|
-
| `
|
|
20
|
-
| `
|
|
21
|
-
| `host.json
|
|
22
|
-
| `
|
|
19
|
+
| `next-runs.json` | job id → when it runs next, as computed by pi-loops, or by a window while pi-loops is not running (`{ at, next }`). Written only when an answer changes, so it can be older than the last tick; the browser panel reads it rather than parsing schedules a second time |
|
|
20
|
+
| `presence/` | records written by pi-loops 0.22 windows, read only during an upgrade |
|
|
21
|
+
| `host-service.json`, `host.log` | pi-loops process record (pid, version, heartbeat, the page's address and token, or why the page is waiting; 0600) and log |
|
|
22
|
+
| `host-spawns.json` | pi-loops start attempts, the retry backoff and the crash pause |
|
|
23
|
+
| `host-service.sock` | pi-loops' control socket (0600): status, abort, run, stop, and opening a conversation in the page |
|
|
24
|
+
| `pages/<session>.json` | one per conversation open in the page: its pi's pid and start time, and when a tab last used it (0600). A pi-loops that restarts stops the pis these name, rather than adopt them |
|
|
25
|
+
| `pages/args/<session>.json` | the pi arguments of the launch that last opened that conversation in the page (0600). Kept after its pi stops: a pi the page starts again for it gets the same ones |
|
|
26
|
+
| `leases/<session>.json` | which process holds each conversation |
|
|
27
|
+
| `requests/<session>.jsonl` | requests addressed to a conversation (rule results), each kept until it is in the conversation or in the inbox as a dead letter |
|
|
28
|
+
| `seen.json` | when each project was last looked at (`/inbox`, or the page's Mark all seen), for the "since you last looked" line and the page's away card |
|
|
29
|
+
| `scheduler.json` | written by pi-loops during an upgrade only, so pi-loops 0.22 windows do not run loops beside it |
|
|
30
|
+
| `*.lock` | short-lived locks around writes |
|
|
31
|
+
| `polls.json` | last dynamic check per project (shared with 0.22 windows during an upgrade, so a project is not checked twice in one interval) |
|
|
23
32
|
| `dedup.json` | machine-wide trigger dedup window (5 minutes) |
|
|
24
|
-
| `web-token` | the browser
|
|
33
|
+
| `web-token` | the browser page's token, mode 0600. It lives in a file rather than being made per start so the address stays the same one and a signed-in device stays signed in across restarts and upgrades. Delete it and run `pi-loops restart` to sign every device out. The first 0.24 start replaces a token from an earlier version once, and writes `web-token.v024` beside it to record that; a token you write into `web-token` after that is kept |
|
|
25
34
|
|
|
26
35
|
Project-level: `<project>/.pi/mcp.toml` (trusted projects only) and `<project>/.pi/hooks.toml`
|
|
27
36
|
(when allowed).
|
|
28
|
-
An invalid `[triggers] poll_interval_secs`
|
|
37
|
+
An invalid `[triggers] poll_interval_secs` is reported at startup
|
|
29
38
|
and ignored.
|
|
30
39
|
|
|
31
40
|
## `[cron] max_concurrent_runs`
|
|
@@ -55,22 +64,57 @@ indistinguishable from a goal that was never set. They can therefore take the to
|
|
|
55
64
|
which is why the in-flight count is reported against it (`4 of 3 sub-agent slot(s) in use`) rather than assumed
|
|
56
65
|
to be under it. If you have a goal running and want headroom for it, size the setting one higher.
|
|
57
66
|
|
|
67
|
+
## `[host] auto`
|
|
68
|
+
|
|
69
|
+
`true`, the default: a pi window starts pi-loops in the background when there is a loop, a rule, a
|
|
70
|
+
push server or a closed conversation's plain job to run, and pi-loops keeps running after every
|
|
71
|
+
window has closed. `false` means what it meant in 0.22.8: loops run only while a pi window is open.
|
|
72
|
+
The first window still starts pi-loops; loops, rule checks and `/cron run <loop>` work in that
|
|
73
|
+
window; and pi-loops leaves once the last window has closed, after giving the window that started it
|
|
74
|
+
a minute to announce itself. Nothing runs with no window open, and the line a quitting window prints
|
|
75
|
+
says so. The browser page counts as a window: `pi-loops` opening it starts pi-loops, and a
|
|
76
|
+
conversation open in the page, or a tab on one, keeps it running. pi-loops leaves once the last
|
|
77
|
+
terminal window, page conversation and tab are gone. A `pi-loops start --foreground` run by a service manager, or a `pi-loops start` /
|
|
78
|
+
`/pi-loops start` you typed, is yours to stop, and stays either way. An invalid value is reported at startup and leaves the default.
|
|
79
|
+
|
|
80
|
+
## `[engine] window_guard`
|
|
81
|
+
|
|
82
|
+
A plain job's prompt runs under the job's own `--tools` and the dangerous-command policy
|
|
83
|
+
([loops.md](loops.md)) wherever it runs, and so does the turn an MCP server's `inject_and_run` push
|
|
84
|
+
starts, under the dangerous-command policy alone ([mcp.md](mcp.md)). In the background nobody can be asked, so a call outside
|
|
85
|
+
them is refused. In an open window you can be: the same call shows a question with a 60-second
|
|
86
|
+
countdown and two answers, `No, refuse it` focused and `Yes, run it once` under it, so Enter
|
|
87
|
+
refuses. The yes answer runs it this once; anything else, Escape or no answer refuses it with the
|
|
88
|
+
wording the background uses, so the model does the safe part and reports. `[danger] allow` applies in both places. The guard
|
|
89
|
+
asks nothing about pi-loops' own tools: `cron_list` and `list_triggers` only read; `cron_create` in
|
|
90
|
+
such a turn makes a job capped to the turn's tools, so it widens nothing; and `new_trigger`, the
|
|
91
|
+
trigger tools, `cron_remove` and `set_cron_job_state` ask through their own confirm, once, the same
|
|
92
|
+
question with the same minute, where the background would refuse. The cap holds until the turn settles, through pi's own retries and follow-ups. A row
|
|
93
|
+
written into `requests/<session>.jsonl` by anything but this window, with a source that would make
|
|
94
|
+
it yours, runs as a scheduled turn with no declared tools: the danger check asks you. Your own
|
|
95
|
+
turns, a `/goal` continuation, a claimed finding, a recipe hand-off and `/new-trigger` never ask:
|
|
96
|
+
they are yours, and a message you send into a scheduled turn makes the rest of it yours too.
|
|
97
|
+
|
|
98
|
+
`window_guard = false` turns the cap off in windows (the background is unchanged): a scheduled
|
|
99
|
+
prompt in an open window then runs as your own message would, as it did in pi-loops 0.22. An
|
|
100
|
+
invalid value is reported at startup and leaves the guard on.
|
|
101
|
+
|
|
58
102
|
## Flags and environment
|
|
59
103
|
|
|
60
104
|
| | |
|
|
61
105
|
|---|---|
|
|
62
|
-
| `--trigger-poll-secs <n>` |
|
|
106
|
+
| `--trigger-poll-secs <n>` | ignored: pi-loops checks rules every `[triggers] poll_interval_secs`; passing it warns at session start |
|
|
63
107
|
| `PI_LOOPS_DIR` | relocate the data directory |
|
|
64
|
-
| `PI_WEB_TOKEN` | use this instead of the token in `web-token`. Letters, digits, `-` and `_`, at least 8 of them: it is substituted into a JavaScript string in the page, and a quote there would end the string early |
|
|
65
108
|
| `PI_ALLOW_PROJECT_HOOKS=1` | allow project hooks. `true` works too; anything else is off |
|
|
66
|
-
| `PI_LOOPS_HOST=
|
|
109
|
+
| `PI_LOOPS_HOST=off` | prevent this process from starting pi-loops |
|
|
110
|
+
| `PI_LOOPS_PAGE_PORT=<n>` | the browser page's port, 0 to 65535 (0 takes a free one); it wins over `[page] port`. Read when pi-loops in the background starts, so set it for `pi-loops restart` |
|
|
67
111
|
| `PI_LOOPS_DEBUG=1` | add a line per provider retry, compaction and tool call of a sub-agent run to `logs/pi-<pid>.log` |
|
|
68
|
-
| `PI_BIN` | the pi executable to start
|
|
112
|
+
| `PI_BIN` | the pi executable to start for `pi-loops --tui` and for each conversation in the page; without it the page starts the pi that pi-loops in the background runs on, never the one on `PATH` |
|
|
69
113
|
|
|
70
114
|
## What is remembered between sessions
|
|
71
115
|
|
|
72
116
|
A session opens on pi's default model, so choosing the same one every morning was the first thing
|
|
73
|
-
the browser
|
|
117
|
+
the browser page asked of anybody. Changing the model or the thinking level there records it in
|
|
74
118
|
`ui.json`, and `pi-loops` applies it when it starts the next session — the terminal window too,
|
|
75
119
|
since the launcher is what applies it.
|
|
76
120
|
|
|
@@ -80,10 +124,33 @@ their conversation was had with. Delete the keys from `ui.json` to go back to pi
|
|
|
80
124
|
|
|
81
125
|
A `--model` — yours or the remembered one — lasts as long as the process, not as long as the
|
|
82
126
|
session: pi re-resolves it every time the session inside the process is replaced. So **resume** in
|
|
83
|
-
the browser
|
|
127
|
+
the browser page re-applies the model the session it is going back to was last using, which is
|
|
84
128
|
what a fresh `pi --resume` would have done.
|
|
85
129
|
|
|
86
|
-
##
|
|
130
|
+
## `[page]`
|
|
131
|
+
|
|
132
|
+
The browser page's address. pi-loops in the background serves it, so it is set here rather than on
|
|
133
|
+
the command line, and a change takes effect after `pi-loops restart`. Full explanation in
|
|
134
|
+
[cli.md](cli.md#the-address).
|
|
135
|
+
|
|
136
|
+
```toml
|
|
137
|
+
[page]
|
|
138
|
+
port = 4173
|
|
139
|
+
host = "127.0.0.1"
|
|
140
|
+
allow_host = []
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
| | |
|
|
144
|
+
|---|---|
|
|
145
|
+
| `port` | default 4173, fixed on purpose so the address is worth bookmarking. Unset, only the default loops directory uses 4173: a loops directory moved with `PI_LOOPS_DIR` takes a free port, so two of them do not fight over one. Set here, it is used for any loops directory, so a moved one keeps its address across restarts. `PI_LOOPS_PAGE_PORT` wins over both. A port another program holds costs the page, never the loops: `pi-loops status` and `host.log` say so |
|
|
146
|
+
| `host` | the address to bind. Anything but loopback puts the page on the local network, unencrypted: `host.log` says `page on the local network, unencrypted: http://<ip>:<port>/`, and anyone on the wire can read the cookie. `tailscale serve` gets a phone in over TLS with the page still on loopback |
|
|
147
|
+
| `allow_host` | names the page answers to in the `Host` header besides loopback and a tailnet's, for a reverse proxy in front. A name resolved by public DNS puts the token alone in charge of keeping strangers out |
|
|
148
|
+
|
|
149
|
+
Tailnet names (`*.ts.net`) are accepted from a tailnet connection without `allow_host`, which is
|
|
150
|
+
what makes `tailscale serve --bg 4173` work with the page still on loopback. An invalid value is
|
|
151
|
+
reported at startup and leaves the default.
|
|
152
|
+
|
|
153
|
+
### The browser page
|
|
87
154
|
|
|
88
155
|
`pi-loops` takes these for itself and passes everything else to pi. Full explanation in
|
|
89
156
|
[cli.md](cli.md); this is the list.
|
|
@@ -91,15 +158,10 @@ what a fresh `pi --resume` would have done.
|
|
|
91
158
|
| | |
|
|
92
159
|
|---|---|
|
|
93
160
|
| `--web` / `--tui` | which window, when the guess is wrong |
|
|
94
|
-
| `--
|
|
95
|
-
| `--host <addr>` | bind somewhere other than loopback, so a phone on the same network can reach it. Prints the addresses it can be reached on, and says that they are unencrypted |
|
|
96
|
-
| `--allow-host <name,…>` | accept these values in the `Host` header, for a reverse proxy in front. A name resolved by public DNS puts the token back in charge of keeping strangers out |
|
|
97
|
-
| `--no-auth` | no token and no cookie. Loopback only, whatever route a request took, and refused outright with `--host` |
|
|
98
|
-
| `--no-open` | do not open a browser |
|
|
161
|
+
| `--no-open` | print the page's address without opening a browser |
|
|
99
162
|
| `--loops-dir <dir>` | the same thing `PI_LOOPS_DIR` does |
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
what makes `tailscale serve --bg 4173` work with the server still on loopback.
|
|
163
|
+
| `--port`, `--host`, `--allow-host` | refused (exit 2) with the `[page]` lines that replace them |
|
|
164
|
+
| `--no-auth` | refused (exit 2): gone in 0.24, the page is always behind its token |
|
|
103
165
|
|
|
104
166
|
## Per-job options (`/cron add`)
|
|
105
167
|
|