@piagent/platform 1.27.1 → 1.28.1
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 +1 -1
- package/README.vi.md +1 -1
- 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/benchmark.html +1 -1
- package/docs-site/commands.html +1 -1
- package/docs-site/content/en/index.html +4 -4
- package/docs-site/content/en/whats-new.html +12 -0
- package/docs-site/content/index.html +4 -4
- package/docs-site/content/whats-new.html +12 -0
- 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 +5 -5
- 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/whats-new.html +13 -1
- package/docs-site/en/windows.html +1 -1
- package/docs-site/en/workflows.html +1 -1
- package/docs-site/index.html +5 -5
- 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/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/whats-new.html +13 -1
- 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/extensions/company-session-guard.ts +36 -0
- package/packages/piagent-core/extensions/piagent-guard.ts +2 -0
- package/packages/piagent-core/managed/memory-lessons.mjs +63 -0
- package/packages/piagent-core/managed/memory-outbox.mjs +93 -0
- package/packages/piagent-core/managed/memory.mjs +246 -93
- package/packages/piagent-core/managed/session.mjs +6 -6
- package/packages/piagent-core/package.json +1 -1
- package/scripts/company-terminal.mjs +12 -0
- package/scripts/install-global.sh +11 -0
- package/scripts/personal-pi-config.mjs +137 -0
- package/scripts/piagent-cli.mjs +8 -0
- package/scripts/piagent-studio.mjs +4 -0
- package/scripts/team-doctor.sh +4 -0
- package/scripts/update-global.mjs +9 -2
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// What in the member's personal Pi configuration (~/.pi/agent) conflicts with
|
|
3
|
+
// Piagent, and the repair. Personal Pi loads Piagent as a Pi package; an
|
|
4
|
+
// extension path that points into another Piagent copy registers the same
|
|
5
|
+
// tools twice and Pi refuses to start ("Tool … conflicts with …"). Older Agent
|
|
6
|
+
// Watch releases also wrote company providers (agent_watch_*, a direct key
|
|
7
|
+
// read from Agent Watch) and a company default model into personal Pi; with a
|
|
8
|
+
// managed binding (agent-watch-managed.json) the company runs only through
|
|
9
|
+
// `piagent studio`, so those entries are leftovers that send personal Pi to
|
|
10
|
+
// Studio outside the team's Harness, or fail.
|
|
11
|
+
//
|
|
12
|
+
// personal-pi-config.mjs check [--agent-dir <dir>] [--json] exit 1 when something conflicts
|
|
13
|
+
// personal-pi-config.mjs repair [--agent-dir <dir>] backs up, then removes what conflicts
|
|
14
|
+
import fs from "node:fs";
|
|
15
|
+
import os from "node:os";
|
|
16
|
+
import path from "node:path";
|
|
17
|
+
import { fileURLToPath } from "node:url";
|
|
18
|
+
|
|
19
|
+
const COMPANY_PROVIDER = /^agent_watch_/;
|
|
20
|
+
const SESSION_HEADER_EXTENSION = "agentwatch-studio-session.mjs";
|
|
21
|
+
|
|
22
|
+
const readJson = (file) => { try { return JSON.parse(fs.readFileSync(file, "utf8")); } catch { return null; } };
|
|
23
|
+
const real = (file) => { try { return fs.realpathSync(file); } catch { return null; } };
|
|
24
|
+
|
|
25
|
+
// The Piagent package a path belongs to (its root), or null.
|
|
26
|
+
export function piagentRootOf(file) {
|
|
27
|
+
let dir = real(file);
|
|
28
|
+
if (!dir) return null;
|
|
29
|
+
if (!fs.statSync(dir).isDirectory()) dir = path.dirname(dir);
|
|
30
|
+
for (; dir !== path.dirname(dir); dir = path.dirname(dir)) {
|
|
31
|
+
const manifest = readJson(path.join(dir, "package.json"));
|
|
32
|
+
if (manifest?.name === "@piagent/platform") return dir;
|
|
33
|
+
}
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const isPiagentSource = (source) => /^(git:github\.com\/Vt-mmm\/piagent|https:\/\/github\.com\/Vt-mmm\/piagent|npm:@piagent\/platform)(@|$)/.test(String(source));
|
|
38
|
+
|
|
39
|
+
// The installed Pi package's version (git:… sources live under <agentDir>/git/…).
|
|
40
|
+
export function packageVersion(agentDir, source) {
|
|
41
|
+
const match = /^git:([^@]+)/.exec(String(source));
|
|
42
|
+
if (match) return readJson(path.join(agentDir, "git", match[1], "package.json"))?.version ?? null;
|
|
43
|
+
const npm = /^npm:(@piagent\/platform)/.exec(String(source));
|
|
44
|
+
if (npm) return readJson(path.join(agentDir, "npm", "node_modules", npm[1], "package.json"))?.version ?? null;
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function inspect(agentDir) {
|
|
49
|
+
const settingsFile = path.join(agentDir, "settings.json"), modelsFile = path.join(agentDir, "models.json");
|
|
50
|
+
const settings = readJson(settingsFile) ?? {}, models = readJson(modelsFile) ?? {};
|
|
51
|
+
const managed = fs.existsSync(path.join(agentDir, "agent-watch-managed.json"));
|
|
52
|
+
const packages = Array.isArray(settings.packages) ? settings.packages : [];
|
|
53
|
+
const piagentPackages = packages.filter(isPiagentSource);
|
|
54
|
+
const extensions = Array.isArray(settings.extensions) ? settings.extensions : [];
|
|
55
|
+
const findings = [];
|
|
56
|
+
|
|
57
|
+
// Extensions inside a Piagent copy load beside the Pi package: duplicate tools.
|
|
58
|
+
const duplicate = extensions.filter((entry) => piagentRootOf(path.resolve(agentDir, String(entry))) && piagentPackages.length);
|
|
59
|
+
if (duplicate.length) findings.push({ kind: "duplicate-extensions", file: settingsFile, entries: duplicate,
|
|
60
|
+
text: `${duplicate.length} extension(s) point into another Piagent copy while the Piagent Pi package is installed: Pi refuses to start (tools registered twice).` });
|
|
61
|
+
if (piagentPackages.length > 1) findings.push({ kind: "duplicate-packages", file: settingsFile, entries: piagentPackages,
|
|
62
|
+
text: `${piagentPackages.length} Piagent Pi packages are registered; keep one.` });
|
|
63
|
+
|
|
64
|
+
if (managed) {
|
|
65
|
+
const providers = Object.keys(models.providers ?? {}).filter((name) => COMPANY_PROVIDER.test(name));
|
|
66
|
+
if (providers.length) findings.push({ kind: "company-providers", file: modelsFile, entries: providers,
|
|
67
|
+
text: `Old Agent Watch company providers (${providers.join(", ")}) are in personal Pi; company work runs only through \`piagent studio\`.` });
|
|
68
|
+
const defaults = [];
|
|
69
|
+
if (COMPANY_PROVIDER.test(String(settings.defaultProvider ?? ""))) defaults.push(`defaultProvider=${settings.defaultProvider}`);
|
|
70
|
+
for (const entry of Array.isArray(settings.enabledModels) ? settings.enabledModels : []) if (COMPANY_PROVIDER.test(String(entry))) defaults.push(`enabledModels:${entry}`);
|
|
71
|
+
if (defaults.length) findings.push({ kind: "company-defaults", file: settingsFile, entries: defaults,
|
|
72
|
+
text: "Personal Pi's default model or model list still names an old company provider." });
|
|
73
|
+
const header = extensions.filter((entry) => path.basename(String(entry)) === SESSION_HEADER_EXTENSION);
|
|
74
|
+
if (header.length) findings.push({ kind: "company-extension", file: settingsFile, entries: header,
|
|
75
|
+
text: "The old Agent Watch session-header extension is still loaded in personal Pi." });
|
|
76
|
+
}
|
|
77
|
+
return { agentDir, managed, findings, packages: piagentPackages.map((source) => ({ source, version: packageVersion(agentDir, source) })) };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function backup(file, stamp) {
|
|
81
|
+
if (!fs.existsSync(file)) return null;
|
|
82
|
+
const copy = `${file}.before-piagent-repair-${stamp}`;
|
|
83
|
+
fs.copyFileSync(file, copy);
|
|
84
|
+
fs.chmodSync(copy, 0o600);
|
|
85
|
+
return copy;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function writeJson(file, value) {
|
|
89
|
+
const tmp = `${file}.${process.pid}.tmp`;
|
|
90
|
+
fs.writeFileSync(tmp, `${JSON.stringify(value, null, 2)}\n`, { mode: 0o600 });
|
|
91
|
+
fs.renameSync(tmp, file);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Removes what inspect() found (duplicate packages are left to the installer,
|
|
95
|
+
// which knows the one to keep). Every changed file is backed up first.
|
|
96
|
+
export function repair(agentDir, now = new Date()) {
|
|
97
|
+
const report = inspect(agentDir);
|
|
98
|
+
const kinds = new Set(report.findings.map((finding) => finding.kind));
|
|
99
|
+
const stamp = now.toISOString().replace(/[:.]/g, "-");
|
|
100
|
+
const changed = [], backups = [];
|
|
101
|
+
const settingsFile = path.join(agentDir, "settings.json"), modelsFile = path.join(agentDir, "models.json");
|
|
102
|
+
if (kinds.has("duplicate-extensions") || kinds.has("company-defaults") || kinds.has("company-extension")) {
|
|
103
|
+
const settings = readJson(settingsFile);
|
|
104
|
+
const drop = new Set(report.findings.filter((f) => f.kind === "duplicate-extensions" || f.kind === "company-extension").flatMap((f) => f.entries));
|
|
105
|
+
if (Array.isArray(settings.extensions)) settings.extensions = settings.extensions.filter((entry) => !drop.has(entry));
|
|
106
|
+
if (Array.isArray(settings.extensions) && !settings.extensions.length) delete settings.extensions;
|
|
107
|
+
if (COMPANY_PROVIDER.test(String(settings.defaultProvider ?? ""))) { delete settings.defaultProvider; delete settings.defaultModel; }
|
|
108
|
+
if (Array.isArray(settings.enabledModels)) settings.enabledModels = settings.enabledModels.filter((entry) => !COMPANY_PROVIDER.test(String(entry)));
|
|
109
|
+
backups.push(backup(settingsFile, stamp)); writeJson(settingsFile, settings); changed.push(settingsFile);
|
|
110
|
+
}
|
|
111
|
+
if (kinds.has("company-providers")) {
|
|
112
|
+
const models = readJson(modelsFile);
|
|
113
|
+
for (const name of Object.keys(models.providers ?? {})) if (COMPANY_PROVIDER.test(name)) delete models.providers[name];
|
|
114
|
+
backups.push(backup(modelsFile, stamp)); writeJson(modelsFile, models); changed.push(modelsFile);
|
|
115
|
+
}
|
|
116
|
+
return { ...report, changed, backups: backups.filter(Boolean), after: inspect(agentDir) };
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const invoked = process.argv[1] && real(process.argv[1]) === real(fileURLToPath(import.meta.url));
|
|
120
|
+
if (invoked) {
|
|
121
|
+
const args = process.argv.slice(2), command = args[0];
|
|
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"));
|
|
124
|
+
if (!["check", "repair"].includes(command)) { console.error("Usage: personal-pi-config.mjs check|repair [--agent-dir <dir>] [--json]"); process.exit(2); }
|
|
125
|
+
const result = command === "repair" ? repair(agentDir) : inspect(agentDir);
|
|
126
|
+
if (args.includes("--json")) console.log(JSON.stringify(result, null, 2));
|
|
127
|
+
else if (command === "repair") {
|
|
128
|
+
for (const finding of result.findings) console.log(`fixed: ${finding.text}`);
|
|
129
|
+
for (const file of result.backups) console.log(`backup: ${file}`);
|
|
130
|
+
if (!result.findings.length) console.log("personal Pi configuration: nothing to repair");
|
|
131
|
+
} else {
|
|
132
|
+
for (const finding of result.findings) console.log(`WARN: ${finding.text}`);
|
|
133
|
+
if (result.findings.length) console.log("Fix: piagent-update (or node scripts/personal-pi-config.mjs repair)");
|
|
134
|
+
else console.log("PASS: personal Pi configuration has no Piagent conflicts");
|
|
135
|
+
}
|
|
136
|
+
process.exit(command === "check" && result.findings.length ? 1 : 0);
|
|
137
|
+
}
|
package/scripts/piagent-cli.mjs
CHANGED
|
@@ -158,8 +158,16 @@ child.once("error", (error) => {
|
|
|
158
158
|
process.exit(1);
|
|
159
159
|
});
|
|
160
160
|
|
|
161
|
+
// A command stopped from outside (a script, an editor, a timeout) stops its
|
|
162
|
+
// child too, rather than leaving a company run going in the background. A
|
|
163
|
+
// terminal's Ctrl+C already reaches the whole process group: the launcher just
|
|
164
|
+
// stays until the child has finished.
|
|
165
|
+
for (const signal of ["SIGTERM", "SIGHUP"]) process.on(signal, () => { try { child.kill(signal); } catch { /* already gone */ } });
|
|
166
|
+
process.on("SIGINT", () => {});
|
|
167
|
+
|
|
161
168
|
child.once("exit", (code, signal) => {
|
|
162
169
|
if (signal) {
|
|
170
|
+
process.removeAllListeners(signal);
|
|
163
171
|
process.kill(process.pid, signal);
|
|
164
172
|
return;
|
|
165
173
|
}
|
|
@@ -162,6 +162,10 @@ try {
|
|
|
162
162
|
// (updating Pi breaks the Agent Watch import), no package-update, catalog or
|
|
163
163
|
// install-telemetry calls. Search tools were provisioned above.
|
|
164
164
|
process.env.PI_OFFLINE = '1'; process.env.PI_SKIP_VERSION_CHECK = '1';
|
|
165
|
+
// Pi's exit line names `pi --session-dir …`, which would open this company
|
|
166
|
+
// conversation in personal Pi; name the company Terminal instead.
|
|
167
|
+
const write = process.stdout.write.bind(process.stdout), project = /^[\w./~:-]+$/.test(cwd) ? cwd : `'${cwd.replaceAll("'", "'\\''")}'`;
|
|
168
|
+
process.stdout.write = (chunk, ...rest) => write(typeof chunk === 'string' && chunk.includes('To resume this session:') ? `Mở lại cuộc trò chuyện này: piagent studio --project ${project}, rồi /resume\n` : chunk, ...rest);
|
|
165
169
|
await new api.InteractiveMode(host, { verbose: false }).run();
|
|
166
170
|
}
|
|
167
171
|
}
|
package/scripts/team-doctor.sh
CHANGED
|
@@ -70,6 +70,10 @@ const warnings = [];
|
|
|
70
70
|
const { piagentInstallations } = await import(pathToFileURL(path.join(platformRoot, "scripts/piagent-installations.mjs")).href);
|
|
71
71
|
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
|
+
// Personal Pi loading Piagent twice does not start; old company providers send it to Studio outside the Harness.
|
|
74
|
+
const { inspect: inspectPersonalPi } = await import(pathToFileURL(path.join(platformRoot, "scripts/personal-pi-config.mjs")).href);
|
|
75
|
+
for (const finding of inspectPersonalPi(process.env.PI_CODING_AGENT_DIR || path.join(process.env.HOME || "", ".pi", "agent")).findings)
|
|
76
|
+
(finding.kind === "duplicate-extensions" ? errors : warnings).push(`personal Pi: ${finding.text} Fix: piagent-update`);
|
|
73
77
|
const { verifyCapabilityLock } = await import(pathToFileURL(path.join(platformRoot, "packages", "piagent-core", "capabilities", "capability-core.js")).href);
|
|
74
78
|
|
|
75
79
|
function exists(rel) {
|
|
@@ -21,6 +21,7 @@ import os from "node:os";
|
|
|
21
21
|
import path from "node:path";
|
|
22
22
|
import { spawnSync } from "node:child_process";
|
|
23
23
|
import { fileURLToPath } from "node:url";
|
|
24
|
+
import { inspect as inspectPersonalPi } from "./personal-pi-config.mjs";
|
|
24
25
|
|
|
25
26
|
const HELPER_PACKAGE = "@piagent/platform";
|
|
26
27
|
const HOST_PACKAGE = "@earendil-works/pi-coding-agent";
|
|
@@ -430,13 +431,19 @@ function main() {
|
|
|
430
431
|
console.log("Pi Agent Platform update");
|
|
431
432
|
console.log(` helper: ${currentHelper} -> ${targetHelper}${helperNeedsChange ? "" : " (already current)"}`);
|
|
432
433
|
console.log(` host: ${currentHost ?? "not installed"} -> ${targetHost}${hostNeedsChange ? "" : " (already current)"}`);
|
|
433
|
-
|
|
434
|
+
// The Pi package can lag the helper (installed from an older tag), and
|
|
435
|
+
// personal Pi can hold conflicting Piagent or old company entries.
|
|
436
|
+
const personal = inspectPersonalPi(process.env.PI_CODING_AGENT_DIR || path.join(os.homedir(), ".pi", "agent"));
|
|
437
|
+
const installedPackages = personal.packages.map((item) => item.version ?? "unknown");
|
|
438
|
+
const packageNeedsChange = options.package && (options.force || installedPackages.length !== 1 || installedPackages[0] !== targetHelper);
|
|
439
|
+
console.log(` package: ${installedPackages.join(", ") || "not installed"} -> v${targetHelper}${options.package ? packageNeedsChange ? " (reinstalled as a resolved commit)" : " (already current)" : " (skipped)"}`);
|
|
440
|
+
for (const finding of personal.findings) console.log(` personal Pi: ${finding.text}`);
|
|
434
441
|
if (options.project) {
|
|
435
442
|
console.log(` project: ${path.resolve(options.project)} (${options.migrate ? "migrate + doctor" : "doctor only"})`);
|
|
436
443
|
}
|
|
437
444
|
|
|
438
445
|
if (options.check) {
|
|
439
|
-
const pending = [helperNeedsChange && "helper", hostNeedsChange && "host"].filter(Boolean);
|
|
446
|
+
const pending = [helperNeedsChange && "helper", hostNeedsChange && "host", packageNeedsChange && "package", personal.findings.length && "personal Pi configuration"].filter(Boolean);
|
|
440
447
|
console.log(pending.length ? `\nUpdate available: ${pending.join(", ")}.` : "\nEverything is on the target release.");
|
|
441
448
|
return;
|
|
442
449
|
}
|