dsh-lark-bot 0.19.5 → 0.19.6

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/README.md CHANGED
@@ -354,9 +354,16 @@ npx dsh-lark-bot@latest upgrade --profile dsh-lark --yes
354
354
  - `--no-guardian`:跳过守护升级;
355
355
  - **runtime profile 一致性修复**:升级后自动把 `dsh-lark-sdk` / `dsh-lark-acp` 的
356
356
  own-package 链接重指到新版本,并当场强制刷新 runtime profile 及被链接主插件中版本陈旧或物理
357
- 内容损坏的 SDK server / ACP 依赖;
358
- - **受管服务安全重载**:更新 worker 的临时 callback URL/token npx cache PATH 不会写入持久
359
- service env;只有飞书通道、callback server Guardian 心跳均已就绪后才确认更新成功。
357
+ 内容损坏的 SDK server / ACP 依赖;修复目标以 dsh profile 中重新读取并验证的安装包为准,
358
+ 不会链接到临时 npx worker。npm/npx 扁平依赖按 Node 的真实模块解析结果校验;pnpm 安装则先
359
+ 解引用 profile 中的包链接,再从 `.pnpm` 物理包目录解析其依赖,避免误判缺失并强制重装;
360
+ - **托管环境 pnpm 一致性**:执行 `dsh plugin add` 前读取既有 profile 的
361
+ `node_modules/.modules.yaml`,把其中记录的精确 pnpm 版本同步到 profile `package.json`,避免
362
+ Corepack 因 source-managed dsh 的工作目录选择另一 pnpm 主版本并触发
363
+ `ERR_PNPM_UNEXPECTED_STORE`;全新 profile 不会被提前创建 manifest;
364
+ - **受管服务安全重载**:更新 worker 的临时 callback URL/token 与 npx/npm package-bin PATH 不会写入持久
365
+ service env 或 Guardian unit;Guardian 仅保留 Node 所在目录及稳定的用户/系统 PATH。只有飞书通道、
366
+ callback server 和 Guardian 心跳均已就绪后才确认更新成功。
360
367
 
361
368
  无需交互确认时加 `--yes`(非交互环境不带 `--yes` 会安全中止)。其余方式:
362
369
 
package/README_EN.md CHANGED
@@ -94,7 +94,7 @@ question-card buttons. If an existing app was created by an older flow, enable c
94
94
  Console → Events & Callbacks → Callback Configuration and publish the app again. Without it, messages still work
95
95
  while card clicks never reach the bot.
96
96
 
97
- `setup` automatically: locates your local dsh → pre-approves pnpm's build policy (protobufjs) → runs the standard `dsh plugin --profile dsh-lark add dsh-lark-bot@<version>` (pinned to the running package) → installs the safety-net guardian system service. One command installs everything.
97
+ `setup` automatically: locates your local dsh → pre-approves pnpm's build policy (protobufjs; an existing profile is also pinned to the exact pnpm release recorded by its install metadata, preventing Corepack/store-major conflicts) → runs the standard `dsh plugin --profile dsh-lark add dsh-lark-bot@<version>` (pinned to the running package) → installs the safety-net guardian system service. A fresh profile is still initialized by dsh. One command installs everything.
98
98
 
99
99
  > **No public IP / domain / server / tunneling required** (Feishu outbound WebSocket long connection); works on Linux / macOS / Windows.
100
100
  > With an existing PersonalAgent app you can skip the QR step (see Configuration): `DSH_LARK_APP_ID=cli_xxx DSH_LARK_APP_SECRET=<secret> DSH_LARK_TENANT=feishu dsh --profile dsh-lark`
@@ -358,12 +358,16 @@ npx dsh-lark-bot@latest upgrade --profile dsh-lark --yes
358
358
  - `--rollback`: reinstall the version recorded before the last upgrade (`~/.dsh-lark/upgrade-state.json`);
359
359
  - `--force`: reinstall the running version when npm is unreachable (offline);
360
360
  - `--no-guardian`: skip the guardian upgrade;
361
- - **Runtime-profile consistency repair**: after upgrading, the own-package links of `dsh-lark-sdk` / `dsh-lark-acp` are re-pointed and stale SDK-server / ACP dependencies are idempotently reinstalled immediately.
361
+ - **Runtime-profile consistency repair**: after `dsh plugin add`, the upgrader re-reads and verifies the exact target installed in the dsh profile, then re-points `dsh-lark-sdk` / `dsh-lark-acp` only to that stable package root—never to the transient npx worker—and refreshes stale SDK-server / ACP dependencies.
362
+ - **npm/npx/pnpm layout support**: npm/npx-flat dependencies use Node's actual module resolution; pnpm package links are resolved to their physical `.pnpm` target before dependency validation, avoiding false corruption reports and unnecessary forced installs.
363
+ - **Corepack-managed dsh compatibility**: before installing into an existing profile, the exact pnpm release from `node_modules/.modules.yaml` is persisted to `package.json#packageManager`, preventing a source-managed dsh working directory from selecting an incompatible pnpm store major.
362
364
 
363
365
  Pass `--yes` to skip the interactive confirmation (non-interactive runs fail closed without it). Alternatives:
364
366
 
365
367
  - Plugin: re-run `setup` (or `dsh plugin --profile <name> add dsh-lark-bot`) to pull the latest npm release.
366
368
  - Safety-net guardian: installed / upgraded together with `upgrade` / `setup` (idempotent), or standalone via `dsh-lark-bot guardian install`.
369
+ - Managed reloads never persist the update worker's npx/npm package-bin paths into the service environment or
370
+ Guardian unit. Guardian keeps only the Node directory and stable user/system PATH entries.
367
371
  - CLI tool (optional): `npm i -g dsh-lark-bot@latest`; not needed when using `npx`.
368
372
  - Restart the profile after upgrading (when not using `--restart`): `dsh --profile dsh-lark`.
369
373
 
package/dist/cli.js CHANGED
@@ -282,8 +282,7 @@ import { join as join7 } from "path";
282
282
  function acpProfileRoot(home, profile, env) {
283
283
  return join7(resolveDshHome(home, env), "profiles", profile);
284
284
  }
285
- function packageJsonFor2(profile) {
286
- const own = ownPackageInfo();
285
+ function packageJsonFor2(profile, own = ownPackageInfo()) {
287
286
  return `${JSON.stringify(
288
287
  {
289
288
  name: `dsh-profile-${profile}`,
@@ -372,12 +371,11 @@ function acpPatchYaml(provider, model) {
372
371
  ""
373
372
  ].join("\n");
374
373
  }
375
- function isAcpProfileReady(profileRoot) {
376
- const own = ownPackageInfo();
374
+ function isAcpProfileReady(profileRoot, own = ownPackageInfo()) {
377
375
  return existsSync4(join7(profileRoot, "package.json")) && existsSync4(join7(profileRoot, "cordis.yml")) && existsSync4(join7(profileRoot, "cordis.patch.yml")) && profilePackageMatches(profileRoot, ACP_PACKAGE, ACP_VERSION) && ownPackageLinked2(profileRoot, own);
378
376
  }
379
- function isAcpManagedProfileCurrent(profileRoot, provider, model) {
380
- if (!isAcpProfileReady(profileRoot)) return false;
377
+ function isAcpManagedProfileCurrent(profileRoot, provider, model, own = ownPackageInfo()) {
378
+ if (!isAcpProfileReady(profileRoot, own)) return false;
381
379
  try {
382
380
  return readFileSync4(join7(profileRoot, "cordis.patch.yml"), "utf8") === acpPatchYaml(provider, model);
383
381
  } catch {
@@ -423,10 +421,11 @@ ${tail}`));
423
421
  }
424
422
  async function ensureAcpProfile(options) {
425
423
  const profile = options.profile ?? DEFAULT_ACP_PROFILE;
424
+ const own = options.ownPackage ?? ownPackageInfo();
426
425
  const root = acpProfileRoot(options.home, profile, options.env);
427
426
  const provider = options.provider?.trim();
428
427
  const model = options.model?.trim();
429
- const ready = isAcpProfileReady(root);
428
+ const ready = isAcpProfileReady(root, own);
430
429
  try {
431
430
  if (!provider || !model) {
432
431
  throw new Error(
@@ -434,14 +433,14 @@ async function ensureAcpProfile(options) {
434
433
  );
435
434
  }
436
435
  await mkdir4(root, { recursive: true });
437
- await writeFile4(join7(root, "package.json"), packageJsonFor2(profile), "utf8");
436
+ await writeFile4(join7(root, "package.json"), packageJsonFor2(profile, own), "utf8");
438
437
  await writeFile4(join7(root, "cordis.yml"), "[]\n", "utf8");
439
438
  await writeFile4(join7(root, "cordis.patch.yml"), acpPatchYaml(provider, model), "utf8");
440
439
  if (!ready) {
441
440
  const install = options.install ?? runPnpmInstall2;
442
441
  await install(root, { force: true });
443
442
  }
444
- if (!isAcpProfileReady(root)) {
443
+ if (!isAcpProfileReady(root, own)) {
445
444
  return {
446
445
  ok: false,
447
446
  created: true,
@@ -2765,6 +2764,7 @@ init_bridge_persona();
2765
2764
  init_own_package();
2766
2765
  init_profile_package();
2767
2766
  import { spawn as spawn3 } from "cross-spawn";
2767
+ import { createRequire } from "module";
2768
2768
  import { existsSync as existsSync3, realpathSync as realpathSync2 } from "fs";
2769
2769
  import { readFileSync as readFileSync3 } from "fs";
2770
2770
  import { mkdir as mkdir3, writeFile as writeFile3 } from "fs/promises";
@@ -2900,7 +2900,24 @@ function isSdkManagedProfileCurrent(profileRoot, options, own = ownPackageInfo()
2900
2900
  return isSdkProfileReady(profileRoot, own) && readFileIfPresent(join6(profileRoot, "cordis.patch.yml")) === patchYamlFor(options);
2901
2901
  }
2902
2902
  function ownSdkServerDependencyReady(own) {
2903
- return profilePackageMatches(own.root, SDK_SERVER_PACKAGE, SDK_SERVER_VERSION);
2903
+ try {
2904
+ const physicalOwnRoot = realpathSync2(own.root);
2905
+ const entry = createRequire(join6(physicalOwnRoot, "package.json")).resolve(SDK_SERVER_PACKAGE);
2906
+ let current = dirname5(entry);
2907
+ for (let depth = 0; depth < 8; depth += 1) {
2908
+ try {
2909
+ const parsed = JSON.parse(readFileSync3(join6(current, "package.json"), "utf8"));
2910
+ if (parsed.name === SDK_SERVER_PACKAGE) return parsed.version === SDK_SERVER_VERSION;
2911
+ } catch {
2912
+ }
2913
+ const parent = dirname5(current);
2914
+ if (parent === current) return false;
2915
+ current = parent;
2916
+ }
2917
+ return false;
2918
+ } catch {
2919
+ return false;
2920
+ }
2904
2921
  }
2905
2922
  function findPnpmProjectRoot(start) {
2906
2923
  let current = start;
@@ -4141,6 +4158,20 @@ function systemdUnit(nodeBin, cliEntry, env = {}) {
4141
4158
  ""
4142
4159
  ].join("\n");
4143
4160
  }
4161
+ function stableGuardianServicePath(nodeBin, inheritedPath = process.env.PATH) {
4162
+ const entries = [dirname9(nodeBin), ...inheritedPath?.split(delimiter) ?? []];
4163
+ const seen = /* @__PURE__ */ new Set();
4164
+ return entries.filter((entry) => {
4165
+ if (!entry) return false;
4166
+ const normalized = entry.replaceAll("\\", "/");
4167
+ if (normalized.includes("/node_modules/.bin") || /\/_npx(?:\/|$)/.test(normalized) || /\/guardian\/update-worker\/npm-cache(?:\/|$)/.test(normalized)) {
4168
+ return false;
4169
+ }
4170
+ if (seen.has(entry)) return false;
4171
+ seen.add(entry);
4172
+ return true;
4173
+ }).join(delimiter);
4174
+ }
4144
4175
  function launchdPlist(nodeBin, cliEntry, label2 = `io.dsh-lark.${GUARDIAN_LABEL}`, logPath = "/tmp/dsh-lark-guardian.log") {
4145
4176
  return [
4146
4177
  '<?xml version="1.0" encoding="UTF-8"?>',
@@ -4203,7 +4234,7 @@ async function installGuardian(options) {
4203
4234
  await writeFile5(
4204
4235
  unitPath,
4205
4236
  systemdUnit(nodeBin, cliEntry, {
4206
- PATH: [dirname9(nodeBin), process.env.PATH].filter((value) => Boolean(value)).join(delimiter),
4237
+ PATH: stableGuardianServicePath(nodeBin),
4207
4238
  DSH_LARK_GUARDIAN_DISABLED: "0"
4208
4239
  }),
4209
4240
  "utf8"
@@ -18513,6 +18544,7 @@ import { spawn as spawn8 } from "child_process";
18513
18544
  import { mkdir as mkdir23, readFile as readFile32, writeFile as writeFile9 } from "fs/promises";
18514
18545
  import { homedir as homedir17 } from "os";
18515
18546
  import { join as join28 } from "path";
18547
+ import { parse } from "yaml";
18516
18548
  async function runSetup(options = {}) {
18517
18549
  const profile = options.profile ?? "dsh-lark";
18518
18550
  const own = ownPackageInfo();
@@ -18586,6 +18618,7 @@ function runDshPlugin(bin, profile, packageSpec, env = process.env) {
18586
18618
  });
18587
18619
  }
18588
18620
  async function approveBuilds(profileDir) {
18621
+ await preserveInstalledPnpmVersion(profileDir);
18589
18622
  const workspaceFile = join28(profileDir, "pnpm-workspace.yaml");
18590
18623
  let existing = "";
18591
18624
  try {
@@ -18610,6 +18643,32 @@ allowBuilds:
18610
18643
  }
18611
18644
  await writeFile9(workspaceFile, existing, "utf8");
18612
18645
  }
18646
+ async function preserveInstalledPnpmVersion(profileDir) {
18647
+ const packageFile = join28(profileDir, "package.json");
18648
+ const modulesFile = join28(profileDir, "node_modules", ".modules.yaml");
18649
+ let packageText;
18650
+ let modulesText;
18651
+ try {
18652
+ [packageText, modulesText] = await Promise.all([
18653
+ readFile32(packageFile, "utf8"),
18654
+ readFile32(modulesFile, "utf8")
18655
+ ]);
18656
+ } catch {
18657
+ return;
18658
+ }
18659
+ try {
18660
+ const profilePackage = JSON.parse(packageText);
18661
+ const modules = parse(modulesText);
18662
+ const packageManager = modules?.packageManager;
18663
+ if (typeof packageManager !== "string" || !/^pnpm@\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/.test(packageManager) || profilePackage.packageManager === packageManager) {
18664
+ return;
18665
+ }
18666
+ profilePackage.packageManager = packageManager;
18667
+ await writeFile9(packageFile, `${JSON.stringify(profilePackage, null, 2)}
18668
+ `, "utf8");
18669
+ } catch {
18670
+ }
18671
+ }
18613
18672
 
18614
18673
  // src/cli/commands/upgrade.ts
18615
18674
  init_own_package();
@@ -18768,7 +18827,7 @@ function existingAcpRoute(profileRoot) {
18768
18827
  }
18769
18828
  }
18770
18829
  async function repairRuntimeProfiles(options) {
18771
- const own = ownPackageInfo();
18830
+ const own = options.ownPackage ?? ownPackageInfo();
18772
18831
  const env = options.env ?? process.env;
18773
18832
  const relink = options.relinkFn ?? defaultRelink;
18774
18833
  const managedEnv = { ...env, DSH_HOME: options.dshHome };
@@ -18786,18 +18845,19 @@ async function repairRuntimeProfiles(options) {
18786
18845
  {
18787
18846
  profile: DEFAULT_SDK_PROFILE,
18788
18847
  root: sdkRoot,
18789
- isReady: isSdkManagedProfileCurrent,
18790
- ensure: () => ensureSdk({ home: options.dshHome, env: managedEnv })
18848
+ isReady: (root) => isSdkManagedProfileCurrent(root, void 0, own),
18849
+ ensure: () => ensureSdk({ home: options.dshHome, env: managedEnv, ownPackage: own })
18791
18850
  },
18792
18851
  {
18793
18852
  profile: DEFAULT_ACP_PROFILE,
18794
18853
  root: acpRoot,
18795
- isReady: (root) => isAcpManagedProfileCurrent(root, acpRoute.provider, acpRoute.model),
18854
+ isReady: (root) => isAcpManagedProfileCurrent(root, acpRoute.provider, acpRoute.model, own),
18796
18855
  ensure: () => ensureAcp({
18797
18856
  home: options.dshHome,
18798
18857
  env: managedEnv,
18799
18858
  provider: acpRoute.provider,
18800
- model: acpRoute.model
18859
+ model: acpRoute.model,
18860
+ ownPackage: own
18801
18861
  })
18802
18862
  }
18803
18863
  ];
@@ -19014,10 +19074,21 @@ ${changeLines.join("\n")}
19014
19074
  await (options.pluginSpawnFn ?? runDshPlugin)(bin, profile, target.spec);
19015
19075
  write(out, `\u2705 \u5305\u672C\u4F53\u5DF2\u66F4\u65B0\u5230 ${target.version}\u3002
19016
19076
  `);
19077
+ const installedTarget = await (options.readInstalledPackageFn ?? readInstalledPackage)(
19078
+ dshHome,
19079
+ profile,
19080
+ own.name
19081
+ );
19082
+ if (!installedTarget || installedTarget.name !== own.name || installedTarget.version !== target.version) {
19083
+ throw new Error(
19084
+ `dsh plugin add completed but ${own.name}@${target.version} is not installed in profile ${profile}`
19085
+ );
19086
+ }
19017
19087
  const repair = options.repairRuntimeFn ?? repairRuntimeProfiles;
19018
19088
  const runtimeStates = await repair({
19019
19089
  dshHome,
19020
19090
  env: process.env,
19091
+ ownPackage: installedTarget,
19021
19092
  provider: env.provider,
19022
19093
  model: env.model
19023
19094
  });
@@ -19133,7 +19204,7 @@ import { join as join32 } from "path";
19133
19204
  import {
19134
19205
  createLarkChannel as createLarkChannel3
19135
19206
  } from "@larksuite/channel";
19136
- import { parse } from "yaml";
19207
+ import { parse as parse2 } from "yaml";
19137
19208
  init_dsh_runtime();
19138
19209
 
19139
19210
  // src/guardian/control.ts
@@ -20382,7 +20453,7 @@ function safeScopeForAction(event) {
20382
20453
  function readDshDefaultSelection(home, env) {
20383
20454
  try {
20384
20455
  const file = join32(resolveDshHome(home, env), "settings.yaml");
20385
- const doc = parse(readFileSync6(file, "utf8"));
20456
+ const doc = parse2(readFileSync6(file, "utf8"));
20386
20457
  const value = doc?.["agent-default-model"];
20387
20458
  if (typeof value === "object" && value !== null && typeof value.provider === "string" && typeof value.model === "string") {
20388
20459
  const selection = value;