dsh-lark-bot 0.15.5 → 0.15.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
@@ -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,
@@ -4495,7 +4555,7 @@ async function readOptional(file) {
4495
4555
  throw error;
4496
4556
  }
4497
4557
  }
4498
- function sleep(ms) {
4558
+ function sleep2(ms) {
4499
4559
  return new Promise((resolve5) => setTimeout(resolve5, ms));
4500
4560
  }
4501
4561
  async function withFileLock(filename, operation) {
@@ -4511,7 +4571,7 @@ async function withFileLock(filename, operation) {
4511
4571
  if (Date.now() >= deadline) {
4512
4572
  throw new Error(`dsh config lock timed out at ${lockPath}`);
4513
4573
  }
4514
- await sleep(80 + Math.random() * 120);
4574
+ await sleep2(80 + Math.random() * 120);
4515
4575
  }
4516
4576
  }
4517
4577
  try {
@@ -4742,6 +4802,25 @@ var DshProviderManager = class {
4742
4802
  }
4743
4803
  return true;
4744
4804
  }
4805
+ /**
4806
+ * Heal the common misconfiguration where a credential was stored under the
4807
+ * provider id (`/key set kingapi …`) but the provider never got an
4808
+ * apiKeyEnv. Links the ref to the matching pi-ai provider once; returns
4809
+ * true when a link was applied. Idempotent and a no-op when the provider
4810
+ * already has a ref or no matching credential exists.
4811
+ */
4812
+ async linkCredentialRefIfMissing(providerId) {
4813
+ const settings = await this.readSettings();
4814
+ const piAi = isMapLike(settings[PIAI_NAMESPACE]) ? settings[PIAI_NAMESPACE] : {};
4815
+ const providers = isMapLike(piAi.providers) ? piAi.providers : {};
4816
+ const section = isMapLike(providers[providerId]) ? providers[providerId] : {};
4817
+ if (typeof section.apiKeyEnv === "string" && section.apiKeyEnv.length > 0) {
4818
+ return false;
4819
+ }
4820
+ if (!await this.hasCredential(providerId)) return false;
4821
+ await this.upsertPiAiProvider({ id: providerId, apiKeyEnv: providerId });
4822
+ return true;
4823
+ }
4745
4824
  async addPiAiModel(providerId, input) {
4746
4825
  validateProviderId(providerId);
4747
4826
  const settings = await this.readSettings();
@@ -5127,9 +5206,24 @@ async function handleKey(args, ctx) {
5127
5206
  await reply(ctx, `\u5199\u5165\u5931\u8D25\uFF1A${error instanceof Error ? error.message : String(error)}`);
5128
5207
  return;
5129
5208
  }
5209
+ const providers = await ctx.dshConfig.listProviders();
5210
+ const target = providers.find(
5211
+ (provider) => provider.id === ref && provider.namespace === PIAI_NAMESPACE && provider.credentialRef === void 0
5212
+ );
5213
+ let autoLinked = false;
5214
+ if (target) {
5215
+ await ctx.dshConfig.upsertPiAiProvider({ id: ref, apiKeyEnv: ref });
5216
+ autoLinked = true;
5217
+ }
5130
5218
  await reply(
5131
5219
  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`
5220
+ [
5221
+ `\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`,
5222
+ ...autoLinked ? [
5223
+ "",
5224
+ `\u{1F517} \u5DF2\u81EA\u52A8\u628A provider \`${ref}\` \u7684 apiKeyEnv \u5173\u8054\u5230 \`${ref}\`\uFF08\u4E0B\u4E00\u8BF7\u6C42\u751F\u6548\uFF09\u3002`
5225
+ ] : []
5226
+ ].join("\n")
5133
5227
  );
5134
5228
  return;
5135
5229
  }
@@ -5788,7 +5882,22 @@ var FLOWS = {
5788
5882
  const ref = asString(data.ref);
5789
5883
  const value = asString(data.value);
5790
5884
  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`;
5885
+ const providers = await ctx.dshConfig.listProviders();
5886
+ const target = providers.find(
5887
+ (provider) => provider.id === ref && provider.namespace === "llm-pi-ai" && provider.credentialRef === void 0
5888
+ );
5889
+ let autoLinked = false;
5890
+ if (target) {
5891
+ await ctx.dshConfig.upsertPiAiProvider({ id: ref, apiKeyEnv: ref });
5892
+ autoLinked = true;
5893
+ }
5894
+ return [
5895
+ `\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`,
5896
+ ...autoLinked ? [
5897
+ "",
5898
+ `\u{1F517} \u5DF2\u81EA\u52A8\u628A provider \`${ref}\` \u7684 apiKeyEnv \u5173\u8054\u5230 \`${ref}\`\uFF08\u4E0B\u4E00\u8BF7\u6C42\u751F\u6548\uFF09\u3002`
5899
+ ] : []
5900
+ ].join("\n");
5792
5901
  }
5793
5902
  },
5794
5903
  "key-remove": {
@@ -5844,17 +5953,24 @@ async function renderCurrentStep(ctx, state) {
5844
5953
  const options = await stepOptions(step, ctx, state.data);
5845
5954
  if (step.kind === "options" && options) {
5846
5955
  if (ctx.channel.sendCard) {
5847
- await ctx.channel.sendCard(
5848
- ctx.chatId,
5849
- renderWizardOptionsCard({
5956
+ try {
5957
+ await ctx.channel.sendCard(
5958
+ ctx.chatId,
5959
+ renderWizardOptionsCard({
5960
+ flow: flow.id,
5961
+ step: state.step,
5962
+ question: step.question,
5963
+ options,
5964
+ ...step.hint === void 0 ? {} : { hint: step.hint }
5965
+ })
5966
+ );
5967
+ return;
5968
+ } catch (error) {
5969
+ log.warn("wizard", "card-send-failed", {
5850
5970
  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;
5971
+ error: error instanceof Error ? error.message : String(error)
5972
+ });
5973
+ }
5858
5974
  }
5859
5975
  const labels = options.map((option) => `\`${option.label}\``).join("\u3001");
5860
5976
  await ctx.channel.sendMarkdown(
@@ -5868,17 +5984,24 @@ ${labels}
5868
5984
  return;
5869
5985
  }
5870
5986
  if (ctx.channel.sendCard) {
5871
- await ctx.channel.sendCard(
5872
- ctx.chatId,
5873
- renderWizardTextStepCard({
5987
+ try {
5988
+ await ctx.channel.sendCard(
5989
+ ctx.chatId,
5990
+ renderWizardTextStepCard({
5991
+ flow: flow.id,
5992
+ step: state.step,
5993
+ question: step.question,
5994
+ ...step.placeholder === void 0 ? {} : { placeholder: step.placeholder },
5995
+ ...step.hint === void 0 ? {} : { hint: step.hint }
5996
+ })
5997
+ );
5998
+ return;
5999
+ } catch (error) {
6000
+ log.warn("wizard", "card-send-failed", {
5874
6001
  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;
6002
+ error: error instanceof Error ? error.message : String(error)
6003
+ });
6004
+ }
5882
6005
  }
5883
6006
  await ctx.channel.sendMarkdown(
5884
6007
  ctx.chatId,
@@ -6019,11 +6142,17 @@ async function showConfigHub(ctx) {
6019
6142
  const defaultSelection = await ctx.dshConfig.defaultModelSelection();
6020
6143
  const currentModel = ctx.models.get(ctx.scope) ?? ctx.defaultModel;
6021
6144
  if (ctx.channel.sendCard) {
6022
- await ctx.channel.sendCard(
6023
- ctx.chatId,
6024
- renderConfigHubCard({ providers, defaultSelection, currentModel })
6025
- );
6026
- return;
6145
+ try {
6146
+ await ctx.channel.sendCard(
6147
+ ctx.chatId,
6148
+ renderConfigHubCard({ providers, defaultSelection, currentModel })
6149
+ );
6150
+ return;
6151
+ } catch (error) {
6152
+ log.warn("wizard", "hub-card-send-failed", {
6153
+ error: error instanceof Error ? error.message : String(error)
6154
+ });
6155
+ }
6027
6156
  }
6028
6157
  const lines = providers.map((provider) => {
6029
6158
  const models = provider.models.map((model) => model.id).join(", ") || "(\u65E0)";
@@ -6956,9 +7085,17 @@ async function startChannel(deps) {
6956
7085
  defaultModel: deps.defaultModel,
6957
7086
  senderId: msg.senderId
6958
7087
  };
6959
- const handled = await tryHandleCommand(msg.content, context).catch((error) => {
7088
+ const handled = await tryHandleCommand(msg.content, context).catch(async (error) => {
6960
7089
  log.fail("channel-command", error, { scope });
6961
- return false;
7090
+ try {
7091
+ await commandChannel.sendMarkdown(
7092
+ msg.chatId,
7093
+ `\u26A0\uFE0F \u547D\u4EE4\u6267\u884C\u5931\u8D25\uFF1A${error instanceof Error ? error.message : String(error)}`,
7094
+ { replyTo: msg.messageId }
7095
+ );
7096
+ } catch {
7097
+ }
7098
+ return true;
6962
7099
  });
6963
7100
  if (!handled) {
6964
7101
  const queued = deps.pending.size(scope);
@@ -8413,6 +8550,9 @@ async function startBridgeEngine(options) {
8413
8550
  );
8414
8551
  return;
8415
8552
  }
8553
+ if (modelRoute && modelRoute.provider !== DEEPSEEK_PROVIDER) {
8554
+ await dshConfig.linkCredentialRefIfMissing(modelRoute.provider).catch(() => void 0);
8555
+ }
8416
8556
  const runInput = {
8417
8557
  scope,
8418
8558
  chatId: first.chatId,