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.
@@ -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)