@piagent/platform 1.44.4 → 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.
- package/CHANGELOG.md +39 -0
- package/README.md +5 -3
- package/README.vi.md +5 -3
- package/docs/command-reference-vietnamese.md +2 -2
- package/docs/distribution-standard.md +6 -6
- package/docs/languages.json +2 -2
- package/docs/operator-manual-vietnamese.md +7 -7
- package/docs/publishing-for-teams.md +2 -0
- package/docs/release-install-policy.md +2 -2
- package/docs/team-onboarding.md +2 -2
- package/docs-site/architecture.html +1 -1
- package/docs-site/assets/search-en.json +1 -1
- package/docs-site/assets/search-vi.json +1 -1
- package/docs-site/benchmark.html +1 -1
- package/docs-site/commands.html +1 -1
- package/docs-site/context-engine.html +1 -1
- package/docs-site/ecosystem.html +1 -1
- package/docs-site/en/architecture.html +1 -1
- package/docs-site/en/benchmark.html +1 -1
- package/docs-site/en/commands.html +1 -1
- package/docs-site/en/context-engine.html +1 -1
- package/docs-site/en/ecosystem.html +1 -1
- package/docs-site/en/index.html +15 -15
- package/docs-site/en/mcp-approval.html +1 -1
- package/docs-site/en/mcp-auth.html +1 -1
- package/docs-site/en/mcp-servers.html +1 -1
- package/docs-site/en/mcp.html +1 -1
- package/docs-site/en/permissions.html +1 -1
- package/docs-site/en/profiles.html +1 -1
- package/docs-site/en/quickstart.html +1 -1
- package/docs-site/en/runtime.html +1 -1
- package/docs-site/en/scope.html +1 -1
- package/docs-site/en/security.html +1 -1
- package/docs-site/en/team.html +1 -1
- package/docs-site/en/update.html +3 -3
- package/docs-site/en/whats-new.html +58 -35
- package/docs-site/en/windows.html +1 -1
- package/docs-site/en/workflows.html +1 -1
- package/docs-site/index.html +15 -15
- package/docs-site/mcp-approval.html +1 -1
- package/docs-site/mcp-auth.html +1 -1
- package/docs-site/mcp-servers.html +1 -1
- package/docs-site/mcp.html +1 -1
- package/docs-site/permissions.html +1 -1
- package/docs-site/profiles.html +1 -1
- package/docs-site/quickstart.html +1 -1
- package/docs-site/releases.json +75 -0
- package/docs-site/runtime.html +1 -1
- package/docs-site/scope.html +1 -1
- package/docs-site/security.html +1 -1
- package/docs-site/team.html +1 -1
- package/docs-site/update.html +3 -3
- package/docs-site/whats-new.html +58 -35
- package/docs-site/windows.html +1 -1
- package/docs-site/workflows.html +1 -1
- package/evals/private-holdout-v1/public-exposure.v2.json +2 -2
- package/package.json +1 -1
- package/packages/piagent-core/benchmark/benchmark-bootstrap.js +3 -1
- package/packages/piagent-core/extensions/agent-home.d.ts +4 -0
- package/packages/piagent-core/extensions/agent-home.js +52 -0
- package/packages/piagent-core/extensions/piagent-guard.ts +5 -1
- package/packages/piagent-core/extensions/working-tree-snapshot-worker.mjs +9 -0
- package/packages/piagent-core/extensions/workspace-evidence-roots.js +28 -2
- package/packages/piagent-core/mcp/mcp-config-layers.js +4 -1
- package/packages/piagent-core/package.json +1 -1
- package/packages/piagent-core/runtime/hooks/input-hook.ts +17 -3
- package/packages/piagent-core/runtime/input/chat-images.ts +66 -10
- package/packages/piagent-core/runtime/input/file-arguments.ts +102 -0
- package/packages/piagent-core/runtime/inspection/webui-snapshot.ts +2 -6
- package/packages/piagent-core/runtime/inspection/working-tree-off-thread.ts +52 -0
- package/packages/piagent-core/runtime/solver/runtime-features.ts +15 -3
- package/packages/piagent-webui/extension/piagent-webui.ts +2 -0
- package/packages/piagent-webui/gateway/gateway-service.ts +4 -1
- package/packages/piagent-webui/gateway/pi-host.ts +2 -2
- package/packages/piagent-webui/ownership/profile-state.ts +10 -1
- package/packages/piagent-webui/server/benchmark-release-monitor.ts +2 -1
- package/scripts/benchmark-runner-support.mjs +2 -1
- package/scripts/dashboard-update-job.mjs +4 -2
- package/scripts/install-global.sh +113 -5
- package/scripts/model-scope-prune.mjs +2 -1
- package/scripts/personal-pi-config.mjs +2 -1
- package/scripts/personal-pi-restore.mjs +184 -0
- package/scripts/pi-usage-history.mjs +2 -1
- package/scripts/piagent-cli.mjs +49 -9
- package/scripts/piagent-dashboard.mjs +5 -3
- package/scripts/setup.sh +2 -6
- package/scripts/team-doctor.sh +11 -5
- package/scripts/uninstall-global.sh +17 -2
- package/scripts/update-global.mjs +5 -2
|
@@ -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
|
|
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 `
|
|
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 "
|
|
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
|
|
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
|
-
|
|
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));
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Takes Piagent out of the member's personal Pi (~/.pi/agent) and leaves it as
|
|
3
|
+
// stock Pi. Releases before 1.46 installed Piagent, its add-ons, a model scope
|
|
4
|
+
// and a subagent baseline there, so every plain `pi` loaded them. Piagent now
|
|
5
|
+
// runs from its own home (~/.piagent/agent, opened with `piagent`).
|
|
6
|
+
//
|
|
7
|
+
// personal-pi-restore.mjs [--apply] [--copy-login] [--json]
|
|
8
|
+
//
|
|
9
|
+
// Without --apply nothing changes: it reports what it would do. With --apply:
|
|
10
|
+
// - carries the member's model choices, custom models and trust decisions to
|
|
11
|
+
// Piagent's home when that home has none yet (the login only with
|
|
12
|
+
// --copy-login: credentials are copied when the member says so);
|
|
13
|
+
// - removes the Piagent package and the exact-pinned add-ons Piagent
|
|
14
|
+
// installed (pi-mcp-adapter, pi-subagents, pi-web-access) from personal Pi;
|
|
15
|
+
// - removes the subagent baseline and the model list Piagent wrote, when the
|
|
16
|
+
// list still holds only models Piagent offered.
|
|
17
|
+
// Every changed file is backed up first. Sessions, the login and anything the
|
|
18
|
+
// member added stay in personal Pi.
|
|
19
|
+
import { spawnSync } from "node:child_process";
|
|
20
|
+
import fs from "node:fs";
|
|
21
|
+
import path from "node:path";
|
|
22
|
+
import { fileURLToPath } from "node:url";
|
|
23
|
+
import { personalPiAgentDir, piagentAgentDir } from "../packages/piagent-core/extensions/agent-home.js";
|
|
24
|
+
|
|
25
|
+
const ADDONS = ["pi-mcp-adapter", "pi-subagents", "pi-web-access"];
|
|
26
|
+
const MODEL_KEYS = ["defaultProvider", "defaultModel", "defaultThinkingLevel", "enabledModels"];
|
|
27
|
+
const CARRIED_FILES = ["models.json", "trust.json", "piagent-model-scope.json"];
|
|
28
|
+
const isPiagentSource = (source) => /(@piagent\/platform|Vt-mmm\/piagent|pi-company-platform)/.test(source);
|
|
29
|
+
// Piagent always installed add-ons at an exact version; an unpinned entry is the member's own.
|
|
30
|
+
const isPinnedAddon = (source) => ADDONS.some((name) => source.startsWith(`npm:${name}@`));
|
|
31
|
+
|
|
32
|
+
const readJson = (file) => { try { return JSON.parse(fs.readFileSync(file, "utf8")); } catch { return null; } };
|
|
33
|
+
const sourceOf = (entry) => (typeof entry === "string" ? entry : typeof entry?.source === "string" ? entry.source : "");
|
|
34
|
+
|
|
35
|
+
function isLocalPiagent(agentDir, source) {
|
|
36
|
+
if (/^(npm|git|https?):/.test(source)) return false;
|
|
37
|
+
return readJson(path.join(path.resolve(agentDir, source), "package.json"))?.name === "@piagent/platform";
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function plan({ personalDir = personalPiAgentDir(), piagentDir = piagentAgentDir(), copyLogin = false } = {}) {
|
|
41
|
+
const settings = readJson(path.join(personalDir, "settings.json")) ?? {};
|
|
42
|
+
const packages = (Array.isArray(settings.packages) ? settings.packages : []).map(sourceOf).filter(Boolean);
|
|
43
|
+
const remove = packages.filter((source) => isPiagentSource(source) || isLocalPiagent(personalDir, source) || isPinnedAddon(source));
|
|
44
|
+
const kept = packages.filter((source) => !remove.includes(source) && ADDONS.some((name) => source.includes(name)));
|
|
45
|
+
const offered = readJson(path.join(personalDir, "piagent-model-scope.json"))?.offered;
|
|
46
|
+
const models = Array.isArray(settings.enabledModels) ? settings.enabledModels : [];
|
|
47
|
+
const piagentModelList = Array.isArray(offered) && models.length > 0 && models.every((model) => offered.includes(model));
|
|
48
|
+
const subagentConfig = path.join(personalDir, "extensions", "subagent", "config.json");
|
|
49
|
+
const target = readJson(path.join(piagentDir, "settings.json")) ?? {};
|
|
50
|
+
const carry = {
|
|
51
|
+
settings: MODEL_KEYS.filter((key) => settings[key] !== undefined && target[key] === undefined),
|
|
52
|
+
files: CARRIED_FILES.filter((name) => fs.existsSync(path.join(personalDir, name)) && !fs.existsSync(path.join(piagentDir, name))),
|
|
53
|
+
login: copyLogin && fs.existsSync(path.join(personalDir, "auth.json")) && !fs.existsSync(path.join(piagentDir, "auth.json"))
|
|
54
|
+
};
|
|
55
|
+
const cleanup = {
|
|
56
|
+
subagentsSetting: settings.subagents !== undefined && remove.some((source) => source.startsWith("npm:pi-subagents@")),
|
|
57
|
+
subagentConfig: fs.existsSync(subagentConfig) && remove.some((source) => source.startsWith("npm:pi-subagents@")),
|
|
58
|
+
enabledModels: piagentModelList,
|
|
59
|
+
modelScopeRecord: fs.existsSync(path.join(personalDir, "piagent-model-scope.json"))
|
|
60
|
+
};
|
|
61
|
+
const loginAvailable = fs.existsSync(path.join(personalDir, "auth.json")) && !fs.existsSync(path.join(piagentDir, "auth.json"));
|
|
62
|
+
const legacy = remove.some((source) => isPiagentSource(source) || isLocalPiagent(personalDir, source));
|
|
63
|
+
return { personalDir, piagentDir, legacy, remove, kept, carry, cleanup, loginAvailable };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function backup(file, stamp) {
|
|
67
|
+
if (!fs.existsSync(file)) return null;
|
|
68
|
+
const copy = `${file}.before-piagent-restore-${stamp}`;
|
|
69
|
+
fs.copyFileSync(file, copy);
|
|
70
|
+
fs.chmodSync(copy, 0o600);
|
|
71
|
+
return copy;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function writeJson(file, value) {
|
|
75
|
+
fs.mkdirSync(path.dirname(file), { recursive: true, mode: 0o700 });
|
|
76
|
+
const tmp = `${file}.${process.pid}.tmp`;
|
|
77
|
+
fs.writeFileSync(tmp, `${JSON.stringify(value, null, 2)}\n`, { mode: 0o600 });
|
|
78
|
+
fs.renameSync(tmp, file);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function copyPrivate(from, to) {
|
|
82
|
+
fs.mkdirSync(path.dirname(to), { recursive: true, mode: 0o700 });
|
|
83
|
+
fs.copyFileSync(from, to, fs.constants.COPYFILE_EXCL);
|
|
84
|
+
fs.chmodSync(to, 0o600);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// `pi remove` also deletes the package's files; when Pi is missing or refuses,
|
|
88
|
+
// the entry still leaves settings.json so personal Pi stops loading it.
|
|
89
|
+
function removePackage(personalDir, source) {
|
|
90
|
+
const env = { ...process.env, PI_CODING_AGENT_DIR: personalDir };
|
|
91
|
+
delete env.PIAGENT_ACTIVE;
|
|
92
|
+
delete env.PIAGENT_AGENT_DIR;
|
|
93
|
+
const result = spawnSync("pi", ["remove", source], { env, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], shell: process.platform === "win32" });
|
|
94
|
+
if (result.status === 0) return "pi remove";
|
|
95
|
+
const file = path.join(personalDir, "settings.json"), settings = readJson(file);
|
|
96
|
+
if (settings && Array.isArray(settings.packages)) {
|
|
97
|
+
settings.packages = settings.packages.filter((entry) => sourceOf(entry) !== source);
|
|
98
|
+
writeJson(file, settings);
|
|
99
|
+
}
|
|
100
|
+
return "settings.json";
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function apply(options = {}, now = new Date()) {
|
|
104
|
+
const report = plan(options);
|
|
105
|
+
const { personalDir, piagentDir, carry, cleanup } = report;
|
|
106
|
+
const stamp = now.toISOString().replace(/[:.]/g, "-");
|
|
107
|
+
const backups = [], done = [];
|
|
108
|
+
const personalSettingsFile = path.join(personalDir, "settings.json");
|
|
109
|
+
const anything = report.remove.length || Object.values(cleanup).some(Boolean);
|
|
110
|
+
if (anything) backups.push(backup(personalSettingsFile, stamp));
|
|
111
|
+
|
|
112
|
+
// Carry first, so nothing the member chose is lost if a removal fails.
|
|
113
|
+
if (carry.settings.length) {
|
|
114
|
+
const from = readJson(personalSettingsFile) ?? {}, targetFile = path.join(piagentDir, "settings.json");
|
|
115
|
+
const target = readJson(targetFile) ?? {};
|
|
116
|
+
for (const key of carry.settings) target[key] = from[key];
|
|
117
|
+
writeJson(targetFile, target);
|
|
118
|
+
done.push(`carried ${carry.settings.join(", ")} to ${targetFile}`);
|
|
119
|
+
}
|
|
120
|
+
for (const name of carry.files) {
|
|
121
|
+
copyPrivate(path.join(personalDir, name), path.join(piagentDir, name));
|
|
122
|
+
done.push(`carried ${name} to ${piagentDir}`);
|
|
123
|
+
}
|
|
124
|
+
if (carry.login) {
|
|
125
|
+
copyPrivate(path.join(personalDir, "auth.json"), path.join(piagentDir, "auth.json"));
|
|
126
|
+
done.push(`copied the pi login to ${piagentDir} (personal pi keeps its own)`);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
for (const source of report.remove) done.push(`removed ${source} from personal pi (${removePackage(personalDir, source)})`);
|
|
130
|
+
|
|
131
|
+
if (cleanup.subagentsSetting || cleanup.enabledModels) {
|
|
132
|
+
const settings = readJson(personalSettingsFile) ?? {};
|
|
133
|
+
if (cleanup.subagentsSetting) { delete settings.subagents; done.push("removed the subagents setting Piagent wrote"); }
|
|
134
|
+
if (cleanup.enabledModels) { delete settings.enabledModels; done.push("removed the model list Piagent wrote"); }
|
|
135
|
+
writeJson(personalSettingsFile, settings);
|
|
136
|
+
}
|
|
137
|
+
if (cleanup.subagentConfig) {
|
|
138
|
+
const file = path.join(personalDir, "extensions", "subagent", "config.json");
|
|
139
|
+
fs.renameSync(file, `${file}.before-piagent-restore-${stamp}`);
|
|
140
|
+
backups.push(`${file}.before-piagent-restore-${stamp}`);
|
|
141
|
+
done.push("set aside the subagent baseline Piagent wrote");
|
|
142
|
+
}
|
|
143
|
+
if (cleanup.modelScopeRecord) {
|
|
144
|
+
fs.rmSync(path.join(personalDir, "piagent-model-scope.json"), { force: true });
|
|
145
|
+
done.push("removed Piagent's model-scope record from personal pi");
|
|
146
|
+
}
|
|
147
|
+
return { ...report, done, backups: backups.filter(Boolean) };
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function describe(report, applied) {
|
|
151
|
+
const lines = [`Personal pi: ${report.personalDir}`, `Piagent home: ${report.piagentDir}`];
|
|
152
|
+
if (applied) {
|
|
153
|
+
lines.push(...(report.done.length ? report.done.map((line) => ` ${line}`) : [" nothing to change: personal pi has no Piagent entries"]));
|
|
154
|
+
for (const file of report.backups) lines.push(` backup: ${file}`);
|
|
155
|
+
} else {
|
|
156
|
+
if (!report.remove.length && !Object.values(report.cleanup).some(Boolean)) lines.push(" nothing to change: personal pi has no Piagent entries");
|
|
157
|
+
for (const source of report.remove) lines.push(` would remove ${source}`);
|
|
158
|
+
if (report.cleanup.subagentsSetting) lines.push(" would remove the subagents setting Piagent wrote");
|
|
159
|
+
if (report.cleanup.subagentConfig) lines.push(" would set aside extensions/subagent/config.json");
|
|
160
|
+
if (report.cleanup.enabledModels) lines.push(" would remove the model list Piagent wrote");
|
|
161
|
+
if (report.carry.settings.length || report.carry.files.length) lines.push(` would carry ${[...report.carry.settings, ...report.carry.files].join(", ")} to Piagent's home`);
|
|
162
|
+
if (report.carry.login) lines.push(" would copy the pi login to Piagent's home");
|
|
163
|
+
else if (report.loginAvailable) lines.push(" login stays personal; add --copy-login to use it in Piagent too (else run `piagent`, then /login)");
|
|
164
|
+
}
|
|
165
|
+
for (const source of report.kept) lines.push(` kept ${source} (not a version Piagent pinned; remove it with pi remove if you do not want it)`);
|
|
166
|
+
if (!applied) lines.push("Dry run. Re-run with --apply to make these changes.");
|
|
167
|
+
return lines.join("\n");
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const real = (file) => { try { return fs.realpathSync(file); } catch { return null; } };
|
|
171
|
+
const invoked = process.argv[1] && real(process.argv[1]) === real(fileURLToPath(import.meta.url));
|
|
172
|
+
if (invoked) {
|
|
173
|
+
const args = process.argv.slice(2);
|
|
174
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
175
|
+
console.log("Usage: piagent restore-pi [--apply] [--copy-login] [--json]\n\nTakes an older Piagent install out of your personal pi (~/.pi/agent) and leaves it stock.\nWithout --apply it only reports. --copy-login also copies your pi login into Piagent's home.");
|
|
176
|
+
process.exit(0);
|
|
177
|
+
}
|
|
178
|
+
const unknown = args.filter((value) => !["--apply", "--copy-login", "--json"].includes(value));
|
|
179
|
+
if (unknown.length) { console.error(`Unknown argument: ${unknown[0]}`); process.exit(2); }
|
|
180
|
+
const options = { copyLogin: args.includes("--copy-login") };
|
|
181
|
+
const applied = args.includes("--apply");
|
|
182
|
+
const report = applied ? apply(options) : plan(options);
|
|
183
|
+
console.log(args.includes("--json") ? JSON.stringify(report, null, 2) : describe(report, applied));
|
|
184
|
+
}
|
|
@@ -3,6 +3,7 @@ import fs from "node:fs";
|
|
|
3
3
|
import os from "node:os";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { pathToFileURL } from "node:url";
|
|
6
|
+
import { piagentAgentDir } from "../packages/piagent-core/extensions/agent-home.js";
|
|
6
7
|
|
|
7
8
|
const USAGE = `Usage:
|
|
8
9
|
scripts/pi-usage-history.mjs [project-path] [options]
|
|
@@ -63,7 +64,7 @@ function parseArgs(argv) {
|
|
|
63
64
|
includeSubagents: true,
|
|
64
65
|
limit: 20,
|
|
65
66
|
sessionsDir: process.env.PI_CODING_AGENT_SESSION_DIR
|
|
66
|
-
|| path.join(
|
|
67
|
+
|| path.join(piagentAgentDir(), "sessions"),
|
|
67
68
|
projectPath: undefined,
|
|
68
69
|
since: undefined,
|
|
69
70
|
until: undefined
|
package/scripts/piagent-cli.mjs
CHANGED
|
@@ -28,7 +28,8 @@ const scriptByCommand = {
|
|
|
28
28
|
"piagent-webui": "scripts/piagent-webui-launcher.mjs",
|
|
29
29
|
"piagent-dashboard": "scripts/piagent-dashboard.mjs",
|
|
30
30
|
"piagent-pi-host": "scripts/pi-host.mjs",
|
|
31
|
-
"piagent-skills": "scripts/skill-sources.mjs"
|
|
31
|
+
"piagent-skills": "scripts/skill-sources.mjs",
|
|
32
|
+
"piagent-restore-pi": "scripts/personal-pi-restore.mjs"
|
|
32
33
|
};
|
|
33
34
|
|
|
34
35
|
const packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
@@ -36,9 +37,28 @@ const invokedAs = path.basename(process.argv[1] ?? "");
|
|
|
36
37
|
let forwardedArgs = process.argv.slice(2);
|
|
37
38
|
let script = scriptByCommand[invokedAs];
|
|
38
39
|
|
|
40
|
+
// Piagent's Pi lives in its own home so the member's `pi` (~/.pi/agent) stays
|
|
41
|
+
// stock; Agent Watch's files stay in ~/.pi/agent. Same rule as
|
|
42
|
+
// packages/piagent-core/extensions/agent-home.js, inlined because this
|
|
43
|
+
// launcher runs before anything else of the package resolves.
|
|
44
|
+
const personalPiDir = path.resolve(os.homedir(), ".pi", "agent");
|
|
45
|
+
function piagentHome() {
|
|
46
|
+
const own = process.env.PIAGENT_AGENT_DIR?.trim();
|
|
47
|
+
if (own) return path.resolve(own);
|
|
48
|
+
const configured = process.env.PI_CODING_AGENT_DIR?.trim();
|
|
49
|
+
if (configured && path.resolve(configured) !== personalPiDir) return path.resolve(configured);
|
|
50
|
+
return path.resolve(os.homedir(), ".piagent", "agent");
|
|
51
|
+
}
|
|
52
|
+
let terminal = false;
|
|
53
|
+
|
|
39
54
|
if (invokedAs === "piagent") {
|
|
40
55
|
const subcommand = forwardedArgs[0];
|
|
41
|
-
if (subcommand === "
|
|
56
|
+
if (subcommand === undefined || (subcommand.startsWith("-") && !["--help", "-h"].includes(subcommand))) {
|
|
57
|
+
// `piagent` on its own (or with Pi's flags, `piagent -c`) opens Piagent here.
|
|
58
|
+
script = "scripts/pi-host.mjs";
|
|
59
|
+
forwardedArgs = ["run", "--", ...forwardedArgs];
|
|
60
|
+
terminal = true;
|
|
61
|
+
} else if (subcommand === "studio") {
|
|
42
62
|
script = "scripts/piagent-studio.mjs";
|
|
43
63
|
forwardedArgs = forwardedArgs.slice(1);
|
|
44
64
|
} else if (subcommand === "dashboard") {
|
|
@@ -48,6 +68,7 @@ if (invokedAs === "piagent") {
|
|
|
48
68
|
// Pi itself, on the release Piagent is qualified on, whatever `pi` the member keeps.
|
|
49
69
|
script = "scripts/pi-host.mjs";
|
|
50
70
|
forwardedArgs = ["run", "--", ...forwardedArgs.slice(1)];
|
|
71
|
+
terminal = true;
|
|
51
72
|
} else if (subcommand === "explain") {
|
|
52
73
|
script = "scripts/explain-command.mjs";
|
|
53
74
|
forwardedArgs = forwardedArgs.slice(1);
|
|
@@ -61,16 +82,18 @@ if (invokedAs === "piagent") {
|
|
|
61
82
|
// `piagent doctor` is `piagent-doctor`: members type it both ways.
|
|
62
83
|
script = scriptByCommand[`piagent-${subcommand}`];
|
|
63
84
|
forwardedArgs = forwardedArgs.slice(1);
|
|
64
|
-
} else if ([
|
|
85
|
+
} else if (["help", "--help", "-h"].includes(subcommand)) {
|
|
65
86
|
// Subcommands are listed, their flags are not. Restating them here meant a
|
|
66
87
|
// second copy that drifts: this line still advertised the dashboard without
|
|
67
88
|
// `--repair` after that flag shipped. Each command states its own surface.
|
|
68
|
-
console.log("Usage: piagent
|
|
89
|
+
console.log("Usage: piagent [pi options] Open Piagent in this terminal (its own Pi home, ~/.piagent/agent)");
|
|
90
|
+
console.log(" piagent <command> [options]");
|
|
69
91
|
console.log("");
|
|
70
92
|
console.log("Commands:");
|
|
71
93
|
console.log(" dashboard Open and manage the local session hub");
|
|
72
94
|
console.log(" studio Open the managed company agent imported by Agent Watch");
|
|
73
|
-
console.log(" pi
|
|
95
|
+
console.log(" pi Same as `piagent`: Pi on the release Piagent is qualified on, in Piagent's home");
|
|
96
|
+
console.log(" restore-pi Take an older Piagent install out of your own pi (~/.pi/agent)");
|
|
74
97
|
console.log(" pi-host Show or install the Pi host Piagent runs");
|
|
75
98
|
console.log(" skills Add, list or remove skill sources such as mattpocock/skills");
|
|
76
99
|
console.log(" explain Say why the guard would allow or block a shell command");
|
|
@@ -91,7 +114,7 @@ if (!script) {
|
|
|
91
114
|
function benchmarkSourceRoot() {
|
|
92
115
|
if (fs.existsSync(path.join(packageRoot, ".git"))) return packageRoot;
|
|
93
116
|
if (process.argv.slice(2).some((value) => value === "--help" || value === "-h")) return packageRoot;
|
|
94
|
-
const agentRoot =
|
|
117
|
+
const agentRoot = piagentHome();
|
|
95
118
|
const installed = path.join(agentRoot, "git", "github.com", "Vt-mmm", "piagent");
|
|
96
119
|
try {
|
|
97
120
|
const helperVersion = JSON.parse(fs.readFileSync(path.join(packageRoot, "package.json"), "utf8")).version;
|
|
@@ -133,8 +156,9 @@ async function recordRuntime() {
|
|
|
133
156
|
// an entrypoint that no longer exists.
|
|
134
157
|
const osTemp = fs.realpathSync.native(os.tmpdir()), installRoot = fs.realpathSync.native(packageRoot);
|
|
135
158
|
if (installRoot === osTemp || installRoot.startsWith(`${osTemp}${path.sep}`)) return;
|
|
136
|
-
|
|
137
|
-
|
|
159
|
+
// Agent Watch reads the record from the personal Pi folder, not Piagent's home.
|
|
160
|
+
const agentDir = personalPiDir;
|
|
161
|
+
fs.mkdirSync(agentDir, { recursive: true, mode: 0o700 });
|
|
138
162
|
const body = `${JSON.stringify({
|
|
139
163
|
schema_version: 1,
|
|
140
164
|
version: JSON.parse(fs.readFileSync(path.join(packageRoot, "package.json"), "utf8")).version,
|
|
@@ -151,6 +175,21 @@ async function recordRuntime() {
|
|
|
151
175
|
}
|
|
152
176
|
await recordRuntime();
|
|
153
177
|
|
|
178
|
+
if (terminal) {
|
|
179
|
+
let packages = [];
|
|
180
|
+
try { packages = JSON.parse(fs.readFileSync(path.join(piagentHome(), "settings.json"), "utf8")).packages ?? []; } catch { /* not installed yet */ }
|
|
181
|
+
const isPiagent = (entry) => {
|
|
182
|
+
const source = String(typeof entry === "string" ? entry : entry?.source ?? "");
|
|
183
|
+
if (/@piagent\/platform|Vt-mmm\/piagent/.test(source)) return true;
|
|
184
|
+
try { return JSON.parse(fs.readFileSync(path.join(path.resolve(piagentHome(), source), "package.json"), "utf8")).name === "@piagent/platform"; } catch { return false; }
|
|
185
|
+
};
|
|
186
|
+
if (!packages.some(isPiagent)) {
|
|
187
|
+
console.error(`Piagent is not installed in ${piagentHome()} yet. Run: piagent-install --stable`);
|
|
188
|
+
console.error("An install from before 1.46 lives in your own pi; piagent-install moves it, or run: piagent restore-pi");
|
|
189
|
+
process.exit(1);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
154
193
|
const sourceRoot = invokedAs === "piagent-benchmark" ? benchmarkSourceRoot() : packageRoot;
|
|
155
194
|
const target = path.join(sourceRoot, script);
|
|
156
195
|
const runner = target.endsWith(".mjs") ? process.execPath : "bash";
|
|
@@ -159,7 +198,8 @@ const runnerArgs = target.endsWith(".mjs")
|
|
|
159
198
|
: [target];
|
|
160
199
|
const child = spawn(runner, [...runnerArgs, ...forwardedArgs], {
|
|
161
200
|
cwd: process.cwd(),
|
|
162
|
-
env: script === "scripts/piagent-studio.mjs" ? { PATH: "/usr/bin:/bin", HOME: os.homedir(), TERM: process.env.TERM || "xterm-256color", LANG: "en_US.UTF-8" }
|
|
201
|
+
env: script === "scripts/piagent-studio.mjs" ? { PATH: "/usr/bin:/bin", HOME: os.homedir(), TERM: process.env.TERM || "xterm-256color", LANG: "en_US.UTF-8" }
|
|
202
|
+
: { ...process.env, PI_CODING_AGENT_DIR: piagentHome(), ...(terminal ? { PIAGENT_ACTIVE: "1" } : {}) },
|
|
163
203
|
stdio: "inherit"
|
|
164
204
|
});
|
|
165
205
|
|
|
@@ -12,6 +12,7 @@ import { gatewayProfileState, profileRef, readGatewayDescriptor, readOrCreateCat
|
|
|
12
12
|
removeGatewayDescriptor } from "../packages/piagent-webui/gateway/profile-state.ts";
|
|
13
13
|
import { startPiagentGateway } from "../packages/piagent-webui/gateway/gateway-service.ts";
|
|
14
14
|
import { agentWatchCompanyConnector } from "../packages/piagent-webui/gateway/managed-launch.ts";
|
|
15
|
+
import { agentWatchControlDir, piagentAgentDir } from "../packages/piagent-core/extensions/agent-home.js";
|
|
15
16
|
|
|
16
17
|
const packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
17
18
|
const packageJson = JSON.parse(fs.readFileSync(path.join(packageRoot, "package.json"), "utf8"));
|
|
@@ -177,7 +178,7 @@ async function ensureStarted() {
|
|
|
177
178
|
}
|
|
178
179
|
const existing = await currentLaunchUrl() ?? await waitForRunningGateway();
|
|
179
180
|
if (existing) return existing;
|
|
180
|
-
const effectiveAgentDir = path.resolve(agentDir ??
|
|
181
|
+
const effectiveAgentDir = path.resolve(agentDir ?? piagentAgentDir());
|
|
181
182
|
const state = gatewayProfileState(effectiveAgentDir);
|
|
182
183
|
writeGatewayErrorLog(state);
|
|
183
184
|
const child = spawn(process.execPath, [
|
|
@@ -210,8 +211,9 @@ async function ensureStarted() {
|
|
|
210
211
|
|
|
211
212
|
async function serve() {
|
|
212
213
|
// Company sessions come from the Agent Watch binding in this Pi folder.
|
|
213
|
-
|
|
214
|
-
const
|
|
214
|
+
// Agent Watch writes the company binding into the personal Pi folder.
|
|
215
|
+
const bindingDir = agentDir ? path.resolve(agentDir) : agentWatchControlDir();
|
|
216
|
+
const gateway = await startPiagentGateway({ packageRoot, expectedPiVersion, agentDir: agentDir ?? piagentAgentDir(), company: agentWatchCompanyConnector(bindingDir) });
|
|
215
217
|
const shutdown = () => { void gateway.close(); };
|
|
216
218
|
process.once("SIGINT", shutdown);
|
|
217
219
|
process.once("SIGTERM", shutdown);
|
package/scripts/setup.sh
CHANGED
|
@@ -504,12 +504,8 @@ echo "Next:"
|
|
|
504
504
|
if [[ "$DO_PROJECT" == true ]]; then
|
|
505
505
|
echo " cd \"$PROJECT_PATH\""
|
|
506
506
|
fi
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
else
|
|
510
|
-
echo " pi"
|
|
511
|
-
fi
|
|
512
|
-
echo " /login # first time only"
|
|
507
|
+
echo " piagent # Piagent in its own Pi home; plain \`pi\` stays your own, stock"
|
|
508
|
+
echo " /login # first time only, unless the login of your pi was copied"
|
|
513
509
|
if [[ "$DO_PROJECT" == true ]]; then
|
|
514
510
|
echo " /model # or Ctrl+L: select provider/model from Pi selector"
|
|
515
511
|
echo " /scoped-models # optional: edit Ctrl+P model cycle scope"
|