dsh-lark-bot 0.15.5 → 0.15.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 CHANGED
@@ -163,9 +163,12 @@ dsh-lark-bot guardian install --dsh-profile dsh-lark
163
163
  字面密钥不进 settings / 聊天记录。
164
164
  - **凭据引用必须关联**:`/key set <引用名> <值>` 只写入凭据文件;provider 要生效还须在其
165
165
  `apiKeyEnv` 字段引用同一名字(`/provider add|update ... --api-key-env <引用名>`,或向导中填写)。
166
+ 引用名与 provider ID 相同且 provider 未设 `apiKeyEnv` 时,`/key set` 会自动补关联;
167
+ 已存在的老配置在下次运行时也会自动补齐。
166
168
  - **热重载**:桥接在每轮运行前把模型解析为「provider + model」路由并传给 dsh runtime;SDK 适配器
167
169
  在路由变化时自动重建 runtime(下一轮生效)。pi-ai 的 Base URL 填根域名(如
168
- `https://www.kingapi.xyz`)会自动补全为 `/v1`。
170
+ `https://www.kingapi.xyz`)会自动补全为 `/v1`。dsh runtime 启动后需几百毫秒才注册
171
+ pi-ai 路由,桥接会重试握手直到注册完成(避免 “no adapter registered for provider”)。
169
172
 
170
173
  安全提醒:在飞书会话输入密钥会对可见成员暴露,建议私聊使用或 `--api-key-env` 引用环境变量;bot 不在任何回复中回显密钥值。
171
174
 
package/dist/cli.js CHANGED
@@ -1256,6 +1256,52 @@ function createSdkRun(harness, prompt, options) {
1256
1256
  }
1257
1257
 
1258
1258
  // src/adapters/dsh/sdk-adapter.ts
1259
+ var RETRYABLE_INIT_ERROR = /no adapter registered for provider/i;
1260
+ var INIT_RETRY_ATTEMPTS = 6;
1261
+ var INIT_RETRY_DELAY_MS = 250;
1262
+ function sleep(ms) {
1263
+ return new Promise((resolve5) => setTimeout(resolve5, ms));
1264
+ }
1265
+ function isRetryableInitError(error) {
1266
+ return error instanceof Error && RETRYABLE_INIT_ERROR.test(error.message);
1267
+ }
1268
+ function withRetryableStart(harness) {
1269
+ const originalStart = harness.start.bind(harness);
1270
+ const internals = harness;
1271
+ let handled = false;
1272
+ harness.start = async () => {
1273
+ if (handled) return originalStart();
1274
+ try {
1275
+ const result = await originalStart();
1276
+ handled = true;
1277
+ return result;
1278
+ } catch (error) {
1279
+ if (!isRetryableInitError(error)) throw error;
1280
+ handled = true;
1281
+ const client = internals.client;
1282
+ if (!client) throw error;
1283
+ client.start();
1284
+ let lastError = error;
1285
+ for (let attempt = 1; attempt <= INIT_RETRY_ATTEMPTS; attempt += 1) {
1286
+ await sleep(INIT_RETRY_DELAY_MS * attempt);
1287
+ try {
1288
+ await client.initialize({
1289
+ cwd: internals.cwd,
1290
+ provider: internals.provider,
1291
+ model: internals.model
1292
+ });
1293
+ internals.initialized = Promise.resolve();
1294
+ return void 0;
1295
+ } catch (retryError) {
1296
+ lastError = retryError;
1297
+ if (!isRetryableInitError(retryError)) throw retryError;
1298
+ }
1299
+ }
1300
+ throw lastError;
1301
+ }
1302
+ };
1303
+ return harness;
1304
+ }
1259
1305
  function waitWithTimeout(promise, timeoutMs) {
1260
1306
  return new Promise((resolve5) => {
1261
1307
  const timer = setTimeout(() => resolve5(false), timeoutMs > 0 ? timeoutMs : 5e3);
@@ -1308,21 +1354,35 @@ var SdkDshAdapter = class {
1308
1354
  });
1309
1355
  try {
1310
1356
  client.start();
1311
- const info = await client.initialize({
1312
- cwd: process.cwd(),
1313
- provider: this.provider,
1314
- model: this.model,
1315
- ...this.maxTokens === void 0 ? {} : { maxTokens: this.maxTokens }
1316
- });
1317
- return {
1318
- ok: true,
1319
- error: void 0,
1320
- version: `${info.serverInfo.name}@${info.serverInfo.version}`
1321
- };
1322
- } catch (error) {
1357
+ let lastError;
1358
+ for (let attempt = 1; attempt <= INIT_RETRY_ATTEMPTS; attempt += 1) {
1359
+ try {
1360
+ const info = await client.initialize({
1361
+ cwd: process.cwd(),
1362
+ provider: this.provider,
1363
+ model: this.model,
1364
+ ...this.maxTokens === void 0 ? {} : { maxTokens: this.maxTokens }
1365
+ });
1366
+ return {
1367
+ ok: true,
1368
+ error: void 0,
1369
+ version: `${info.serverInfo.name}@${info.serverInfo.version}`
1370
+ };
1371
+ } catch (error) {
1372
+ lastError = error;
1373
+ if (!isRetryableInitError(error) || attempt === INIT_RETRY_ATTEMPTS) {
1374
+ return {
1375
+ ok: false,
1376
+ error: error instanceof Error ? error.message : String(error),
1377
+ version: void 0
1378
+ };
1379
+ }
1380
+ await sleep(INIT_RETRY_DELAY_MS * attempt);
1381
+ }
1382
+ }
1323
1383
  return {
1324
1384
  ok: false,
1325
- error: error instanceof Error ? error.message : String(error),
1385
+ error: lastError instanceof Error ? lastError.message : String(lastError),
1326
1386
  version: void 0
1327
1387
  };
1328
1388
  } finally {
@@ -1408,7 +1468,7 @@ var SdkDshAdapter = class {
1408
1468
  }
1409
1469
  }
1410
1470
  const entry = {
1411
- harness: this.harnessFactory(cwd, route),
1471
+ harness: withRetryableStart(this.harnessFactory(cwd, route)),
1412
1472
  provider: route.provider,
1413
1473
  model: route.model,
1414
1474
  active: 0,
@@ -3890,7 +3950,19 @@ function renderApprovalCard(input) {
3890
3950
  body: {
3891
3951
  elements: [
3892
3952
  { tag: "markdown", content: markdown2 },
3893
- { tag: "action", actions }
3953
+ ...actions.length > 0 ? [
3954
+ {
3955
+ tag: "column_set",
3956
+ flex_mode: "none",
3957
+ horizontal_spacing: "default",
3958
+ columns: actions.map((button2) => ({
3959
+ tag: "column",
3960
+ width: "auto",
3961
+ vertical_align: "center",
3962
+ elements: [button2]
3963
+ }))
3964
+ }
3965
+ ] : []
3894
3966
  ]
3895
3967
  }
3896
3968
  };
@@ -3933,15 +4005,18 @@ function renderQuestionCard(input) {
3933
4005
  body: {
3934
4006
  elements: [
3935
4007
  { tag: "markdown", content: `\u2753 ${input.question}` },
3936
- formElement(input),
3937
4008
  {
3938
- tag: "action",
3939
- actions: [
4009
+ tag: "form",
4010
+ name: `form-${input.id}`,
4011
+ elements: [
4012
+ formElement(input),
3940
4013
  {
3941
4014
  tag: "button",
3942
4015
  text: { tag: "plain_text", content: "\u63D0\u4EA4" },
3943
4016
  type: "primary",
3944
- value: { cmd: "question-submit", id: input.id }
4017
+ value: { cmd: "question-submit", id: input.id },
4018
+ form_action_type: "submit",
4019
+ name: `btn-submit-${input.id}`
3945
4020
  }
3946
4021
  ]
3947
4022
  }
@@ -4495,7 +4570,7 @@ async function readOptional(file) {
4495
4570
  throw error;
4496
4571
  }
4497
4572
  }
4498
- function sleep(ms) {
4573
+ function sleep2(ms) {
4499
4574
  return new Promise((resolve5) => setTimeout(resolve5, ms));
4500
4575
  }
4501
4576
  async function withFileLock(filename, operation) {
@@ -4511,7 +4586,7 @@ async function withFileLock(filename, operation) {
4511
4586
  if (Date.now() >= deadline) {
4512
4587
  throw new Error(`dsh config lock timed out at ${lockPath}`);
4513
4588
  }
4514
- await sleep(80 + Math.random() * 120);
4589
+ await sleep2(80 + Math.random() * 120);
4515
4590
  }
4516
4591
  }
4517
4592
  try {
@@ -4742,6 +4817,25 @@ var DshProviderManager = class {
4742
4817
  }
4743
4818
  return true;
4744
4819
  }
4820
+ /**
4821
+ * Heal the common misconfiguration where a credential was stored under the
4822
+ * provider id (`/key set kingapi …`) but the provider never got an
4823
+ * apiKeyEnv. Links the ref to the matching pi-ai provider once; returns
4824
+ * true when a link was applied. Idempotent and a no-op when the provider
4825
+ * already has a ref or no matching credential exists.
4826
+ */
4827
+ async linkCredentialRefIfMissing(providerId) {
4828
+ const settings = await this.readSettings();
4829
+ const piAi = isMapLike(settings[PIAI_NAMESPACE]) ? settings[PIAI_NAMESPACE] : {};
4830
+ const providers = isMapLike(piAi.providers) ? piAi.providers : {};
4831
+ const section = isMapLike(providers[providerId]) ? providers[providerId] : {};
4832
+ if (typeof section.apiKeyEnv === "string" && section.apiKeyEnv.length > 0) {
4833
+ return false;
4834
+ }
4835
+ if (!await this.hasCredential(providerId)) return false;
4836
+ await this.upsertPiAiProvider({ id: providerId, apiKeyEnv: providerId });
4837
+ return true;
4838
+ }
4745
4839
  async addPiAiModel(providerId, input) {
4746
4840
  validateProviderId(providerId);
4747
4841
  const settings = await this.readSettings();
@@ -5127,9 +5221,24 @@ async function handleKey(args, ctx) {
5127
5221
  await reply(ctx, `\u5199\u5165\u5931\u8D25\uFF1A${error instanceof Error ? error.message : String(error)}`);
5128
5222
  return;
5129
5223
  }
5224
+ const providers = await ctx.dshConfig.listProviders();
5225
+ const target = providers.find(
5226
+ (provider) => provider.id === ref && provider.namespace === PIAI_NAMESPACE && provider.credentialRef === void 0
5227
+ );
5228
+ let autoLinked = false;
5229
+ if (target) {
5230
+ await ctx.dshConfig.upsertPiAiProvider({ id: ref, apiKeyEnv: ref });
5231
+ autoLinked = true;
5232
+ }
5130
5233
  await reply(
5131
5234
  ctx,
5132
- `\u5DF2\u5199\u5165\u51ED\u636E \`${ref}\` \u5230 \`~/.dsh/.credentials.yaml\`\uFF080600\uFF0C\u503C\u5DF2\u9690\u85CF\uFF09\u3002\u5EFA\u8BAE\u5728\u79C1\u804A\u4E2D\u4F7F\u7528\uFF1B\u7FA4\u804A\u91CC\u7C98\u8D34\u7684\u5BC6\u94A5\u4F1A\u5BF9\u7FA4\u6210\u5458\u53EF\u89C1\u3002`
5235
+ [
5236
+ `\u5DF2\u5199\u5165\u51ED\u636E \`${ref}\` \u5230 \`~/.dsh/.credentials.yaml\`\uFF080600\uFF0C\u503C\u5DF2\u9690\u85CF\uFF09\u3002\u5EFA\u8BAE\u5728\u79C1\u804A\u4E2D\u4F7F\u7528\uFF1B\u7FA4\u804A\u91CC\u7C98\u8D34\u7684\u5BC6\u94A5\u4F1A\u5BF9\u7FA4\u6210\u5458\u53EF\u89C1\u3002`,
5237
+ ...autoLinked ? [
5238
+ "",
5239
+ `\u{1F517} \u5DF2\u81EA\u52A8\u628A provider \`${ref}\` \u7684 apiKeyEnv \u5173\u8054\u5230 \`${ref}\`\uFF08\u4E0B\u4E00\u8BF7\u6C42\u751F\u6548\uFF09\u3002`
5240
+ ] : []
5241
+ ].join("\n")
5133
5242
  );
5134
5243
  return;
5135
5244
  }
@@ -5155,8 +5264,18 @@ function button(text, value, type = "default") {
5155
5264
  value
5156
5265
  };
5157
5266
  }
5158
- function actionRow(actions) {
5159
- return { tag: "action", actions };
5267
+ function buttonRow(buttons) {
5268
+ return {
5269
+ tag: "column_set",
5270
+ flex_mode: "none",
5271
+ horizontal_spacing: "default",
5272
+ columns: buttons.map((button2) => ({
5273
+ tag: "column",
5274
+ width: "auto",
5275
+ vertical_align: "center",
5276
+ elements: [button2]
5277
+ }))
5278
+ };
5160
5279
  }
5161
5280
  function wizardValue(flow, step, extra) {
5162
5281
  return { cmd: "wizard", flow, step, ...extra };
@@ -5187,22 +5306,22 @@ function renderConfigHubCard(input) {
5187
5306
  `dsh \u9ED8\u8BA4\u6A21\u578B\uFF1A${defaultLine}`
5188
5307
  ].join("\n")
5189
5308
  },
5190
- actionRow([
5309
+ buttonRow([
5191
5310
  button("\u2795 \u6DFB\u52A0 Provider", { cmd: "cfg", action: "provider-add" }, "primary"),
5192
5311
  button("\u270F\uFE0F \u4FEE\u6539 Provider", { cmd: "cfg", action: "provider-update" }),
5193
5312
  button("\u{1F5D1} \u5220\u9664 Provider", { cmd: "cfg", action: "provider-remove" }, "danger")
5194
5313
  ]),
5195
- actionRow([
5314
+ buttonRow([
5196
5315
  button("\u{1F9E0} \u6DFB\u52A0\u6A21\u578B", { cmd: "cfg", action: "model-add" }),
5197
5316
  button("\u274C \u5220\u9664\u6A21\u578B", { cmd: "cfg", action: "model-remove" }, "danger"),
5198
5317
  button("\u{1F3AF} \u5207\u6362\u6A21\u578B", { cmd: "cfg", action: "model-use" })
5199
5318
  ]),
5200
- actionRow([
5319
+ buttonRow([
5201
5320
  button("\u{1F3E0} \u8BBE\u7F6E\u9ED8\u8BA4\u6A21\u578B", { cmd: "cfg", action: "model-default" }),
5202
5321
  button("\u{1F511} \u8BBE\u7F6E\u51ED\u636E", { cmd: "cfg", action: "key-set" }),
5203
5322
  button("\u{1F513} \u5220\u9664\u51ED\u636E", { cmd: "cfg", action: "key-remove" }, "danger")
5204
5323
  ]),
5205
- actionRow([
5324
+ buttonRow([
5206
5325
  button("\u{1F504} \u5237\u65B0", { cmd: "cfg", action: "refresh" }),
5207
5326
  button("\u2716\uFE0F \u5173\u95ED", { cmd: "cfg", action: "dismiss" })
5208
5327
  ])
@@ -5215,13 +5334,14 @@ function renderWizardOptionsCard(input) {
5215
5334
  const rows = [];
5216
5335
  for (let index = 0; index < input.options.length; index += 3) {
5217
5336
  rows.push(
5218
- actionRow(
5337
+ buttonRow(
5219
5338
  input.options.slice(index, index + 3).map(
5220
5339
  (option, offset) => button(option.label, wizardValue(input.flow, input.step, { choose: index + offset }))
5221
5340
  )
5222
5341
  )
5223
5342
  );
5224
5343
  }
5344
+ rows.push(buttonRow([cancel]));
5225
5345
  return {
5226
5346
  schema: "2.0",
5227
5347
  config: {
@@ -5234,8 +5354,7 @@ function renderWizardOptionsCard(input) {
5234
5354
  content: [input.question, input.hint ? `
5235
5355
  ${input.hint}` : ""].join("")
5236
5356
  },
5237
- ...rows,
5238
- actionRow([cancel])
5357
+ ...rows
5239
5358
  ]
5240
5359
  }
5241
5360
  };
@@ -5254,14 +5373,23 @@ function renderWizardTextStepCard(input) {
5254
5373
  ${input.hint}` : ""].join("")
5255
5374
  },
5256
5375
  {
5257
- tag: "input",
5258
- name: "answer",
5259
- placeholder: { tag: "plain_text", content: input.placeholder ?? "\u8BF7\u8F93\u5165\u2026" }
5376
+ tag: "form",
5377
+ name: `form-${input.flow}-${input.step}`,
5378
+ elements: [
5379
+ {
5380
+ tag: "input",
5381
+ name: "answer",
5382
+ ...input.required === true ? { required: true } : {},
5383
+ placeholder: { tag: "plain_text", content: input.placeholder ?? "\u8BF7\u8F93\u5165\u2026" }
5384
+ },
5385
+ {
5386
+ ...button("\u63D0\u4EA4", wizardValue(input.flow, input.step, { submit: true }), "primary"),
5387
+ form_action_type: "submit",
5388
+ name: `btn-submit-${input.flow}-${input.step}`
5389
+ }
5390
+ ]
5260
5391
  },
5261
- actionRow([
5262
- button("\u63D0\u4EA4", wizardValue(input.flow, input.step, { submit: true }), "primary"),
5263
- button("\u53D6\u6D88", wizardValue(input.flow, input.step, { cancel: true }))
5264
- ])
5392
+ button("\u53D6\u6D88", wizardValue(input.flow, input.step, { cancel: true }))
5265
5393
  ]
5266
5394
  }
5267
5395
  };
@@ -5280,7 +5408,7 @@ function renderWizardConfirmStepCard(input) {
5280
5408
 
5281
5409
  ${input.summary}`
5282
5410
  },
5283
- actionRow([
5411
+ buttonRow([
5284
5412
  button(input.confirmLabel ?? "\u2705 \u786E\u8BA4", wizardValue(input.flow, input.step, { confirm: true }), "primary"),
5285
5413
  button("\u53D6\u6D88", wizardValue(input.flow, input.step, { cancel: true }))
5286
5414
  ])
@@ -5788,7 +5916,22 @@ var FLOWS = {
5788
5916
  const ref = asString(data.ref);
5789
5917
  const value = asString(data.value);
5790
5918
  await ctx.dshConfig.setCredential(ref, value);
5791
- return `\u5DF2\u5199\u5165\u51ED\u636E \`${ref}\` \u5230 \`~/.dsh/.credentials.yaml\`\uFF080600\uFF0C\u503C\u5DF2\u9690\u85CF\uFF09\u3002\u5EFA\u8BAE\u5728\u79C1\u804A\u4E2D\u4F7F\u7528\uFF1B\u7FA4\u804A\u91CC\u7C98\u8D34\u7684\u5BC6\u94A5\u4F1A\u5BF9\u7FA4\u6210\u5458\u53EF\u89C1\u3002`;
5919
+ const providers = await ctx.dshConfig.listProviders();
5920
+ const target = providers.find(
5921
+ (provider) => provider.id === ref && provider.namespace === "llm-pi-ai" && provider.credentialRef === void 0
5922
+ );
5923
+ let autoLinked = false;
5924
+ if (target) {
5925
+ await ctx.dshConfig.upsertPiAiProvider({ id: ref, apiKeyEnv: ref });
5926
+ autoLinked = true;
5927
+ }
5928
+ return [
5929
+ `\u5DF2\u5199\u5165\u51ED\u636E \`${ref}\` \u5230 \`~/.dsh/.credentials.yaml\`\uFF080600\uFF0C\u503C\u5DF2\u9690\u85CF\uFF09\u3002\u5EFA\u8BAE\u5728\u79C1\u804A\u4E2D\u4F7F\u7528\uFF1B\u7FA4\u804A\u91CC\u7C98\u8D34\u7684\u5BC6\u94A5\u4F1A\u5BF9\u7FA4\u6210\u5458\u53EF\u89C1\u3002`,
5930
+ ...autoLinked ? [
5931
+ "",
5932
+ `\u{1F517} \u5DF2\u81EA\u52A8\u628A provider \`${ref}\` \u7684 apiKeyEnv \u5173\u8054\u5230 \`${ref}\`\uFF08\u4E0B\u4E00\u8BF7\u6C42\u751F\u6548\uFF09\u3002`
5933
+ ] : []
5934
+ ].join("\n");
5792
5935
  }
5793
5936
  },
5794
5937
  "key-remove": {
@@ -5844,17 +5987,24 @@ async function renderCurrentStep(ctx, state) {
5844
5987
  const options = await stepOptions(step, ctx, state.data);
5845
5988
  if (step.kind === "options" && options) {
5846
5989
  if (ctx.channel.sendCard) {
5847
- await ctx.channel.sendCard(
5848
- ctx.chatId,
5849
- renderWizardOptionsCard({
5990
+ try {
5991
+ await ctx.channel.sendCard(
5992
+ ctx.chatId,
5993
+ renderWizardOptionsCard({
5994
+ flow: flow.id,
5995
+ step: state.step,
5996
+ question: step.question,
5997
+ options,
5998
+ ...step.hint === void 0 ? {} : { hint: step.hint }
5999
+ })
6000
+ );
6001
+ return;
6002
+ } catch (error) {
6003
+ log.warn("wizard", "card-send-failed", {
5850
6004
  flow: flow.id,
5851
- step: state.step,
5852
- question: step.question,
5853
- options,
5854
- ...step.hint === void 0 ? {} : { hint: step.hint }
5855
- })
5856
- );
5857
- return;
6005
+ error: error instanceof Error ? error.message : String(error)
6006
+ });
6007
+ }
5858
6008
  }
5859
6009
  const labels = options.map((option) => `\`${option.label}\``).join("\u3001");
5860
6010
  await ctx.channel.sendMarkdown(
@@ -5868,17 +6018,25 @@ ${labels}
5868
6018
  return;
5869
6019
  }
5870
6020
  if (ctx.channel.sendCard) {
5871
- await ctx.channel.sendCard(
5872
- ctx.chatId,
5873
- renderWizardTextStepCard({
6021
+ try {
6022
+ await ctx.channel.sendCard(
6023
+ ctx.chatId,
6024
+ renderWizardTextStepCard({
6025
+ flow: flow.id,
6026
+ step: state.step,
6027
+ question: step.question,
6028
+ ...step.optional === true ? {} : { required: true },
6029
+ ...step.placeholder === void 0 ? {} : { placeholder: step.placeholder },
6030
+ ...step.hint === void 0 ? {} : { hint: step.hint }
6031
+ })
6032
+ );
6033
+ return;
6034
+ } catch (error) {
6035
+ log.warn("wizard", "card-send-failed", {
5874
6036
  flow: flow.id,
5875
- step: state.step,
5876
- question: step.question,
5877
- ...step.placeholder === void 0 ? {} : { placeholder: step.placeholder },
5878
- ...step.hint === void 0 ? {} : { hint: step.hint }
5879
- })
5880
- );
5881
- return;
6037
+ error: error instanceof Error ? error.message : String(error)
6038
+ });
6039
+ }
5882
6040
  }
5883
6041
  await ctx.channel.sendMarkdown(
5884
6042
  ctx.chatId,
@@ -6019,11 +6177,17 @@ async function showConfigHub(ctx) {
6019
6177
  const defaultSelection = await ctx.dshConfig.defaultModelSelection();
6020
6178
  const currentModel = ctx.models.get(ctx.scope) ?? ctx.defaultModel;
6021
6179
  if (ctx.channel.sendCard) {
6022
- await ctx.channel.sendCard(
6023
- ctx.chatId,
6024
- renderConfigHubCard({ providers, defaultSelection, currentModel })
6025
- );
6026
- return;
6180
+ try {
6181
+ await ctx.channel.sendCard(
6182
+ ctx.chatId,
6183
+ renderConfigHubCard({ providers, defaultSelection, currentModel })
6184
+ );
6185
+ return;
6186
+ } catch (error) {
6187
+ log.warn("wizard", "hub-card-send-failed", {
6188
+ error: error instanceof Error ? error.message : String(error)
6189
+ });
6190
+ }
6027
6191
  }
6028
6192
  const lines = providers.map((provider) => {
6029
6193
  const models = provider.models.map((model) => model.id).join(", ") || "(\u65E0)";
@@ -6956,9 +7120,17 @@ async function startChannel(deps) {
6956
7120
  defaultModel: deps.defaultModel,
6957
7121
  senderId: msg.senderId
6958
7122
  };
6959
- const handled = await tryHandleCommand(msg.content, context).catch((error) => {
7123
+ const handled = await tryHandleCommand(msg.content, context).catch(async (error) => {
6960
7124
  log.fail("channel-command", error, { scope });
6961
- return false;
7125
+ try {
7126
+ await commandChannel.sendMarkdown(
7127
+ msg.chatId,
7128
+ `\u26A0\uFE0F \u547D\u4EE4\u6267\u884C\u5931\u8D25\uFF1A${error instanceof Error ? error.message : String(error)}`,
7129
+ { replyTo: msg.messageId }
7130
+ );
7131
+ } catch {
7132
+ }
7133
+ return true;
6962
7134
  });
6963
7135
  if (!handled) {
6964
7136
  const queued = deps.pending.size(scope);
@@ -8413,6 +8585,9 @@ async function startBridgeEngine(options) {
8413
8585
  );
8414
8586
  return;
8415
8587
  }
8588
+ if (modelRoute && modelRoute.provider !== DEEPSEEK_PROVIDER) {
8589
+ await dshConfig.linkCredentialRefIfMissing(modelRoute.provider).catch(() => void 0);
8590
+ }
8416
8591
  const runInput = {
8417
8592
  scope,
8418
8593
  chatId: first.chatId,