@ycodium-ai/plugin-im 0.2.2974 → 0.2.2978

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
@@ -126213,9 +126213,6 @@ var ATTACHED_THREAD_LIMIT = 200;
126213
126213
  var attachedKey = (chatKey, threadId) => `${chatKey}
126214
126214
  ${threadId}`;
126215
126215
  var hasAttached = (state, chatKey, threadId) => state.attached?.[attachedKey(chatKey, threadId)] !== void 0;
126216
- var attachedOnChat = (state, chatKey, projectId) => Object.values(state.attached ?? {}).filter(
126217
- (record2) => record2.chatKey === chatKey && record2.projectId === projectId
126218
- );
126219
126216
  var nonEmpty = (value2) => typeof value2 === "string" && value2 !== "" ? value2 : null;
126220
126217
  var readAttached = (data) => {
126221
126218
  const chatKey = nonEmpty(data["chatKey"]);
@@ -126655,6 +126652,49 @@ var readString = (row, key) => {
126655
126652
  return typeof value2 === "string" ? value2.trim() : "";
126656
126653
  };
126657
126654
 
126655
+ // src/settingsContract.ts
126656
+ var SETTINGS_VIEW_COMMAND = "settings.view";
126657
+ var SETTINGS_SAVE_BOT_COMMAND = "settings.saveBot";
126658
+ var SETTINGS_REMOVE_BOT_COMMAND = "settings.removeBot";
126659
+ var SETTINGS_SAVE_GENERAL_COMMAND = "settings.saveGeneral";
126660
+ var SETTINGS_SAVE_TARGET_COMMAND = "settings.saveTarget";
126661
+ var SETTINGS_REMOVE_TARGET_COMMAND = "settings.removeTarget";
126662
+ var LARK_SETUP_SESSION_COMMAND = "larkSetup.session";
126663
+ var BOT_ACTION_COMMANDS = {
126664
+ checkConnection: "im.settings.checkConnection",
126665
+ copyDiagnostics: "im.settings.copyDiagnostics",
126666
+ larkRepair: "im.settings.larkRepair"
126667
+ };
126668
+ var DEFAULT_PLATFORM = "lark";
126669
+ var REPLY_LANGUAGE_OPTIONS = [
126670
+ { value: "zh-CN", labelKey: "settings.language.zhCN" },
126671
+ { value: "en", labelKey: "settings.language.en" }
126672
+ ];
126673
+ var DELIVERY_BOT_COLUMN = "botId";
126674
+ var platformOf = (row) => readString(row, "platform") || DEFAULT_PLATFORM;
126675
+ var foldFlowItem = (snapshot, item) => {
126676
+ switch (item.kind) {
126677
+ case "qr": {
126678
+ const { expired: _expired, ...rest } = snapshot;
126679
+ const qr = item.expiresAt === void 0 ? { payload: item.payload } : { payload: item.payload, expiresAt: item.expiresAt };
126680
+ return { ...rest, qr };
126681
+ }
126682
+ case "status": {
126683
+ const status = item.params === void 0 ? { statusKey: item.statusKey } : { statusKey: item.statusKey, params: item.params };
126684
+ if (item.expired !== true) return { ...snapshot, status };
126685
+ const { qr: _qr, ...rest } = snapshot;
126686
+ return { ...rest, status, expired: true };
126687
+ }
126688
+ case "steps":
126689
+ return { ...snapshot, steps: item.steps };
126690
+ case "done":
126691
+ case "failed": {
126692
+ const { status: _status, ...rest } = snapshot;
126693
+ return { ...rest, result: item };
126694
+ }
126695
+ }
126696
+ };
126697
+
126658
126698
  // src/targetKind.ts
126659
126699
  var TARGET_KIND_OPTIONS = [
126660
126700
  { value: "chat", labelKey: "settings.deliveryKind.chat" },
@@ -126674,26 +126714,37 @@ var DELIVERY_HTTP_TOKEN_FIELD = "deliveryHttpToken";
126674
126714
  var DEFAULT_DELIVERY_HTTP_PORT = 3847;
126675
126715
  var TARGET_ID_PATTERN = /^[A-Za-z0-9._:@-]{1,128}$/;
126676
126716
  var isValidTargetId = (value2) => TARGET_ID_PATTERN.test(value2);
126717
+ var REQUIRED_TARGET_COLUMNS = [DELIVERY_BOT_COLUMN, "targetId", "platformChatId"];
126718
+ var targetIssues = (row) => {
126719
+ const issues = [];
126720
+ for (const column of REQUIRED_TARGET_COLUMNS) {
126721
+ if (readString(row, column) === "") issues.push({ column, code: "required" });
126722
+ }
126723
+ const targetId = readString(row, "targetId");
126724
+ if (targetId !== "" && !isValidTargetId(targetId)) {
126725
+ issues.push({ column: "targetId", code: "format" });
126726
+ }
126727
+ const kind = readString(row, "kind");
126728
+ if (kind !== "" && !isTargetKind(kind)) issues.push({ column: "kind", code: "format" });
126729
+ return issues;
126730
+ };
126677
126731
  var readDeliveryTargets = (config2) => {
126678
126732
  const table2 = config2[DELIVERY_TARGETS_FIELD];
126679
126733
  if (typeof table2 !== "object" || table2 === null) return [];
126680
126734
  return Object.entries(table2).flatMap(([rowId, row]) => {
126681
126735
  if (typeof row !== "object" || row === null) return [];
126682
126736
  const record2 = row;
126683
- const botId = readString(record2, "botId");
126684
- const targetId = readString(record2, "targetId");
126685
- const platformChatId = readString(record2, "platformChatId");
126686
126737
  const kind = readString(record2, "kind") || "chat";
126687
- if (botId === "" || targetId === "" || platformChatId === "" || !isTargetKind(kind)) return [];
126688
- if (!isValidTargetId(targetId)) return [];
126738
+ if (targetIssues(record2).length > 0 || !isTargetKind(kind)) return [];
126739
+ const targetId = readString(record2, "targetId");
126689
126740
  return [
126690
126741
  {
126691
126742
  rowId,
126692
- botId,
126743
+ botId: readString(record2, DELIVERY_BOT_COLUMN),
126693
126744
  targetId,
126694
126745
  name: readString(record2, "name") || targetId,
126695
126746
  kind,
126696
- platformChatId
126747
+ platformChatId: readString(record2, "platformChatId")
126697
126748
  }
126698
126749
  ];
126699
126750
  });
@@ -126710,6 +126761,41 @@ var readDeliveryHttpToken = (config2) => {
126710
126761
  const value2 = config2[DELIVERY_HTTP_TOKEN_FIELD];
126711
126762
  return typeof value2 === "string" ? value2 : "";
126712
126763
  };
126764
+ var deliveryTargetColumns = () => [
126765
+ {
126766
+ key: DELIVERY_BOT_COLUMN,
126767
+ kind: "text",
126768
+ labelKey: "column.deliveryBotId.label",
126769
+ descriptionKey: "column.deliveryBotId.description",
126770
+ placeholderKey: "column.deliveryBotId.placeholder"
126771
+ },
126772
+ {
126773
+ key: "targetId",
126774
+ kind: "text",
126775
+ labelKey: "column.deliveryTargetId.label",
126776
+ descriptionKey: "column.deliveryTargetId.description",
126777
+ placeholderKey: "column.deliveryTargetId.placeholder"
126778
+ },
126779
+ {
126780
+ key: "name",
126781
+ kind: "text",
126782
+ labelKey: "column.deliveryName.label",
126783
+ placeholderKey: "column.deliveryName.placeholder"
126784
+ },
126785
+ {
126786
+ key: "kind",
126787
+ kind: "select",
126788
+ labelKey: "column.deliveryKind.label",
126789
+ options: [...TARGET_KIND_OPTIONS]
126790
+ },
126791
+ {
126792
+ key: "platformChatId",
126793
+ kind: "text",
126794
+ labelKey: "column.deliveryPlatformChatId.label",
126795
+ descriptionKey: "column.deliveryPlatformChatId.description",
126796
+ placeholderKey: "column.deliveryPlatformChatId.placeholder"
126797
+ }
126798
+ ];
126713
126799
  var buildDeliverySettingsFields = () => [
126714
126800
  {
126715
126801
  key: DELIVERY_HTTP_ENABLED_FIELD,
@@ -126736,48 +126822,13 @@ var buildDeliverySettingsFields = () => [
126736
126822
  kind: "table",
126737
126823
  labelKey: "settings.deliveryTargets.label",
126738
126824
  descriptionKey: "settings.deliveryTargets.description",
126739
- columns: [
126740
- {
126741
- key: "botId",
126742
- kind: "text",
126743
- labelKey: "column.deliveryBotId.label",
126744
- descriptionKey: "column.deliveryBotId.description",
126745
- placeholderKey: "column.deliveryBotId.placeholder"
126746
- },
126747
- {
126748
- key: "targetId",
126749
- kind: "text",
126750
- labelKey: "column.deliveryTargetId.label",
126751
- descriptionKey: "column.deliveryTargetId.description",
126752
- placeholderKey: "column.deliveryTargetId.placeholder"
126753
- },
126754
- {
126755
- key: "name",
126756
- kind: "text",
126757
- labelKey: "column.deliveryName.label",
126758
- placeholderKey: "column.deliveryName.placeholder"
126759
- },
126760
- {
126761
- key: "kind",
126762
- kind: "select",
126763
- labelKey: "column.deliveryKind.label",
126764
- options: [...TARGET_KIND_OPTIONS]
126765
- },
126766
- {
126767
- key: "platformChatId",
126768
- kind: "text",
126769
- labelKey: "column.deliveryPlatformChatId.label",
126770
- descriptionKey: "column.deliveryPlatformChatId.description",
126771
- placeholderKey: "column.deliveryPlatformChatId.placeholder"
126772
- }
126773
- ]
126825
+ columns: [...deliveryTargetColumns()]
126774
126826
  }
126775
126827
  ];
126776
126828
 
126777
126829
  // src/botConfig.ts
126778
126830
  var BOTS_FIELD = "bots";
126779
126831
  var REPLY_LANGUAGE_FIELD = "replyLanguage";
126780
- var DEFAULT_PLATFORM = "lark";
126781
126832
  var parseGroupMention = (value2) => value2 === "never" || value2 === "ambient" ? value2 : "always";
126782
126833
  var INBOUND_RUNTIME_MODES = /* @__PURE__ */ new Set([
126783
126834
  "approval-required",
@@ -126809,6 +126860,7 @@ var LARK_PANEL_MODES = new Set(
126809
126860
  );
126810
126861
  var DEFAULT_LARK_PANEL_MODE = "default";
126811
126862
  var chatKeyOf = (binding) => `${binding.rowId}:${binding.chatId}`;
126863
+ var isChatKeyOfRow = (chatKey, rowId) => chatKey.startsWith(chatKeyOf({ rowId, chatId: "" }));
126812
126864
  var parseQueueMode = (value2) => QUEUE_MODES.has(value2) ? value2 : DEFAULT_QUEUE_MODE;
126813
126865
  var parseAccessMode = (value2) => ACCESS_MODES.has(value2) ? value2 : DEFAULT_ACCESS_MODE;
126814
126866
  var parseLarkPanelMode = (value2) => LARK_PANEL_MODES.has(value2) ? value2 : DEFAULT_LARK_PANEL_MODE;
@@ -126817,36 +126869,7 @@ var parseOnOff = (value2, defaultOn) => {
126817
126869
  if (value2 === "off") return false;
126818
126870
  return defaultOn;
126819
126871
  };
126820
- var ROW_ACTION_COMMANDS = {
126821
- checkConnection: "im.settings.checkConnection",
126822
- copyDiagnostics: "im.settings.copyDiagnostics",
126823
- larkRepair: "im.settings.larkRepair"
126824
- };
126825
- var BOT_ROW_ACTIONS = [
126826
- {
126827
- id: "checkConnection",
126828
- labelKey: "rowAction.checkConnection.label",
126829
- descriptionKey: "rowAction.checkConnection.description",
126830
- command: ROW_ACTION_COMMANDS.checkConnection,
126831
- fills: []
126832
- },
126833
- {
126834
- id: "copyDiagnostics",
126835
- labelKey: "rowAction.copyDiagnostics.label",
126836
- descriptionKey: "rowAction.copyDiagnostics.description",
126837
- command: ROW_ACTION_COMMANDS.copyDiagnostics,
126838
- fills: []
126839
- },
126840
- {
126841
- id: "larkRepair",
126842
- labelKey: "rowAction.larkRepair.label",
126843
- descriptionKey: "rowAction.larkRepair.description",
126844
- command: ROW_ACTION_COMMANDS.larkRepair,
126845
- showWhen: { key: "platform", equals: ["lark"] },
126846
- fills: []
126847
- }
126848
- ];
126849
- var buildSettingsCard = (adapters) => {
126872
+ var buildBotColumns = (adapters) => {
126850
126873
  const platformColumn = {
126851
126874
  key: "platform",
126852
126875
  kind: "select",
@@ -126864,134 +126887,139 @@ var buildSettingsCard = (adapters) => {
126864
126887
  showWhen: { key: platformColumn.key, equals: [adapter.platform] }
126865
126888
  }))
126866
126889
  );
126890
+ return [
126891
+ {
126892
+ key: "displayName",
126893
+ kind: "text",
126894
+ labelKey: "column.displayName.label",
126895
+ placeholderKey: "column.displayName.placeholder"
126896
+ },
126897
+ platformColumn,
126898
+ ...credentialColumns,
126899
+ {
126900
+ key: "accessMode",
126901
+ kind: "select",
126902
+ labelKey: "column.accessMode.label",
126903
+ descriptionKey: "column.accessMode.description",
126904
+ options: [...ACCESS_MODE_OPTIONS]
126905
+ },
126906
+ {
126907
+ key: "allowedSenders",
126908
+ kind: "text",
126909
+ labelKey: "column.allowedSenders.label",
126910
+ descriptionKey: "column.allowedSenders.description",
126911
+ // 缺省/空串=allowlist(与 parseAccessMode 一致),仍显示白名单框。
126912
+ showWhen: { key: "accessMode", equals: ["allowlist", ""] }
126913
+ },
126914
+ {
126915
+ // 选项由宿主填,连三态一起——插件只声明「这个格子装的是代理」,宿主因此
126916
+ // 也就认得出这一行引用了哪个代理,删代理时拦得住(路线图 #64)。
126917
+ key: "proxy",
126918
+ kind: "proxy",
126919
+ labelKey: "column.proxy.label",
126920
+ descriptionKey: "column.proxy.description"
126921
+ },
126922
+ {
126923
+ key: "runtimeMode",
126924
+ kind: "select",
126925
+ labelKey: "column.runtimeMode.label",
126926
+ descriptionKey: "column.runtimeMode.description",
126927
+ options: [
126928
+ { value: "approval-required", labelKey: "settings.runtimeMode.approvalRequired" },
126929
+ { value: "auto-accept-edits", labelKey: "settings.runtimeMode.autoAcceptEdits" },
126930
+ { value: "auto", labelKey: "settings.runtimeMode.auto" },
126931
+ { value: "full-access", labelKey: "settings.runtimeMode.fullAccess" }
126932
+ ]
126933
+ },
126934
+ {
126935
+ key: "groupMention",
126936
+ kind: "select",
126937
+ labelKey: "column.groupMention.label",
126938
+ descriptionKey: "column.groupMention.description",
126939
+ options: [
126940
+ { value: "always", labelKey: "settings.groupMention.always" },
126941
+ { value: "never", labelKey: "settings.groupMention.never" },
126942
+ { value: "ambient", labelKey: "settings.groupMention.ambient" }
126943
+ ]
126944
+ },
126945
+ {
126946
+ key: "queueMode",
126947
+ kind: "select",
126948
+ labelKey: "column.queueMode.label",
126949
+ descriptionKey: "column.queueMode.description",
126950
+ options: [...QUEUE_MODE_OPTIONS]
126951
+ },
126952
+ {
126953
+ key: "larkPanelMode",
126954
+ kind: "select",
126955
+ labelKey: "column.larkPanelMode.label",
126956
+ descriptionKey: "column.larkPanelMode.description",
126957
+ showWhen: { key: "platform", equals: ["lark"] },
126958
+ options: [...LARK_PANEL_MODE_OPTIONS]
126959
+ },
126960
+ {
126961
+ key: "wecomNumberedHints",
126962
+ kind: "select",
126963
+ labelKey: "column.wecomNumberedHints.label",
126964
+ descriptionKey: "column.wecomNumberedHints.description",
126965
+ showWhen: { key: "platform", equals: ["wecom"] },
126966
+ options: [
126967
+ { value: "on", labelKey: "settings.onOff.on" },
126968
+ { value: "off", labelKey: "settings.onOff.off" }
126969
+ ]
126970
+ }
126971
+ ];
126972
+ };
126973
+ var buildSettingsCard = (adapters) => {
126867
126974
  return {
126868
126975
  titleKey: "settings.title",
126869
126976
  descriptionKey: "settings.description",
126977
+ presentation: "hidden",
126870
126978
  fields: [
126871
126979
  {
126872
126980
  key: REPLY_LANGUAGE_FIELD,
126873
126981
  kind: "select",
126874
126982
  labelKey: "settings.language.label",
126875
126983
  descriptionKey: "settings.language.description",
126876
- options: [
126877
- { value: "zh-CN", labelKey: "settings.language.zhCN" },
126878
- { value: "en", labelKey: "settings.language.en" }
126879
- ]
126984
+ options: [...REPLY_LANGUAGE_OPTIONS]
126880
126985
  },
126881
126986
  {
126882
126987
  key: BOTS_FIELD,
126883
126988
  kind: "table",
126884
126989
  labelKey: "settings.bots",
126885
- columns: [
126886
- {
126887
- key: "displayName",
126888
- kind: "text",
126889
- labelKey: "column.displayName.label",
126890
- placeholderKey: "column.displayName.placeholder"
126891
- },
126892
- platformColumn,
126893
- ...credentialColumns,
126894
- {
126895
- key: "accessMode",
126896
- kind: "select",
126897
- labelKey: "column.accessMode.label",
126898
- descriptionKey: "column.accessMode.description",
126899
- options: [...ACCESS_MODE_OPTIONS]
126900
- },
126901
- {
126902
- key: "allowedSenders",
126903
- kind: "text",
126904
- labelKey: "column.allowedSenders.label",
126905
- descriptionKey: "column.allowedSenders.description",
126906
- // 缺省/空串=allowlist(与 parseAccessMode 一致),仍显示白名单框。
126907
- showWhen: { key: "accessMode", equals: ["allowlist", ""] }
126908
- },
126909
- {
126910
- // 选项由宿主填,连三态一起——插件只声明「这个格子装的是代理」,宿主因此
126911
- // 也就认得出这一行引用了哪个代理,删代理时拦得住(路线图 #64)。
126912
- key: "proxy",
126913
- kind: "proxy",
126914
- labelKey: "column.proxy.label",
126915
- descriptionKey: "column.proxy.description"
126916
- },
126917
- {
126918
- key: "runtimeMode",
126919
- kind: "select",
126920
- labelKey: "column.runtimeMode.label",
126921
- descriptionKey: "column.runtimeMode.description",
126922
- options: [
126923
- { value: "approval-required", labelKey: "settings.runtimeMode.approvalRequired" },
126924
- { value: "auto-accept-edits", labelKey: "settings.runtimeMode.autoAcceptEdits" },
126925
- { value: "auto", labelKey: "settings.runtimeMode.auto" },
126926
- { value: "full-access", labelKey: "settings.runtimeMode.fullAccess" }
126927
- ]
126928
- },
126929
- {
126930
- key: "groupMention",
126931
- kind: "select",
126932
- labelKey: "column.groupMention.label",
126933
- descriptionKey: "column.groupMention.description",
126934
- options: [
126935
- { value: "always", labelKey: "settings.groupMention.always" },
126936
- { value: "never", labelKey: "settings.groupMention.never" },
126937
- { value: "ambient", labelKey: "settings.groupMention.ambient" }
126938
- ]
126939
- },
126940
- {
126941
- key: "queueMode",
126942
- kind: "select",
126943
- labelKey: "column.queueMode.label",
126944
- descriptionKey: "column.queueMode.description",
126945
- options: [...QUEUE_MODE_OPTIONS]
126946
- },
126947
- {
126948
- key: "larkPanelMode",
126949
- kind: "select",
126950
- labelKey: "column.larkPanelMode.label",
126951
- descriptionKey: "column.larkPanelMode.description",
126952
- showWhen: { key: "platform", equals: ["lark"] },
126953
- options: [...LARK_PANEL_MODE_OPTIONS]
126954
- },
126955
- {
126956
- key: "wecomNumberedHints",
126957
- kind: "select",
126958
- labelKey: "column.wecomNumberedHints.label",
126959
- descriptionKey: "column.wecomNumberedHints.description",
126960
- showWhen: { key: "platform", equals: ["wecom"] },
126961
- options: [
126962
- { value: "on", labelKey: "settings.onOff.on" },
126963
- { value: "off", labelKey: "settings.onOff.off" }
126964
- ]
126965
- }
126966
- ],
126967
- rowActions: [...BOT_ROW_ACTIONS]
126990
+ columns: [...buildBotColumns(adapters)]
126968
126991
  },
126969
126992
  ...buildDeliverySettingsFields()
126970
126993
  ]
126971
126994
  };
126972
126995
  };
126996
+ var readBotView = (rowId, row, adapter) => {
126997
+ const values = {};
126998
+ for (const key of Object.keys(row)) values[key] = readString(row, key);
126999
+ const proxyValue = readString(row, "proxy");
127000
+ return {
127001
+ rowId,
127002
+ displayName: readString(row, "displayName") || adapter.label,
127003
+ // 空串=跟机器默认(省略),`direct`=明确不加代理(null),其余就是那个 id。
127004
+ proxy: proxyValue === "" ? void 0 : proxyValue === PLUGIN_PROXY_DIRECT ? null : proxyValue,
127005
+ values,
127006
+ groupMention: parseGroupMention(readString(row, "groupMention"))
127007
+ };
127008
+ };
126973
127009
  var readBot = (adapterFor, rowId, row) => {
126974
- const platform = readString(row, "platform") || DEFAULT_PLATFORM;
127010
+ const platform = platformOf(row);
126975
127011
  const adapter = adapterFor(platform);
126976
127012
  if (adapter === void 0) return null;
126977
- const values = {};
126978
- for (const key of Object.keys(row)) values[key] = readString(row, key);
126979
127013
  const requested = readString(row, "runtimeMode");
126980
- const proxyValue = readString(row, "proxy");
126981
127014
  const allowedSenders = new Set(
126982
127015
  readString(row, "allowedSenders").split(",").map((entry) => entry.trim()).filter((entry) => entry !== "")
126983
127016
  );
126984
127017
  const bot = {
126985
- rowId,
127018
+ ...readBotView(rowId, row, adapter),
126986
127019
  platform,
126987
- displayName: readString(row, "displayName") || adapter.label,
126988
- // 空串=跟机器默认(省略),`direct`=明确不加代理(null),其余就是那个 id。
126989
- proxy: proxyValue === "" ? void 0 : proxyValue === PLUGIN_PROXY_DIRECT ? null : proxyValue,
126990
127020
  accessMode: parseAccessMode(readString(row, "accessMode")),
126991
127021
  allowedSenders,
126992
- values,
126993
127022
  runtimeMode: INBOUND_RUNTIME_MODES.has(requested) ? requested : DEFAULT_INBOUND_RUNTIME_MODE,
126994
- groupMention: parseGroupMention(readString(row, "groupMention")),
126995
127023
  queueMode: parseQueueMode(readString(row, "queueMode")),
126996
127024
  larkPanelMode: parseLarkPanelMode(readString(row, "larkPanelMode")),
126997
127025
  wecomNumberedHints: parseOnOff(readString(row, "wecomNumberedHints"), true)
@@ -127014,7 +127042,7 @@ var bindingOf = (bot, chatId, state) => ({
127014
127042
  projectId: chatOf(state, chatKeyOf({ rowId: bot.rowId, chatId })).projectId ?? ""
127015
127043
  });
127016
127044
  var findBindingByChatKey = (bots, state, chatKey) => {
127017
- const bot = bots.find((candidate) => chatKey.startsWith(`${candidate.rowId}:`));
127045
+ const bot = bots.find((candidate) => isChatKeyOfRow(chatKey, candidate.rowId));
127018
127046
  return bot === void 0 ? void 0 : bindingOf(bot, chatKey.slice(bot.rowId.length + 1), state);
127019
127047
  };
127020
127048
  var isAllowedSender = (bot, sender) => {
@@ -127123,13 +127151,15 @@ var makeApprovals = (core, anchors) => {
127123
127151
  if (binding === void 0) return;
127124
127152
  const adapter = adapterFor(binding.platform);
127125
127153
  if (adapter === void 0) return;
127126
- const pending = await host.projections.listPendingApprovals(threadId);
127154
+ const pending = await host.approvals.pending.list({ threadId });
127127
127155
  const fresh = pending.filter(
127128
127156
  (candidate) => core.state.approvals[candidate.requestId] === void 0
127129
127157
  );
127130
127158
  const offers = (adapter.answerByText === void 0 ? fresh : fresh.slice(0, 1)).map(
127131
127159
  (approval) => {
127132
- const offered = approval.options.filter((option) => option.decision !== "acceptAlways");
127160
+ const offered = (approval.options ?? []).filter(
127161
+ (option) => option.decision !== "acceptAlways"
127162
+ );
127133
127163
  const options = offered.length > 0 ? offered : [
127134
127164
  { decision: "accept", label: t("approval.allow") },
127135
127165
  { decision: "decline", label: t("approval.deny") }
@@ -127227,6 +127257,57 @@ var pickProject = (arg, listed, rows) => {
127227
127257
  return prefixed.length === 1 ? prefixed[0]?.projectId ?? null : null;
127228
127258
  };
127229
127259
 
127260
+ // src/sessionList.ts
127261
+ var SESSION_LIST_MAX = 20;
127262
+ var isSessionRunning = (status) => status === "running";
127263
+ var SESSION_ID_SHORT_LENGTH = 8;
127264
+ var ageLabel = (updatedAt, now, t) => {
127265
+ const at = Date.parse(updatedAt);
127266
+ if (Number.isNaN(at)) return "";
127267
+ const minutes = Math.floor(Math.max(0, now - at) / 6e4);
127268
+ if (minutes < 1) return t("sessions.age.justNow");
127269
+ if (minutes < 60) return t("sessions.age.minutes", { count: minutes });
127270
+ const hours = Math.floor(minutes / 60);
127271
+ if (hours < 24) return t("sessions.age.hours", { count: hours });
127272
+ return t("sessions.age.days", { count: Math.floor(hours / 24) });
127273
+ };
127274
+ var shortId = (threadId) => threadId.slice(0, SESSION_ID_SHORT_LENGTH);
127275
+ var sessionLine = (row, position, now, t) => {
127276
+ const title = row.title.replace(/\s+/g, " ").trim() || t("sessions.untitled");
127277
+ const age = ageLabel(row.updatedAt, now, t);
127278
+ const badges = [
127279
+ row.current ? t("sessions.badge.current") : null,
127280
+ row.running ? t("sessions.badge.running") : null,
127281
+ row.elsewhere ? t("sessions.badge.elsewhere") : null,
127282
+ row.origin === "web" ? t("sessions.badge.web") : null
127283
+ ].filter((badge) => badge !== null);
127284
+ return [`${String(position)}. ${title}`, age, ...badges, shortId(row.threadId)].filter((part) => part !== "").join(" \xB7 ");
127285
+ };
127286
+ var formatSessions = (rows, now, t) => {
127287
+ if (rows.length === 0) return t("sessions.empty");
127288
+ const shown = rows.slice(0, SESSION_LIST_MAX);
127289
+ return [
127290
+ t("sessions.title", { count: rows.length }),
127291
+ ...shown.map((row, index) => sessionLine(row, index + 1, now, t)),
127292
+ ...rows.length > shown.length ? [t("sessions.more", { count: rows.length - shown.length })] : [],
127293
+ "",
127294
+ t("sessions.footer")
127295
+ ].join("\n");
127296
+ };
127297
+ var listedThreadIds = (rows) => rows.slice(0, SESSION_LIST_MAX).map((row) => row.threadId);
127298
+ var looksLikeListNumber = (wanted) => /^[1-9]\d*$/u.test(wanted) && wanted.length <= String(SESSION_LIST_MAX).length;
127299
+ var pickSession = (arg, listed, rows) => {
127300
+ const wanted = arg.trim();
127301
+ if (wanted === "") return null;
127302
+ if (looksLikeListNumber(wanted)) {
127303
+ const ids = listed ?? listedThreadIds(rows);
127304
+ return ids[Number(wanted) - 1] ?? null;
127305
+ }
127306
+ const needle = wanted.toLowerCase();
127307
+ const matches = rows.filter((row) => row.threadId.toLowerCase().startsWith(needle));
127308
+ return matches.length === 1 ? matches[0]?.threadId ?? null : null;
127309
+ };
127310
+
127230
127311
  // src/chatSetup.ts
127231
127312
  var makeChatSetup = (core, deps) => {
127232
127313
  const { host, t, adapterFor, commit, deliver, bindingOf: bindingOf2, hasPanel } = core;
@@ -127285,7 +127366,7 @@ var makeChatSetup = (core, deps) => {
127285
127366
  const threadId = chatOf(core.state, chatKey).threadId;
127286
127367
  if (threadId !== null) {
127287
127368
  const thread = await host.projections.getThread(threadId).catch(() => null);
127288
- if (thread?.session?.activeTurnId != null) {
127369
+ if (isSessionRunning(thread?.session?.status)) {
127289
127370
  await deliver(binding, t("repo.busy"));
127290
127371
  return;
127291
127372
  }
@@ -127640,7 +127721,7 @@ var makeCommands = (core, deps) => {
127640
127721
  return true;
127641
127722
  }
127642
127723
  const thread = await host.projections.getThread(threadId);
127643
- const running = thread?.session?.activeTurnId != null;
127724
+ const running = isSessionRunning(thread?.session?.status);
127644
127725
  await deliver(
127645
127726
  binding,
127646
127727
  [
@@ -128443,6 +128524,10 @@ var makeBoundedMap = (limit) => {
128443
128524
  const oldest = entries.keys().next().value;
128444
128525
  if (oldest !== void 0) entries.delete(oldest);
128445
128526
  }
128527
+ },
128528
+ /** 忘掉键满足条件的那些。 */
128529
+ forget: (matches) => {
128530
+ for (const key of [...entries.keys()]) if (matches(key)) entries.delete(key);
128446
128531
  }
128447
128532
  };
128448
128533
  };
@@ -129569,6 +129654,44 @@ var SETUP_ITEM_SCHEMA = makeFlowItemSchema({
129569
129654
  qrRequired: ["payload", "expiresAt"],
129570
129655
  doneRequired: ["messageKey"]
129571
129656
  });
129657
+ var TERMINAL_ITEM_BRANCHES = SETUP_ITEM_SCHEMA.oneOf.filter(
129658
+ (branch) => ["done", "failed"].includes(branch.properties.kind.const)
129659
+ );
129660
+ var SETUP_SNAPSHOT_SCHEMA = {
129661
+ type: "object",
129662
+ additionalProperties: false,
129663
+ properties: {
129664
+ qr: {
129665
+ type: "object",
129666
+ additionalProperties: false,
129667
+ required: ["payload", "expiresAt"],
129668
+ properties: { payload: { type: "string" }, expiresAt: { type: "string" } }
129669
+ },
129670
+ status: {
129671
+ type: "object",
129672
+ additionalProperties: false,
129673
+ required: ["statusKey"],
129674
+ properties: { statusKey: { type: "string" }, params: { type: "object" } }
129675
+ },
129676
+ expired: { const: true },
129677
+ steps: SETUP_ITEM_SCHEMA.properties.steps,
129678
+ result: { ...SETUP_ITEM_SCHEMA, oneOf: TERMINAL_ITEM_BRANCHES }
129679
+ }
129680
+ };
129681
+
129682
+ // src/settingsUpdater.ts
129683
+ var makeSettingsUpdater = (settings) => {
129684
+ let tail = Promise.resolve();
129685
+ return (change) => {
129686
+ const run = tail.then(async () => {
129687
+ const { result, write } = change(await settings.read());
129688
+ if (write !== void 0) await settings.write(write.config, write.options);
129689
+ return result;
129690
+ });
129691
+ tail = run.catch(() => void 0);
129692
+ return run;
129693
+ };
129694
+ };
129572
129695
 
129573
129696
  // src/lark/setup/saveBot.ts
129574
129697
  var OPEN_ID_PREFIX = "ou_";
@@ -130986,16 +131109,7 @@ var createSetupRunner = (deps) => {
130986
131109
  };
130987
131110
 
130988
131111
  // src/lark/setup/setupCommands.ts
130989
- var SETUP_START_COMMAND = "larkSetup.start";
130990
131112
  var SETUP_CHECKLIST_COMMAND = "larkSetup.checklist";
130991
- var LARK_SETUP_CARD_ACTION = {
130992
- id: "larkSetup",
130993
- labelKey: "setup.action.label",
130994
- descriptionKey: "setup.action.description",
130995
- badgeKey: "setup.action.badge",
130996
- warningKey: "setup.action.warning",
130997
- command: SETUP_START_COMMAND
130998
- };
130999
131113
  var MANUAL_STEP_IDS = [
131000
131114
  "createApp",
131001
131115
  "enableBot",
@@ -131004,7 +131118,7 @@ var MANUAL_STEP_IDS = [
131004
131118
  "publish",
131005
131119
  "fillRow"
131006
131120
  ];
131007
- var SETUP_START_PAYLOAD_SCHEMA = {
131121
+ var SETUP_SESSION_PAYLOAD_SCHEMA = {
131008
131122
  type: "object",
131009
131123
  additionalProperties: false
131010
131124
  };
@@ -131059,10 +131173,15 @@ var makeRealClock = () => ({
131059
131173
  signal?.addEventListener("abort", onAbort, { once: true });
131060
131174
  })
131061
131175
  });
131062
- var saveAndConclude = async (ctx, result) => {
131176
+ var saveAndConclude = async (ctx, updateSettings, result) => {
131063
131177
  try {
131064
- const saved = upsertLarkBot(await ctx.settings.read(), result);
131065
- await ctx.settings.write(saved.config, { secrets: [saved.secretPath] });
131178
+ const saved = await updateSettings((config2) => {
131179
+ const upserted = upsertLarkBot(config2, result);
131180
+ return {
131181
+ result: upserted,
131182
+ write: { config: upserted.config, options: { secrets: [upserted.secretPath] } }
131183
+ };
131184
+ });
131066
131185
  ctx.log.info("lark setup saved the bot", {
131067
131186
  appId: result.appId,
131068
131187
  rowId: saved.rowId,
@@ -131096,6 +131215,7 @@ var saveAndConclude = async (ctx, result) => {
131096
131215
  }
131097
131216
  };
131098
131217
  var registerLarkSetup = (ctx, options = {}) => {
131218
+ const updateSettings = options.updateSettings ?? makeSettingsUpdater(ctx.settings);
131099
131219
  const runner = createSetupRunner({
131100
131220
  fetch: ctx.http,
131101
131221
  clock: options.clock ?? makeRealClock(),
@@ -131108,15 +131228,19 @@ var registerLarkSetup = (ctx, options = {}) => {
131108
131228
  ...options.sessionRetryWindowMs === void 0 ? {} : { sessionRetryWindowMs: options.sessionRetryWindowMs }
131109
131229
  });
131110
131230
  ctx.stream({
131111
- name: SETUP_START_COMMAND,
131231
+ name: LARK_SETUP_SESSION_COMMAND,
131112
131232
  description: "Set up a Lark app through the developer console after one QR scan.",
131113
131233
  scope: "operate",
131114
- payloadSchema: SETUP_START_PAYLOAD_SCHEMA,
131115
- itemSchema: SETUP_ITEM_SCHEMA,
131116
- // 条目是一份份快照与终态,慢订阅方丢掉的只该是旧的。
131234
+ payloadSchema: SETUP_SESSION_PAYLOAD_SCHEMA,
131235
+ itemSchema: SETUP_SNAPSHOT_SCHEMA,
131236
+ // 每一条都是完整的快照,慢订阅方丢掉的只该是旧的。
131117
131237
  buffer: { latest: 16 },
131118
131238
  handle: async (_payload, emit, signal) => {
131119
- const send = (item) => emit(item);
131239
+ let snapshot = {};
131240
+ const send = (item) => {
131241
+ snapshot = foldFlowItem(snapshot, item);
131242
+ emit(snapshot);
131243
+ };
131120
131244
  const result = await runner.run(
131121
131245
  {},
131122
131246
  // 先落到宿主约定的条目类型上:形状不对在编译期就报,而不是到客户端被整条丢掉。
@@ -131124,7 +131248,7 @@ var registerLarkSetup = (ctx, options = {}) => {
131124
131248
  signal
131125
131249
  );
131126
131250
  if (result === void 0) return;
131127
- send(await saveAndConclude(ctx, result));
131251
+ send(await saveAndConclude(ctx, updateSettings, result));
131128
131252
  }
131129
131253
  });
131130
131254
  ctx.command({
@@ -137182,6 +137306,13 @@ var credentials = (binding) => ({
137182
137306
  appId: binding.values["larkAppId"] ?? "",
137183
137307
  appSecret: binding.values["larkAppSecret"] ?? ""
137184
137308
  });
137309
+ var larkConfigIssues = (binding) => {
137310
+ const { appId, appSecret } = credentials(binding);
137311
+ const issues = [];
137312
+ if (appId === "") issues.push({ column: "larkAppId", code: "required" });
137313
+ if (appSecret === "") issues.push({ column: "larkAppSecret", code: "required" });
137314
+ return issues;
137315
+ };
137185
137316
  var makeLarkAdapter = (deps) => {
137186
137317
  const tenantToken = async (binding) => {
137187
137318
  const { appId, appSecret } = credentials(binding);
@@ -137194,12 +137325,16 @@ var makeLarkAdapter = (deps) => {
137194
137325
  };
137195
137326
  const aliasPorts = /* @__PURE__ */ new Map();
137196
137327
  const topicThreadIds = makeBoundedMap(TOPIC_THREAD_CACHE_LIMIT);
137328
+ const unreported = /* @__PURE__ */ new Map();
137197
137329
  const learning = /* @__PURE__ */ new Map();
137198
137330
  const learnTopic = (binding, target2, threadId) => {
137199
137331
  const key = chatKeyOf(binding);
137200
137332
  const report = (id) => {
137201
137333
  topicThreadIds.set(key, id);
137202
- target2.port?.noteChatAlias?.(topicAliasOf(target2.chatId, id), binding.chatId);
137334
+ const alias = topicAliasOf(target2.chatId, id);
137335
+ const port = target2.port ?? aliasPorts.get(binding.rowId);
137336
+ if (port === void 0) unreported.set(key, { alias, chatId: binding.chatId });
137337
+ else port.noteChatAlias?.(alias, binding.chatId);
137203
137338
  };
137204
137339
  const known = threadId ?? topicThreadIds.get(key);
137205
137340
  if (known !== void 0) {
@@ -137264,10 +137399,8 @@ var makeLarkAdapter = (deps) => {
137264
137399
  const { chatId: groupId, rootMessageId } = splitTopicChatId(chatId);
137265
137400
  return rootMessageId === void 0 ? void 0 : groupId;
137266
137401
  },
137267
- isConfigured: (binding) => {
137268
- const { appId, appSecret } = credentials(binding);
137269
- return appId !== "" && appSecret !== "";
137270
- },
137402
+ validate: larkConfigIssues,
137403
+ isConfigured: (binding) => larkConfigIssues(binding).length === 0,
137271
137404
  connect: (binding, handlers) => {
137272
137405
  const { appId, appSecret } = credentials(binding);
137273
137406
  const port = {
@@ -137275,6 +137408,11 @@ var makeLarkAdapter = (deps) => {
137275
137408
  knowsChatAlias: handlers.knowsChatAlias
137276
137409
  };
137277
137410
  aliasPorts.set(binding.rowId, port);
137411
+ for (const [key, pending] of [...unreported]) {
137412
+ if (!isChatKeyOfRow(key, binding.rowId)) continue;
137413
+ port.noteChatAlias?.(pending.alias, pending.chatId);
137414
+ unreported.delete(key);
137415
+ }
137278
137416
  let open = true;
137279
137417
  const inbound = startLarkInbound({
137280
137418
  appId,
@@ -137328,7 +137466,10 @@ var makeLarkAdapter = (deps) => {
137328
137466
  return {
137329
137467
  close: () => {
137330
137468
  open = false;
137331
- if (aliasPorts.get(binding.rowId) === port) aliasPorts.delete(binding.rowId);
137469
+ if (aliasPorts.get(binding.rowId) === port) {
137470
+ aliasPorts.delete(binding.rowId);
137471
+ topicThreadIds.forget((key) => isChatKeyOfRow(key, binding.rowId));
137472
+ }
137332
137473
  inbound.close();
137333
137474
  }
137334
137475
  };
@@ -137502,7 +137643,7 @@ var makeLarkAdapter = (deps) => {
137502
137643
  var en_default = {
137503
137644
  settings: {
137504
137645
  title: "Chat",
137505
- 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 above 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.",
137646
+ 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.",
137506
137647
  bots: "Bots",
137507
137648
  language: {
137508
137649
  label: "Reply language",
@@ -137609,8 +137750,8 @@ var en_default = {
137609
137750
  description: "When the bot answers in a group (unset means only when it is mentioned); direct chats always count. WeCom only delivers messages that mention the bot, so this changes nothing there."
137610
137751
  },
137611
137752
  deliveryBotId: {
137612
- label: "Bot ID",
137613
- description: "The settings-table row id for that bot (opaque).",
137753
+ label: "Bot",
137754
+ description: "The bot that sends the message. Callers pass the id in brackets as botId.",
137614
137755
  placeholder: "bot row id"
137615
137756
  },
137616
137757
  deliveryTargetId: {
@@ -137945,7 +138086,7 @@ var en_default = {
137945
138086
  setup: {
137946
138087
  action: {
137947
138088
  label: "Connect a Lark bot",
137948
- description: "Scan once with the Lark app. Ycodium creates a bot app under your account, publishes it for you only, and adds the bot to the list below with its credentials saved. Nothing to fill in.",
138089
+ description: "Scan once with the Lark app. Ycodium creates a bot app under your account, publishes it for you only, and adds the bot to the list with its credentials saved. Nothing to fill in.",
137949
138090
  badge: "Beta",
137950
138091
  warning: "This relies on Lark developer console interfaces that are not public, so it may stop working. If it does, set the app up by hand in the developer console."
137951
138092
  },
@@ -137972,7 +138113,7 @@ var en_default = {
137972
138113
  },
137973
138114
  fillRow: {
137974
138115
  title: "Add the bot",
137975
- body: "On the Chat card, add a bot: choose Lark, paste the App ID and App secret, put your own Lark user id in Allowed people (message the bot and it tells you your id), then save."
138116
+ body: "Under Chat in Settings, add a bot: choose Lark, paste the App ID and App secret, put your own Lark user id in Allowed people (message the bot and it tells you your id), then save."
137976
138117
  }
137977
138118
  },
137978
138119
  status: {
@@ -138106,6 +138247,66 @@ var en_default = {
138106
138247
  thread: "Go back to a normal session",
138107
138248
  panel: "Open the command panel",
138108
138249
  help: "Show all commands"
138250
+ },
138251
+ settingsSection: {
138252
+ empty: "No bots yet",
138253
+ unavailable: "This machine doesn't have the chat bridge right now.",
138254
+ loadFailed: "Couldn't load the chat-bridge settings.",
138255
+ retry: "Try again",
138256
+ general: "General",
138257
+ addBot: "Add a bot",
138258
+ save: "Save",
138259
+ revert: "Undo changes",
138260
+ saveFailed: "Couldn't save.",
138261
+ remove: "Delete",
138262
+ removeConfirm: "Delete this bot",
138263
+ cancel: "Cancel",
138264
+ removeHint: "Deleting this bot drops the bindings for every chat on it.",
138265
+ copy: "Copy",
138266
+ copied: "Copied.",
138267
+ copyFailed: "Couldn't copy that.",
138268
+ removeFailed: "Couldn't delete this bot.",
138269
+ status: {
138270
+ ready: "Ready",
138271
+ incomplete: "Incomplete"
138272
+ },
138273
+ secret: {
138274
+ saved: "Saved. Leave blank to keep it.",
138275
+ clear: "Clear saved value",
138276
+ clearUndo: "Undo clear"
138277
+ },
138278
+ issue: {
138279
+ required: "Fill this in.",
138280
+ format: "This format isn't right."
138281
+ },
138282
+ publicField: {
138283
+ callbackUrl: "Callback URL"
138284
+ },
138285
+ action: {
138286
+ running: "Working\u2026",
138287
+ done: "Done.",
138288
+ close: "Close",
138289
+ failedToRun: "Couldn't run this on the machine.",
138290
+ saveFirst: "Save or undo your changes first. These check the saved settings.",
138291
+ qrTitle: "QR code to scan with the Lark app"
138292
+ },
138293
+ step: {
138294
+ pending: "Waiting",
138295
+ running: "In progress",
138296
+ done: "Done",
138297
+ skipped: "Skipped",
138298
+ failed: "Failed"
138299
+ },
138300
+ select: {
138301
+ default: "Default"
138302
+ },
138303
+ target: {
138304
+ description: "Places a script or another tool can send a message to through a bot. The caller names the bot and the target id; the chat id on the platform stays here.",
138305
+ add: "Add a target",
138306
+ untitled: "New target",
138307
+ removeConfirm: "Delete this target",
138308
+ removeFailed: "Couldn't delete this target."
138309
+ }
138109
138310
  }
138110
138311
  };
138111
138312
 
@@ -138113,7 +138314,7 @@ var en_default = {
138113
138314
  var zh_CN_default = {
138114
138315
  settings: {
138115
138316
  title: "\u804A\u5929",
138116
- 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\u4E0A\u9762\u7684\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",
138317
+ 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",
138117
138318
  bots: "\u673A\u5668\u4EBA",
138118
138319
  language: {
138119
138320
  label: "\u56DE\u8BDD\u8BED\u8A00",
@@ -138220,8 +138421,8 @@ var zh_CN_default = {
138220
138421
  description: "\u673A\u5668\u4EBA\u5728\u7FA4\u91CC\u4EC0\u4E48\u65F6\u5019\u56DE\u5E94\uFF08\u4E0D\u9009\u5C31\u662F\u88AB @ \u624D\u7406\uFF09\uFF1B\u5355\u804A\u6C38\u8FDC\u7406\u3002\u4F01\u4E1A\u5FAE\u4FE1\u53EA\u628A @ \u5230\u673A\u5668\u4EBA\u7684\u7FA4\u6D88\u606F\u63A8\u8FC7\u6765\uFF0C\u8FD9\u4E00\u9879\u5BF9\u5B83\u6CA1\u6709\u6548\u679C\u3002"
138221
138422
  },
138222
138423
  deliveryBotId: {
138223
- label: "Bot ID",
138224
- description: "\u8BBE\u7F6E\u8868\u91CC\u8BE5\u673A\u5668\u4EBA\u90A3\u4E00\u884C\u7684 id\uFF08\u4E0D\u900F\u660E\u5B57\u7B26\u4E32\uFF09\u3002",
138424
+ label: "\u673A\u5668\u4EBA",
138425
+ description: "\u53D1\u6D88\u606F\u7684\u90A3\u4E2A\u673A\u5668\u4EBA\u3002\u8C03\u7528\u65B9\u628A\u62EC\u53F7\u91CC\u7684 id \u5F53\u4F5C botId \u4F20\u3002",
138225
138426
  placeholder: "\u673A\u5668\u4EBA\u884C id"
138226
138427
  },
138227
138428
  deliveryTargetId: {
@@ -138556,7 +138757,7 @@ var zh_CN_default = {
138556
138757
  setup: {
138557
138758
  action: {
138558
138759
  label: "\u8FDE\u63A5\u98DE\u4E66\u673A\u5668\u4EBA",
138559
- description: "\u7528\u98DE\u4E66 App \u626B\u4E00\u6B21\u7801\u3002Ycodium \u4F1A\u5728\u4F60\u7684\u8D26\u53F7\u4E0B\u5EFA\u597D\u673A\u5668\u4EBA\u5E94\u7528\uFF0C\u53D1\u5E03\u4E3A\u4EC5\u81EA\u5DF1\u53EF\u89C1\uFF0C\u5E76\u628A\u8FD9\u4E2A\u673A\u5668\u4EBA\u8FDE\u540C\u51ED\u636E\u76F4\u63A5\u5B58\u8FDB\u4E0B\u9762\u7684\u5217\u8868\uFF0C\u4E0D\u7528\u624B\u586B\u3002",
138760
+ description: "\u7528\u98DE\u4E66 App \u626B\u4E00\u6B21\u7801\u3002Ycodium \u4F1A\u5728\u4F60\u7684\u8D26\u53F7\u4E0B\u5EFA\u597D\u673A\u5668\u4EBA\u5E94\u7528\uFF0C\u53D1\u5E03\u4E3A\u4EC5\u81EA\u5DF1\u53EF\u89C1\uFF0C\u5E76\u628A\u8FD9\u4E2A\u673A\u5668\u4EBA\u8FDE\u540C\u51ED\u636E\u76F4\u63A5\u5B58\u8FDB\u673A\u5668\u4EBA\u5217\u8868\uFF0C\u4E0D\u7528\u624B\u586B\u3002",
138560
138761
  badge: "\u6D4B\u8BD5\u7248",
138561
138762
  warning: "\u8FD9\u4E2A\u529F\u80FD\u4F9D\u8D56\u98DE\u4E66\u5F00\u53D1\u8005\u540E\u53F0\u7684\u975E\u516C\u5F00\u63A5\u53E3\uFF0C\u53EF\u80FD\u4F1A\u5931\u6548\u3002\u5931\u6548\u65F6\u8BF7\u5230\u5F00\u53D1\u8005\u540E\u53F0\u624B\u52A8\u914D\u7F6E\u5E94\u7528\u3002"
138562
138763
  },
@@ -138583,7 +138784,7 @@ var zh_CN_default = {
138583
138784
  },
138584
138785
  fillRow: {
138585
138786
  title: "\u6DFB\u52A0\u673A\u5668\u4EBA",
138586
- body: "\u5728\u804A\u5929\u5361\u91CC\u6DFB\u52A0\u4E00\u4E2A\u673A\u5668\u4EBA\uFF1A\u5E73\u53F0\u9009\u98DE\u4E66\uFF0C\u586B\u5165 App ID \u4E0E App Secret\uFF0C\u628A\u4F60\u81EA\u5DF1\u7684\u98DE\u4E66\u7528\u6237 id \u586B\u8FDB\u300C\u5141\u8BB8\u7684\u4EBA\u300D\uFF08\u7ED9\u673A\u5668\u4EBA\u53D1\u4E00\u6761\u6D88\u606F\uFF0C\u5B83\u4F1A\u544A\u8BC9\u4F60\uFF09\uFF0C\u7136\u540E\u4FDD\u5B58\u3002"
138787
+ body: "\u5728\u8BBE\u7F6E\u7684\u300C\u804A\u5929\u300D\u5206\u533A\u91CC\u6DFB\u52A0\u4E00\u4E2A\u673A\u5668\u4EBA\uFF1A\u5E73\u53F0\u9009\u98DE\u4E66\uFF0C\u586B\u5165 App ID \u4E0E App Secret\uFF0C\u628A\u4F60\u81EA\u5DF1\u7684\u98DE\u4E66\u7528\u6237 id \u586B\u8FDB\u300C\u5141\u8BB8\u7684\u4EBA\u300D\uFF08\u7ED9\u673A\u5668\u4EBA\u53D1\u4E00\u6761\u6D88\u606F\uFF0C\u5B83\u4F1A\u544A\u8BC9\u4F60\uFF09\uFF0C\u7136\u540E\u4FDD\u5B58\u3002"
138587
138788
  }
138588
138789
  },
138589
138790
  status: {
@@ -138717,6 +138918,66 @@ var zh_CN_default = {
138717
138918
  thread: "\u5207\u56DE\u666E\u901A\u4F1A\u8BDD",
138718
138919
  panel: "\u6253\u5F00\u547D\u4EE4\u9762\u677F",
138719
138920
  help: "\u5217\u51FA\u5168\u90E8\u547D\u4EE4"
138921
+ },
138922
+ settingsSection: {
138923
+ empty: "\u8FD8\u6CA1\u6709\u673A\u5668\u4EBA",
138924
+ unavailable: "\u8FD9\u53F0\u673A\u5668\u73B0\u5728\u6CA1\u6709\u804A\u5929\u6865\u3002",
138925
+ loadFailed: "\u804A\u5929\u6865\u7684\u8BBE\u7F6E\u6CA1\u80FD\u8BFB\u51FA\u6765\u3002",
138926
+ retry: "\u518D\u8BD5\u4E00\u6B21",
138927
+ general: "\u901A\u7528\u8BBE\u7F6E",
138928
+ addBot: "\u6DFB\u52A0\u673A\u5668\u4EBA",
138929
+ save: "\u4FDD\u5B58",
138930
+ revert: "\u64A4\u9500\u6539\u52A8",
138931
+ saveFailed: "\u6CA1\u80FD\u5B58\u4E0A\u3002",
138932
+ remove: "\u5220\u9664",
138933
+ removeConfirm: "\u786E\u8BA4\u5220\u9664",
138934
+ cancel: "\u53D6\u6D88",
138935
+ removeHint: "\u5220\u6389\u8FD9\u4E2A\u673A\u5668\u4EBA\u540E\uFF0C\u5B83\u4E0B\u9762\u6240\u6709\u4F1A\u8BDD\u7684\u7ED1\u5B9A\u90FD\u4F1A\u4E00\u8D77\u4E22\u6389\u3002",
138936
+ copy: "\u590D\u5236",
138937
+ copied: "\u5DF2\u590D\u5236\u3002",
138938
+ copyFailed: "\u6CA1\u590D\u5236\u4E0A\u3002",
138939
+ removeFailed: "\u6CA1\u80FD\u5220\u6389\u8FD9\u4E2A\u673A\u5668\u4EBA\u3002",
138940
+ status: {
138941
+ ready: "\u53EF\u7528",
138942
+ incomplete: "\u8FD8\u6CA1\u586B\u5B8C"
138943
+ },
138944
+ secret: {
138945
+ saved: "\u5DF2\u7ECF\u5B58\u8FC7\uFF0C\u7559\u7A7A\u5C31\u4FDD\u6301\u539F\u503C\u3002",
138946
+ clear: "\u6E05\u9664\u5DF2\u5B58\u7684\u503C",
138947
+ clearUndo: "\u64A4\u9500\u6E05\u9664"
138948
+ },
138949
+ issue: {
138950
+ required: "\u8FD9\u4E00\u9879\u8981\u586B\u3002",
138951
+ format: "\u8FD9\u4E2A\u683C\u5F0F\u4E0D\u5BF9\u3002"
138952
+ },
138953
+ publicField: {
138954
+ callbackUrl: "\u56DE\u8C03\u5730\u5740"
138955
+ },
138956
+ action: {
138957
+ running: "\u6B63\u5728\u5904\u7406\u2026",
138958
+ done: "\u5B8C\u6210\u3002",
138959
+ close: "\u5173\u95ED",
138960
+ failedToRun: "\u6CA1\u80FD\u5728\u8FD9\u53F0\u673A\u5668\u4E0A\u8FD0\u884C\u3002",
138961
+ saveFirst: "\u5148\u4FDD\u5B58\u6216\u64A4\u9500\u6539\u52A8\u3002\u8FD9\u51E0\u9879\u68C0\u67E5\u7684\u662F\u5DF2\u4FDD\u5B58\u7684\u8BBE\u7F6E\u3002",
138962
+ qrTitle: "\u7528\u98DE\u4E66 App \u626B\u7684\u4E8C\u7EF4\u7801"
138963
+ },
138964
+ step: {
138965
+ pending: "\u7B49\u5F85\u4E2D",
138966
+ running: "\u8FDB\u884C\u4E2D",
138967
+ done: "\u5DF2\u5B8C\u6210",
138968
+ skipped: "\u5DF2\u8DF3\u8FC7",
138969
+ failed: "\u5931\u8D25"
138970
+ },
138971
+ select: {
138972
+ default: "\u9ED8\u8BA4"
138973
+ },
138974
+ target: {
138975
+ description: "\u811A\u672C\u6216\u522B\u7684\u5DE5\u5177\u53EF\u4EE5\u7ECF\u673A\u5668\u4EBA\u628A\u6D88\u606F\u53D1\u5230\u8FD9\u4E9B\u5730\u65B9\u3002\u8C03\u7528\u65B9\u53EA\u62A5\u673A\u5668\u4EBA\u548C\u76EE\u6807 id\uFF0C\u5E73\u53F0\u4E0A\u7684\u804A\u5929 id \u7559\u5728\u8FD9\u91CC\u3002",
138976
+ add: "\u6DFB\u52A0\u76EE\u6807",
138977
+ untitled: "\u65B0\u76EE\u6807",
138978
+ removeConfirm: "\u5220\u9664\u8FD9\u4E2A\u76EE\u6807",
138979
+ removeFailed: "\u8FD9\u4E2A\u76EE\u6807\u6CA1\u5220\u6389\u3002"
138980
+ }
138720
138981
  }
138721
138982
  };
138722
138983
 
@@ -139062,56 +139323,6 @@ var MINIMAL_HIDDEN = /* @__PURE__ */ new Set([
139062
139323
  ]);
139063
139324
  var panelCommandsFor = (mode = "default") => mode === "minimal" ? PANEL_COMMANDS.filter((spec) => !MINIMAL_HIDDEN.has(spec.id)) : PANEL_COMMANDS;
139064
139325
 
139065
- // src/sessionList.ts
139066
- var SESSION_LIST_MAX = 20;
139067
- var SESSION_ID_SHORT_LENGTH = 8;
139068
- var ageLabel = (updatedAt, now, t) => {
139069
- const at = Date.parse(updatedAt);
139070
- if (Number.isNaN(at)) return "";
139071
- const minutes = Math.floor(Math.max(0, now - at) / 6e4);
139072
- if (minutes < 1) return t("sessions.age.justNow");
139073
- if (minutes < 60) return t("sessions.age.minutes", { count: minutes });
139074
- const hours = Math.floor(minutes / 60);
139075
- if (hours < 24) return t("sessions.age.hours", { count: hours });
139076
- return t("sessions.age.days", { count: Math.floor(hours / 24) });
139077
- };
139078
- var shortId = (threadId) => threadId.slice(0, SESSION_ID_SHORT_LENGTH);
139079
- var sessionLine = (row, position, now, t) => {
139080
- const title = row.title.replace(/\s+/g, " ").trim() || t("sessions.untitled");
139081
- const age = ageLabel(row.updatedAt, now, t);
139082
- const badges = [
139083
- row.current ? t("sessions.badge.current") : null,
139084
- row.running ? t("sessions.badge.running") : null,
139085
- row.elsewhere ? t("sessions.badge.elsewhere") : null,
139086
- row.origin === "web" ? t("sessions.badge.web") : null
139087
- ].filter((badge) => badge !== null);
139088
- return [`${String(position)}. ${title}`, age, ...badges, shortId(row.threadId)].filter((part) => part !== "").join(" \xB7 ");
139089
- };
139090
- var formatSessions = (rows, now, t) => {
139091
- if (rows.length === 0) return t("sessions.empty");
139092
- const shown = rows.slice(0, SESSION_LIST_MAX);
139093
- return [
139094
- t("sessions.title", { count: rows.length }),
139095
- ...shown.map((row, index) => sessionLine(row, index + 1, now, t)),
139096
- ...rows.length > shown.length ? [t("sessions.more", { count: rows.length - shown.length })] : [],
139097
- "",
139098
- t("sessions.footer")
139099
- ].join("\n");
139100
- };
139101
- var listedThreadIds = (rows) => rows.slice(0, SESSION_LIST_MAX).map((row) => row.threadId);
139102
- var looksLikeListNumber = (wanted) => /^[1-9]\d*$/u.test(wanted) && wanted.length <= String(SESSION_LIST_MAX).length;
139103
- var pickSession = (arg, listed, rows) => {
139104
- const wanted = arg.trim();
139105
- if (wanted === "") return null;
139106
- if (looksLikeListNumber(wanted)) {
139107
- const ids = listed ?? listedThreadIds(rows);
139108
- return ids[Number(wanted) - 1] ?? null;
139109
- }
139110
- const needle = wanted.toLowerCase();
139111
- const matches = rows.filter((row) => row.threadId.toLowerCase().startsWith(needle));
139112
- return matches.length === 1 ? matches[0]?.threadId ?? null : null;
139113
- };
139114
-
139115
139326
  // src/panelController.ts
139116
139327
  var PANEL_PAGE_SIZE = 8;
139117
139328
  var PANEL_CARD_LIMIT = 200;
@@ -140183,8 +140394,14 @@ var makeSendFileTool = (deps) => ({
140183
140394
  });
140184
140395
 
140185
140396
  // src/sessions.ts
140186
- var SESSION_SCAN_LIMIT = 50;
140187
140397
  var WEB_LIST_PAGE = 100;
140398
+ var shellOf = (thread) => ({
140399
+ threadId: thread.threadId,
140400
+ title: thread.title,
140401
+ updatedAt: thread.updatedAt,
140402
+ archivedAt: thread.archivedAt,
140403
+ sessionStatus: thread.session?.status ?? null
140404
+ });
140188
140405
  var makeSessions = (core) => {
140189
140406
  const { host, t, commit, bindChat, deliver } = core;
140190
140407
  const listedByChat = /* @__PURE__ */ new Map();
@@ -140195,7 +140412,7 @@ var makeSessions = (core) => {
140195
140412
  threadId: thread.threadId,
140196
140413
  title: thread.title,
140197
140414
  updatedAt: thread.updatedAt,
140198
- running: thread.session?.activeTurnId != null,
140415
+ running: isSessionRunning(thread.sessionStatus),
140199
140416
  current: chatOf(core.state, chatKey).threadId === thread.threadId,
140200
140417
  elsewhere: Object.entries(core.state.chats).some(
140201
140418
  ([otherKey, record2]) => otherKey !== chatKey && record2.threadId === thread.threadId
@@ -140203,74 +140420,43 @@ var makeSessions = (core) => {
140203
140420
  origin
140204
140421
  };
140205
140422
  };
140206
- const webShellsForProject = async (projectId) => {
140207
- const listThreads = host.projections.listThreads;
140208
- if (listThreads === void 0 || projectId === "") return [];
140423
+ const projectShells = async (projectId) => {
140424
+ if (projectId === "") return [];
140209
140425
  const shells = [];
140210
- let cursor = void 0;
140211
- for (let page = 0; page < 5; page += 1) {
140212
- const result = await listThreads({
140426
+ let cursor = null;
140427
+ do {
140428
+ const result = await host.threads.list({
140429
+ projectId,
140213
140430
  limit: WEB_LIST_PAGE,
140214
- includeArchived: false,
140215
- ...cursor === void 0 || cursor === null ? {} : { cursor }
140431
+ archived: "exclude",
140432
+ cursor
140216
140433
  });
140217
- for (const shell of result.threads) {
140218
- if (shell.projectId !== projectId) continue;
140219
- if (shell.archivedAt != null) continue;
140434
+ for (const shell of result.items) {
140220
140435
  shells.push({
140221
140436
  threadId: shell.threadId,
140222
140437
  title: shell.title,
140223
140438
  updatedAt: shell.updatedAt,
140224
140439
  archivedAt: shell.archivedAt,
140225
- session: {
140226
- activeTurnId: shell.sessionStatus === "running" ? "running" : null
140227
- }
140440
+ sessionStatus: shell.sessionStatus
140228
140441
  });
140229
140442
  }
140230
- if (result.nextCursor === null) break;
140231
140443
  cursor = result.nextCursor;
140232
- }
140444
+ } while (cursor !== null);
140233
140445
  return shells;
140234
140446
  };
140447
+ const originOf = (threadId, projectId) => core.state.opened[threadId]?.projectId === projectId ? "plugin" : "web";
140235
140448
  const sessionRowsFor = async (binding) => {
140236
140449
  const chatKey = chatKeyOf(binding);
140237
- if (binding.projectId === "") return [];
140238
- const openedIds = new Set(
140239
- Object.entries(core.state.opened).filter(([, opened]) => opened.projectId === binding.projectId).map(([threadId]) => threadId)
140240
- );
140241
- const candidates = Object.entries(core.state.opened).filter(([, opened]) => opened.projectId === binding.projectId).toReversed().slice(0, SESSION_SCAN_LIMIT);
140242
- const ownedThreads = await Promise.all(
140243
- candidates.map(([threadId]) => host.projections.getThread(threadId))
140244
- );
140245
- const rows = [];
140246
- const seen = /* @__PURE__ */ new Set();
140247
- for (const thread of ownedThreads) {
140248
- if (thread === null) continue;
140249
- const row = rowFromThread(thread, chatKey, "plugin");
140250
- if (row === null) continue;
140251
- rows.push(row);
140252
- seen.add(row.threadId);
140253
- }
140254
- const shells = await webShellsForProject(binding.projectId);
140255
- const listed = new Set(shells.map((shell) => shell.threadId));
140256
- const attached = await Promise.all(
140257
- attachedOnChat(core.state, chatKey, binding.projectId).filter(({ threadId }) => !listed.has(threadId)).map(({ threadId }) => host.projections.getThread(threadId))
140258
- );
140259
- for (const thread of attached) {
140260
- if (thread === null || seen.has(thread.threadId) || openedIds.has(thread.threadId)) continue;
140261
- const row = rowFromThread(thread, chatKey, "web");
140262
- if (row === null) continue;
140263
- rows.push(row);
140264
- seen.add(row.threadId);
140265
- }
140266
- for (const shell of shells) {
140267
- if (seen.has(shell.threadId) || openedIds.has(shell.threadId)) continue;
140268
- const row = rowFromThread(shell, chatKey, "web");
140269
- if (row === null) continue;
140270
- rows.push(row);
140271
- seen.add(row.threadId);
140272
- }
140273
- return rows.toSorted((left, right) => right.updatedAt.localeCompare(left.updatedAt));
140450
+ const shells = await projectShells(binding.projectId);
140451
+ return shells.flatMap((shell) => {
140452
+ const row = rowFromThread(shell, chatKey, originOf(shell.threadId, binding.projectId));
140453
+ return row === null ? [] : [row];
140454
+ });
140455
+ };
140456
+ const rowForThread = async (binding, chatKey, threadId) => {
140457
+ const thread = await host.projections.getThread(threadId);
140458
+ if (thread === null || thread.projectId !== binding.projectId) return null;
140459
+ return rowFromThread(shellOf(thread), chatKey, originOf(threadId, binding.projectId));
140274
140460
  };
140275
140461
  const showSessions = async (binding) => {
140276
140462
  const rows = await sessionRowsFor(binding);
@@ -140316,24 +140502,27 @@ var makeSessions = (core) => {
140316
140502
  await deliver(binding, t("resume.nothingPending"));
140317
140503
  return;
140318
140504
  }
140319
- const rows = await sessionRowsFor(binding);
140320
- let targetId;
140321
- if (confirming) {
140322
- targetId = pending ?? null;
140323
- } else {
140324
- targetId = pickSession(wanted, listedByChat.get(chatKey), rows);
140505
+ const listed = listedByChat.get(chatKey);
140506
+ let targetId = confirming ? pending ?? null : listed === void 0 ? null : pickSession(wanted, listed, []);
140507
+ if (targetId === null) {
140508
+ targetId = pickSession(wanted, listed, await sessionRowsFor(binding));
140325
140509
  if (targetId === null) {
140326
140510
  await deliver(binding, t("resume.notFound", { query: arg }));
140327
140511
  return;
140328
140512
  }
140329
140513
  }
140330
- const target2 = rows.find((row) => row.threadId === targetId);
140331
- if (target2 === void 0) {
140514
+ const currentId = chatOf(core.state, chatKey).threadId;
140515
+ const [target2, other] = await Promise.all([
140516
+ rowForThread(binding, chatKey, targetId),
140517
+ currentId === null || currentId === targetId ? null : rowForThread(binding, chatKey, currentId)
140518
+ ]);
140519
+ const currentBusy = (currentId === targetId ? target2 : other)?.running === true;
140520
+ if (target2 === null) {
140332
140521
  if (confirming) pendingAttachByChat.delete(chatKey);
140333
140522
  await deliver(binding, t("resume.gone"));
140334
140523
  return;
140335
140524
  }
140336
- if (rows.some((row) => row.current && row.running)) {
140525
+ if (currentBusy) {
140337
140526
  await deliver(binding, t("resume.busy"));
140338
140527
  return;
140339
140528
  }
@@ -140875,6 +141064,7 @@ var tokenOf = (binding) => (binding.values["telegramToken"] ?? "").trim();
140875
141064
  var REACTION_WORKING = "\u{1F440}";
140876
141065
  var REACTION_DONE = "\u{1F44D}";
140877
141066
  var REACTION_FAILED = "\u{1F44E}";
141067
+ var telegramConfigIssues = (binding) => tokenOf(binding) === "" ? [{ column: "telegramToken", code: "required" }] : [];
140878
141068
  var defaultSleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
140879
141069
  var webhookHost = (url) => {
140880
141070
  try {
@@ -140976,7 +141166,8 @@ var makeTelegramAdapter = (deps) => {
140976
141166
  icon: "telegram",
140977
141167
  label: "Telegram",
140978
141168
  columns: COLUMNS2,
140979
- isConfigured: (binding) => tokenOf(binding) !== "",
141169
+ validate: telegramConfigIssues,
141170
+ isConfigured: (binding) => telegramConfigIssues(binding).length === 0,
140980
141171
  connect: async (binding, handlers) => {
140981
141172
  let stop = () => {
140982
141173
  };
@@ -141493,10 +141684,9 @@ var makeWebSync = (core) => {
141493
141684
  // src/settingsRowActions.ts
141494
141685
  var ROW_PAYLOAD_SCHEMA = {
141495
141686
  type: "object",
141496
- required: ["rowId", "values"],
141687
+ required: ["rowId"],
141497
141688
  properties: {
141498
- rowId: { type: "string" },
141499
- values: { type: "object" }
141689
+ rowId: { type: "string" }
141500
141690
  },
141501
141691
  additionalProperties: false
141502
141692
  };
@@ -141584,19 +141774,19 @@ var registerSettingsRowActions = (ctx, adapters) => {
141584
141774
  });
141585
141775
  };
141586
141776
  registerProbeAction({
141587
- name: ROW_ACTION_COMMANDS.checkConnection,
141777
+ name: BOT_ACTION_COMMANDS.checkConnection,
141588
141778
  summary: "Probe an IM bot's platform credentials without calling a model.",
141589
141779
  keyPrefix: "rowAction.checkConnection",
141590
141780
  copyable: false
141591
141781
  });
141592
141782
  registerProbeAction({
141593
- name: ROW_ACTION_COMMANDS.copyDiagnostics,
141783
+ name: BOT_ACTION_COMMANDS.copyDiagnostics,
141594
141784
  summary: "Probe an IM bot and return a copy-paste diagnostic report.",
141595
141785
  keyPrefix: "rowAction.copyDiagnostics",
141596
141786
  copyable: true
141597
141787
  });
141598
141788
  ctx.stream({
141599
- name: ROW_ACTION_COMMANDS.larkRepair,
141789
+ name: BOT_ACTION_COMMANDS.larkRepair,
141600
141790
  description: "Guide Lark scope repair and verify the saved app can still mint a token.",
141601
141791
  scope: "operate",
141602
141792
  payloadSchema: ROW_PAYLOAD_SCHEMA,
@@ -141662,6 +141852,289 @@ var registerSettingsRowActions = (ctx, adapters) => {
141662
141852
  });
141663
141853
  };
141664
141854
 
141855
+ // src/settingsView.ts
141856
+ var isRecord2 = (value2) => typeof value2 === "object" && value2 !== null && !Array.isArray(value2);
141857
+ var hasStoredSecret = (row, key) => {
141858
+ const value2 = row[key];
141859
+ return typeof value2 === "string" && value2 !== "";
141860
+ };
141861
+ var nextRowId = (table2) => {
141862
+ for (let index = 1; ; index += 1) {
141863
+ const candidate = `row${index}`;
141864
+ if (!Object.hasOwn(table2, candidate)) return candidate;
141865
+ }
141866
+ };
141867
+ var columnError = (code2, column) => ({ ok: false, code: code2, column });
141868
+ var targetTable = () => ({
141869
+ field: DELIVERY_TARGETS_FIELD,
141870
+ columns: deliveryTargetColumns()
141871
+ });
141872
+ var targetViews = (config2) => {
141873
+ const table2 = config2[DELIVERY_TARGETS_FIELD];
141874
+ if (!isRecord2(table2)) return [];
141875
+ const columns = deliveryTargetColumns();
141876
+ return Object.entries(table2).flatMap(([rowId, raw]) => {
141877
+ if (!isRecord2(raw)) return [];
141878
+ const issues = targetIssues(raw);
141879
+ const values = Object.fromEntries(
141880
+ columns.map((column) => [column.key, readString(raw, column.key)])
141881
+ );
141882
+ return [{ rowId, configured: issues.length === 0, values, issues }];
141883
+ });
141884
+ };
141885
+ var buildSettingsView = (config2, adapters) => {
141886
+ const columns = buildBotColumns(adapters);
141887
+ const table2 = config2[BOTS_FIELD];
141888
+ const bots = [];
141889
+ if (isRecord2(table2)) {
141890
+ for (const [rowId, raw] of Object.entries(table2)) {
141891
+ if (!isRecord2(raw)) continue;
141892
+ const platform = platformOf(raw);
141893
+ const values = {};
141894
+ const secrets = {};
141895
+ for (const column of columns) {
141896
+ if (column.kind === "password") secrets[column.key] = hasStoredSecret(raw, column.key);
141897
+ else values[column.key] = readString(raw, column.key);
141898
+ }
141899
+ const adapter = adapters.find((candidate) => candidate.platform === platform);
141900
+ if (adapter === void 0) {
141901
+ bots.push({
141902
+ rowId,
141903
+ platform,
141904
+ configured: false,
141905
+ values,
141906
+ secrets,
141907
+ issues: [],
141908
+ publicFields: []
141909
+ });
141910
+ continue;
141911
+ }
141912
+ const bot = readBotView(rowId, raw, adapter);
141913
+ bots.push({
141914
+ rowId,
141915
+ platform,
141916
+ configured: adapter.isConfigured(bot),
141917
+ values,
141918
+ secrets,
141919
+ issues: adapter.validate?.(bot) ?? [],
141920
+ publicFields: adapter.publicFields?.(bot) ?? []
141921
+ });
141922
+ }
141923
+ }
141924
+ return {
141925
+ replyLanguage: parseReplyLanguage(config2[REPLY_LANGUAGE_FIELD]),
141926
+ bots,
141927
+ targets: targetViews(config2),
141928
+ delivery: {
141929
+ enabled: readDeliveryHttpEnabled(config2),
141930
+ port: readDeliveryHttpPort(config2),
141931
+ hasToken: readDeliveryHttpToken(config2) !== ""
141932
+ }
141933
+ };
141934
+ };
141935
+ var applyRowEdit = (config2, { field, columns }, edit) => {
141936
+ const plain = new Set(
141937
+ columns.filter((column) => column.kind !== "password").map((column) => column.key)
141938
+ );
141939
+ const secretColumns = new Set(
141940
+ columns.filter((column) => column.kind === "password").map((column) => column.key)
141941
+ );
141942
+ for (const key of Object.keys(edit.values)) {
141943
+ if (!plain.has(key)) return columnError("unknown-column", key);
141944
+ }
141945
+ const secrets = edit.secrets ?? {};
141946
+ for (const [key, value2] of Object.entries(secrets)) {
141947
+ if (!secretColumns.has(key)) return columnError("not-a-secret", key);
141948
+ if (value2 === "") return columnError("empty-secret", key);
141949
+ }
141950
+ const clearSecrets = edit.clearSecrets ?? [];
141951
+ for (const key of clearSecrets) {
141952
+ if (!secretColumns.has(key)) return columnError("not-a-secret", key);
141953
+ }
141954
+ const table2 = isRecord2(config2[field]) ? config2[field] : {};
141955
+ const requested = edit.rowId;
141956
+ const current = requested === void 0 ? {} : Object.hasOwn(table2, requested) ? table2[requested] : void 0;
141957
+ if (!isRecord2(current)) return { ok: false, code: "unknown-row" };
141958
+ const rowId = requested ?? nextRowId(table2);
141959
+ const nextRow = { ...current, ...edit.values };
141960
+ const clearing = clearSecrets.filter((key) => !Object.hasOwn(secrets, key));
141961
+ for (const key of clearing) delete nextRow[key];
141962
+ for (const [key, value2] of Object.entries(secrets)) nextRow[key] = value2;
141963
+ return {
141964
+ ok: true,
141965
+ rowId,
141966
+ config: { ...config2, [field]: { ...table2, [rowId]: nextRow } },
141967
+ secrets: Object.keys(secrets).map((key) => [field, rowId, key]),
141968
+ clearSecrets: clearing.map((key) => [field, rowId, key])
141969
+ };
141970
+ };
141971
+ var applyBotEdit = (config2, adapters, edit) => applyRowEdit(config2, { field: BOTS_FIELD, columns: buildBotColumns(adapters) }, edit);
141972
+ var applyTargetEdit = (config2, edit) => applyRowEdit(config2, targetTable(), edit);
141973
+ var removeRow = (config2, field, rowId) => {
141974
+ const table2 = config2[field];
141975
+ if (!isRecord2(table2) || !Object.hasOwn(table2, rowId)) return null;
141976
+ const nextTable = { ...table2 };
141977
+ delete nextTable[rowId];
141978
+ return { ...config2, [field]: nextTable };
141979
+ };
141980
+ var removeBotRow = (config2, rowId) => removeRow(config2, BOTS_FIELD, rowId);
141981
+ var removeTargetRow = (config2, rowId) => removeRow(config2, DELIVERY_TARGETS_FIELD, rowId);
141982
+
141983
+ // src/settingsCommands.ts
141984
+ var CLEAR_DELIVERY_HTTP_TOKEN = "clearDeliveryHttpToken";
141985
+ var EMPTY_PAYLOAD = {
141986
+ type: "object",
141987
+ additionalProperties: false
141988
+ };
141989
+ var SAVE_BOT_PAYLOAD = {
141990
+ type: "object",
141991
+ additionalProperties: false,
141992
+ required: ["values"],
141993
+ properties: {
141994
+ rowId: { type: "string" },
141995
+ values: { type: "object", additionalProperties: { type: "string" } },
141996
+ secrets: { type: "object", additionalProperties: { type: "string" } },
141997
+ clearSecrets: { type: "array", items: { type: "string" } }
141998
+ }
141999
+ };
142000
+ var SAVE_TARGET_PAYLOAD = {
142001
+ type: "object",
142002
+ additionalProperties: false,
142003
+ required: ["values"],
142004
+ properties: {
142005
+ rowId: { type: "string" },
142006
+ values: { type: "object", additionalProperties: { type: "string" } }
142007
+ }
142008
+ };
142009
+ var REMOVE_BOT_PAYLOAD = {
142010
+ type: "object",
142011
+ additionalProperties: false,
142012
+ required: ["rowId"],
142013
+ properties: {
142014
+ rowId: { type: "string" }
142015
+ }
142016
+ };
142017
+ var SAVE_GENERAL_PAYLOAD = {
142018
+ type: "object",
142019
+ additionalProperties: false,
142020
+ properties: {
142021
+ [REPLY_LANGUAGE_FIELD]: {
142022
+ type: "string",
142023
+ enum: REPLY_LANGUAGE_OPTIONS.map((option) => option.value)
142024
+ },
142025
+ [DELIVERY_HTTP_ENABLED_FIELD]: { type: "boolean" },
142026
+ [DELIVERY_HTTP_PORT_FIELD]: { type: "integer", minimum: 1, maximum: 65535 },
142027
+ [DELIVERY_HTTP_TOKEN_FIELD]: { type: "string", minLength: 1 },
142028
+ [CLEAR_DELIVERY_HTTP_TOKEN]: { type: "boolean" }
142029
+ }
142030
+ };
142031
+ var proxyChoice = (proxy) => proxy.displayName === void 0 ? { id: proxy.id, isDefault: proxy.isDefault } : { id: proxy.id, displayName: proxy.displayName, isDefault: proxy.isDefault };
142032
+ var botEditOf = (payload) => ({
142033
+ values: payload["values"],
142034
+ ..."rowId" in payload ? { rowId: payload["rowId"] } : {},
142035
+ ..."secrets" in payload ? { secrets: payload["secrets"] } : {},
142036
+ ..."clearSecrets" in payload ? { clearSecrets: payload["clearSecrets"] } : {}
142037
+ });
142038
+ var generalWriteOptions = (payload, next) => {
142039
+ if (DELIVERY_HTTP_TOKEN_FIELD in payload) {
142040
+ next[DELIVERY_HTTP_TOKEN_FIELD] = payload[DELIVERY_HTTP_TOKEN_FIELD];
142041
+ return { secrets: [[DELIVERY_HTTP_TOKEN_FIELD]] };
142042
+ }
142043
+ if (payload[CLEAR_DELIVERY_HTTP_TOKEN] === true) {
142044
+ delete next[DELIVERY_HTTP_TOKEN_FIELD];
142045
+ return { clearSecrets: [[DELIVERY_HTTP_TOKEN_FIELD]] };
142046
+ }
142047
+ return void 0;
142048
+ };
142049
+ var viewAfterWrite = async (ctx, adapters) => buildSettingsView(await ctx.settings.read(), adapters);
142050
+ var registerSettingsCommands = (ctx, adapters, updateSettings) => {
142051
+ ctx.command({
142052
+ name: SETTINGS_VIEW_COMMAND,
142053
+ description: "Read the chat-bridge settings view, bot columns, and this machine's proxies.",
142054
+ scope: "read",
142055
+ payloadSchema: EMPTY_PAYLOAD,
142056
+ handle: async () => {
142057
+ const [config2, listed] = await Promise.all([ctx.settings.read(), ctx.proxies.list()]);
142058
+ return {
142059
+ view: buildSettingsView(config2, adapters),
142060
+ columns: buildBotColumns(adapters),
142061
+ targetColumns: deliveryTargetColumns(),
142062
+ proxies: listed.map(proxyChoice)
142063
+ };
142064
+ }
142065
+ });
142066
+ const saveRow = async (edit) => {
142067
+ const edited = await updateSettings((config2) => {
142068
+ const result = edit(config2);
142069
+ if (!result.ok) return { result };
142070
+ const { config: next, secrets, clearSecrets } = result;
142071
+ return { result, write: { config: next, options: { secrets, clearSecrets } } };
142072
+ });
142073
+ if (!edited.ok) return edited;
142074
+ return { ok: true, rowId: edited.rowId, view: await viewAfterWrite(ctx, adapters) };
142075
+ };
142076
+ const removeRow2 = async (remove) => {
142077
+ const removed = await updateSettings((config2) => {
142078
+ const next = remove(config2);
142079
+ return next === null ? { result: false } : { result: true, write: { config: next } };
142080
+ });
142081
+ if (!removed) return { ok: false, code: "unknown-row" };
142082
+ return { ok: true, view: await viewAfterWrite(ctx, adapters) };
142083
+ };
142084
+ ctx.command({
142085
+ name: SETTINGS_SAVE_BOT_COMMAND,
142086
+ description: "Create or update one bot row.",
142087
+ scope: "operate",
142088
+ payloadSchema: SAVE_BOT_PAYLOAD,
142089
+ handle: (payload) => saveRow((config2) => applyBotEdit(config2, adapters, botEditOf(payload)))
142090
+ });
142091
+ ctx.command({
142092
+ name: SETTINGS_REMOVE_BOT_COMMAND,
142093
+ description: "Remove one bot row.",
142094
+ scope: "operate",
142095
+ payloadSchema: REMOVE_BOT_PAYLOAD,
142096
+ handle: (payload) => removeRow2((config2) => removeBotRow(config2, payload["rowId"]))
142097
+ });
142098
+ ctx.command({
142099
+ name: SETTINGS_SAVE_TARGET_COMMAND,
142100
+ description: "Create or update one proactive delivery target.",
142101
+ scope: "operate",
142102
+ payloadSchema: SAVE_TARGET_PAYLOAD,
142103
+ handle: (payload) => saveRow((config2) => applyTargetEdit(config2, botEditOf(payload)))
142104
+ });
142105
+ ctx.command({
142106
+ name: SETTINGS_REMOVE_TARGET_COMMAND,
142107
+ description: "Remove one proactive delivery target.",
142108
+ scope: "operate",
142109
+ payloadSchema: REMOVE_BOT_PAYLOAD,
142110
+ handle: (payload) => removeRow2((config2) => removeTargetRow(config2, payload["rowId"]))
142111
+ });
142112
+ ctx.command({
142113
+ name: SETTINGS_SAVE_GENERAL_COMMAND,
142114
+ description: "Update reply language and proactive delivery HTTP settings.",
142115
+ scope: "operate",
142116
+ payloadSchema: SAVE_GENERAL_PAYLOAD,
142117
+ handle: async (payload) => {
142118
+ await updateSettings((config2) => {
142119
+ const next = { ...config2 };
142120
+ for (const field of [
142121
+ REPLY_LANGUAGE_FIELD,
142122
+ DELIVERY_HTTP_ENABLED_FIELD,
142123
+ DELIVERY_HTTP_PORT_FIELD
142124
+ ]) {
142125
+ if (field in payload) next[field] = payload[field];
142126
+ }
142127
+ const options = generalWriteOptions(payload, next);
142128
+ return {
142129
+ result: void 0,
142130
+ write: options === void 0 ? { config: next } : { config: next, options }
142131
+ };
142132
+ });
142133
+ return { ok: true, view: await viewAfterWrite(ctx, adapters) };
142134
+ }
142135
+ });
142136
+ };
142137
+
141665
142138
  // src/wechatApi.ts
141666
142139
  var WECHAT_TEXT_MAX_CHARS = 1800;
141667
142140
 
@@ -141675,6 +142148,7 @@ var COLUMNS3 = [
141675
142148
  }
141676
142149
  ];
141677
142150
  var tokenOf2 = (bot) => bot.values["wechatBotToken"] ?? "";
142151
+ var wechatConfigIssues = (bot) => tokenOf2(bot) === "" ? [{ column: "wechatBotToken", code: "required" }] : [];
141678
142152
  var makeWechatAdapter = (deps) => {
141679
142153
  return {
141680
142154
  platform: "wechat",
@@ -141682,7 +142156,8 @@ var makeWechatAdapter = (deps) => {
141682
142156
  // 平台名与设置表的平台选项同一个键,免得默认机器人名与选项写成两种叫法。
141683
142157
  label: deps.translate("platform.wechat"),
141684
142158
  columns: COLUMNS3,
141685
- isConfigured: (bot) => tokenOf2(bot) !== "",
142159
+ validate: wechatConfigIssues,
142160
+ isConfigured: (bot) => wechatConfigIssues(bot).length === 0,
141686
142161
  connect: (bot, _handlers) => {
141687
142162
  deps.log.warn("wechat iLink connect is stubbed pending security-review gate (YCO-236)", {
141688
142163
  bot: bot.displayName,
@@ -142216,6 +142691,13 @@ var credentials2 = (binding) => ({
142216
142691
  botSecret: binding.values["wecomBotSecret"] ?? "",
142217
142692
  endpoint: normalizeWecomEndpoint(binding.values["wecomEndpoint"] || DEFAULT_WECOM_ENDPOINT)
142218
142693
  });
142694
+ var wecomConfigIssues = (binding) => {
142695
+ const { botId, botSecret } = credentials2(binding);
142696
+ const issues = [];
142697
+ if (botId === "") issues.push({ column: "wecomBotId", code: "required" });
142698
+ if (botSecret === "") issues.push({ column: "wecomBotSecret", code: "required" });
142699
+ return issues;
142700
+ };
142219
142701
  var makeWecomAdapter = (deps) => {
142220
142702
  const clients = /* @__PURE__ */ new Map();
142221
142703
  const t = deps.translate;
@@ -142260,10 +142742,8 @@ ${kept}`;
142260
142742
  label: "WeCom",
142261
142743
  columns: COLUMNS4,
142262
142744
  unlistedValues: ["wecomEndpoint"],
142263
- isConfigured: (binding) => {
142264
- const { botId, botSecret } = credentials2(binding);
142265
- return botId !== "" && botSecret !== "";
142266
- },
142745
+ validate: wecomConfigIssues,
142746
+ isConfigured: (binding) => wecomConfigIssues(binding).length === 0,
142267
142747
  connect: (binding, handlers) => {
142268
142748
  const { botId, botSecret, endpoint } = credentials2(binding);
142269
142749
  let stopped = false;
@@ -142755,7 +143235,7 @@ var createWecomAppCrypto = (input) => {
142755
143235
  var deriveCallbackPathSecret = (token, encodingAESKey) => NodeCrypto4.createHash("sha256").update(`${token.trim()}:${encodingAESKey.trim()}`).digest("hex").slice(0, 24);
142756
143236
 
142757
143237
  // src/wecomAppParse.ts
142758
- var isRecord2 = (value2) => value2 !== null && typeof value2 === "object" && !Array.isArray(value2);
143238
+ var isRecord3 = (value2) => value2 !== null && typeof value2 === "object" && !Array.isArray(value2);
142759
143239
  var isXmlFormat = (raw) => {
142760
143240
  const trimmed = raw.trim();
142761
143241
  return trimmed.startsWith("<") && trimmed.endsWith(">");
@@ -142801,7 +143281,7 @@ var parseWecomAppPlainMessage = (raw) => {
142801
143281
  } catch {
142802
143282
  return null;
142803
143283
  }
142804
- if (!isRecord2(data)) return null;
143284
+ if (!isRecord3(data)) return null;
142805
143285
  const fromRaw = data["from"];
142806
143286
  const textRaw = data["text"];
142807
143287
  const imageRaw = data["image"];
@@ -142810,10 +143290,10 @@ var parseWecomAppPlainMessage = (raw) => {
142810
143290
  msgtype: String(data["msgtype"] ?? data["MsgType"] ?? "").toLowerCase(),
142811
143291
  createTime: data["createTime"] !== void 0 ? String(data["createTime"]) : data["CreateTime"] !== void 0 ? String(data["CreateTime"]) : void 0,
142812
143292
  agentId: data["agentid"] !== void 0 ? String(data["agentid"]) : data["AgentID"] !== void 0 ? String(data["AgentID"]) : void 0,
142813
- from: isRecord2(fromRaw) ? { userid: typeof fromRaw["userid"] === "string" ? fromRaw["userid"] : void 0 } : typeof data["FromUserName"] === "string" ? { userid: data["FromUserName"] } : void 0,
143293
+ from: isRecord3(fromRaw) ? { userid: typeof fromRaw["userid"] === "string" ? fromRaw["userid"] : void 0 } : typeof data["FromUserName"] === "string" ? { userid: data["FromUserName"] } : void 0,
142814
143294
  to: typeof data["to"] === "string" ? data["to"] : void 0,
142815
- text: isRecord2(textRaw) ? { content: typeof textRaw["content"] === "string" ? textRaw["content"] : void 0 } : void 0,
142816
- image: isRecord2(imageRaw) ? { url: typeof imageRaw["url"] === "string" ? imageRaw["url"] : void 0 } : void 0,
143295
+ text: isRecord3(textRaw) ? { content: typeof textRaw["content"] === "string" ? textRaw["content"] : void 0 } : void 0,
143296
+ image: isRecord3(imageRaw) ? { url: typeof imageRaw["url"] === "string" ? imageRaw["url"] : void 0 } : void 0,
142817
143297
  mediaId: typeof data["mediaid"] === "string" ? data["mediaid"] : void 0,
142818
143298
  recognition: typeof data["recognition"] === "string" ? data["recognition"] : void 0,
142819
143299
  event: String(data["event"] ?? data["eventtype"] ?? "").toLowerCase() || void 0,
@@ -143183,6 +143663,21 @@ var WECOM_APP_IMAGE_BYTES = 10 * 1024 * 1024;
143183
143663
  var WECOM_APP_FILE_BYTES = 20 * 1024 * 1024;
143184
143664
  var WECOM_APP_MIN_BYTES = 5;
143185
143665
  var WECOM_APP_IMAGE_TYPES = /* @__PURE__ */ new Set(["image/jpeg", "image/png"]);
143666
+ var joinCallbackUrl = (baseOrOrigin, path) => `${baseOrOrigin}${path}`;
143667
+ var wecomAppPublicFields = (bot) => {
143668
+ const c = wecomAppCredentials(bot);
143669
+ if (c.callbackBaseUrl === "") return [];
143670
+ return [
143671
+ {
143672
+ key: "callbackUrl",
143673
+ value: joinCallbackUrl(
143674
+ c.callbackBaseUrl,
143675
+ callbackRoutePath(bot.rowId, c.token, c.encodingAESKey)
143676
+ ),
143677
+ copyable: true
143678
+ }
143679
+ ];
143680
+ };
143186
143681
  var OUTBOUND_COLUMNS = /* @__PURE__ */ new Set([
143187
143682
  "wecomAppCorpId",
143188
143683
  "wecomAppAgentId",
@@ -143252,11 +143747,14 @@ var makeWecomAppAdapter = (deps) => {
143252
143747
  icon: "wecom",
143253
143748
  label: deps.translate("platform.wecom-app"),
143254
143749
  columns: COLUMNS5,
143255
- // 必填的都填了、EncodingAESKey 格式对(解不了回调的行不算配好)。别的列格式不对的行照样交给
143256
- // connect,由它报出哪几列不对(一条日志,不重试),也不挡设置页的「检查连接」去探出站凭据。
143750
+ // 设置页标格子用完整的逐列结果。算不算配齐仍是原来那份,不能收成「一个问题都没有」:
143751
+ // 必填都填了、EncodingAESKey 格式对。别的列格式不对的行照样交给 connect 报出来(一条日志,
143752
+ // 不重试),也不挡设置页的「检查连接」去探出站凭据。
143753
+ validate: validateWecomAppBot,
143257
143754
  isConfigured: (bot) => validateWecomAppBot(bot).every(
143258
143755
  (issue) => issue.code !== "required" && issue.column !== "wecomAppEncodingAESKey"
143259
143756
  ),
143757
+ publicFields: wecomAppPublicFields,
143260
143758
  /**
143261
143759
  * 自建应用是回调式渠道:入站由企微推到我们的 listener,没有可探的长连接;能探的是出站
143262
143760
  * 凭据——和真实发送同一条路,向 gettoken 换一次 access_token。
@@ -143320,7 +143818,10 @@ var makeWecomAppAdapter = (deps) => {
143320
143818
  deps.log.info("wecom-app connected", {
143321
143819
  bot: bot.displayName,
143322
143820
  callbackPath: path,
143323
- callbackUrl: c.callbackBaseUrl !== "" ? `${c.callbackBaseUrl}${path}` : `${server.origin}${path}`,
143821
+ callbackUrl: joinCallbackUrl(
143822
+ c.callbackBaseUrl !== "" ? c.callbackBaseUrl : server.origin,
143823
+ path
143824
+ ),
143324
143825
  listen: `${server.host}:${String(server.port)}`
143325
143826
  });
143326
143827
  return {
@@ -143364,7 +143865,7 @@ var makeWecomAppAdapter = (deps) => {
143364
143865
  // src/index.ts
143365
143866
  var imPlugin = definePlugin({
143366
143867
  id: "im",
143367
- apiVersion: 3,
143868
+ apiVersion: 5,
143368
143869
  activate: async (ctx) => {
143369
143870
  const { t, setLanguage } = makeLiveTranslate(
143370
143871
  parseReplyLanguage(ctx.config[REPLY_LANGUAGE_FIELD])
@@ -143421,13 +143922,17 @@ var imPlugin = definePlugin({
143421
143922
  }
143422
143923
  })
143423
143924
  ];
143424
- ctx.settingsCard({ ...buildSettingsCard(adapters), actions: [LARK_SETUP_CARD_ACTION] });
143425
- registerLarkSetup(ctx);
143925
+ ctx.settingsCard(buildSettingsCard(adapters));
143926
+ const updateSettings = makeSettingsUpdater(ctx.settings);
143927
+ registerLarkSetup(ctx, { updateSettings });
143426
143928
  registerSettingsRowActions(ctx, adapters);
143929
+ registerSettingsCommands(ctx, adapters, updateSettings);
143427
143930
  const core = makeImCore({
143428
143931
  host: {
143429
143932
  log: ctx.log,
143430
143933
  projections: ctx.projections,
143934
+ threads: ctx.threads,
143935
+ approvals: ctx.approvals,
143431
143936
  commands: ctx.commands,
143432
143937
  plugins: ctx.plugins,
143433
143938
  attachments: ctx.attachments,