@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.
- package/CHANGELOG.md +19 -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 +1 -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 +9 -9
- 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 +31 -19
- package/docs-site/en/windows.html +1 -1
- package/docs-site/en/workflows.html +1 -1
- package/docs-site/index.html +9 -9
- 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 +39 -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 +31 -19
- 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/mcp/mcp-config-layers.js +4 -1
- package/packages/piagent-core/package.json +1 -1
- 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
|
@@ -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"
|
package/scripts/team-doctor.sh
CHANGED
|
@@ -72,8 +72,14 @@ const installations = piagentInstallations();
|
|
|
72
72
|
if(installations.length>1) warnings.push("Multiple Piagent installations detected. Use the pinned Watch launcher; other PATHs may run an older build: "+installations.map(i=>`${i.version} [${i.fingerprint}] ${i.root}`).join("; "));
|
|
73
73
|
// Personal Pi loading Piagent twice does not start; old company providers send it to Studio outside the Harness.
|
|
74
74
|
const { inspect: inspectPersonalPi } = await import(pathToFileURL(path.join(platformRoot, "scripts/personal-pi-config.mjs")).href);
|
|
75
|
-
|
|
76
|
-
|
|
75
|
+
const personalPiDir = path.join(process.env.HOME || "", ".pi", "agent");
|
|
76
|
+
for (const agentDir of new Set([personalPiDir, process.env.PI_CODING_AGENT_DIR || personalPiDir]))
|
|
77
|
+
for (const finding of inspectPersonalPi(agentDir).findings)
|
|
78
|
+
(finding.kind === "duplicate-extensions" ? errors : warnings).push(`${agentDir === personalPiDir ? "personal Pi" : "Piagent home"}: ${finding.text} Fix: piagent-update`);
|
|
79
|
+
// Since 1.46 Piagent runs from its own home; an older install left in personal Pi keeps loading there.
|
|
80
|
+
const { plan: personalPiRestorePlan } = await import(pathToFileURL(path.join(platformRoot, "scripts/personal-pi-restore.mjs")).href);
|
|
81
|
+
if (personalPiRestorePlan({ personalDir: personalPiDir }).legacy)
|
|
82
|
+
warnings.push("personal Pi still loads an older Piagent install (~/.pi/agent). Fix: piagent restore-pi --apply");
|
|
77
83
|
const { verifyCapabilityLock } = await import(pathToFileURL(path.join(platformRoot, "packages", "piagent-core", "capabilities", "capability-core.js")).href);
|
|
78
84
|
|
|
79
85
|
function exists(rel) {
|
|
@@ -271,9 +277,9 @@ if (commandExists("pi")) {
|
|
|
271
277
|
piHasMcpAdapter = combined.includes("pi-mcp-adapter");
|
|
272
278
|
piHasSubagents = combined.includes("pi-subagents");
|
|
273
279
|
piHasWebAccess = combined.includes("pi-web-access");
|
|
274
|
-
if (!piHasMcpAdapter) warnings.push("Pi MCP adapter not found in `pi list`; run
|
|
275
|
-
if (!piHasSubagents) warnings.push("Pi subagents package not found in `pi list`; run
|
|
276
|
-
if (!piHasWebAccess) warnings.push("Pi web access package not found in `pi list`; run `piagent-
|
|
280
|
+
if (!piHasMcpAdapter) warnings.push("Pi MCP adapter not found in `pi list`; run `piagent-install --with-mcp` (installs into Piagent's home, not your personal pi)");
|
|
281
|
+
if (!piHasSubagents) warnings.push("Pi subagents package not found in `pi list`; run `piagent-install --with-subagents` if you want them");
|
|
282
|
+
if (!piHasWebAccess) warnings.push("Pi web access package not found in `pi list`; run `piagent-install --with-web-access` so researcher subagents can browse/fetch external sources");
|
|
277
283
|
}
|
|
278
284
|
|
|
279
285
|
const mcpFiles = [
|
|
@@ -9,7 +9,16 @@ set -euo pipefail
|
|
|
9
9
|
# deleted.
|
|
10
10
|
|
|
11
11
|
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
12
|
-
|
|
12
|
+
# Piagent's own Pi home (same rule as packages/piagent-core/extensions/agent-home.js).
|
|
13
|
+
PERSONAL_PI_DIR="${HOME}/.pi/agent"
|
|
14
|
+
if [[ -n "${PIAGENT_AGENT_DIR:-}" ]]; then
|
|
15
|
+
PI_AGENT_DIR="$PIAGENT_AGENT_DIR"
|
|
16
|
+
elif [[ -n "${PI_CODING_AGENT_DIR:-}" && "${PI_CODING_AGENT_DIR%/}" != "$PERSONAL_PI_DIR" ]]; then
|
|
17
|
+
PI_AGENT_DIR="$PI_CODING_AGENT_DIR"
|
|
18
|
+
else
|
|
19
|
+
PI_AGENT_DIR="${HOME}/.piagent/agent"
|
|
20
|
+
fi
|
|
21
|
+
export PI_CODING_AGENT_DIR="$PI_AGENT_DIR"
|
|
13
22
|
GLOBAL_SETTINGS="$PI_AGENT_DIR/settings.json"
|
|
14
23
|
|
|
15
24
|
APPLY=false
|
|
@@ -26,7 +35,9 @@ usage() {
|
|
|
26
35
|
Usage:
|
|
27
36
|
piagent-uninstall [--apply] [--with-addons] [--with-host] [--project <path>]
|
|
28
37
|
|
|
29
|
-
Removes the Pi package this platform installed from
|
|
38
|
+
Removes the Pi package this platform installed from Piagent's own Pi home
|
|
39
|
+
(~/.piagent/agent). An install from before 1.46 lives in your personal pi
|
|
40
|
+
(~/.pi/agent): `piagent restore-pi --apply` takes it out of there.
|
|
30
41
|
|
|
31
42
|
(default) Dry run. Reports what would be removed and exits 0.
|
|
32
43
|
--apply Perform the removal.
|
|
@@ -291,6 +302,10 @@ NODE
|
|
|
291
302
|
fi
|
|
292
303
|
|
|
293
304
|
echo
|
|
305
|
+
if node "$ROOT/scripts/personal-pi-restore.mjs" --json 2>/dev/null | grep -q '"legacy": true'; then
|
|
306
|
+
echo "Your personal pi ($PERSONAL_PI_DIR) still loads an older Piagent install."
|
|
307
|
+
echo " Take it out with: piagent restore-pi --apply"
|
|
308
|
+
fi
|
|
294
309
|
if [[ "$APPLY" == true ]]; then
|
|
295
310
|
echo "Uninstall complete."
|
|
296
311
|
echo "The npm-global helper is separate and stays until you remove it:"
|
|
@@ -22,6 +22,7 @@ import path from "node:path";
|
|
|
22
22
|
import { spawnSync } from "node:child_process";
|
|
23
23
|
import { fileURLToPath } from "node:url";
|
|
24
24
|
import { inspect as inspectPersonalPi } from "./personal-pi-config.mjs";
|
|
25
|
+
import { personalPiAgentDir, piagentAgentDir } from "../packages/piagent-core/extensions/agent-home.js";
|
|
25
26
|
import { hostPlan, installPrivateHost, privateHostRoot } from "../packages/piagent-core/managed/pi-host.mjs";
|
|
26
27
|
|
|
27
28
|
const HELPER_PACKAGE = "@piagent/platform";
|
|
@@ -438,8 +439,10 @@ function main() {
|
|
|
438
439
|
: ` host: ${currentHost ?? "not installed"} -> ${targetHost}${hostNeedsChange ? "" : " (already current)"}`);
|
|
439
440
|
// The Pi package can lag the helper (installed from an older tag), and
|
|
440
441
|
// personal Pi can hold conflicting Piagent or old company entries.
|
|
441
|
-
|
|
442
|
-
|
|
442
|
+
// The package lives in Piagent's home; old company entries and an older
|
|
443
|
+
// Piagent install live in the member's personal pi.
|
|
444
|
+
const personal = inspectPersonalPi(personalPiAgentDir()), own = inspectPersonalPi(piagentAgentDir());
|
|
445
|
+
const installedPackages = own.packages.map((item) => item.version ?? "unknown");
|
|
443
446
|
const packageNeedsChange = options.package && (options.force || installedPackages.length !== 1 || installedPackages[0] !== targetHelper);
|
|
444
447
|
console.log(` package: ${installedPackages.join(", ") || "not installed"} -> v${targetHelper}${options.package ? packageNeedsChange ? " (reinstalled as a resolved commit)" : " (already current)" : " (skipped)"}`);
|
|
445
448
|
for (const finding of personal.findings) console.log(` personal Pi: ${finding.text}`);
|