@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.
Files changed (68) hide show
  1. package/CHANGELOG.md +1989 -0
  2. package/LICENSE +21 -0
  3. package/README.md +370 -0
  4. package/README.zh-CN.md +606 -0
  5. package/docs/cli.md +252 -0
  6. package/docs/configuration.md +110 -0
  7. package/docs/design.md +155 -0
  8. package/docs/goal.md +59 -0
  9. package/docs/hooks.md +110 -0
  10. package/docs/loops.md +315 -0
  11. package/docs/mcp.md +90 -0
  12. package/docs/screenshot.png +0 -0
  13. package/docs/session-archive.md +64 -0
  14. package/docs/triggers.md +140 -0
  15. package/docs/troubleshooting.md +111 -0
  16. package/docs/web-ui-parity.md +305 -0
  17. package/examples/README.md +18 -0
  18. package/examples/mcp-notify-server.mjs +35 -0
  19. package/examples/mcp.toml +8 -0
  20. package/package.json +71 -0
  21. package/skills/pi-loops/SKILL.md +50 -0
  22. package/src/archive.ts +476 -0
  23. package/src/args.ts +217 -0
  24. package/src/cli-entry.mjs +23 -0
  25. package/src/cli.ts +610 -0
  26. package/src/config.ts +103 -0
  27. package/src/danger.ts +128 -0
  28. package/src/goal.ts +178 -0
  29. package/src/hooks.ts +436 -0
  30. package/src/host-control-channel.ts +243 -0
  31. package/src/host-control.ts +203 -0
  32. package/src/host-entry.mjs +14 -0
  33. package/src/host-runtime.ts +222 -0
  34. package/src/host.ts +329 -0
  35. package/src/inbox.ts +232 -0
  36. package/src/job-edit.ts +113 -0
  37. package/src/job-health.ts +29 -0
  38. package/src/lock.ts +193 -0
  39. package/src/log.ts +108 -0
  40. package/src/mcp-pool.ts +141 -0
  41. package/src/mcp.ts +885 -0
  42. package/src/paths.ts +33 -0
  43. package/src/pi-loops.ts +2292 -0
  44. package/src/pi-resolver.mjs +49 -0
  45. package/src/presence.ts +143 -0
  46. package/src/protocol.ts +198 -0
  47. package/src/redact.ts +61 -0
  48. package/src/register-pi.mjs +3 -0
  49. package/src/runner.ts +149 -0
  50. package/src/schedule.ts +431 -0
  51. package/src/scheduler.ts +981 -0
  52. package/src/sdk-runner.ts +536 -0
  53. package/src/share.ts +121 -0
  54. package/src/slots.ts +76 -0
  55. package/src/snapshot.ts +40 -0
  56. package/src/store.ts +599 -0
  57. package/src/subagent-guard.ts +47 -0
  58. package/src/thinking.ts +30 -0
  59. package/src/toml.ts +244 -0
  60. package/src/tools.ts +455 -0
  61. package/src/transcript.ts +84 -0
  62. package/src/trigger-runtime.ts +711 -0
  63. package/src/triggers.ts +558 -0
  64. package/src/trust.ts +47 -0
  65. package/src/ts-entry.mjs +67 -0
  66. package/src/ui-prefs.ts +84 -0
  67. package/src/version.ts +14 -0
  68. package/src/web.mjs +4484 -0
package/src/slots.ts ADDED
@@ -0,0 +1,76 @@
1
+ /**
2
+ * The one place that answers "may another sub-agent start now".
3
+ *
4
+ * A loop run, a trigger check and the /goal evaluator are the same thing to this process: an
5
+ * in-process pi session with its own model calls and its own bill. `[cron] max_concurrent_runs`
6
+ * reads as "at most this many sub-agents at once" — that is how anyone sizes it, for memory, for
7
+ * rate limits, for their own money — but the scheduler and the trigger runtime each counted only
8
+ * their own kind against it, so 3 permitted three runs *plus* three checks *plus* an evaluator.
9
+ * One counter, taken from and released by both pipelines, is what makes the number true.
10
+ *
11
+ * Per process on purpose. This bounds the sessions *this* pi opens; two pi windows are two
12
+ * processes with two pools, as they are two of everything else here. Nothing coordinates across
13
+ * processes and nothing should: a file lock in the admission path would put a disk round-trip
14
+ * inside every tick to bound a resource (this process's memory and sockets) that is not shared.
15
+ *
16
+ * What a refusal *means* is deliberately not decided here. The scheduler leaves the tick owed and
17
+ * retries; the trigger runtime queues a push and drops a periodic check. Admission is one question
18
+ * with one answer; what to do with a "no" belongs to the pipeline that asked.
19
+ */
20
+
21
+ export interface SubagentSlot {
22
+ /** Idempotent: the release sites are `finally` blocks, and more than one can cover a path. */
23
+ release(): void;
24
+ }
25
+
26
+ export class SubagentSlots {
27
+ private inUse = 0;
28
+ private readonly getLimit: () => number;
29
+
30
+ constructor(limit: number | (() => number)) {
31
+ this.getLimit = typeof limit === "function" ? limit : () => limit;
32
+ }
33
+
34
+ /** Read live, so a config reload takes effect at the next admission and not at the next restart. */
35
+ get limit(): number {
36
+ return this.getLimit();
37
+ }
38
+
39
+ /** What `/triggers running` reports against `limit`, across both pipelines. */
40
+ get inUseCount(): number {
41
+ return this.inUse;
42
+ }
43
+
44
+ /** Never negative: `occupy` can push the count past the limit. */
45
+ get free(): number {
46
+ return Math.max(0, this.limit - this.inUse);
47
+ }
48
+
49
+ /** A slot for work that may be refused; `undefined` when the limit is already reached. */
50
+ acquire(): SubagentSlot | undefined {
51
+ if (this.inUse >= this.limit) return undefined;
52
+ return this.take();
53
+ }
54
+
55
+ /**
56
+ * A slot for work that is never refused: `/cron run` and the /goal evaluator, which the user
57
+ * asked for directly and which would break silently — a goal that stops being evaluated looks
58
+ * like a goal that was never set — if a busy machine could say no. It still counts, so the
59
+ * number stays honest; this is how `/triggers running` comes to say "4 of 3 sub-agent slot(s) in use".
60
+ */
61
+ occupy(): SubagentSlot {
62
+ return this.take();
63
+ }
64
+
65
+ private take(): SubagentSlot {
66
+ this.inUse++;
67
+ let released = false;
68
+ return {
69
+ release: () => {
70
+ if (released) return;
71
+ released = true;
72
+ this.inUse--;
73
+ },
74
+ };
75
+ }
76
+ }
@@ -0,0 +1,40 @@
1
+ /**
2
+ * What change deserves a permanent entry in the session file.
3
+ *
4
+ * `/cron`'s snapshot is appended with `pi.appendEntry`, which is forever: it resumes with the session
5
+ * and travels in archives. A snapshot per tick would grow the transcript for nobody, so the decision
6
+ * has two halves — what counts as a change at all, and how often even a real change may be written.
7
+ */
8
+
9
+ /** No more than one entry a minute, however often the numbers move. */
10
+ export const SNAPSHOT_MIN_GAP_MS = 60_000;
11
+
12
+ /**
13
+ * What counts as a change worth an entry: which servers are connected and what they exposed, who
14
+ * owns the clock, the tools, the hooks, how many jobs and rules there are. Deliberately not the
15
+ * counters — a server pushing every ten seconds moves `queued` constantly, and writing the session
16
+ * file four times a minute to record that is not observability, it is noise. The counters are still
17
+ * in the entry; they just do not trigger one, and `/cron snapshot` forces a fresh entry whenever a
18
+ * reader wants current numbers. The timestamp is excluded for the same reason.
19
+ */
20
+ export function snapshotFingerprint(data: any): string {
21
+ return JSON.stringify({
22
+ scheduler: { running: data.scheduler?.running, leader: data.scheduler?.leader },
23
+ counts: data.counts,
24
+ mcp: (data.mcp ?? []).map((m: any) => ({ name: m.name, state: m.state, tools: m.tools, attention: m.attention, lastError: m.lastError })),
25
+ mcpConfigError: data.mcpConfigError,
26
+ hooks: data.hooks,
27
+ tools: data.tools,
28
+ });
29
+ }
30
+
31
+ /**
32
+ * Whether to write the snapshot whose fingerprint is `next`. `force` is `/cron snapshot`: someone is
33
+ * asking for the current numbers, and they get an entry even if nothing changed and the last one was
34
+ * a second ago.
35
+ */
36
+ export function shouldEmitSnapshot(prev: { fingerprint?: string; at: number }, next: string, now: number, force: boolean): boolean {
37
+ if (force) return true;
38
+ if (next === prev.fingerprint) return false;
39
+ return now - prev.at >= SNAPSHOT_MIN_GAP_MS;
40
+ }