@ycodium-ai/plugin-im 0.2.3232 → 0.2.3238

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/dist/index.js CHANGED
@@ -126208,9 +126208,7 @@ var makeAcknowledgements = (core) => {
126208
126208
  };
126209
126209
 
126210
126210
  // src/botConfig.ts
126211
- import {
126212
- PLUGIN_PROXY_DIRECT
126213
- } from "@ycodium-ai/plugin-api";
126211
+ import { PLUGIN_PROXY_DIRECT } from "@ycodium-ai/plugin-api";
126214
126212
 
126215
126213
  // src/attachedThreads.ts
126216
126214
  var ATTACHED_THREAD_LIMIT = 200;
@@ -126510,6 +126508,23 @@ var makeConclusionLedger = (core) => {
126510
126508
  };
126511
126509
  };
126512
126510
 
126511
+ // src/sessionTransfer.ts
126512
+ var foldSessionTransferred = (state, data) => {
126513
+ const chatKey = data["chatKey"];
126514
+ const projectId = data["projectId"];
126515
+ const threadId = data["threadId"];
126516
+ if (typeof chatKey !== "string" || chatKey === "" || typeof projectId !== "string" || projectId === "" || typeof threadId !== "string" || threadId === "")
126517
+ return state;
126518
+ const chats = { ...state.chats };
126519
+ for (const [key, record4] of Object.entries(chats)) {
126520
+ if (key !== chatKey && record4.threadId === threadId) {
126521
+ chats[key] = { ...record4, threadId: null };
126522
+ }
126523
+ }
126524
+ chats[chatKey] = { ...chatOf(state, chatKey), projectId, threadId, target: { kind: "thread" } };
126525
+ return { ...state, chats };
126526
+ };
126527
+
126513
126528
  // src/chatState.ts
126514
126529
  var IM_STATE_KEY = "chats";
126515
126530
  var IM_STATE_VERSION = 1;
@@ -126676,6 +126691,8 @@ var withChat = (state, chatKey, patch) => ({
126676
126691
  var foldImEvent = (state, name, data) => {
126677
126692
  if (!isRecord(data)) return state;
126678
126693
  switch (name) {
126694
+ case "session-transferred":
126695
+ return foldSessionTransferred(state, data);
126679
126696
  case "conclusion-owed":
126680
126697
  case "conclusion-delivered":
126681
126698
  return foldConclusionEvent(state, name, data);
@@ -127248,6 +127265,68 @@ var buildDeliverySettingsFields = () => [
127248
127265
  }
127249
127266
  ];
127250
127267
 
127268
+ // src/modelChoice.ts
127269
+ var MODEL_LIST_MAX = 30;
127270
+ var parseModelValue = (value2) => {
127271
+ const separator = value2.indexOf("/");
127272
+ return separator <= 0 || separator === value2.length - 1 ? null : { instanceId: value2.slice(0, separator), model: value2.slice(separator + 1) };
127273
+ };
127274
+ var modelValue = (selection) => selection === null ? "" : `${selection.instanceId}/${selection.model}`;
127275
+ var isModelCatalogComplete = (instances) => instances !== null && instances.every((instance) => !instance.enabled || instance.models !== void 0);
127276
+ var modelChoices = (instances) => instances.flatMap(
127277
+ (instance) => instance.enabled ? (instance.models ?? []).map(
127278
+ (model) => ({
127279
+ instanceId: instance.instanceId,
127280
+ model: model.slug,
127281
+ instanceName: instance.displayName ?? instance.instanceId,
127282
+ modelName: model.name === "" ? model.slug : model.name
127283
+ })
127284
+ ) : []
127285
+ );
127286
+ var modelLabel = (choice) => `${choice.instanceName} \xB7 ${choice.modelName}`;
127287
+ var rawModelLabel = (selection) => `${selection.instanceId} \xB7 ${selection.model}`;
127288
+ var sameModel = (left, right) => left.instanceId === right.instanceId && left.model === right.model;
127289
+ var modelCatalogHas = (choices, selection) => choices.some((choice) => sameModel(choice, selection));
127290
+ var formatModels = (current, choices, t) => {
127291
+ const currentLabel = current === null ? t("model.currentUnknown") : t("model.current", {
127292
+ model: modelLabel(
127293
+ choices.find((choice) => sameModel(choice, current)) ?? {
127294
+ instanceName: current.instanceId,
127295
+ modelName: current.model
127296
+ }
127297
+ )
127298
+ });
127299
+ if (choices.length === 0) return [currentLabel, t("model.unavailable")].join("\n");
127300
+ const shown = choices.slice(0, MODEL_LIST_MAX);
127301
+ return [
127302
+ currentLabel,
127303
+ "",
127304
+ t("model.listTitle"),
127305
+ ...shown.map(
127306
+ (choice, index) => `${String(index + 1)}. ${current !== null && sameModel(choice, current) ? "\u2605 " : ""}${modelLabel(choice)}`
127307
+ ),
127308
+ ...choices.length > shown.length ? [t("model.more", { count: choices.length - shown.length })] : [],
127309
+ "",
127310
+ t("model.footer")
127311
+ ].join("\n");
127312
+ };
127313
+ var pickModel = (arg, choices) => {
127314
+ const wanted = arg.trim();
127315
+ if (wanted === "") return null;
127316
+ if (/^\d+$/.test(wanted)) return choices.slice(0, MODEL_LIST_MAX)[Number(wanted) - 1] ?? null;
127317
+ const needle = wanted.toLowerCase();
127318
+ const selection = parseModelValue(needle);
127319
+ const qualified = choices.filter(
127320
+ (choice) => selection !== null && choice.instanceId.toLowerCase() === selection.instanceId && (choice.model.toLowerCase() === selection.model || choice.modelName.toLowerCase() === selection.model)
127321
+ );
127322
+ if (qualified.length === 1) return qualified[0] ?? null;
127323
+ if (qualified.length > 1) return null;
127324
+ const named = choices.filter(
127325
+ (choice) => choice.model.toLowerCase() === needle || choice.modelName.toLowerCase() === needle
127326
+ );
127327
+ return named.length === 1 ? named[0] ?? null : null;
127328
+ };
127329
+
127251
127330
  // src/botConfig.ts
127252
127331
  var BOTS_FIELD = "bots";
127253
127332
  var REPLY_LANGUAGE_FIELD = "replyLanguage";
@@ -127267,7 +127346,9 @@ var QUEUE_MODES = new Set(QUEUE_MODE_OPTIONS.map((option2) => option2.value));
127267
127346
  var DEFAULT_QUEUE_MODE = "queue";
127268
127347
  var ACCESS_MODE_OPTIONS = [
127269
127348
  { value: "allowlist", labelKey: "settings.accessMode.allowlist" },
127270
- { value: "open", labelKey: "settings.accessMode.open" }
127349
+ { value: "open", labelKey: "settings.accessMode.open" },
127350
+ { value: "privateOpen", labelKey: "settings.accessMode.privateOpen" },
127351
+ { value: "groupOpen", labelKey: "settings.accessMode.groupOpen" }
127271
127352
  ];
127272
127353
  var ACCESS_MODES = new Set(
127273
127354
  ACCESS_MODE_OPTIONS.map((option2) => option2.value)
@@ -127291,9 +127372,10 @@ var parseOnOff = (value2, defaultOn) => {
127291
127372
  if (value2 === "off") return false;
127292
127373
  return defaultOn;
127293
127374
  };
127294
- var buildBotColumns = (adapters) => {
127375
+ var buildBotColumns = (adapters, models = [], catalogComplete = true) => {
127295
127376
  const platformColumn = {
127296
127377
  key: "platform",
127378
+ group: "essential",
127297
127379
  kind: "select",
127298
127380
  labelKey: "column.platform.label",
127299
127381
  options: adapters.map((adapter) => ({
@@ -127312,6 +127394,7 @@ var buildBotColumns = (adapters) => {
127312
127394
  return [
127313
127395
  {
127314
127396
  key: "displayName",
127397
+ group: "essential",
127315
127398
  kind: "text",
127316
127399
  labelKey: "column.displayName.label",
127317
127400
  placeholderKey: "column.displayName.placeholder"
@@ -127320,6 +127403,7 @@ var buildBotColumns = (adapters) => {
127320
127403
  ...credentialColumns,
127321
127404
  {
127322
127405
  key: "accessMode",
127406
+ group: "essential",
127323
127407
  kind: "select",
127324
127408
  labelKey: "column.accessMode.label",
127325
127409
  descriptionKey: "column.accessMode.description",
@@ -127327,22 +127411,47 @@ var buildBotColumns = (adapters) => {
127327
127411
  },
127328
127412
  {
127329
127413
  key: "allowedSenders",
127414
+ group: "essential",
127330
127415
  kind: "text",
127331
127416
  labelKey: "column.allowedSenders.label",
127332
127417
  descriptionKey: "column.allowedSenders.description",
127333
- // 缺省/空串=allowlist(与 parseAccessMode 一致),仍显示白名单框。
127334
- showWhen: { key: "accessMode", equals: ["allowlist", ""] }
127418
+ // 除了两边都放开的 open,哪一档都至少有一边按名单;缺省/空串=allowlist,也显示。
127419
+ showWhen: {
127420
+ key: "accessMode",
127421
+ equals: [
127422
+ ...ACCESS_MODE_OPTIONS.flatMap(({ value: value2 }) => value2 === "open" ? [] : [value2]),
127423
+ ""
127424
+ ]
127425
+ }
127335
127426
  },
127336
127427
  {
127337
127428
  // 选项由宿主填,连三态一起——插件只声明「这个格子装的是代理」,宿主因此
127338
127429
  // 也就认得出这一行引用了哪个代理,删代理时拦得住(路线图 #64)。
127339
127430
  key: "proxy",
127431
+ group: "more",
127340
127432
  kind: "proxy",
127341
127433
  labelKey: "column.proxy.label",
127342
127434
  descriptionKey: "column.proxy.description"
127343
127435
  },
127436
+ {
127437
+ key: "defaultModel",
127438
+ group: "more",
127439
+ kind: "select",
127440
+ listsUnknownValue: true,
127441
+ labelKey: "column.defaultModel.label",
127442
+ descriptionKey: "column.defaultModel.description",
127443
+ ...catalogComplete ? { unavailableLabelKey: "settings.defaultModel.unavailable" } : {},
127444
+ options: [
127445
+ { value: "", labelKey: "settings.defaultModel.follow" },
127446
+ ...models.map((choice) => ({
127447
+ value: modelValue(choice),
127448
+ label: modelLabel(choice)
127449
+ }))
127450
+ ]
127451
+ },
127344
127452
  {
127345
127453
  key: "runtimeMode",
127454
+ group: "more",
127346
127455
  kind: "select",
127347
127456
  labelKey: "column.runtimeMode.label",
127348
127457
  descriptionKey: "column.runtimeMode.description",
@@ -127355,6 +127464,7 @@ var buildBotColumns = (adapters) => {
127355
127464
  },
127356
127465
  {
127357
127466
  key: "groupMention",
127467
+ group: "more",
127358
127468
  kind: "select",
127359
127469
  labelKey: "column.groupMention.label",
127360
127470
  descriptionKey: "column.groupMention.description",
@@ -127366,6 +127476,7 @@ var buildBotColumns = (adapters) => {
127366
127476
  },
127367
127477
  {
127368
127478
  key: "queueMode",
127479
+ group: "more",
127369
127480
  kind: "select",
127370
127481
  labelKey: "column.queueMode.label",
127371
127482
  descriptionKey: "column.queueMode.description",
@@ -127373,6 +127484,7 @@ var buildBotColumns = (adapters) => {
127373
127484
  },
127374
127485
  {
127375
127486
  key: "larkPanelMode",
127487
+ group: "more",
127376
127488
  kind: "select",
127377
127489
  labelKey: "column.larkPanelMode.label",
127378
127490
  descriptionKey: "column.larkPanelMode.description",
@@ -127381,6 +127493,7 @@ var buildBotColumns = (adapters) => {
127381
127493
  },
127382
127494
  {
127383
127495
  key: "wecomNumberedHints",
127496
+ group: "more",
127384
127497
  kind: "select",
127385
127498
  labelKey: "column.wecomNumberedHints.label",
127386
127499
  descriptionKey: "column.wecomNumberedHints.description",
@@ -127434,10 +127547,12 @@ var readBot = (adapterFor, rowId, row) => {
127434
127547
  const adapter = adapterFor(platform);
127435
127548
  if (adapter === void 0) return null;
127436
127549
  const requested = readString(row, "runtimeMode");
127550
+ const defaultModel = parseModelValue(readString(row, "defaultModel"));
127437
127551
  const allowedSenders = new Set(parseAllowedSenders(readString(row, "allowedSenders")));
127438
127552
  const bot = {
127439
127553
  ...readBotView(rowId, row, adapter),
127440
127554
  platform,
127555
+ ...defaultModel === null ? {} : { defaultModel },
127441
127556
  accessMode: parseAccessMode(readString(row, "accessMode")),
127442
127557
  allowedSenders,
127443
127558
  runtimeMode: INBOUND_RUNTIME_MODES.has(requested) ? requested : DEFAULT_INBOUND_RUNTIME_MODE,
@@ -127466,9 +127581,10 @@ var findBindingByChatKey = (bots, state, chatKey) => {
127466
127581
  const bot = bots.find((candidate) => isChatKeyOfRow(chatKey, candidate.rowId));
127467
127582
  return bot === void 0 ? void 0 : bindingOf(bot, chatKey.slice(bot.rowId.length + 1), state);
127468
127583
  };
127469
- var isAllowedSender = (bot, sender) => {
127584
+ var isAllowedSender = (bot, sender, chatKind) => {
127470
127585
  if (sender === null || sender === "") return false;
127471
- if (bot.accessMode === "open") return true;
127586
+ if (bot.accessMode === "open" || bot.accessMode === "privateOpen" && chatKind === "private" || bot.accessMode === "groupOpen" && chatKind === "group")
127587
+ return true;
127472
127588
  return bot.allowedSenders.has(sender);
127473
127589
  };
127474
127590
 
@@ -127613,7 +127729,7 @@ var makeApprovals = (core, anchors) => {
127613
127729
  if (found === void 0) return null;
127614
127730
  if (found.approval.resolved) return t("approval.alreadyHandled");
127615
127731
  const target2 = bindingForChatKey(found.approval.chatKey);
127616
- if (target2 === void 0 || !isAllowedSender(target2, operatorId)) {
127732
+ if (target2 === void 0 || !isAllowedSender(target2, operatorId, chatOf(core.state, found.approval.chatKey).chatKind)) {
127617
127733
  host.log.warn("chat ignored an approval click from someone not on the allow list", {
127618
127734
  bot: target2?.displayName ?? found.approval.chatKey,
127619
127735
  operator: operatorId
@@ -127720,16 +127836,19 @@ var formatProjects = (rows, t) => {
127720
127836
  ].join("\n");
127721
127837
  };
127722
127838
  var listedProjectIds = (rows) => rows.slice(0, PROJECT_LIST_MAX).map((row) => row.projectId);
127839
+ var isProjectNumber = (arg) => /^[1-9]\d*$/u.test(arg.trim());
127840
+ var pickProjectNumber = (arg, listed, rows) => {
127841
+ if (!isProjectNumber(arg)) return null;
127842
+ const ids = listed ?? listedProjectIds(rows);
127843
+ return ids[Number(arg.trim()) - 1] ?? null;
127844
+ };
127723
127845
  var pickProject = (arg, listed, rows) => {
127724
127846
  const wanted = arg.trim();
127725
127847
  if (wanted === "") return null;
127726
127848
  const byId = rows.find((row) => row.projectId === wanted);
127727
127849
  if (byId !== void 0) return byId.projectId;
127728
- if (/^[1-9]\d*$/u.test(wanted)) {
127729
- const ids = listed ?? listedProjectIds(rows);
127730
- const byNumber = ids[Number(wanted) - 1];
127731
- if (byNumber !== void 0) return byNumber;
127732
- }
127850
+ const byNumber = pickProjectNumber(wanted, listed, rows);
127851
+ if (byNumber !== null) return byNumber;
127733
127852
  const needle = wanted.toLowerCase();
127734
127853
  const exact = rows.filter((row) => row.title.toLowerCase() === needle);
127735
127854
  if (exact.length > 0) return exact.length === 1 ? exact[0]?.projectId ?? null : null;
@@ -127774,29 +127893,42 @@ var sessionLine = (row, position, now, t) => {
127774
127893
  ].filter((badge) => badge !== null);
127775
127894
  return [`${String(position)}. ${title}`, age, ...badges, shortId(row.threadId)].filter((part) => part !== "").join(" \xB7 ");
127776
127895
  };
127777
- var formatSessions = (rows, now, t) => {
127778
- if (rows.length === 0) return t("sessions.empty");
127896
+ var formatSessions = (rows, now, t, options = {}) => {
127897
+ if (rows.length === 0) return options.empty ?? t("sessions.empty");
127779
127898
  const shown = rows.slice(0, SESSION_LIST_MAX);
127780
127899
  return [
127781
- t("sessions.title", { count: rows.length }),
127900
+ options.title ?? t("sessions.title", { count: rows.length }),
127782
127901
  ...shown.map((row, index) => sessionLine(row, index + 1, now, t)),
127783
127902
  ...rows.length > shown.length ? [t("sessions.more", { count: rows.length - shown.length })] : [],
127784
127903
  "",
127785
- t("sessions.footer")
127904
+ options.footer ?? t("sessions.footer")
127786
127905
  ].join("\n");
127787
127906
  };
127907
+ var formatProjectSessions = (rows, now, t, project) => formatSessions(rows, now, t, {
127908
+ title: t("sessions.otherProjectTitle", { project, count: rows.length }),
127909
+ empty: `${t("sessions.project", { title: project })}
127910
+ ${t("sessions.projectEmpty")}`,
127911
+ footer: t("sessions.otherProjectFooter")
127912
+ });
127788
127913
  var listedThreadIds = (rows) => rows.slice(0, SESSION_LIST_MAX).map((row) => row.threadId);
127789
127914
  var looksLikeListNumber = (wanted) => /^[1-9]\d*$/u.test(wanted) && wanted.length <= String(SESSION_LIST_MAX).length;
127790
- var pickSession = (arg, listed, rows) => {
127915
+ var matchSession = (arg, listed, rows) => {
127791
127916
  const wanted = arg.trim();
127792
- if (wanted === "") return null;
127917
+ if (wanted === "") return { kind: "notFound" };
127793
127918
  if (looksLikeListNumber(wanted)) {
127794
127919
  const ids = listed ?? listedThreadIds(rows);
127795
- return ids[Number(wanted) - 1] ?? null;
127920
+ const threadId2 = ids[Number(wanted) - 1];
127921
+ return threadId2 === void 0 ? { kind: "notFound" } : { kind: "unique", threadId: threadId2 };
127796
127922
  }
127797
127923
  const needle = wanted.toLowerCase();
127798
127924
  const matches = rows.filter((row) => row.threadId.toLowerCase().startsWith(needle));
127799
- return matches.length === 1 ? matches[0]?.threadId ?? null : null;
127925
+ if (matches.length > 1) return { kind: "ambiguous" };
127926
+ const threadId = matches[0]?.threadId;
127927
+ return threadId === void 0 ? { kind: "notFound" } : { kind: "unique", threadId };
127928
+ };
127929
+ var pickSession = (arg, listed, rows) => {
127930
+ const match5 = matchSession(arg, listed, rows);
127931
+ return match5.kind === "unique" ? match5.threadId : null;
127800
127932
  };
127801
127933
 
127802
127934
  // src/chatSetup.ts
@@ -133162,59 +133294,31 @@ var SimpleChatCommand = Struct({
133162
133294
  "help",
133163
133295
  "panel",
133164
133296
  "selectTeam",
133165
- "selectThread",
133166
- "sessions"
133297
+ "selectThread"
133167
133298
  ])
133168
133299
  });
133169
133300
  var PlanCommandMode = Literals(["toggle", "on", "off", "invalid"]);
133170
133301
  var ParsedChatCommand = Union2([
133171
133302
  SimpleChatCommand,
133303
+ Struct({ kind: Literal2("sessions"), arg: optionalKey2(String4) }),
133172
133304
  Struct({ kind: Literals(["resume", "model", "repo"]), arg: String4 }),
133173
133305
  Struct({ kind: Literal2("plan"), mode: PlanCommandMode }),
133174
133306
  Struct({ kind: Literal2("group"), name: String4 })
133175
133307
  ]);
133176
- var teamArgument = (text3, chinese) => {
133177
- const match5 = text3.match(/^\/team(?:\s+([\s\S]*))?$/i);
133178
- if (match5) return (match5[1] ?? "").trim();
133179
- if (!chinese) return void 0;
133180
- const alias = text3.match(/^换团队(?:\s+([\s\S]*))?$/);
133181
- return alias ? (alias[1] ?? "").trim() : void 0;
133182
- };
133183
- var resumeArgument = (text3, chinese) => {
133184
- const match5 = text3.match(/^\/resume(?:\s+([\s\S]*))?$/i);
133185
- if (match5) return (match5[1] ?? "").trim();
133186
- if (!chinese) return void 0;
133187
- const alias = text3.match(/^切换会话(?:\s+([\s\S]*))?$/);
133188
- return alias ? (alias[1] ?? "").trim() : void 0;
133189
- };
133190
- var modelArgument = (text3, chinese) => {
133191
- const match5 = text3.match(/^\/model(?:\s+([\s\S]*))?$/i);
133192
- if (match5) return (match5[1] ?? "").trim();
133193
- if (!chinese) return void 0;
133194
- const alias = text3.match(/^换模型(?:\s+([\s\S]*))?$/);
133195
- return alias ? (alias[1] ?? "").trim() : void 0;
133196
- };
133197
- var planArgument = (text3, chinese) => {
133198
- const match5 = text3.match(/^\/plan(?:\s+([\s\S]*))?$/i);
133199
- if (match5) return (match5[1] ?? "").trim().toLowerCase();
133200
- if (!chinese) return void 0;
133201
- const alias = text3.match(/^计划模式(?:\s+([\s\S]*))?$/);
133202
- return alias ? (alias[1] ?? "").trim().toLowerCase() : void 0;
133203
- };
133204
- var repoArgument = (text3, chinese) => {
133205
- const match5 = text3.match(/^\/repo(?:\s+([\s\S]*))?$/i);
133206
- if (match5) return (match5[1] ?? "").trim();
133207
- if (!chinese) return void 0;
133208
- const alias = text3.match(/^换项目(?:\s+([\s\S]*))?$/);
133209
- return alias ? (alias[1] ?? "").trim() : void 0;
133210
- };
133211
- var groupArgument = (text3, chinese) => {
133212
- const match5 = text3.match(/^\/group(?:\s+([\s\S]*))?$/i);
133308
+ var commandArgument = (text3, chinese, english, aliasPattern) => {
133309
+ const match5 = text3.match(english);
133213
133310
  if (match5) return (match5[1] ?? "").trim();
133214
133311
  if (!chinese) return void 0;
133215
- const alias = text3.match(/^建群(?:\s+([\s\S]*))?$/);
133312
+ const alias = text3.match(aliasPattern);
133216
133313
  return alias ? (alias[1] ?? "").trim() : void 0;
133217
133314
  };
133315
+ var teamArgument = (text3, chinese) => commandArgument(text3, chinese, /^\/team(?:\s+([\s\S]*))?$/i, /^换团队(?:\s+([\s\S]*))?$/);
133316
+ var sessionsArgument = (text3, chinese) => commandArgument(text3, chinese, /^\/sessions(?:\s+([\s\S]*))?$/i, /^会话列表(?:\s+([\s\S]*))?$/);
133317
+ var resumeArgument = (text3, chinese) => commandArgument(text3, chinese, /^\/resume(?:\s+([\s\S]*))?$/i, /^切换会话(?:\s+([\s\S]*))?$/);
133318
+ var modelArgument = (text3, chinese) => commandArgument(text3, chinese, /^\/model(?:\s+([\s\S]*))?$/i, /^换模型(?:\s+([\s\S]*))?$/);
133319
+ var planArgument = (text3, chinese) => commandArgument(text3, chinese, /^\/plan(?:\s+([\s\S]*))?$/i, /^计划模式(?:\s+([\s\S]*))?$/);
133320
+ var repoArgument = (text3, chinese) => commandArgument(text3, chinese, /^\/repo(?:\s+([\s\S]*))?$/i, /^换项目(?:\s+([\s\S]*))?$/);
133321
+ var groupArgument = (text3, chinese) => commandArgument(text3, chinese, /^\/group(?:\s+([\s\S]*))?$/i, /^建群(?:\s+([\s\S]*))?$/);
133218
133322
  var planModeOf = (argument, chinese) => {
133219
133323
  if (argument === "") return "toggle";
133220
133324
  if (argument === "on" || chinese && argument === "\u5F00") return "on";
@@ -133230,8 +133334,7 @@ var ZH_ALIASES = {
133230
133334
  \u72B6\u6001: "status",
133231
133335
  \u65B0\u5BF9\u8BDD: "new",
133232
133336
  \u5E2E\u52A9: "help",
133233
- \u9762\u677F: "panel",
133234
- \u4F1A\u8BDD\u5217\u8868: "sessions"
133337
+ \u9762\u677F: "panel"
133235
133338
  };
133236
133339
  var parseChatCommand = (input) => {
133237
133340
  const trimmed = input.text.trim();
@@ -133241,7 +133344,10 @@ var parseChatCommand = (input) => {
133241
133344
  if (/^\/new$/i.test(trimmed)) return { kind: "new" };
133242
133345
  if (/^\/help$/i.test(trimmed)) return { kind: "help" };
133243
133346
  if (/^\/panel$/i.test(trimmed)) return { kind: "panel" };
133244
- if (/^\/sessions$/i.test(trimmed)) return { kind: "sessions" };
133347
+ const sessions = sessionsArgument(trimmed, input.chinese);
133348
+ if (sessions !== void 0) {
133349
+ return sessions === "" ? { kind: "sessions" } : { kind: "sessions", arg: sessions };
133350
+ }
133245
133351
  const resume = resumeArgument(input.text, input.chinese);
133246
133352
  if (resume !== void 0) {
133247
133353
  return resume === "" ? { kind: "sessions" } : { kind: "resume", arg: resume };
@@ -133249,7 +133355,8 @@ var parseChatCommand = (input) => {
133249
133355
  const model = modelArgument(input.text, input.chinese);
133250
133356
  if (model !== void 0) return { kind: "model", arg: model };
133251
133357
  const plan = planArgument(input.text, input.chinese);
133252
- if (plan !== void 0) return { kind: "plan", mode: planModeOf(plan, input.chinese) };
133358
+ if (plan !== void 0)
133359
+ return { kind: "plan", mode: planModeOf(plan.toLowerCase(), input.chinese) };
133253
133360
  const repo = repoArgument(input.text, input.chinese);
133254
133361
  if (repo !== void 0) return { kind: "repo", arg: repo };
133255
133362
  const group = groupArgument(input.text, input.chinese);
@@ -133284,60 +133391,6 @@ var helpText = (t, options = {}) => [
133284
133391
  ...options.panel === true ? ["", t("help.panel")] : []
133285
133392
  ].join("\n");
133286
133393
 
133287
- // src/modelChoice.ts
133288
- var MODEL_LIST_MAX = 30;
133289
- var modelChoices = (instances) => instances.flatMap(
133290
- (instance) => instance.enabled ? (instance.models ?? []).map(
133291
- (model) => ({
133292
- instanceId: instance.instanceId,
133293
- model: model.slug,
133294
- instanceName: instance.displayName ?? instance.instanceId,
133295
- modelName: model.name === "" ? model.slug : model.name
133296
- })
133297
- ) : []
133298
- );
133299
- var modelLabel = (choice) => `${choice.instanceName} \xB7 ${choice.modelName}`;
133300
- var rawModelLabel = (selection) => `${selection.instanceId} \xB7 ${selection.model}`;
133301
- var sameModel = (left, right) => left.instanceId === right.instanceId && left.model === right.model;
133302
- var formatModels = (current, choices, t) => {
133303
- const currentLabel = current === null ? t("model.currentUnknown") : t("model.current", {
133304
- model: modelLabel(
133305
- choices.find((choice) => sameModel(choice, current)) ?? {
133306
- instanceName: current.instanceId,
133307
- modelName: current.model
133308
- }
133309
- )
133310
- });
133311
- if (choices.length === 0) return [currentLabel, t("model.unavailable")].join("\n");
133312
- const shown = choices.slice(0, MODEL_LIST_MAX);
133313
- return [
133314
- currentLabel,
133315
- "",
133316
- t("model.listTitle"),
133317
- ...shown.map(
133318
- (choice, index) => `${String(index + 1)}. ${current !== null && sameModel(choice, current) ? "\u2605 " : ""}${modelLabel(choice)}`
133319
- ),
133320
- ...choices.length > shown.length ? [t("model.more", { count: choices.length - shown.length })] : [],
133321
- "",
133322
- t("model.footer")
133323
- ].join("\n");
133324
- };
133325
- var pickModel = (arg, choices) => {
133326
- const wanted = arg.trim();
133327
- if (wanted === "") return null;
133328
- if (/^\d+$/.test(wanted)) return choices.slice(0, MODEL_LIST_MAX)[Number(wanted) - 1] ?? null;
133329
- const needle = wanted.toLowerCase();
133330
- const qualified = choices.filter(
133331
- (choice) => `${choice.instanceId}/${choice.model}`.toLowerCase() === needle || `${choice.instanceId}/${choice.modelName}`.toLowerCase() === needle
133332
- );
133333
- if (qualified.length === 1) return qualified[0] ?? null;
133334
- if (qualified.length > 1) return null;
133335
- const named = choices.filter(
133336
- (choice) => choice.model.toLowerCase() === needle || choice.modelName.toLowerCase() === needle
133337
- );
133338
- return named.length === 1 ? named[0] ?? null : null;
133339
- };
133340
-
133341
133394
  // src/commands.ts
133342
133395
  var NEEDS_PROJECT_COMMANDS = /* @__PURE__ */ new Set(["sessions", "resume", "selectTeam", "selectThread"]);
133343
133396
  var makeCommands = (core, deps) => {
@@ -133397,7 +133450,7 @@ var makeCommands = (core, deps) => {
133397
133450
  await deliver(binding, helpText(t, { panel: false }));
133398
133451
  return;
133399
133452
  case "sessions":
133400
- await showSessions(binding);
133453
+ await showSessions(binding, command.arg);
133401
133454
  return;
133402
133455
  case "resume":
133403
133456
  await resumeSession(binding, command.arg);
@@ -134296,8 +134349,8 @@ var startConnections = (core, inboundHandlersFor, inboundState, recovery) => {
134296
134349
  },
134297
134350
  onButton: (...args2) => closed || disposed ? null : inbound.onButton(...args2),
134298
134351
  onPanelClick: (click) => closed || disposed ? null : inbound.onPanelClick?.(click) ?? null,
134299
- onGreeting: (chatId, senderId) => {
134300
- if (!closed && !disposed) inbound.onGreeting?.(chatId, senderId);
134352
+ onGreeting: (chatId, senderId, chatKind) => {
134353
+ if (!closed && !disposed) inbound.onGreeting?.(chatId, senderId, chatKind);
134301
134354
  },
134302
134355
  onConnectionState: (status) => {
134303
134356
  if (closed) return;
@@ -134723,7 +134776,15 @@ var OUTBOUND_QUEUE_LIMIT = 100;
134723
134776
  var makeImCore = (input) => {
134724
134777
  const { host, t, adapters } = input;
134725
134778
  const adapterFor = (platform) => adapters.find((adapter) => adapter.platform === platform);
134726
- let state = emptyState();
134779
+ let confirmed = emptyState();
134780
+ let state = confirmed;
134781
+ const pending = [];
134782
+ const rebuildState = () => {
134783
+ state = pending.reduce(
134784
+ (current, event) => foldImEvent(current, event.name, event.data),
134785
+ confirmed
134786
+ );
134787
+ };
134727
134788
  const stateHandle = input.derivedState({
134728
134789
  key: IM_STATE_KEY,
134729
134790
  version: IM_STATE_VERSION,
@@ -134733,18 +134794,30 @@ var makeImCore = (input) => {
134733
134794
  });
134734
134795
  const ready = stateHandle.get().then(
134735
134796
  (restored) => {
134736
- state = restored;
134797
+ confirmed = restored;
134798
+ rebuildState();
134737
134799
  },
134738
134800
  (error) => {
134739
134801
  host.log.warn("chat could not restore its saved state", { error: describeError(error) });
134740
134802
  }
134741
134803
  );
134742
- let persisting = Promise.resolve();
134804
+ let persisting = ready;
134743
134805
  const commitDurably = (name, data) => {
134806
+ pending.push({ name, data });
134744
134807
  state = foldImEvent(state, name, data);
134745
134808
  const saved = persisting.then(
134746
134809
  () => host.events.append({ name, aggregate: { kind: "plugin", id: input.pluginId }, data })
134747
- ).then(() => void 0);
134810
+ ).then(
134811
+ () => {
134812
+ confirmed = foldImEvent(confirmed, name, data);
134813
+ pending.shift();
134814
+ },
134815
+ (error) => {
134816
+ pending.shift();
134817
+ rebuildState();
134818
+ throw error;
134819
+ }
134820
+ );
134748
134821
  persisting = saved.catch((error) => {
134749
134822
  host.log.warn("chat could not save its state", { name, error: describeError(error) });
134750
134823
  });
@@ -134872,6 +134945,17 @@ var diagnosticRef = (kind, nowMs = Date.now()) => {
134872
134945
  const stamp = nowMs.toString(36).toUpperCase();
134873
134946
  return `${PREFIX[kind]}-${stamp}-${shortSalt()}`;
134874
134947
  };
134948
+ var reportOf = (bot, stage, ok, detail, hint) => ({
134949
+ ref: diagnosticRef("conn"),
134950
+ platform: bot.platform,
134951
+ botName: bot.displayName,
134952
+ rowId: bot.rowId,
134953
+ stage,
134954
+ ok,
134955
+ detail,
134956
+ at: (/* @__PURE__ */ new Date()).toISOString(),
134957
+ ...hint === void 0 ? {} : { hint }
134958
+ });
134875
134959
  var formatDiagnosticCopy = (report) => [
134876
134960
  `ok=${report.ok ? "yes" : "no"}`,
134877
134961
  `platform=${report.platform}`,
@@ -135497,18 +135581,60 @@ var makeInboxReplay = (core, currentBot, state, enqueueForChat, resumeMessage, c
135497
135581
  };
135498
135582
  };
135499
135583
 
135584
+ // src/connectionTest.ts
135585
+ var connectionTestMessage = (t, botName, platformLabel) => t("reply.connectionTest", { name: botName.trim() || platformLabel });
135586
+ var makeConnectionTests = (t) => {
135587
+ const targets = /* @__PURE__ */ new Map();
135588
+ return {
135589
+ remember: (current, message, key) => {
135590
+ if (message.chatKind !== "private" || !isAllowedSender(current, message.senderId, message.chatKind))
135591
+ return;
135592
+ targets.set(current.rowId, { chatId: message.chatId, key });
135593
+ },
135594
+ retainRows: (rowIds) => {
135595
+ const kept = new Set(rowIds);
135596
+ for (const rowId of targets.keys()) if (!kept.has(rowId)) targets.delete(rowId);
135597
+ },
135598
+ send: async (bot, adapter) => {
135599
+ const remembered = targets.get(bot.rowId);
135600
+ if (remembered === void 0 || remembered.key !== connectionKey(adapter, bot))
135601
+ return { sent: false, code: "test-target-unavailable" };
135602
+ try {
135603
+ await adapter.sendText(
135604
+ { ...bot, chatId: remembered.chatId },
135605
+ connectionTestMessage(t, bot.displayName, adapter.label)
135606
+ );
135607
+ return { sent: true };
135608
+ } catch (error) {
135609
+ return {
135610
+ sent: false,
135611
+ code: "test-message-failed",
135612
+ error: reportOf(
135613
+ bot,
135614
+ "connection.test",
135615
+ false,
135616
+ botRedactor(adapter.columns, bot.values).redact(describeError(error))
135617
+ )
135618
+ };
135619
+ }
135620
+ }
135621
+ };
135622
+ };
135623
+
135500
135624
  // src/greetings.ts
135501
135625
  var GREETING_COOLDOWN_MS = 12 * 60 * 6e4;
135502
135626
  var GREETING_KEY_LIMIT = 1e3;
135503
135627
  var makeGreetings = (core, enqueueForChat, now) => {
135504
135628
  const cooldown = makeCooldown(GREETING_COOLDOWN_MS, now, GREETING_KEY_LIMIT);
135505
- return (rowId, chatId, senderId) => {
135629
+ return (rowId, chatId, senderId, eventChatKind) => {
135630
+ const chatKey = chatKeyOf({ rowId, chatId });
135631
+ const chatKind = () => eventChatKind ?? chatOf(core.state, chatKey).chatKind;
135506
135632
  const bot = core.bots().find((candidate) => candidate.rowId === rowId);
135507
- if (bot === void 0 || !isAllowedSender(bot, senderId)) return;
135633
+ if (bot === void 0 || !isAllowedSender(bot, senderId, chatKind())) return;
135508
135634
  if (!cooldown.tryAcquire(`${rowId}:${senderId}`)) return;
135509
- enqueueForChat(chatKeyOf({ rowId, chatId }), async () => {
135635
+ enqueueForChat(chatKey, async () => {
135510
135636
  const current = core.bots().find((candidate) => candidate.rowId === rowId);
135511
- if (current === void 0 || !isAllowedSender(current, senderId)) return;
135637
+ if (current === void 0 || !isAllowedSender(current, senderId, chatKind())) return;
135512
135638
  await core.deliver(core.bindingOf(current, chatId), core.t("reply.welcome"));
135513
135639
  });
135514
135640
  };
@@ -135561,6 +135687,7 @@ var NOT_ALLOWED_REPLY_KEY_LIMIT = 1e3;
135561
135687
  var makeInbound = (core, deps) => {
135562
135688
  const { host, t, adapterFor, commit, deliver, bindingOf: bindingOf2, bots } = core;
135563
135689
  const { enqueueForChat } = deps;
135690
+ const connectionTests = makeConnectionTests(t);
135564
135691
  const now = deps.now ?? deps.retryClock?.now ?? (() => Date.now());
135565
135692
  const greet = makeGreetings(core, enqueueForChat, now);
135566
135693
  const { targetOf } = deps.commands;
@@ -135721,7 +135848,7 @@ var makeInbound = (core, deps) => {
135721
135848
  await execute(work.route);
135722
135849
  return;
135723
135850
  }
135724
- if (!isAllowedSender(target2, message.senderId)) {
135851
+ if (!isAllowedSender(target2, message.senderId, message.chatKind)) {
135725
135852
  await saveRoute({ kind: "unavailable" });
135726
135853
  await complete();
135727
135854
  turnAwayStranger(target2, message.senderId);
@@ -135779,7 +135906,7 @@ var makeInbound = (core, deps) => {
135779
135906
  },
135780
135907
  noteChatAlias: (alias, chatId) => noteChatAlias(bot, alias, chatId),
135781
135908
  knowsChatAlias: (alias) => chatAliasOf(core.state, bot.rowId, alias) !== void 0,
135782
- onGreeting: (chatId, senderId) => greet(bot.rowId, chatId, senderId),
135909
+ onGreeting: (chatId, senderId, chatKind) => greet(bot.rowId, chatId, senderId, chatKind),
135783
135910
  onMessage: async (received) => {
135784
135911
  const current = currentBot(bot.rowId);
135785
135912
  if (!accepting || current === void 0) throw new Error("inbound is not accepting messages");
@@ -135797,6 +135924,9 @@ var makeInbound = (core, deps) => {
135797
135924
  }
135798
135925
  const claim = inboundState.claimReceipt(current.rowId, work.key, saved, now());
135799
135926
  if (!claim.first) return claim.persisted;
135927
+ const receivedAdapter = adapterFor(bot.platform);
135928
+ if (receivedAdapter !== void 0)
135929
+ connectionTests.remember(current, message, connectionKey(receivedAdapter, bot));
135800
135930
  const receipt = claim.persisted.catch((error) => {
135801
135931
  inboundState.abandon(current.rowId, work.key);
135802
135932
  host.log.warn(
@@ -135839,6 +135969,7 @@ var makeInbound = (core, deps) => {
135839
135969
  replay.enqueue(rowId, entry.key, ready, { entry, message: messages.get(entry.key) });
135840
135970
  };
135841
135971
  return {
135972
+ connectionTests,
135842
135973
  inboundHandlersFor,
135843
135974
  resumeMessage,
135844
135975
  enqueueReplay,
@@ -136467,7 +136598,8 @@ var makeInteractions = (core, deps) => {
136467
136598
  anchored: (binding, threadId) => deps.anchors.anchored(binding, threadId, null),
136468
136599
  allowed: (chatKey, operatorId) => {
136469
136600
  const binding = bindingForChatKey(chatKey);
136470
- if (binding !== void 0 && isAllowedSender(binding, operatorId)) return true;
136601
+ if (binding !== void 0 && isAllowedSender(binding, operatorId, chatOf(core.state, chatKey).chatKind))
136602
+ return true;
136471
136603
  host.log.warn("chat ignored a button click from someone not on the allow list", {
136472
136604
  bot: binding?.displayName ?? chatKey,
136473
136605
  operator: operatorId
@@ -138420,6 +138552,35 @@ var registerWecomSetup = (ctx, options = {}) => {
138420
138552
  });
138421
138553
  };
138422
138554
 
138555
+ // src/larkBotColumns.ts
138556
+ var larkBotColumns = [
138557
+ {
138558
+ key: "larkAppId",
138559
+ group: "essential",
138560
+ kind: "text",
138561
+ labelKey: "column.larkAppId.label",
138562
+ placeholderKey: "column.larkAppId.placeholder"
138563
+ },
138564
+ {
138565
+ key: "larkAppSecret",
138566
+ group: "essential",
138567
+ kind: "password",
138568
+ labelKey: "column.larkAppSecret.label"
138569
+ },
138570
+ // 被 @ 时答复要不要开话题。只有飞书适配器读它,没填过就是开(见 larkAdapter.ts 的 `topicOnMentionOf`)。
138571
+ {
138572
+ key: "larkTopicOnMention",
138573
+ group: "more",
138574
+ kind: "select",
138575
+ labelKey: "column.larkTopicOnMention.label",
138576
+ descriptionKey: "column.larkTopicOnMention.description",
138577
+ options: [
138578
+ { value: "on", labelKey: "settings.onOff.on" },
138579
+ { value: "off", labelKey: "settings.onOff.off" }
138580
+ ]
138581
+ }
138582
+ ];
138583
+
138423
138584
  // src/larkApi.ts
138424
138585
  import { parseRetryAfterMs } from "@ycodium-ai/plugin-api";
138425
138586
 
@@ -145264,7 +145425,7 @@ var en_default = {
145264
145425
  },
145265
145426
  settings: {
145266
145427
  title: "Chat",
145267
- description: "Connect chat bots to Ycodium. One row per bot: create the bot on its platform, paste its credentials, and pick an access mode (allowlist / open). In allowlist mode, list the people allowed to use it. For Lark, Connect a Lark bot does all of that with one scan. Admitted people message the bot (or add it to a group), pick a project in the chat, and can start work.",
145428
+ description: 'Connect chat bots to Ycodium. One row per bot: create the bot on its platform, paste its credentials, and pick one access mode that decides who can use it in private chats and groups. Where the allowlist applies, list the people in "Allowed people". For Lark, Connect a Lark bot does all of that with one scan. Admitted people message the bot (or add it to a group), pick a project in the chat, and can start work.',
145268
145429
  bots: "Bots",
145269
145430
  language: {
145270
145431
  label: "Reply language",
@@ -145272,6 +145433,10 @@ var en_default = {
145272
145433
  zhCN: "\u4E2D\u6587",
145273
145434
  en: "English"
145274
145435
  },
145436
+ defaultModel: {
145437
+ follow: "Follow chat / project default",
145438
+ unavailable: "{{value}} (unavailable)"
145439
+ },
145275
145440
  runtimeMode: {
145276
145441
  approvalRequired: "Ask before commands and file changes",
145277
145442
  autoAcceptEdits: "Auto-accept edits, ask before other actions",
@@ -145320,7 +145485,9 @@ var en_default = {
145320
145485
  },
145321
145486
  accessMode: {
145322
145487
  allowlist: "Allowlist only (default)",
145323
- open: "Allow everyone"
145488
+ open: "Allow everyone",
145489
+ privateOpen: "Private chats: open; groups: allowlist",
145490
+ groupOpen: "Private chats: allowlist; groups: open"
145324
145491
  }
145325
145492
  },
145326
145493
  platform: {
@@ -145356,12 +145523,16 @@ var en_default = {
145356
145523
  },
145357
145524
  allowedSenders: {
145358
145525
  label: "Allowed people",
145359
- description: "Comma-separated user ids from that platform. Used only in allowlist mode; while the list is empty, nobody can. Message the bot and it tells you your own id."
145526
+ description: "Comma-separated user ids from that platform. Used in chats where the access mode requires the allowlist; while the list is empty, nobody can use the bot in those chats. To find an id: from an account not yet on the list, message the bot in a chat that requires the allowlist (mention it in a group). The bot replies with the id, at most once every 10 minutes per account."
145360
145527
  },
145361
145528
  proxy: {
145362
145529
  label: "Proxy",
145363
145530
  description: "Some platforms are only reachable through a proxy on this machine, and which one can differ per bot."
145364
145531
  },
145532
+ defaultModel: {
145533
+ label: "Default model",
145534
+ description: "Used for new sessions when the chat has no /model selection. Existing sessions keep their model; send /new to use this default. Unavailable choices fall back to the project default."
145535
+ },
145365
145536
  runtimeMode: {
145366
145537
  label: "What a chat message may do",
145367
145538
  description: "Admitted people start turns from chat, so this is deliberately not full access by default. Whichever mode asks, the request lands in the chat."
@@ -145452,11 +145623,12 @@ var en_default = {
145452
145623
  },
145453
145624
  accessMode: {
145454
145625
  label: "Access mode",
145455
- description: 'Allowlist admits only "Allowed people"; open admits any identifiable sender. New bots default to allowlist.'
145626
+ description: 'Allowlist admits only "Allowed people"; open admits any identifiable sender. New bots default to allowlist. "Private chats: open; groups: allowlist" and "Private chats: allowlist; groups: open" share the same "Allowed people"; in either of them, when the chat type cannot be confirmed, only "Allowed people" can use the bot.'
145456
145627
  }
145457
145628
  },
145458
145629
  reply: {
145459
145630
  deliveryFailed: "The reply could not be delivered. Check this bot's connection and permissions, then try again.",
145631
+ connectionTest: "\u2705 Ycodium connection test\nThis test message was sent by \u201C{{name}}\u201D using Check connection in Settings. No reply needed.",
145460
145632
  wecomDeliveryFailed: "This reply didn't reach {{recipient}} in WeCom: they are outside this app's visible range. Add them to the app's visible range in the WeCom admin console, then ask the agent to send it again.",
145461
145633
  conclusionRecovered: "(Finished while restarting; delivered now)",
145462
145634
  notAllowed: "You are not on this bot's allow list, so this tap had no effect.",
@@ -145581,8 +145753,8 @@ var en_default = {
145581
145753
  team: "\xB7 /team \u2014 give this project's team a goal with each message; use /repo to pick the project first",
145582
145754
  thread: "\xB7 /thread \u2014 go back to a normal session",
145583
145755
  aliases: "Chinese shortcuts (\u505C / \u7EE7\u7EED\u53D1\u9001 / \u72B6\u6001 / \u65B0\u5BF9\u8BDD / \u4F1A\u8BDD\u5217\u8868 / \u5207\u6362\u4F1A\u8BDD / \u6362\u6A21\u578B / \u8BA1\u5212\u6A21\u5F0F / \u6362\u9879\u76EE / \u5EFA\u7FA4 / \u6362\u56E2\u961F / \u6362\u5BF9\u8BDD) work when the reply language is Chinese.",
145584
- sessions: "\xB7 /sessions \u2014 list the sessions this bot opened for this chat's project",
145585
- resume: "\xB7 /resume number \u2014 switch to one of them (a number from /sessions, or the start of its id)",
145756
+ sessions: "\xB7 /sessions \u2014 list this project's sessions; add a project number from /repo to list the ones this bot opened in another project",
145757
+ resume: "\xB7 /resume number-or-id-prefix \u2014 switch sessions; one from another project switches the project too",
145586
145758
  model: "\xB7 /model \u2014 see the model this chat uses; /model number or /model instance/model switches it",
145587
145759
  plan: "\xB7 /plan \u2014 toggle plan mode (the agent proposes a plan instead of acting); /plan on, /plan off set it",
145588
145760
  panel: "Send /panel to open the command panel with a button for every command.",
@@ -145591,12 +145763,18 @@ var en_default = {
145591
145763
  syncNote: "\xB7 After a private chat attaches a session, web user text and final assistant text sync here (groups never sync)."
145592
145764
  },
145593
145765
  sessions: {
145594
- title: "Sessions this bot opened for this chat's project ({{count}}):",
145595
- empty: "This bot has not opened any session for this chat's project yet. Send a message to start one.",
145766
+ title: "This project's sessions ({{count}}):",
145767
+ empty: "This bot has not opened any session in this project yet. Send a message to start one.",
145596
145768
  more: "{{count}} more not shown \u2014 reach them with /resume and the start of an id.",
145597
145769
  footer: "Switch with /resume number, or /resume and the start of an id.",
145598
145770
  untitled: "Untitled",
145599
145771
  unavailable: "Could not read the session list right now. Try again in a moment.",
145772
+ projectNotFound: "That project number does not exist. Send /repo to see the numbers.",
145773
+ projectNumberExpected: "Put a project number after /sessions (a number from /repo), for example /sessions 2.",
145774
+ project: 'Project: "{{title}}"',
145775
+ otherProjectTitle: 'Sessions this bot opened in project "{{project}}" ({{count}}):',
145776
+ otherProjectFooter: "You are still in this chat's current project. /resume number, or /resume with the start of an id (also an unlisted one this bot opened), switches there and switches the chat's project too.",
145777
+ projectEmpty: "This bot has not opened any session in that project yet. To start one there, send /repo with that project number to move this chat there, then send a message.",
145600
145778
  badge: {
145601
145779
  current: "\u2605 current",
145602
145780
  running: "\u25CF running",
@@ -145612,11 +145790,14 @@ var en_default = {
145612
145790
  },
145613
145791
  resume: {
145614
145792
  notFound: 'No session matches "{{query}}". Use /sessions to see the list.',
145793
+ ambiguous: 'More than one session starts with "{{query}}". Give a few more characters of the id.',
145615
145794
  busy: "A turn is running in this chat \u2014 send /stop first, then switch.",
145616
145795
  already: "This chat is already on that session.",
145617
145796
  gone: "That session was deleted or archived, so it cannot be resumed.",
145618
145797
  runningElsewhere: "That session is running a turn in another chat \u2014 stop it there first.",
145619
145798
  done: 'Switched to "{{title}}". Send a message to keep going from there.',
145799
+ doneInProject: 'Switched to project "{{project}}", session "{{title}}". Send a message to keep going from there.',
145800
+ switchFailed: "Could not switch to that session. The chat's project and session have not changed; send that /resume again in a moment.",
145620
145801
  confirmWeb: '"{{title}}" was opened on the web. Send /resume with the same number again (or /resume confirm) to attach; /resume cancel to abort.',
145621
145802
  cancelled: "Attach cancelled.",
145622
145803
  nothingPending: "No web session is waiting for confirm. Use /sessions to see the list."
@@ -145649,6 +145830,8 @@ var en_default = {
145649
145830
  interrupted: "Interrupted",
145650
145831
  continued: "Continues in the card below"
145651
145832
  },
145833
+ thinking: "Thinking\u2026",
145834
+ usingTool: "Using {{name}}\u2026",
145652
145835
  step: {
145653
145836
  think: "think",
145654
145837
  command: "command",
@@ -145842,11 +146025,20 @@ var en_default = {
145842
146025
  rowAction: {
145843
146026
  checkConnection: {
145844
146027
  label: "Check connection",
145845
- description: "Verify platform credentials still work; does not open a session or call a model.",
146028
+ description: "Verify credentials and send a test message to the most recent private chat; does not open a session or call a model.",
145846
146029
  running: "Checking {{rowId}}\u2026",
145847
146030
  done: "Connection OK",
146031
+ doneTestFailed: "Credentials OK, but the test message was not sent",
146032
+ doneNoTarget: "Credentials OK, but no test message was sent",
145848
146033
  failed: "Connection failed",
145849
146034
  notSaved: "Save this row first (credentials are read from saved config only).",
146035
+ credentialRef: "Credential check reference: {{ref}}\n{{detail}}",
146036
+ testMessage: {
146037
+ sent: "Sent to whoever last messaged the bot privately. Ask them to confirm.",
146038
+ "test-target-unavailable": "No private chat to test with yet. Message the bot privately from an account allowed to use the bot, then check again.",
146039
+ "test-message-failed": "Check that the bot can still send to that private chat (reason below), then check again.",
146040
+ ref: "Test message reference: {{ref}}\n{{detail}}"
146041
+ },
145850
146042
  hint: {
145851
146043
  networkMultipleCauses: "This failure has more than one network cause. Check this machine's network and this bot's Proxy setting, then check again.",
145852
146044
  networkDns: "The service's domain name could not be resolved. Check this machine's DNS and network settings, then check again.",
@@ -145981,11 +146173,14 @@ var en_default = {
145981
146173
  reference: "Reference {{referenceId}}"
145982
146174
  },
145983
146175
  empty: "No bots yet",
146176
+ emptyGuide: `Use "{{larkLabel}}" or "{{wecomLabel}}" to scan and create a bot in one step. For other platforms, choose "{{addBotLabel}}" and enter the credentials from that platform's developer console.`,
145984
146177
  unavailable: "This machine doesn't have the chat bridge right now.",
145985
146178
  loadFailed: "Couldn't load the chat-bridge settings.",
145986
146179
  retry: "Try again",
145987
146180
  general: "General",
145988
146181
  addBot: "Add a bot",
146182
+ moreSettings: "More settings",
146183
+ moreSettingsIssues: "More settings ({{count}} to fix)",
145989
146184
  save: "Save",
145990
146185
  revert: "Undo changes",
145991
146186
  saveFailed: "Couldn't save.",
@@ -146105,7 +146300,7 @@ var zh_CN_default = {
146105
146300
  },
146106
146301
  settings: {
146107
146302
  title: "\u804A\u5929",
146108
- description: "\u628A\u804A\u5929\u673A\u5668\u4EBA\u63A5\u5230 Ycodium\u3002\u4E00\u884C\u4E00\u4E2A\u673A\u5668\u4EBA\uFF1A\u5148\u5728\u5BF9\u5E94\u5E73\u53F0\u5EFA\u597D\u673A\u5668\u4EBA\u3001\u628A\u51ED\u636E\u586B\u8FDB\u6765\uFF0C\u518D\u9009\u8BBF\u95EE\u6A21\u5F0F\uFF08\u4EC5\u767D\u540D\u5355\uFF0F\u5141\u8BB8\u6240\u6709\u4EBA\uFF09\u3002\u4EC5\u767D\u540D\u5355\u65F6\u628A\u5141\u8BB8\u4F7F\u7528\u5B83\u7684\u4EBA\u586B\u8FDB\u300C\u5141\u8BB8\u7684\u4EBA\u300D\u3002\u98DE\u4E66\u53EF\u4EE5\u7528\u300C\u8FDE\u63A5\u98DE\u4E66\u673A\u5668\u4EBA\u300D\uFF0C\u626B\u4E00\u6B21\u7801\u5168\u90E8\u914D\u597D\u3002\u83B7\u51C6\u7684\u4EBA\u79C1\u804A\u673A\u5668\u4EBA\uFF08\u6216\u628A\u5B83\u62C9\u8FDB\u7FA4\uFF09\u3001\u5728\u804A\u5929\u91CC\u9009\u597D\u9879\u76EE\uFF0C\u5C31\u80FD\u6D3E\u6D3B\u3002",
146303
+ description: "\u628A\u804A\u5929\u673A\u5668\u4EBA\u63A5\u5230 Ycodium\u3002\u4E00\u884C\u4E00\u4E2A\u673A\u5668\u4EBA\uFF1A\u5148\u5728\u5BF9\u5E94\u5E73\u53F0\u5EFA\u597D\u673A\u5668\u4EBA\u3001\u628A\u51ED\u636E\u586B\u8FDB\u6765\uFF0C\u518D\u9009\u4E00\u4E2A\u8BBF\u95EE\u6A21\u5F0F\uFF0C\u51B3\u5B9A\u79C1\u804A\u548C\u7FA4\u91CC\u8C01\u80FD\u7528\uFF1B\u7528\u5230\u540D\u5355\u65F6\uFF0C\u628A\u4EBA\u586B\u8FDB\u300C\u5141\u8BB8\u7684\u4EBA\u300D\u3002\u98DE\u4E66\u53EF\u4EE5\u7528\u300C\u8FDE\u63A5\u98DE\u4E66\u673A\u5668\u4EBA\u300D\uFF0C\u626B\u4E00\u6B21\u7801\u5168\u90E8\u914D\u597D\u3002\u83B7\u51C6\u7684\u4EBA\u79C1\u804A\u673A\u5668\u4EBA\uFF08\u6216\u628A\u5B83\u62C9\u8FDB\u7FA4\uFF09\u3001\u5728\u804A\u5929\u91CC\u9009\u597D\u9879\u76EE\uFF0C\u5C31\u80FD\u6D3E\u6D3B\u3002",
146109
146304
  bots: "\u673A\u5668\u4EBA",
146110
146305
  language: {
146111
146306
  label: "\u56DE\u8BDD\u8BED\u8A00",
@@ -146113,6 +146308,10 @@ var zh_CN_default = {
146113
146308
  zhCN: "\u4E2D\u6587",
146114
146309
  en: "English"
146115
146310
  },
146311
+ defaultModel: {
146312
+ follow: "\u8DDF\u968F\u804A\u5929 / \u9879\u76EE\u9ED8\u8BA4",
146313
+ unavailable: "{{value}}\uFF08\u4E0D\u53EF\u7528\uFF09"
146314
+ },
146116
146315
  runtimeMode: {
146117
146316
  approvalRequired: "\u547D\u4EE4\u548C\u6539\u6587\u4EF6\u524D\u5148\u95EE",
146118
146317
  autoAcceptEdits: "\u81EA\u52A8\u63A5\u53D7\u6539\u52A8\uFF0C\u5176\u4ED6\u64CD\u4F5C\u5148\u95EE",
@@ -146161,7 +146360,9 @@ var zh_CN_default = {
146161
146360
  },
146162
146361
  accessMode: {
146163
146362
  allowlist: "\u4EC5\u767D\u540D\u5355\uFF08\u9ED8\u8BA4\uFF09",
146164
- open: "\u5141\u8BB8\u6240\u6709\u4EBA"
146363
+ open: "\u5141\u8BB8\u6240\u6709\u4EBA",
146364
+ privateOpen: "\u79C1\u804A\u653E\u5F00\uFF0C\u7FA4\u91CC\u9650\u540D\u5355",
146365
+ groupOpen: "\u79C1\u804A\u9650\u540D\u5355\uFF0C\u7FA4\u91CC\u653E\u5F00"
146165
146366
  }
146166
146367
  },
146167
146368
  platform: {
@@ -146197,12 +146398,16 @@ var zh_CN_default = {
146197
146398
  },
146198
146399
  allowedSenders: {
146199
146400
  label: "\u5141\u8BB8\u7684\u4EBA",
146200
- description: "\u8BE5\u5E73\u53F0\u7684\u7528\u6237 id\uFF0C\u9017\u53F7\u5206\u9694\u3002\u4EC5\u5728\u300C\u4EC5\u767D\u540D\u5355\u300D\u6A21\u5F0F\u4E0B\u751F\u6548\uFF1B\u7559\u7A7A\u5219\u8C01\u90FD\u4E0D\u80FD\u3002\u7ED9\u673A\u5668\u4EBA\u53D1\u4E00\u53E5\u8BDD\uFF0C\u5B83\u4F1A\u544A\u8BC9\u4F60\u81EA\u5DF1\u7684 id\u3002"
146401
+ description: "\u8BE5\u5E73\u53F0\u7684\u7528\u6237 id\uFF0C\u9017\u53F7\u5206\u9694\u3002\u5728\u8BBF\u95EE\u6A21\u5F0F\u8981\u6C42\u540D\u5355\u7684\u804A\u5929\u91CC\u751F\u6548\uFF1B\u7559\u7A7A\u5219\u8C01\u90FD\u4E0D\u80FD\u5728\u8FD9\u4E9B\u804A\u5929\u91CC\u4F7F\u7528\u673A\u5668\u4EBA\u3002\u67E5 id\uFF1A\u7528\u8FD8\u4E0D\u5728\u540D\u5355\u4E0A\u7684\u8D26\u53F7\uFF0C\u5728\u8981\u6C42\u540D\u5355\u7684\u804A\u5929\u91CC\u7ED9\u673A\u5668\u4EBA\u53D1\u4E00\u53E5\u8BDD\uFF08\u7FA4\u91CC\u8981 @ \u5B83\uFF09\uFF0C\u5B83\u4F1A\u56DE\u590D\u8FD9\u4E2A id\uFF1B\u540C\u4E00\u8D26\u53F7 10 \u5206\u949F\u5185\u53EA\u56DE\u4E00\u6B21\u3002"
146201
146402
  },
146202
146403
  proxy: {
146203
146404
  label: "\u4EE3\u7406",
146204
146405
  description: "\u6709\u4E9B\u5E73\u53F0\u53EA\u80FD\u7ECF\u672C\u673A\u4E0A\u7684\u67D0\u4E2A\u4EE3\u7406\u624D\u8FDE\u5F97\u4E0A\uFF0C\u800C\u4E14\u6BCF\u4E2A\u673A\u5668\u4EBA\u53EF\u4EE5\u4E0D\u4E00\u6837\u3002"
146205
146406
  },
146407
+ defaultModel: {
146408
+ label: "\u9ED8\u8BA4\u6A21\u578B",
146409
+ description: "\u804A\u5929\u672A\u7528 /model \u9009\u6A21\u578B\u65F6\uFF0C\u65B0\u4F1A\u8BDD\u4F7F\u7528\u6B64\u9ED8\u8BA4\u6A21\u578B\u3002\u5DF2\u6709\u4F1A\u8BDD\u4FDD\u7559\u539F\u6A21\u578B\uFF1B\u53D1\u9001 /new \u540E\u4F7F\u7528\u65B0\u9ED8\u8BA4\u3002\u4E0D\u53EF\u7528\u65F6\u56DE\u9000\u5230\u9879\u76EE\u9ED8\u8BA4\u3002"
146410
+ },
146206
146411
  runtimeMode: {
146207
146412
  label: "\u804A\u5929\u6D88\u606F\u80FD\u505A\u4EC0\u4E48",
146208
146413
  description: "\u83B7\u51C6\u7684\u4EBA\u90FD\u80FD\u4ECE\u804A\u5929\u91CC\u5F00\u4E00\u8F6E\uFF0C\u6240\u4EE5\u9ED8\u8BA4\u6545\u610F\u4E0D\u7ED9\u6EE1\u6743\u9650\u3002\u4E0D\u8BBA\u9009\u54EA\u4E00\u6863\uFF0C\u8981\u62CD\u677F\u7684\u4E8B\u90FD\u4F1A\u6446\u5230\u804A\u5929\u91CC\u3002"
@@ -146293,11 +146498,12 @@ var zh_CN_default = {
146293
146498
  },
146294
146499
  accessMode: {
146295
146500
  label: "\u8BBF\u95EE\u6A21\u5F0F",
146296
- description: "\u4EC5\u767D\u540D\u5355\uFF1D\u53EA\u6709\u300C\u5141\u8BB8\u7684\u4EBA\u300D\u80FD\u89E6\u53D1\uFF1B\u5141\u8BB8\u6240\u6709\u4EBA\uFF1D\u5E73\u53F0\u80FD\u8BC6\u522B\u7684\u53D1\u9001\u8005\u5747\u53EF\u3002\u65B0\u673A\u5668\u4EBA\u9ED8\u8BA4\u4EC5\u767D\u540D\u5355\u3002"
146501
+ description: "\u4EC5\u767D\u540D\u5355\uFF1D\u53EA\u6709\u300C\u5141\u8BB8\u7684\u4EBA\u300D\u80FD\u89E6\u53D1\uFF1B\u5141\u8BB8\u6240\u6709\u4EBA\uFF1D\u5E73\u53F0\u80FD\u8BC6\u522B\u7684\u53D1\u9001\u8005\u5747\u53EF\u3002\u65B0\u673A\u5668\u4EBA\u9ED8\u8BA4\u4EC5\u767D\u540D\u5355\u3002\u300C\u79C1\u804A\u653E\u5F00\uFF0C\u7FA4\u91CC\u9650\u540D\u5355\u300D\u4E0E\u300C\u79C1\u804A\u9650\u540D\u5355\uFF0C\u7FA4\u91CC\u653E\u5F00\u300D\u5171\u7528\u540C\u4E00\u4EFD\u300C\u5141\u8BB8\u7684\u4EBA\u300D\uFF1B\u8FD9\u4E24\u79CD\u6A21\u5F0F\u4E0B\uFF0C\u65E0\u6CD5\u786E\u8BA4\u662F\u79C1\u804A\u8FD8\u662F\u7FA4\u65F6\uFF0C\u53EA\u6709\u300C\u5141\u8BB8\u7684\u4EBA\u300D\u80FD\u7528\u673A\u5668\u4EBA\u3002"
146297
146502
  }
146298
146503
  },
146299
146504
  reply: {
146300
146505
  deliveryFailed: "\u56DE\u590D\u672A\u80FD\u9001\u8FBE\u3002\u8BF7\u68C0\u67E5\u8FD9\u4E2A\u673A\u5668\u4EBA\u7684\u8FDE\u63A5\u4E0E\u6743\u9650\uFF0C\u7136\u540E\u91CD\u8BD5\u3002",
146506
+ connectionTest: "\u2705 Ycodium \u8FDE\u63A5\u6D4B\u8BD5\n\u8FD9\u6761\u6D4B\u8BD5\u6D88\u606F\u7531\u300C{{name}}\u300D\u673A\u5668\u4EBA\u901A\u8FC7\u8BBE\u7F6E\u9875\u7684\u300C\u68C0\u67E5\u8FDE\u63A5\u300D\u53D1\u51FA\uFF0C\u65E0\u9700\u56DE\u590D\u3002",
146301
146507
  wecomDeliveryFailed: "\u8FD9\u6761\u56DE\u590D\u6CA1\u9001\u5230\u4F01\u4E1A\u5FAE\u4FE1\u91CC\u7684 {{recipient}}\uFF1A\u5BF9\u65B9\u4E0D\u5728\u8FD9\u4E2A\u5E94\u7528\u7684\u53EF\u89C1\u8303\u56F4\u3002\u5230\u4F01\u4E1A\u5FAE\u4FE1\u7BA1\u7406\u540E\u53F0\u628A\u4ED6\u4EEC\u52A0\u8FDB\u5E94\u7528\u7684\u53EF\u89C1\u8303\u56F4\uFF0C\u518D\u8BA9 agent \u91CD\u53D1\u4E00\u6B21\u3002",
146302
146508
  conclusionRecovered: "\uFF08\u91CD\u542F\u671F\u95F4\u5B8C\u6210\uFF0C\u73B0\u5728\u8865\u53D1\uFF09",
146303
146509
  notAllowed: "\u4F60\u4E0D\u5728\u8FD9\u4E2A\u673A\u5668\u4EBA\u7684\u5141\u8BB8\u540D\u5355\u91CC\uFF0C\u8FD9\u6B21\u70B9\u51FB\u6CA1\u6709\u751F\u6548\u3002",
@@ -146422,8 +146628,8 @@ var zh_CN_default = {
146422
146628
  team: "\xB7 /team \u2014\u2014 \u6BCF\u6761\u6D88\u606F\u90FD\u7ED9\u8FD9\u4E2A\u9879\u76EE\u7684\u56E2\u961F\u5F00\u4E00\u4E2A\u76EE\u6807\uFF1B\u5148\u7528 /repo \u9009\u9879\u76EE",
146423
146629
  thread: "\xB7 /thread \u2014\u2014 \u5207\u56DE\u666E\u901A\u4F1A\u8BDD",
146424
146630
  aliases: "\u4E2D\u6587\u4E5F\u884C\uFF1A\u505C / \u7EE7\u7EED\u53D1\u9001 / \u72B6\u6001 / \u65B0\u5BF9\u8BDD / \u4F1A\u8BDD\u5217\u8868 / \u5207\u6362\u4F1A\u8BDD / \u6362\u6A21\u578B / \u8BA1\u5212\u6A21\u5F0F / \u6362\u9879\u76EE / \u5EFA\u7FA4 / \u6362\u56E2\u961F / \u6362\u5BF9\u8BDD\u3002",
146425
- sessions: "\xB7 /sessions \u2014\u2014 \u5217\u51FA\u672C\u673A\u5668\u4EBA\u4E3A\u8FD9\u4E2A\u804A\u5929\u7684\u9879\u76EE\u5F00\u8FC7\u7684\u4F1A\u8BDD",
146426
- resume: "\xB7 /resume \u7F16\u53F7 \u2014\u2014 \u5207\u5230\u5176\u4E2D\u4E00\u6761\uFF08/sessions \u91CC\u7684\u7F16\u53F7\uFF0C\u6216\u4F1A\u8BDD id \u7684\u5F00\u5934\uFF09",
146631
+ sessions: "\xB7 /sessions \u2014\u2014 \u5217\u51FA\u672C\u9879\u76EE\u7684\u4F1A\u8BDD\uFF1B\u52A0 /repo \u91CC\u7684\u9879\u76EE\u7F16\u53F7\uFF0C\u5217\u51FA\u672C\u673A\u5668\u4EBA\u5728\u522B\u7684\u9879\u76EE\u5F00\u8FC7\u7684",
146632
+ resume: "\xB7 /resume \u7F16\u53F7\u6216 id \u5F00\u5934 \u2014\u2014 \u5207\u4F1A\u8BDD\uFF1B\u522B\u7684\u9879\u76EE\u7684\u4F1A\u8BDD\u4F1A\u8FDE\u540C\u9879\u76EE\u4E00\u8D77\u5207",
146427
146633
  model: "\xB7 /model \u2014\u2014 \u770B\u8FD9\u4E2A\u804A\u5929\u7528\u7684\u6A21\u578B\uFF1B/model \u7F16\u53F7 \u6216 /model \u5B9E\u4F8B/\u6A21\u578B \u5207\u6362",
146428
146634
  plan: "\xB7 /plan \u2014\u2014 \u5F00\u5173\u8BA1\u5212\u6A21\u5F0F\uFF08agent \u5148\u51FA\u8BA1\u5212\u3001\u4E0D\u52A8\u624B\uFF09\uFF1B/plan on\u3001/plan off \u660E\u786E\u8BBE\u5B9A",
146429
146635
  panel: "\u53D1 /panel \u6253\u5F00\u53EF\u70B9\u7684\u547D\u4EE4\u9762\u677F\u3002",
@@ -146432,12 +146638,18 @@ var zh_CN_default = {
146432
146638
  syncNote: "\xB7 \u79C1\u804A\u9644\u7740\u4F1A\u8BDD\u540E\uFF0C\u7F51\u9875\u4E0A\u7684\u7528\u6237\u6587\u5B57\u4E0E\u7EC8\u6001\u52A9\u624B\u6587\u5B57\u4F1A\u540C\u6B65\u5230\u6B64\uFF08\u7FA4\u804A\u4E0D\u540C\u6B65\uFF09\u3002"
146433
146639
  },
146434
146640
  sessions: {
146435
- title: "\u672C\u673A\u5668\u4EBA\u4E3A\u8FD9\u4E2A\u804A\u5929\u7684\u9879\u76EE\u5F00\u8FC7\u7684\u4F1A\u8BDD\uFF08{{count}} \u6761\uFF09\uFF1A",
146436
- empty: "\u672C\u673A\u5668\u4EBA\u8FD8\u6CA1\u6709\u4E3A\u8FD9\u4E2A\u804A\u5929\u7684\u9879\u76EE\u5F00\u8FC7\u4F1A\u8BDD\uFF0C\u53D1\u4E00\u53E5\u8BDD\u5C31\u4F1A\u5F00\u4E00\u6761\u3002",
146641
+ title: "\u672C\u9879\u76EE\u7684\u4F1A\u8BDD\uFF08{{count}} \u6761\uFF09\uFF1A",
146642
+ empty: "\u672C\u673A\u5668\u4EBA\u8FD8\u6CA1\u5728\u672C\u9879\u76EE\u5F00\u8FC7\u4F1A\u8BDD\uFF0C\u53D1\u4E00\u53E5\u8BDD\u5C31\u4F1A\u5F00\u4E00\u6761\u3002",
146437
146643
  more: "\u8FD8\u6709 {{count}} \u6761\u6CA1\u5217\u51FA\u2014\u2014\u7528 /resume \u52A0\u4F1A\u8BDD id \u7684\u5F00\u5934\u4E5F\u80FD\u5207\u5230\u3002",
146438
146644
  footer: "\u7528 /resume \u7F16\u53F7 \u5207\u6362\uFF0C\u6216 /resume \u52A0\u4F1A\u8BDD id \u7684\u5F00\u5934\u3002",
146439
146645
  untitled: "\uFF08\u65E0\u6807\u9898\uFF09",
146440
146646
  unavailable: "\u73B0\u5728\u8BFB\u4E0D\u51FA\u4F1A\u8BDD\u5217\u8868\uFF0C\u7A0D\u540E\u518D\u8BD5\u3002",
146647
+ projectNotFound: "\u9879\u76EE\u7F16\u53F7\u4E0D\u5B58\u5728\u3002\u53D1 /repo \u770B\u6709\u54EA\u4E9B\u7F16\u53F7\u3002",
146648
+ projectNumberExpected: "/sessions \u540E\u9762\u8DDF\u9879\u76EE\u7F16\u53F7\uFF08/repo \u5217\u51FA\u7684\u6570\u5B57\uFF09\uFF0C\u4F8B\u5982 /sessions 2\u3002",
146649
+ project: "\u9879\u76EE\uFF1A\u300C{{title}}\u300D",
146650
+ otherProjectTitle: "\u672C\u673A\u5668\u4EBA\u5728\u9879\u76EE\u300C{{project}}\u300D\u91CC\u5F00\u8FC7\u7684\u4F1A\u8BDD\uFF08{{count}} \u6761\uFF09\uFF1A",
146651
+ otherProjectFooter: "\u4F60\u4ECD\u5728\u8FD9\u4E2A\u804A\u5929\u5F53\u524D\u7684\u9879\u76EE\u91CC\u3002\u53D1 /resume \u7F16\u53F7\uFF0C\u6216 /resume \u52A0\u4F1A\u8BDD id \u7684\u5F00\u5934\uFF08\u672C\u673A\u5668\u4EBA\u5F00\u8FC7\u4F46\u6CA1\u5217\u51FA\u7684\u4E5F\u884C\uFF09\uFF0C\u5C31\u80FD\u5207\u8FC7\u53BB\uFF0C\u804A\u5929\u7684\u9879\u76EE\u4F1A\u4E00\u8D77\u5207\u3002",
146652
+ projectEmpty: "\u672C\u673A\u5668\u4EBA\u8FD8\u6CA1\u5728\u90A3\u4E2A\u9879\u76EE\u91CC\u5F00\u8FC7\u4F1A\u8BDD\u3002\u60F3\u5728\u90A3\u91CC\u5F00\uFF0C\u53D1 /repo \u52A0\u90A3\u4E2A\u9879\u76EE\u7F16\u53F7\u6362\u8FC7\u53BB\uFF0C\u518D\u53D1\u4E00\u53E5\u8BDD\u3002",
146441
146653
  badge: {
146442
146654
  current: "\u2605 \u5F53\u524D",
146443
146655
  running: "\u25CF \u5728\u8DD1",
@@ -146453,11 +146665,14 @@ var zh_CN_default = {
146453
146665
  },
146454
146666
  resume: {
146455
146667
  notFound: "\u6CA1\u6709\u54EA\u6761\u4F1A\u8BDD\u5BF9\u5F97\u4E0A\u300C{{query}}\u300D\u3002\u7528 /sessions \u770B\u770B\u6709\u54EA\u4E9B\u3002",
146668
+ ambiguous: "\u5BF9\u5F97\u4E0A\u300C{{query}}\u300D\u7684\u4F1A\u8BDD\u4E0D\u6B62\u4E00\u6761\uFF0Cid \u5F00\u5934\u518D\u591A\u7ED9\u51E0\u4F4D\u3002",
146456
146669
  busy: "\u8FD9\u4E2A\u804A\u5929\u6709\u4E00\u8F6E\u6B63\u5728\u8DD1\u2014\u2014\u5148 /stop\uFF0C\u518D\u5207\u6362\u3002",
146457
146670
  already: "\u8FD9\u4E2A\u804A\u5929\u5DF2\u7ECF\u5728\u90A3\u6761\u4F1A\u8BDD\u91CC\u4E86\u3002",
146458
146671
  gone: "\u90A3\u6761\u4F1A\u8BDD\u5DF2\u88AB\u5220\u9664\u6216\u5F52\u6863\uFF0C\u63A5\u4E0D\u4E0A\u4E86\u3002",
146459
146672
  runningElsewhere: "\u90A3\u6761\u4F1A\u8BDD\u6B63\u5728\u522B\u7684\u804A\u5929\u91CC\u8DD1\u7740\u4E00\u8F6E\u2014\u2014\u5148\u5230\u90A3\u8FB9\u505C\u6389\u5B83\u3002",
146460
146673
  done: "\u5DF2\u5207\u5230\u300C{{title}}\u300D\u3002\u53D1\u4E00\u53E5\u8BDD\u5C31\u4ECE\u90A3\u91CC\u63A5\u7740\u804A\u3002",
146674
+ doneInProject: "\u5DF2\u5207\u5230\u9879\u76EE\u300C{{project}}\u300D\u7684\u4F1A\u8BDD\u300C{{title}}\u300D\u3002\u53D1\u4E00\u53E5\u8BDD\u5C31\u4ECE\u90A3\u91CC\u63A5\u7740\u804A\u3002",
146675
+ switchFailed: "\u6CA1\u80FD\u5207\u5230\u90A3\u6761\u4F1A\u8BDD\uFF0C\u804A\u5929\u7684\u9879\u76EE\u548C\u4F1A\u8BDD\u90FD\u6CA1\u53D8\uFF0C\u7A0D\u540E\u91CD\u53D1\u521A\u624D\u90A3\u6761 /resume\u3002",
146461
146676
  confirmWeb: "\u300C{{title}}\u300D\u662F\u7F51\u9875\u5F00\u7684\u4F1A\u8BDD\u3002\u518D\u53D1\u4E00\u6B21 /resume \u540C\u4E00\u7F16\u53F7\uFF08\u6216 /resume confirm\uFF09\u786E\u8BA4\u9644\u7740\uFF1B/resume cancel \u53D6\u6D88\u3002",
146462
146677
  cancelled: "\u5DF2\u53D6\u6D88\u9644\u7740\u3002",
146463
146678
  nothingPending: "\u6CA1\u6709\u5F85\u786E\u8BA4\u7684\u7F51\u9875\u4F1A\u8BDD\u3002\u7528 /sessions \u770B\u770B\u6709\u54EA\u4E9B\u3002"
@@ -146490,6 +146705,8 @@ var zh_CN_default = {
146490
146705
  interrupted: "\u5DF2\u4E2D\u65AD",
146491
146706
  continued: "\u540E\u7EED\u89C1\u4E0B\u65B9\u5361\u7247"
146492
146707
  },
146708
+ thinking: "\u6B63\u5728\u601D\u8003\u4E2D\u2026",
146709
+ usingTool: "\u6B63\u5728\u4F7F\u7528 {{name}}\u2026",
146493
146710
  step: {
146494
146711
  think: "\u601D\u8003",
146495
146712
  command: "\u547D\u4EE4",
@@ -146683,11 +146900,20 @@ var zh_CN_default = {
146683
146900
  rowAction: {
146684
146901
  checkConnection: {
146685
146902
  label: "\u68C0\u67E5\u8FDE\u63A5",
146686
- description: "\u9A8C\u8BC1\u5E73\u53F0\u51ED\u636E\u662F\u5426\u4ECD\u53EF\u7528\uFF1B\u4E0D\u4F1A\u5F00\u4F1A\u8BDD\uFF0C\u4E5F\u4E0D\u4F1A\u8C03\u6A21\u578B\u3002",
146903
+ description: "\u9A8C\u8BC1\u5E73\u53F0\u51ED\u636E\uFF0C\u5E76\u5411\u6700\u8FD1\u7684\u79C1\u804A\u53D1\u4E00\u6761\u6D4B\u8BD5\u6D88\u606F\uFF1B\u4E0D\u4F1A\u5F00\u4F1A\u8BDD\uFF0C\u4E5F\u4E0D\u4F1A\u8C03\u6A21\u578B\u3002",
146687
146904
  running: "\u6B63\u5728\u68C0\u67E5 {{rowId}} \u2026",
146688
146905
  done: "\u8FDE\u63A5\u6B63\u5E38",
146906
+ doneTestFailed: "\u51ED\u636E\u6B63\u5E38\uFF0C\u4F46\u6D4B\u8BD5\u6D88\u606F\u6CA1\u53D1\u51FA\u53BB",
146907
+ doneNoTarget: "\u51ED\u636E\u6B63\u5E38\uFF0C\u4F46\u8FD8\u6CA1\u53D1\u6D4B\u8BD5\u6D88\u606F",
146689
146908
  failed: "\u8FDE\u63A5\u5931\u8D25",
146690
146909
  notSaved: "\u8BF7\u5148\u4FDD\u5B58\u8FD9\u4E00\u884C\u518D\u68C0\u67E5\u8FDE\u63A5\uFF08\u51ED\u636E\u53EA\u4ECE\u5DF2\u4FDD\u5B58\u914D\u7F6E\u8BFB\u53D6\uFF09\u3002",
146910
+ credentialRef: "\u51ED\u636E\u68C0\u67E5\u53C2\u8003\u53F7\uFF1A{{ref}}\n{{detail}}",
146911
+ testMessage: {
146912
+ sent: "\u5DF2\u53D1\u5230\u6700\u8FD1\u79C1\u804A\u8FC7\u673A\u5668\u4EBA\u7684\u4EBA\uFF0C\u8BF7\u5BF9\u65B9\u786E\u8BA4\u6536\u5230\u3002",
146913
+ "test-target-unavailable": "\u8FD8\u6CA1\u6709\u53EF\u7528\u4E8E\u6D4B\u8BD5\u7684\u79C1\u804A\u3002\u7528\u83B7\u51C6\u4F7F\u7528\u8FD9\u4E2A\u673A\u5668\u4EBA\u7684\u8D26\u53F7\u79C1\u804A\u5B83\u4E00\u53E5\uFF0C\u518D\u68C0\u67E5\u3002",
146914
+ "test-message-failed": "\u786E\u8BA4\u673A\u5668\u4EBA\u8FD8\u80FD\u5728\u90A3\u4E2A\u79C1\u804A\u91CC\u53D1\u6D88\u606F\uFF08\u539F\u56E0\u89C1\u4E0B\u65B9\uFF09\uFF0C\u518D\u68C0\u67E5\u3002",
146915
+ ref: "\u6D4B\u8BD5\u6D88\u606F\u53C2\u8003\u53F7\uFF1A{{ref}}\n{{detail}}"
146916
+ },
146691
146917
  hint: {
146692
146918
  networkMultipleCauses: "\u8FD9\u6B21\u5931\u8D25\u80CC\u540E\u4E0D\u6B62\u4E00\u79CD\u7F51\u7EDC\u95EE\u9898\u3002\u8BF7\u68C0\u67E5\u8FD9\u53F0\u673A\u5668\u7684\u7F51\u7EDC\u548C\u8FD9\u4E2A\u673A\u5668\u4EBA\u7684\u300C\u4EE3\u7406\u300D\u8BBE\u7F6E\uFF0C\u518D\u68C0\u67E5\u4E00\u6B21\u3002",
146693
146919
  networkDns: "\u670D\u52A1\u57DF\u540D\u89E3\u6790\u5931\u8D25\u3002\u8BF7\u68C0\u67E5\u8FD9\u53F0\u673A\u5668\u7684 DNS \u548C\u7F51\u7EDC\u8BBE\u7F6E\uFF0C\u518D\u68C0\u67E5\u4E00\u6B21\u3002",
@@ -146822,11 +147048,14 @@ var zh_CN_default = {
146822
147048
  reference: "\u53C2\u8003\u53F7 {{referenceId}}"
146823
147049
  },
146824
147050
  empty: "\u8FD8\u6CA1\u6709\u673A\u5668\u4EBA",
147051
+ emptyGuide: "\u7528\u300C{{larkLabel}}\u300D\u6216\u300C{{wecomLabel}}\u300D\u626B\u4E00\u4E0B\u5C31\u80FD\u5EFA\u597D\u673A\u5668\u4EBA\u3002\u5176\u4ED6\u5E73\u53F0\u70B9\u300C{{addBotLabel}}\u300D\uFF0C\u586B\u5165\u5728\u8BE5\u5E73\u53F0\u5F00\u53D1\u8005\u540E\u53F0\u62FF\u5230\u7684\u51ED\u636E\u3002",
146825
147052
  unavailable: "\u8FD9\u53F0\u673A\u5668\u73B0\u5728\u6CA1\u6709\u804A\u5929\u6865\u3002",
146826
147053
  loadFailed: "\u804A\u5929\u6865\u7684\u8BBE\u7F6E\u6CA1\u80FD\u8BFB\u51FA\u6765\u3002",
146827
147054
  retry: "\u518D\u8BD5\u4E00\u6B21",
146828
147055
  general: "\u901A\u7528\u8BBE\u7F6E",
146829
147056
  addBot: "\u6DFB\u52A0\u673A\u5668\u4EBA",
147057
+ moreSettings: "\u66F4\u591A\u8BBE\u7F6E",
147058
+ moreSettingsIssues: "\u66F4\u591A\u8BBE\u7F6E\uFF08{{count}} \u9879\u5F85\u4FEE\u6B63\uFF09",
146830
147059
  save: "\u4FDD\u5B58",
146831
147060
  revert: "\u64A4\u9500\u6539\u52A8",
146832
147061
  saveFailed: "\u6CA1\u80FD\u5B58\u4E0A\u3002",
@@ -147297,26 +147526,6 @@ var makeLarkSlashCommands = (deps) => {
147297
147526
 
147298
147527
  // src/larkAdapter.ts
147299
147528
  var TOKEN_CACHE_MAX_AGE_MS = 30 * 6e4;
147300
- var COLUMNS = [
147301
- {
147302
- key: "larkAppId",
147303
- kind: "text",
147304
- labelKey: "column.larkAppId.label",
147305
- placeholderKey: "column.larkAppId.placeholder"
147306
- },
147307
- { key: "larkAppSecret", kind: "password", labelKey: "column.larkAppSecret.label" },
147308
- // 被 @ 时答复要不要开话题。只有这个适配器读它,没填过就是开(见 `topicOnMentionOf`)。
147309
- {
147310
- key: "larkTopicOnMention",
147311
- kind: "select",
147312
- labelKey: "column.larkTopicOnMention.label",
147313
- descriptionKey: "column.larkTopicOnMention.description",
147314
- options: [
147315
- { value: "on", labelKey: "settings.onOff.on" },
147316
- { value: "off", labelKey: "settings.onOff.off" }
147317
- ]
147318
- }
147319
- ];
147320
147529
  var topicOnMentionOf = (binding) => binding.values["larkTopicOnMention"] !== "off";
147321
147530
  var TOPIC_THREAD_CACHE_LIMIT = 2e3;
147322
147531
  var MESSAGE_CONTEXT_CACHE_LIMIT = 2e3;
@@ -147413,7 +147622,7 @@ var makeLarkAdapter = (deps) => {
147413
147622
  botIdentity: (bot) => larkCredentials(bot).appId,
147414
147623
  icon: "lark",
147415
147624
  label: "Lark",
147416
- columns: COLUMNS,
147625
+ columns: larkBotColumns,
147417
147626
  unlistedValues: ["larkPanelMode"],
147418
147627
  quotesReplies: true,
147419
147628
  parentChatIdOf: (chatId) => {
@@ -147753,10 +147962,24 @@ var makeLarkAdapter = (deps) => {
147753
147962
 
147754
147963
  // src/modelPlan.ts
147755
147964
  import { threadOrNull as threadOrNull4 } from "@ycodium-ai/plugin-api";
147965
+
147966
+ // src/botModel.ts
147967
+ var resolveDefaultModel = async (bot, chatModel, projectDefault, providers, knownInstances) => {
147968
+ if (chatModel !== null) return chatModel;
147969
+ if (bot.defaultModel !== void 0) {
147970
+ const instances = knownInstances === void 0 ? await providers.list({}).catch(() => null) : knownInstances;
147971
+ if (!isModelCatalogComplete(instances) || modelCatalogHas(modelChoices(instances ?? []), bot.defaultModel)) {
147972
+ return bot.defaultModel;
147973
+ }
147974
+ }
147975
+ return projectDefault;
147976
+ };
147977
+
147978
+ // src/modelPlan.ts
147756
147979
  var makeModelPlan = (core, deps) => {
147757
147980
  const { host, t, commit, deliver } = core;
147758
147981
  const { threadStillUsable } = deps;
147759
- const currentModelOf = async (binding) => {
147982
+ const currentModelOf = async (binding, instances) => {
147760
147983
  const chat = chatOf(core.state, chatKeyOf(binding));
147761
147984
  if (chat.threadId !== null) {
147762
147985
  const thread = await threadOrNull4(host.threads, chat.threadId).catch(() => null);
@@ -147769,15 +147992,20 @@ var makeModelPlan = (core, deps) => {
147769
147992
  }
147770
147993
  if (chat.model !== null) return chat.model;
147771
147994
  const project = binding.projectId === "" ? null : await host.projects.get({ projectId: binding.projectId }).catch(() => null);
147772
- const fallback = project?.defaultModelSelection ?? null;
147773
- return fallback === null ? null : { instanceId: fallback.instanceId, model: fallback.model };
147995
+ return resolveDefaultModel(
147996
+ binding,
147997
+ chat.model,
147998
+ project?.defaultModelSelection ?? null,
147999
+ host.providers.instances,
148000
+ instances
148001
+ );
147774
148002
  };
147775
148003
  const handleModel = async (binding, arg) => {
147776
148004
  const chatKey = chatKeyOf(binding);
147777
148005
  const instances = await host.providers.instances.list({}).catch(() => null);
147778
148006
  const choices = instances === null ? [] : modelChoices(instances);
147779
148007
  if (arg === "") {
147780
- await deliver(binding, formatModels(await currentModelOf(binding), choices, t));
148008
+ await deliver(binding, formatModels(await currentModelOf(binding, instances), choices, t));
147781
148009
  return;
147782
148010
  }
147783
148011
  if (choices.length === 0) {
@@ -148491,7 +148719,8 @@ var makePanelWiring = (core, deps) => {
148491
148719
  const binding = bindingForChatKey(chatKey);
148492
148720
  const instances = await host.providers.instances.list({}).catch(() => null);
148493
148721
  if (binding === void 0 || instances === null) return null;
148494
- return { current: await currentModelOf(binding), choices: modelChoices(instances) };
148722
+ const choices = modelChoices(instances);
148723
+ return { current: await currentModelOf(binding, instances), choices };
148495
148724
  },
148496
148725
  loadProjects: async (chatKey) => {
148497
148726
  const rows = await projectRowsFor(chatKey);
@@ -148523,7 +148752,7 @@ var makePanelWiring = (core, deps) => {
148523
148752
  const opener = remembered === void 0 ? void 0 : bindingForChatKey(remembered);
148524
148753
  const chatId = opener !== void 0 && opener.rowId === bot.rowId && adapterFor(opener.platform)?.parentChatIdOf?.(opener.chatId) === click.chatId ? opener.chatId : click.chatId;
148525
148754
  const target2 = bindingOf2(bot, chatId);
148526
- if (!isAllowedSender(target2, click.operatorId)) {
148755
+ if (!isAllowedSender(target2, click.operatorId, chatOf(core.state, chatKeyOf(target2)).chatKind)) {
148527
148756
  host.log.warn("chat ignored a panel click from someone not on the allow list", {
148528
148757
  bot: target2.displayName,
148529
148758
  operator: click.operatorId
@@ -149327,8 +149556,8 @@ var shellOf = (thread) => ({
149327
149556
  archivedAt: thread.archivedAt,
149328
149557
  sessionStatus: thread.session?.status ?? null
149329
149558
  });
149330
- var makeSessions = (core) => {
149331
- const { host, t, commit, bindChat, deliver } = core;
149559
+ var makeSessions = (core, projects = makeProjectRows(core)) => {
149560
+ const { host, t, commit, deliver } = core;
149332
149561
  const listedByChat = /* @__PURE__ */ new Map();
149333
149562
  const pendingAttachByChat = /* @__PURE__ */ new Map();
149334
149563
  const rowFromThread = (thread, chatKey, origin) => {
@@ -149369,27 +149598,112 @@ var makeSessions = (core) => {
149369
149598
  return shells;
149370
149599
  };
149371
149600
  const originOf = (threadId, projectId) => core.state.opened[threadId]?.projectId === projectId ? "plugin" : "web";
149372
- const sessionRowsFor = async (binding) => {
149601
+ const isPluginThread = (threadId, projectId) => originOf(threadId, projectId) === "plugin";
149602
+ const sessionRowsFor = async (binding, projectId = binding.projectId, pluginOnly = false) => {
149373
149603
  const chatKey = chatKeyOf(binding);
149374
- const shells = await projectShells(binding.projectId);
149604
+ const shells = await projectShells(projectId);
149375
149605
  return shells.flatMap((shell) => {
149376
- const row = rowFromThread(shell, chatKey, originOf(shell.threadId, binding.projectId));
149606
+ if (pluginOnly && !isPluginThread(shell.threadId, projectId)) return [];
149607
+ const row = rowFromThread(shell, chatKey, originOf(shell.threadId, projectId));
149377
149608
  return row === null ? [] : [row];
149378
149609
  });
149379
149610
  };
149380
149611
  const rowForThread = async (binding, chatKey, threadId) => {
149381
149612
  const thread = await threadOrNull7(host.threads, threadId, { archived: "include" });
149382
- if (thread === null || thread.projectId !== binding.projectId) return null;
149383
- return rowFromThread(shellOf(thread), chatKey, originOf(threadId, binding.projectId));
149613
+ if (thread === null) return null;
149614
+ if (thread.projectId !== binding.projectId && !isPluginThread(threadId, thread.projectId)) {
149615
+ return null;
149616
+ }
149617
+ const row = rowFromThread(shellOf(thread), chatKey, originOf(threadId, thread.projectId));
149618
+ return row === null ? null : { ...row, projectId: thread.projectId };
149384
149619
  };
149385
- const showSessions = async (binding) => {
149386
- const rows = await sessionRowsFor(binding);
149387
- listedByChat.set(chatKeyOf(binding), listedThreadIds(rows));
149388
- await deliver(binding, formatSessions(rows, Date.now(), t));
149620
+ const resumeRowsFor = async (binding, wanted, listed) => {
149621
+ if (looksLikeListNumber(wanted)) return listed === void 0 ? sessionRowsFor(binding) : [];
149622
+ const needle = wanted.toLowerCase();
149623
+ const threadIds = Object.entries(core.state.opened).filter(
149624
+ ([id, opened]) => opened.projectId !== binding.projectId && id.toLowerCase().startsWith(needle)
149625
+ ).map(([id]) => id);
149626
+ const [rows, candidates] = await Promise.all([
149627
+ sessionRowsFor(binding),
149628
+ Promise.all(threadIds.map((id) => rowForThread(binding, chatKeyOf(binding), id)))
149629
+ ]);
149630
+ return [
149631
+ ...rows,
149632
+ ...candidates.flatMap(
149633
+ (row) => row === null || row.projectId === binding.projectId ? [] : [row]
149634
+ )
149635
+ ];
149636
+ };
149637
+ const showSessions = async (binding, arg = "") => {
149638
+ const chatKey = chatKeyOf(binding);
149639
+ const wanted = arg.trim();
149640
+ if (wanted === "") {
149641
+ const rows = await sessionRowsFor(binding);
149642
+ listedByChat.set(chatKey, listedThreadIds(rows));
149643
+ await deliver(binding, formatSessions(rows, Date.now(), t));
149644
+ return;
149645
+ }
149646
+ if (!isProjectNumber(wanted)) {
149647
+ await deliver(binding, t("sessions.projectNumberExpected"));
149648
+ return;
149649
+ }
149650
+ try {
149651
+ const available = await projects.projectRowsFor(chatKey);
149652
+ const picked = pickProjectNumber(
149653
+ wanted,
149654
+ projects.listedProjectsByChat.get(chatKey),
149655
+ available
149656
+ );
149657
+ if (picked === null) {
149658
+ await deliver(binding, t("sessions.projectNotFound"));
149659
+ return;
149660
+ }
149661
+ const project = available.find((row) => row.projectId === picked);
149662
+ if (project === void 0) {
149663
+ await deliver(binding, t("repo.gone"));
149664
+ return;
149665
+ }
149666
+ if (picked === binding.projectId) {
149667
+ await showSessions(binding);
149668
+ return;
149669
+ }
149670
+ const rows = await sessionRowsFor(binding, project.projectId, true);
149671
+ listedByChat.set(chatKey, listedThreadIds(rows));
149672
+ await deliver(binding, formatProjectSessions(rows, Date.now(), t, project.title));
149673
+ } catch (error) {
149674
+ host.log.warn("chat could not list the sessions", { error: describeError(error) });
149675
+ await deliver(binding, t("sessions.unavailable"));
149676
+ }
149389
149677
  };
149390
149678
  const bindToSession = async (binding, chatKey, target2) => {
149391
- for (const [otherKey, record4] of Object.entries(core.state.chats)) {
149392
- if (otherKey !== chatKey && record4.threadId === target2.threadId) bindChat(otherKey, null);
149679
+ let project;
149680
+ if (target2.projectId !== binding.projectId) {
149681
+ try {
149682
+ project = (await projects.projectRowsFor(chatKey)).find(
149683
+ (row) => row.projectId === target2.projectId
149684
+ );
149685
+ } catch (error) {
149686
+ host.log.warn("chat could not list the projects", { error: describeError(error) });
149687
+ await deliver(binding, t("resume.switchFailed"));
149688
+ return;
149689
+ }
149690
+ if (project === void 0) {
149691
+ await deliver(binding, t("repo.gone"));
149692
+ return;
149693
+ }
149694
+ }
149695
+ try {
149696
+ await core.commitDurably("session-transferred", {
149697
+ chatKey,
149698
+ projectId: target2.projectId,
149699
+ threadId: target2.threadId
149700
+ });
149701
+ } catch (error) {
149702
+ host.log.warn("chat could not switch the project and session", {
149703
+ error: describeError(error)
149704
+ });
149705
+ await deliver(binding, t("resume.switchFailed"));
149706
+ return;
149393
149707
  }
149394
149708
  if (target2.origin === "web" && !hasAttached(core.state, chatKey, target2.threadId)) {
149395
149709
  commit("thread-attached", {
@@ -149399,10 +149713,13 @@ var makeSessions = (core) => {
149399
149713
  attachedAt: (/* @__PURE__ */ new Date()).toISOString()
149400
149714
  });
149401
149715
  }
149402
- commit("chat-updated", { chatKey, threadId: target2.threadId, target: { kind: "thread" } });
149403
149716
  pendingAttachByChat.delete(chatKey);
149404
149717
  listedByChat.delete(chatKey);
149405
- await deliver(binding, t("resume.done", { title: target2.title }));
149718
+ if (project !== void 0) projects.listedProjectsByChat.delete(chatKey);
149719
+ await deliver(
149720
+ binding,
149721
+ project === void 0 ? t("resume.done", { title: target2.title }) : t("resume.doneInProject", { project: project.title, title: target2.title })
149722
+ );
149406
149723
  };
149407
149724
  const resumeSession = async (binding, arg) => {
149408
149725
  const chatKey = chatKeyOf(binding);
@@ -149429,11 +149746,12 @@ var makeSessions = (core) => {
149429
149746
  const listed = listedByChat.get(chatKey);
149430
149747
  let targetId = confirming ? pending ?? null : listed === void 0 ? null : pickSession(wanted, listed, []);
149431
149748
  if (targetId === null) {
149432
- targetId = pickSession(wanted, listed, await sessionRowsFor(binding));
149433
- if (targetId === null) {
149434
- await deliver(binding, t("resume.notFound", { query: arg }));
149749
+ const match5 = matchSession(wanted, listed, await resumeRowsFor(binding, wanted, listed));
149750
+ if (match5.kind !== "unique") {
149751
+ await deliver(binding, t(`resume.${match5.kind}`, { query: arg }));
149435
149752
  return;
149436
149753
  }
149754
+ targetId = match5.threadId;
149437
149755
  }
149438
149756
  const currentId = chatOf(core.state, chatKey).threadId;
149439
149757
  const [target2, other] = await Promise.all([
@@ -150133,21 +150451,38 @@ var clip4 = (text3, max) => {
150133
150451
  const chars = Array.from(text3);
150134
150452
  return chars.length <= max ? text3 : `${chars.slice(0, max - 1).join("")}\u2026`;
150135
150453
  };
150136
- var turnTextParts = (view, t) => {
150454
+ var titleAndSteps = (view, t) => {
150455
+ const lines = [view.title === "" ? t("card.untitled") : view.title];
150137
150456
  const names = view.steps.map((kind) => t(`card.step.${kind}`));
150138
150457
  if (view.omitted > 0) names.unshift(t("card.step.omitted", { count: view.omitted }));
150139
- const headLines = [view.title === "" ? t("card.untitled") : view.title];
150140
- if (names.length > 0) headLines.push(names.join(" \u2192 "));
150458
+ if (names.length > 0) lines.push(names.join(" \u2192 "));
150459
+ return lines;
150460
+ };
150461
+ var settledFoot = (view, t) => [t(`card.status.${view.status}`), ...view.errorText ? [view.errorText] : []].join("\n");
150462
+ var turnTextParts = (view, t) => {
150463
+ const headLines = titleAndSteps(view, t);
150464
+ const hasSteps = headLines.length > 1;
150141
150465
  if (view.running !== null) headLines.push(`\u{1F527} ${clip4(view.running, RUNNING_LABEL_MAX_CHARS)}`);
150142
150466
  const body = view.body.trim();
150143
- const footLines = [];
150144
- if (view.status === "working") {
150145
- if (body === "" && names.length === 0) footLines.push(t("card.status.working"));
150146
- } else {
150147
- footLines.push(t(`card.status.${view.status}`));
150148
- if (view.errorText !== null && view.errorText !== "") footLines.push(view.errorText);
150149
- }
150150
- return { head: headLines.join("\n"), body, foot: footLines.join("\n") };
150467
+ const foot = view.status !== "working" ? settledFoot(view, t) : body === "" && !hasSteps ? t("card.status.working") : "";
150468
+ return { head: headLines.join("\n"), body, foot };
150469
+ };
150470
+ var wecomSettledText = (view, t) => {
150471
+ const body = view.body.trim();
150472
+ const context2 = body === "" ? titleAndSteps(view, t).join("\n") : body;
150473
+ return joinParts(context2, settledFoot(view, t));
150474
+ };
150475
+ var wecomThinkingText = (view, t) => {
150476
+ const lines = titleAndSteps(view, t);
150477
+ lines.push(
150478
+ view.running === null ? t("card.thinking") : (
150479
+ // 文案自己以「…」结尾,截短的标签不再叠一个。
150480
+ t("card.usingTool", {
150481
+ name: clip4(view.running, RUNNING_LABEL_MAX_CHARS).replace(/…$/u, "")
150482
+ })
150483
+ )
150484
+ );
150485
+ return lines.join("\n").replace(/<\/?think>/gi, "");
150151
150486
  };
150152
150487
  var joinParts = (...parts) => parts.filter((part) => part !== "").join("\n\n");
150153
150488
 
@@ -150203,8 +150538,13 @@ var makeTurnWriter = (deps) => {
150203
150538
  };
150204
150539
 
150205
150540
  // src/telegramAdapter.ts
150206
- var COLUMNS2 = [
150207
- { key: "telegramToken", kind: "password", labelKey: "column.telegramToken.label" }
150541
+ var COLUMNS = [
150542
+ {
150543
+ key: "telegramToken",
150544
+ group: "essential",
150545
+ kind: "password",
150546
+ labelKey: "column.telegramToken.label"
150547
+ }
150208
150548
  ];
150209
150549
  var BACKOFF_START_MS = 1e3;
150210
150550
  var BACKOFF_MAX_MS = 6e4;
@@ -150322,7 +150662,7 @@ var makeTelegramAdapter = (deps) => {
150322
150662
  icon: "telegram",
150323
150663
  label: "Telegram",
150324
150664
  quotesReplies: true,
150325
- columns: COLUMNS2,
150665
+ columns: COLUMNS,
150326
150666
  validate: telegramConfigIssues,
150327
150667
  isConfigured: (binding) => telegramConfigIssues(binding).every((issue) => issue.code !== "required"),
150328
150668
  connect: async (binding, handlers) => {
@@ -150340,7 +150680,7 @@ var makeTelegramAdapter = (deps) => {
150340
150680
  let backoff = BACKOFF_START_MS;
150341
150681
  let botUsername;
150342
150682
  let botId;
150343
- let menuSent = false;
150683
+ let menuLanguage;
150344
150684
  let liveApi;
150345
150685
  const isSoloGroup = makeSoloGroupCheck(async (chatId) => {
150346
150686
  const count = await (await apiFor(binding)).getChatMemberCount(chatId);
@@ -150368,8 +150708,8 @@ var makeTelegramAdapter = (deps) => {
150368
150708
  if (running) handlers.onConnectionState?.({ state: "connected" });
150369
150709
  }
150370
150710
  if (!running) return;
150371
- if (!menuSent) {
150372
- menuSent = true;
150711
+ if (menuLanguage !== t.language) {
150712
+ menuLanguage = t.language;
150373
150713
  void api.setMyCommands(telegramCommandMenu(t)).catch((error) => {
150374
150714
  deps.log.warn("telegram could not set the command menu", {
150375
150715
  bot: binding.displayName,
@@ -150880,7 +151220,12 @@ var makeTurns = (core, deps) => {
150880
151220
  });
150881
151221
  return null;
150882
151222
  }
150883
- const modelSelection = chatOf(core.state, chatKey).model ?? project.defaultModelSelection;
151223
+ const modelSelection = await resolveDefaultModel(
151224
+ binding,
151225
+ chatOf(core.state, chatKey).model,
151226
+ project.defaultModelSelection,
151227
+ host.providers.instances
151228
+ );
150884
151229
  if (modelSelection === null) {
150885
151230
  host.log.warn("chat cannot start a thread: the project has no default model", {
150886
151231
  bot: binding.displayName,
@@ -151279,17 +151624,6 @@ var botOf = (config2, adapters, rowId) => {
151279
151624
  const adapter = adapters.find((candidate) => candidate.platform === platform);
151280
151625
  return { ...readBotView(rowId, raw, adapter ?? { label: platform }), platform };
151281
151626
  };
151282
- var reportOf = (bot, stage, ok, detail, hint) => ({
151283
- ref: diagnosticRef("conn"),
151284
- platform: bot.platform,
151285
- botName: bot.displayName,
151286
- rowId: bot.rowId,
151287
- stage,
151288
- ok,
151289
- detail,
151290
- at: (/* @__PURE__ */ new Date()).toISOString(),
151291
- ...hint === void 0 ? {} : { hint }
151292
- });
151293
151627
  var probe = async (adapters, bot) => {
151294
151628
  const adapter = adapters.find((candidate) => candidate.platform === bot.platform);
151295
151629
  if (adapter?.checkConnection === void 0) {
@@ -151315,14 +151649,31 @@ var probe = async (adapters, bot) => {
151315
151649
  );
151316
151650
  }
151317
151651
  };
151318
- var emitReport = (emit, report, action) => {
151652
+ var emitReport = (emit, report, action, testMessage) => {
151319
151653
  const detail = action.copyable ? formatDiagnosticCopy(report) : report.detail;
151320
151654
  if (report.ok) {
151655
+ const unsent = testMessage === void 0 || testMessage.sent ? void 0 : testMessage;
151656
+ const messageKey = unsent === void 0 ? `${action.keyPrefix}.done` : unsent.code === "test-message-failed" ? "rowAction.checkConnection.doneTestFailed" : "rowAction.checkConnection.doneNoTarget";
151321
151657
  emit({
151322
151658
  kind: "done",
151323
151659
  fills: {},
151324
- messageKey: `${action.keyPrefix}.done`,
151325
- warnings: [{ key: "rowAction.diagnostic.ref", params: { ref: report.ref, detail } }]
151660
+ messageKey,
151661
+ ...unsent?.code === "test-message-failed" ? { tone: "caution" } : {},
151662
+ // 顺序:该做什么 → 测试消息的诊断 → 凭据检查的诊断。
151663
+ warnings: [
151664
+ ...testMessage === void 0 ? [] : [
151665
+ {
151666
+ key: `rowAction.checkConnection.testMessage.${testMessage.sent ? "sent" : testMessage.code}`
151667
+ }
151668
+ ],
151669
+ ...unsent?.error === void 0 ? [] : [
151670
+ {
151671
+ key: "rowAction.checkConnection.testMessage.ref",
151672
+ params: { ref: unsent.error.ref, detail: unsent.error.detail }
151673
+ }
151674
+ ],
151675
+ { key: action.refKey, params: { ref: report.ref, detail } }
151676
+ ]
151326
151677
  });
151327
151678
  return;
151328
151679
  }
@@ -151340,7 +151691,7 @@ var emitReport = (emit, report, action) => {
151340
151691
  detail
151341
151692
  });
151342
151693
  };
151343
- var registerSettingsRowActions = (ctx, adapters, connections, inboundState, beginLarkCardActionProbe) => {
151694
+ var registerSettingsRowActions = (ctx, adapters, connections, inboundState, beginLarkCardActionProbe, connectionTests) => {
151344
151695
  const resolveBot = (rowId) => botOf(ctx.config, adapters, rowId);
151345
151696
  const registerProbeAction = (action) => {
151346
151697
  ctx.stream({
@@ -151369,7 +151720,10 @@ var registerSettingsRowActions = (ctx, adapters, connections, inboundState, begi
151369
151720
  );
151370
151721
  return;
151371
151722
  }
151372
- emitReport(emit, await probe(adapters, bot), action);
151723
+ const report = await probe(adapters, bot);
151724
+ const adapter = adapters.find((candidate) => candidate.platform === bot.platform);
151725
+ const testMessage = report.ok && action.sendsTestMessage && adapter !== void 0 ? await connectionTests.send(bot, adapter) : void 0;
151726
+ emitReport(emit, report, action, testMessage);
151373
151727
  }
151374
151728
  });
151375
151729
  };
@@ -151377,7 +151731,9 @@ var registerSettingsRowActions = (ctx, adapters, connections, inboundState, begi
151377
151731
  name: BOT_ACTION_COMMANDS.checkConnection,
151378
151732
  summary: "Probe an IM bot's platform credentials without calling a model.",
151379
151733
  keyPrefix: "rowAction.checkConnection",
151380
- copyable: false
151734
+ copyable: false,
151735
+ sendsTestMessage: true,
151736
+ refKey: "rowAction.checkConnection.credentialRef"
151381
151737
  });
151382
151738
  ctx.stream({
151383
151739
  name: BOT_ACTION_COMMANDS.reconnect,
@@ -151403,7 +151759,9 @@ var registerSettingsRowActions = (ctx, adapters, connections, inboundState, begi
151403
151759
  name: BOT_ACTION_COMMANDS.copyDiagnostics,
151404
151760
  summary: "Probe an IM bot and return a copy-paste diagnostic report.",
151405
151761
  keyPrefix: "rowAction.copyDiagnostics",
151406
- copyable: true
151762
+ copyable: true,
151763
+ sendsTestMessage: false,
151764
+ refKey: "rowAction.diagnostic.ref"
151407
151765
  });
151408
151766
  ctx.stream({
151409
151767
  name: BOT_ACTION_COMMANDS.resetInbox,
@@ -151715,17 +152073,22 @@ var registerSettingsCommands = (ctx, adapters, updateSettings, connections) => {
151715
152073
  });
151716
152074
  ctx.command({
151717
152075
  name: SETTINGS_VIEW_COMMAND,
151718
- description: "Read the chat-bridge settings view, bot columns, and this machine's proxies.",
152076
+ description: "Read the chat-bridge settings view, bot columns, model choices, and this machine's proxies.",
151719
152077
  scope: "read",
151720
152078
  payloadSchema: EMPTY_PAYLOAD,
151721
152079
  handle: async () => {
151722
- const [config2, listed] = await Promise.all([
152080
+ const [config2, listed, instances] = await Promise.all([
151723
152081
  ctx.settings.read(),
151724
- ctx.machineSettings.proxies.list({})
152082
+ ctx.machineSettings.proxies.list({}),
152083
+ ctx.providers.instances.list({}).catch(() => null)
151725
152084
  ]);
151726
152085
  return {
151727
152086
  view: buildSettingsView(config2, adapters),
151728
- columns: buildBotColumns(adapters),
152087
+ columns: buildBotColumns(
152088
+ adapters,
152089
+ modelChoices(instances ?? []),
152090
+ isModelCatalogComplete(instances)
152091
+ ),
151729
152092
  targetColumns: deliveryTargetColumns(),
151730
152093
  proxies: listed.map(proxyChoice)
151731
152094
  };
@@ -151807,9 +152170,10 @@ var registerSettingsCommands = (ctx, adapters, updateSettings, connections) => {
151807
152170
  var WECHAT_TEXT_MAX_CHARS = 1800;
151808
152171
 
151809
152172
  // src/wechatAdapter.ts
151810
- var COLUMNS3 = [
152173
+ var COLUMNS2 = [
151811
152174
  {
151812
152175
  key: "wechatBotToken",
152176
+ group: "essential",
151813
152177
  kind: "password",
151814
152178
  labelKey: "column.wechatBotToken.label",
151815
152179
  descriptionKey: "column.wechatBotToken.description"
@@ -151821,10 +152185,10 @@ var makeWechatAdapter = (deps) => {
151821
152185
  return {
151822
152186
  platform: "wechat",
151823
152187
  botIdentity: () => "wechat-stub",
151824
- icon: "wechat",
152188
+ icon: "weixin",
151825
152189
  // 平台名与设置表的平台选项同一个键,免得默认机器人名与选项写成两种叫法。
151826
152190
  label: deps.translate("platform.wechat"),
151827
- columns: COLUMNS3,
152191
+ columns: COLUMNS2,
151828
152192
  validate: wechatConfigIssues,
151829
152193
  isConfigured: (bot) => wechatConfigIssues(bot).length === 0,
151830
152194
  connect: (bot, handlers) => {
@@ -152467,9 +152831,19 @@ var WECOM_CARD_THROTTLE_MS = 400;
152467
152831
  var LIVE_STREAM_LIMIT = 200;
152468
152832
  var BACKOFF_START_MS2 = 1e3;
152469
152833
  var BACKOFF_MAX_MS2 = 6e4;
152470
- var COLUMNS4 = [
152471
- { key: "wecomBotId", kind: "text", labelKey: "column.wecomBotId.label" },
152472
- { key: "wecomBotSecret", kind: "password", labelKey: "column.wecomBotSecret.label" }
152834
+ var COLUMNS3 = [
152835
+ {
152836
+ key: "wecomBotId",
152837
+ group: "essential",
152838
+ kind: "text",
152839
+ labelKey: "column.wecomBotId.label"
152840
+ },
152841
+ {
152842
+ key: "wecomBotSecret",
152843
+ group: "essential",
152844
+ kind: "password",
152845
+ labelKey: "column.wecomBotSecret.label"
152846
+ }
152473
152847
  ];
152474
152848
  var defaultSleep2 = (ms) => new Promise((resolve2) => setTimeout(resolve2, ms));
152475
152849
  var credentials = (binding) => ({
@@ -152507,21 +152881,26 @@ var makeWecomAdapter = (deps) => {
152507
152881
  }
152508
152882
  };
152509
152883
  const emit = async (entry, view) => {
152510
- const { head, body, foot } = turnTextParts(view, t);
152511
152884
  if (view.status === "working") {
152512
- let shown = body;
152513
- const whole = joinParts(head, body, foot);
152514
- if (whole.length > WECOM_TEXT_MAX_CHARS) {
152515
- const marker = t("card.truncated", { count: 9999999 });
152516
- const room = WECOM_TEXT_MAX_CHARS - head.length - foot.length - marker.length - 6;
152517
- const kept = room > 0 ? body.slice(Math.max(0, body.length - room)) : "";
152518
- shown = `${t("card.truncated", { count: body.length - kept.length })}
152885
+ const body = view.body.trim();
152886
+ let content = `<think>${wecomThinkingText(view, t)}`;
152887
+ if (body !== "") {
152888
+ const prefix = `${content}</think>
152889
+ `;
152890
+ let shown = body;
152891
+ if (prefix.length + body.length > WECOM_TEXT_MAX_CHARS) {
152892
+ const marker = t("card.truncated", { count: 9999999 });
152893
+ const room = WECOM_TEXT_MAX_CHARS - prefix.length - marker.length - 6;
152894
+ const kept = room > 0 ? body.slice(Math.max(0, body.length - room)) : "";
152895
+ shown = `${t("card.truncated", { count: body.length - kept.length })}
152519
152896
  ${kept}`;
152897
+ }
152898
+ content = `${prefix}${shown}`;
152520
152899
  }
152521
- await sending("replyStream", () => entry.stream.send(joinParts(head, shown, foot), false));
152900
+ await sending("replyStream", () => entry.stream.send(content, false));
152522
152901
  return;
152523
152902
  }
152524
- const chunks = splitForWecom(joinParts(head, body, foot));
152903
+ const chunks = splitForWecom(wecomSettledText(view, t));
152525
152904
  const delivered = await sending("replyStream", () => entry.stream.send(chunks[0] ?? "", true));
152526
152905
  for (const chunk of delivered ? chunks.slice(1) : chunks) {
152527
152906
  await sending("sendMessage", () => entry.client.sendText(entry.chatId, chunk));
@@ -152538,7 +152917,7 @@ ${kept}`;
152538
152917
  botIdentity: (bot) => credentials(bot).botId,
152539
152918
  icon: "wecom",
152540
152919
  label: "WeCom",
152541
- columns: COLUMNS4,
152920
+ columns: COLUMNS3,
152542
152921
  unlistedValues: ["wecomEndpoint"],
152543
152922
  validate: wecomConfigIssues,
152544
152923
  isConfigured: (binding) => wecomConfigIssues(binding).length === 0,
@@ -153585,29 +153964,43 @@ var readWecomAppConnectConfig = (bot) => {
153585
153964
  };
153586
153965
 
153587
153966
  // src/wecomAppAdapter.ts
153588
- var COLUMNS5 = [
153967
+ var COLUMNS4 = [
153589
153968
  {
153590
153969
  key: "wecomAppCorpId",
153970
+ group: "essential",
153591
153971
  kind: "text",
153592
153972
  labelKey: "column.wecomAppCorpId.label",
153593
153973
  placeholderKey: "column.wecomAppCorpId.placeholder"
153594
153974
  },
153595
153975
  {
153596
153976
  key: "wecomAppAgentId",
153977
+ group: "essential",
153597
153978
  kind: "text",
153598
153979
  labelKey: "column.wecomAppAgentId.label",
153599
153980
  placeholderKey: "column.wecomAppAgentId.placeholder"
153600
153981
  },
153601
- { key: "wecomAppSecret", kind: "password", labelKey: "column.wecomAppSecret.label" },
153602
- { key: "wecomAppToken", kind: "password", labelKey: "column.wecomAppToken.label" },
153982
+ {
153983
+ key: "wecomAppSecret",
153984
+ group: "essential",
153985
+ kind: "password",
153986
+ labelKey: "column.wecomAppSecret.label"
153987
+ },
153988
+ {
153989
+ key: "wecomAppToken",
153990
+ group: "essential",
153991
+ kind: "password",
153992
+ labelKey: "column.wecomAppToken.label"
153993
+ },
153603
153994
  {
153604
153995
  key: "wecomAppEncodingAESKey",
153996
+ group: "essential",
153605
153997
  kind: "password",
153606
153998
  labelKey: "column.wecomAppEncodingAESKey.label",
153607
153999
  placeholderKey: "column.wecomAppEncodingAESKey.placeholder"
153608
154000
  },
153609
154001
  {
153610
154002
  key: "wecomAppCallbackBaseUrl",
154003
+ group: "more",
153611
154004
  kind: "text",
153612
154005
  labelKey: "column.wecomAppCallbackBaseUrl.label",
153613
154006
  descriptionKey: "column.wecomAppCallbackBaseUrl.description",
@@ -153615,6 +154008,7 @@ var COLUMNS5 = [
153615
154008
  },
153616
154009
  {
153617
154010
  key: "wecomAppApiBaseUrl",
154011
+ group: "more",
153618
154012
  kind: "text",
153619
154013
  labelKey: "column.wecomAppApiBaseUrl.label",
153620
154014
  descriptionKey: "column.wecomAppApiBaseUrl.description",
@@ -153622,6 +154016,7 @@ var COLUMNS5 = [
153622
154016
  },
153623
154017
  {
153624
154018
  key: "wecomAppListenPort",
154019
+ group: "more",
153625
154020
  kind: "text",
153626
154021
  labelKey: "column.wecomAppListenPort.label",
153627
154022
  descriptionKey: "column.wecomAppListenPort.description",
@@ -153629,6 +154024,7 @@ var COLUMNS5 = [
153629
154024
  },
153630
154025
  {
153631
154026
  key: "wecomAppListenHost",
154027
+ group: "more",
153632
154028
  kind: "text",
153633
154029
  labelKey: "column.wecomAppListenHost.label",
153634
154030
  descriptionKey: "column.wecomAppListenHost.description",
@@ -153718,12 +154114,12 @@ var makeWecomAppAdapter = (deps) => {
153718
154114
  if (chatId === "") return;
153719
154115
  if (message.msgtype === "event") {
153720
154116
  if (message.event === "subscribe" || message.event === "enter_agent") {
153721
- handlers.onGreeting?.(chatId, chatId);
154117
+ handlers.onGreeting?.(chatId, chatId, "private");
153722
154118
  }
153723
154119
  return;
153724
154120
  }
153725
154121
  const messageId = message.msgid ?? `${chatId}:${message.createTime ?? Date.now()}`;
153726
- const base2 = { messageId, chatId, senderId: chatId };
154122
+ const base2 = { messageId, chatId, senderId: chatId, chatKind: "private" };
153727
154123
  const receive = (inbound) => {
153728
154124
  void handlers.onMessage(inbound).catch((error) => {
153729
154125
  deps.log.warn("wecom-app could not persist inbound message", {
@@ -153760,7 +154156,7 @@ var makeWecomAppAdapter = (deps) => {
153760
154156
  },
153761
154157
  icon: "wecom",
153762
154158
  label: deps.translate("platform.wecom-app"),
153763
- columns: COLUMNS5,
154159
+ columns: COLUMNS4,
153764
154160
  // 设置页标格子用完整的逐列结果。算不算配齐仍是原来那份,不能收成「一个问题都没有」:
153765
154161
  // 必填都填了、EncodingAESKey 格式对。别的列格式不对的行照样交给 connect 报出来(一条日志,
153766
154162
  // 不重试),也不挡设置页的「检查连接」去探出站凭据。
@@ -153958,7 +154354,7 @@ var makeWecomAppAdapter = (deps) => {
153958
154354
  // src/index.ts
153959
154355
  var definition = {
153960
154356
  id: "im",
153961
- apiVersion: 24,
154357
+ apiVersion: 25,
153962
154358
  // 第二个参数只给插件自己的测试夹具:换掉文件 IO 与派发重试的等待。宿主只传 ctx。
153963
154359
  activate: async (ctx, testing = {}) => {
153964
154360
  const { dispatchSleep } = testing;
@@ -154099,9 +154495,9 @@ var definition = {
154099
154495
  now: testing.retryClock?.now,
154100
154496
  timeZone: testing.timeZone
154101
154497
  });
154102
- const sessions = makeSessions(core);
154103
- const modelPlan = makeModelPlan(core, turns);
154104
154498
  const projectRows = makeProjectRows(core);
154499
+ const sessions = makeSessions(core, projectRows);
154500
+ const modelPlan = makeModelPlan(core, turns);
154105
154501
  const chatQueue = makeChatQueue(core);
154106
154502
  const panelWiring = makePanelWiring(core, {
154107
154503
  enqueueForChat: chatQueue.enqueueForChat,
@@ -154163,7 +154559,14 @@ var definition = {
154163
154559
  await chatQueue.closeAndDrain();
154164
154560
  }
154165
154561
  });
154166
- registerSettingsRowActions(ctx, adapters, connections, inboundState, lark.beginCardActionProbe);
154562
+ registerSettingsRowActions(
154563
+ ctx,
154564
+ adapters,
154565
+ connections,
154566
+ inboundState,
154567
+ lark.beginCardActionProbe,
154568
+ inbound.connectionTests
154569
+ );
154167
154570
  registerSettingsCommands(ctx, adapters, updateSettings, connections);
154168
154571
  const { bots, connecting } = connections.reconcile();
154169
154572
  const deliveryHttp = makeDeliveryHttp({
@@ -154176,6 +154579,7 @@ var definition = {
154176
154579
  if (typeof ctx.settings.onChange === "function") {
154177
154580
  ctx.settings.onChange(async (config2) => {
154178
154581
  setLanguage(parseReplyLanguage(config2[REPLY_LANGUAGE_FIELD]));
154582
+ inbound.connectionTests.retainRows(Object.keys(config2[BOTS_FIELD] ?? {}));
154179
154583
  connections.reconcile();
154180
154584
  await deliveryHttp.apply(config2);
154181
154585
  });