@alphacoder-v0/pi-loops 0.22.7 → 0.24.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/CHANGELOG.md +681 -0
- package/CONTRACT.md +7 -7
- package/README.md +91 -63
- package/README.zh-CN.md +59 -39
- package/docs/cli.md +272 -83
- package/docs/configuration.md +86 -24
- package/docs/design.md +395 -56
- 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 +37 -26
- package/docs/web-ui-parity.md +691 -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/cli-entry.mjs +12 -3
- package/src/cli.ts +746 -125
- 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 -41
- 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 +1301 -450
- 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 +598 -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 +16 -5
- package/src/watch-plan.ts +174 -0
- package/src/web.d.mts +3 -0
- package/src/web.mjs +2260 -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/triggers.md
CHANGED
|
@@ -11,27 +11,80 @@ A rule is a natural-language **condition** and **action**:
|
|
|
11
11
|
|
|
12
12
|
`/new-trigger` asks the agent to extract condition and action and call the `new_trigger` tool
|
|
13
13
|
(the agent may also do this from ordinary chat). Rules **fire once** by default (`fire_once=false`
|
|
14
|
-
for repeating)
|
|
14
|
+
for repeating). A matched rule's result is a finding in the inbox and a row in the page's loop
|
|
15
|
+
activity strip, whatever `promote_to_chat` says; `promote_to_chat` also puts it in the conversation,
|
|
16
|
+
and it is still one finding. The finding is news whatever the result says: it comes from outside, so
|
|
17
|
+
a `· waits:` in it does not make it a decision. Its card and its strip row lead with what the check
|
|
18
|
+
said after the `matched <rule-id>` it starts with; the finding keeps the whole text. A check whose
|
|
19
|
+
result says exactly what the latest new finding of the same rule and project says files nothing
|
|
20
|
+
more. The check records on its result's audit row whether it filed the finding or skipped it as a
|
|
21
|
+
repeat; when the promotion falls back to the inbox, or its row dies, nothing more is filed for that
|
|
22
|
+
check. A result that no check decided, such as a push with no rule, is filed once by its trace id.
|
|
15
23
|
Time-based requests ("every hour", "daily", "定时任务") are refused and routed to `cron_create`.
|
|
16
24
|
|
|
17
25
|
Tools: `new_trigger` (condition, action, spec, fire_once, promote_to_chat), `list_triggers`,
|
|
18
|
-
`remove_trigger` (id | all), `set_trigger_state`.
|
|
26
|
+
`remove_trigger` (id | all), `set_trigger_state`. Removing and re-enabling ask the user to
|
|
19
27
|
confirm, with `ctx.ui.confirm`.
|
|
20
28
|
|
|
29
|
+
With a person present, in their own turn, `new_trigger` asks one question before it creates
|
|
30
|
+
anything, with pi's `ctx.ui.select`: a terminal shows pi's selector, and the browser page draws the
|
|
31
|
+
same question as a card. Its answers are Result, `promote_to_chat` (*Show it in this conversation*,
|
|
32
|
+
the default, or *Put it in my inbox*), and Repeats, `fire_once` (*Once, then turn itself off*, the
|
|
33
|
+
default, or *Every time it happens*). The tool returns `created dynamic trigger …` or
|
|
34
|
+
`cancelled: …` in the same call; Cancel, Esc, an abort and thirty minutes with no answer write
|
|
35
|
+
nothing. The page's **Watch for…** box sends one sentence as the chat message
|
|
36
|
+
`Watch for: <sentence>`; a sentence with no time and nothing to wait for is answered by the page
|
|
37
|
+
itself ("this reads like something to do now") and reaches no model. A sub-agent and a scheduled
|
|
38
|
+
turn ask nobody: a scheduled turn in an open window asks the window guard's confirm, and a
|
|
39
|
+
sub-agent is refused. See `docs/design.md`, decision 24.
|
|
40
|
+
|
|
21
41
|
The model-facing tools see the calling project only: `list_triggers` takes `all_projects: true`
|
|
22
42
|
when the user asks about the rest, and `remove_trigger { all: true }` clears this project's rules,
|
|
23
43
|
never the machine's.
|
|
24
44
|
|
|
45
|
+
### Rules that wait for a path
|
|
46
|
+
|
|
47
|
+
A condition that is only "a path exists or appears" is recorded as pre-checkable when the rule is
|
|
48
|
+
created (`precheck` on the rule in `triggers.json`: the condition it was read from, the path as
|
|
49
|
+
typed, and the absolute path). The path is one token: `~/…`, `$HOME/…`, `${HOME}/…`, an absolute
|
|
50
|
+
path, `./…` or `../…`, a relative path with a `/`, a file name with an extension (`build.done`), or a
|
|
51
|
+
quoted path that may hold spaces. The verb is one of `exists`, `appears`, `is created`, `shows up`,
|
|
52
|
+
`is there`, `is present`, `存在`, `出现`, `被创建`, `生成` (a `文件` or `目录` before the verb is the
|
|
53
|
+
noun only when a space sets it apart from the path: `~/备份文件 存在` waits for `~/备份文件`), with an optional leading `when`, `if`,
|
|
54
|
+
`once`, `当`, `如果` or `一旦` and an optional trailing `的时候`, `时` or `后`. `~` and `$HOME` are
|
|
55
|
+
resolved in the process that creates the rule, so they are your home even when pi-loops in the
|
|
56
|
+
background was started with another one; a relative path is resolved against the rule's project.
|
|
57
|
+
|
|
58
|
+
Anything else is refused as path-only and checked by the model, as before: a glob, a second clause
|
|
59
|
+
(`~/x appears and it contains ERROR`, `~/a exists or ~/b exists`), another verb, a bare word with no
|
|
60
|
+
`/` or extension (`README`), a variable other than `$HOME`, `~user`, a URL, or a network path
|
|
61
|
+
(`//server/…`, `/net/…`, `/Network/…`). A rule created before
|
|
62
|
+
0.24.0 has no `precheck` and keeps calling the model; create it again to get the pre-check. So does
|
|
63
|
+
a rule whose condition was edited by hand after it was created. An imported rule, from 0.22 or later,
|
|
64
|
+
is read again when it is imported, against the project it is imported into and this machine's home,
|
|
65
|
+
so a path-only one gets the pre-check then.
|
|
66
|
+
|
|
25
67
|
## Evaluation
|
|
26
68
|
|
|
27
|
-
While at least one enabled rule exists, every `poll_interval_secs`
|
|
28
|
-
|
|
29
|
-
(the session that created the rules if it is open, otherwise the lowest pid there), or by the
|
|
30
|
-
machine leader when no pi is open in the project. The check receives the
|
|
69
|
+
While at least one enabled rule exists, every `[triggers] poll_interval_secs` in `config.toml`
|
|
70
|
+
(default 600; pi-loops re-reads it every minute) one check per project that has rules is run by pi-loops. The check receives the
|
|
31
71
|
event JSON and every enabled rule and is told to inspect the filesystem, commands, clock or network
|
|
32
72
|
as needed, execute the actions of matching rules, and reply `matched dyn-…` or exactly
|
|
33
73
|
`no dynamic trigger rule matched`. Matched fire-once rules are disabled with `fired_at`.
|
|
34
74
|
|
|
75
|
+
A rule that waits for a path (`precheck`, above) is looked at first: after the poll is claimed,
|
|
76
|
+
pi-loops `stat`s the path, which follows symlinks, and gives up after 2 seconds. At most two looks
|
|
77
|
+
are out at once in the process, and one per path; past that a look counts as one that cannot answer. While the path is
|
|
78
|
+
absent the rule is left out of the check, so a project whose only rules wait for absent paths
|
|
79
|
+
makes no model call and writes no `running` row. The audit gets one `trigger` row `waiting`
|
|
80
|
+
("no model call until ~/build.done exists") at the first look pi-loops makes and whenever the path
|
|
81
|
+
goes away, and one `appeared` when it is back; the check that follows names the rules the look let
|
|
82
|
+
through in `details.prechecked` on its `running` row. When the look cannot answer (no permission, a
|
|
83
|
+
mount that does not answer in time) the model checks the rule, as before. A repeating rule is
|
|
84
|
+
checked every poll while its path exists, and matches again after the path is deleted and comes
|
|
85
|
+
back. The rule's row on the page says what it waits for and when the next look is: "waits for
|
|
86
|
+
~/build.done · next look 09:22 · no model call until the file exists".
|
|
87
|
+
|
|
35
88
|
Pushed MCP notifications (see [mcp.md](mcp.md)) enter the same runtime and are evaluated against
|
|
36
89
|
the rules unless the server is configured as an `inject_summary` / `inject_and_run` feed.
|
|
37
90
|
|
|
@@ -41,7 +94,9 @@ job backoff, same numbers — literally the same function, `backoffWaitMs` in `s
|
|
|
41
94
|
The count lives on the rule (`consecutive_failures`,
|
|
42
95
|
`last_check_failed_at`), a check that completes clears it — including `/triggers run <id>`, which is
|
|
43
96
|
the way to retry a backed-off rule now — and entering the backoff is audited as `backoff` with the
|
|
44
|
-
rule ids and the failure count. Pushes are unaffected: an event is worth one attempt.
|
|
97
|
+
rule ids and the failure count. Pushes are unaffected: an event is worth one attempt. While a rule
|
|
98
|
+
is in that state `/triggers rules` marks it `[failing ×N]` after its state bracket; a disabled rule
|
|
99
|
+
is not polled and is not marked.
|
|
45
100
|
|
|
46
101
|
## What happens when the machine is busy or over budget
|
|
47
102
|
|
|
@@ -66,10 +121,26 @@ on what was refused:
|
|
|
66
121
|
|
|
67
122
|
## Promotion and audit
|
|
68
123
|
|
|
69
|
-
A matched rule with `promote_to_chat`
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
124
|
+
A matched rule with `promote_to_chat` is a request addressed to a conversation: the one that
|
|
125
|
+
created the rule while a window holds it; else the conversation a window holds in the rule's project
|
|
126
|
+
(lowest pid first, as 0.22.8 chose its owner), so the result reaches the chat you are working in;
|
|
127
|
+
else the closed creating conversation. It is one row in `requests/<session>.jsonl`, which stays there until `[Trigger <trace>] <result>` is
|
|
128
|
+
in the conversation. The window holding the conversation inserts it (visible to future turns; no
|
|
129
|
+
model call when idle, a follow-up turn when busy: `promoted`, `to: "chat"` in the audit when it
|
|
130
|
+
landed at once, `to: "queued"` when another process or a later drain delivers it). If no window
|
|
131
|
+
holds the conversation, pi-loops appends the result to it, holding the conversation for that one
|
|
132
|
+
write, so it is there when the conversation is resumed (`appended`); the rule's finding in the
|
|
133
|
+
inbox is what the next session start's `[inbox] since you last looked` line counts. pi-loops appends it within moments of the row being
|
|
134
|
+
written: pi-loops watches `requests/` and `leases/`, and a window that wrote the row rings it over
|
|
135
|
+
the control socket; its 30-second tick is the floor under both, so a ring that is lost costs at most
|
|
136
|
+
one tick, and never a row. If pi-loops cannot take the conversation at
|
|
137
|
+
that instant (someone wrote it in the last two minutes), the row waits and pi-loops appends it on a
|
|
138
|
+
later tick. A row whose delivery fails waits a minute, then five, and blocks nothing behind it; at
|
|
139
|
+
the third failure, or when the conversation's file is gone, it is a dead letter: a finding in the
|
|
140
|
+
inbox marked for the conversation, then a `request` audit row (`dead`), then the row is removed, in
|
|
141
|
+
that order so a crash in between loses nothing. A rule's result has its finding already (every match is one), so its dead letter files no second one; any other row's finding reads `could not be delivered (<why>): <prompt>`. A rule with no
|
|
142
|
+
creating conversation (made from a shell, imported, or made by a loop's run) sends results to the
|
|
143
|
+
project's open window as above, and to the inbox (`redirected`, which finds the match's finding and adds nothing) when none is open. One check evaluates every
|
|
73
144
|
rule of the project at once, so it runs under **one** model: the first rule that has one recorded
|
|
74
145
|
(`/triggers set <id> --model … | -`), and that rule's thinking level with it — a thinking level
|
|
75
146
|
belongs to the model it was chosen for, and a rule with a model but no level falls back to the
|
|
@@ -78,22 +149,41 @@ the longest of the project's rules (`--timeout`), else `[triggers] run_timeout_s
|
|
|
78
149
|
session entries (`trigger`, `trigger_result`, `trigger_promotion`); `/triggers audit [N] [--all]`
|
|
79
150
|
shows this project's rows with decisions and transcript paths. The complete set of states:
|
|
80
151
|
|
|
81
|
-
- admission — `accepted`, `deduped`, `deferred` (no slot,
|
|
82
|
-
`
|
|
83
|
-
`cycle_suppressed` (it reached a sub-agent), `backoff`, `disabled` (the rule's project is gone)
|
|
152
|
+
- admission — `accepted`, `deduped`, `deferred` (no slot), `dropped` (the held list was full),
|
|
153
|
+
`budget_exceeded`,
|
|
154
|
+
`cycle_suppressed` (it reached a sub-agent), `backoff`, `disabled` (the rule's project is gone),
|
|
155
|
+
`waiting` (a rule's path is absent: no model call until it exists) and `appeared` (it is back);
|
|
84
156
|
- the run — `running`, then `completed` / `failed` / `aborted`, or `no_rules`;
|
|
85
|
-
- promotion — `promoted
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
`
|
|
157
|
+
- promotion — `promoted` (into this chat, or queued for the conversation), `appended` (pi-loops wrote
|
|
158
|
+
a queued result into the closed conversation), `redirected` (to the inbox: no window is open in the rule's project, and the rule has no
|
|
159
|
+
conversation or its conversation's file is unknown or gone; or a pi-loops 0.22 window has it open), `skipped` (no matched rule has `promote_to_chat`).
|
|
160
|
+
|
|
161
|
+
Rows of type `request` record what became of a row in `requests/<session>.jsonl` that did not end
|
|
162
|
+
in the conversation: `failed` (one delivery failed, pi refused the hand-over, or pi dropped it
|
|
163
|
+
unwritten; the row waits out a backoff, or is offered again), `dead` (three failures, or the
|
|
164
|
+
conversation's file is gone: it is a finding in the inbox now; never a person's message), `cancelled`,
|
|
165
|
+
`done` (a stop or cancel that acted: the turn aborted, or the queued row taken out), `stale` (a stop
|
|
166
|
+
or cancel with nothing to act on, a goal row without a goal, or a person's message whose
|
|
167
|
+
conversation is gone), `job-removed`, `refused` (the row's
|
|
168
|
+
`mode` is not the one its source gets, so a line edited in the file, or written by another version,
|
|
169
|
+
cannot widen what a turn may do; the row never runs). Two more come from the inbox in the browser
|
|
170
|
+
page: `claimed` (a finding's claim was queued for the conversation that created its loop; the row
|
|
171
|
+
is the `inbox:<id>` one `/inbox claim` writes, and the trace id is its id) and `dismissed` (a
|
|
172
|
+
finding was dismissed from the page; the trace id is the finding's, and `details.reason` is what the
|
|
173
|
+
person gave, redacted, or null).
|
|
174
|
+
|
|
175
|
+
Rows of type `cron_control_plane` use the operation as the state instead: `add`, `set` (a
|
|
176
|
+
schedule changed in place by `/cron set --schedule` or by a suggestion applied from the page, with
|
|
177
|
+
`details.before_schedule` and `details.after_schedule`; an edit that leaves the schedule alone
|
|
178
|
+
writes none), `enable`, `disable`, `remove`. The page's and `/cron set`'s rows are in
|
|
179
|
+
`triggers-audit.jsonl` (source `page` or `slash`); the others are also session entries.
|
|
90
180
|
|
|
91
181
|
A 5-minute dedup window collapses repeated events with the same idempotency key (per project for
|
|
92
182
|
rule evaluation, per window for injected pushes).
|
|
93
183
|
|
|
94
184
|
```text
|
|
95
|
-
/triggers
|
|
96
|
-
/triggers
|
|
185
|
+
/triggers this project's rules; the same as /triggers rules (--all for every project)
|
|
186
|
+
/triggers status rule counts, what checks them, the last check, push sources
|
|
97
187
|
/triggers sources local checker + each MCP server: state, queued/dropped/deduped, tools
|
|
98
188
|
(/triggers hooks is the same view)
|
|
99
189
|
/triggers enable <id> /triggers disable <id> /triggers remove <id> also --all | --all-projects
|
|
@@ -106,7 +196,10 @@ rule evaluation, per window for injected pushes).
|
|
|
106
196
|
```
|
|
107
197
|
|
|
108
198
|
Cron runs share this runtime's views: they appear in `/triggers running` and `/triggers audit`
|
|
109
|
-
and can be aborted by run id.
|
|
199
|
+
and can be aborted by run id. A plain job's turn running in a closed conversation in the background
|
|
200
|
+
is stopped through a request (`stop` in `requests/<session>.jsonl`), which that process reads within
|
|
201
|
+
a tick whether or not its socket answers: `/triggers abort <run-id>` says `requested stop for
|
|
202
|
+
<run-id> (session <id>)`. Loop runs and checks, and `--all`, are asked of it over the socket as before.
|
|
110
203
|
|
|
111
204
|
`/triggers run <id>` takes the same path a periodic check takes — dedup, audit, the check
|
|
112
205
|
sub-agent, promotion — and skips only the poll ledger, which is what running it now means. The
|
|
@@ -118,8 +211,9 @@ Cycles are bounded by a hop count: sub-agents run at hop 1 and still have the
|
|
|
118
211
|
cron/trigger tools, so a trigger action can schedule a job; they never run the trigger runtime, so
|
|
119
212
|
nothing nests. Prompt-class operations — creating or removing a trigger,
|
|
120
213
|
re-enabling a trigger or a cron job — are denied fail-closed in sub-agents (no control-plane
|
|
121
|
-
prompt channel there)
|
|
122
|
-
`actor: sub-agent`.
|
|
214
|
+
prompt channel there). `cron_create` works there, and so does disabling a job of the sub-agent's own
|
|
215
|
+
project; the control-plane audit records `actor: sub-agent`. `cron_remove`, re-enabling a job and
|
|
216
|
+
touching another project's job are denied there like the other prompt-class operations. Sub-agents never handle triggers themselves: they ignore MCP pushes, and the
|
|
123
217
|
runtime audits anything reaching hop ≥ 1 as `cycle_suppressed` (hops are counted per trace, but the
|
|
124
218
|
bound that matters is structural: a sub-agent session never acts on a trigger at all).
|
|
125
219
|
|
|
@@ -127,8 +221,8 @@ bound that matters is structural: a sub-agent session never acts on a trigger at
|
|
|
127
221
|
|
|
128
222
|
`/triggers enable|disable <id>` prints the rule's state, condition and action (and the
|
|
129
223
|
fire-once note); `/triggers sources` lists MCP servers, the cron hook and the dynamic
|
|
130
|
-
checker in registration order with a `
|
|
131
|
-
|
|
224
|
+
checker in registration order with a `Sources: N, M connected, K need attention` summary in
|
|
225
|
+
`/triggers status` (the last part only when K is not 0). Errors are worded once and kept that way (`unknown /triggers command:
|
|
132
226
|
…`, `usage: /triggers remove <id>|--all`, `/new-trigger` parse messages). `/triggers remove --all`
|
|
133
227
|
clears this project's rules; `--all-projects` is the explicit machine-wide sweep.
|
|
134
228
|
|
package/docs/troubleshooting.md
CHANGED
|
@@ -1,14 +1,23 @@
|
|
|
1
1
|
# Troubleshooting
|
|
2
2
|
|
|
3
|
-
**Nothing fires.** `/
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
**Nothing fires.** `/pi-loops status` shows whether pi-loops is running. If it cannot start, the window
|
|
4
|
+
shows `loops paused: pi-loops could not start`; `/pi-loops status` gives the reason and log path. A job shows
|
|
5
|
+
`next …` when it will run somewhere: here, in another window, or in the background. `paused: <why>` means
|
|
6
|
+
pi-loops will not run it; `/pi-loops status` says why. A plain job marked `[session <id> — resume it to run]`
|
|
7
|
+
belongs to a conversation whose file pi-loops never saw (`--resume` it once, or `/cron add` it again
|
|
8
|
+
in this chat); a loop marked
|
|
7
9
|
`[orphan: cwd missing]` was disabled because its checkout is gone.
|
|
8
10
|
|
|
9
11
|
**A run failed.** `/cron runs` shows the error; `/cron trace <job> 1` shows the sub-agent's
|
|
10
|
-
transcript; `pi --session <file>` resumes it.
|
|
11
|
-
credentials, tools
|
|
12
|
+
transcript; `pi --session <file>` resumes it. Loop runs and rule checks run in the background. They
|
|
13
|
+
use the credentials pi saved, the built-in tools of the chat the loop was made in (or the job's
|
|
14
|
+
`--tools`), the MCP servers in `mcp.toml`, and the project's own servers and settings only when pi
|
|
15
|
+
has saved trust for that exact directory (`/trust` in a pi opened there). A plain job's run in its
|
|
16
|
+
closed conversation uses pi's saved trust for that directory or a parent of it. They do not get `-e` extensions or flags from your pi
|
|
17
|
+
window, and they cannot answer permission prompts. The background process has the environment of
|
|
18
|
+
the process that started it: a pi window's, whose shell's exported keys it then has, or a service
|
|
19
|
+
manager's, which has none of them. `pi-loops status` names that process; `pi-loops restart` from a
|
|
20
|
+
shell gives it that shell's environment. Keys saved with pi's `/login` always reach it.
|
|
12
21
|
|
|
13
22
|
**Findings never arrive.** The loop must end its reply with `<inbox>…</inbox>` tags; check
|
|
14
23
|
`/cron trace`. Quiet runs are normal. With `--verify`, dropped findings and reasons are on the run
|
|
@@ -39,7 +48,7 @@ digits into the box on that page. The terminal that started the session printed
|
|
|
39
48
|
|
|
40
49
|
**The phone cannot reach it at all.** The server binds loopback by default, and a phone has no
|
|
41
50
|
route to that. Either `tailscale serve --bg 4173` (the server stays on loopback; the tailnet does
|
|
42
|
-
TLS) or
|
|
51
|
+
TLS) or set `[page] host = "0.0.0.0"` in `config.toml` and run `pi-loops restart` for a phone on the same wifi. See
|
|
43
52
|
[cli.md](cli.md#from-a-phone).
|
|
44
53
|
|
|
45
54
|
**The QR is offered but scanning it goes nowhere.** The QR encodes the address *the browser you
|
|
@@ -48,20 +57,21 @@ address a phone could use — it says so instead of drawing one. Open the tailne
|
|
|
48
57
|
the desktop first, then press it there. Where the machine has several addresses, the dialog lists
|
|
49
58
|
them with the interface each belongs to; a docker or libvirt bridge is not the one.
|
|
50
59
|
|
|
51
|
-
**"
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
the
|
|
55
|
-
|
|
56
|
-
|
|
60
|
+
**"an older pi-loops page is still open on 127.0.0.1:4173" or "is used by another program".** The
|
|
61
|
+
page is served by pi-loops in the background, and a second `pi-loops` opens a conversation in it
|
|
62
|
+
rather than starting a second page. Before 0.24 the page ran in its terminal: quit that one (Ctrl-C
|
|
63
|
+
in its terminal) and the page takes the port within 30 seconds. Another program on 4173 is not
|
|
64
|
+
waited for: set `[page] port` in `config.toml`, then `pi-loops restart`. A browser pairs again on the
|
|
65
|
+
new port: the secret it keeps belongs to the old address, port included. Run `pi-loops` and it opens
|
|
66
|
+
a window that is signed in.
|
|
57
67
|
|
|
58
68
|
**A pairing code stopped working.** They last ten minutes, are good for one use, and closing the
|
|
59
69
|
dialog retires the one it was showing. Twenty wrong guesses disable pairing until another code is
|
|
60
70
|
made. Press **add device** again.
|
|
61
71
|
|
|
62
72
|
**Every device was signed out at once.** Something removed `~/.pi/agent/loops/web-token`, or
|
|
63
|
-
`PI_LOOPS_DIR` points somewhere new — the token is that file, and
|
|
64
|
-
|
|
73
|
+
`PI_LOOPS_DIR` points somewhere new — the token is that file, and every paired device is recorded
|
|
74
|
+
against it in `web-devices.json`.
|
|
65
75
|
|
|
66
76
|
**The window stops answering — you type, your message appears, no reply ever does.** The event
|
|
67
77
|
stream is numbered per run of the server process, so a page that was open across a restart can end
|
|
@@ -79,7 +89,9 @@ Bearer tokens come from `$TOKEN_REF` or pi's credential store; endpoints must be
|
|
|
79
89
|
127.0.0.1. Custom notifications without `_meta.pi_dedup_key` are dropped and counted.
|
|
80
90
|
|
|
81
91
|
**Hooks do not run.** Startup warnings list malformed rules. Project hooks need
|
|
82
|
-
`allow_project_hooks = true`.
|
|
92
|
+
`allow_project_hooks = true`. `agent_*`, `turn_*`, `message_*`, `tool_*` and `compaction` fire in the pi you are talking to.
|
|
93
|
+
`run_start` / `run_end` fire only in pi-loops in the background, for loop runs (output in
|
|
94
|
+
`host.log`); a plain job fires neither. Sub-agents fire nothing. See [hooks.md](hooks.md).
|
|
83
95
|
|
|
84
96
|
**`/session-export` says the session is ephemeral.** pi writes the session file after the first
|
|
85
97
|
message; `--no-session` sessions cannot be exported.
|
|
@@ -93,17 +105,16 @@ run is recorded as aborted rather than failed, so the slot is still owed and the
|
|
|
93
105
|
streak is untouched; it simply will not be dispatched again until the day rolls over or the cap is
|
|
94
106
|
raised.
|
|
95
107
|
|
|
96
|
-
**`pi-loops
|
|
108
|
+
**`pi-loops status` says pi-loops "is not answering" but it is running.** Before 0.4.0 this
|
|
97
109
|
happened whenever `PI_LOOPS_DIR` was deep: a unix socket path is capped at 108 bytes, so
|
|
98
|
-
|
|
99
|
-
to a short path under a per-user directory in the temp directory. If you still see it,
|
|
100
|
-
genuinely wedged — `host.log` has its last line, and `pi-loops
|
|
101
|
-
|
|
102
|
-
**
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
After testing with a throwaway `PI_LOOPS_DIR`, remember that the host it spawned outlives the pi
|
|
110
|
+
the old socket path failed to bind and the control channel silently did not exist. It now falls back
|
|
111
|
+
to a short path under a per-user directory in the temp directory. If you still see it, pi-loops is
|
|
112
|
+
genuinely wedged — `host.log` has its last line, and `pi-loops stop` escalates to SIGTERM.
|
|
113
|
+
|
|
114
|
+
**pi-loops is running and you did not start it.** A window starts pi-loops when there is work to run, and
|
|
115
|
+
`pi-loops status` shows its state; `pi-loops stop`
|
|
116
|
+
stops it until `pi-loops start`, or until `pi-loops` opens the page. `[host] auto = false` prevents windows from starting it.
|
|
117
|
+
After testing with a throwaway `PI_LOOPS_DIR`, remember that pi-loops it started outlives the pi
|
|
107
118
|
that spawned it — it is polling and billing against *that* directory until stopped.
|
|
108
119
|
|
|
109
120
|
**The browser front end refuses a slash command.** It drives `pi --mode rpc`,
|