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.
@@ -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 key = `${command}|${(options.commandArgs ?? []).join("\0")}|${options.env?.PI_CODING_AGENT_DIR ?? ""}|${options.agentDir ?? ""}`;
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: { ...process.env, ...options.env, ...(options.agentDir ? { PI_CODING_AGENT_DIR: path.resolve(options.agentDir) } : {}), NO_COLOR: "1" },
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 key = `${command}|${(options.commandArgs ?? []).join("\0")}|${options.env?.PI_CODING_AGENT_DIR ?? ""}|${options.agentDir ?? ""}`;
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: { ...process.env, ...options.env, ...(options.agentDir ? { PI_CODING_AGENT_DIR: path.resolve(options.agentDir) } : {}), NO_COLOR: "1" },
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 result = (0, node_child_process_1.spawnSync)(executableState.path, ["--version"], { cwd, env, encoding: "utf8", timeout: 5_000, maxBuffer: 64 * 1024 });
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 sourceDir = sourceDirectory(env);
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(env, sourceDir);
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(env.LARKIN_PI_COMMAND || "pi"), sourceExecutable,
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: { PATH: env.PATH, LARKIN_PI_COMMAND: env.LARKIN_PI_COMMAND },
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
- const probeEnvironment = { ...process.env, ...resolutionEnvironment };
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"], { cwd: state.sourceDir, env: probeEnvironment, encoding: "utf8", timeout: 5_000, maxBuffer: 64 * 1024 });
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 sameEnvironment = process.env.PATH === plan.sourceEnvironment.PATH
569
- && process.env.LARKIN_PI_COMMAND === plan.sourceEnvironment.LARKIN_PI_COMMAND;
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, process.env, false);
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)