larkin 0.4.17 → 0.4.19
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/dist/runtime/builtin-pi-assets.cjs +117 -0
- package/dist/runtime/builtin-pi-assets.mjs +72 -0
- package/dist/runtime/pi-inline-extensions.mjs +5 -0
- package/dist/runtime/pi-model-catalog.cjs +16 -2
- package/dist/runtime/pi-model-catalog.mjs +16 -2
- package/dist/runtime/pi-profile-migration.cjs +113 -13
- package/dist/runtime/pi-profile-migration.mjs +113 -13
- package/dist/runtime/pi-subagent-ledger.cjs +632 -0
- package/dist/runtime/pi-subagent-ledger.mjs +594 -0
- package/dist/runtime/pi-subagent-record-watchdog-injection.mjs +43 -0
- package/dist/runtime/pi-subagent-record-watchdog.bundle.js +183 -0
- package/dist/runtime/pi-subagent-record-watchdog.mjs +46 -0
- package/dist/runtime/pi-subagents-notification.mjs +14 -0
- package/dist/runtime/runtime-adapters.mjs +68 -20
- package/dist/runtime/runtime-host.cjs +187 -14
- package/dist/runtime/runtime-host.mjs +187 -14
- package/dist/runtime/runtime-readiness.cjs +9 -2
- package/dist/runtime/runtime-readiness.mjs +9 -2
- package/dist/setup/setup-bind.mjs +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.prepareBuiltinPiPackageAssets = prepareBuiltinPiPackageAssets;
|
|
7
|
+
exports.piChildDistribution = piChildDistribution;
|
|
8
|
+
exports.piChildDistributionFromOverrides = piChildDistributionFromOverrides;
|
|
9
|
+
exports.resolveExternalPiPackageDir = resolveExternalPiPackageDir;
|
|
10
|
+
exports.isLarkinBuiltinPiPackageDir = isLarkinBuiltinPiPackageDir;
|
|
11
|
+
exports.catalogPiChildDistribution = catalogPiChildDistribution;
|
|
12
|
+
exports.applyPiPackageDirForChild = applyPiPackageDirForChild;
|
|
13
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
14
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
15
|
+
function privateDirectory(directory) {
|
|
16
|
+
node_fs_1.default.mkdirSync(directory, { recursive: true, mode: 0o700 });
|
|
17
|
+
const stat = node_fs_1.default.lstatSync(directory);
|
|
18
|
+
if (!stat.isDirectory() || stat.isSymbolicLink())
|
|
19
|
+
throw new Error(`unsafe bundled Pi asset directory: ${directory}`);
|
|
20
|
+
node_fs_1.default.chmodSync(directory, 0o700);
|
|
21
|
+
}
|
|
22
|
+
function writeEmbeddedFile(file, contents) {
|
|
23
|
+
const temporary = `${file}.${process.pid}.${Math.random().toString(16).slice(2)}.tmp`;
|
|
24
|
+
node_fs_1.default.writeFileSync(temporary, contents, { mode: 0o600, flag: "wx" });
|
|
25
|
+
node_fs_1.default.renameSync(temporary, file);
|
|
26
|
+
node_fs_1.default.chmodSync(file, 0o600);
|
|
27
|
+
}
|
|
28
|
+
/** Materialize upstream runtime assets that Bun cannot serve through ordinary fs paths. */
|
|
29
|
+
function prepareBuiltinPiPackageAssets() {
|
|
30
|
+
const assets = globalThis.__LARKIN_EMBEDDED_BUILTIN_PI_ASSETS__;
|
|
31
|
+
if (!assets)
|
|
32
|
+
return;
|
|
33
|
+
const agentDir = process.env.PI_CODING_AGENT_DIR;
|
|
34
|
+
if (!agentDir)
|
|
35
|
+
throw new Error("bundled Pi requires PI_CODING_AGENT_DIR");
|
|
36
|
+
privateDirectory(agentDir);
|
|
37
|
+
const packageDir = node_path_1.default.join(agentDir, ".larkin-official-pi-package");
|
|
38
|
+
const themeDir = node_path_1.default.join(packageDir, "theme");
|
|
39
|
+
privateDirectory(packageDir);
|
|
40
|
+
privateDirectory(themeDir);
|
|
41
|
+
writeEmbeddedFile(node_path_1.default.join(packageDir, "package.json"), assets.packageJson);
|
|
42
|
+
writeEmbeddedFile(node_path_1.default.join(themeDir, "dark.json"), assets.darkTheme);
|
|
43
|
+
writeEmbeddedFile(node_path_1.default.join(themeDir, "light.json"), assets.lightTheme);
|
|
44
|
+
process.env.PI_PACKAGE_DIR = packageDir;
|
|
45
|
+
}
|
|
46
|
+
function piChildDistribution(env) {
|
|
47
|
+
return env?.LARKIN_PI_DISTRIBUTION === "builtin" ? "builtin" : "external";
|
|
48
|
+
}
|
|
49
|
+
/** Host process env may be builtin Pi; only explicit child overrides select bundled assets. */
|
|
50
|
+
function piChildDistributionFromOverrides(...overrides) {
|
|
51
|
+
for (let index = overrides.length - 1; index >= 0; index -= 1) {
|
|
52
|
+
const value = overrides[index]?.LARKIN_PI_DISTRIBUTION;
|
|
53
|
+
if (value === "builtin" || value === "external")
|
|
54
|
+
return value;
|
|
55
|
+
}
|
|
56
|
+
return "external";
|
|
57
|
+
}
|
|
58
|
+
function existingPiThemeFile(directory) {
|
|
59
|
+
const theme = node_path_1.default.join(directory, "dist", "modes", "interactive", "theme", "dark.json");
|
|
60
|
+
let fd;
|
|
61
|
+
try {
|
|
62
|
+
fd = node_fs_1.default.openSync(theme, node_fs_1.default.constants.O_RDONLY);
|
|
63
|
+
return node_fs_1.default.fstatSync(fd).isFile() ? theme : undefined;
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
return undefined;
|
|
67
|
+
}
|
|
68
|
+
finally {
|
|
69
|
+
if (fd !== undefined) {
|
|
70
|
+
try {
|
|
71
|
+
node_fs_1.default.closeSync(fd);
|
|
72
|
+
}
|
|
73
|
+
catch { /* already closed or unreadable */ }
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
function resolveExternalPiPackageDir(directory) {
|
|
78
|
+
if (!directory?.trim())
|
|
79
|
+
return undefined;
|
|
80
|
+
try {
|
|
81
|
+
const resolved = node_fs_1.default.realpathSync(directory);
|
|
82
|
+
return existingPiThemeFile(resolved) ? resolved : undefined;
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
return undefined;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
function isLarkinBuiltinPiPackageDir(directory) {
|
|
89
|
+
if (!directory)
|
|
90
|
+
return false;
|
|
91
|
+
try {
|
|
92
|
+
const resolved = node_fs_1.default.realpathSync(directory);
|
|
93
|
+
if (node_path_1.default.basename(resolved).toLowerCase() !== ".larkin-official-pi-package")
|
|
94
|
+
return false;
|
|
95
|
+
return !existingPiThemeFile(resolved);
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
return node_path_1.default.basename(directory).toLowerCase() === ".larkin-official-pi-package";
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
function catalogPiChildDistribution(commandArgs) {
|
|
102
|
+
return commandArgs?.includes("pi-rpc") ? "builtin" : "external";
|
|
103
|
+
}
|
|
104
|
+
/** External/npm Pi resolves PI_PACKAGE_DIR as a Node package root and looks for dist/themes. */
|
|
105
|
+
function applyPiPackageDirForChild(env, distribution = piChildDistribution(env)) {
|
|
106
|
+
const options = typeof distribution === "string" ? { distribution } : distribution;
|
|
107
|
+
const resolved = options.distribution ?? piChildDistribution(env);
|
|
108
|
+
const next = { ...env };
|
|
109
|
+
if (resolved === "builtin")
|
|
110
|
+
return next;
|
|
111
|
+
const kept = resolveExternalPiPackageDir(options.explicitPackageDir) ?? resolveExternalPiPackageDir(next.PI_PACKAGE_DIR);
|
|
112
|
+
if (kept)
|
|
113
|
+
next.PI_PACKAGE_DIR = kept;
|
|
114
|
+
else
|
|
115
|
+
delete next.PI_PACKAGE_DIR;
|
|
116
|
+
return next;
|
|
117
|
+
}
|
|
@@ -31,3 +31,75 @@ export function prepareBuiltinPiPackageAssets() {
|
|
|
31
31
|
writeEmbeddedFile(path.join(themeDir, "light.json"), assets.lightTheme);
|
|
32
32
|
process.env.PI_PACKAGE_DIR = packageDir;
|
|
33
33
|
}
|
|
34
|
+
export function piChildDistribution(env) {
|
|
35
|
+
return env?.LARKIN_PI_DISTRIBUTION === "builtin" ? "builtin" : "external";
|
|
36
|
+
}
|
|
37
|
+
/** Host process env may be builtin Pi; only explicit child overrides select bundled assets. */
|
|
38
|
+
export function piChildDistributionFromOverrides(...overrides) {
|
|
39
|
+
for (let index = overrides.length - 1; index >= 0; index -= 1) {
|
|
40
|
+
const value = overrides[index]?.LARKIN_PI_DISTRIBUTION;
|
|
41
|
+
if (value === "builtin" || value === "external")
|
|
42
|
+
return value;
|
|
43
|
+
}
|
|
44
|
+
return "external";
|
|
45
|
+
}
|
|
46
|
+
function existingPiThemeFile(directory) {
|
|
47
|
+
const theme = path.join(directory, "dist", "modes", "interactive", "theme", "dark.json");
|
|
48
|
+
let fd;
|
|
49
|
+
try {
|
|
50
|
+
fd = fs.openSync(theme, fs.constants.O_RDONLY);
|
|
51
|
+
return fs.fstatSync(fd).isFile() ? theme : undefined;
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
return undefined;
|
|
55
|
+
}
|
|
56
|
+
finally {
|
|
57
|
+
if (fd !== undefined) {
|
|
58
|
+
try {
|
|
59
|
+
fs.closeSync(fd);
|
|
60
|
+
}
|
|
61
|
+
catch { /* already closed or unreadable */ }
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
export function resolveExternalPiPackageDir(directory) {
|
|
66
|
+
if (!directory?.trim())
|
|
67
|
+
return undefined;
|
|
68
|
+
try {
|
|
69
|
+
const resolved = fs.realpathSync(directory);
|
|
70
|
+
return existingPiThemeFile(resolved) ? resolved : undefined;
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
return undefined;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
export function isLarkinBuiltinPiPackageDir(directory) {
|
|
77
|
+
if (!directory)
|
|
78
|
+
return false;
|
|
79
|
+
try {
|
|
80
|
+
const resolved = fs.realpathSync(directory);
|
|
81
|
+
if (path.basename(resolved).toLowerCase() !== ".larkin-official-pi-package")
|
|
82
|
+
return false;
|
|
83
|
+
return !existingPiThemeFile(resolved);
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
return path.basename(directory).toLowerCase() === ".larkin-official-pi-package";
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
export function catalogPiChildDistribution(commandArgs) {
|
|
90
|
+
return commandArgs?.includes("pi-rpc") ? "builtin" : "external";
|
|
91
|
+
}
|
|
92
|
+
/** External/npm Pi resolves PI_PACKAGE_DIR as a Node package root and looks for dist/themes. */
|
|
93
|
+
export function applyPiPackageDirForChild(env, distribution = piChildDistribution(env)) {
|
|
94
|
+
const options = typeof distribution === "string" ? { distribution } : distribution;
|
|
95
|
+
const resolved = options.distribution ?? piChildDistribution(env);
|
|
96
|
+
const next = { ...env };
|
|
97
|
+
if (resolved === "builtin")
|
|
98
|
+
return next;
|
|
99
|
+
const kept = resolveExternalPiPackageDir(options.explicitPackageDir) ?? resolveExternalPiPackageDir(next.PI_PACKAGE_DIR);
|
|
100
|
+
if (kept)
|
|
101
|
+
next.PI_PACKAGE_DIR = kept;
|
|
102
|
+
else
|
|
103
|
+
delete next.PI_PACKAGE_DIR;
|
|
104
|
+
return next;
|
|
105
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { pathToFileURL } from "node:url";
|
|
2
2
|
import bashTimeoutExtension from "./pi-bash-timeout-extension.mjs";
|
|
3
3
|
import { bundledPiSubagentExtensionPath } from "./pi-subagent-injection.mjs";
|
|
4
|
+
import piSubagentRecordWatchdog from "./pi-subagent-record-watchdog.mjs";
|
|
4
5
|
/**
|
|
5
6
|
* Load the prebuilt, patched subagent bundle shipped in dist/ rather than the
|
|
6
7
|
* package-manager copy. Standalone binaries additionally load the patched
|
|
@@ -30,7 +31,11 @@ const bundledSubagentsExtension = async (pi) => {
|
|
|
30
31
|
else
|
|
31
32
|
await extension.factory(pi);
|
|
32
33
|
};
|
|
34
|
+
// Watchdog must register session_shutdown before the bundled subagent extension
|
|
35
|
+
// so the final sweep can still read AgentManager.getRecord and bridge consumed
|
|
36
|
+
// terminals before manager teardown.
|
|
33
37
|
export const BUILTIN_PI_EXTENSION_FACTORIES = Object.freeze([
|
|
38
|
+
piSubagentRecordWatchdog,
|
|
34
39
|
bundledSubagentsExtension,
|
|
35
40
|
bashTimeoutExtension,
|
|
36
41
|
]);
|
|
@@ -41,6 +41,7 @@ exports.findExactPiModel = findExactPiModel;
|
|
|
41
41
|
exports.discoverPiModelCatalog = discoverPiModelCatalog;
|
|
42
42
|
const node_child_process_1 = require("node:child_process");
|
|
43
43
|
const path = __importStar(require("node:path"));
|
|
44
|
+
const builtin_pi_assets_js_1 = require("./builtin-pi-assets.cjs");
|
|
44
45
|
const pi_rpc_client_js_1 = require("./pi-rpc-client.cjs");
|
|
45
46
|
const runtime_readiness_js_1 = require("./runtime-readiness.cjs");
|
|
46
47
|
exports.PI_THINKING_LEVELS = ["off", "minimal", "low", "medium", "high", "xhigh", "max"];
|
|
@@ -71,10 +72,23 @@ function findExactPiModel(reference, models) {
|
|
|
71
72
|
*/
|
|
72
73
|
const discoveryCache = new Map();
|
|
73
74
|
/** Discover only through Pi's structured RPC protocol; no table parsing or static fallback. */
|
|
75
|
+
function piCatalogChildEnv(options) {
|
|
76
|
+
const mergedEnv = {
|
|
77
|
+
...process.env,
|
|
78
|
+
...options.env,
|
|
79
|
+
...(options.agentDir ? { PI_CODING_AGENT_DIR: path.resolve(options.agentDir) } : {}),
|
|
80
|
+
NO_COLOR: "1",
|
|
81
|
+
};
|
|
82
|
+
return (0, builtin_pi_assets_js_1.applyPiPackageDirForChild)(mergedEnv, {
|
|
83
|
+
distribution: (0, builtin_pi_assets_js_1.catalogPiChildDistribution)(options.commandArgs),
|
|
84
|
+
explicitPackageDir: options.packageDir,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
74
87
|
async function discoverPiModelCatalog(options) {
|
|
75
88
|
if (!options.spawn) {
|
|
76
89
|
const command = options.command ?? options.env?.LARKIN_PI_COMMAND ?? process.env.LARKIN_PI_COMMAND ?? "pi";
|
|
77
|
-
const
|
|
90
|
+
const childEnv = piCatalogChildEnv(options);
|
|
91
|
+
const key = `${command}|${(options.commandArgs ?? []).join("\0")}|${childEnv.PI_CODING_AGENT_DIR ?? ""}|${options.agentDir ?? ""}|${childEnv.PI_PACKAGE_DIR ?? ""}|${options.packageDir ?? ""}`;
|
|
78
92
|
const cached = discoveryCache.get(key);
|
|
79
93
|
if (cached)
|
|
80
94
|
return cached;
|
|
@@ -93,7 +107,7 @@ async function discoverPiModelCatalogUncached(options) {
|
|
|
93
107
|
const command = options.command ?? options.env?.LARKIN_PI_COMMAND ?? process.env.LARKIN_PI_COMMAND ?? "pi";
|
|
94
108
|
const child = spawn(command, [...(options.commandArgs ?? []), "--mode", "rpc", "--no-session"], {
|
|
95
109
|
cwd: options.cwd,
|
|
96
|
-
env:
|
|
110
|
+
env: piCatalogChildEnv(options),
|
|
97
111
|
stdio: ["pipe", "pipe", "pipe"],
|
|
98
112
|
});
|
|
99
113
|
const client = new pi_rpc_client_js_1.PiRpcClient(child, { requestTimeoutMs: options.timeoutMs ?? 10_000 });
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { spawn as nodeSpawn } from "node:child_process";
|
|
2
2
|
import * as path from "node:path";
|
|
3
|
+
import { applyPiPackageDirForChild, catalogPiChildDistribution } from "./builtin-pi-assets.mjs";
|
|
3
4
|
import { PiRpcClient } from "./pi-rpc-client.mjs";
|
|
4
5
|
import { classifyRuntimePrerequisite, RuntimePrerequisiteError } from "./runtime-readiness.mjs";
|
|
5
6
|
export const PI_THINKING_LEVELS = ["off", "minimal", "low", "medium", "high", "xhigh", "max"];
|
|
@@ -30,10 +31,23 @@ export function findExactPiModel(reference, models) {
|
|
|
30
31
|
*/
|
|
31
32
|
const discoveryCache = new Map();
|
|
32
33
|
/** Discover only through Pi's structured RPC protocol; no table parsing or static fallback. */
|
|
34
|
+
function piCatalogChildEnv(options) {
|
|
35
|
+
const mergedEnv = {
|
|
36
|
+
...process.env,
|
|
37
|
+
...options.env,
|
|
38
|
+
...(options.agentDir ? { PI_CODING_AGENT_DIR: path.resolve(options.agentDir) } : {}),
|
|
39
|
+
NO_COLOR: "1",
|
|
40
|
+
};
|
|
41
|
+
return applyPiPackageDirForChild(mergedEnv, {
|
|
42
|
+
distribution: catalogPiChildDistribution(options.commandArgs),
|
|
43
|
+
explicitPackageDir: options.packageDir,
|
|
44
|
+
});
|
|
45
|
+
}
|
|
33
46
|
export async function discoverPiModelCatalog(options) {
|
|
34
47
|
if (!options.spawn) {
|
|
35
48
|
const command = options.command ?? options.env?.LARKIN_PI_COMMAND ?? process.env.LARKIN_PI_COMMAND ?? "pi";
|
|
36
|
-
const
|
|
49
|
+
const childEnv = piCatalogChildEnv(options);
|
|
50
|
+
const key = `${command}|${(options.commandArgs ?? []).join("\0")}|${childEnv.PI_CODING_AGENT_DIR ?? ""}|${options.agentDir ?? ""}|${childEnv.PI_PACKAGE_DIR ?? ""}|${options.packageDir ?? ""}`;
|
|
37
51
|
const cached = discoveryCache.get(key);
|
|
38
52
|
if (cached)
|
|
39
53
|
return cached;
|
|
@@ -52,7 +66,7 @@ async function discoverPiModelCatalogUncached(options) {
|
|
|
52
66
|
const command = options.command ?? options.env?.LARKIN_PI_COMMAND ?? process.env.LARKIN_PI_COMMAND ?? "pi";
|
|
53
67
|
const child = spawn(command, [...(options.commandArgs ?? []), "--mode", "rpc", "--no-session"], {
|
|
54
68
|
cwd: options.cwd,
|
|
55
|
-
env:
|
|
69
|
+
env: piCatalogChildEnv(options),
|
|
56
70
|
stdio: ["pipe", "pipe", "pipe"],
|
|
57
71
|
});
|
|
58
72
|
const client = new PiRpcClient(child, { requestTimeoutMs: options.timeoutMs ?? 10_000 });
|
|
@@ -49,6 +49,7 @@ const os = __importStar(require("node:os"));
|
|
|
49
49
|
const path = __importStar(require("node:path"));
|
|
50
50
|
const node_child_process_1 = require("node:child_process");
|
|
51
51
|
const process_inspect_cjs_1 = __importDefault(require("../platform/process-inspect.cjs"));
|
|
52
|
+
const builtin_pi_assets_js_1 = require("./builtin-pi-assets.cjs");
|
|
52
53
|
const runtime_readiness_js_1 = require("./runtime-readiness.cjs");
|
|
53
54
|
const pi_compaction_recovery_js_1 = require("./pi-compaction-recovery.cjs");
|
|
54
55
|
const pi_provider_config_js_1 = require("./pi-provider-config.cjs");
|
|
@@ -61,6 +62,60 @@ const EXECUTABLE_LIMIT = 256 * 1024 * 1024;
|
|
|
61
62
|
const SOURCE_MODES = new Set([0o600, 0o644, 0o640]);
|
|
62
63
|
const TARGET_LOCK_SUFFIX = ".larkin-pi-import.lock";
|
|
63
64
|
function hash(bytes) { return crypto.createHash("sha256").update(bytes).digest("hex"); }
|
|
65
|
+
function isPiOrLarkinEnvKey(key) {
|
|
66
|
+
return key === "PATH" || key.startsWith("PI_") || key.startsWith("LARKIN_");
|
|
67
|
+
}
|
|
68
|
+
function isolatePlannedReplayEnv(source) {
|
|
69
|
+
const next = {};
|
|
70
|
+
for (const [key, value] of Object.entries(process.env)) {
|
|
71
|
+
if (isPiOrLarkinEnvKey(key))
|
|
72
|
+
continue;
|
|
73
|
+
next[key] = value;
|
|
74
|
+
}
|
|
75
|
+
if (source.PATH)
|
|
76
|
+
next.PATH = source.PATH;
|
|
77
|
+
if (source.LARKIN_PI_COMMAND)
|
|
78
|
+
next.LARKIN_PI_COMMAND = source.LARKIN_PI_COMMAND;
|
|
79
|
+
if (source.PI_PACKAGE_DIR)
|
|
80
|
+
next.PI_PACKAGE_DIR = source.PI_PACKAGE_DIR;
|
|
81
|
+
return next;
|
|
82
|
+
}
|
|
83
|
+
function capturePackageRootProof(directory) {
|
|
84
|
+
const resolved = fs.realpathSync(directory);
|
|
85
|
+
const rootStat = fs.statSync(resolved);
|
|
86
|
+
const theme = path.join(resolved, "dist", "modes", "interactive", "theme", "dark.json");
|
|
87
|
+
let fd;
|
|
88
|
+
try {
|
|
89
|
+
fd = fs.openSync(theme, fs.constants.O_RDONLY);
|
|
90
|
+
const themeStat = fs.fstatSync(fd);
|
|
91
|
+
if (!themeStat.isFile())
|
|
92
|
+
throw new Error("external Pi package root changed; refusing migration");
|
|
93
|
+
const bytes = fs.readFileSync(fd);
|
|
94
|
+
return { dir: resolved, dev: rootStat.dev, ino: rootStat.ino, themeSha256: hash(bytes) };
|
|
95
|
+
}
|
|
96
|
+
finally {
|
|
97
|
+
if (fd !== undefined)
|
|
98
|
+
try {
|
|
99
|
+
fs.closeSync(fd);
|
|
100
|
+
}
|
|
101
|
+
catch { /* ignore */ }
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
function assertPackageRootProof(state) {
|
|
105
|
+
if (!state.sourcePackageDir)
|
|
106
|
+
return;
|
|
107
|
+
let current;
|
|
108
|
+
try {
|
|
109
|
+
current = capturePackageRootProof(state.sourcePackageDir);
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
throw new Error("external Pi package root changed; refusing migration");
|
|
113
|
+
}
|
|
114
|
+
if (current.dir !== state.sourcePackageDir || current.dev !== state.sourcePackageDev
|
|
115
|
+
|| current.ino !== state.sourcePackageIno || current.themeSha256 !== state.sourcePackageThemeSha256) {
|
|
116
|
+
throw new Error("external Pi package root changed; refusing migration");
|
|
117
|
+
}
|
|
118
|
+
}
|
|
64
119
|
function targetLockFile(state) { return path.join(path.dirname(state.targetDir), `${state.agentId}${TARGET_LOCK_SUFFIX}`); }
|
|
65
120
|
function acquireTargetLock(state) {
|
|
66
121
|
const file = targetLockFile(state);
|
|
@@ -314,13 +369,20 @@ function targetDirectory(configDir, agentId) {
|
|
|
314
369
|
throw new Error("Pi Agent ID 格式无效");
|
|
315
370
|
return path.join(path.resolve(configDir), "providers", "pi", agentId);
|
|
316
371
|
}
|
|
317
|
-
function sourceVersion(env, cwd) {
|
|
372
|
+
function sourceVersion(env, cwd, alreadySanitized = false) {
|
|
318
373
|
const command = String(env.LARKIN_PI_COMMAND || "pi");
|
|
319
374
|
const executable = (0, runtime_readiness_js_1.resolveRuntimeExecutable)(command, env);
|
|
320
375
|
if (!executable)
|
|
321
376
|
throw new Error("外部 Pi 0.84.2 profile import requires an installed Pi executable");
|
|
322
377
|
const executableState = readExecutable(executable, "external Pi executable");
|
|
323
|
-
const
|
|
378
|
+
const probeEnv = alreadySanitized ? env : (0, builtin_pi_assets_js_1.applyPiPackageDirForChild)(env, { distribution: "external" });
|
|
379
|
+
const result = (0, node_child_process_1.spawnSync)(executableState.path, ["--version"], {
|
|
380
|
+
cwd,
|
|
381
|
+
env: probeEnv,
|
|
382
|
+
encoding: "utf8",
|
|
383
|
+
timeout: 5_000,
|
|
384
|
+
maxBuffer: 64 * 1024,
|
|
385
|
+
});
|
|
324
386
|
if (result.status !== 0)
|
|
325
387
|
throw new Error("external Pi version probe failed");
|
|
326
388
|
try {
|
|
@@ -366,10 +428,11 @@ function targetPrior(directory) {
|
|
|
366
428
|
return { stat, entries, files };
|
|
367
429
|
}
|
|
368
430
|
function preparePiProfileMigration(env, configDir, agentId, distribution = "builtin") {
|
|
369
|
-
const
|
|
431
|
+
const sanitized = (0, builtin_pi_assets_js_1.applyPiPackageDirForChild)({ ...env }, { distribution: "external" });
|
|
432
|
+
const sourceDir = sourceDirectory(sanitized);
|
|
370
433
|
verifySourceProfile(sourceDir);
|
|
371
434
|
const sourceDirectoryState = assertDirectory(sourceDir, "external Pi profile");
|
|
372
|
-
const sourceExecutable = sourceVersion(
|
|
435
|
+
const sourceExecutable = sourceVersion(sanitized, sourceDir, true);
|
|
373
436
|
const sourceFiles = Object.fromEntries(FILES.map((name) => {
|
|
374
437
|
const file = readRegular(path.join(sourceDir, name), `external Pi ${name}`, false);
|
|
375
438
|
if (!file)
|
|
@@ -391,14 +454,28 @@ function preparePiProfileMigration(env, configDir, agentId, distribution = "buil
|
|
|
391
454
|
const afterFiles = Object.fromEntries(FILES.map((name) => [name, safeState({ bytes: imported[name], mode: 0o600 }, false)]));
|
|
392
455
|
return {
|
|
393
456
|
state: { version: 1, agentId, sourceDir, sourceDirMode: sourceDirectoryState.mode & 0o777,
|
|
394
|
-
sourceCommand: String(
|
|
457
|
+
sourceCommand: String(sanitized.LARKIN_PI_COMMAND || "pi"), sourceExecutable,
|
|
395
458
|
sourceFiles: Object.fromEntries(FILES.map((name) => [name, safeState(sourceFiles[name], false)])),
|
|
396
459
|
targetDir, targetDirExisted: Boolean(target.stat), targetDirMode: target.stat ? target.stat.mode & 0o777 : 0o700,
|
|
397
460
|
...(target.stat ? { targetDirDevice: target.stat.dev, targetDirInode: target.stat.ino } : {}), targetEntries: target.entries,
|
|
398
|
-
priorFiles: target.files, afterFiles
|
|
461
|
+
priorFiles: target.files, afterFiles,
|
|
462
|
+
...(sanitized.PATH ? { sourcePath: sanitized.PATH } : {}),
|
|
463
|
+
...(sanitized.PI_PACKAGE_DIR ? (() => {
|
|
464
|
+
const proof = capturePackageRootProof(sanitized.PI_PACKAGE_DIR);
|
|
465
|
+
return {
|
|
466
|
+
sourcePackageDir: proof.dir,
|
|
467
|
+
sourcePackageDev: proof.dev,
|
|
468
|
+
sourcePackageIno: proof.ino,
|
|
469
|
+
sourcePackageThemeSha256: proof.themeSha256,
|
|
470
|
+
};
|
|
471
|
+
})() : {}) },
|
|
399
472
|
sourceBytes: { "auth.json": sourceFiles["auth.json"].bytes, "models.json": sourceFiles["models.json"].bytes,
|
|
400
473
|
"settings.json": imported["settings.json"] },
|
|
401
|
-
sourceEnvironment: {
|
|
474
|
+
sourceEnvironment: {
|
|
475
|
+
PATH: sanitized.PATH,
|
|
476
|
+
LARKIN_PI_COMMAND: String(sanitized.LARKIN_PI_COMMAND || "pi"),
|
|
477
|
+
...(sanitized.PI_PACKAGE_DIR ? { PI_PACKAGE_DIR: capturePackageRootProof(sanitized.PI_PACKAGE_DIR).dir } : {}),
|
|
478
|
+
},
|
|
402
479
|
};
|
|
403
480
|
}
|
|
404
481
|
function validatePiProfileMigrationState(value) {
|
|
@@ -439,12 +516,26 @@ function validatePiProfileMigrationState(value) {
|
|
|
439
516
|
}
|
|
440
517
|
}
|
|
441
518
|
}
|
|
519
|
+
function replayExternalPiPackageEnv(env) {
|
|
520
|
+
const next = { ...env };
|
|
521
|
+
const planned = env.PI_PACKAGE_DIR;
|
|
522
|
+
if (!planned) {
|
|
523
|
+
delete next.PI_PACKAGE_DIR;
|
|
524
|
+
return next;
|
|
525
|
+
}
|
|
526
|
+
const resolved = (0, builtin_pi_assets_js_1.applyPiPackageDirForChild)({ PI_PACKAGE_DIR: planned }, { distribution: "external", explicitPackageDir: planned }).PI_PACKAGE_DIR;
|
|
527
|
+
if (!resolved)
|
|
528
|
+
throw new Error("external Pi package root changed; refusing migration");
|
|
529
|
+
next.PI_PACKAGE_DIR = resolved;
|
|
530
|
+
return next;
|
|
531
|
+
}
|
|
442
532
|
function assertSourceUnchanged(state, resolutionEnvironment = process.env, verifyResolution = true) {
|
|
443
533
|
const directory = assertDirectory(state.sourceDir, "external Pi profile");
|
|
444
534
|
if ((directory.mode & 0o777) !== state.sourceDirMode)
|
|
445
535
|
throw new Error("external Pi profile changed; refusing migration");
|
|
446
536
|
verifySourceProfile(state.sourceDir);
|
|
447
|
-
|
|
537
|
+
assertPackageRootProof(state);
|
|
538
|
+
const probeEnvironment = replayExternalPiPackageEnv(resolutionEnvironment);
|
|
448
539
|
const resolved = verifyResolution ? (0, runtime_readiness_js_1.resolveRuntimeExecutable)(state.sourceCommand, probeEnvironment) : state.sourceExecutable.path;
|
|
449
540
|
if (!resolved)
|
|
450
541
|
throw new Error("external Pi executable changed; refusing migration");
|
|
@@ -453,7 +544,13 @@ function assertSourceUnchanged(state, resolutionEnvironment = process.env, verif
|
|
|
453
544
|
|| executable.bytes !== state.sourceExecutable.bytes || executable.mode !== state.sourceExecutable.mode) {
|
|
454
545
|
throw new Error("external Pi executable changed; refusing migration");
|
|
455
546
|
}
|
|
456
|
-
const result = (0, node_child_process_1.spawnSync)(executable.path, ["--version"], {
|
|
547
|
+
const result = (0, node_child_process_1.spawnSync)(executable.path, ["--version"], {
|
|
548
|
+
cwd: state.sourceDir,
|
|
549
|
+
env: probeEnvironment,
|
|
550
|
+
encoding: "utf8",
|
|
551
|
+
timeout: 5_000,
|
|
552
|
+
maxBuffer: 64 * 1024,
|
|
553
|
+
});
|
|
457
554
|
if (result.status !== 0)
|
|
458
555
|
throw new Error("external Pi version probe failed");
|
|
459
556
|
try {
|
|
@@ -565,9 +662,8 @@ function restoreFile(file, prior) {
|
|
|
565
662
|
fs.chmodSync(file, prior.mode);
|
|
566
663
|
}
|
|
567
664
|
function applyPiProfileMigration(plan) {
|
|
568
|
-
const
|
|
569
|
-
|
|
570
|
-
assertSourceUnchanged(plan.state, sameEnvironment ? process.env : plan.sourceEnvironment);
|
|
665
|
+
const replayEnv = isolatePlannedReplayEnv(plan.sourceEnvironment);
|
|
666
|
+
assertSourceUnchanged(plan.state, replayEnv);
|
|
571
667
|
const parent = path.dirname(plan.state.targetDir);
|
|
572
668
|
assertNoSymlinkAncestors(parent);
|
|
573
669
|
fs.mkdirSync(parent, { recursive: true, mode: 0o700 });
|
|
@@ -613,7 +709,11 @@ function fsyncDirectory(directory) {
|
|
|
613
709
|
}
|
|
614
710
|
function rollbackPiProfileMigration(state) {
|
|
615
711
|
validatePiProfileMigrationState(state);
|
|
616
|
-
assertSourceUnchanged(state,
|
|
712
|
+
assertSourceUnchanged(state, isolatePlannedReplayEnv({
|
|
713
|
+
PATH: state.sourcePath,
|
|
714
|
+
LARKIN_PI_COMMAND: state.sourceCommand,
|
|
715
|
+
...(state.sourcePackageDir ? { PI_PACKAGE_DIR: state.sourcePackageDir } : {}),
|
|
716
|
+
}), false);
|
|
617
717
|
const target = assertDirectory(state.targetDir, "Pi provider target", false);
|
|
618
718
|
if (!target) {
|
|
619
719
|
if (state.targetDirExisted)
|