@piagent/platform 1.45.0 → 1.46.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.
Files changed (81) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/README.md +5 -3
  3. package/README.vi.md +5 -3
  4. package/docs/command-reference-vietnamese.md +2 -2
  5. package/docs/distribution-standard.md +6 -6
  6. package/docs/languages.json +2 -2
  7. package/docs/operator-manual-vietnamese.md +7 -7
  8. package/docs/publishing-for-teams.md +1 -0
  9. package/docs/release-install-policy.md +2 -2
  10. package/docs/team-onboarding.md +2 -2
  11. package/docs-site/architecture.html +1 -1
  12. package/docs-site/assets/search-en.json +1 -1
  13. package/docs-site/assets/search-vi.json +1 -1
  14. package/docs-site/benchmark.html +1 -1
  15. package/docs-site/commands.html +1 -1
  16. package/docs-site/context-engine.html +1 -1
  17. package/docs-site/ecosystem.html +1 -1
  18. package/docs-site/en/architecture.html +1 -1
  19. package/docs-site/en/benchmark.html +1 -1
  20. package/docs-site/en/commands.html +1 -1
  21. package/docs-site/en/context-engine.html +1 -1
  22. package/docs-site/en/ecosystem.html +1 -1
  23. package/docs-site/en/index.html +9 -9
  24. package/docs-site/en/mcp-approval.html +1 -1
  25. package/docs-site/en/mcp-auth.html +1 -1
  26. package/docs-site/en/mcp-servers.html +1 -1
  27. package/docs-site/en/mcp.html +1 -1
  28. package/docs-site/en/permissions.html +1 -1
  29. package/docs-site/en/profiles.html +1 -1
  30. package/docs-site/en/quickstart.html +1 -1
  31. package/docs-site/en/runtime.html +1 -1
  32. package/docs-site/en/scope.html +1 -1
  33. package/docs-site/en/security.html +1 -1
  34. package/docs-site/en/team.html +1 -1
  35. package/docs-site/en/update.html +3 -3
  36. package/docs-site/en/whats-new.html +31 -19
  37. package/docs-site/en/windows.html +1 -1
  38. package/docs-site/en/workflows.html +1 -1
  39. package/docs-site/index.html +9 -9
  40. package/docs-site/mcp-approval.html +1 -1
  41. package/docs-site/mcp-auth.html +1 -1
  42. package/docs-site/mcp-servers.html +1 -1
  43. package/docs-site/mcp.html +1 -1
  44. package/docs-site/permissions.html +1 -1
  45. package/docs-site/profiles.html +1 -1
  46. package/docs-site/quickstart.html +1 -1
  47. package/docs-site/releases.json +39 -0
  48. package/docs-site/runtime.html +1 -1
  49. package/docs-site/scope.html +1 -1
  50. package/docs-site/security.html +1 -1
  51. package/docs-site/team.html +1 -1
  52. package/docs-site/update.html +3 -3
  53. package/docs-site/whats-new.html +31 -19
  54. package/docs-site/windows.html +1 -1
  55. package/docs-site/workflows.html +1 -1
  56. package/evals/private-holdout-v1/public-exposure.v2.json +2 -2
  57. package/package.json +1 -1
  58. package/packages/piagent-core/benchmark/benchmark-bootstrap.js +3 -1
  59. package/packages/piagent-core/extensions/agent-home.d.ts +4 -0
  60. package/packages/piagent-core/extensions/agent-home.js +52 -0
  61. package/packages/piagent-core/extensions/piagent-guard.ts +5 -1
  62. package/packages/piagent-core/mcp/mcp-config-layers.js +4 -1
  63. package/packages/piagent-core/package.json +1 -1
  64. package/packages/piagent-webui/extension/piagent-webui.ts +2 -0
  65. package/packages/piagent-webui/gateway/gateway-service.ts +4 -1
  66. package/packages/piagent-webui/gateway/pi-host.ts +2 -2
  67. package/packages/piagent-webui/ownership/profile-state.ts +10 -1
  68. package/packages/piagent-webui/server/benchmark-release-monitor.ts +2 -1
  69. package/scripts/benchmark-runner-support.mjs +2 -1
  70. package/scripts/dashboard-update-job.mjs +4 -2
  71. package/scripts/install-global.sh +113 -5
  72. package/scripts/model-scope-prune.mjs +2 -1
  73. package/scripts/personal-pi-config.mjs +2 -1
  74. package/scripts/personal-pi-restore.mjs +184 -0
  75. package/scripts/pi-usage-history.mjs +2 -1
  76. package/scripts/piagent-cli.mjs +49 -9
  77. package/scripts/piagent-dashboard.mjs +5 -3
  78. package/scripts/setup.sh +2 -6
  79. package/scripts/team-doctor.sh +11 -5
  80. package/scripts/uninstall-global.sh +17 -2
  81. package/scripts/update-global.mjs +5 -2
@@ -37,7 +37,7 @@
37
37
  <span class="brand-mark"><img src="/assets/piagent-logo.svg" alt="" width="28" height="28" /></span>
38
38
  <span class="brand-name"><b>Piagent</b> <span>Platform</span></span>
39
39
  </a>
40
- <a class="version-badge" href="/whats-new" data-docs-version="v1.45.0" title="v1.45.0 docs · VI"><span class="dot"></span>v1.45.0</a>
40
+ <a class="version-badge" href="/whats-new" data-docs-version="v1.46.0" title="v1.46.0 docs · VI"><span class="dot"></span>v1.46.0</a>
41
41
  <nav class="top-nav" aria-label="Tài liệu">
42
42
  <a href="/quickstart">Tài liệu</a>
43
43
  <a href="/whats-new" aria-current="page">Changelog</a>
@@ -108,7 +108,21 @@
108
108
  <div class="release-filter" role="toolbar" aria-label="Lọc theo loại thay đổi"><button type="button" data-filter-kind="all" aria-pressed="true">Tất cả</button><button type="button" data-filter-kind="new" aria-pressed="false">Mới</button><button type="button" data-filter-kind="improved" aria-pressed="false">Cải thiện</button><button type="button" data-filter-kind="fixed" aria-pressed="false">Sửa lỗi</button><button type="button" data-filter-kind="security" aria-pressed="false">Bảo mật</button></div>
109
109
  <section class="section releases" data-releases>
110
110
  <h2 id="latest" class="sr-only">Mới nhất</h2>
111
- <article class="release latest" id="v1-45-0" data-release>
111
+ <article class="release latest" id="v1-46-0" data-release>
112
+ <div class="release-meta"><a class="release-version" href="#v1-46-0">v1.46.0</a><time class="release-date" datetime="2026-10-28">28/10/2026</time></div>
113
+ <div class="release-body">
114
+ <h3 data-toc-skip id="v1-46-0-title">Piagent chạy trong Pi home riêng, pi của bạn giữ nguyên bản gốc</h3>
115
+ <p>Piagent cài vào ~/.piagent/agent và mở bằng <code>piagent</code>. <code>pi</code> thường không còn nạp Piagent, add-on, danh sách model hay panel footer.</p>
116
+ <ul class="release-items">
117
+ <li data-kind="breaking"><span class="kind kind-breaking">Thay đổi lớn</span><span>Piagent cài vào Pi home riêng ~/.piagent/agent và mở bằng <code>piagent</code>. <code>pi</code> thường trong ~/.pi/agent giữ nguyên bản gốc.</span></li>
118
+ <li data-kind="new"><span class="kind kind-new">Mới</span><span>Lần cài đầu trong terminal hỏi cài add-on nào và có dùng lại login của pi không; cờ dòng lệnh trả lời trước được.</span></li>
119
+ <li data-kind="new"><span class="kind kind-new">Mới</span><span><code>piagent restore-pi --apply</code> dời bản cài cũ khỏi ~/.pi/agent, có sao lưu; installer tự làm khi cập nhật.</span></li>
120
+ <li data-kind="fixed"><span class="kind kind-fixed">Sửa lỗi</span><span>Khi cài pi-mcp-adapter, Piagent tắt mcp có sẵn của Pi trong home của mình nên /mcp không còn xung đột.</span></li>
121
+ </ul>
122
+ <p class="release-requires"><strong>Cần:</strong> Agent Watch 0.15.16 để thấy session cá nhân của Piagent</p>
123
+ </div>
124
+ </article>
125
+ <article class="release" id="v1-45-0" data-release>
112
126
  <div class="release-meta"><a class="release-version" href="#v1-45-0">v1.45.0</a><time class="release-date" datetime="2026-10-28">28/10/2026</time></div>
113
127
  <div class="release-body">
114
128
  <h3 data-toc-skip id="v1-45-0-title">File và ảnh trong prompt theo đúng quyền đọc, terminal không còn đứng hình</h3>
@@ -407,28 +421,26 @@
407
421
  </ul>
408
422
  </div>
409
423
  </article>
410
- <article class="release" id="v1-38-0" data-release>
411
- <div class="release-meta"><a class="release-version" href="#v1-38-0">v1.38.0</a><time class="release-date" datetime="2026-10-23">23/10/2026</time></div>
412
- <div class="release-body">
413
- <h3 data-toc-skip id="v1-38-0-title">Piagent thành editor, Bypass không hỏi gì <span class="tag">không phát hành</span></h3>
414
- <p>Dashboard có file, terminal, review inline và checkpoint, cùng chế độ Bypass không hỏi gì, cả trong cuộc trò chuyện công ty khi Harness cho phép.</p>
415
- <ul class="release-items">
416
- <li data-kind="breaking"><span class="kind kind-breaking">Thay đổi lớn</span><span>Cuộc trò chuyện công ty có Hỏi trước, Toàn quyền (trước đây gọi là Bypass) và Bypass, chỉ có khi Harness cho phép.</span></li>
417
- <li data-kind="new"><span class="kind kind-new">Mới</span><span>Chế độ <code>bypass</code> (<code>/permission bypass</code>, <code>piagent-auto --bypass</code>) tắt mọi bước chặn; output vẫn được che secret và <code>/undo</code> vẫn hoàn tác được.</span></li>
418
- <li data-kind="new"><span class="kind kind-new">Mới</span><span>Dashboard mở, sửa, lưu, đổi tên và xóa file của project, có Cmd/Ctrl+P, tìm kiếm và báo xung đột khi file đã đổi trên đĩa.</span></li>
419
- <li data-kind="new"><span class="kind kind-new">Mới</span><span>Terminal của project ngay trên dashboard; terminal của cuộc trò chuyện công ty chạy trong sandbox, cả trên macOS, không bao giờ chạy trên host.</span></li>
420
- <li data-kind="new"><span class="kind kind-new">Mới</span><span>Review inline từng hunk agent sửa với Giữ và Hoàn tác, và khôi phục trong Timeline có xem trước, dùng chung checkpoint với <code>/undo</code>.</span></li>
421
- <li data-kind="fixed"><span class="kind kind-fixed">Sửa lỗi</span><span>Checkpoint không tạo được từng chặn mọi lần sửa; giờ thì không, và snapshot mất khoảng 0,5 s thay vì 35 s trên repo 4.000 file.</span></li>
422
- </ul>
423
- <p class="release-requires"><strong>Cần:</strong> Agent Studio 0.38.0 và Agent Watch 0.15.14 (Windows 0.1.17) cho mức Bypass ở chế độ công ty</p>
424
- </div>
425
- </article>
426
424
  </section>
427
425
  <section class="section archive">
428
426
  <h2 id="archive">Các bản trước</h2>
429
427
  <p class="section-lead">Bản cũ hơn, gom theo tháng. Mở một tháng để xem từng thay đổi.</p>
430
428
  <details class="release-archive">
431
- <summary><span>Tháng 10/2026</span><span class="archive-count">56</span></summary>
429
+ <summary><span>Tháng 10/2026</span><span class="archive-count">57</span></summary>
430
+ <div class="archive-row" id="v1-38-0">
431
+ <div class="archive-head"><a class="release-version" href="#v1-38-0">v1.38.0</a> <span class="release-date">23/10/2026</span></div>
432
+ <div class="archive-body">
433
+ <p><strong>Piagent thành editor, Bypass không hỏi gì</strong> <span class="tag">không phát hành</span> — Dashboard có file, terminal, review inline và checkpoint, cùng chế độ Bypass không hỏi gì, cả trong cuộc trò chuyện công ty khi Harness cho phép.</p>
434
+ <ul class="release-items">
435
+ <li data-kind="breaking"><span class="kind kind-breaking">Thay đổi lớn</span><span>Cuộc trò chuyện công ty có Hỏi trước, Toàn quyền (trước đây gọi là Bypass) và Bypass, chỉ có khi Harness cho phép.</span></li>
436
+ <li data-kind="new"><span class="kind kind-new">Mới</span><span>Chế độ <code>bypass</code> (<code>/permission bypass</code>, <code>piagent-auto --bypass</code>) tắt mọi bước chặn; output vẫn được che secret và <code>/undo</code> vẫn hoàn tác được.</span></li>
437
+ <li data-kind="new"><span class="kind kind-new">Mới</span><span>Dashboard mở, sửa, lưu, đổi tên và xóa file của project, có Cmd/Ctrl+P, tìm kiếm và báo xung đột khi file đã đổi trên đĩa.</span></li>
438
+ <li data-kind="new"><span class="kind kind-new">Mới</span><span>Terminal của project ngay trên dashboard; terminal của cuộc trò chuyện công ty chạy trong sandbox, cả trên macOS, không bao giờ chạy trên host.</span></li>
439
+ <li data-kind="new"><span class="kind kind-new">Mới</span><span>Review inline từng hunk agent sửa với Giữ và Hoàn tác, và khôi phục trong Timeline có xem trước, dùng chung checkpoint với <code>/undo</code>.</span></li>
440
+ <li data-kind="fixed"><span class="kind kind-fixed">Sửa lỗi</span><span>Checkpoint không tạo được từng chặn mọi lần sửa; giờ thì không, và snapshot mất khoảng 0,5 s thay vì 35 s trên repo 4.000 file.</span></li>
441
+ </ul>
442
+ </div>
443
+ </div>
432
444
  <div class="archive-row" id="v1-37-1">
433
445
  <div class="archive-head"><a class="release-version" href="#v1-37-1">v1.37.1</a> <span class="release-date">22/10/2026</span></div>
434
446
  <div class="archive-body">
@@ -37,7 +37,7 @@
37
37
  <span class="brand-mark"><img src="/assets/piagent-logo.svg" alt="" width="28" height="28" /></span>
38
38
  <span class="brand-name"><b>Piagent</b> <span>Platform</span></span>
39
39
  </a>
40
- <a class="version-badge" href="/whats-new" data-docs-version="v1.45.0" title="v1.45.0 docs · VI"><span class="dot"></span>v1.45.0</a>
40
+ <a class="version-badge" href="/whats-new" data-docs-version="v1.46.0" title="v1.46.0 docs · VI"><span class="dot"></span>v1.46.0</a>
41
41
  <nav class="top-nav" aria-label="Tài liệu">
42
42
  <a href="/quickstart">Tài liệu</a>
43
43
  <a href="/whats-new">Changelog</a>
@@ -37,7 +37,7 @@
37
37
  <span class="brand-mark"><img src="/assets/piagent-logo.svg" alt="" width="28" height="28" /></span>
38
38
  <span class="brand-name"><b>Piagent</b> <span>Platform</span></span>
39
39
  </a>
40
- <a class="version-badge" href="/whats-new" data-docs-version="v1.45.0" title="v1.45.0 docs · VI"><span class="dot"></span>v1.45.0</a>
40
+ <a class="version-badge" href="/whats-new" data-docs-version="v1.46.0" title="v1.46.0 docs · VI"><span class="dot"></span>v1.46.0</a>
41
41
  <nav class="top-nav" aria-label="Tài liệu">
42
42
  <a href="/quickstart">Tài liệu</a>
43
43
  <a href="/whats-new">Changelog</a>
@@ -256,8 +256,8 @@
256
256
  },
257
257
  {
258
258
  "path": "tests",
259
- "fileCount": 634,
260
- "sha256": "a3731ca8ba76de242f6ce801a4bd1e9a1e61951fe6dcada375503ab9976197be"
259
+ "fileCount": 635,
260
+ "sha256": "e192ed7528a5d5a992cd33c3837ee6f779b404e45cedc06f79978e2bfa9272eb"
261
261
  }
262
262
  ],
263
263
  "custodianComparisonRules": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@piagent/platform",
3
- "version": "1.45.0",
3
+ "version": "1.46.0",
4
4
  "license": "MIT",
5
5
  "publishConfig": {
6
6
  "access": "public",
@@ -494,7 +494,9 @@ function requestedPiProvider(model, configRoot, credentials) {
494
494
  }
495
495
 
496
496
  function snapshotPiAgentHome(temporaryRoot, runtimeParent, argv, cwd, replay, { providerFreeFinalization = false } = {}) {
497
- const sourceRoot = path.resolve(process.env.PI_CODING_AGENT_DIR || path.join(os.homedir(), ".pi", "agent"));
497
+ // Piagent's own Pi home holds the login benchmarks borrow (see extensions/agent-home.js).
498
+ const personal = path.resolve(os.homedir(), ".pi", "agent"), configured = process.env.PI_CODING_AGENT_DIR;
499
+ const sourceRoot = path.resolve(process.env.PIAGENT_AGENT_DIR || (configured && path.resolve(configured) !== personal ? configured : path.join(os.homedir(), ".piagent", "agent")));
498
500
  const configRoot = path.join(temporaryRoot, "pi-agent-config");
499
501
  fs.mkdirSync(configRoot, { recursive: true, mode: 0o700 });
500
502
  const copied = [];
@@ -0,0 +1,4 @@
1
+ export function personalPiAgentDir(home?: string): string;
2
+ export function piagentAgentDir(env?: NodeJS.ProcessEnv, home?: string): string;
3
+ export function agentWatchControlDir(home?: string): string;
4
+ export function dormantInPersonalPi(env?: NodeJS.ProcessEnv, home?: string, argv?: string[]): boolean;
@@ -0,0 +1,52 @@
1
+ import fs from "node:fs";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+
5
+ // Piagent runs Pi from its own home (~/.piagent/agent) so the member's
6
+ // personal `pi` stays the stock host: no Piagent package, add-ons, model scope
7
+ // or footer panel lands in ~/.pi/agent. Agent Watch still reads its control
8
+ // files (runtime record, managed binding, status) from ~/.pi/agent, so those
9
+ // stay where Watch looks; Pi itself ignores them.
10
+
11
+ /** The stock Pi home, the member's personal `pi`. */
12
+ export function personalPiAgentDir(home = os.homedir()) {
13
+ return path.resolve(home, ".pi", "agent");
14
+ }
15
+
16
+ /**
17
+ * Where Piagent's Pi keeps settings, packages, login and sessions. An explicit
18
+ * PIAGENT_AGENT_DIR wins; a PI_CODING_AGENT_DIR that points anywhere but the
19
+ * personal home is honoured (tests, benchmarks, custom setups); otherwise
20
+ * ~/.piagent/agent.
21
+ */
22
+ export function piagentAgentDir(env = process.env, home = os.homedir()) {
23
+ const own = env.PIAGENT_AGENT_DIR?.trim();
24
+ if (own) return path.resolve(own);
25
+ const configured = env.PI_CODING_AGENT_DIR?.trim();
26
+ if (configured && path.resolve(configured) !== personalPiAgentDir(home)) return path.resolve(configured);
27
+ return path.resolve(home, ".piagent", "agent");
28
+ }
29
+
30
+ /** Agent Watch's files: the runtime record, the managed binding and status. */
31
+ export function agentWatchControlDir(home = os.homedir()) {
32
+ return personalPiAgentDir(home);
33
+ }
34
+
35
+ function realpath(file) {
36
+ try { return fs.realpathSync(file); } catch { return path.resolve(file); }
37
+ }
38
+
39
+ /**
40
+ * True when this process is the stock `pi` command running from the personal
41
+ * home: Piagent was registered there by an older release or a project's
42
+ * .pi/settings.json, and it stays out of the member's way. Hosts that embed Pi
43
+ * (studio, dashboard gateway, benchmarks) and `piagent` itself are never
44
+ * dormant; PIAGENT_ACTIVE=1 opts a personal `pi` back in.
45
+ */
46
+ export function dormantInPersonalPi(env = process.env, home = os.homedir(), argv = process.argv) {
47
+ if (env.PIAGENT_ACTIVE === "1") return false;
48
+ const configured = env.PI_CODING_AGENT_DIR?.trim();
49
+ if (configured && path.resolve(configured) !== personalPiAgentDir(home)) return false;
50
+ const entry = argv[1] ? realpath(argv[1]) : "";
51
+ return /[\\/]@earendil-works[\\/]pi-coding-agent[\\/]/.test(entry);
52
+ }
@@ -37,6 +37,7 @@ import {
37
37
  } from "./redaction-core.js";
38
38
  import { detectProfileName } from "./project-shape.js";
39
39
  import { evaluateUpdateCheck, isInstalledPlatform, readUpdateCache, startUpdateProbe } from "./update-check.js";
40
+ import { dormantInPersonalPi, personalPiAgentDir } from "./agent-home.js";
40
41
  import {
41
42
  REPOSITORY_SCOPES,
42
43
  collectServers,
@@ -366,7 +367,7 @@ const PLATFORM_ROOT = findPlatformRoot(path.dirname(fileURLToPath(import.meta.ur
366
367
  // package's and the user's (also those kept for other coding agents, such as
367
368
  // ~/.claude/skills). Reading them is allowed; writes stay in the project.
368
369
  const SKILL_READ_ROOTS = [path.join(PLATFORM_ROOT, "packages", "piagent-core", "skills"),
369
- path.join(process.env.PI_CODING_AGENT_DIR || path.join(os.homedir(), ".pi", "agent"), "skills"), ...userSkillFolders()]
370
+ path.join(process.env.PI_CODING_AGENT_DIR || personalPiAgentDir(), "skills"), path.join(personalPiAgentDir(), "skills"), ...userSkillFolders()]
370
371
  .flatMap((root) => { try { return [fs.realpathSync.native(root)]; } catch { return []; } });
371
372
  const UPDATE_CHECK_MODULE = fileURLToPath(new URL("./update-check.js", import.meta.url));
372
373
  // The installed version, read from the package this file ships in. A maintainer
@@ -3452,6 +3453,9 @@ function activeSessionTask(_cwd: string, _sessionId: string): TaskContract | und
3452
3453
  const noTask = (..._args: unknown[]): undefined => undefined;
3453
3454
 
3454
3455
  export default function piagentGuard(pi: ExtensionAPI) {
3456
+ // The member's own `pi` stays stock even when an older install or a project
3457
+ // still registers this package there; Piagent runs through `piagent`.
3458
+ if (dormantInPersonalPi()) return;
3455
3459
  const extensionDir = path.dirname(fileURLToPath(import.meta.url));
3456
3460
  const policy = loadPolicy(extensionDir);
3457
3461
  const bashResults = createBashResultLedger({ maxEntries: 300 });
@@ -44,7 +44,10 @@ export function configPathForScope(scope, options = {}) {
44
44
  case "global":
45
45
  return path.join(env.XDG_CONFIG_HOME || path.join(home, ".config"), "mcp", "mcp.json");
46
46
  case "pi-global":
47
- return path.join(env.PI_CODING_AGENT_DIR || path.join(home, ".pi", "agent"), "mcp.json");
47
+ // Piagent's own Pi home, not the member's personal pi (see extensions/agent-home.js).
48
+ if (env.PIAGENT_AGENT_DIR) return path.join(env.PIAGENT_AGENT_DIR, "mcp.json");
49
+ if (env.PI_CODING_AGENT_DIR && path.resolve(env.PI_CODING_AGENT_DIR) !== path.resolve(home, ".pi", "agent")) return path.join(env.PI_CODING_AGENT_DIR, "mcp.json");
50
+ return path.join(home, ".piagent", "agent", "mcp.json");
48
51
  case "project":
49
52
  return path.join(projectPath, ".mcp.json");
50
53
  case "pi-project":
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@piagent/core",
3
- "version": "1.45.0",
3
+ "version": "1.46.0",
4
4
  "type": "module",
5
5
  "private": true,
6
6
  "license": "MIT",
@@ -34,6 +34,7 @@ import { VSCodeHandoff } from "./vscode-handoff.ts";
34
34
  import { browserCommand } from "../../piagent-core/runtime/host/browser-opener.mjs";
35
35
  import { TerminalSessionAdapter } from "./terminal-session-adapter.ts";
36
36
  import { isGatewayRuntimeContext } from "../ownership/gateway-runtime-context.ts";
37
+ import { dormantInPersonalPi } from "../../piagent-core/extensions/agent-home.js";
37
38
 
38
39
  const PACKAGE_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
39
40
  const PLATFORM_ROOT = path.resolve(PACKAGE_ROOT, "../..");
@@ -183,6 +184,7 @@ function readMethod(provider: CoreInspectionProvider, queue: HeldMessageQueue, s
183
184
  }
184
185
 
185
186
  export default function piagentWebUiExtension(pi: ExtensionAPI): void {
187
+ if (dormantInPersonalPi()) return;
186
188
  const runtimeInstanceId = createWebUiExtensionRuntimeInstanceRef();
187
189
  let terminalAdapter: TerminalSessionAdapter | null = null;
188
190
  const managers = new Map<string, Manager>();
@@ -53,6 +53,7 @@ import { CompanyRelay, type CompanyConnector } from "./company-relay.ts";
53
53
  import { ProviderAuthBroker } from "./provider-auth-broker.ts";
54
54
  import { McpAuthBroker } from "./mcp-auth-broker.ts";
55
55
  import { AgentStatusFiles } from "./agent-status-files.ts";
56
+ import { agentWatchControlDir, piagentAgentDir } from "../../piagent-core/extensions/agent-home.js";
56
57
  import type { ScopedBrokerRouter, RuntimeFactory } from "./session-runtime-factory.ts";
57
58
  import { UiPreferencesStore } from "./ui-preferences-store.ts";
58
59
 
@@ -249,7 +250,9 @@ export async function startPiagentGateway(options: {
249
250
  // Agent Watch reads the member's ~/.pi/agent/status. The dashboard's
250
251
  // agentDir is that folder; the company Gateway's is its private session
251
252
  // folder under Agent Watch, so it writes to the member's folder instead.
252
- statusFiles = new AgentStatusFiles((options.managed ? path.join(os.homedir(), ".pi", "agent", "status", "company") : path.join(state.agentDir, "status", "dashboard")), async () => { const catalog = await readCatalog(); if (catalog.state !== "ready") throw new Error("catalog-not-ready"); return catalog; }, () => Boolean(options.managed)); statusFiles.start();
253
+ // Piagent's own home serves the member's dashboard; a test or benchmark home keeps its status beside it.
254
+ const watchRoot = options.managed || state.agentDir === piagentAgentDir() ? agentWatchControlDir() : state.agentDir;
255
+ statusFiles = new AgentStatusFiles(path.join(watchRoot, "status", options.managed ? "company" : "dashboard"), async () => { const catalog = await readCatalog(); if (catalog.state !== "ready") throw new Error("catalog-not-ready"); return catalog; }, () => Boolean(options.managed)); statusFiles.start();
253
256
  const projectFolder = async (projectRef: string) => projects.resolve(projectRef)
254
257
  ?? (await runtimes!.listSessions()).find((info) => typeof info.cwd === "string" && projectRefForCwd(key, info.cwd) === projectRef)?.cwd
255
258
  ?? (relay ? (await relay.folders()).find((folder) => projectRefForCwd(key, folder) === projectRef) : undefined);
@@ -5,6 +5,7 @@ import path from "node:path";
5
5
  import { createRequire } from "node:module";
6
6
  import { pathToFileURL } from "node:url";
7
7
  import { privateHostRoot } from "../../piagent-core/managed/pi-host.mjs";
8
+ import { agentWatchControlDir } from "../../piagent-core/extensions/agent-home.js";
8
9
 
9
10
  const require = createRequire(import.meta.url);
10
11
 
@@ -58,8 +59,7 @@ export function installedPiHostRoot(): string {
58
59
  const home = os.homedir(), hostPackage = path.join("lib", "node_modules", "@earendil-works", "pi-coding-agent");
59
60
  let recorded: string | null = null;
60
61
  try {
61
- const agentDir = process.env.PI_CODING_AGENT_DIR || path.join(home, ".pi", "agent");
62
- recorded = JSON.parse(fs.readFileSync(path.join(agentDir, "piagent-runtime.json"), "utf8")).pi_sdk_root ?? null;
62
+ recorded = JSON.parse(fs.readFileSync(path.join(agentWatchControlDir(home), "piagent-runtime.json"), "utf8")).pi_sdk_root ?? null;
63
63
  } catch { /* no record yet */ }
64
64
  // npm's default Windows prefixes (%APPDATA%\npm, or beside node.exe) have no lib folder.
65
65
  const appData = process.env.APPDATA ?? path.join(home, "AppData", "Roaming");
@@ -47,8 +47,17 @@ function shortControlSocket(agentDir: string): string {
47
47
  return path.join(socketRoot, `${digest}.sock`);
48
48
  }
49
49
 
50
+ // Piagent's own Pi home (~/.piagent/agent), never the member's personal pi.
51
+ // Same rule as piagent-core/extensions/agent-home.js, which this layer cannot import.
52
+ function defaultAgentDir(): string {
53
+ const own = process.env.PIAGENT_AGENT_DIR?.trim(), configured = process.env.PI_CODING_AGENT_DIR?.trim();
54
+ if (own) return own;
55
+ if (configured && path.resolve(configured) !== path.resolve(os.homedir(), ".pi", "agent")) return configured;
56
+ return path.join(os.homedir(), ".piagent", "agent");
57
+ }
58
+
50
59
  export function gatewayProfileState(agentDirInput?: string): GatewayProfileState {
51
- const agentDir = path.resolve(agentDirInput || process.env.PI_CODING_AGENT_DIR || path.join(os.homedir(), ".pi", "agent"));
60
+ const agentDir = path.resolve(agentDirInput || defaultAgentDir());
52
61
  fs.mkdirSync(agentDir, { recursive: true, mode: 0o700 });
53
62
  const agentStat = fs.lstatSync(agentDir);
54
63
  if (!agentStat.isDirectory() || agentStat.isSymbolicLink()
@@ -10,6 +10,7 @@ import { assertBenchmarkLedgerBinding, inspectBenchmarkLedger, validateBenchmark
10
10
  import { completedBenchmarkRecord } from "../../piagent-core/benchmark/benchmark-record-validation.js";
11
11
  import { benchmarkGitEnvironment } from "../../piagent-core/benchmark/benchmark-runtime.js";
12
12
  import { redactSensitiveText } from "../../piagent-core/extensions/redaction-core.js";
13
+ import { piagentAgentDir } from "../../piagent-core/extensions/agent-home.js";
13
14
 
14
15
  const MAX_RUNS = 20, MAX_DIRECTORIES = 100, MAX_DIRECTORY_ENTRIES = 5_000, MAX_JSON_BYTES = 32 * 1024 * 1024;
15
16
  type Identity = { projectRef: string; runtimeInstanceId: string; sessionRef: string; taskId: string | null; taskRunId: string | null;
@@ -210,7 +211,7 @@ function releaseReadiness(cwd: string, file: string, repo: ReturnType<typeof rep
210
211
  export function projectBenchmarkReleaseMonitor(input: { cwd: string; identity: Identity; generatedAt?: string; benchmarkRoot?: string; releaseReportPath?: string }) {
211
212
  const generatedAt = input.generatedAt ?? new Date().toISOString();
212
213
  try {
213
- const repo = repository(input.cwd), root = input.benchmarkRoot ?? path.join(process.env.PI_CODING_AGENT_DIR || path.join(os.homedir(), ".pi", "agent"), "benchmarks", "piagent");
214
+ const repo = repository(input.cwd), root = input.benchmarkRoot ?? path.join(piagentAgentDir(), "benchmarks", "piagent");
214
215
  const releasePath = input.releaseReportPath ?? path.join(input.cwd, "plans", "codex-first-product", "evidence", "p7-local-readiness", "report.json");
215
216
  const benchmark = benchmarkRuns(input.cwd, root, repo), release = releaseReadiness(input.cwd, releasePath, repo);
216
217
  const revision = `release-monitor.${createHash("sha256").update(JSON.stringify({ benchmark, release })).digest("hex")}`;
@@ -14,6 +14,7 @@ import { acquireBenchmarkRunLock } from "../packages/piagent-core/benchmark/benc
14
14
  import { recoverBenchmarkObservationCheckpoints } from "../packages/piagent-core/benchmark/benchmark-resume-recovery.js";
15
15
  import { benchmarkSurfaceLabel } from "../packages/piagent-core/benchmark/benchmark-core.js";
16
16
  import { codexModelName, codexThinkingEffort } from "../packages/piagent-core/benchmark/benchmark-codex.js";
17
+ import { piagentAgentDir } from "../packages/piagent-core/extensions/agent-home.js";
17
18
 
18
19
  export function fail(message, code = 2) {
19
20
  const error = new Error(message);
@@ -96,7 +97,7 @@ export function registeredBenchmarkCustodyRoot(runRoot) {
96
97
 
97
98
  export function defaultOutputRoot(bootstrapMetadata) {
98
99
  if (bootstrapMetadata?.defaultOutputRoot) return bootstrapMetadata.defaultOutputRoot;
99
- const agentRoot = process.env.PI_CODING_AGENT_DIR || path.join(os.homedir(), ".pi", "agent");
100
+ const agentRoot = piagentAgentDir();
100
101
  return path.join(agentRoot, "benchmarks", "piagent");
101
102
  }
102
103
 
@@ -59,8 +59,10 @@ if (installed !== to) finish("failed", { reason: "update-version-mismatch", ...(
59
59
  // the launcher needs the binding imported again in Agent Watch.
60
60
  let bindingChanged = false;
61
61
  try {
62
- const agentDir = process.env.PI_CODING_AGENT_DIR?.trim() || path.join(home, ".pi", "agent");
63
- const binding = JSON.parse(fs.readFileSync(path.join(agentDir, "agent-watch-managed.json"), "utf8"));
62
+ // Agent Watch keeps the binding in the personal Pi folder; a test or custom home may hold its own.
63
+ const homes = [process.env.PI_CODING_AGENT_DIR?.trim(), path.join(home, ".pi", "agent")].filter(Boolean);
64
+ const file = homes.map((dir) => path.join(dir, "agent-watch-managed.json")).find((candidate) => fs.existsSync(candidate));
65
+ const binding = JSON.parse(fs.readFileSync(file, "utf8"));
64
66
  if (typeof binding?.entrypoint === "string" && typeof binding?.entrypoint_sha256 === "string")
65
67
  bindingChanged = crypto.createHash("sha256").update(fs.readFileSync(binding.entrypoint)).digest("hex") !== binding.entrypoint_sha256;
66
68
  } catch { /* no company binding on this machine */ }
@@ -23,6 +23,9 @@ Options:
23
23
  --with-web-access | --no-web-access
24
24
  Install pi-web-access for the researcher subagent (default: install).
25
25
  --with-herdr Also configure Herdr integration.
26
+ --copy-login | --no-copy-login Copy (or not) your personal pi login into Piagent's home.
27
+ Default: ask in a terminal; without one, copy only when
28
+ moving an older install out of your personal pi.
26
29
  --model-scope <full|codex|claude>
27
30
  Model families to enable in Pi (default: full). A member who
28
31
  already has a default model and model list keeps them; an
@@ -36,7 +39,13 @@ Options:
36
39
  -h, --help Print this help and exit.
37
40
 
38
41
  Purpose:
39
- Install the piagent Pi package into the current user's global Pi settings.
42
+ Install Piagent into its own Pi home (~/.piagent/agent; PIAGENT_AGENT_DIR overrides).
43
+ Your personal `pi` (~/.pi/agent) stays stock: open Piagent with `piagent`.
44
+ An install from an older release that lives in ~/.pi/agent is moved out of it.
45
+
46
+ Run in a terminal, a first install asks which add-ons to install and whether
47
+ to use your pi login; flags answer those questions up front, and a run
48
+ without a terminal (CI, the dashboard updater) takes the defaults.
40
49
 
41
50
  Package source examples:
42
51
  # Recommended stable install: exact current release tag
@@ -61,7 +70,8 @@ Package source examples:
61
70
  Notes:
62
71
  - `currentRelease` in output is the version of the terminal helper executing this command.
63
72
  - Use exactly one package selector. The first CLI selector overrides environment defaults; a second selector fails closed.
64
- - OAuth is intentionally not automated. Run `pi` then `/login`.
73
+ - OAuth is intentionally not automated. Run `piagent` then `/login`, or let the
74
+ installer copy the login your personal pi already has.
65
75
  - Model scope is configured with Pi's native `enabledModels` so users choose via `/model`, Ctrl+L, `/scoped-models`, and Ctrl+P.
66
76
  - Herdr integration is optional and modifies user-level Herdr/Pi config.
67
77
  - MCP is installed by default with the core preset: Context7 docs, Chrome DevTools, GitHub.
@@ -76,6 +86,21 @@ USAGE
76
86
  }
77
87
 
78
88
  PLATFORM_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
89
+ # Piagent's own Pi home (packages/piagent-core/extensions/agent-home.js has the
90
+ # same rule). Every `pi` call below works there, never in the personal home.
91
+ PERSONAL_PI_DIR="${HOME}/.pi/agent"
92
+ if [[ -n "${PIAGENT_AGENT_DIR:-}" ]]; then
93
+ PIAGENT_HOME="$PIAGENT_AGENT_DIR"
94
+ elif [[ -n "${PI_CODING_AGENT_DIR:-}" && "${PI_CODING_AGENT_DIR%/}" != "$PERSONAL_PI_DIR" ]]; then
95
+ PIAGENT_HOME="$PI_CODING_AGENT_DIR"
96
+ else
97
+ PIAGENT_HOME="${HOME}/.piagent/agent"
98
+ fi
99
+ export PI_CODING_AGENT_DIR="$PIAGENT_HOME"
100
+ MCP_EXPLICIT=false
101
+ WEB_ACCESS_EXPLICIT=false
102
+ MODEL_SCOPE_EXPLICIT=false
103
+ COPY_LOGIN=""
79
104
  CURRENT_RELEASE_TAG=""
80
105
  EXPECTED_PI_VERSION=""
81
106
  EXPECTED_RELEASE_COMMIT="${PIAGENT_EXPECTED_RELEASE_COMMIT:-}"
@@ -486,10 +511,12 @@ while [[ $# -gt 0 ]]; do
486
511
  ;;
487
512
  --with-mcp)
488
513
  WITH_MCP=true
514
+ MCP_EXPLICIT=true
489
515
  shift
490
516
  ;;
491
517
  --no-mcp)
492
518
  WITH_MCP=false
519
+ MCP_EXPLICIT=true
493
520
  shift
494
521
  ;;
495
522
  --mcp-preset)
@@ -520,10 +547,12 @@ while [[ $# -gt 0 ]]; do
520
547
  ;;
521
548
  --with-web-access)
522
549
  WITH_WEB_ACCESS=true
550
+ WEB_ACCESS_EXPLICIT=true
523
551
  shift
524
552
  ;;
525
553
  --no-web-access)
526
554
  WITH_WEB_ACCESS=false
555
+ WEB_ACCESS_EXPLICIT=true
527
556
  shift
528
557
  ;;
529
558
  --with-herdr)
@@ -533,6 +562,7 @@ while [[ $# -gt 0 ]]; do
533
562
  --model-scope)
534
563
  require_value "$1" "${2:-}"
535
564
  MODEL_SCOPE_PRESET="$2"
565
+ MODEL_SCOPE_EXPLICIT=true
536
566
  shift 2
537
567
  ;;
538
568
  --default-model)
@@ -546,6 +576,15 @@ while [[ $# -gt 0 ]]; do
546
576
  ;;
547
577
  --no-model-scope)
548
578
  CONFIGURE_MODEL_SCOPE=false
579
+ MODEL_SCOPE_EXPLICIT=true
580
+ shift
581
+ ;;
582
+ --copy-login)
583
+ COPY_LOGIN=true
584
+ shift
585
+ ;;
586
+ --no-copy-login)
587
+ COPY_LOGIN=false
549
588
  shift
550
589
  ;;
551
590
  -h|--help)
@@ -569,6 +608,54 @@ fi
569
608
 
570
609
  require_node_version
571
610
 
611
+ # What personal Pi holds from an older Piagent release (packages, login).
612
+ LEGACY_REPORT="$(node "$PLATFORM_ROOT/scripts/personal-pi-restore.mjs" --json 2>/dev/null || echo '{}')"
613
+ legacy_field() {
614
+ node -e 'const r = JSON.parse(process.argv[1] || "{}"); const v = process.argv[2].split(".").reduce((o, k) => o?.[k], r); process.stdout.write(Array.isArray(v) ? v.join("\n") : String(v ?? ""));' "$LEGACY_REPORT" "$1"
615
+ }
616
+ LEGACY_INSTALL="$(legacy_field legacy)"
617
+ LEGACY_SUBAGENTS=false
618
+ if legacy_field remove | grep -q '^npm:pi-subagents@'; then LEGACY_SUBAGENTS=true; fi
619
+ FIRST_INSTALL=true
620
+ if [[ -f "$PIAGENT_HOME/settings.json" ]] && grep -qE '@piagent/platform|Vt-mmm/piagent' "$PIAGENT_HOME/settings.json" 2>/dev/null; then
621
+ FIRST_INSTALL=false
622
+ fi
623
+
624
+ # Ask, rather than decide for the member, what goes into Piagent's home: only on
625
+ # a first install (an update keeps what is there), only in a terminal, and never
626
+ # for a question a flag already answered.
627
+ ask_yes_no() {
628
+ local question="$1" default="$2" answer hint="[Y/n]"
629
+ [[ "$default" == false ]] && hint="[y/N]"
630
+ read -r -p "$question $hint " answer </dev/tty || answer=""
631
+ case "$answer" in
632
+ [Yy]*) echo true ;;
633
+ [Nn]*) echo false ;;
634
+ *) echo "$default" ;;
635
+ esac
636
+ }
637
+ if [[ "$FIRST_INSTALL" == true && "$DRY_RUN" == false && -t 0 && -t 1 && -z "${PIAGENT_INSTALL_DEFAULTS:-}" ]]; then
638
+ echo "Piagent installs into its own Pi home: $PIAGENT_HOME"
639
+ echo "Your personal pi ($PERSONAL_PI_DIR) is not changed by these choices."
640
+ [[ "$MCP_EXPLICIT" == true ]] || WITH_MCP="$(ask_yes_no "Install the MCP adapter (pi-mcp-adapter, servers: $MCP_PRESET)?" "$WITH_MCP")"
641
+ if [[ "$SUBAGENTS_EXPLICIT" == false ]]; then
642
+ WITH_SUBAGENTS="$(ask_yes_no "Install pi-subagents (optional parallel helpers; company sessions use their own)?" "$LEGACY_SUBAGENTS")"
643
+ SUBAGENTS_EXPLICIT=true
644
+ fi
645
+ [[ "$WEB_ACCESS_EXPLICIT" == true ]] || WITH_WEB_ACCESS="$(ask_yes_no "Install pi-web-access (web search and fetch for the researcher subagent)?" "$WITH_WEB_ACCESS")"
646
+ [[ "$MODEL_SCOPE_EXPLICIT" == true ]] || CONFIGURE_MODEL_SCOPE="$(ask_yes_no "Set Piagent's model list and default model ($DEFAULT_MODEL)?" "$CONFIGURE_MODEL_SCOPE")"
647
+ if [[ -z "$COPY_LOGIN" && "$(legacy_field loginAvailable)" == true ]]; then
648
+ COPY_LOGIN="$(ask_yes_no "Use the login your personal pi already has (copies auth.json once)?" true)"
649
+ fi
650
+ echo
651
+ fi
652
+ if [[ -z "$COPY_LOGIN" ]]; then
653
+ COPY_LOGIN="$LEGACY_INSTALL"
654
+ fi
655
+ if [[ "$LEGACY_SUBAGENTS" == true && "$SUBAGENTS_EXPLICIT" == false ]]; then
656
+ WITH_SUBAGENTS=true
657
+ fi
658
+
572
659
  # The preset is only read at the very last step, after the platform package and
573
660
  # the MCP adapter are already installed. A name with a typo therefore failed
574
661
  # after two installs had landed, leaving a half-configured machine, and the
@@ -736,10 +823,25 @@ if [[ -n "$RESOLVED_PACKAGE_COMMIT" ]]; then
736
823
  fi
737
824
  echo " source: $PACKAGE_SOURCE"
738
825
  sync_local_helper
826
+ echo " home: $PIAGENT_HOME (your personal pi stays in $PERSONAL_PI_DIR)"
827
+ run_cmd mkdir -p "$PIAGENT_HOME"
739
828
  echo "Removing previous Pi Agent Platform package registrations:"
740
829
  remove_existing_piagent_platform_sources
741
830
  run_cmd pi install "$PACKAGE_SOURCE"
742
831
 
832
+ # An older release installed Piagent into personal Pi. Move it out: carry the
833
+ # member's model choices (and the login, when agreed) to Piagent's home, then
834
+ # leave personal Pi stock. Backups of every changed file stay beside it.
835
+ if [[ "$LEGACY_INSTALL" == true ]]; then
836
+ echo "Moving the older Piagent install out of your personal pi:"
837
+ restore_args=("$PLATFORM_ROOT/scripts/personal-pi-restore.mjs" --apply)
838
+ [[ "$COPY_LOGIN" == true ]] && restore_args+=(--copy-login)
839
+ run_cmd node "${restore_args[@]}"
840
+ elif [[ "$COPY_LOGIN" == true && "$(legacy_field loginAvailable)" == true ]]; then
841
+ echo "Copying your pi login to Piagent's home:"
842
+ run_cmd node -e 'const fs = require("node:fs"); fs.copyFileSync(process.argv[1], process.argv[2], fs.constants.COPYFILE_EXCL); fs.chmodSync(process.argv[2], 0o600);' "$PERSONAL_PI_DIR/auth.json" "$PIAGENT_HOME/auth.json"
843
+ fi
844
+
743
845
  if [[ "$WITH_MCP" == true ]]; then
744
846
  echo "Installing Pi MCP adapter:"
745
847
  if [[ "$DRY_RUN" == false ]]; then
@@ -747,6 +849,9 @@ if [[ "$WITH_MCP" == true ]]; then
747
849
  fi
748
850
  remove_existing_npm_package_sources "pi-mcp-adapter"
749
851
  run_cmd pi install "$PI_MCP_ADAPTER_SOURCE"
852
+ # Pi's built-in `mcp` extension registers the same /mcp command as the
853
+ # adapter; Piagent's home turns the built-in off so only one loads.
854
+ run_cmd node -e 'const fs = require("node:fs"); const f = process.argv[1]; const s = JSON.parse(fs.readFileSync(f, "utf8")); const e = Array.isArray(s.extensions) ? s.extensions : []; if (!e.includes("-builtin:mcp")) { s.extensions = [...e, "-builtin:mcp"]; fs.writeFileSync(f, JSON.stringify(s, null, 2) + "\n"); }' "$PIAGENT_HOME/settings.json"
750
855
  if [[ "$DRY_RUN" == false ]] && command -v pi-mcp-adapter >/dev/null 2>&1; then
751
856
  pi-mcp-adapter init || true
752
857
  fi
@@ -802,10 +907,13 @@ if [[ "$DRY_RUN" == true ]]; then
802
907
  print_cmd node "$PLATFORM_ROOT/scripts/personal-pi-config.mjs" repair
803
908
  else
804
909
  node "$PLATFORM_ROOT/scripts/personal-pi-config.mjs" repair || echo "WARN: could not check the personal Pi configuration; run: node \"$PLATFORM_ROOT/scripts/personal-pi-config.mjs\" check" >&2
910
+ node "$PLATFORM_ROOT/scripts/personal-pi-config.mjs" repair --agent-dir "$PIAGENT_HOME" >/dev/null || true
805
911
  fi
806
912
 
807
913
  echo
808
- echo "Installed packages:"
914
+ echo "Open Piagent with: piagent (your own pi in $PERSONAL_PI_DIR stays stock)"
915
+ echo
916
+ echo "Installed packages in $PIAGENT_HOME:"
809
917
  if [[ "$DRY_RUN" == true ]]; then
810
918
  print_cmd pi list
811
919
  else
@@ -814,8 +922,8 @@ fi
814
922
 
815
923
  echo
816
924
  echo "Next:"
817
- echo " pi"
818
- echo " /login"
925
+ echo " piagent # Piagent in this terminal; plain \`pi\` stays your own"
926
+ echo " /login # unless the login of your pi was copied"
819
927
  echo " /model # or Ctrl+L: select provider/model from Pi selector"
820
928
  echo " /scoped-models # optional: edit Ctrl+P model cycle scope"
821
929
  if [[ "$WITH_MCP" == true ]]; then
@@ -10,6 +10,7 @@ import fs from "node:fs";
10
10
  import path from "node:path";
11
11
  import { execFileSync } from "node:child_process";
12
12
  import { pathToFileURL } from "node:url";
13
+ import { piagentAgentDir } from "../packages/piagent-core/extensions/agent-home.js";
13
14
 
14
15
  // The Pi install `pi` runs from.
15
16
  export function piPackageRoot(environment = process.env) {
@@ -55,7 +56,7 @@ export function pruneSettings(settingsPath, stale) {
55
56
 
56
57
  if (process.argv[1] && path.resolve(process.argv[1]) === path.resolve(new URL(import.meta.url).pathname)) {
57
58
  const args = process.argv.slice(2), at = args.indexOf("--settings");
58
- const agentDir = process.env.PI_CODING_AGENT_DIR || path.join(process.env.HOME ?? "", ".pi", "agent");
59
+ const agentDir = piagentAgentDir(process.env, process.env.HOME ?? "");
59
60
  const settingsPath = at >= 0 && args[at + 1] ? path.resolve(args[at + 1]) : path.join(agentDir, "settings.json");
60
61
  const json = args.includes("--json"), fix = args.includes("--fix");
61
62
  const out = (value, text) => process.stdout.write(json ? `${JSON.stringify(value)}\n` : `${text}\n`);
@@ -120,7 +120,8 @@ const invoked = process.argv[1] && real(process.argv[1]) === real(fileURLToPath(
120
120
  if (invoked) {
121
121
  const args = process.argv.slice(2), command = args[0];
122
122
  const at = args.indexOf("--agent-dir");
123
- const agentDir = at >= 0 ? path.resolve(args[at + 1]) : (process.env.PI_CODING_AGENT_DIR || path.join(os.homedir(), ".pi", "agent"));
123
+ // The member's personal pi, whatever home Piagent itself runs from.
124
+ const agentDir = at >= 0 ? path.resolve(args[at + 1]) : path.join(os.homedir(), ".pi", "agent");
124
125
  if (!["check", "repair"].includes(command)) { console.error("Usage: personal-pi-config.mjs check|repair [--agent-dir <dir>] [--json]"); process.exit(2); }
125
126
  const result = command === "repair" ? repair(agentDir) : inspect(agentDir);
126
127
  if (args.includes("--json")) console.log(JSON.stringify(result, null, 2));