@ycodium-ai/plugin-im 0.2.2976 → 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) => {
@@ -127229,6 +127257,57 @@ var pickProject = (arg, listed, rows) => {
127229
127257
  return prefixed.length === 1 ? prefixed[0]?.projectId ?? null : null;
127230
127258
  };
127231
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
+
127232
127311
  // src/chatSetup.ts
127233
127312
  var makeChatSetup = (core, deps) => {
127234
127313
  const { host, t, adapterFor, commit, deliver, bindingOf: bindingOf2, hasPanel } = core;
@@ -127287,7 +127366,7 @@ var makeChatSetup = (core, deps) => {
127287
127366
  const threadId = chatOf(core.state, chatKey).threadId;
127288
127367
  if (threadId !== null) {
127289
127368
  const thread = await host.projections.getThread(threadId).catch(() => null);
127290
- if (thread?.session?.activeTurnId != null) {
127369
+ if (isSessionRunning(thread?.session?.status)) {
127291
127370
  await deliver(binding, t("repo.busy"));
127292
127371
  return;
127293
127372
  }
@@ -127642,7 +127721,7 @@ var makeCommands = (core, deps) => {
127642
127721
  return true;
127643
127722
  }
127644
127723
  const thread = await host.projections.getThread(threadId);
127645
- const running = thread?.session?.activeTurnId != null;
127724
+ const running = isSessionRunning(thread?.session?.status);
127646
127725
  await deliver(
127647
127726
  binding,
127648
127727
  [
@@ -128445,6 +128524,10 @@ var makeBoundedMap = (limit) => {
128445
128524
  const oldest = entries.keys().next().value;
128446
128525
  if (oldest !== void 0) entries.delete(oldest);
128447
128526
  }
128527
+ },
128528
+ /** 忘掉键满足条件的那些。 */
128529
+ forget: (matches) => {
128530
+ for (const key of [...entries.keys()]) if (matches(key)) entries.delete(key);
128448
128531
  }
128449
128532
  };
128450
128533
  };
@@ -129571,6 +129654,44 @@ var SETUP_ITEM_SCHEMA = makeFlowItemSchema({
129571
129654
  qrRequired: ["payload", "expiresAt"],
129572
129655
  doneRequired: ["messageKey"]
129573
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
+ };
129574
129695
 
129575
129696
  // src/lark/setup/saveBot.ts
129576
129697
  var OPEN_ID_PREFIX = "ou_";
@@ -130988,16 +131109,7 @@ var createSetupRunner = (deps) => {
130988
131109
  };
130989
131110
 
130990
131111
  // src/lark/setup/setupCommands.ts
130991
- var SETUP_START_COMMAND = "larkSetup.start";
130992
131112
  var SETUP_CHECKLIST_COMMAND = "larkSetup.checklist";
130993
- var LARK_SETUP_CARD_ACTION = {
130994
- id: "larkSetup",
130995
- labelKey: "setup.action.label",
130996
- descriptionKey: "setup.action.description",
130997
- badgeKey: "setup.action.badge",
130998
- warningKey: "setup.action.warning",
130999
- command: SETUP_START_COMMAND
131000
- };
131001
131113
  var MANUAL_STEP_IDS = [
131002
131114
  "createApp",
131003
131115
  "enableBot",
@@ -131006,7 +131118,7 @@ var MANUAL_STEP_IDS = [
131006
131118
  "publish",
131007
131119
  "fillRow"
131008
131120
  ];
131009
- var SETUP_START_PAYLOAD_SCHEMA = {
131121
+ var SETUP_SESSION_PAYLOAD_SCHEMA = {
131010
131122
  type: "object",
131011
131123
  additionalProperties: false
131012
131124
  };
@@ -131061,10 +131173,15 @@ var makeRealClock = () => ({
131061
131173
  signal?.addEventListener("abort", onAbort, { once: true });
131062
131174
  })
131063
131175
  });
131064
- var saveAndConclude = async (ctx, result) => {
131176
+ var saveAndConclude = async (ctx, updateSettings, result) => {
131065
131177
  try {
131066
- const saved = upsertLarkBot(await ctx.settings.read(), result);
131067
- 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
+ });
131068
131185
  ctx.log.info("lark setup saved the bot", {
131069
131186
  appId: result.appId,
131070
131187
  rowId: saved.rowId,
@@ -131098,6 +131215,7 @@ var saveAndConclude = async (ctx, result) => {
131098
131215
  }
131099
131216
  };
131100
131217
  var registerLarkSetup = (ctx, options = {}) => {
131218
+ const updateSettings = options.updateSettings ?? makeSettingsUpdater(ctx.settings);
131101
131219
  const runner = createSetupRunner({
131102
131220
  fetch: ctx.http,
131103
131221
  clock: options.clock ?? makeRealClock(),
@@ -131110,15 +131228,19 @@ var registerLarkSetup = (ctx, options = {}) => {
131110
131228
  ...options.sessionRetryWindowMs === void 0 ? {} : { sessionRetryWindowMs: options.sessionRetryWindowMs }
131111
131229
  });
131112
131230
  ctx.stream({
131113
- name: SETUP_START_COMMAND,
131231
+ name: LARK_SETUP_SESSION_COMMAND,
131114
131232
  description: "Set up a Lark app through the developer console after one QR scan.",
131115
131233
  scope: "operate",
131116
- payloadSchema: SETUP_START_PAYLOAD_SCHEMA,
131117
- itemSchema: SETUP_ITEM_SCHEMA,
131118
- // 条目是一份份快照与终态,慢订阅方丢掉的只该是旧的。
131234
+ payloadSchema: SETUP_SESSION_PAYLOAD_SCHEMA,
131235
+ itemSchema: SETUP_SNAPSHOT_SCHEMA,
131236
+ // 每一条都是完整的快照,慢订阅方丢掉的只该是旧的。
131119
131237
  buffer: { latest: 16 },
131120
131238
  handle: async (_payload, emit, signal) => {
131121
- const send = (item) => emit(item);
131239
+ let snapshot = {};
131240
+ const send = (item) => {
131241
+ snapshot = foldFlowItem(snapshot, item);
131242
+ emit(snapshot);
131243
+ };
131122
131244
  const result = await runner.run(
131123
131245
  {},
131124
131246
  // 先落到宿主约定的条目类型上:形状不对在编译期就报,而不是到客户端被整条丢掉。
@@ -131126,7 +131248,7 @@ var registerLarkSetup = (ctx, options = {}) => {
131126
131248
  signal
131127
131249
  );
131128
131250
  if (result === void 0) return;
131129
- send(await saveAndConclude(ctx, result));
131251
+ send(await saveAndConclude(ctx, updateSettings, result));
131130
131252
  }
131131
131253
  });
131132
131254
  ctx.command({
@@ -137184,6 +137306,13 @@ var credentials = (binding) => ({
137184
137306
  appId: binding.values["larkAppId"] ?? "",
137185
137307
  appSecret: binding.values["larkAppSecret"] ?? ""
137186
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
+ };
137187
137316
  var makeLarkAdapter = (deps) => {
137188
137317
  const tenantToken = async (binding) => {
137189
137318
  const { appId, appSecret } = credentials(binding);
@@ -137196,12 +137325,16 @@ var makeLarkAdapter = (deps) => {
137196
137325
  };
137197
137326
  const aliasPorts = /* @__PURE__ */ new Map();
137198
137327
  const topicThreadIds = makeBoundedMap(TOPIC_THREAD_CACHE_LIMIT);
137328
+ const unreported = /* @__PURE__ */ new Map();
137199
137329
  const learning = /* @__PURE__ */ new Map();
137200
137330
  const learnTopic = (binding, target2, threadId) => {
137201
137331
  const key = chatKeyOf(binding);
137202
137332
  const report = (id) => {
137203
137333
  topicThreadIds.set(key, id);
137204
- 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);
137205
137338
  };
137206
137339
  const known = threadId ?? topicThreadIds.get(key);
137207
137340
  if (known !== void 0) {
@@ -137266,10 +137399,8 @@ var makeLarkAdapter = (deps) => {
137266
137399
  const { chatId: groupId, rootMessageId } = splitTopicChatId(chatId);
137267
137400
  return rootMessageId === void 0 ? void 0 : groupId;
137268
137401
  },
137269
- isConfigured: (binding) => {
137270
- const { appId, appSecret } = credentials(binding);
137271
- return appId !== "" && appSecret !== "";
137272
- },
137402
+ validate: larkConfigIssues,
137403
+ isConfigured: (binding) => larkConfigIssues(binding).length === 0,
137273
137404
  connect: (binding, handlers) => {
137274
137405
  const { appId, appSecret } = credentials(binding);
137275
137406
  const port = {
@@ -137277,6 +137408,11 @@ var makeLarkAdapter = (deps) => {
137277
137408
  knowsChatAlias: handlers.knowsChatAlias
137278
137409
  };
137279
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
+ }
137280
137416
  let open = true;
137281
137417
  const inbound = startLarkInbound({
137282
137418
  appId,
@@ -137330,7 +137466,10 @@ var makeLarkAdapter = (deps) => {
137330
137466
  return {
137331
137467
  close: () => {
137332
137468
  open = false;
137333
- 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
+ }
137334
137473
  inbound.close();
137335
137474
  }
137336
137475
  };
@@ -137504,7 +137643,7 @@ var makeLarkAdapter = (deps) => {
137504
137643
  var en_default = {
137505
137644
  settings: {
137506
137645
  title: "Chat",
137507
- 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.",
137508
137647
  bots: "Bots",
137509
137648
  language: {
137510
137649
  label: "Reply language",
@@ -137611,8 +137750,8 @@ var en_default = {
137611
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."
137612
137751
  },
137613
137752
  deliveryBotId: {
137614
- label: "Bot ID",
137615
- 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.",
137616
137755
  placeholder: "bot row id"
137617
137756
  },
137618
137757
  deliveryTargetId: {
@@ -137947,7 +138086,7 @@ var en_default = {
137947
138086
  setup: {
137948
138087
  action: {
137949
138088
  label: "Connect a Lark bot",
137950
- 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.",
137951
138090
  badge: "Beta",
137952
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."
137953
138092
  },
@@ -137974,7 +138113,7 @@ var en_default = {
137974
138113
  },
137975
138114
  fillRow: {
137976
138115
  title: "Add the bot",
137977
- 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."
137978
138117
  }
137979
138118
  },
137980
138119
  status: {
@@ -138108,6 +138247,66 @@ var en_default = {
138108
138247
  thread: "Go back to a normal session",
138109
138248
  panel: "Open the command panel",
138110
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
+ }
138111
138310
  }
138112
138311
  };
138113
138312
 
@@ -138115,7 +138314,7 @@ var en_default = {
138115
138314
  var zh_CN_default = {
138116
138315
  settings: {
138117
138316
  title: "\u804A\u5929",
138118
- 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",
138119
138318
  bots: "\u673A\u5668\u4EBA",
138120
138319
  language: {
138121
138320
  label: "\u56DE\u8BDD\u8BED\u8A00",
@@ -138222,8 +138421,8 @@ var zh_CN_default = {
138222
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"
138223
138422
  },
138224
138423
  deliveryBotId: {
138225
- label: "Bot ID",
138226
- 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",
138227
138426
  placeholder: "\u673A\u5668\u4EBA\u884C id"
138228
138427
  },
138229
138428
  deliveryTargetId: {
@@ -138558,7 +138757,7 @@ var zh_CN_default = {
138558
138757
  setup: {
138559
138758
  action: {
138560
138759
  label: "\u8FDE\u63A5\u98DE\u4E66\u673A\u5668\u4EBA",
138561
- 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",
138562
138761
  badge: "\u6D4B\u8BD5\u7248",
138563
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"
138564
138763
  },
@@ -138585,7 +138784,7 @@ var zh_CN_default = {
138585
138784
  },
138586
138785
  fillRow: {
138587
138786
  title: "\u6DFB\u52A0\u673A\u5668\u4EBA",
138588
- 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"
138589
138788
  }
138590
138789
  },
138591
138790
  status: {
@@ -138719,6 +138918,66 @@ var zh_CN_default = {
138719
138918
  thread: "\u5207\u56DE\u666E\u901A\u4F1A\u8BDD",
138720
138919
  panel: "\u6253\u5F00\u547D\u4EE4\u9762\u677F",
138721
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
+ }
138722
138981
  }
138723
138982
  };
138724
138983
 
@@ -139064,56 +139323,6 @@ var MINIMAL_HIDDEN = /* @__PURE__ */ new Set([
139064
139323
  ]);
139065
139324
  var panelCommandsFor = (mode = "default") => mode === "minimal" ? PANEL_COMMANDS.filter((spec) => !MINIMAL_HIDDEN.has(spec.id)) : PANEL_COMMANDS;
139066
139325
 
139067
- // src/sessionList.ts
139068
- var SESSION_LIST_MAX = 20;
139069
- var SESSION_ID_SHORT_LENGTH = 8;
139070
- var ageLabel = (updatedAt, now, t) => {
139071
- const at = Date.parse(updatedAt);
139072
- if (Number.isNaN(at)) return "";
139073
- const minutes = Math.floor(Math.max(0, now - at) / 6e4);
139074
- if (minutes < 1) return t("sessions.age.justNow");
139075
- if (minutes < 60) return t("sessions.age.minutes", { count: minutes });
139076
- const hours = Math.floor(minutes / 60);
139077
- if (hours < 24) return t("sessions.age.hours", { count: hours });
139078
- return t("sessions.age.days", { count: Math.floor(hours / 24) });
139079
- };
139080
- var shortId = (threadId) => threadId.slice(0, SESSION_ID_SHORT_LENGTH);
139081
- var sessionLine = (row, position, now, t) => {
139082
- const title = row.title.replace(/\s+/g, " ").trim() || t("sessions.untitled");
139083
- const age = ageLabel(row.updatedAt, now, t);
139084
- const badges = [
139085
- row.current ? t("sessions.badge.current") : null,
139086
- row.running ? t("sessions.badge.running") : null,
139087
- row.elsewhere ? t("sessions.badge.elsewhere") : null,
139088
- row.origin === "web" ? t("sessions.badge.web") : null
139089
- ].filter((badge) => badge !== null);
139090
- return [`${String(position)}. ${title}`, age, ...badges, shortId(row.threadId)].filter((part) => part !== "").join(" \xB7 ");
139091
- };
139092
- var formatSessions = (rows, now, t) => {
139093
- if (rows.length === 0) return t("sessions.empty");
139094
- const shown = rows.slice(0, SESSION_LIST_MAX);
139095
- return [
139096
- t("sessions.title", { count: rows.length }),
139097
- ...shown.map((row, index) => sessionLine(row, index + 1, now, t)),
139098
- ...rows.length > shown.length ? [t("sessions.more", { count: rows.length - shown.length })] : [],
139099
- "",
139100
- t("sessions.footer")
139101
- ].join("\n");
139102
- };
139103
- var listedThreadIds = (rows) => rows.slice(0, SESSION_LIST_MAX).map((row) => row.threadId);
139104
- var looksLikeListNumber = (wanted) => /^[1-9]\d*$/u.test(wanted) && wanted.length <= String(SESSION_LIST_MAX).length;
139105
- var pickSession = (arg, listed, rows) => {
139106
- const wanted = arg.trim();
139107
- if (wanted === "") return null;
139108
- if (looksLikeListNumber(wanted)) {
139109
- const ids = listed ?? listedThreadIds(rows);
139110
- return ids[Number(wanted) - 1] ?? null;
139111
- }
139112
- const needle = wanted.toLowerCase();
139113
- const matches = rows.filter((row) => row.threadId.toLowerCase().startsWith(needle));
139114
- return matches.length === 1 ? matches[0]?.threadId ?? null : null;
139115
- };
139116
-
139117
139326
  // src/panelController.ts
139118
139327
  var PANEL_PAGE_SIZE = 8;
139119
139328
  var PANEL_CARD_LIMIT = 200;
@@ -140185,8 +140394,14 @@ var makeSendFileTool = (deps) => ({
140185
140394
  });
140186
140395
 
140187
140396
  // src/sessions.ts
140188
- var SESSION_SCAN_LIMIT = 50;
140189
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
+ });
140190
140405
  var makeSessions = (core) => {
140191
140406
  const { host, t, commit, bindChat, deliver } = core;
140192
140407
  const listedByChat = /* @__PURE__ */ new Map();
@@ -140197,7 +140412,7 @@ var makeSessions = (core) => {
140197
140412
  threadId: thread.threadId,
140198
140413
  title: thread.title,
140199
140414
  updatedAt: thread.updatedAt,
140200
- running: thread.session?.activeTurnId != null,
140415
+ running: isSessionRunning(thread.sessionStatus),
140201
140416
  current: chatOf(core.state, chatKey).threadId === thread.threadId,
140202
140417
  elsewhere: Object.entries(core.state.chats).some(
140203
140418
  ([otherKey, record2]) => otherKey !== chatKey && record2.threadId === thread.threadId
@@ -140205,73 +140420,43 @@ var makeSessions = (core) => {
140205
140420
  origin
140206
140421
  };
140207
140422
  };
140208
- const webShellsForProject = async (projectId) => {
140423
+ const projectShells = async (projectId) => {
140209
140424
  if (projectId === "") return [];
140210
140425
  const shells = [];
140211
140426
  let cursor = null;
140212
- for (let page = 0; page < 5; page += 1) {
140427
+ do {
140213
140428
  const result = await host.threads.list({
140429
+ projectId,
140214
140430
  limit: WEB_LIST_PAGE,
140215
140431
  archived: "exclude",
140216
140432
  cursor
140217
140433
  });
140218
140434
  for (const shell of result.items) {
140219
- if (shell.projectId !== projectId) continue;
140220
- if (shell.archivedAt != null) continue;
140221
140435
  shells.push({
140222
140436
  threadId: shell.threadId,
140223
140437
  title: shell.title,
140224
140438
  updatedAt: shell.updatedAt,
140225
140439
  archivedAt: shell.archivedAt,
140226
- session: {
140227
- activeTurnId: shell.sessionStatus === "running" ? "running" : null
140228
- }
140440
+ sessionStatus: shell.sessionStatus
140229
140441
  });
140230
140442
  }
140231
- if (result.nextCursor === null) break;
140232
140443
  cursor = result.nextCursor;
140233
- }
140444
+ } while (cursor !== null);
140234
140445
  return shells;
140235
140446
  };
140447
+ const originOf = (threadId, projectId) => core.state.opened[threadId]?.projectId === projectId ? "plugin" : "web";
140236
140448
  const sessionRowsFor = async (binding) => {
140237
140449
  const chatKey = chatKeyOf(binding);
140238
- if (binding.projectId === "") return [];
140239
- const openedIds = new Set(
140240
- Object.entries(core.state.opened).filter(([, opened]) => opened.projectId === binding.projectId).map(([threadId]) => threadId)
140241
- );
140242
- const candidates = Object.entries(core.state.opened).filter(([, opened]) => opened.projectId === binding.projectId).toReversed().slice(0, SESSION_SCAN_LIMIT);
140243
- const ownedThreads = await Promise.all(
140244
- candidates.map(([threadId]) => host.projections.getThread(threadId))
140245
- );
140246
- const rows = [];
140247
- const seen = /* @__PURE__ */ new Set();
140248
- for (const thread of ownedThreads) {
140249
- if (thread === null) continue;
140250
- const row = rowFromThread(thread, chatKey, "plugin");
140251
- if (row === null) continue;
140252
- rows.push(row);
140253
- seen.add(row.threadId);
140254
- }
140255
- const shells = await webShellsForProject(binding.projectId);
140256
- const listed = new Set(shells.map((shell) => shell.threadId));
140257
- const attached = await Promise.all(
140258
- attachedOnChat(core.state, chatKey, binding.projectId).filter(({ threadId }) => !listed.has(threadId)).map(({ threadId }) => host.projections.getThread(threadId))
140259
- );
140260
- for (const thread of attached) {
140261
- if (thread === null || seen.has(thread.threadId) || openedIds.has(thread.threadId)) continue;
140262
- const row = rowFromThread(thread, chatKey, "web");
140263
- if (row === null) continue;
140264
- rows.push(row);
140265
- seen.add(row.threadId);
140266
- }
140267
- for (const shell of shells) {
140268
- if (seen.has(shell.threadId) || openedIds.has(shell.threadId)) continue;
140269
- const row = rowFromThread(shell, chatKey, "web");
140270
- if (row === null) continue;
140271
- rows.push(row);
140272
- seen.add(row.threadId);
140273
- }
140274
- 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));
140275
140460
  };
140276
140461
  const showSessions = async (binding) => {
140277
140462
  const rows = await sessionRowsFor(binding);
@@ -140317,24 +140502,27 @@ var makeSessions = (core) => {
140317
140502
  await deliver(binding, t("resume.nothingPending"));
140318
140503
  return;
140319
140504
  }
140320
- const rows = await sessionRowsFor(binding);
140321
- let targetId;
140322
- if (confirming) {
140323
- targetId = pending ?? null;
140324
- } else {
140325
- 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));
140326
140509
  if (targetId === null) {
140327
140510
  await deliver(binding, t("resume.notFound", { query: arg }));
140328
140511
  return;
140329
140512
  }
140330
140513
  }
140331
- const target2 = rows.find((row) => row.threadId === targetId);
140332
- 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) {
140333
140521
  if (confirming) pendingAttachByChat.delete(chatKey);
140334
140522
  await deliver(binding, t("resume.gone"));
140335
140523
  return;
140336
140524
  }
140337
- if (rows.some((row) => row.current && row.running)) {
140525
+ if (currentBusy) {
140338
140526
  await deliver(binding, t("resume.busy"));
140339
140527
  return;
140340
140528
  }
@@ -140876,6 +141064,7 @@ var tokenOf = (binding) => (binding.values["telegramToken"] ?? "").trim();
140876
141064
  var REACTION_WORKING = "\u{1F440}";
140877
141065
  var REACTION_DONE = "\u{1F44D}";
140878
141066
  var REACTION_FAILED = "\u{1F44E}";
141067
+ var telegramConfigIssues = (binding) => tokenOf(binding) === "" ? [{ column: "telegramToken", code: "required" }] : [];
140879
141068
  var defaultSleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
140880
141069
  var webhookHost = (url) => {
140881
141070
  try {
@@ -140977,7 +141166,8 @@ var makeTelegramAdapter = (deps) => {
140977
141166
  icon: "telegram",
140978
141167
  label: "Telegram",
140979
141168
  columns: COLUMNS2,
140980
- isConfigured: (binding) => tokenOf(binding) !== "",
141169
+ validate: telegramConfigIssues,
141170
+ isConfigured: (binding) => telegramConfigIssues(binding).length === 0,
140981
141171
  connect: async (binding, handlers) => {
140982
141172
  let stop = () => {
140983
141173
  };
@@ -141494,10 +141684,9 @@ var makeWebSync = (core) => {
141494
141684
  // src/settingsRowActions.ts
141495
141685
  var ROW_PAYLOAD_SCHEMA = {
141496
141686
  type: "object",
141497
- required: ["rowId", "values"],
141687
+ required: ["rowId"],
141498
141688
  properties: {
141499
- rowId: { type: "string" },
141500
- values: { type: "object" }
141689
+ rowId: { type: "string" }
141501
141690
  },
141502
141691
  additionalProperties: false
141503
141692
  };
@@ -141585,19 +141774,19 @@ var registerSettingsRowActions = (ctx, adapters) => {
141585
141774
  });
141586
141775
  };
141587
141776
  registerProbeAction({
141588
- name: ROW_ACTION_COMMANDS.checkConnection,
141777
+ name: BOT_ACTION_COMMANDS.checkConnection,
141589
141778
  summary: "Probe an IM bot's platform credentials without calling a model.",
141590
141779
  keyPrefix: "rowAction.checkConnection",
141591
141780
  copyable: false
141592
141781
  });
141593
141782
  registerProbeAction({
141594
- name: ROW_ACTION_COMMANDS.copyDiagnostics,
141783
+ name: BOT_ACTION_COMMANDS.copyDiagnostics,
141595
141784
  summary: "Probe an IM bot and return a copy-paste diagnostic report.",
141596
141785
  keyPrefix: "rowAction.copyDiagnostics",
141597
141786
  copyable: true
141598
141787
  });
141599
141788
  ctx.stream({
141600
- name: ROW_ACTION_COMMANDS.larkRepair,
141789
+ name: BOT_ACTION_COMMANDS.larkRepair,
141601
141790
  description: "Guide Lark scope repair and verify the saved app can still mint a token.",
141602
141791
  scope: "operate",
141603
141792
  payloadSchema: ROW_PAYLOAD_SCHEMA,
@@ -141663,6 +141852,289 @@ var registerSettingsRowActions = (ctx, adapters) => {
141663
141852
  });
141664
141853
  };
141665
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
+
141666
142138
  // src/wechatApi.ts
141667
142139
  var WECHAT_TEXT_MAX_CHARS = 1800;
141668
142140
 
@@ -141676,6 +142148,7 @@ var COLUMNS3 = [
141676
142148
  }
141677
142149
  ];
141678
142150
  var tokenOf2 = (bot) => bot.values["wechatBotToken"] ?? "";
142151
+ var wechatConfigIssues = (bot) => tokenOf2(bot) === "" ? [{ column: "wechatBotToken", code: "required" }] : [];
141679
142152
  var makeWechatAdapter = (deps) => {
141680
142153
  return {
141681
142154
  platform: "wechat",
@@ -141683,7 +142156,8 @@ var makeWechatAdapter = (deps) => {
141683
142156
  // 平台名与设置表的平台选项同一个键,免得默认机器人名与选项写成两种叫法。
141684
142157
  label: deps.translate("platform.wechat"),
141685
142158
  columns: COLUMNS3,
141686
- isConfigured: (bot) => tokenOf2(bot) !== "",
142159
+ validate: wechatConfigIssues,
142160
+ isConfigured: (bot) => wechatConfigIssues(bot).length === 0,
141687
142161
  connect: (bot, _handlers) => {
141688
142162
  deps.log.warn("wechat iLink connect is stubbed pending security-review gate (YCO-236)", {
141689
142163
  bot: bot.displayName,
@@ -142217,6 +142691,13 @@ var credentials2 = (binding) => ({
142217
142691
  botSecret: binding.values["wecomBotSecret"] ?? "",
142218
142692
  endpoint: normalizeWecomEndpoint(binding.values["wecomEndpoint"] || DEFAULT_WECOM_ENDPOINT)
142219
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
+ };
142220
142701
  var makeWecomAdapter = (deps) => {
142221
142702
  const clients = /* @__PURE__ */ new Map();
142222
142703
  const t = deps.translate;
@@ -142261,10 +142742,8 @@ ${kept}`;
142261
142742
  label: "WeCom",
142262
142743
  columns: COLUMNS4,
142263
142744
  unlistedValues: ["wecomEndpoint"],
142264
- isConfigured: (binding) => {
142265
- const { botId, botSecret } = credentials2(binding);
142266
- return botId !== "" && botSecret !== "";
142267
- },
142745
+ validate: wecomConfigIssues,
142746
+ isConfigured: (binding) => wecomConfigIssues(binding).length === 0,
142268
142747
  connect: (binding, handlers) => {
142269
142748
  const { botId, botSecret, endpoint } = credentials2(binding);
142270
142749
  let stopped = false;
@@ -142756,7 +143235,7 @@ var createWecomAppCrypto = (input) => {
142756
143235
  var deriveCallbackPathSecret = (token, encodingAESKey) => NodeCrypto4.createHash("sha256").update(`${token.trim()}:${encodingAESKey.trim()}`).digest("hex").slice(0, 24);
142757
143236
 
142758
143237
  // src/wecomAppParse.ts
142759
- var isRecord2 = (value2) => value2 !== null && typeof value2 === "object" && !Array.isArray(value2);
143238
+ var isRecord3 = (value2) => value2 !== null && typeof value2 === "object" && !Array.isArray(value2);
142760
143239
  var isXmlFormat = (raw) => {
142761
143240
  const trimmed = raw.trim();
142762
143241
  return trimmed.startsWith("<") && trimmed.endsWith(">");
@@ -142802,7 +143281,7 @@ var parseWecomAppPlainMessage = (raw) => {
142802
143281
  } catch {
142803
143282
  return null;
142804
143283
  }
142805
- if (!isRecord2(data)) return null;
143284
+ if (!isRecord3(data)) return null;
142806
143285
  const fromRaw = data["from"];
142807
143286
  const textRaw = data["text"];
142808
143287
  const imageRaw = data["image"];
@@ -142811,10 +143290,10 @@ var parseWecomAppPlainMessage = (raw) => {
142811
143290
  msgtype: String(data["msgtype"] ?? data["MsgType"] ?? "").toLowerCase(),
142812
143291
  createTime: data["createTime"] !== void 0 ? String(data["createTime"]) : data["CreateTime"] !== void 0 ? String(data["CreateTime"]) : void 0,
142813
143292
  agentId: data["agentid"] !== void 0 ? String(data["agentid"]) : data["AgentID"] !== void 0 ? String(data["AgentID"]) : void 0,
142814
- 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,
142815
143294
  to: typeof data["to"] === "string" ? data["to"] : void 0,
142816
- text: isRecord2(textRaw) ? { content: typeof textRaw["content"] === "string" ? textRaw["content"] : void 0 } : void 0,
142817
- 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,
142818
143297
  mediaId: typeof data["mediaid"] === "string" ? data["mediaid"] : void 0,
142819
143298
  recognition: typeof data["recognition"] === "string" ? data["recognition"] : void 0,
142820
143299
  event: String(data["event"] ?? data["eventtype"] ?? "").toLowerCase() || void 0,
@@ -143184,6 +143663,21 @@ var WECOM_APP_IMAGE_BYTES = 10 * 1024 * 1024;
143184
143663
  var WECOM_APP_FILE_BYTES = 20 * 1024 * 1024;
143185
143664
  var WECOM_APP_MIN_BYTES = 5;
143186
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
+ };
143187
143681
  var OUTBOUND_COLUMNS = /* @__PURE__ */ new Set([
143188
143682
  "wecomAppCorpId",
143189
143683
  "wecomAppAgentId",
@@ -143253,11 +143747,14 @@ var makeWecomAppAdapter = (deps) => {
143253
143747
  icon: "wecom",
143254
143748
  label: deps.translate("platform.wecom-app"),
143255
143749
  columns: COLUMNS5,
143256
- // 必填的都填了、EncodingAESKey 格式对(解不了回调的行不算配好)。别的列格式不对的行照样交给
143257
- // connect,由它报出哪几列不对(一条日志,不重试),也不挡设置页的「检查连接」去探出站凭据。
143750
+ // 设置页标格子用完整的逐列结果。算不算配齐仍是原来那份,不能收成「一个问题都没有」:
143751
+ // 必填都填了、EncodingAESKey 格式对。别的列格式不对的行照样交给 connect 报出来(一条日志,
143752
+ // 不重试),也不挡设置页的「检查连接」去探出站凭据。
143753
+ validate: validateWecomAppBot,
143258
143754
  isConfigured: (bot) => validateWecomAppBot(bot).every(
143259
143755
  (issue) => issue.code !== "required" && issue.column !== "wecomAppEncodingAESKey"
143260
143756
  ),
143757
+ publicFields: wecomAppPublicFields,
143261
143758
  /**
143262
143759
  * 自建应用是回调式渠道:入站由企微推到我们的 listener,没有可探的长连接;能探的是出站
143263
143760
  * 凭据——和真实发送同一条路,向 gettoken 换一次 access_token。
@@ -143321,7 +143818,10 @@ var makeWecomAppAdapter = (deps) => {
143321
143818
  deps.log.info("wecom-app connected", {
143322
143819
  bot: bot.displayName,
143323
143820
  callbackPath: path,
143324
- callbackUrl: c.callbackBaseUrl !== "" ? `${c.callbackBaseUrl}${path}` : `${server.origin}${path}`,
143821
+ callbackUrl: joinCallbackUrl(
143822
+ c.callbackBaseUrl !== "" ? c.callbackBaseUrl : server.origin,
143823
+ path
143824
+ ),
143325
143825
  listen: `${server.host}:${String(server.port)}`
143326
143826
  });
143327
143827
  return {
@@ -143422,9 +143922,11 @@ var imPlugin = definePlugin({
143422
143922
  }
143423
143923
  })
143424
143924
  ];
143425
- ctx.settingsCard({ ...buildSettingsCard(adapters), actions: [LARK_SETUP_CARD_ACTION] });
143426
- registerLarkSetup(ctx);
143925
+ ctx.settingsCard(buildSettingsCard(adapters));
143926
+ const updateSettings = makeSettingsUpdater(ctx.settings);
143927
+ registerLarkSetup(ctx, { updateSettings });
143427
143928
  registerSettingsRowActions(ctx, adapters);
143929
+ registerSettingsCommands(ctx, adapters, updateSettings);
143428
143930
  const core = makeImCore({
143429
143931
  host: {
143430
143932
  log: ctx.log,