dsh-lark-bot 0.19.5 → 0.19.7
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 +13 -4
- package/README_EN.md +10 -3
- package/dist/cli.js +198 -32
- package/dist/cli.js.map +1 -1
- package/dist/plugin.d.ts +10 -0
- package/dist/plugin.js +134 -22
- package/dist/plugin.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -162,7 +162,9 @@ Markdown、toast 与旧客户端降级路径同时显示中英文。agent 最终
|
|
|
162
162
|
|
|
163
163
|
飞书消息中的图片会按文件内容识别 PNG/JPEG/WebP/GIF 并补全安全扩展名;默认 SDK 会经 dsh
|
|
164
164
|
附件存储校验后发送原生 image block,而不是把路径当作图片。无法读取或模型不支持视觉时会明确
|
|
165
|
-
失败,agent
|
|
165
|
+
失败,agent 被要求不得用工作区内其他图片替代。选中的 `deepseek-official` 视觉模型会在每轮运行前
|
|
166
|
+
幂等补入 dsh runtime 实际消费的模型目录并声明 `text,image`,避免展示目录已识别视觉能力但 rc.8
|
|
167
|
+
runtime 仍按 text-only 拒绝。文本类文件会读取内容并注入任务上下文。
|
|
166
168
|
`/model` 卡片会把 dsh 默认模型并入可切换目录(即使 provider 的显式列表尚未包含它),
|
|
167
169
|
并用去除公共前缀后的短标签、每行最多两个按钮适配移动端。provider 名称、模型、输入模态和
|
|
168
170
|
推理档位从 models.dev 运行时目录发现(15 分钟内存缓存);网络失败时只使用 dsh settings 中的
|
|
@@ -354,9 +356,16 @@ npx dsh-lark-bot@latest upgrade --profile dsh-lark --yes
|
|
|
354
356
|
- `--no-guardian`:跳过守护升级;
|
|
355
357
|
- **runtime profile 一致性修复**:升级后自动把 `dsh-lark-sdk` / `dsh-lark-acp` 的
|
|
356
358
|
own-package 链接重指到新版本,并当场强制刷新 runtime profile 及被链接主插件中版本陈旧或物理
|
|
357
|
-
内容损坏的 SDK server / ACP
|
|
358
|
-
|
|
359
|
-
|
|
359
|
+
内容损坏的 SDK server / ACP 依赖;修复目标以 dsh profile 中重新读取并验证的安装包为准,
|
|
360
|
+
不会链接到临时 npx worker。npm/npx 扁平依赖按 Node 的真实模块解析结果校验;pnpm 安装则先
|
|
361
|
+
解引用 profile 中的包链接,再从 `.pnpm` 物理包目录解析其依赖,避免误判缺失并强制重装;
|
|
362
|
+
- **托管环境 pnpm 一致性**:执行 `dsh plugin add` 前读取既有 profile 的
|
|
363
|
+
`node_modules/.modules.yaml`,把其中记录的精确 pnpm 版本同步到 profile `package.json`,避免
|
|
364
|
+
Corepack 因 source-managed dsh 的工作目录选择另一 pnpm 主版本并触发
|
|
365
|
+
`ERR_PNPM_UNEXPECTED_STORE`;全新 profile 不会被提前创建 manifest;
|
|
366
|
+
- **受管服务安全重载**:更新 worker 的临时 callback URL/token 与 npx/npm package-bin PATH 不会写入持久
|
|
367
|
+
service env 或 Guardian unit;Guardian 仅保留 Node 所在目录及稳定的用户/系统 PATH。只有飞书通道、
|
|
368
|
+
callback server 和 Guardian 心跳均已就绪后才确认更新成功。
|
|
360
369
|
|
|
361
370
|
无需交互确认时加 `--yes`(非交互环境不带 `--yes` 会安全中止)。其余方式:
|
|
362
371
|
|
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`
|
|
@@ -165,7 +165,10 @@ to the cloud LLM.
|
|
|
165
165
|
Feishu images are detected by content as PNG/JPEG/WebP/GIF and receive a safe extension. The default
|
|
166
166
|
SDK validates them through dsh's attachment store and sends native image blocks instead of path text.
|
|
167
167
|
Unreadable or unsupported images fail explicitly, and the agent is instructed never to substitute another
|
|
168
|
-
workspace image.
|
|
168
|
+
workspace image. Before each run, the selected `deepseek-official` vision model is idempotently added to the
|
|
169
|
+
model catalogue consumed by the dsh runtime with `text,image` modalities. This prevents rc.8 from treating the
|
|
170
|
+
model as text-only even when the display catalogue already recognizes its vision capability. Text files are read
|
|
171
|
+
and injected into the task context.
|
|
169
172
|
The `/model` card merges the dsh default into its switchable catalogue even when a provider's explicit
|
|
170
173
|
list omits it, and uses compact distinguishing labels with at most two buttons per mobile row. Provider
|
|
171
174
|
names, models, input modalities, and reasoning-effort options are discovered from the models.dev runtime
|
|
@@ -358,12 +361,16 @@ npx dsh-lark-bot@latest upgrade --profile dsh-lark --yes
|
|
|
358
361
|
- `--rollback`: reinstall the version recorded before the last upgrade (`~/.dsh-lark/upgrade-state.json`);
|
|
359
362
|
- `--force`: reinstall the running version when npm is unreachable (offline);
|
|
360
363
|
- `--no-guardian`: skip the guardian upgrade;
|
|
361
|
-
- **Runtime-profile consistency repair**: after
|
|
364
|
+
- **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.
|
|
365
|
+
- **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.
|
|
366
|
+
- **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
367
|
|
|
363
368
|
Pass `--yes` to skip the interactive confirmation (non-interactive runs fail closed without it). Alternatives:
|
|
364
369
|
|
|
365
370
|
- Plugin: re-run `setup` (or `dsh plugin --profile <name> add dsh-lark-bot`) to pull the latest npm release.
|
|
366
371
|
- Safety-net guardian: installed / upgraded together with `upgrade` / `setup` (idempotent), or standalone via `dsh-lark-bot guardian install`.
|
|
372
|
+
- Managed reloads never persist the update worker's npx/npm package-bin paths into the service environment or
|
|
373
|
+
Guardian unit. Guardian keeps only the Node directory and stable user/system PATH entries.
|
|
367
374
|
- CLI tool (optional): `npm i -g dsh-lark-bot@latest`; not needed when using `npx`.
|
|
368
375
|
- Restart the profile after upgrading (when not using `--restart`): `dsh --profile dsh-lark`.
|
|
369
376
|
|
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,
|
|
@@ -1094,7 +1093,7 @@ import { homedir as homedir3 } from "os";
|
|
|
1094
1093
|
import { mkdir as mkdir2, readFile, rm, writeFile as writeFile2 } from "fs/promises";
|
|
1095
1094
|
import { homedir } from "os";
|
|
1096
1095
|
import { dirname as dirname3, join as join3 } from "path";
|
|
1097
|
-
import { Document, parseDocument } from "yaml";
|
|
1096
|
+
import { Document, isMap, isSeq, parseDocument } from "yaml";
|
|
1098
1097
|
|
|
1099
1098
|
// src/platform/atomic-write.ts
|
|
1100
1099
|
import { randomBytes } from "crypto";
|
|
@@ -1564,6 +1563,22 @@ function patchNode(document, path, current, next) {
|
|
|
1564
1563
|
}
|
|
1565
1564
|
if (!deepEqualJson(current, next)) document.setIn([...path], next);
|
|
1566
1565
|
}
|
|
1566
|
+
function patchRuntimeModelModalities(document, modelId, modalities) {
|
|
1567
|
+
const modelsPath = [DEEPSEEK_NAMESPACE, "models"];
|
|
1568
|
+
const modelsNode = document.getIn(modelsPath, true);
|
|
1569
|
+
if (!isSeq(modelsNode)) {
|
|
1570
|
+
document.setIn(modelsPath, [{ id: modelId, inputModalities: modalities }]);
|
|
1571
|
+
return;
|
|
1572
|
+
}
|
|
1573
|
+
const modelIndex = modelsNode.items.findIndex(
|
|
1574
|
+
(item) => isMap(item) && item.get("id") === modelId
|
|
1575
|
+
);
|
|
1576
|
+
if (modelIndex === -1) {
|
|
1577
|
+
modelsNode.add({ id: modelId, inputModalities: modalities });
|
|
1578
|
+
return;
|
|
1579
|
+
}
|
|
1580
|
+
document.setIn([...modelsPath, modelIndex, "inputModalities"], modalities);
|
|
1581
|
+
}
|
|
1567
1582
|
function parseYamlMap(text, filename) {
|
|
1568
1583
|
if (text === void 0 || text.trim().length === 0) return {};
|
|
1569
1584
|
const document = parseDocument(text, { prettyErrors: true });
|
|
@@ -1753,6 +1768,12 @@ var DshProviderManager = class {
|
|
|
1753
1768
|
if (!provider) return void 0;
|
|
1754
1769
|
return { provider: provider.id, model: selection };
|
|
1755
1770
|
}
|
|
1771
|
+
/** Resolve a model route and make its managed runtime catalog ready. */
|
|
1772
|
+
async resolveRuntimeModelRoute(selection) {
|
|
1773
|
+
const route = await this.resolveModelRoute(selection);
|
|
1774
|
+
if (route !== void 0) await this.ensureRuntimeModelModalities(route);
|
|
1775
|
+
return route;
|
|
1776
|
+
}
|
|
1756
1777
|
async setDefaultModel(model) {
|
|
1757
1778
|
if (!model.trim()) throw new Error("\u9ED8\u8BA4\u6A21\u578B\u4E0D\u80FD\u4E3A\u7A7A");
|
|
1758
1779
|
const route = await this.resolveModelRoute(model);
|
|
@@ -1800,6 +1821,57 @@ var DshProviderManager = class {
|
|
|
1800
1821
|
models: [...models, modelRecord(input)]
|
|
1801
1822
|
});
|
|
1802
1823
|
}
|
|
1824
|
+
/**
|
|
1825
|
+
* Ensure the selected DeepSeek vision model is present in the catalog that
|
|
1826
|
+
* the managed SDK/ACP runtime actually consumes. The upstream DeepSeek
|
|
1827
|
+
* adapter treats an unlisted model as text-only even when its id identifies
|
|
1828
|
+
* a vision endpoint, so read-time normalization alone is insufficient.
|
|
1829
|
+
* Returns true only when settings were changed.
|
|
1830
|
+
*/
|
|
1831
|
+
async ensureRuntimeModelModalities(route) {
|
|
1832
|
+
if (route.provider !== DEEPSEEK_PROVIDER || !isVisionModelId(route.model)) return false;
|
|
1833
|
+
await mkdir2(dirname3(this.settingsFile), { recursive: true });
|
|
1834
|
+
let changed = false;
|
|
1835
|
+
await withFileLock(this.settingsFile, async () => {
|
|
1836
|
+
const text = await readOptional(this.settingsFile);
|
|
1837
|
+
const root = parseYamlMap(text, this.settingsFile);
|
|
1838
|
+
const current = isMapLike(root[DEEPSEEK_NAMESPACE]) ? root[DEEPSEEK_NAMESPACE] : {};
|
|
1839
|
+
const models = rawModels(current.models);
|
|
1840
|
+
const index = models.findIndex((model) => model.id === route.model);
|
|
1841
|
+
const existing = index === -1 ? void 0 : models[index];
|
|
1842
|
+
const configured = Array.isArray(existing?.inputModalities) ? existing.inputModalities.filter(
|
|
1843
|
+
(modality) => modality === "text" || modality === "image"
|
|
1844
|
+
) : void 0;
|
|
1845
|
+
const modalities = normalizeVisionModelInputModalities(
|
|
1846
|
+
route.model,
|
|
1847
|
+
configured,
|
|
1848
|
+
false
|
|
1849
|
+
);
|
|
1850
|
+
if (existing !== void 0 && deepEqualJson(existing.inputModalities, modalities)) return;
|
|
1851
|
+
const nextModel = { ...existing ?? { id: route.model }, inputModalities: modalities };
|
|
1852
|
+
const section = {
|
|
1853
|
+
...current,
|
|
1854
|
+
models: index === -1 ? [...models, nextModel] : models.map((model, modelIndex) => modelIndex === index ? nextModel : model)
|
|
1855
|
+
};
|
|
1856
|
+
if (text === void 0 || text.trim().length === 0) {
|
|
1857
|
+
await writeFileAtomic(
|
|
1858
|
+
this.settingsFile,
|
|
1859
|
+
new Document({ [DEEPSEEK_NAMESPACE]: section }).toString(),
|
|
1860
|
+
{}
|
|
1861
|
+
);
|
|
1862
|
+
changed = true;
|
|
1863
|
+
return;
|
|
1864
|
+
}
|
|
1865
|
+
const document = parseDocument(text);
|
|
1866
|
+
if (document.errors.length > 0) {
|
|
1867
|
+
throw new Error(`invalid dsh settings at ${this.settingsFile}`);
|
|
1868
|
+
}
|
|
1869
|
+
patchRuntimeModelModalities(document, route.model, modalities);
|
|
1870
|
+
await writeFileAtomic(this.settingsFile, document.toString(), {});
|
|
1871
|
+
changed = true;
|
|
1872
|
+
});
|
|
1873
|
+
return changed;
|
|
1874
|
+
}
|
|
1803
1875
|
async removeDeepseekModel(id) {
|
|
1804
1876
|
const settings = await this.readSettings();
|
|
1805
1877
|
const current = isMapLike(settings[DEEPSEEK_NAMESPACE]) ? settings[DEEPSEEK_NAMESPACE] : {};
|
|
@@ -2765,6 +2837,7 @@ init_bridge_persona();
|
|
|
2765
2837
|
init_own_package();
|
|
2766
2838
|
init_profile_package();
|
|
2767
2839
|
import { spawn as spawn3 } from "cross-spawn";
|
|
2840
|
+
import { createRequire } from "module";
|
|
2768
2841
|
import { existsSync as existsSync3, realpathSync as realpathSync2 } from "fs";
|
|
2769
2842
|
import { readFileSync as readFileSync3 } from "fs";
|
|
2770
2843
|
import { mkdir as mkdir3, writeFile as writeFile3 } from "fs/promises";
|
|
@@ -2900,7 +2973,24 @@ function isSdkManagedProfileCurrent(profileRoot, options, own = ownPackageInfo()
|
|
|
2900
2973
|
return isSdkProfileReady(profileRoot, own) && readFileIfPresent(join6(profileRoot, "cordis.patch.yml")) === patchYamlFor(options);
|
|
2901
2974
|
}
|
|
2902
2975
|
function ownSdkServerDependencyReady(own) {
|
|
2903
|
-
|
|
2976
|
+
try {
|
|
2977
|
+
const physicalOwnRoot = realpathSync2(own.root);
|
|
2978
|
+
const entry = createRequire(join6(physicalOwnRoot, "package.json")).resolve(SDK_SERVER_PACKAGE);
|
|
2979
|
+
let current = dirname5(entry);
|
|
2980
|
+
for (let depth = 0; depth < 8; depth += 1) {
|
|
2981
|
+
try {
|
|
2982
|
+
const parsed = JSON.parse(readFileSync3(join6(current, "package.json"), "utf8"));
|
|
2983
|
+
if (parsed.name === SDK_SERVER_PACKAGE) return parsed.version === SDK_SERVER_VERSION;
|
|
2984
|
+
} catch {
|
|
2985
|
+
}
|
|
2986
|
+
const parent = dirname5(current);
|
|
2987
|
+
if (parent === current) return false;
|
|
2988
|
+
current = parent;
|
|
2989
|
+
}
|
|
2990
|
+
return false;
|
|
2991
|
+
} catch {
|
|
2992
|
+
return false;
|
|
2993
|
+
}
|
|
2904
2994
|
}
|
|
2905
2995
|
function findPnpmProjectRoot(start) {
|
|
2906
2996
|
let current = start;
|
|
@@ -4141,6 +4231,20 @@ function systemdUnit(nodeBin, cliEntry, env = {}) {
|
|
|
4141
4231
|
""
|
|
4142
4232
|
].join("\n");
|
|
4143
4233
|
}
|
|
4234
|
+
function stableGuardianServicePath(nodeBin, inheritedPath = process.env.PATH) {
|
|
4235
|
+
const entries = [dirname9(nodeBin), ...inheritedPath?.split(delimiter) ?? []];
|
|
4236
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4237
|
+
return entries.filter((entry) => {
|
|
4238
|
+
if (!entry) return false;
|
|
4239
|
+
const normalized = entry.replaceAll("\\", "/");
|
|
4240
|
+
if (normalized.includes("/node_modules/.bin") || /\/_npx(?:\/|$)/.test(normalized) || /\/guardian\/update-worker\/npm-cache(?:\/|$)/.test(normalized)) {
|
|
4241
|
+
return false;
|
|
4242
|
+
}
|
|
4243
|
+
if (seen.has(entry)) return false;
|
|
4244
|
+
seen.add(entry);
|
|
4245
|
+
return true;
|
|
4246
|
+
}).join(delimiter);
|
|
4247
|
+
}
|
|
4144
4248
|
function launchdPlist(nodeBin, cliEntry, label2 = `io.dsh-lark.${GUARDIAN_LABEL}`, logPath = "/tmp/dsh-lark-guardian.log") {
|
|
4145
4249
|
return [
|
|
4146
4250
|
'<?xml version="1.0" encoding="UTF-8"?>',
|
|
@@ -4203,7 +4307,7 @@ async function installGuardian(options) {
|
|
|
4203
4307
|
await writeFile5(
|
|
4204
4308
|
unitPath,
|
|
4205
4309
|
systemdUnit(nodeBin, cliEntry, {
|
|
4206
|
-
PATH:
|
|
4310
|
+
PATH: stableGuardianServicePath(nodeBin),
|
|
4207
4311
|
DSH_LARK_GUARDIAN_DISABLED: "0"
|
|
4208
4312
|
}),
|
|
4209
4313
|
"utf8"
|
|
@@ -8166,7 +8270,7 @@ async function runAttempt(input, cwd, workspaceCwd, sessionId, resuming, replyOp
|
|
|
8166
8270
|
const consume = async () => {
|
|
8167
8271
|
for await (const event of run.events) {
|
|
8168
8272
|
if (timedOut) return;
|
|
8169
|
-
if (resuming && !sawActivity && event.type === "error" && event.terminationReason === "failed") {
|
|
8273
|
+
if (resuming && !sawActivity && event.type === "error" && event.terminationReason === "failed" && classifySessionError(event.message) !== void 0) {
|
|
8170
8274
|
await showResumeRecovery(event.message);
|
|
8171
8275
|
return;
|
|
8172
8276
|
}
|
|
@@ -8261,10 +8365,19 @@ async function runAttempt(input, cwd, workspaceCwd, sessionId, resuming, replyOp
|
|
|
8261
8365
|
try {
|
|
8262
8366
|
await consume();
|
|
8263
8367
|
} catch (error) {
|
|
8264
|
-
if (resuming && !sawActivity) {
|
|
8368
|
+
if (resuming && !sawActivity && classifySessionError(errorMessage(error)) !== void 0) {
|
|
8265
8369
|
await showResumeRecovery(error);
|
|
8266
8370
|
return;
|
|
8267
8371
|
}
|
|
8372
|
+
if (state.terminal === "running") {
|
|
8373
|
+
state = applyEvent(state, {
|
|
8374
|
+
type: "error",
|
|
8375
|
+
message: errorMessage(error),
|
|
8376
|
+
terminationReason: "failed"
|
|
8377
|
+
}, stopRequested);
|
|
8378
|
+
state = { ...state, lastActivityMs: Date.now() };
|
|
8379
|
+
await safeUpdate();
|
|
8380
|
+
}
|
|
8268
8381
|
throw error;
|
|
8269
8382
|
}
|
|
8270
8383
|
};
|
|
@@ -8369,7 +8482,7 @@ async function runAttempt(input, cwd, workspaceCwd, sessionId, resuming, replyOp
|
|
|
8369
8482
|
if (resumeFailure !== void 0) {
|
|
8370
8483
|
throw new Error(resumeFailure);
|
|
8371
8484
|
}
|
|
8372
|
-
if (resuming && state.terminal === "error" && !sawActivity) {
|
|
8485
|
+
if (resuming && state.terminal === "error" && !sawActivity && classifySessionError(state.errorMsg ?? "") !== void 0) {
|
|
8373
8486
|
throw new Error(state.errorMsg ?? "native session resume failed");
|
|
8374
8487
|
}
|
|
8375
8488
|
input.sessions.recordExchange(input.scope, workspaceCwd, input.messages, assistantOutput, {
|
|
@@ -8384,14 +8497,14 @@ async function runAttempt(input, cwd, workspaceCwd, sessionId, resuming, replyOp
|
|
|
8384
8497
|
} : {}
|
|
8385
8498
|
});
|
|
8386
8499
|
} catch (error) {
|
|
8387
|
-
|
|
8388
|
-
|
|
8500
|
+
const runErrorText = errorMessage(error);
|
|
8501
|
+
const healKind = classifySessionError(runErrorText);
|
|
8502
|
+
if (resuming && !sawActivity && healKind !== void 0) {
|
|
8389
8503
|
log.warn("run-flow", "resume-attempt-failed", { scope: input.scope, runId });
|
|
8390
8504
|
throw error;
|
|
8391
8505
|
}
|
|
8506
|
+
if (state.terminal === "running") state = markInterrupted(state);
|
|
8392
8507
|
log.fail("run-flow", error, { scope: input.scope, runId });
|
|
8393
|
-
const runErrorText = errorMessage(error);
|
|
8394
|
-
const healKind = classifySessionError(runErrorText);
|
|
8395
8508
|
if (healKind !== void 0) {
|
|
8396
8509
|
const brokenSessionId = input.sessions.getRaw(input.scope, workspaceCwd)?.sessionId;
|
|
8397
8510
|
if (brokenSessionId !== void 0) {
|
|
@@ -11988,6 +12101,7 @@ var ResilientCardStreamController = class {
|
|
|
11988
12101
|
closed = false;
|
|
11989
12102
|
timer;
|
|
11990
12103
|
inFlight;
|
|
12104
|
+
reanchorInFlight;
|
|
11991
12105
|
async update(card) {
|
|
11992
12106
|
if (this.closed || this.failed) return;
|
|
11993
12107
|
this.latest = card;
|
|
@@ -12001,10 +12115,11 @@ var ResilientCardStreamController = class {
|
|
|
12001
12115
|
this.timer = void 0;
|
|
12002
12116
|
}
|
|
12003
12117
|
if (this.inFlight !== void 0) await this.inFlight;
|
|
12118
|
+
if (this.reanchorInFlight !== void 0) await this.reanchorInFlight;
|
|
12004
12119
|
if (!this.failed && this.dirty) await this.flush();
|
|
12005
12120
|
}
|
|
12006
12121
|
schedule() {
|
|
12007
|
-
if (this.timer !== void 0 || this.inFlight !== void 0 || this.failed) return;
|
|
12122
|
+
if (this.timer !== void 0 || this.inFlight !== void 0 || this.reanchorInFlight !== void 0 || this.failed) return;
|
|
12008
12123
|
this.timer = setTimeout(() => {
|
|
12009
12124
|
this.timer = void 0;
|
|
12010
12125
|
this.startFlush();
|
|
@@ -12072,6 +12187,17 @@ var ResilientCardStreamController = class {
|
|
|
12072
12187
|
*/
|
|
12073
12188
|
async reanchor() {
|
|
12074
12189
|
if (this.closed || this.failed) return this.messageId;
|
|
12190
|
+
if (this.reanchorInFlight !== void 0) return this.reanchorInFlight;
|
|
12191
|
+
const task = this.performReanchor();
|
|
12192
|
+
this.reanchorInFlight = task;
|
|
12193
|
+
try {
|
|
12194
|
+
return await task;
|
|
12195
|
+
} finally {
|
|
12196
|
+
if (this.reanchorInFlight === task) this.reanchorInFlight = void 0;
|
|
12197
|
+
if (!this.closed && this.dirty && !this.failed) this.schedule();
|
|
12198
|
+
}
|
|
12199
|
+
}
|
|
12200
|
+
async performReanchor() {
|
|
12075
12201
|
if (this.inFlight !== void 0) await this.inFlight;
|
|
12076
12202
|
if (this.timer !== void 0) {
|
|
12077
12203
|
clearTimeout(this.timer);
|
|
@@ -12079,9 +12205,11 @@ var ResilientCardStreamController = class {
|
|
|
12079
12205
|
}
|
|
12080
12206
|
const card = this.latest;
|
|
12081
12207
|
if (card === void 0) return this.messageId;
|
|
12208
|
+
this.dirty = false;
|
|
12082
12209
|
try {
|
|
12083
12210
|
await this.channel.recallMessage(this.messageId);
|
|
12084
12211
|
} catch (error) {
|
|
12212
|
+
this.dirty = true;
|
|
12085
12213
|
log.warn("lark-card-stream", "reanchor-recall-failed", {
|
|
12086
12214
|
messageId: this.messageId,
|
|
12087
12215
|
error: error instanceof Error ? error.message : String(error)
|
|
@@ -12091,8 +12219,6 @@ var ResilientCardStreamController = class {
|
|
|
12091
12219
|
const sent = await this.channel.send(this.chatId, { card }, {});
|
|
12092
12220
|
if (!sent.messageId) throw new Error("Feishu card re-anchor returned no message_id");
|
|
12093
12221
|
this.messageId = sent.messageId;
|
|
12094
|
-
this.latest = card;
|
|
12095
|
-
this.dirty = false;
|
|
12096
12222
|
this.failed = false;
|
|
12097
12223
|
return this.messageId;
|
|
12098
12224
|
}
|
|
@@ -18093,13 +18219,13 @@ async function startBridgeEngine(options) {
|
|
|
18093
18219
|
let modelRoute2;
|
|
18094
18220
|
if (resolvedModel) {
|
|
18095
18221
|
try {
|
|
18096
|
-
modelRoute2 = await dshConfig.
|
|
18222
|
+
modelRoute2 = await dshConfig.resolveRuntimeModelRoute(resolvedModel);
|
|
18097
18223
|
} catch (error) {
|
|
18098
18224
|
await streaming.sendMarkdown(
|
|
18099
18225
|
first.chatId,
|
|
18100
18226
|
bilingualMarkdown(
|
|
18101
|
-
`\u26A0\uFE0F \u8BFB\u53D6 dsh \u914D\u7F6E\u5931\u8D25\uFF0C\u65E0\u6CD5\u89E3\u6790\u6A21\u578B \`${resolvedModel}\` \u7684 provider \u8DEF\u7531\uFF1A${error instanceof Error ? error.message : String(error)}`,
|
|
18102
|
-
`\u26A0\uFE0F Failed to read dsh configuration, so the provider route for model \`${resolvedModel}\` could not be resolved: ${error instanceof Error ? error.message : String(error)}`
|
|
18227
|
+
`\u26A0\uFE0F \u8BFB\u53D6\u6216\u51C6\u5907 dsh \u8FD0\u884C\u65F6\u914D\u7F6E\u5931\u8D25\uFF0C\u65E0\u6CD5\u89E3\u6790\u6A21\u578B \`${resolvedModel}\` \u7684 provider \u8DEF\u7531\uFF1A${error instanceof Error ? error.message : String(error)}`,
|
|
18228
|
+
`\u26A0\uFE0F Failed to read or prepare dsh runtime configuration, so the provider route for model \`${resolvedModel}\` could not be resolved: ${error instanceof Error ? error.message : String(error)}`
|
|
18103
18229
|
),
|
|
18104
18230
|
{ replyTo: first.messageId }
|
|
18105
18231
|
);
|
|
@@ -18513,6 +18639,7 @@ import { spawn as spawn8 } from "child_process";
|
|
|
18513
18639
|
import { mkdir as mkdir23, readFile as readFile32, writeFile as writeFile9 } from "fs/promises";
|
|
18514
18640
|
import { homedir as homedir17 } from "os";
|
|
18515
18641
|
import { join as join28 } from "path";
|
|
18642
|
+
import { parse } from "yaml";
|
|
18516
18643
|
async function runSetup(options = {}) {
|
|
18517
18644
|
const profile = options.profile ?? "dsh-lark";
|
|
18518
18645
|
const own = ownPackageInfo();
|
|
@@ -18586,6 +18713,7 @@ function runDshPlugin(bin, profile, packageSpec, env = process.env) {
|
|
|
18586
18713
|
});
|
|
18587
18714
|
}
|
|
18588
18715
|
async function approveBuilds(profileDir) {
|
|
18716
|
+
await preserveInstalledPnpmVersion(profileDir);
|
|
18589
18717
|
const workspaceFile = join28(profileDir, "pnpm-workspace.yaml");
|
|
18590
18718
|
let existing = "";
|
|
18591
18719
|
try {
|
|
@@ -18610,6 +18738,32 @@ allowBuilds:
|
|
|
18610
18738
|
}
|
|
18611
18739
|
await writeFile9(workspaceFile, existing, "utf8");
|
|
18612
18740
|
}
|
|
18741
|
+
async function preserveInstalledPnpmVersion(profileDir) {
|
|
18742
|
+
const packageFile = join28(profileDir, "package.json");
|
|
18743
|
+
const modulesFile = join28(profileDir, "node_modules", ".modules.yaml");
|
|
18744
|
+
let packageText;
|
|
18745
|
+
let modulesText;
|
|
18746
|
+
try {
|
|
18747
|
+
[packageText, modulesText] = await Promise.all([
|
|
18748
|
+
readFile32(packageFile, "utf8"),
|
|
18749
|
+
readFile32(modulesFile, "utf8")
|
|
18750
|
+
]);
|
|
18751
|
+
} catch {
|
|
18752
|
+
return;
|
|
18753
|
+
}
|
|
18754
|
+
try {
|
|
18755
|
+
const profilePackage = JSON.parse(packageText);
|
|
18756
|
+
const modules = parse(modulesText);
|
|
18757
|
+
const packageManager = modules?.packageManager;
|
|
18758
|
+
if (typeof packageManager !== "string" || !/^pnpm@\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/.test(packageManager) || profilePackage.packageManager === packageManager) {
|
|
18759
|
+
return;
|
|
18760
|
+
}
|
|
18761
|
+
profilePackage.packageManager = packageManager;
|
|
18762
|
+
await writeFile9(packageFile, `${JSON.stringify(profilePackage, null, 2)}
|
|
18763
|
+
`, "utf8");
|
|
18764
|
+
} catch {
|
|
18765
|
+
}
|
|
18766
|
+
}
|
|
18613
18767
|
|
|
18614
18768
|
// src/cli/commands/upgrade.ts
|
|
18615
18769
|
init_own_package();
|
|
@@ -18768,7 +18922,7 @@ function existingAcpRoute(profileRoot) {
|
|
|
18768
18922
|
}
|
|
18769
18923
|
}
|
|
18770
18924
|
async function repairRuntimeProfiles(options) {
|
|
18771
|
-
const own = ownPackageInfo();
|
|
18925
|
+
const own = options.ownPackage ?? ownPackageInfo();
|
|
18772
18926
|
const env = options.env ?? process.env;
|
|
18773
18927
|
const relink = options.relinkFn ?? defaultRelink;
|
|
18774
18928
|
const managedEnv = { ...env, DSH_HOME: options.dshHome };
|
|
@@ -18786,18 +18940,19 @@ async function repairRuntimeProfiles(options) {
|
|
|
18786
18940
|
{
|
|
18787
18941
|
profile: DEFAULT_SDK_PROFILE,
|
|
18788
18942
|
root: sdkRoot,
|
|
18789
|
-
isReady: isSdkManagedProfileCurrent,
|
|
18790
|
-
ensure: () => ensureSdk({ home: options.dshHome, env: managedEnv })
|
|
18943
|
+
isReady: (root) => isSdkManagedProfileCurrent(root, void 0, own),
|
|
18944
|
+
ensure: () => ensureSdk({ home: options.dshHome, env: managedEnv, ownPackage: own })
|
|
18791
18945
|
},
|
|
18792
18946
|
{
|
|
18793
18947
|
profile: DEFAULT_ACP_PROFILE,
|
|
18794
18948
|
root: acpRoot,
|
|
18795
|
-
isReady: (root) => isAcpManagedProfileCurrent(root, acpRoute.provider, acpRoute.model),
|
|
18949
|
+
isReady: (root) => isAcpManagedProfileCurrent(root, acpRoute.provider, acpRoute.model, own),
|
|
18796
18950
|
ensure: () => ensureAcp({
|
|
18797
18951
|
home: options.dshHome,
|
|
18798
18952
|
env: managedEnv,
|
|
18799
18953
|
provider: acpRoute.provider,
|
|
18800
|
-
model: acpRoute.model
|
|
18954
|
+
model: acpRoute.model,
|
|
18955
|
+
ownPackage: own
|
|
18801
18956
|
})
|
|
18802
18957
|
}
|
|
18803
18958
|
];
|
|
@@ -19014,10 +19169,21 @@ ${changeLines.join("\n")}
|
|
|
19014
19169
|
await (options.pluginSpawnFn ?? runDshPlugin)(bin, profile, target.spec);
|
|
19015
19170
|
write(out, `\u2705 \u5305\u672C\u4F53\u5DF2\u66F4\u65B0\u5230 ${target.version}\u3002
|
|
19016
19171
|
`);
|
|
19172
|
+
const installedTarget = await (options.readInstalledPackageFn ?? readInstalledPackage)(
|
|
19173
|
+
dshHome,
|
|
19174
|
+
profile,
|
|
19175
|
+
own.name
|
|
19176
|
+
);
|
|
19177
|
+
if (!installedTarget || installedTarget.name !== own.name || installedTarget.version !== target.version) {
|
|
19178
|
+
throw new Error(
|
|
19179
|
+
`dsh plugin add completed but ${own.name}@${target.version} is not installed in profile ${profile}`
|
|
19180
|
+
);
|
|
19181
|
+
}
|
|
19017
19182
|
const repair = options.repairRuntimeFn ?? repairRuntimeProfiles;
|
|
19018
19183
|
const runtimeStates = await repair({
|
|
19019
19184
|
dshHome,
|
|
19020
19185
|
env: process.env,
|
|
19186
|
+
ownPackage: installedTarget,
|
|
19021
19187
|
provider: env.provider,
|
|
19022
19188
|
model: env.model
|
|
19023
19189
|
});
|
|
@@ -19133,7 +19299,7 @@ import { join as join32 } from "path";
|
|
|
19133
19299
|
import {
|
|
19134
19300
|
createLarkChannel as createLarkChannel3
|
|
19135
19301
|
} from "@larksuite/channel";
|
|
19136
|
-
import { parse } from "yaml";
|
|
19302
|
+
import { parse as parse2 } from "yaml";
|
|
19137
19303
|
init_dsh_runtime();
|
|
19138
19304
|
|
|
19139
19305
|
// src/guardian/control.ts
|
|
@@ -20382,7 +20548,7 @@ function safeScopeForAction(event) {
|
|
|
20382
20548
|
function readDshDefaultSelection(home, env) {
|
|
20383
20549
|
try {
|
|
20384
20550
|
const file = join32(resolveDshHome(home, env), "settings.yaml");
|
|
20385
|
-
const doc =
|
|
20551
|
+
const doc = parse2(readFileSync6(file, "utf8"));
|
|
20386
20552
|
const value = doc?.["agent-default-model"];
|
|
20387
20553
|
if (typeof value === "object" && value !== null && typeof value.provider === "string" && typeof value.model === "string") {
|
|
20388
20554
|
const selection = value;
|