@ycodium-ai/plugin-im 0.2.2526 → 0.2.2544

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
@@ -8846,7 +8846,7 @@ var require_mime_types = __commonJS({
8846
8846
  exports.contentType = contentType;
8847
8847
  exports.extension = extension;
8848
8848
  exports.extensions = /* @__PURE__ */ Object.create(null);
8849
- exports.lookup = lookup;
8849
+ exports.lookup = lookup2;
8850
8850
  exports.types = /* @__PURE__ */ Object.create(null);
8851
8851
  populateMaps(exports.extensions, exports.types);
8852
8852
  function charset(type) {
@@ -8888,7 +8888,7 @@ var require_mime_types = __commonJS({
8888
8888
  }
8889
8889
  return exts[0];
8890
8890
  }
8891
- function lookup(path) {
8891
+ function lookup2(path) {
8892
8892
  if (!path || typeof path !== "string") {
8893
8893
  return false;
8894
8894
  }
@@ -15097,12 +15097,12 @@ var require_axios = __commonJS({
15097
15097
  family
15098
15098
  });
15099
15099
  var normalizedLookupCache = /* @__PURE__ */ new WeakMap();
15100
- var normalizeLookup = (lookup) => {
15101
- let normalized = normalizedLookupCache.get(lookup);
15100
+ var normalizeLookup = (lookup2) => {
15101
+ let normalized = normalizedLookupCache.get(lookup2);
15102
15102
  if (normalized) {
15103
15103
  return normalized;
15104
15104
  }
15105
- const callbackLookup = callbackify(lookup, (value) => utils$1.isArray(value) ? value : [value]);
15105
+ const callbackLookup = callbackify(lookup2, (value) => utils$1.isArray(value) ? value : [value]);
15106
15106
  normalized = (hostname, opt, cb) => {
15107
15107
  callbackLookup(hostname, opt, (err, arg0, arg1) => {
15108
15108
  if (err) {
@@ -15117,7 +15117,7 @@ var require_axios = __commonJS({
15117
15117
  opt.all ? cb(err, addresses) : cb(err, addresses[0].address, addresses[0].family);
15118
15118
  });
15119
15119
  };
15120
- normalizedLookupCache.set(lookup, normalized);
15120
+ normalizedLookupCache.set(lookup2, normalized);
15121
15121
  return normalized;
15122
15122
  };
15123
15123
  var http2Transport = {
@@ -15160,7 +15160,7 @@ var require_axios = __commonJS({
15160
15160
  const own2 = (key) => utils$1.getSafeProp(config, key);
15161
15161
  const transitional = own2("transitional") || transitionalDefaults;
15162
15162
  let data = own2("data");
15163
- let lookup = own2("lookup");
15163
+ let lookup2 = own2("lookup");
15164
15164
  let family = own2("family");
15165
15165
  let httpVersion = own2("httpVersion");
15166
15166
  if (httpVersion === void 0) httpVersion = 1;
@@ -15193,8 +15193,8 @@ var require_axios = __commonJS({
15193
15193
  throw new AxiosError(`Unsupported protocol version '${httpVersion}'`, AxiosError.ERR_BAD_OPTION_VALUE, config);
15194
15194
  }
15195
15195
  const isHttp2 = httpVersion === 2;
15196
- if (lookup) {
15197
- lookup = normalizeLookup(lookup);
15196
+ if (lookup2) {
15197
+ lookup2 = normalizeLookup(lookup2);
15198
15198
  }
15199
15199
  const abortEmitter = new events.EventEmitter();
15200
15200
  function abort(reason) {
@@ -15387,9 +15387,9 @@ var require_axios = __commonJS({
15387
15387
  }));
15388
15388
  }
15389
15389
  headers.set("Accept-Encoding", utils$1.hasOwnProp(transitional, "advertiseZstdAcceptEncoding") && transitional.advertiseZstdAcceptEncoding === true ? ACCEPT_ENCODING_WITH_ZSTD : ACCEPT_ENCODING, false);
15390
- if (isHttp2 && lookup) {
15390
+ if (isHttp2 && lookup2) {
15391
15391
  http2Options = Object.assign(/* @__PURE__ */ Object.create(null), http2Options, {
15392
- lookup
15392
+ lookup: lookup2
15393
15393
  });
15394
15394
  }
15395
15395
  const options = Object.assign(/* @__PURE__ */ Object.create(null), {
@@ -15408,7 +15408,7 @@ var require_axios = __commonJS({
15408
15408
  http2Options,
15409
15409
  createConnection: void 0
15410
15410
  });
15411
- !utils$1.isUndefined(lookup) && (options.lookup = lookup);
15411
+ !utils$1.isUndefined(lookup2) && (options.lookup = lookup2);
15412
15412
  let proxyApplied = false;
15413
15413
  if (socketPath) {
15414
15414
  if (typeof socketPath !== "string") {
@@ -123492,11 +123492,11 @@ var require_lib2 = __commonJS({
123492
123492
  const AgentClass = url.startsWith("https:") ? https__default["default"].Agent : http__default["default"].Agent;
123493
123493
  const agent = new AgentClass();
123494
123494
  const family = pinnedIp.includes(":") ? 6 : 4;
123495
- const lookup = (_hostname, _opts, cb) => {
123495
+ const lookup2 = (_hostname, _opts, cb) => {
123496
123496
  cb(null, pinnedIp, family);
123497
123497
  };
123498
123498
  const origCreateConnection = agent.createConnection.bind(agent);
123499
- agent.createConnection = (opts, cb) => origCreateConnection(Object.assign(Object.assign({}, opts), { lookup }), cb);
123499
+ agent.createConnection = (opts, cb) => origCreateConnection(Object.assign(Object.assign({}, opts), { lookup: lookup2 }), cb);
123500
123500
  return agent;
123501
123501
  }
123502
123502
  function composeMentionsTextPrefix(mentions) {
@@ -125927,13 +125927,41 @@ var teamArgument = (text, chinese) => {
125927
125927
  const alias = text.match(/^换团队(?:\s+([\s\S]*))?$/);
125928
125928
  return alias ? (alias[1] ?? "").trim() : void 0;
125929
125929
  };
125930
+ var resumeArgument = (text, chinese) => {
125931
+ const match = text.match(/^\/resume(?:\s+([\s\S]*))?$/i);
125932
+ if (match) return (match[1] ?? "").trim();
125933
+ if (!chinese) return void 0;
125934
+ const alias = text.match(/^切换会话(?:\s+([\s\S]*))?$/);
125935
+ return alias ? (alias[1] ?? "").trim() : void 0;
125936
+ };
125937
+ var modelArgument = (text, chinese) => {
125938
+ const match = text.match(/^\/model(?:\s+([\s\S]*))?$/i);
125939
+ if (match) return (match[1] ?? "").trim();
125940
+ if (!chinese) return void 0;
125941
+ const alias = text.match(/^换模型(?:\s+([\s\S]*))?$/);
125942
+ return alias ? (alias[1] ?? "").trim() : void 0;
125943
+ };
125944
+ var planArgument = (text, chinese) => {
125945
+ const match = text.match(/^\/plan(?:\s+([\s\S]*))?$/i);
125946
+ if (match) return (match[1] ?? "").trim().toLowerCase();
125947
+ if (!chinese) return void 0;
125948
+ const alias = text.match(/^计划模式(?:\s+([\s\S]*))?$/);
125949
+ return alias ? (alias[1] ?? "").trim().toLowerCase() : void 0;
125950
+ };
125951
+ var planModeOf = (argument, chinese) => {
125952
+ if (argument === "") return "toggle";
125953
+ if (argument === "on" || chinese && argument === "\u5F00") return "on";
125954
+ if (argument === "off" || chinese && argument === "\u5173") return "off";
125955
+ return "invalid";
125956
+ };
125930
125957
  var isThreadCommand = (text, chinese) => /^\/thread$/i.test(text.trim()) || chinese && text.trim() === "\u6362\u5BF9\u8BDD";
125931
125958
  var ZH_ALIASES = {
125932
125959
  \u505C: "stop",
125933
125960
  \u505C\u6B62: "stop",
125934
125961
  \u72B6\u6001: "status",
125935
125962
  \u65B0\u5BF9\u8BDD: "new",
125936
- \u5E2E\u52A9: "help"
125963
+ \u5E2E\u52A9: "help",
125964
+ \u4F1A\u8BDD\u5217\u8868: "sessions"
125937
125965
  };
125938
125966
  var resolveTeam = (query, teams) => {
125939
125967
  const byId = teams.find((team) => team.teamId === query);
@@ -125950,6 +125978,15 @@ var parseChatCommand = (input) => {
125950
125978
  if (/^\/status$/i.test(trimmed)) return { kind: "status" };
125951
125979
  if (/^\/new$/i.test(trimmed)) return { kind: "new" };
125952
125980
  if (/^\/help$/i.test(trimmed)) return { kind: "help" };
125981
+ if (/^\/sessions$/i.test(trimmed)) return { kind: "sessions" };
125982
+ const resume = resumeArgument(input.text, input.chinese);
125983
+ if (resume !== void 0) {
125984
+ return resume === "" ? { kind: "sessions" } : { kind: "resume", arg: resume };
125985
+ }
125986
+ const model = modelArgument(input.text, input.chinese);
125987
+ if (model !== void 0) return { kind: "model", arg: model };
125988
+ const plan = planArgument(input.text, input.chinese);
125989
+ if (plan !== void 0) return { kind: "plan", mode: planModeOf(plan, input.chinese) };
125953
125990
  const query = teamArgument(input.text, input.chinese);
125954
125991
  if (query !== void 0) {
125955
125992
  if (input.teams.length === 0) return { kind: "noTeams" };
@@ -125965,19 +126002,157 @@ var parseChatCommand = (input) => {
125965
126002
  }
125966
126003
  return { kind: "message" };
125967
126004
  };
125968
- var helpText = () => [
125969
- "\u80FD\u7528\u7684\u547D\u4EE4\uFF1A",
125970
- "\xB7 /stop \u2014\u2014 \u505C\u6389\u6B63\u5728\u8DD1\u7684\u8FD9\u4E00\u8F6E\uFF0C\u4F1A\u8BDD\u7559\u7740",
125971
- "\xB7 /status \u2014\u2014 \u73B0\u5728\u5728\u5E72\u4EC0\u4E48",
125972
- "\xB7 /new \u2014\u2014 \u5FD8\u6389\u5F53\u524D\u4F1A\u8BDD\uFF0C\u4E0B\u4E00\u53E5\u5F00\u65B0\u7684",
125973
- "\xB7 /team <\u961F\u540D> \u2014\u2014 \u628A\u63A5\u4E0B\u6765\u7684\u6D3B\u6D3E\u7ED9\u4E00\u652F\u961F\uFF1B/team \u4E0D\u5E26\u540D\u5B57\u770B\u6709\u54EA\u4E9B\u961F",
125974
- "\xB7 /thread \u2014\u2014 \u5207\u56DE\u666E\u901A\u4F1A\u8BDD",
125975
- "\u4E2D\u6587\u4E5F\u884C\uFF1A\u505C / \u72B6\u6001 / \u65B0\u5BF9\u8BDD / \u6362\u56E2\u961F / \u6362\u5BF9\u8BDD\u3002"
126005
+ var helpText = (t) => [
126006
+ t("help.title"),
126007
+ t("help.stop"),
126008
+ t("help.status"),
126009
+ t("help.new"),
126010
+ t("help.sessions"),
126011
+ t("help.resume"),
126012
+ t("help.model"),
126013
+ t("help.plan"),
126014
+ t("help.team"),
126015
+ t("help.thread"),
126016
+ t("help.aliases")
125976
126017
  ].join("\n");
125977
- var describeTargets = (target, teams) => {
125978
- const current = target.kind === "thread" ? "\u73B0\u5728\u8FD9\u4E2A\u7FA4\u5F00\u7684\u662F\u666E\u901A\u4F1A\u8BDD\u3002" : `\u73B0\u5728\u6D3E\u7ED9\uFF1A${teams.find((team) => team.teamId === target.teamId)?.title ?? target.teamId}`;
125979
- const list = teams.length === 0 ? "\u8FD9\u53F0\u673A\u5668\u4E0A\u8FD8\u6CA1\u6709\u56E2\u961F\u3002" : ["\u53EF\u9009\u7684\u961F\uFF1A", ...teams.map((team) => `\xB7 ${team.title}`)].join("\n");
125980
- return [current, "", list, "", "\u7528 /team <\u961F\u540D> \u5207\u6362\uFF0C/thread \u5207\u56DE\u666E\u901A\u4F1A\u8BDD\u3002"].join("\n");
126018
+ var describeTargets = (target, teams, t) => {
126019
+ const current = target.kind === "thread" ? t("targets.currentThread") : t("targets.currentTeam", {
126020
+ title: teams.find((team) => team.teamId === target.teamId)?.title ?? target.teamId
126021
+ });
126022
+ const list = teams.length === 0 ? t("targets.noTeams") : [t("targets.teamList"), ...teams.map((team) => `\xB7 ${team.title}`)].join("\n");
126023
+ return [current, "", list, "", t("targets.hint")].join("\n");
126024
+ };
126025
+
126026
+ // src/chatState.ts
126027
+ var IM_STATE_KEY = "chats";
126028
+ var IM_STATE_VERSION = 1;
126029
+ var OPENED_THREAD_LIMIT = 200;
126030
+ var APPROVAL_RECORD_LIMIT = 100;
126031
+ var emptyState = () => ({ chats: {}, opened: {}, approvals: {} });
126032
+ var DEFAULT_CHAT_RECORD = {
126033
+ threadId: null,
126034
+ target: { kind: "thread" },
126035
+ model: null,
126036
+ plan: false
126037
+ };
126038
+ var chatOf = (state, chatKey) => ({
126039
+ ...DEFAULT_CHAT_RECORD,
126040
+ ...state.chats[chatKey]
126041
+ });
126042
+ var findApprovalByToken = (state, token) => {
126043
+ for (const approval of Object.values(state.approvals)) {
126044
+ const button = approval.options.find((option) => option.token === token);
126045
+ if (button !== void 0) return { approval, button };
126046
+ }
126047
+ return void 0;
126048
+ };
126049
+ var openApprovalOf = (state, chatKey) => Object.values(state.approvals).findLast(
126050
+ (approval) => approval.chatKey === chatKey && !approval.resolved
126051
+ );
126052
+ var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
126053
+ var nonEmpty = (value) => typeof value === "string" && value !== "" ? value : null;
126054
+ var readTarget = (value) => {
126055
+ if (!isRecord(value)) return null;
126056
+ if (value["kind"] === "thread") return { kind: "thread" };
126057
+ const teamId = nonEmpty(value["teamId"]);
126058
+ return value["kind"] === "team" && teamId !== null ? { kind: "team", teamId } : null;
126059
+ };
126060
+ var readButtons = (value) => {
126061
+ if (!Array.isArray(value)) return null;
126062
+ const buttons = [];
126063
+ for (const entry of value) {
126064
+ if (!isRecord(entry)) return null;
126065
+ const token = nonEmpty(entry["token"]);
126066
+ const decision = nonEmpty(entry["decision"]);
126067
+ const label = typeof entry["label"] === "string" ? entry["label"] : null;
126068
+ if (token === null || decision === null || label === null) return null;
126069
+ buttons.push({ token, label, decision });
126070
+ }
126071
+ return buttons.length === 0 ? null : buttons;
126072
+ };
126073
+ var keepNewest = (record, limit) => {
126074
+ const entries = Object.entries(record);
126075
+ return entries.length <= limit ? record : Object.fromEntries(entries.slice(entries.length - limit));
126076
+ };
126077
+ var withChat = (state, chatKey, patch) => ({
126078
+ ...state,
126079
+ chats: { ...state.chats, [chatKey]: { ...chatOf(state, chatKey), ...patch } }
126080
+ });
126081
+ var foldImEvent = (state, name, data) => {
126082
+ if (!isRecord(data)) return state;
126083
+ switch (name) {
126084
+ case "chat-updated": {
126085
+ const chatKey = nonEmpty(data["chatKey"]);
126086
+ if (chatKey === null) return state;
126087
+ const patch = {};
126088
+ if (data["threadId"] === null) patch.threadId = null;
126089
+ else if (nonEmpty(data["threadId"]) !== null) patch.threadId = nonEmpty(data["threadId"]);
126090
+ const target = readTarget(data["target"]);
126091
+ if (target !== null) patch.target = target;
126092
+ if (data["model"] === null) patch.model = null;
126093
+ else if (isRecord(data["model"])) {
126094
+ const instanceId = nonEmpty(data["model"]["instanceId"]);
126095
+ const model = nonEmpty(data["model"]["model"]);
126096
+ if (instanceId !== null && model !== null) patch.model = { instanceId, model };
126097
+ }
126098
+ if (typeof data["plan"] === "boolean") patch.plan = data["plan"];
126099
+ return withChat(state, chatKey, patch);
126100
+ }
126101
+ case "thread-opened": {
126102
+ const chatKey = nonEmpty(data["chatKey"]);
126103
+ const threadId = nonEmpty(data["threadId"]);
126104
+ const projectId = nonEmpty(data["projectId"]);
126105
+ const openedAt = nonEmpty(data["openedAt"]);
126106
+ if (chatKey === null || threadId === null || projectId === null || openedAt === null) {
126107
+ return state;
126108
+ }
126109
+ const bound = withChat(state, chatKey, { threadId });
126110
+ return {
126111
+ ...bound,
126112
+ opened: keepNewest(
126113
+ { ...bound.opened, [threadId]: { chatKey, projectId, openedAt } },
126114
+ OPENED_THREAD_LIMIT
126115
+ )
126116
+ };
126117
+ }
126118
+ case "approval-offered": {
126119
+ const chatKey = nonEmpty(data["chatKey"]);
126120
+ const threadId = nonEmpty(data["threadId"]);
126121
+ const requestId = nonEmpty(data["requestId"]);
126122
+ const options = readButtons(data["options"]);
126123
+ if (chatKey === null || threadId === null || requestId === null || options === null) {
126124
+ return state;
126125
+ }
126126
+ return {
126127
+ ...state,
126128
+ approvals: keepNewest(
126129
+ {
126130
+ ...state.approvals,
126131
+ [requestId]: { chatKey, threadId, requestId, options, resolved: false }
126132
+ },
126133
+ APPROVAL_RECORD_LIMIT
126134
+ )
126135
+ };
126136
+ }
126137
+ case "approval-resolved": {
126138
+ const requestId = nonEmpty(data["requestId"]);
126139
+ const existing = requestId === null ? void 0 : state.approvals[requestId];
126140
+ if (requestId === null || existing === void 0) return state;
126141
+ return {
126142
+ ...state,
126143
+ approvals: { ...state.approvals, [requestId]: { ...existing, resolved: true } }
126144
+ };
126145
+ }
126146
+ default:
126147
+ return state;
126148
+ }
126149
+ };
126150
+ var foldPluginEvent = (pluginId) => (state, event) => {
126151
+ if (event.type !== "plugin.event-appended" || !isRecord(event.payload)) return state;
126152
+ if (event.payload["pluginId"] !== pluginId) return state;
126153
+ const fullName = event.payload["name"];
126154
+ if (typeof fullName !== "string") return state;
126155
+ return foldImEvent(state, fullName.slice(fullName.lastIndexOf("/") + 1), event.payload["data"]);
125981
126156
  };
125982
126157
 
125983
126158
  // src/cooldown.ts
@@ -126455,13 +126630,502 @@ var makeLarkAdapter = (deps) => {
126455
126630
  };
126456
126631
  };
126457
126632
 
126633
+ // locales/en.json
126634
+ var en_default = {
126635
+ settings: {
126636
+ title: "Chat",
126637
+ description: "Connect chat groups to Ycodium. One row per group: create a bot on that platform, add it to the group, and paste its credentials. Pick a project and that group can start work; leave the project empty and the row only reports, on every project.",
126638
+ groups: "Groups",
126639
+ language: {
126640
+ label: "Reply language",
126641
+ description: "The language of the bot's own sentences in the group: help, status, confirmations and notices. Chinese shortcut commands only work when this is Chinese. Saving restarts the plugin.",
126642
+ zhCN: "\u4E2D\u6587",
126643
+ en: "English"
126644
+ },
126645
+ reportOnly: "Report only, all projects",
126646
+ archivedSuffix: "(archived)",
126647
+ runtimeMode: {
126648
+ approvalRequired: "Ask before commands and file changes",
126649
+ autoAcceptEdits: "Auto-accept edits, ask before other actions",
126650
+ auto: "Approve routine actions",
126651
+ fullAccess: "Commands and edits without prompts"
126652
+ },
126653
+ notifyMode: {
126654
+ turn: "One message per finished turn",
126655
+ message: "Every reply the agent completes"
126656
+ }
126657
+ },
126658
+ platform: {
126659
+ lark: "Lark",
126660
+ telegram: "Telegram",
126661
+ wecom: "WeCom"
126662
+ },
126663
+ column: {
126664
+ displayName: {
126665
+ label: "Name",
126666
+ placeholder: "Team chat"
126667
+ },
126668
+ platform: {
126669
+ label: "Platform"
126670
+ },
126671
+ larkAppId: {
126672
+ label: "Lark app ID"
126673
+ },
126674
+ larkAppSecret: {
126675
+ label: "Lark app secret"
126676
+ },
126677
+ telegramToken: {
126678
+ label: "Telegram bot token"
126679
+ },
126680
+ wecomBotId: {
126681
+ label: "WeCom bot ID"
126682
+ },
126683
+ wecomBotSecret: {
126684
+ label: "WeCom bot secret"
126685
+ },
126686
+ chatId: {
126687
+ label: "Group id",
126688
+ description: "The platform's own id for the group to post in."
126689
+ },
126690
+ allowedSenders: {
126691
+ label: "Who may command the bot",
126692
+ description: "Comma-separated user ids from that platform. Empty means anyone in the group can make the agent run things on this machine."
126693
+ },
126694
+ proxy: {
126695
+ label: "Proxy",
126696
+ description: "Some platforms are only reachable through a proxy on this machine, and which one can differ per row."
126697
+ },
126698
+ projectId: {
126699
+ label: "Project",
126700
+ description: "Where a message from this group starts its thread. Empty means the row only reports, and reports every project."
126701
+ },
126702
+ runtimeMode: {
126703
+ label: "What a chat message may do",
126704
+ description: "Anyone who can mention the bot starts a turn, so this is deliberately not full access by default. Whichever mode asks, the request lands in the group."
126705
+ },
126706
+ mode: {
126707
+ label: "How much to send",
126708
+ description: "A single turn can produce dozens of assistant messages \u2014 mostly commentary between tool calls \u2014 so one per finished turn is the readable default."
126709
+ }
126710
+ },
126711
+ reply: {
126712
+ notAllowed: "You are not on this group's allow list, so this tap had no effect.",
126713
+ noDefaultModel: 'Project "{{title}}" has no default model yet, so a thread cannot be started. Pick a default model for it in Ycodium, then message again.',
126714
+ projectArchived: 'Project "{{title}}" is archived. Restore it, then message again.',
126715
+ projectMissing: `The project bound to "{{group}}" is no longer on this machine (it may have been deleted), so a thread cannot be started. In Ycodium's chat settings, pick a project for this group again.`,
126716
+ threadGone: "The earlier session was deleted or archived, so this group now starts a new one.",
126717
+ teamGone: "The team this group was sending to no longer exists, so messages go to a normal session again."
126718
+ },
126719
+ approval: {
126720
+ allow: "Allow",
126721
+ deny: "Deny",
126722
+ alreadyHandled: "Already handled",
126723
+ replyHint: "Reply with the number."
126724
+ },
126725
+ command: {
126726
+ teamSelected: 'OK. From now on messages go to "{{title}}".',
126727
+ threadSelected: "OK. From now on messages start a normal session.",
126728
+ teamNotFound: 'No team is called "{{query}}", or more than one matches.',
126729
+ noTeams: "There are no teams on this machine yet.",
126730
+ newSession: "OK. The next message starts a new session.",
126731
+ stopNoSession: "Nothing is running in this group.",
126732
+ stopNoTurn: "No turn is running right now.",
126733
+ stopped: "Stopped. The session is still there \u2014 just keep talking."
126734
+ },
126735
+ status: {
126736
+ routedTeam: "Sent to: {{title}}",
126737
+ routedThread: "Normal session",
126738
+ nothingRunning: "{{where}}. Nothing is running.",
126739
+ session: "Session: {{title}}",
126740
+ running: "Running. Send /stop to stop it.",
126741
+ idle: "Idle.",
126742
+ model: "Model: {{model}}",
126743
+ planOn: "Plan mode: on"
126744
+ },
126745
+ targets: {
126746
+ currentThread: "This group is on a normal session.",
126747
+ currentTeam: "Sent to: {{title}}",
126748
+ noTeams: "There are no teams on this machine yet.",
126749
+ teamList: "Teams you can pick:",
126750
+ hint: "Use /team name to switch, /thread to go back to a normal session."
126751
+ },
126752
+ help: {
126753
+ title: "Commands:",
126754
+ stop: "\xB7 /stop \u2014 stop the turn that is running; the session stays",
126755
+ status: "\xB7 /status \u2014 what is going on right now",
126756
+ new: "\xB7 /new \u2014 forget the current session; the next message starts a new one",
126757
+ team: "\xB7 /team name \u2014 send the next messages to a team; /team alone lists the teams",
126758
+ thread: "\xB7 /thread \u2014 go back to a normal session",
126759
+ aliases: "Chinese shortcuts (\u505C / \u72B6\u6001 / \u65B0\u5BF9\u8BDD / \u4F1A\u8BDD\u5217\u8868 / \u5207\u6362\u4F1A\u8BDD / \u6362\u6A21\u578B / \u8BA1\u5212\u6A21\u5F0F / \u6362\u56E2\u961F / \u6362\u5BF9\u8BDD) work when the reply language is Chinese.",
126760
+ sessions: "\xB7 /sessions \u2014 list the sessions this bot opened for this group's project",
126761
+ resume: "\xB7 /resume number \u2014 switch to one of them (a number from /sessions, or the start of its id)",
126762
+ model: "\xB7 /model \u2014 see the model this group uses; /model number or /model instance/model switches it",
126763
+ plan: "\xB7 /plan \u2014 toggle plan mode (the agent proposes a plan instead of acting); /plan on, /plan off set it"
126764
+ },
126765
+ sessions: {
126766
+ title: "Sessions this bot opened for this group's project ({{count}}):",
126767
+ empty: "This bot has not opened any session for this group's project yet. Send a message to start one.",
126768
+ more: "{{count}} more not shown \u2014 reach them with /resume and the start of an id.",
126769
+ footer: "Switch with /resume number, or /resume and the start of an id.",
126770
+ untitled: "Untitled",
126771
+ unavailable: "Could not read the session list right now. Try again in a moment.",
126772
+ badge: {
126773
+ current: "\u2605 current",
126774
+ running: "\u25CF running",
126775
+ elsewhere: "in another chat"
126776
+ },
126777
+ age: {
126778
+ justNow: "just now",
126779
+ minutes: "{{count}} min ago",
126780
+ hours: "{{count}} h ago",
126781
+ days: "{{count}} d ago"
126782
+ }
126783
+ },
126784
+ resume: {
126785
+ notFound: 'No session matches "{{query}}". Use /sessions to see the list.',
126786
+ busy: "A turn is running in this group \u2014 send /stop first, then switch.",
126787
+ already: "This group is already on that session.",
126788
+ gone: "That session was deleted or archived, so it cannot be resumed.",
126789
+ runningElsewhere: "That session is running a turn in another chat \u2014 stop it there first.",
126790
+ done: 'Switched to "{{title}}". Send a message to keep going from there.'
126791
+ },
126792
+ model: {
126793
+ current: "Model: {{model}}",
126794
+ currentUnknown: "Model: not decided yet \u2014 a new session uses the project's default.",
126795
+ unavailable: "The available models cannot be read right now, so the model cannot be switched.",
126796
+ listTitle: "Available models:",
126797
+ more: "{{count}} more not shown \u2014 pick one with /model instance/model.",
126798
+ footer: "Switch with /model number, or /model instance/model.",
126799
+ notFound: 'No single model matches "{{query}}". Use /model to see the list.',
126800
+ setNow: "Model set to {{model}}. The current session uses it from its next turn.",
126801
+ setNext: "Model set to {{model}}. It applies to the next new session.",
126802
+ setFailed: "Could not switch the current session's model. Try again in a moment."
126803
+ },
126804
+ plan: {
126805
+ on: "Plan mode is on: from the next message the agent proposes a plan instead of acting. Send /plan off to leave it.",
126806
+ off: "Plan mode is off. Messages are handled normally again.",
126807
+ alreadyOn: "Plan mode is already on.",
126808
+ alreadyOff: "Plan mode is already off.",
126809
+ usage: "Usage: /plan toggles plan mode; /plan on and /plan off set it."
126810
+ }
126811
+ };
126812
+
126813
+ // locales/zh-CN.json
126814
+ var zh_CN_default = {
126815
+ settings: {
126816
+ title: "\u804A\u5929",
126817
+ description: "\u628A\u804A\u5929\u7FA4\u63A5\u5230 Ycodium\u3002\u4E00\u884C\u4E00\u4E2A\u7FA4\uFF1A\u5148\u5728\u5BF9\u5E94\u5E73\u53F0\u5EFA\u597D\u673A\u5668\u4EBA\u3001\u62C9\u8FDB\u7FA4\uFF0C\u518D\u628A\u5B83\u7684\u51ED\u636E\u586B\u8FDB\u6765\u3002\u9009\u4E86\u9879\u76EE\uFF0C\u8FD9\u4E2A\u7FA4\u5C31\u80FD\u6D3E\u6D3B\uFF1B\u9879\u76EE\u7559\u7A7A\uFF0C\u8FD9\u4E00\u884C\u5C31\u53EA\u5F80\u7FA4\u91CC\u62A5\uFF0C\u800C\u4E14\u62A5\u6240\u6709\u9879\u76EE\u3002",
126818
+ groups: "\u7FA4",
126819
+ language: {
126820
+ label: "\u56DE\u8BDD\u8BED\u8A00",
126821
+ description: "\u673A\u5668\u4EBA\u5728\u7FA4\u91CC\u81EA\u5DF1\u8BF4\u7684\u8BDD\u7528\u4EC0\u4E48\u8BED\u8A00\uFF1A\u5E2E\u52A9\u3001\u72B6\u6001\u3001\u786E\u8BA4\u548C\u5404\u79CD\u63D0\u793A\u3002\u4E2D\u6587\u5FEB\u6377\u547D\u4EE4\u53EA\u5728\u9009\u4E2D\u6587\u65F6\u8BA4\u3002\u4FDD\u5B58\u540E\u63D2\u4EF6\u4F1A\u91CD\u542F\u3002",
126822
+ zhCN: "\u4E2D\u6587",
126823
+ en: "English"
126824
+ },
126825
+ reportOnly: "\u53EA\u62A5\u4E0D\u6536\uFF0C\u62A5\u6240\u6709\u9879\u76EE",
126826
+ archivedSuffix: "\uFF08\u5DF2\u5F52\u6863\uFF09",
126827
+ runtimeMode: {
126828
+ approvalRequired: "\u547D\u4EE4\u548C\u6539\u6587\u4EF6\u524D\u5148\u95EE",
126829
+ autoAcceptEdits: "\u81EA\u52A8\u63A5\u53D7\u6539\u52A8\uFF0C\u5176\u4ED6\u64CD\u4F5C\u5148\u95EE",
126830
+ auto: "\u5E38\u89C4\u64CD\u4F5C\u81EA\u52A8\u6279\u51C6",
126831
+ fullAccess: "\u547D\u4EE4\u548C\u6539\u52A8\u90FD\u4E0D\u95EE"
126832
+ },
126833
+ notifyMode: {
126834
+ turn: "\u6BCF\u8F6E\u6536\u5C3E\u53D1\u4E00\u6761",
126835
+ message: "agent \u8BF4\u5B8C\u4E00\u53E5\u5C31\u53D1\u4E00\u53E5"
126836
+ }
126837
+ },
126838
+ platform: {
126839
+ lark: "\u98DE\u4E66",
126840
+ telegram: "Telegram",
126841
+ wecom: "\u4F01\u4E1A\u5FAE\u4FE1"
126842
+ },
126843
+ column: {
126844
+ displayName: {
126845
+ label: "\u540D\u79F0",
126846
+ placeholder: "\u56E2\u961F\u7FA4"
126847
+ },
126848
+ platform: {
126849
+ label: "\u5E73\u53F0"
126850
+ },
126851
+ larkAppId: {
126852
+ label: "\u98DE\u4E66\u5E94\u7528 ID"
126853
+ },
126854
+ larkAppSecret: {
126855
+ label: "\u98DE\u4E66\u5E94\u7528\u5BC6\u94A5"
126856
+ },
126857
+ telegramToken: {
126858
+ label: "Telegram \u673A\u5668\u4EBA\u4EE4\u724C"
126859
+ },
126860
+ wecomBotId: {
126861
+ label: "\u4F01\u4E1A\u5FAE\u4FE1\u673A\u5668\u4EBA ID"
126862
+ },
126863
+ wecomBotSecret: {
126864
+ label: "\u4F01\u4E1A\u5FAE\u4FE1\u673A\u5668\u4EBA\u5BC6\u94A5"
126865
+ },
126866
+ chatId: {
126867
+ label: "\u7FA4 id",
126868
+ description: "\u8BE5\u5E73\u53F0\u81EA\u5DF1\u7ED9\u8FD9\u4E2A\u7FA4\u7684 id\uFF0C\u673A\u5668\u4EBA\u5F80\u8FD9\u91CC\u53D1\u3002"
126869
+ },
126870
+ allowedSenders: {
126871
+ label: "\u8C01\u80FD\u6307\u6325\u673A\u5668\u4EBA",
126872
+ description: "\u8BE5\u5E73\u53F0\u7684\u7528\u6237 id\uFF0C\u9017\u53F7\u5206\u9694\u3002\u7559\u7A7A\u8868\u793A\u7FA4\u91CC\u4EFB\u4F55\u4EBA\u90FD\u80FD\u8BA9 agent \u5728\u8FD9\u53F0\u673A\u5668\u4E0A\u8DD1\u4E1C\u897F\u3002"
126873
+ },
126874
+ proxy: {
126875
+ label: "\u4EE3\u7406",
126876
+ description: "\u6709\u4E9B\u5E73\u53F0\u53EA\u80FD\u7ECF\u672C\u673A\u4E0A\u7684\u67D0\u4E2A\u4EE3\u7406\u624D\u8FDE\u5F97\u4E0A\uFF0C\u800C\u4E14\u6BCF\u4E00\u884C\u53EF\u4EE5\u4E0D\u4E00\u6837\u3002"
126877
+ },
126878
+ projectId: {
126879
+ label: "\u9879\u76EE",
126880
+ description: "\u8FD9\u4E2A\u7FA4\u91CC\u7684\u6D88\u606F\u5728\u54EA\u4E2A\u9879\u76EE\u91CC\u5F00\u4F1A\u8BDD\u3002\u7559\u7A7A\u8868\u793A\u8FD9\u4E00\u884C\u53EA\u62A5\u4E0D\u6536\uFF0C\u5E76\u4E14\u62A5\u6240\u6709\u9879\u76EE\u3002"
126881
+ },
126882
+ runtimeMode: {
126883
+ label: "\u804A\u5929\u6D88\u606F\u80FD\u505A\u4EC0\u4E48",
126884
+ description: "\u4EFB\u4F55\u80FD @ \u5230\u673A\u5668\u4EBA\u7684\u4EBA\u90FD\u80FD\u5F00\u4E00\u8F6E\uFF0C\u6240\u4EE5\u9ED8\u8BA4\u6545\u610F\u4E0D\u7ED9\u6EE1\u6743\u9650\u3002\u4E0D\u8BBA\u9009\u54EA\u4E00\u6863\uFF0C\u8981\u62CD\u677F\u7684\u4E8B\u90FD\u4F1A\u6446\u5230\u7FA4\u91CC\u3002"
126885
+ },
126886
+ mode: {
126887
+ label: "\u53D1\u591A\u5C11",
126888
+ description: "\u4E00\u8F6E\u6D3B\u513F\u53EF\u80FD\u4EA7\u751F\u51E0\u5341\u6761\u52A9\u624B\u6D88\u606F\uFF0C\u5927\u591A\u662F\u5DE5\u5177\u8C03\u7528\u4E4B\u95F4\u7684\u788E\u788E\u5FF5\uFF0C\u6240\u4EE5\u9ED8\u8BA4\u6BCF\u8F6E\u6536\u5C3E\u53EA\u53D1\u4E00\u6761\uFF0C\u8BFB\u8D77\u6765\u624D\u4E0D\u7D2F\u3002"
126889
+ }
126890
+ },
126891
+ reply: {
126892
+ notAllowed: "\u4F60\u4E0D\u5728\u8FD9\u4E2A\u7FA4\u7684\u53EF\u6307\u6325\u540D\u5355\u91CC\uFF0C\u8FD9\u6B21\u70B9\u51FB\u6CA1\u6709\u751F\u6548\u3002",
126893
+ noDefaultModel: "\u9879\u76EE\u300C{{title}}\u300D\u8FD8\u6CA1\u6709\u9ED8\u8BA4\u6A21\u578B\uFF0C\u5F00\u4E0D\u4E86\u4F1A\u8BDD\u3002\u5148\u5728 Ycodium \u91CC\u7ED9\u5B83\u9009\u4E00\u4E2A\u9ED8\u8BA4\u6A21\u578B\uFF0C\u518D\u56DE\u6765\u53D1\u6D88\u606F\u3002",
126894
+ projectArchived: "\u9879\u76EE\u300C{{title}}\u300D\u5DF2\u5F52\u6863\uFF0C\u6062\u590D\u540E\u518D\u53D1\u6D88\u606F\u3002",
126895
+ projectMissing: "\u300C{{group}}\u300D\u7ED1\u7684\u9879\u76EE\u5DF2\u7ECF\u4E0D\u5728\u8FD9\u53F0\u673A\u5668\u4E0A\u4E86\uFF08\u53EF\u80FD\u88AB\u5220\u4E86\uFF09\uFF0C\u5F00\u4E0D\u4E86\u4F1A\u8BDD\u3002\u5230 Ycodium \u7684\u804A\u5929\u8BBE\u7F6E\u91CC\u7ED9\u8FD9\u4E2A\u7FA4\u91CD\u65B0\u9009\u4E00\u4E2A\u9879\u76EE\u3002",
126896
+ threadGone: "\u4E4B\u524D\u90A3\u6761\u4F1A\u8BDD\u5DF2\u88AB\u5220\u9664\u6216\u5F52\u6863\uFF0C\u8FD9\u4E2A\u7FA4\u6539\u4E3A\u5F00\u4E00\u6761\u65B0\u4F1A\u8BDD\u3002",
126897
+ teamGone: "\u8FD9\u4E2A\u7FA4\u539F\u6765\u6D3E\u7ED9\u7684\u961F\u5DF2\u7ECF\u4E0D\u5728\u4E86\uFF0C\u63A5\u4E0B\u6765\u6539\u56DE\u666E\u901A\u4F1A\u8BDD\u3002"
126898
+ },
126899
+ approval: {
126900
+ allow: "\u5141\u8BB8",
126901
+ deny: "\u62D2\u7EDD",
126902
+ alreadyHandled: "\u5DF2\u5904\u7406",
126903
+ replyHint: "\u56DE\u590D\u7F16\u53F7\u5373\u53EF\u3002"
126904
+ },
126905
+ command: {
126906
+ teamSelected: "\u597D\uFF0C\u63A5\u4E0B\u6765\u6D3E\u7ED9\u300C{{title}}\u300D\u3002",
126907
+ threadSelected: "\u597D\uFF0C\u63A5\u4E0B\u6765\u5F00\u666E\u901A\u4F1A\u8BDD\u3002",
126908
+ teamNotFound: "\u6CA1\u627E\u5230\u53EB\u300C{{query}}\u300D\u7684\u961F\uFF0C\u4E5F\u53EF\u80FD\u662F\u4E0D\u6B62\u4E00\u652F\u5BF9\u5F97\u4E0A\u3002",
126909
+ noTeams: "\u8FD9\u53F0\u673A\u5668\u4E0A\u8FD8\u6CA1\u6709\u56E2\u961F\u3002",
126910
+ newSession: "\u597D\uFF0C\u4E0B\u4E00\u53E5\u8BDD\u5F00\u4E00\u6761\u65B0\u4F1A\u8BDD\u3002",
126911
+ stopNoSession: "\u8FD9\u4E2A\u7FA4\u73B0\u5728\u6CA1\u6709\u5728\u8DD1\u7684\u6D3B\u513F\u3002",
126912
+ stopNoTurn: "\u73B0\u5728\u6CA1\u6709\u5728\u8DD1\u7684\u4E00\u8F6E\u3002",
126913
+ stopped: "\u505C\u4E86\u3002\u4F1A\u8BDD\u8FD8\u5728\uFF0C\u63A5\u7740\u8BF4\u5C31\u884C\u3002"
126914
+ },
126915
+ status: {
126916
+ routedTeam: "\u6D3E\u7ED9\uFF1A{{title}}",
126917
+ routedThread: "\u666E\u901A\u4F1A\u8BDD",
126918
+ nothingRunning: "{{where}}\u3002\u73B0\u5728\u6CA1\u6709\u5728\u8DD1\u7684\u6D3B\u513F\u3002",
126919
+ session: "\u4F1A\u8BDD\uFF1A{{title}}",
126920
+ running: "\u6B63\u5728\u8DD1\u3002\u60F3\u505C\u5C31\u6253 /stop\u3002",
126921
+ idle: "\u7A7A\u95F2\u7740\u3002",
126922
+ model: "\u6A21\u578B\uFF1A{{model}}",
126923
+ planOn: "\u8BA1\u5212\u6A21\u5F0F\uFF1A\u5F00"
126924
+ },
126925
+ targets: {
126926
+ currentThread: "\u73B0\u5728\u8FD9\u4E2A\u7FA4\u5F00\u7684\u662F\u666E\u901A\u4F1A\u8BDD\u3002",
126927
+ currentTeam: "\u73B0\u5728\u6D3E\u7ED9\uFF1A{{title}}",
126928
+ noTeams: "\u8FD9\u53F0\u673A\u5668\u4E0A\u8FD8\u6CA1\u6709\u56E2\u961F\u3002",
126929
+ teamList: "\u53EF\u9009\u7684\u961F\uFF1A",
126930
+ hint: "\u7528 /team \u961F\u540D \u5207\u6362\uFF0C/thread \u5207\u56DE\u666E\u901A\u4F1A\u8BDD\u3002"
126931
+ },
126932
+ help: {
126933
+ title: "\u80FD\u7528\u7684\u547D\u4EE4\uFF1A",
126934
+ stop: "\xB7 /stop \u2014\u2014 \u505C\u6389\u6B63\u5728\u8DD1\u7684\u8FD9\u4E00\u8F6E\uFF0C\u4F1A\u8BDD\u7559\u7740",
126935
+ status: "\xB7 /status \u2014\u2014 \u73B0\u5728\u5728\u5E72\u4EC0\u4E48",
126936
+ new: "\xB7 /new \u2014\u2014 \u5FD8\u6389\u5F53\u524D\u4F1A\u8BDD\uFF0C\u4E0B\u4E00\u53E5\u5F00\u65B0\u7684",
126937
+ team: "\xB7 /team \u961F\u540D \u2014\u2014 \u628A\u63A5\u4E0B\u6765\u7684\u6D3B\u6D3E\u7ED9\u4E00\u652F\u961F\uFF1B/team \u4E0D\u5E26\u540D\u5B57\u770B\u6709\u54EA\u4E9B\u961F",
126938
+ thread: "\xB7 /thread \u2014\u2014 \u5207\u56DE\u666E\u901A\u4F1A\u8BDD",
126939
+ aliases: "\u4E2D\u6587\u4E5F\u884C\uFF1A\u505C / \u72B6\u6001 / \u65B0\u5BF9\u8BDD / \u4F1A\u8BDD\u5217\u8868 / \u5207\u6362\u4F1A\u8BDD / \u6362\u6A21\u578B / \u8BA1\u5212\u6A21\u5F0F / \u6362\u56E2\u961F / \u6362\u5BF9\u8BDD\u3002",
126940
+ sessions: "\xB7 /sessions \u2014\u2014 \u5217\u51FA\u672C\u673A\u5668\u4EBA\u4E3A\u8FD9\u4E2A\u7FA4\u7684\u9879\u76EE\u5F00\u8FC7\u7684\u4F1A\u8BDD",
126941
+ resume: "\xB7 /resume \u7F16\u53F7 \u2014\u2014 \u5207\u5230\u5176\u4E2D\u4E00\u6761\uFF08/sessions \u91CC\u7684\u7F16\u53F7\uFF0C\u6216\u4F1A\u8BDD id \u7684\u5F00\u5934\uFF09",
126942
+ model: "\xB7 /model \u2014\u2014 \u770B\u8FD9\u4E2A\u7FA4\u7528\u7684\u6A21\u578B\uFF1B/model \u7F16\u53F7 \u6216 /model \u5B9E\u4F8B/\u6A21\u578B \u5207\u6362",
126943
+ plan: "\xB7 /plan \u2014\u2014 \u5F00\u5173\u8BA1\u5212\u6A21\u5F0F\uFF08agent \u5148\u51FA\u8BA1\u5212\u3001\u4E0D\u52A8\u624B\uFF09\uFF1B/plan on\u3001/plan off \u660E\u786E\u8BBE\u5B9A"
126944
+ },
126945
+ sessions: {
126946
+ title: "\u672C\u673A\u5668\u4EBA\u4E3A\u8FD9\u4E2A\u7FA4\u7684\u9879\u76EE\u5F00\u8FC7\u7684\u4F1A\u8BDD\uFF08{{count}} \u6761\uFF09\uFF1A",
126947
+ empty: "\u672C\u673A\u5668\u4EBA\u8FD8\u6CA1\u6709\u4E3A\u8FD9\u4E2A\u7FA4\u7684\u9879\u76EE\u5F00\u8FC7\u4F1A\u8BDD\uFF0C\u53D1\u4E00\u53E5\u8BDD\u5C31\u4F1A\u5F00\u4E00\u6761\u3002",
126948
+ more: "\u8FD8\u6709 {{count}} \u6761\u6CA1\u5217\u51FA\u2014\u2014\u7528 /resume \u52A0\u4F1A\u8BDD id \u7684\u5F00\u5934\u4E5F\u80FD\u5207\u5230\u3002",
126949
+ footer: "\u7528 /resume \u7F16\u53F7 \u5207\u6362\uFF0C\u6216 /resume \u52A0\u4F1A\u8BDD id \u7684\u5F00\u5934\u3002",
126950
+ untitled: "\uFF08\u65E0\u6807\u9898\uFF09",
126951
+ unavailable: "\u73B0\u5728\u8BFB\u4E0D\u51FA\u4F1A\u8BDD\u5217\u8868\uFF0C\u7A0D\u540E\u518D\u8BD5\u3002",
126952
+ badge: {
126953
+ current: "\u2605 \u5F53\u524D",
126954
+ running: "\u25CF \u5728\u8DD1",
126955
+ elsewhere: "\u63A5\u5728\u522B\u7684\u7FA4"
126956
+ },
126957
+ age: {
126958
+ justNow: "\u521A\u521A",
126959
+ minutes: "{{count}} \u5206\u949F\u524D",
126960
+ hours: "{{count}} \u5C0F\u65F6\u524D",
126961
+ days: "{{count}} \u5929\u524D"
126962
+ }
126963
+ },
126964
+ resume: {
126965
+ notFound: "\u6CA1\u6709\u54EA\u6761\u4F1A\u8BDD\u5BF9\u5F97\u4E0A\u300C{{query}}\u300D\u3002\u7528 /sessions \u770B\u770B\u6709\u54EA\u4E9B\u3002",
126966
+ busy: "\u8FD9\u4E2A\u7FA4\u6709\u4E00\u8F6E\u6B63\u5728\u8DD1\u2014\u2014\u5148 /stop\uFF0C\u518D\u5207\u6362\u3002",
126967
+ already: "\u8FD9\u4E2A\u7FA4\u5DF2\u7ECF\u5728\u90A3\u6761\u4F1A\u8BDD\u91CC\u4E86\u3002",
126968
+ gone: "\u90A3\u6761\u4F1A\u8BDD\u5DF2\u88AB\u5220\u9664\u6216\u5F52\u6863\uFF0C\u63A5\u4E0D\u4E0A\u4E86\u3002",
126969
+ runningElsewhere: "\u90A3\u6761\u4F1A\u8BDD\u6B63\u5728\u522B\u7684\u7FA4\u91CC\u8DD1\u7740\u4E00\u8F6E\u2014\u2014\u5148\u5230\u90A3\u8FB9\u505C\u6389\u5B83\u3002",
126970
+ done: "\u5DF2\u5207\u5230\u300C{{title}}\u300D\u3002\u53D1\u4E00\u53E5\u8BDD\u5C31\u4ECE\u90A3\u91CC\u63A5\u7740\u804A\u3002"
126971
+ },
126972
+ model: {
126973
+ current: "\u6A21\u578B\uFF1A{{model}}",
126974
+ currentUnknown: "\u6A21\u578B\uFF1A\u8FD8\u6CA1\u5B9A\u2014\u2014\u65B0\u5F00\u4F1A\u8BDD\u65F6\u7528\u9879\u76EE\u7684\u9ED8\u8BA4\u6A21\u578B\u3002",
126975
+ unavailable: "\u73B0\u5728\u8BFB\u4E0D\u51FA\u53EF\u9009\u7684\u6A21\u578B\uFF0C\u6CA1\u6CD5\u5207\u6362\u6A21\u578B\u3002",
126976
+ listTitle: "\u53EF\u9009\u7684\u6A21\u578B\uFF1A",
126977
+ more: "\u8FD8\u6709 {{count}} \u4E2A\u6CA1\u5217\u51FA\u2014\u2014\u7528 /model \u5B9E\u4F8B/\u6A21\u578B \u4E5F\u80FD\u9009\u5230\u3002",
126978
+ footer: "\u7528 /model \u7F16\u53F7 \u5207\u6362\uFF0C\u6216 /model \u5B9E\u4F8B/\u6A21\u578B\u3002",
126979
+ notFound: "\u6CA1\u6709\u552F\u4E00\u5BF9\u5F97\u4E0A\u300C{{query}}\u300D\u7684\u6A21\u578B\u3002\u7528 /model \u770B\u770B\u6709\u54EA\u4E9B\u3002",
126980
+ setNow: "\u6A21\u578B\u5DF2\u8BBE\u4E3A {{model}}\uFF0C\u5F53\u524D\u4F1A\u8BDD\u4ECE\u4E0B\u4E00\u8F6E\u8D77\u7528\u5B83\u3002",
126981
+ setNext: "\u6A21\u578B\u5DF2\u8BBE\u4E3A {{model}}\uFF0C\u4E0B\u4E00\u6761\u65B0\u4F1A\u8BDD\u8D77\u7528\u5B83\u3002",
126982
+ setFailed: "\u6CA1\u80FD\u5207\u6362\u5F53\u524D\u4F1A\u8BDD\u7684\u6A21\u578B\uFF0C\u7A0D\u540E\u518D\u8BD5\u3002"
126983
+ },
126984
+ plan: {
126985
+ on: "\u8BA1\u5212\u6A21\u5F0F\u5DF2\u5F00\uFF1A\u4ECE\u4E0B\u4E00\u6761\u6D88\u606F\u8D77\uFF0Cagent \u5148\u51FA\u8BA1\u5212\u3001\u4E0D\u52A8\u624B\u3002\u53D1 /plan off \u9000\u51FA\u3002",
126986
+ off: "\u8BA1\u5212\u6A21\u5F0F\u5DF2\u5173\uFF0C\u6D88\u606F\u7167\u5E38\u5904\u7406\u3002",
126987
+ alreadyOn: "\u8BA1\u5212\u6A21\u5F0F\u5DF2\u7ECF\u662F\u5F00\u7740\u7684\u3002",
126988
+ alreadyOff: "\u8BA1\u5212\u6A21\u5F0F\u5DF2\u7ECF\u662F\u5173\u7740\u7684\u3002",
126989
+ usage: "\u7528\u6CD5\uFF1A/plan \u5207\u6362\u8BA1\u5212\u6A21\u5F0F\uFF1B/plan on\u3001/plan off \u660E\u786E\u8BBE\u5B9A\u3002"
126990
+ }
126991
+ };
126992
+
126993
+ // src/messages.ts
126994
+ var DEFAULT_REPLY_LANGUAGE = "zh-CN";
126995
+ var parseReplyLanguage = (value) => value === "en" ? "en" : DEFAULT_REPLY_LANGUAGE;
126996
+ var CATALOGS = { en: en_default, "zh-CN": zh_CN_default };
126997
+ var lookup = (catalog, key) => {
126998
+ let node = catalog;
126999
+ for (const segment of key.split(".")) {
127000
+ if (typeof node !== "object") return void 0;
127001
+ node = node[segment];
127002
+ }
127003
+ return typeof node === "string" ? node : void 0;
127004
+ };
127005
+ var interpolate = (template, params) => params === void 0 ? template : template.replace(/{{\s*([\w.]+)\s*}}/g, (whole, name) => {
127006
+ const value = params[name];
127007
+ return value === void 0 ? whole : String(value);
127008
+ });
127009
+ var makeTranslate = (language) => {
127010
+ const catalog = CATALOGS[language];
127011
+ const optional = (key, params) => {
127012
+ const text = lookup(catalog, key);
127013
+ return text === void 0 ? void 0 : interpolate(text, params);
127014
+ };
127015
+ return Object.assign(
127016
+ (key, params) => optional(key, params) ?? key,
127017
+ { optional, language }
127018
+ );
127019
+ };
127020
+
127021
+ // src/modelChoice.ts
127022
+ var MODEL_LIST_MAX = 30;
127023
+ var modelChoices = (instances) => instances.flatMap(
127024
+ (instance) => instance.enabled ? (instance.models ?? []).map(
127025
+ (model) => ({
127026
+ instanceId: instance.instanceId,
127027
+ model: model.slug,
127028
+ instanceName: instance.displayName ?? instance.instanceId,
127029
+ modelName: model.name === "" ? model.slug : model.name
127030
+ })
127031
+ ) : []
127032
+ );
127033
+ var modelLabel = (choice) => `${choice.instanceName} \xB7 ${choice.modelName}`;
127034
+ var rawModelLabel = (selection) => `${selection.instanceId} \xB7 ${selection.model}`;
127035
+ var sameModel = (left, right) => left.instanceId === right.instanceId && left.model === right.model;
127036
+ var formatModels = (current, choices, t) => {
127037
+ const currentLabel = current === null ? t("model.currentUnknown") : t("model.current", {
127038
+ model: modelLabel(
127039
+ choices.find((choice) => sameModel(choice, current)) ?? {
127040
+ instanceName: current.instanceId,
127041
+ modelName: current.model
127042
+ }
127043
+ )
127044
+ });
127045
+ if (choices.length === 0) return [currentLabel, t("model.unavailable")].join("\n");
127046
+ const shown = choices.slice(0, MODEL_LIST_MAX);
127047
+ return [
127048
+ currentLabel,
127049
+ "",
127050
+ t("model.listTitle"),
127051
+ ...shown.map(
127052
+ (choice, index) => `${String(index + 1)}. ${current !== null && sameModel(choice, current) ? "\u2605 " : ""}${modelLabel(choice)}`
127053
+ ),
127054
+ ...choices.length > shown.length ? [t("model.more", { count: choices.length - shown.length })] : [],
127055
+ "",
127056
+ t("model.footer")
127057
+ ].join("\n");
127058
+ };
127059
+ var pickModel = (arg, choices) => {
127060
+ const wanted = arg.trim();
127061
+ if (wanted === "") return null;
127062
+ if (/^\d+$/.test(wanted)) return choices.slice(0, MODEL_LIST_MAX)[Number(wanted) - 1] ?? null;
127063
+ const needle = wanted.toLowerCase();
127064
+ const qualified = choices.filter(
127065
+ (choice) => `${choice.instanceId}/${choice.model}`.toLowerCase() === needle || `${choice.instanceId}/${choice.modelName}`.toLowerCase() === needle
127066
+ );
127067
+ if (qualified.length === 1) return qualified[0] ?? null;
127068
+ if (qualified.length > 1) return null;
127069
+ const named = choices.filter(
127070
+ (choice) => choice.model.toLowerCase() === needle || choice.modelName.toLowerCase() === needle
127071
+ );
127072
+ return named.length === 1 ? named[0] ?? null : null;
127073
+ };
127074
+
126458
127075
  // src/projectArchive.ts
126459
- var imProjectOptions = (projects, boundIds) => projects.flatMap((project) => {
127076
+ var imProjectOptions = (projects, boundIds, archivedSuffix) => projects.flatMap((project) => {
126460
127077
  if (project.archivedAt == null) return [{ value: project.projectId, label: project.title }];
126461
- return boundIds.has(project.projectId) ? [{ value: project.projectId, label: `${project.title} (archived)` }] : [];
127078
+ return boundIds.has(project.projectId) ? [{ value: project.projectId, label: `${project.title} ${archivedSuffix}` }] : [];
126462
127079
  });
126463
- var archivedProjectReply = (title) => `\u9879\u76EE\u300C${title}\u300D\u5DF2\u5F52\u6863\uFF0C\u6062\u590D\u540E\u518D\u53D1\u6D88\u606F\u3002`;
126464
- var missingProjectReply = (group) => `\u300C${group}\u300D\u7ED1\u7684\u9879\u76EE\u5DF2\u7ECF\u4E0D\u5728\u8FD9\u53F0\u673A\u5668\u4E0A\u4E86\uFF08\u53EF\u80FD\u88AB\u5220\u4E86\uFF09\uFF0C\u5F00\u4E0D\u4E86\u4F1A\u8BDD\u3002\u5230 Ycodium \u7684 IM \u8BBE\u7F6E\u91CC\u7ED9\u8FD9\u4E2A\u7FA4\u91CD\u65B0\u9009\u4E00\u4E2A\u9879\u76EE\u3002`;
127080
+
127081
+ // src/sessionList.ts
127082
+ var SESSION_LIST_MAX = 20;
127083
+ var SESSION_ID_SHORT_LENGTH = 8;
127084
+ var ageLabel = (updatedAt, now, t) => {
127085
+ const at = Date.parse(updatedAt);
127086
+ if (Number.isNaN(at)) return "";
127087
+ const minutes = Math.floor(Math.max(0, now - at) / 6e4);
127088
+ if (minutes < 1) return t("sessions.age.justNow");
127089
+ if (minutes < 60) return t("sessions.age.minutes", { count: minutes });
127090
+ const hours = Math.floor(minutes / 60);
127091
+ if (hours < 24) return t("sessions.age.hours", { count: hours });
127092
+ return t("sessions.age.days", { count: Math.floor(hours / 24) });
127093
+ };
127094
+ var shortId = (threadId) => threadId.slice(0, SESSION_ID_SHORT_LENGTH);
127095
+ var sessionLine = (row, position, now, t) => {
127096
+ const title = row.title.replace(/\s+/g, " ").trim() || t("sessions.untitled");
127097
+ const age = ageLabel(row.updatedAt, now, t);
127098
+ const badges = [
127099
+ row.current ? t("sessions.badge.current") : null,
127100
+ row.running ? t("sessions.badge.running") : null,
127101
+ row.elsewhere ? t("sessions.badge.elsewhere") : null
127102
+ ].filter((badge) => badge !== null);
127103
+ return [`${String(position)}. ${title}`, age, ...badges, shortId(row.threadId)].filter((part) => part !== "").join(" \xB7 ");
127104
+ };
127105
+ var formatSessions = (rows, now, t) => {
127106
+ if (rows.length === 0) return t("sessions.empty");
127107
+ const shown = rows.slice(0, SESSION_LIST_MAX);
127108
+ return [
127109
+ t("sessions.title", { count: rows.length }),
127110
+ ...shown.map((row, index) => sessionLine(row, index + 1, now, t)),
127111
+ ...rows.length > shown.length ? [t("sessions.more", { count: rows.length - shown.length })] : [],
127112
+ "",
127113
+ t("sessions.footer")
127114
+ ].join("\n");
127115
+ };
127116
+ var listedThreadIds = (rows) => rows.slice(0, SESSION_LIST_MAX).map((row) => row.threadId);
127117
+ var pickSession = (arg, listed, rows) => {
127118
+ const wanted = arg.trim();
127119
+ if (wanted === "") return null;
127120
+ if (/^\d+$/.test(wanted)) {
127121
+ const index = Number(wanted);
127122
+ const ids = listed ?? listedThreadIds(rows);
127123
+ return ids[index - 1] ?? null;
127124
+ }
127125
+ const needle = wanted.toLowerCase();
127126
+ const matches = rows.filter((row) => row.threadId.toLowerCase().startsWith(needle));
127127
+ return matches.length === 1 ? matches[0]?.threadId ?? null : null;
127128
+ };
126465
127129
 
126466
127130
  // src/telegramApi.ts
126467
127131
  var API_BASE = "https://api.telegram.org";
@@ -127009,7 +127673,7 @@ var makeWecomAdapter = (deps) => {
127009
127673
  "",
127010
127674
  ...request.options.map((option, index) => `${String(index + 1)}. ${option.label}`),
127011
127675
  "",
127012
- "\u56DE\u590D\u7F16\u53F7\u5373\u53EF\u3002"
127676
+ request.replyHint
127013
127677
  ];
127014
127678
  await (clients.get(binding.rowId)?.sendText(binding.chatId, lines.join("\n")) ?? Promise.resolve());
127015
127679
  },
@@ -127022,13 +127686,13 @@ var makeWecomAdapter = (deps) => {
127022
127686
 
127023
127687
  // src/index.ts
127024
127688
  var GROUPS_FIELD = "groups";
127689
+ var REPLY_LANGUAGE_FIELD = "replyLanguage";
127025
127690
  var SEEN_MESSAGE_LIMIT = 256;
127026
127691
  var THREAD_PROJECT_CACHE_LIMIT = 256;
127027
127692
  var TITLE_MAX_CHARS = 72;
127028
127693
  var DEFAULT_PLATFORM = "lark";
127694
+ var SESSION_SCAN_LIMIT = 50;
127029
127695
  var ARCHIVED_REPLY_COOLDOWN_MS = 10 * 6e4;
127030
- var NOT_ALLOWED_NOTICE = "\u4F60\u4E0D\u5728\u8FD9\u4E2A\u7FA4\u7684\u53EF\u6307\u6325\u540D\u5355\u91CC\uFF0C\u8FD9\u6B21\u70B9\u51FB\u6CA1\u6709\u751F\u6548\u3002";
127031
- var noDefaultModelReply = (title) => `\u9879\u76EE\u300C${title}\u300D\u8FD8\u6CA1\u6709\u9ED8\u8BA4\u6A21\u578B\uFF0C\u5F00\u4E0D\u4E86\u4F1A\u8BDD\u3002\u5148\u5728 Ycodium \u91CC\u7ED9\u5B83\u9009\u4E00\u4E2A\u9ED8\u8BA4\u6A21\u578B\uFF0C\u518D\u56DE\u6765\u53D1\u6D88\u606F\u3002`;
127032
127696
  var INBOUND_RUNTIME_MODES = /* @__PURE__ */ new Set([
127033
127697
  "approval-required",
127034
127698
  "auto-accept-edits",
@@ -127047,6 +127711,7 @@ var imPlugin = definePlugin({
127047
127711
  id: "im",
127048
127712
  apiVersion: 2,
127049
127713
  activate: async (ctx) => {
127714
+ const t = makeTranslate(parseReplyLanguage(ctx.config[REPLY_LANGUAGE_FIELD]));
127050
127715
  const adapters = [
127051
127716
  makeLarkAdapter({
127052
127717
  http: ctx.http,
@@ -127092,84 +127757,99 @@ var imPlugin = definePlugin({
127092
127757
  const platformColumn = {
127093
127758
  key: "platform",
127094
127759
  kind: "select",
127095
- label: "Platform",
127760
+ label: t("column.platform.label"),
127096
127761
  options: adapters.map((adapter) => ({
127097
127762
  value: adapter.platform,
127098
- label: adapter.label,
127763
+ // 平台名在目录里有本地叫法(飞书、企业微信);没有就用适配器自己的名字。
127764
+ label: t.optional(`platform.${adapter.platform}`) ?? adapter.label,
127099
127765
  icon: adapter.icon
127100
127766
  }))
127101
127767
  };
127102
127768
  const credentialColumns = adapters.flatMap(
127103
127769
  (adapter) => adapter.columns.map((column) => ({
127104
127770
  ...column,
127771
+ // 凭据列的字也走目录;适配器自己写的英文只是目录里没有这个键时的回落。
127772
+ label: t.optional(`column.${column.key}.label`) ?? column.label,
127105
127773
  showWhen: { key: platformColumn.key, equals: [adapter.platform] }
127106
127774
  }))
127107
127775
  );
127108
127776
  ctx.settingsCard({
127109
- title: "Chat",
127110
- description: "Connect chat groups to Ycodium. One row per group: create a bot on that platform, add it to the group, and paste its credentials. Pick a project and that group can start work; leave the project empty and the row only reports, on every project.",
127777
+ title: t("settings.title"),
127778
+ description: t("settings.description"),
127111
127779
  fields: [
127780
+ {
127781
+ key: REPLY_LANGUAGE_FIELD,
127782
+ kind: "select",
127783
+ label: t("settings.language.label"),
127784
+ description: t("settings.language.description"),
127785
+ options: [
127786
+ { value: "zh-CN", label: t("settings.language.zhCN") },
127787
+ { value: "en", label: t("settings.language.en") }
127788
+ ]
127789
+ },
127112
127790
  {
127113
127791
  key: GROUPS_FIELD,
127114
127792
  kind: "table",
127115
- label: "Groups",
127793
+ label: t("settings.groups"),
127116
127794
  columns: [
127117
- { key: "displayName", kind: "text", label: "Name", placeholder: "Team chat" },
127795
+ {
127796
+ key: "displayName",
127797
+ kind: "text",
127798
+ label: t("column.displayName.label"),
127799
+ placeholder: t("column.displayName.placeholder")
127800
+ },
127118
127801
  platformColumn,
127119
127802
  ...credentialColumns,
127120
127803
  {
127121
127804
  key: "chatId",
127122
127805
  kind: "text",
127123
- label: "Group id",
127124
- description: "The platform's own id for the group to post in."
127806
+ label: t("column.chatId.label"),
127807
+ description: t("column.chatId.description")
127125
127808
  },
127126
127809
  {
127127
127810
  key: "allowedSenders",
127128
127811
  kind: "text",
127129
- label: "Who may command the bot",
127130
- description: "Comma-separated user ids from that platform. Empty means anyone in the group can make the agent run things on this machine."
127812
+ label: t("column.allowedSenders.label"),
127813
+ description: t("column.allowedSenders.description")
127131
127814
  },
127132
127815
  {
127133
127816
  // 选项由宿主填,连三态一起——插件只声明「这个格子装的是代理」,宿主因此
127134
127817
  // 也就认得出这一行引用了哪个代理,删代理时拦得住(路线图 #64)。
127135
127818
  key: "proxy",
127136
127819
  kind: "proxy",
127137
- label: "Proxy",
127138
- description: "Some platforms are only reachable through a proxy on this machine, and which one can differ per row."
127820
+ label: t("column.proxy.label"),
127821
+ description: t("column.proxy.description")
127139
127822
  },
127140
127823
  {
127141
127824
  key: "projectId",
127142
127825
  kind: "select",
127143
- label: "Project",
127144
- description: "Where a message from this group starts its thread. Empty means the row only reports, and reports every project.",
127826
+ label: t("column.projectId.label"),
127827
+ description: t("column.projectId.description"),
127145
127828
  options: [
127146
- { value: "", label: "Report only, all projects" },
127147
- ...imProjectOptions(projects, boundProjectIds)
127829
+ { value: "", label: t("settings.reportOnly") },
127830
+ ...imProjectOptions(projects, boundProjectIds, t("settings.archivedSuffix"))
127148
127831
  ]
127149
127832
  },
127150
127833
  {
127151
127834
  key: "runtimeMode",
127152
127835
  kind: "select",
127153
- label: "What a chat message may do",
127154
- description: "Anyone who can mention the bot starts a turn, so this is deliberately not full access by default. Whichever mode asks, the request lands in the group.",
127836
+ label: t("column.runtimeMode.label"),
127837
+ description: t("column.runtimeMode.description"),
127155
127838
  options: [
127156
- { value: "approval-required", label: "Ask before commands and file changes" },
127157
- {
127158
- value: "auto-accept-edits",
127159
- label: "Auto-accept edits, ask before other actions"
127160
- },
127161
- { value: "auto", label: "Approve routine actions" },
127162
- { value: "full-access", label: "Commands and edits without prompts" }
127839
+ { value: "approval-required", label: t("settings.runtimeMode.approvalRequired") },
127840
+ { value: "auto-accept-edits", label: t("settings.runtimeMode.autoAcceptEdits") },
127841
+ { value: "auto", label: t("settings.runtimeMode.auto") },
127842
+ { value: "full-access", label: t("settings.runtimeMode.fullAccess") }
127163
127843
  ]
127164
127844
  },
127165
127845
  {
127166
127846
  key: "mode",
127167
127847
  kind: "select",
127168
- label: "How much to send",
127169
- description: "A single turn can produce dozens of assistant messages \u2014 mostly commentary between tool calls \u2014 so one per finished turn is the readable default.",
127848
+ label: t("column.mode.label"),
127849
+ description: t("column.mode.description"),
127170
127850
  options: [
127171
- { value: "turn", label: "One message per finished turn" },
127172
- { value: "message", label: "Every reply the agent completes" }
127851
+ { value: "turn", label: t("settings.notifyMode.turn") },
127852
+ { value: "message", label: t("settings.notifyMode.message") }
127173
127853
  ]
127174
127854
  }
127175
127855
  ]
@@ -127352,65 +128032,90 @@ ${final.text}`, final.attachments);
127352
128032
  await deliver(binding, event.summary);
127353
128033
  }
127354
128034
  });
127355
- const threadByChat = /* @__PURE__ */ new Map();
127356
128035
  const seen = /* @__PURE__ */ new Set();
127357
128036
  const seenOrder = [];
127358
- const answersByToken = /* @__PURE__ */ new Map();
127359
- const openOptionsByChat = /* @__PURE__ */ new Map();
127360
- const answerByToken = async (token) => {
127361
- const answer = answersByToken.get(token);
127362
- if (answer === void 0) return;
127363
- for (const [key, value] of answersByToken) {
127364
- if (value.requestId === answer.requestId) answersByToken.delete(key);
127365
- }
127366
- for (const [chatKey, options] of openOptionsByChat) {
127367
- if (options.some((option) => option.token === token)) openOptionsByChat.delete(chatKey);
128037
+ let state = emptyState();
128038
+ const stateHandle = ctx.derivedState({
128039
+ key: IM_STATE_KEY,
128040
+ version: IM_STATE_VERSION,
128041
+ events: ["plugin.event-appended"],
128042
+ init: emptyState,
128043
+ apply: foldPluginEvent(ctx.id)
128044
+ });
128045
+ const ready = stateHandle.get().then(
128046
+ (restored) => {
128047
+ state = restored;
128048
+ },
128049
+ (error) => {
128050
+ ctx.log.warn("chat could not restore its saved state", { error: describeError(error) });
127368
128051
  }
128052
+ );
128053
+ let persisting = Promise.resolve();
128054
+ const commit = (name, data) => {
128055
+ state = foldImEvent(state, name, data);
128056
+ persisting = persisting.then(() => ctx.events.append({ name, aggregate: { kind: "plugin", id: ctx.id }, data })).then(
128057
+ () => void 0,
128058
+ (error) => {
128059
+ ctx.log.warn("chat could not save its state", { name, error: describeError(error) });
128060
+ }
128061
+ );
128062
+ };
128063
+ const bindChat = (chatKey, threadId) => {
128064
+ commit("chat-updated", { chatKey, threadId });
128065
+ };
128066
+ const answerApproval = async (approval, decision) => {
128067
+ commit("approval-resolved", { requestId: approval.requestId });
127369
128068
  await ctx.commands.dispatch({
127370
128069
  type: "thread.approval.respond",
127371
128070
  commandId: crypto.randomUUID(),
127372
- threadId: answer.threadId,
127373
- requestId: answer.requestId,
127374
- decision: answer.decision,
128071
+ threadId: approval.threadId,
128072
+ requestId: approval.requestId,
128073
+ decision,
127375
128074
  createdAt: (/* @__PURE__ */ new Date()).toISOString()
127376
128075
  });
127377
- ctx.log.info("chat answered an approval", {
127378
- threadId: answer.threadId,
127379
- decision: answer.decision
127380
- });
128076
+ ctx.log.info("chat answered an approval", { threadId: approval.threadId, decision });
128077
+ };
128078
+ const answerByToken = async (token) => {
128079
+ const found = findApprovalByToken(state, token);
128080
+ if (found === void 0 || found.approval.resolved) return;
128081
+ await answerApproval(found.approval, found.button.decision);
127381
128082
  };
127382
128083
  ctx.events.on("thread.approval-pending", async (event) => {
127383
128084
  const threadId = event.threadId;
127384
128085
  if (threadId === null) return;
128086
+ await ready;
127385
128087
  const binding = readBindings().find(
127386
- (candidate) => threadByChat.get(chatKeyOf(candidate)) === threadId
128088
+ (candidate) => chatOf(state, chatKeyOf(candidate)).threadId === threadId
127387
128089
  );
127388
128090
  if (binding === void 0) return;
127389
128091
  const adapter = adapterFor(binding.platform);
127390
128092
  if (adapter === void 0) return;
127391
128093
  const pending = await ctx.projections.listPendingApprovals(threadId);
127392
- const approval = pending[0];
128094
+ const approval = pending.find(
128095
+ (candidate) => state.approvals[candidate.requestId] === void 0
128096
+ );
127393
128097
  if (approval === void 0) return;
127394
128098
  const options = approval.options.length > 0 ? approval.options : [
127395
- { decision: "accept", label: "Allow" },
127396
- { decision: "decline", label: "Deny" }
128099
+ { decision: "accept", label: t("approval.allow") },
128100
+ { decision: "decline", label: t("approval.deny") }
127397
128101
  ];
127398
- const buttons = options.map((option) => {
127399
- const token = crypto.randomUUID().slice(0, 8);
127400
- answersByToken.set(token, {
127401
- threadId,
127402
- requestId: approval.requestId,
127403
- decision: option.decision,
127404
- chatKey: chatKeyOf(binding)
127405
- });
127406
- return { label: option.label, token };
128102
+ const buttons = options.map((option) => ({
128103
+ token: crypto.randomUUID().slice(0, 8),
128104
+ label: option.label,
128105
+ decision: option.decision
128106
+ }));
128107
+ commit("approval-offered", {
128108
+ chatKey: chatKeyOf(binding),
128109
+ threadId,
128110
+ requestId: approval.requestId,
128111
+ options: buttons
127407
128112
  });
127408
- openOptionsByChat.set(chatKeyOf(binding), buttons);
127409
128113
  try {
127410
128114
  await adapter.askApproval(binding, {
127411
128115
  title: approval.appName ?? "Ycodium",
127412
128116
  body: approval.detail ?? event.summary,
127413
- options: buttons
128117
+ options: buttons.map(({ label, token }) => ({ label, token })),
128118
+ replyHint: t("approval.replyHint")
127414
128119
  });
127415
128120
  ctx.log.info("chat asked for an approval", { threadId, requestId: approval.requestId });
127416
128121
  } catch (error) {
@@ -127419,17 +128124,20 @@ ${final.text}`, final.attachments);
127419
128124
  });
127420
128125
  const isAllowedSender = (binding, sender) => binding.allowedSenders.size === 0 || sender !== null && binding.allowedSenders.has(sender);
127421
128126
  const handleApprovalClick = (token, operatorId) => {
127422
- const answer = answersByToken.get(token);
127423
- if (answer === void 0) return null;
127424
- const target = readBindings().find((binding) => chatKeyOf(binding) === answer.chatKey);
128127
+ const found = findApprovalByToken(state, token);
128128
+ if (found === void 0) return null;
128129
+ if (found.approval.resolved) return t("approval.alreadyHandled");
128130
+ const target = readBindings().find(
128131
+ (binding) => chatKeyOf(binding) === found.approval.chatKey
128132
+ );
127425
128133
  if (target === void 0 || !isAllowedSender(target, operatorId)) {
127426
128134
  ctx.log.warn("chat ignored an approval click from someone not on the allow list", {
127427
- group: target?.displayName ?? answer.chatKey,
128135
+ group: target?.displayName ?? found.approval.chatKey,
127428
128136
  operator: operatorId
127429
128137
  });
127430
- return NOT_ALLOWED_NOTICE;
128138
+ return t("reply.notAllowed");
127431
128139
  }
127432
- void answerByToken(token).catch((error) => {
128140
+ void answerApproval(found.approval, found.button.decision).catch((error) => {
127433
128141
  ctx.log.warn("chat could not answer the approval", { error: describeError(error) });
127434
128142
  });
127435
128143
  return null;
@@ -127474,19 +128182,153 @@ ${final.text}`, final.attachments);
127474
128182
  return [];
127475
128183
  }
127476
128184
  };
127477
- const targetByChat = /* @__PURE__ */ new Map();
128185
+ const targetOf = (binding) => chatOf(state, chatKeyOf(binding)).target;
127478
128186
  const chatByRun = /* @__PURE__ */ new Map();
127479
- const handleCommand = async (binding, message) => {
127480
- const teams = await ctx.plugins.invoke({
127481
- pluginId: "teams",
127482
- command: "choices",
127483
- payload: { projectId: binding.projectId }
127484
- }).then(
127485
- (result) => Array.isArray(result) ? result.flatMap(
127486
- (row) => typeof row === "object" && row !== null && typeof row.teamId === "string" && typeof row.title === "string" ? [{ teamId: row.teamId, title: row.title }] : []
127487
- ) : []
127488
- ).catch(() => []);
127489
- const command = parseChatCommand({ text: message.text, teams, chinese: true });
128187
+ const fetchTeams = async (binding) => ctx.plugins.invoke({
128188
+ pluginId: "teams",
128189
+ command: "choices",
128190
+ payload: { projectId: binding.projectId }
128191
+ }).then(
128192
+ (result) => Array.isArray(result) ? result.flatMap(
128193
+ (row) => typeof row === "object" && row !== null && typeof row.teamId === "string" && typeof row.title === "string" ? [{ teamId: row.teamId, title: row.title }] : []
128194
+ ) : null
128195
+ ).catch(() => null);
128196
+ const listedByChat = /* @__PURE__ */ new Map();
128197
+ const sessionRowsFor = async (binding) => {
128198
+ const chatKey = chatKeyOf(binding);
128199
+ const candidates = Object.entries(state.opened).filter(([, opened]) => opened.projectId === binding.projectId).toReversed().slice(0, SESSION_SCAN_LIMIT);
128200
+ const threads = await Promise.all(
128201
+ candidates.map(([threadId]) => ctx.projections.getThread(threadId))
128202
+ );
128203
+ const rows = [];
128204
+ for (const thread of threads) {
128205
+ if (thread === null || thread.archivedAt != null) continue;
128206
+ rows.push({
128207
+ threadId: thread.threadId,
128208
+ title: thread.title,
128209
+ updatedAt: thread.updatedAt,
128210
+ running: thread.session?.activeTurnId != null,
128211
+ current: chatOf(state, chatKey).threadId === thread.threadId,
128212
+ elsewhere: Object.entries(state.chats).some(
128213
+ ([otherKey, record]) => otherKey !== chatKey && record.threadId === thread.threadId
128214
+ )
128215
+ });
128216
+ }
128217
+ return rows.toSorted((left, right) => right.updatedAt.localeCompare(left.updatedAt));
128218
+ };
128219
+ const showSessions = async (binding) => {
128220
+ const rows = await sessionRowsFor(binding);
128221
+ listedByChat.set(chatKeyOf(binding), listedThreadIds(rows));
128222
+ await deliver(binding, formatSessions(rows, Date.now(), t));
128223
+ };
128224
+ const resumeSession = async (binding, arg) => {
128225
+ const chatKey = chatKeyOf(binding);
128226
+ const rows = await sessionRowsFor(binding);
128227
+ const picked = pickSession(arg, listedByChat.get(chatKey), rows);
128228
+ if (picked === null) {
128229
+ await deliver(binding, t("resume.notFound", { query: arg }));
128230
+ return;
128231
+ }
128232
+ const target = rows.find((row) => row.threadId === picked);
128233
+ if (target === void 0) {
128234
+ await deliver(binding, t("resume.gone"));
128235
+ return;
128236
+ }
128237
+ if (rows.some((row) => row.current && row.running)) {
128238
+ await deliver(binding, t("resume.busy"));
128239
+ return;
128240
+ }
128241
+ if (target.current) {
128242
+ await deliver(binding, t("resume.already"));
128243
+ return;
128244
+ }
128245
+ if (target.running && target.elsewhere) {
128246
+ await deliver(binding, t("resume.runningElsewhere"));
128247
+ return;
128248
+ }
128249
+ for (const [otherKey, record] of Object.entries(state.chats)) {
128250
+ if (otherKey !== chatKey && record.threadId === picked) bindChat(otherKey, null);
128251
+ }
128252
+ commit("chat-updated", { chatKey, threadId: picked, target: { kind: "thread" } });
128253
+ listedByChat.delete(chatKey);
128254
+ await deliver(binding, t("resume.done", { title: target.title }));
128255
+ };
128256
+ const currentModelOf = async (binding) => {
128257
+ const chat = chatOf(state, chatKeyOf(binding));
128258
+ if (chat.threadId !== null) {
128259
+ const thread = await ctx.projections.getThread(chat.threadId).catch(() => null);
128260
+ if (thread !== null) {
128261
+ return {
128262
+ instanceId: thread.modelSelection.instanceId,
128263
+ model: thread.modelSelection.model
128264
+ };
128265
+ }
128266
+ }
128267
+ if (chat.model !== null) return chat.model;
128268
+ const project = await ctx.projections.getProject(binding.projectId).catch(() => null);
128269
+ const fallback = project?.defaultModelSelection ?? null;
128270
+ return fallback === null ? null : { instanceId: fallback.instanceId, model: fallback.model };
128271
+ };
128272
+ const handleModel = async (binding, arg) => {
128273
+ const chatKey = chatKeyOf(binding);
128274
+ const instances = await ctx.providers.listInstances().catch(() => null);
128275
+ const choices = instances === null ? [] : modelChoices(instances);
128276
+ if (arg === "") {
128277
+ await deliver(binding, formatModels(await currentModelOf(binding), choices, t));
128278
+ return;
128279
+ }
128280
+ if (choices.length === 0) {
128281
+ await deliver(binding, t("model.unavailable"));
128282
+ return;
128283
+ }
128284
+ const picked = pickModel(arg, choices);
128285
+ if (picked === null) {
128286
+ await deliver(binding, t("model.notFound", { query: arg }));
128287
+ return;
128288
+ }
128289
+ const selection = { instanceId: picked.instanceId, model: picked.model };
128290
+ commit("chat-updated", { chatKey, model: selection });
128291
+ const threadId = chatOf(state, chatKey).threadId;
128292
+ if (threadId !== null && await threadStillUsable(binding, threadId)) {
128293
+ try {
128294
+ await ctx.commands.dispatch({
128295
+ type: "thread.meta.update",
128296
+ commandId: crypto.randomUUID(),
128297
+ threadId,
128298
+ modelSelection: selection
128299
+ });
128300
+ } catch (error) {
128301
+ ctx.log.warn("chat could not switch the thread model", { error: describeError(error) });
128302
+ await deliver(binding, t("model.setFailed"));
128303
+ return;
128304
+ }
128305
+ await deliver(binding, t("model.setNow", { model: modelLabel(picked) }));
128306
+ return;
128307
+ }
128308
+ await deliver(binding, t("model.setNext", { model: modelLabel(picked) }));
128309
+ };
128310
+ const handlePlan = async (binding, mode) => {
128311
+ if (mode === "invalid") {
128312
+ await deliver(binding, t("plan.usage"));
128313
+ return;
128314
+ }
128315
+ const chatKey = chatKeyOf(binding);
128316
+ const current = chatOf(state, chatKey).plan;
128317
+ const next = mode === "toggle" ? !current : mode === "on";
128318
+ if (next === current) {
128319
+ await deliver(binding, t(next ? "plan.alreadyOn" : "plan.alreadyOff"));
128320
+ return;
128321
+ }
128322
+ commit("chat-updated", { chatKey, plan: next });
128323
+ await deliver(binding, t(next ? "plan.on" : "plan.off"));
128324
+ };
128325
+ const handleCommand = async (binding, message, teams) => {
128326
+ const command = parseChatCommand({
128327
+ text: message.text,
128328
+ teams,
128329
+ // 中文别名只在回话语言是中文时认,免得英文用户打一个「停」就被当成命令。
128330
+ chinese: t.language === "zh-CN"
128331
+ });
127490
128332
  const chatKey = chatKeyOf(binding);
127491
128333
  if (command.kind !== "message") {
127492
128334
  ctx.log.info("chat handled a command", {
@@ -127498,47 +128340,61 @@ ${final.text}`, final.attachments);
127498
128340
  case "message":
127499
128341
  return false;
127500
128342
  case "selectTeam":
127501
- targetByChat.set(chatKey, { kind: "team", teamId: command.team.teamId });
127502
- threadByChat.delete(chatKey);
127503
- await deliver(binding, `\u597D\uFF0C\u63A5\u4E0B\u6765\u6D3E\u7ED9\u300C${command.team.title}\u300D\u3002`);
128343
+ commit("chat-updated", {
128344
+ chatKey,
128345
+ target: { kind: "team", teamId: command.team.teamId },
128346
+ threadId: null
128347
+ });
128348
+ await deliver(binding, t("command.teamSelected", { title: command.team.title }));
127504
128349
  return true;
127505
128350
  case "selectThread":
127506
- targetByChat.set(chatKey, { kind: "thread" });
127507
- threadByChat.delete(chatKey);
127508
- await deliver(binding, "\u597D\uFF0C\u63A5\u4E0B\u6765\u5F00\u666E\u901A\u4F1A\u8BDD\u3002");
128351
+ commit("chat-updated", { chatKey, target: { kind: "thread" }, threadId: null });
128352
+ await deliver(binding, t("command.threadSelected"));
127509
128353
  return true;
127510
128354
  case "showTargets":
127511
- await deliver(
127512
- binding,
127513
- describeTargets(targetByChat.get(chatKey) ?? { kind: "thread" }, teams)
127514
- );
128355
+ await deliver(binding, describeTargets(targetOf(binding), teams, t));
127515
128356
  return true;
127516
128357
  case "teamNotFound":
127517
128358
  await deliver(
127518
128359
  binding,
127519
128360
  [
127520
- `\u6CA1\u627E\u5230\u53EB\u300C${command.query}\u300D\u7684\u961F\uFF0C\u4E5F\u53EF\u80FD\u662F\u4E0D\u6B62\u4E00\u652F\u5BF9\u5F97\u4E0A\u3002`,
128361
+ t("command.teamNotFound", { query: command.query }),
127521
128362
  "",
127522
- describeTargets(targetByChat.get(chatKey) ?? { kind: "thread" }, teams)
128363
+ describeTargets(targetOf(binding), teams, t)
127523
128364
  ].join("\n")
127524
128365
  );
127525
128366
  return true;
127526
128367
  case "noTeams":
127527
- await deliver(binding, "\u8FD9\u53F0\u673A\u5668\u4E0A\u8FD8\u6CA1\u6709\u56E2\u961F\u3002");
128368
+ await deliver(binding, t("command.noTeams"));
127528
128369
  return true;
127529
128370
  case "help":
127530
- await deliver(binding, helpText());
128371
+ await deliver(binding, helpText(t));
128372
+ return true;
128373
+ case "sessions":
128374
+ await showSessions(binding);
128375
+ return true;
128376
+ case "resume":
128377
+ await resumeSession(binding, command.arg);
128378
+ return true;
128379
+ case "model":
128380
+ await handleModel(binding, command.arg);
128381
+ return true;
128382
+ case "plan":
128383
+ await handlePlan(binding, command.mode);
127531
128384
  return true;
127532
128385
  case "new":
127533
- threadByChat.delete(chatKey);
127534
- await deliver(binding, "\u597D\uFF0C\u4E0B\u4E00\u53E5\u8BDD\u5F00\u4E00\u6761\u65B0\u4F1A\u8BDD\u3002");
128386
+ bindChat(chatKey, null);
128387
+ await deliver(binding, t("command.newSession"));
127535
128388
  return true;
127536
128389
  case "status": {
127537
- const threadId = threadByChat.get(chatKey);
127538
- const routed = targetByChat.get(chatKey) ?? { kind: "thread" };
127539
- const where = routed.kind === "team" ? `\u6D3E\u7ED9\uFF1A${teams.find((team) => team.teamId === routed.teamId)?.title ?? routed.teamId}` : "\u666E\u901A\u4F1A\u8BDD";
128390
+ const threadId = chatOf(state, chatKey).threadId ?? void 0;
128391
+ const routed = targetOf(binding);
128392
+ const where = routed.kind === "team" ? t("status.routedTeam", {
128393
+ title: teams.find((team) => team.teamId === routed.teamId)?.title ?? routed.teamId
128394
+ }) : t("status.routedThread");
128395
+ const planLine = chatOf(state, chatKey).plan ? [t("status.planOn")] : [];
127540
128396
  if (threadId === void 0) {
127541
- await deliver(binding, `${where}\u3002\u73B0\u5728\u6CA1\u6709\u5728\u8DD1\u7684\u6D3B\u513F\u3002`);
128397
+ await deliver(binding, [t("status.nothingRunning", { where }), ...planLine].join("\n"));
127542
128398
  return true;
127543
128399
  }
127544
128400
  const thread = await ctx.projections.getThread(threadId);
@@ -127547,22 +128403,24 @@ ${final.text}`, final.attachments);
127547
128403
  binding,
127548
128404
  [
127549
128405
  where,
127550
- `\u4F1A\u8BDD\uFF1A${thread?.title ?? threadId}`,
127551
- running ? "\u6B63\u5728\u8DD1\u3002\u60F3\u505C\u5C31\u6253 /stop\u3002" : "\u7A7A\u95F2\u7740\u3002"
128406
+ t("status.session", { title: thread?.title ?? threadId }),
128407
+ ...thread === null ? [] : [t("status.model", { model: rawModelLabel(thread.modelSelection) })],
128408
+ ...planLine,
128409
+ running ? t("status.running") : t("status.idle")
127552
128410
  ].join("\n")
127553
128411
  );
127554
128412
  return true;
127555
128413
  }
127556
128414
  case "stop": {
127557
- const threadId = threadByChat.get(chatKey);
128415
+ const threadId = chatOf(state, chatKey).threadId ?? void 0;
127558
128416
  if (threadId === void 0) {
127559
- await deliver(binding, "\u8FD9\u4E2A\u7FA4\u73B0\u5728\u6CA1\u6709\u5728\u8DD1\u7684\u6D3B\u513F\u3002");
128417
+ await deliver(binding, t("command.stopNoSession"));
127560
128418
  return true;
127561
128419
  }
127562
128420
  const thread = await ctx.projections.getThread(threadId);
127563
128421
  const activeTurnId = thread?.session?.activeTurnId ?? null;
127564
128422
  if (activeTurnId === null) {
127565
- await deliver(binding, "\u73B0\u5728\u6CA1\u6709\u5728\u8DD1\u7684\u4E00\u8F6E\u3002");
128423
+ await deliver(binding, t("command.stopNoTurn"));
127566
128424
  return true;
127567
128425
  }
127568
128426
  await ctx.commands.dispatch({
@@ -127573,7 +128431,7 @@ ${final.text}`, final.attachments);
127573
128431
  createdAt: (/* @__PURE__ */ new Date()).toISOString()
127574
128432
  });
127575
128433
  await clearWorking(binding, threadId);
127576
- await deliver(binding, "\u505C\u4E86\u3002\u4F1A\u8BDD\u8FD8\u5728\uFF0C\u63A5\u7740\u8BF4\u5C31\u884C\u3002");
128434
+ await deliver(binding, t("command.stopped"));
127577
128435
  return true;
127578
128436
  }
127579
128437
  }
@@ -127599,10 +128457,28 @@ ${final.text}`, final.attachments);
127599
128457
  }
127600
128458
  ctx.log.info("chat started a team run", { group: binding.displayName, teamId, runId });
127601
128459
  };
128460
+ const threadStillUsable = async (binding, threadId) => {
128461
+ try {
128462
+ const thread = await ctx.projections.getThread(threadId);
128463
+ return thread !== null && thread.archivedAt == null && thread.projectId === binding.projectId;
128464
+ } catch {
128465
+ return true;
128466
+ }
128467
+ };
128468
+ const interactionModeOf = (chatKey) => chatOf(state, chatKey).plan ? "plan" : "default";
127602
128469
  const startTurn = async (binding, message) => {
127603
128470
  const chatKey = chatKeyOf(binding);
127604
128471
  const text = message.text;
127605
- let threadId = threadByChat.get(chatKey);
128472
+ let threadId = chatOf(state, chatKey).threadId ?? void 0;
128473
+ if (threadId !== void 0 && !await threadStillUsable(binding, threadId)) {
128474
+ ctx.log.info("chat dropped a saved thread that is no longer usable", {
128475
+ group: binding.displayName,
128476
+ threadId
128477
+ });
128478
+ bindChat(chatKey, null);
128479
+ threadId = void 0;
128480
+ await deliver(binding, t("reply.threadGone"));
128481
+ }
127606
128482
  if (threadId === void 0) {
127607
128483
  const project = await ctx.projections.getProject(binding.projectId);
127608
128484
  if (project === null) {
@@ -127612,30 +128488,37 @@ ${final.text}`, final.attachments);
127612
128488
  });
127613
128489
  return null;
127614
128490
  }
127615
- if (project.defaultModelSelection === null) {
128491
+ const modelSelection = chatOf(state, chatKey).model ?? project.defaultModelSelection;
128492
+ if (modelSelection === null) {
127616
128493
  ctx.log.warn("chat cannot start a thread: the project has no default model", {
127617
128494
  group: binding.displayName,
127618
128495
  projectId: binding.projectId
127619
128496
  });
127620
- await deliver(binding, noDefaultModelReply(project.title));
128497
+ await deliver(binding, t("reply.noDefaultModel", { title: project.title }));
127621
128498
  return null;
127622
128499
  }
127623
- threadId = crypto.randomUUID();
128500
+ const createdThreadId = crypto.randomUUID();
128501
+ threadId = createdThreadId;
127624
128502
  await ctx.commands.dispatch({
127625
128503
  type: "thread.create",
127626
128504
  commandId: crypto.randomUUID(),
127627
128505
  threadId,
127628
128506
  projectId: binding.projectId,
127629
128507
  title: titleFrom(text || message.resource?.name || "Chat"),
127630
- modelSelection: project.defaultModelSelection,
128508
+ modelSelection,
127631
128509
  runtimeMode: binding.runtimeMode,
127632
- interactionMode: "default",
128510
+ interactionMode: interactionModeOf(chatKey),
127633
128511
  branch: null,
127634
128512
  worktreePath: null,
127635
128513
  createdAt: (/* @__PURE__ */ new Date()).toISOString()
127636
128514
  });
127637
- threadByChat.set(chatKey, threadId);
127638
- projectByThread.set(threadId, binding.projectId);
128515
+ commit("thread-opened", {
128516
+ chatKey,
128517
+ threadId: createdThreadId,
128518
+ projectId: binding.projectId,
128519
+ openedAt: (/* @__PURE__ */ new Date()).toISOString()
128520
+ });
128521
+ projectByThread.set(createdThreadId, binding.projectId);
127639
128522
  }
127640
128523
  const attachments = await collectAttachment(binding, threadId, message);
127641
128524
  await ctx.commands.dispatch({
@@ -127650,7 +128533,7 @@ ${final.text}`, final.attachments);
127650
128533
  },
127651
128534
  titleSeed: titleFrom(text || attachments[0]?.name || "Chat"),
127652
128535
  runtimeMode: binding.runtimeMode,
127653
- interactionMode: "default",
128536
+ interactionMode: interactionModeOf(chatKey),
127654
128537
  createdAt: (/* @__PURE__ */ new Date()).toISOString()
127655
128538
  });
127656
128539
  ctx.log.info("chat started a turn from a message", {
@@ -127664,6 +128547,15 @@ ${final.text}`, final.attachments);
127664
128547
  const connections = [];
127665
128548
  const started = /* @__PURE__ */ new Set();
127666
128549
  let disposed = false;
128550
+ const chatQueues = /* @__PURE__ */ new Map();
128551
+ const enqueueForChat = (chatKey, work) => {
128552
+ const run = (chatQueues.get(chatKey) ?? Promise.resolve()).then(work).catch((error) => {
128553
+ ctx.log.warn("chat could not handle the message", { error: describeError(error) });
128554
+ }).then(() => {
128555
+ if (chatQueues.get(chatKey) === run) chatQueues.delete(chatKey);
128556
+ });
128557
+ chatQueues.set(chatKey, run);
128558
+ };
127667
128559
  const inboundHandlersFor = (platform) => ({
127668
128560
  onError: (message) => ctx.log.warn("chat inbound connection failed", { platform, message }),
127669
128561
  onApproval: handleApprovalClick,
@@ -127678,16 +128570,21 @@ ${final.text}`, final.attachments);
127678
128570
  });
127679
128571
  return;
127680
128572
  }
127681
- const open = openOptionsByChat.get(chatKeyOf(target));
127682
- const answerToken = open === void 0 ? null : adapterFor(target.platform)?.answerByText?.(target, message.text, open) ?? null;
128573
+ const open = openApprovalOf(state, chatKeyOf(target));
128574
+ const answerToken = open === void 0 ? null : adapterFor(target.platform)?.answerByText?.(
128575
+ target,
128576
+ message.text,
128577
+ open.options.map(({ label, token }) => ({ label, token }))
128578
+ ) ?? null;
127683
128579
  if (answerToken !== null) {
127684
128580
  void answerByToken(answerToken).catch((error) => {
127685
128581
  ctx.log.warn("chat could not answer the approval", { error: describeError(error) });
127686
128582
  });
127687
128583
  return;
127688
128584
  }
127689
- void (async () => {
127690
- if (await handleCommand(target, message)) return;
128585
+ enqueueForChat(chatKeyOf(target), async () => {
128586
+ const teams = await fetchTeams(target);
128587
+ if (await handleCommand(target, message, teams ?? [])) return;
127691
128588
  const project = await ctx.projections.getProject(target.projectId);
127692
128589
  if (project === null || project.archivedAt != null) {
127693
128590
  if (project === null) {
@@ -127699,21 +128596,28 @@ ${final.text}`, final.attachments);
127699
128596
  if (archivedReplyCooldown.tryAcquire(chatKeyOf(target))) {
127700
128597
  await deliver(
127701
128598
  target,
127702
- project === null ? missingProjectReply(target.displayName) : archivedProjectReply(project.title)
128599
+ project === null ? t("reply.projectMissing", { group: target.displayName }) : t("reply.projectArchived", { title: project.title })
127703
128600
  );
127704
128601
  }
127705
128602
  return;
127706
128603
  }
127707
- const routed = targetByChat.get(chatKeyOf(target)) ?? { kind: "thread" };
128604
+ const routed = targetOf(target);
127708
128605
  if (routed.kind === "team") {
127709
- await startTeamRun(target, routed.teamId, message.text);
127710
- await markWorking(target, chatKeyOf(target), message.messageId);
127711
- return;
128606
+ if (teams !== null && !teams.some((team) => team.teamId === routed.teamId)) {
128607
+ commit("chat-updated", {
128608
+ chatKey: chatKeyOf(target),
128609
+ target: { kind: "thread" },
128610
+ threadId: null
128611
+ });
128612
+ await deliver(target, t("reply.teamGone"));
128613
+ } else {
128614
+ await startTeamRun(target, routed.teamId, message.text);
128615
+ await markWorking(target, chatKeyOf(target), message.messageId);
128616
+ return;
128617
+ }
127712
128618
  }
127713
128619
  const threadId = await startTurn(target, message);
127714
128620
  if (threadId !== null) await markWorking(target, threadId, message.messageId);
127715
- })().catch((error) => {
127716
- ctx.log.warn("chat could not handle the message", { error: describeError(error) });
127717
128621
  });
127718
128622
  }
127719
128623
  });
@@ -127723,7 +128627,7 @@ ${final.text}`, final.attachments);
127723
128627
  const fingerprint = `${binding.platform}:${adapter.columns.map((column) => binding.values[column.key] ?? "").join(" ")}`;
127724
128628
  if (started.has(fingerprint)) continue;
127725
128629
  started.add(fingerprint);
127726
- void Promise.resolve().then(() => adapter.connect(binding, inboundHandlersFor(binding.platform))).then((connection) => {
128630
+ void ready.then(() => adapter.connect(binding, inboundHandlersFor(binding.platform))).then((connection) => {
127727
128631
  if (disposed) connection.close();
127728
128632
  else connections.push(connection);
127729
128633
  }).catch((error) => {