@alphacoder-v0/pi-loops 0.17.4 → 0.17.6
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 +35 -0
- package/README.md +4 -3
- package/README.zh-CN.md +4 -3
- package/package.json +2 -2
- package/src/cli.ts +43 -6
- package/src/extension-entry.ts +24 -0
- package/src/pi-floor.ts +37 -0
- package/src/pi-loops.ts +6 -7
- package/src/web.mjs +6 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,41 @@
|
|
|
3
3
|
All notable changes to pi-loops are documented here. The format follows
|
|
4
4
|
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/); versions follow SemVer.
|
|
5
5
|
|
|
6
|
+
## [0.17.6] - 2026-09-14
|
|
7
|
+
|
|
8
|
+
### Added
|
|
9
|
+
- **A pi version floor, and a refusal that names it.** pi-loops needs pi ≥ 0.84.3 — found by
|
|
10
|
+
type-checking `src/` against older releases: 0.80.8 fails on `ctx.thinkingLevel` and
|
|
11
|
+
`session_compact_failed`, 0.84.3 passes clean — and nothing said so. `peerDependencies` is `*`
|
|
12
|
+
because pi's docs ask for that and pi installs with `--legacy-peer-deps`, which reads no range
|
|
13
|
+
anyway; so on an older pi the first sign was a link error, "does not provide an export named
|
|
14
|
+
ModelRuntime", from which nobody could read "your pi is too old". The manifest now points at
|
|
15
|
+
`src/extension-entry.ts`, which imports nothing pi has not exported for a long time, reads
|
|
16
|
+
pi's `VERSION` against `PI_MIN_VERSION` in `src/pi-floor.ts`, and only then imports the
|
|
17
|
+
extension; an old pi prints `pi-loops needs pi 0.84.3 or newer and this is pi 0.80.8: upgrade pi`.
|
|
18
|
+
Both READMEs state the floor and a test keeps them, the manifest and the code on one number.
|
|
19
|
+
|
|
20
|
+
## [0.17.5] - 2026-09-14
|
|
21
|
+
|
|
22
|
+
### Fixed
|
|
23
|
+
- **`/pi-loops install-launcher` ignored `--dir`, and asked about a directory it was not going to
|
|
24
|
+
use.** Found by following the README, which says either route takes `--dir <dir>`: the
|
|
25
|
+
command-line `install-launcher` did, the one inside pi called `installLauncher(undefined)` and
|
|
26
|
+
showed a confirmation naming `~/.local/bin` whatever it was about to do. Answering yes to that
|
|
27
|
+
question, with a launcher you already rely on in `~/.local/bin`, replaced it. The slash command now
|
|
28
|
+
reads `--dir` (quoted, or starting with a `~` that no shell expanded on the way in) and the
|
|
29
|
+
question names the absolute directory that will be written, which is then the one written — and says when that directory is not on your PATH instead
|
|
30
|
+
of promising that `pi-loops` will work from anywhere. With nowhere to write it asks nothing and
|
|
31
|
+
prints the refusal, which already explains `--dir`. The menu shows the flag.
|
|
32
|
+
- **The browser window used `~/.pi/agent/loops` even when pi's agent directory was somewhere else.**
|
|
33
|
+
`PI_CODING_AGENT_DIR` moves everything pi keeps and the extension follows it; the launcher and
|
|
34
|
+
`web.mjs` did not. With the variable set, the page's token, its automation panel and the
|
|
35
|
+
`ui.json` holding the model you pick all lived in the default directory while the session behind
|
|
36
|
+
the page wrote to the moved one — so the page opened on another setup's remembered model (a
|
|
37
|
+
`Model "…" not found` warning, in the case that found it) and picking a model overwrote that
|
|
38
|
+
setup's choice. Both now resolve the directory the way the extension does, `~` included.
|
|
39
|
+
`PI_LOOPS_DIR` and `--loops-dir` still come first.
|
|
40
|
+
|
|
6
41
|
## [0.17.4] - 2026-09-14
|
|
7
42
|
|
|
8
43
|
### Added
|
package/README.md
CHANGED
|
@@ -29,15 +29,16 @@ reaches you is what changed rather than everything that is there.
|
|
|
29
29
|
|
|
30
30
|
## Install
|
|
31
31
|
|
|
32
|
-
Node ≥ 22.6 (pi loads the TypeScript sources directly) and
|
|
33
|
-
is tested against, and older
|
|
32
|
+
Node ≥ 22.6 (pi loads the TypeScript sources directly) and pi ≥ 0.84.3 to load it into — 0.85 is
|
|
33
|
+
what this is tested against, and an older pi is refused at load with a line that names the version
|
|
34
|
+
it needs, rather than a link error naming one missing export. Nothing else: pi-loops has no
|
|
34
35
|
runtime dependencies. What you do want first is a provider you can actually talk to — run `pi`, send
|
|
35
36
|
one message, make sure you get an answer. pi-loops runs sub-agents while you are not watching, and
|
|
36
37
|
the first sign of credentials that do not work should not be an empty inbox tomorrow morning.
|
|
37
38
|
|
|
38
39
|
```bash
|
|
39
40
|
pi install npm:@alphacoder-v0/pi-loops # from npm, following new releases
|
|
40
|
-
pi install git:github.com/alphacoder-v0/pi-loops@v0.17.
|
|
41
|
+
pi install git:github.com/alphacoder-v0/pi-loops@v0.17.6 # or from GitHub, at a pinned tag
|
|
41
42
|
pi install /path/to/pi-loops # or a local checkout — `pi install .` in this repo
|
|
42
43
|
pi -e /path/to/pi-loops # or none of them: try it for one run, installing nothing
|
|
43
44
|
```
|
package/README.zh-CN.md
CHANGED
|
@@ -37,7 +37,7 @@ pi-loops host status # 看一眼没有 pi 开着时在跑的自
|
|
|
37
37
|
|
|
38
38
|
### 1. 先确认前提
|
|
39
39
|
|
|
40
|
-
Node ≥ 22.6(pi 直接加载 TypeScript
|
|
40
|
+
Node ≥ 22.6(pi 直接加载 TypeScript 源码),pi ≥ 0.84.3(实际在 0.85 上测;更老的 pi 会在加载时被明确拒绝,报一句写明所需版本的话,而不是一个缺导出的链接错误),以及**一个真的能说上话的 provider**——先跑一次 `pi`,发一句,确认有回答。pi-loops 会在你不看着的时候替你跑子代理;凭据没配好,别等到第二天早上看见空 inbox 才知道。
|
|
41
41
|
|
|
42
42
|
pi-loops 自己没有任何运行时依赖。
|
|
43
43
|
|
|
@@ -45,7 +45,7 @@ pi-loops 自己没有任何运行时依赖。
|
|
|
45
45
|
|
|
46
46
|
```bash
|
|
47
47
|
pi install npm:@alphacoder-v0/pi-loops # 从 npm 装,跟着新版本走
|
|
48
|
-
pi install git:github.com/alphacoder-v0/pi-loops@v0.17.
|
|
48
|
+
pi install git:github.com/alphacoder-v0/pi-loops@v0.17.6 # 或者从 GitHub 装,钉住一个 tag
|
|
49
49
|
pi install /path/to/pi-loops # 或者本地检出;本仓库里就是 pi install .
|
|
50
50
|
```
|
|
51
51
|
|
|
@@ -515,7 +515,8 @@ Output protocol (mandatory):
|
|
|
515
515
|
## 代码结构
|
|
516
516
|
|
|
517
517
|
```
|
|
518
|
-
src/pi-loops.ts
|
|
518
|
+
src/extension-entry.ts pi 加载的入口:先查 pi 版本(src/pi-floor.ts),够了再导入 src/pi-loops.ts
|
|
519
|
+
src/pi-loops.ts 扩展本体:命令、工具、生命周期、状态栏角标、面板
|
|
519
520
|
src/cli.ts `pi-loops`:会话入口(网页或终端)与 sessions / inspect / export / import / host
|
|
520
521
|
src/cli-entry.mjs bin
|
|
521
522
|
src/ts-entry.mjs .mjs 入口怎么加载本包的 .ts:Node 自己剥类型,装在 node_modules 下就借 pi 的 jiti
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alphacoder-v0/pi-loops",
|
|
3
|
-
"version": "0.17.
|
|
3
|
+
"version": "0.17.6",
|
|
4
4
|
"description": "An automation layer for pi: cron + stateful loops with a triage inbox, maker/checker verification, dynamic triggers, MCP notifications and tools, lifecycle hooks, session archives. A non-invasive pi extension.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
"pi": {
|
|
60
60
|
"image": "https://raw.githubusercontent.com/alphacoder-v0/pi-loops/main/docs/screenshot.png",
|
|
61
61
|
"extensions": [
|
|
62
|
-
"./src/
|
|
62
|
+
"./src/extension-entry.ts"
|
|
63
63
|
],
|
|
64
64
|
"skills": [
|
|
65
65
|
"./skills"
|
package/src/cli.ts
CHANGED
|
@@ -583,6 +583,40 @@ export function cliRoute(argv: string[]): "launch" | "subcommand" {
|
|
|
583
583
|
return "subcommand";
|
|
584
584
|
}
|
|
585
585
|
|
|
586
|
+
/**
|
|
587
|
+
* Where the launcher goes: `--dir` if given, otherwise the first of ~/.local/bin and /usr/local/bin
|
|
588
|
+
* that is on PATH. One function, because the question asked before writing and the write itself
|
|
589
|
+
* have to name the same directory.
|
|
590
|
+
*/
|
|
591
|
+
export function launcherTarget(dir: string | undefined, pathEnv: string = process.env.PATH ?? ""): string | undefined {
|
|
592
|
+
if (dir) return path.resolve(dir);
|
|
593
|
+
return [path.join(os.homedir(), ".local", "bin"), "/usr/local/bin"].find((d) => isOnPath(d, pathEnv));
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
function isOnPath(dir: string, pathEnv: string = process.env.PATH ?? ""): boolean {
|
|
597
|
+
return pathEnv.split(path.delimiter).filter(Boolean).some((p) => path.resolve(p) === path.resolve(dir));
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
/**
|
|
601
|
+
* `/pi-loops install-launcher [--dir <dir>]`: the command line's `install-launcher`, asked first,
|
|
602
|
+
* because inside pi it is a click away from replacing a launcher you already have. The question
|
|
603
|
+
* names the directory that will be written and nothing else. Resolves undefined when the answer
|
|
604
|
+
* is no; with nowhere to write there is nothing to ask, and the refusal says why.
|
|
605
|
+
*/
|
|
606
|
+
export async function installLauncherWithConfirm(args: string[], confirm: (title: string, body: string) => Promise<boolean>, out: (line: string) => void): Promise<number | undefined> {
|
|
607
|
+
const flag = parseCliArgs(["install-launcher", ...args]).flags.get("dir");
|
|
608
|
+
// No shell has expanded `~` on the way here, so it is expanded now or becomes a directory named `~`.
|
|
609
|
+
const dir = typeof flag === "string" ? flag.replace(/^~(?=$|\/)/, os.homedir()) : undefined;
|
|
610
|
+
const target = launcherTarget(dir);
|
|
611
|
+
if (!target) return installLauncher(undefined, out);
|
|
612
|
+
const reach = isOnPath(target) ? ["Afterwards `pi-loops` starts a session from any directory."] : [`${target} is not on your PATH, so \`pi-loops\` will not be found until it is.`];
|
|
613
|
+
const ok = await confirm("Put `pi-loops` on your PATH?", [`This writes a launcher into ${target}.`, "", "It is a two-line shell script that runs this package with this node.", ...reach].join("\n"));
|
|
614
|
+
if (!ok) return undefined;
|
|
615
|
+
// The absolute directory the question named, not the flag again: resolving a relative --dir a
|
|
616
|
+
// second time, after the dialog, would follow a working directory that may have moved meanwhile.
|
|
617
|
+
return installLauncher(target, out);
|
|
618
|
+
}
|
|
619
|
+
|
|
586
620
|
/**
|
|
587
621
|
* `pi install` puts this package under pi's managed directory rather than on your PATH, so the
|
|
588
622
|
* command that is supposed to start your sessions is reachable only by absolute path. This writes
|
|
@@ -590,14 +624,13 @@ export function cliRoute(argv: string[]): "launch" | "subcommand" {
|
|
|
590
624
|
* that fixes it without asking you to publish or install anything else.
|
|
591
625
|
*/
|
|
592
626
|
export async function installLauncher(dir: string | undefined, out: (line: string) => void): Promise<number> {
|
|
593
|
-
const
|
|
594
|
-
const target = dir ? path.resolve(dir) : [path.join(os.homedir(), ".local", "bin"), "/usr/local/bin"].find((d) => onPath.includes(path.resolve(d)));
|
|
627
|
+
const target = launcherTarget(dir);
|
|
595
628
|
if (!target) {
|
|
596
629
|
out("no directory to install into: neither ~/.local/bin nor /usr/local/bin is on your PATH.");
|
|
597
630
|
out("pass one with --dir <dir>, or add ~/.local/bin to PATH and run this again.");
|
|
598
631
|
return 1;
|
|
599
632
|
}
|
|
600
|
-
if (dir && !
|
|
633
|
+
if (dir && !isOnPath(target)) out(`note: ${target} is not on your PATH, so the command will not be found there yet`);
|
|
601
634
|
const entry = path.join(path.dirname(fileURLToPath(import.meta.url)), "cli-entry.mjs");
|
|
602
635
|
const file = path.join(target, "pi-loops");
|
|
603
636
|
// A launcher rather than a symlink: it survives the package moving, and it names the node that
|
|
@@ -641,13 +674,17 @@ async function launch(argv: string[], out: (line: string) => void): Promise<numb
|
|
|
641
674
|
return runChild(process.execPath, [web, ...ours.filter((a) => a !== "--web" && a !== "--tui"), ...(withDefaults.length ? ["--", ...withDefaults] : [])]);
|
|
642
675
|
}
|
|
643
676
|
|
|
644
|
-
/**
|
|
645
|
-
|
|
677
|
+
/**
|
|
678
|
+
* Where the data lives for this run: the flag, then the environment, then the default — the default
|
|
679
|
+
* being the extension's own (`loops` under pi's agent directory, which `PI_CODING_AGENT_DIR` moves),
|
|
680
|
+
* so the window reads what the session behind it writes.
|
|
681
|
+
*/
|
|
682
|
+
export function loopsDir(ours: string[]): string {
|
|
646
683
|
const at = ours.indexOf("--loops-dir");
|
|
647
684
|
if (at !== -1 && ours[at + 1]) return ours[at + 1];
|
|
648
685
|
const eq = ours.find((a) => a.startsWith("--loops-dir="));
|
|
649
686
|
if (eq) return eq.slice("--loops-dir=".length);
|
|
650
|
-
return process.env.PI_LOOPS_DIR ||
|
|
687
|
+
return process.env.PI_LOOPS_DIR || defaultLoopsDir(getAgentDir());
|
|
651
688
|
}
|
|
652
689
|
|
|
653
690
|
/**
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What pi loads (`pi.extensions` in package.json): a version check, then the extension.
|
|
3
|
+
*
|
|
4
|
+
* The check cannot sit in `src/pi-loops.ts`. That file and the modules under it import names pi
|
|
5
|
+
* did not always export (`ModelRuntime`, `readStoredCredential`, `createSyntheticSourceInfo`) and
|
|
6
|
+
* subscribe to events it did not always emit. ESM links a whole import graph before running a line
|
|
7
|
+
* of it, so on an older pi the failure is a link error naming one export — "does not provide an
|
|
8
|
+
* export named ModelRuntime" — and nothing in it says "your pi is too old". This file imports only
|
|
9
|
+
* what every pi has exported for a long time, reads the version, and imports the rest afterwards,
|
|
10
|
+
* so what an old pi prints is the sentence that says which pi it needs.
|
|
11
|
+
*
|
|
12
|
+
* A namespace import on purpose: a name missing from an old pi is then `undefined` rather than a
|
|
13
|
+
* link error, which is exactly the case the check exists for.
|
|
14
|
+
*/
|
|
15
|
+
import * as sdk from "@earendil-works/pi-coding-agent";
|
|
16
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
17
|
+
import { piTooOld } from "./pi-floor.ts";
|
|
18
|
+
|
|
19
|
+
export default async function (pi: ExtensionAPI) {
|
|
20
|
+
const refusal = piTooOld(sdk.VERSION);
|
|
21
|
+
if (refusal) throw new Error(refusal);
|
|
22
|
+
const { default: piLoops } = await import("./pi-loops.ts");
|
|
23
|
+
return piLoops(pi);
|
|
24
|
+
}
|
package/src/pi-floor.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The oldest pi this extension loads into. `src/extension-entry.ts` reads it before anything else
|
|
3
|
+
* is imported, both READMEs state it, and `test/pi-floor.test.ts` keeps the three the same.
|
|
4
|
+
*
|
|
5
|
+
* 0.84.3 is where the API this code is written against became complete: `session_compact_failed`
|
|
6
|
+
* arrived there, `ModelRuntime` and `readStoredCredential` in 0.80.8, `ctx.thinkingLevel` in
|
|
7
|
+
* between. It was found by type-checking `src/` against older releases rather than by reading
|
|
8
|
+
* changelogs — 0.80.8 fails on the two newest names, 0.84.3 passes clean — and it is the version
|
|
9
|
+
* to re-check the same way when a newer name is used. Nothing enforces it at install time: pi
|
|
10
|
+
* installs packages with `--legacy-peer-deps`, so `peerDependencies` stays `*` as pi's docs ask
|
|
11
|
+
* and the range there would not be read anyway.
|
|
12
|
+
*/
|
|
13
|
+
export const PI_MIN_VERSION = "0.84.3";
|
|
14
|
+
|
|
15
|
+
/** `[major, minor, patch]`, or undefined for anything that does not start `N.N.N` (a `v` in front is fine). */
|
|
16
|
+
function parse(version: string): [number, number, number] | undefined {
|
|
17
|
+
const m = /^v?(\d+)\.(\d+)\.(\d+)/.exec(version.trim());
|
|
18
|
+
return m ? [Number(m[1]), Number(m[2]), Number(m[3])] : undefined;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Why this pi cannot load pi-loops, as the one sentence pi prints after "Failed to load extension",
|
|
23
|
+
* or undefined when it can. A pi that reports no version is refused as well: every pi new enough
|
|
24
|
+
* to load this exports `VERSION`, so a missing one is an old one, not an unknown one.
|
|
25
|
+
*/
|
|
26
|
+
export function piTooOld(version: string | undefined, min: string = PI_MIN_VERSION): string | undefined {
|
|
27
|
+
const have = version === undefined ? undefined : parse(version);
|
|
28
|
+
const need = parse(min);
|
|
29
|
+
if (!need) throw new Error(`PI_MIN_VERSION is not a version: ${JSON.stringify(min)}`);
|
|
30
|
+
const upgrade = "upgrade pi (`pi update`, or `npm i -g @earendil-works/pi-coding-agent`)";
|
|
31
|
+
if (!have) return `pi-loops needs pi ${min} or newer and this pi reports ${version === undefined ? "no version" : `version ${JSON.stringify(version)}`}: ${upgrade}`;
|
|
32
|
+
for (let i = 0; i < 3; i++) {
|
|
33
|
+
if (have[i] > need[i]) return undefined;
|
|
34
|
+
if (have[i] < need[i]) return `pi-loops needs pi ${min} or newer and this is pi ${version}: ${upgrade}`;
|
|
35
|
+
}
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
package/src/pi-loops.ts
CHANGED
|
@@ -18,7 +18,7 @@ import { ProjectTrustStore, VERSION as PI_VERSION, getAgentDir, readStoredCreden
|
|
|
18
18
|
import { ARCHIVE_EXT, defaultExportPath, exportSession, importSession } from "./archive.ts";
|
|
19
19
|
import { Box, Text } from "@earendil-works/pi-tui";
|
|
20
20
|
import { Type } from "typebox";
|
|
21
|
-
import { parseAddArgs, parseSetArgs, splitCommand } from "./args.ts";
|
|
21
|
+
import { parseAddArgs, parseSetArgs, splitCommand, tokenize } from "./args.ts";
|
|
22
22
|
import { envFlag, loadConfig } from "./config.ts";
|
|
23
23
|
import { GOAL_ENTRY, type GoalAction, type GoalState, MAX_CONTINUATIONS, applyDecision, branchMovedSince, continuationPrompt, evaluatorPrompt, latestGoal, newGoal, parseDecision, pauseFor, transcriptFromMessages } from "./goal.ts";
|
|
24
24
|
import { withinProject } from "./presence.ts";
|
|
@@ -32,7 +32,7 @@ import { McpSource, PI_BUILTIN_TOOL_NAMES, type McpServerConfig, type McpToolDef
|
|
|
32
32
|
import { capRedacted, previewRedacted, redact } from "./redact.ts";
|
|
33
33
|
import { shouldEmitSnapshot, snapshotFingerprint } from "./snapshot.ts";
|
|
34
34
|
import { type ShareMessage, renderShare, shareSummary } from "./share.ts";
|
|
35
|
-
import {
|
|
35
|
+
import { installLauncherWithConfirm } from "./cli.ts";
|
|
36
36
|
import { createHash } from "node:crypto";
|
|
37
37
|
import { computeDue, computeNext, formatLocal, formatSchedule, localOffset, parseSchedule, stamp } from "./schedule.ts";
|
|
38
38
|
import { applyJobEdit } from "./job-edit.ts";
|
|
@@ -1821,13 +1821,12 @@ export default function piLoops(pi: ExtensionAPI) {
|
|
|
1821
1821
|
// `pi install` does not put the package on your PATH. You are already inside pi with
|
|
1822
1822
|
// this extension loaded, so here is where that circle can be broken.
|
|
1823
1823
|
const lines: string[] = [];
|
|
1824
|
-
const
|
|
1825
|
-
const
|
|
1826
|
-
if (
|
|
1824
|
+
const words = tokenize(splitCommand(args).rest).map((t) => t.value);
|
|
1825
|
+
const code = await installLauncherWithConfirm(words, (title, body) => ctx.ui.confirm(title, body), (l) => lines.push(l));
|
|
1826
|
+
if (code === undefined) {
|
|
1827
1827
|
ctx.ui.notify("not installed", "info");
|
|
1828
1828
|
return;
|
|
1829
1829
|
}
|
|
1830
|
-
const code = await installLauncher(undefined, (l) => lines.push(l));
|
|
1831
1830
|
show(ctx, code === 0 ? "installed the pi-loops launcher" : "could not install the launcher", lines.map((l) => ` ${l.trim()}`));
|
|
1832
1831
|
return;
|
|
1833
1832
|
}
|
|
@@ -1835,7 +1834,7 @@ export default function piLoops(pi: ExtensionAPI) {
|
|
|
1835
1834
|
` loops directory: ${homeRel(dir)}`,
|
|
1836
1835
|
` package: ${homeRel(PACKAGE_DIR)}`,
|
|
1837
1836
|
"",
|
|
1838
|
-
" /pi-loops install-launcher put the `pi-loops` command on your PATH",
|
|
1837
|
+
" /pi-loops install-launcher [--dir <dir>] put the `pi-loops` command on your PATH",
|
|
1839
1838
|
" /cron · /triggers · /inbox · /goal the automation itself",
|
|
1840
1839
|
]);
|
|
1841
1840
|
},
|
package/src/web.mjs
CHANGED
|
@@ -91,7 +91,12 @@ if (!/^\d+$/.test(portArg) || Number(portArg) > 65535) {
|
|
|
91
91
|
process.exit(1);
|
|
92
92
|
}
|
|
93
93
|
const PORT = Number(portArg);
|
|
94
|
-
|
|
94
|
+
// The directory the extension inside the pi behind this page uses (`defaultLoopsDir(getAgentDir())`,
|
|
95
|
+
// which this file cannot import): PI_LOOPS_DIR, else `loops` under pi's agent directory — moved by
|
|
96
|
+
// PI_CODING_AGENT_DIR, with a leading `~` expanded as pi expands it. Reading ~/.pi/agent/loops
|
|
97
|
+
// regardless put the panel, the token and the remembered model in a directory the session was not using.
|
|
98
|
+
const AGENT_DIR = process.env.PI_CODING_AGENT_DIR ? process.env.PI_CODING_AGENT_DIR.replace(process.platform === "win32" ? /^~(?=$|[\\/])/ : /^~(?=$|\/)/, os.homedir()) : path.join(os.homedir(), ".pi", "agent");
|
|
99
|
+
const LOOPS_DIR = optionValue("loops-dir", process.env.PI_LOOPS_DIR || path.join(AGENT_DIR, "loops"));
|
|
95
100
|
/**
|
|
96
101
|
* The token is kept in a file rather than made fresh each launch, so the address stays the same
|
|
97
102
|
* one every time: bookmark http://127.0.0.1:4173/ and it works tomorrow. It is a boring secret —
|