larkin 0.4.17 → 0.4.18

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
+ }
@@ -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)
@@ -4,6 +4,7 @@ import * as os from "node:os";
4
4
  import * as path from "node:path";
5
5
  import { spawnSync } from "node:child_process";
6
6
  import processInspect from "../platform/process-inspect.cjs";
7
+ import { applyPiPackageDirForChild } from "./builtin-pi-assets.mjs";
7
8
  import { resolveRuntimeExecutable } from "./runtime-readiness.mjs";
8
9
  import { mergeOwnedPiSettings, parsePiExecutableVersion } from "./pi-compaction-recovery.mjs";
9
10
  import { BUNDLED_PI_VERSION } from "./pi-provider-config.mjs";
@@ -16,6 +17,60 @@ const EXECUTABLE_LIMIT = 256 * 1024 * 1024;
16
17
  const SOURCE_MODES = new Set([0o600, 0o644, 0o640]);
17
18
  const TARGET_LOCK_SUFFIX = ".larkin-pi-import.lock";
18
19
  function hash(bytes) { return crypto.createHash("sha256").update(bytes).digest("hex"); }
20
+ function isPiOrLarkinEnvKey(key) {
21
+ return key === "PATH" || key.startsWith("PI_") || key.startsWith("LARKIN_");
22
+ }
23
+ function isolatePlannedReplayEnv(source) {
24
+ const next = {};
25
+ for (const [key, value] of Object.entries(process.env)) {
26
+ if (isPiOrLarkinEnvKey(key))
27
+ continue;
28
+ next[key] = value;
29
+ }
30
+ if (source.PATH)
31
+ next.PATH = source.PATH;
32
+ if (source.LARKIN_PI_COMMAND)
33
+ next.LARKIN_PI_COMMAND = source.LARKIN_PI_COMMAND;
34
+ if (source.PI_PACKAGE_DIR)
35
+ next.PI_PACKAGE_DIR = source.PI_PACKAGE_DIR;
36
+ return next;
37
+ }
38
+ function capturePackageRootProof(directory) {
39
+ const resolved = fs.realpathSync(directory);
40
+ const rootStat = fs.statSync(resolved);
41
+ const theme = path.join(resolved, "dist", "modes", "interactive", "theme", "dark.json");
42
+ let fd;
43
+ try {
44
+ fd = fs.openSync(theme, fs.constants.O_RDONLY);
45
+ const themeStat = fs.fstatSync(fd);
46
+ if (!themeStat.isFile())
47
+ throw new Error("external Pi package root changed; refusing migration");
48
+ const bytes = fs.readFileSync(fd);
49
+ return { dir: resolved, dev: rootStat.dev, ino: rootStat.ino, themeSha256: hash(bytes) };
50
+ }
51
+ finally {
52
+ if (fd !== undefined)
53
+ try {
54
+ fs.closeSync(fd);
55
+ }
56
+ catch { /* ignore */ }
57
+ }
58
+ }
59
+ function assertPackageRootProof(state) {
60
+ if (!state.sourcePackageDir)
61
+ return;
62
+ let current;
63
+ try {
64
+ current = capturePackageRootProof(state.sourcePackageDir);
65
+ }
66
+ catch {
67
+ throw new Error("external Pi package root changed; refusing migration");
68
+ }
69
+ if (current.dir !== state.sourcePackageDir || current.dev !== state.sourcePackageDev
70
+ || current.ino !== state.sourcePackageIno || current.themeSha256 !== state.sourcePackageThemeSha256) {
71
+ throw new Error("external Pi package root changed; refusing migration");
72
+ }
73
+ }
19
74
  function targetLockFile(state) { return path.join(path.dirname(state.targetDir), `${state.agentId}${TARGET_LOCK_SUFFIX}`); }
20
75
  function acquireTargetLock(state) {
21
76
  const file = targetLockFile(state);
@@ -269,13 +324,20 @@ function targetDirectory(configDir, agentId) {
269
324
  throw new Error("Pi Agent ID 格式无效");
270
325
  return path.join(path.resolve(configDir), "providers", "pi", agentId);
271
326
  }
272
- function sourceVersion(env, cwd) {
327
+ function sourceVersion(env, cwd, alreadySanitized = false) {
273
328
  const command = String(env.LARKIN_PI_COMMAND || "pi");
274
329
  const executable = resolveRuntimeExecutable(command, env);
275
330
  if (!executable)
276
331
  throw new Error("外部 Pi 0.84.2 profile import requires an installed Pi executable");
277
332
  const executableState = readExecutable(executable, "external Pi executable");
278
- const result = spawnSync(executableState.path, ["--version"], { cwd, env, encoding: "utf8", timeout: 5_000, maxBuffer: 64 * 1024 });
333
+ const probeEnv = alreadySanitized ? env : applyPiPackageDirForChild(env, { distribution: "external" });
334
+ const result = spawnSync(executableState.path, ["--version"], {
335
+ cwd,
336
+ env: probeEnv,
337
+ encoding: "utf8",
338
+ timeout: 5_000,
339
+ maxBuffer: 64 * 1024,
340
+ });
279
341
  if (result.status !== 0)
280
342
  throw new Error("external Pi version probe failed");
281
343
  try {
@@ -321,10 +383,11 @@ function targetPrior(directory) {
321
383
  return { stat, entries, files };
322
384
  }
323
385
  export function preparePiProfileMigration(env, configDir, agentId, distribution = "builtin") {
324
- const sourceDir = sourceDirectory(env);
386
+ const sanitized = applyPiPackageDirForChild({ ...env }, { distribution: "external" });
387
+ const sourceDir = sourceDirectory(sanitized);
325
388
  verifySourceProfile(sourceDir);
326
389
  const sourceDirectoryState = assertDirectory(sourceDir, "external Pi profile");
327
- const sourceExecutable = sourceVersion(env, sourceDir);
390
+ const sourceExecutable = sourceVersion(sanitized, sourceDir, true);
328
391
  const sourceFiles = Object.fromEntries(FILES.map((name) => {
329
392
  const file = readRegular(path.join(sourceDir, name), `external Pi ${name}`, false);
330
393
  if (!file)
@@ -346,14 +409,28 @@ export function preparePiProfileMigration(env, configDir, agentId, distribution
346
409
  const afterFiles = Object.fromEntries(FILES.map((name) => [name, safeState({ bytes: imported[name], mode: 0o600 }, false)]));
347
410
  return {
348
411
  state: { version: 1, agentId, sourceDir, sourceDirMode: sourceDirectoryState.mode & 0o777,
349
- sourceCommand: String(env.LARKIN_PI_COMMAND || "pi"), sourceExecutable,
412
+ sourceCommand: String(sanitized.LARKIN_PI_COMMAND || "pi"), sourceExecutable,
350
413
  sourceFiles: Object.fromEntries(FILES.map((name) => [name, safeState(sourceFiles[name], false)])),
351
414
  targetDir, targetDirExisted: Boolean(target.stat), targetDirMode: target.stat ? target.stat.mode & 0o777 : 0o700,
352
415
  ...(target.stat ? { targetDirDevice: target.stat.dev, targetDirInode: target.stat.ino } : {}), targetEntries: target.entries,
353
- priorFiles: target.files, afterFiles },
416
+ priorFiles: target.files, afterFiles,
417
+ ...(sanitized.PATH ? { sourcePath: sanitized.PATH } : {}),
418
+ ...(sanitized.PI_PACKAGE_DIR ? (() => {
419
+ const proof = capturePackageRootProof(sanitized.PI_PACKAGE_DIR);
420
+ return {
421
+ sourcePackageDir: proof.dir,
422
+ sourcePackageDev: proof.dev,
423
+ sourcePackageIno: proof.ino,
424
+ sourcePackageThemeSha256: proof.themeSha256,
425
+ };
426
+ })() : {}) },
354
427
  sourceBytes: { "auth.json": sourceFiles["auth.json"].bytes, "models.json": sourceFiles["models.json"].bytes,
355
428
  "settings.json": imported["settings.json"] },
356
- sourceEnvironment: { PATH: env.PATH, LARKIN_PI_COMMAND: env.LARKIN_PI_COMMAND },
429
+ sourceEnvironment: {
430
+ PATH: sanitized.PATH,
431
+ LARKIN_PI_COMMAND: String(sanitized.LARKIN_PI_COMMAND || "pi"),
432
+ ...(sanitized.PI_PACKAGE_DIR ? { PI_PACKAGE_DIR: capturePackageRootProof(sanitized.PI_PACKAGE_DIR).dir } : {}),
433
+ },
357
434
  };
358
435
  }
359
436
  export function validatePiProfileMigrationState(value) {
@@ -394,12 +471,26 @@ export function validatePiProfileMigrationState(value) {
394
471
  }
395
472
  }
396
473
  }
474
+ function replayExternalPiPackageEnv(env) {
475
+ const next = { ...env };
476
+ const planned = env.PI_PACKAGE_DIR;
477
+ if (!planned) {
478
+ delete next.PI_PACKAGE_DIR;
479
+ return next;
480
+ }
481
+ const resolved = applyPiPackageDirForChild({ PI_PACKAGE_DIR: planned }, { distribution: "external", explicitPackageDir: planned }).PI_PACKAGE_DIR;
482
+ if (!resolved)
483
+ throw new Error("external Pi package root changed; refusing migration");
484
+ next.PI_PACKAGE_DIR = resolved;
485
+ return next;
486
+ }
397
487
  function assertSourceUnchanged(state, resolutionEnvironment = process.env, verifyResolution = true) {
398
488
  const directory = assertDirectory(state.sourceDir, "external Pi profile");
399
489
  if ((directory.mode & 0o777) !== state.sourceDirMode)
400
490
  throw new Error("external Pi profile changed; refusing migration");
401
491
  verifySourceProfile(state.sourceDir);
402
- const probeEnvironment = { ...process.env, ...resolutionEnvironment };
492
+ assertPackageRootProof(state);
493
+ const probeEnvironment = replayExternalPiPackageEnv(resolutionEnvironment);
403
494
  const resolved = verifyResolution ? resolveRuntimeExecutable(state.sourceCommand, probeEnvironment) : state.sourceExecutable.path;
404
495
  if (!resolved)
405
496
  throw new Error("external Pi executable changed; refusing migration");
@@ -408,7 +499,13 @@ function assertSourceUnchanged(state, resolutionEnvironment = process.env, verif
408
499
  || executable.bytes !== state.sourceExecutable.bytes || executable.mode !== state.sourceExecutable.mode) {
409
500
  throw new Error("external Pi executable changed; refusing migration");
410
501
  }
411
- const result = spawnSync(executable.path, ["--version"], { cwd: state.sourceDir, env: probeEnvironment, encoding: "utf8", timeout: 5_000, maxBuffer: 64 * 1024 });
502
+ const result = spawnSync(executable.path, ["--version"], {
503
+ cwd: state.sourceDir,
504
+ env: probeEnvironment,
505
+ encoding: "utf8",
506
+ timeout: 5_000,
507
+ maxBuffer: 64 * 1024,
508
+ });
412
509
  if (result.status !== 0)
413
510
  throw new Error("external Pi version probe failed");
414
511
  try {
@@ -520,9 +617,8 @@ function restoreFile(file, prior) {
520
617
  fs.chmodSync(file, prior.mode);
521
618
  }
522
619
  export function applyPiProfileMigration(plan) {
523
- const sameEnvironment = process.env.PATH === plan.sourceEnvironment.PATH
524
- && process.env.LARKIN_PI_COMMAND === plan.sourceEnvironment.LARKIN_PI_COMMAND;
525
- assertSourceUnchanged(plan.state, sameEnvironment ? process.env : plan.sourceEnvironment);
620
+ const replayEnv = isolatePlannedReplayEnv(plan.sourceEnvironment);
621
+ assertSourceUnchanged(plan.state, replayEnv);
526
622
  const parent = path.dirname(plan.state.targetDir);
527
623
  assertNoSymlinkAncestors(parent);
528
624
  fs.mkdirSync(parent, { recursive: true, mode: 0o700 });
@@ -568,7 +664,11 @@ function fsyncDirectory(directory) {
568
664
  }
569
665
  export function rollbackPiProfileMigration(state) {
570
666
  validatePiProfileMigrationState(state);
571
- assertSourceUnchanged(state, process.env, false);
667
+ assertSourceUnchanged(state, isolatePlannedReplayEnv({
668
+ PATH: state.sourcePath,
669
+ LARKIN_PI_COMMAND: state.sourceCommand,
670
+ ...(state.sourcePackageDir ? { PI_PACKAGE_DIR: state.sourcePackageDir } : {}),
671
+ }), false);
572
672
  const target = assertDirectory(state.targetDir, "Pi provider target", false);
573
673
  if (!target) {
574
674
  if (state.targetDirExisted)
@@ -3,6 +3,7 @@ import crypto from "node:crypto";
3
3
  import * as fs from "node:fs";
4
4
  import * as path from "node:path";
5
5
  import { createInterface } from "node:readline";
6
+ import { applyPiPackageDirForChild, piChildDistributionFromOverrides } from "./builtin-pi-assets.mjs";
6
7
  import { isPiThinkingLevel } from "./pi-model-catalog.mjs";
7
8
  import { PiRpcClient } from "./pi-rpc-client.mjs";
8
9
  import { internalCommandSpec } from "../app/internal-command.mjs";
@@ -967,23 +968,29 @@ async function createPiRpcBackend(input, dependencies, spawn, productionSpawn =
967
968
  ...(session.sessionFile ? ["--session", session.sessionFile] : []),
968
969
  ...(requestedModel ? ["--model", requestedModel] : []),
969
970
  ...(requestedEffort ? ["--thinking", requestedEffort] : [])];
970
- const builtin = mergedEnv.LARKIN_PI_DISTRIBUTION === "builtin";
971
+ const builtin = piChildDistributionFromOverrides(dependencies.env, input.env) === "builtin";
972
+ if (!builtin && mergedEnv.LARKIN_PI_DISTRIBUTION === "builtin")
973
+ delete mergedEnv.LARKIN_PI_DISTRIBUTION;
971
974
  const builtinSpec = builtin ? internalCommandSpec("pi-rpc", [], mergedEnv) : null;
972
975
  const command = builtinSpec?.command ?? dependencies.piCommand ?? dependencies.env?.LARKIN_PI_COMMAND ?? process.env.LARKIN_PI_COMMAND ?? "pi";
973
976
  const commandPrefix = builtinSpec?.args ?? dependencies.piCommandArgs ?? [];
974
977
  const commandArgs = [...commandPrefix, ...args];
978
+ mergedEnv.PI_CODING_AGENT_DIR = ownedPiDirectory;
979
+ if (builtin)
980
+ mergedEnv.PI_TELEMETRY = "0";
981
+ const childEnv = applyPiPackageDirForChild(mergedEnv, {
982
+ distribution: builtin ? "builtin" : "external",
983
+ explicitPackageDir: input.env?.PI_PACKAGE_DIR ?? dependencies.env?.PI_PACKAGE_DIR,
984
+ });
975
985
  const reportedVersion = productionSpawn && !builtin
976
986
  ? parsePiExecutableVersion(String(spawnSync(command, [...commandPrefix, "--version"], {
977
- cwd: input.workspaceDir, env: mergedEnv, encoding: "utf8", timeout: 5_000,
987
+ cwd: input.workspaceDir, env: childEnv, encoding: "utf8", timeout: 5_000,
978
988
  }).stdout || ""))
979
989
  : BUNDLED_PI_VERSION;
980
- mergedEnv.PI_CODING_AGENT_DIR = ownedPiDirectory;
981
- if (builtin)
982
- mergedEnv.PI_TELEMETRY = "0";
983
990
  const extensionArgs = (dependencies.resolvePiProcessExtensionArgs ?? resolvePiProcessExtensionArgs)({
984
991
  distribution: builtin ? "builtin" : "external",
985
992
  piCommand: command,
986
- env: mergedEnv,
993
+ env: childEnv,
987
994
  platform: process.platform,
988
995
  });
989
996
  commandArgs.push(...extensionArgs);
@@ -993,14 +1000,14 @@ async function createPiRpcBackend(input, dependencies, spawn, productionSpawn =
993
1000
  // requested model first, then write the matching 15% reserve before the
994
1001
  // real session starts; no prompt, provider request, package, or extension
995
1002
  // is loaded by the probe.
996
- probedModel = await discoverEffectivePiContextWindow(input, command, commandPrefix, requestedModel, mergedEnv, spawn, dependencies.piRpcClientOptions);
1003
+ probedModel = await discoverEffectivePiContextWindow(input, command, commandPrefix, requestedModel, childEnv, spawn, dependencies.piRpcClientOptions);
997
1004
  writeOwnedPiSettings(ownedPiDirectory, calculatePiCompactionSettings(probedModel.contextWindow));
998
1005
  }
999
1006
  const artifactEntriesBeforeSpawn = new Set(fs.readdirSync(ownedPiDirectory));
1000
1007
  const artifactSpawnBoundary = Date.now();
1001
1008
  const child = spawn(command, commandArgs, {
1002
1009
  cwd: input.workspaceDir,
1003
- env: mergedEnv,
1010
+ env: childEnv,
1004
1011
  stdio: ["pipe", "pipe", "pipe"],
1005
1012
  });
1006
1013
  traceProcessBoundary(mergedEnv, "pi-rpc:child-spawned", { configDir: mergedEnv.LARKIN_CONFIG_DIR, agentId: input.agentId, targetDir: ownedPiDirectory, childPid: child.pid ?? null });
@@ -1164,8 +1171,7 @@ export function createNativeRuntimeAdapter(id, dependencies = {}) {
1164
1171
  throw new RuntimePrerequisiteError(readiness);
1165
1172
  }
1166
1173
  if (id === "pi") {
1167
- const distribution = ({ ...globalThis.process.env, ...dependencies.env, ...input.env }).LARKIN_PI_DISTRIBUTION === "builtin"
1168
- ? "builtin" : "external";
1174
+ const distribution = piChildDistributionFromOverrides(dependencies.env, input.env);
1169
1175
  return new PiSession(await (dependencies.createPiSession
1170
1176
  ? dependencies.createPiSession(input)
1171
1177
  : createPiRpcBackend(input, { ...dependencies, piCommand: resolvedExecutable }, spawn, productionSpawn)), distribution);
@@ -44,6 +44,7 @@ exports.probeNativeRuntimeReadiness = probeNativeRuntimeReadiness;
44
44
  const node_fs_1 = __importDefault(require("node:fs"));
45
45
  const node_path_1 = __importDefault(require("node:path"));
46
46
  const node_child_process_1 = require("node:child_process");
47
+ const builtin_pi_assets_js_1 = require("./builtin-pi-assets.cjs");
47
48
  const pi_provider_config_js_1 = require("./pi-provider-config.cjs");
48
49
  const process_boundary_trace_js_1 = require("../platform/process-boundary-trace.cjs");
49
50
  function safeProviderLabel(value) {
@@ -131,8 +132,14 @@ function executableVersion(executable, env, commandArgs = []) {
131
132
  }
132
133
  /** Resolve and handshake through each runtime's structured native control protocol. */
133
134
  async function probeNativeRuntimeReadiness(options) {
134
- const env = { ...process.env, ...options.env };
135
- if (options.runtime === "pi" && env.LARKIN_PI_DISTRIBUTION === "builtin") {
135
+ let env = { ...process.env, ...options.env };
136
+ const piDistribution = options.runtime === "pi" ? (0, builtin_pi_assets_js_1.piChildDistributionFromOverrides)(options.env) : "external";
137
+ if (options.runtime === "pi" && piDistribution !== "builtin") {
138
+ if (env.LARKIN_PI_DISTRIBUTION === "builtin")
139
+ delete env.LARKIN_PI_DISTRIBUTION;
140
+ env = (0, builtin_pi_assets_js_1.applyPiPackageDirForChild)(env, { distribution: "external" });
141
+ }
142
+ if (options.runtime === "pi" && piDistribution === "builtin") {
136
143
  try {
137
144
  if (!options.agentId || !env.LARKIN_CONFIG_DIR)
138
145
  throw new Error("内置 Pi readiness 缺少 Agent/config identity");
@@ -1,6 +1,7 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import { spawnSync } from "node:child_process";
4
+ import { applyPiPackageDirForChild, piChildDistributionFromOverrides } from "./builtin-pi-assets.mjs";
4
5
  import { assertBuiltinPiAgentDirectory, BUNDLED_PI_VERSION, piAgentDirectory } from "./pi-provider-config.mjs";
5
6
  import { traceProcessBoundary } from "../platform/process-boundary-trace.mjs";
6
7
  function safeProviderLabel(value) {
@@ -87,8 +88,14 @@ function executableVersion(executable, env, commandArgs = []) {
87
88
  }
88
89
  /** Resolve and handshake through each runtime's structured native control protocol. */
89
90
  export async function probeNativeRuntimeReadiness(options) {
90
- const env = { ...process.env, ...options.env };
91
- if (options.runtime === "pi" && env.LARKIN_PI_DISTRIBUTION === "builtin") {
91
+ let env = { ...process.env, ...options.env };
92
+ const piDistribution = options.runtime === "pi" ? piChildDistributionFromOverrides(options.env) : "external";
93
+ if (options.runtime === "pi" && piDistribution !== "builtin") {
94
+ if (env.LARKIN_PI_DISTRIBUTION === "builtin")
95
+ delete env.LARKIN_PI_DISTRIBUTION;
96
+ env = applyPiPackageDirForChild(env, { distribution: "external" });
97
+ }
98
+ if (options.runtime === "pi" && piDistribution === "builtin") {
92
99
  try {
93
100
  if (!options.agentId || !env.LARKIN_CONFIG_DIR)
94
101
  throw new Error("内置 Pi readiness 缺少 Agent/config identity");
@@ -330,7 +330,7 @@ export async function main() {
330
330
  const catalog = await discoverPiModelCatalog({
331
331
  cwd: CFG_DIR,
332
332
  agentDir: piAgentDirectory(CFG_DIR, profile.appId),
333
- env: process.env,
333
+ env: { ...process.env, LARKIN_PI_DISTRIBUTION: "external" },
334
334
  });
335
335
  const effective = catalog.effectiveModel;
336
336
  const entry = effective ? catalog.models.find((model) => model.id === effective) : undefined;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "larkin",
3
- "version": "0.4.17",
3
+ "version": "0.4.18",
4
4
  "private": false,
5
5
  "larkinPackageRole": "npm-published",
6
6
  "files": [