@primusclaw/personal-agent 0.5.0 → 0.7.0

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
@@ -4029,7 +4029,18 @@ var assistantCardText = defineCatalog({
4029
4029
  minutesAgo: (p) => `${String(p.n)} min ago`,
4030
4030
  hoursAgo: (p) => `${String(p.n)} h ago`,
4031
4031
  daysAgo: (p) => `${String(p.n)} d ago`,
4032
- askedTitle: (p) => `Asked of others \xB7 ${String(p.n)}`
4032
+ askedTitle: (p) => `Asked of others \xB7 ${String(p.n)}`,
4033
+ choiceTitle: "Pick one",
4034
+ choiceTitleMany: "Pick any that apply",
4035
+ choiceSend: "Send",
4036
+ choiceTyped: "You can also just reply in the chat.",
4037
+ choiceNone: "Pick an option first, then press Send.",
4038
+ choiceSent: (p) => `You picked: ${p.answer}`,
4039
+ choiceGone: "This choice is no longer open. Reply in the chat instead.",
4040
+ choiceSending: (p) => `Sending: ${p.answer}. If nothing comes back, reply in the chat instead.`,
4041
+ choiceFailed: "That did not go through. Press Send again.",
4042
+ /** How the options picked are joined into the person's message. */
4043
+ choiceJoin: ", "
4033
4044
  },
4034
4045
  zh: {
4035
4046
  ...english2,
@@ -4066,7 +4077,17 @@ var assistantCardText = defineCatalog({
4066
4077
  minutesAgo: (p) => `${String(p.n)} \u5206\u949F\u524D`,
4067
4078
  hoursAgo: (p) => `${String(p.n)} \u5C0F\u65F6\u524D`,
4068
4079
  daysAgo: (p) => `${String(p.n)} \u5929\u524D`,
4069
- askedTitle: (p) => `\u8BF7\u522B\u4EBA\u505A\u7684\u4EE3\u5BA1 \xB7 ${String(p.n)}`
4080
+ askedTitle: (p) => `\u8BF7\u522B\u4EBA\u505A\u7684\u4EE3\u5BA1 \xB7 ${String(p.n)}`,
4081
+ choiceTitle: "\u8BF7\u9009\u62E9\u4E00\u9879",
4082
+ choiceTitleMany: "\u53EF\u591A\u9009",
4083
+ choiceSend: "\u53D1\u9001",
4084
+ choiceTyped: "\u4E5F\u53EF\u4EE5\u76F4\u63A5\u5728\u804A\u5929\u91CC\u56DE\u590D\u3002",
4085
+ choiceNone: "\u8BF7\u5148\u9009\u4E00\u9879\uFF0C\u518D\u70B9\u53D1\u9001\u3002",
4086
+ choiceSent: (p) => `\u4F60\u9009\u4E86\uFF1A${p.answer}`,
4087
+ choiceGone: "\u8FD9\u4E2A\u9009\u62E9\u5DF2\u7ECF\u5931\u6548\uFF0C\u8BF7\u76F4\u63A5\u5728\u804A\u5929\u91CC\u56DE\u590D\u3002",
4088
+ choiceSending: (p) => `\u6B63\u5728\u53D1\u9001\uFF1A${p.answer}\u3002\u5982\u679C\u4E00\u76F4\u6CA1\u6709\u56DE\u5E94\uFF0C\u8BF7\u76F4\u63A5\u5728\u804A\u5929\u91CC\u56DE\u590D\u3002`,
4089
+ choiceFailed: "\u6CA1\u6709\u53D1\u51FA\u53BB\uFF0C\u8BF7\u518D\u70B9\u4E00\u6B21\u53D1\u9001\u3002",
4090
+ choiceJoin: "\u3001"
4070
4091
  }
4071
4092
  });
4072
4093
  var ASKED_VERDICT = {
@@ -4969,6 +4990,7 @@ var ITEM_KINDS = ["task", "review", "remote"];
4969
4990
 
4970
4991
  // ../claw/packages/protocol/src/claw-api/assistant-events.ts
4971
4992
  var ASSISTANT_EVENT_TYPES = ["assistant.say", "work_item.needs_you", "assistant.action"];
4993
+ var TERMINAL_ASKS = ["after_card", "on_input"];
4972
4994
  var REVIEW_HANDOFF_ACTION = "review_for_other";
4973
4995
 
4974
4996
  // ../claw/packages/protocol/src/claw-api/notifications.ts
@@ -6076,6 +6098,19 @@ function deliveryLine(delivery, url) {
6076
6098
  return turnEnglish.deliveryLine(plain(delivery.name), sizeText(delivery.size), caption, url);
6077
6099
  }
6078
6100
 
6101
+ // packages/core/src/language/chosen.ts
6102
+ function chosenLang(prefs) {
6103
+ if (!prefs.langSet) return void 0;
6104
+ return prefs.lang === "zh" || prefs.lang === "en" ? prefs.lang : void 0;
6105
+ }
6106
+ function replyLanguageContext(prefs) {
6107
+ const lang = chosenLang(prefs);
6108
+ return lang === void 0 ? {} : { reply_language: lang };
6109
+ }
6110
+ function replyLanguageLine(lang) {
6111
+ return `[Reply language: ${lang === "zh" ? "Chinese" : "English"}]`;
6112
+ }
6113
+
6079
6114
  // packages/core/src/language/detect.ts
6080
6115
  var CJK2 = /[㐀-䶿一-鿿豈-﫿]/u;
6081
6116
  function hasCjk2(text8) {
@@ -6103,8 +6138,8 @@ function writtenLang(text8) {
6103
6138
  const words3 = written3.map((w) => w.toLowerCase());
6104
6139
  if (words3.length < 3) return void 0;
6105
6140
  const first = words3[0] ?? "";
6106
- const sentence2 = EN_VERBS.has(first) || words3.some((w) => EN_GLUE.has(w)) || written3.some((w) => EN_FORM.test(w));
6107
- return sentence2 ? "en" : void 0;
6141
+ const sentence3 = EN_VERBS.has(first) || words3.some((w) => EN_GLUE.has(w)) || written3.some((w) => EN_FORM.test(w));
6142
+ return sentence3 ? "en" : void 0;
6108
6143
  }
6109
6144
  function pickLang2(...texts4) {
6110
6145
  for (const text8 of texts4) {
@@ -6209,9 +6244,13 @@ ${text8}` : quote || text8;
6209
6244
  function messageOf(ctx) {
6210
6245
  const said2 = withQuote(ctx.request.sent || ctx.request.text, ctx.request.quote ?? "");
6211
6246
  const note = ctx.record.filesNote;
6212
- return said2 && note ? `${said2}
6247
+ const message = said2 && note ? `${said2}
6213
6248
 
6214
6249
  ${note}` : said2 || note;
6250
+ const chosen = ctx.request.replyLang;
6251
+ return message && chosen !== void 0 ? `${message}
6252
+
6253
+ ${replyLanguageLine(chosen)}` : message;
6215
6254
  }
6216
6255
 
6217
6256
  // packages/core/src/locales/claw.ts
@@ -8837,6 +8876,7 @@ var TurnEngine = class {
8837
8876
  quote: run.quote,
8838
8877
  sent: run.sent,
8839
8878
  lang: run.lang,
8879
+ ...run.replyLang === void 0 ? {} : { replyLang: run.replyLang },
8840
8880
  files: run.files,
8841
8881
  ...review ? reviewRequest(review) : {}
8842
8882
  };
@@ -9518,21 +9558,34 @@ function helpLang(store, userId, locale, asked3) {
9518
9558
  const written3 = asked3 !== void 0 && !prefs.langSet ? writtenLang(asked3) : void 0;
9519
9559
  return written3 ?? langOf2(store, userId, locale);
9520
9560
  }
9561
+ function groupLangOf(store, conversationId, userId, locale, written3) {
9562
+ const chosen = groupChosenLang(store, conversationId);
9563
+ if (chosen !== void 0) return chosen;
9564
+ const prefs = userId ? store.prefs.getPrefs(userId) : void 0;
9565
+ return writtenLang(written3) ?? answerLang(prefs !== void 0 && !prefs.langSet ? prefs.lang : "", locale);
9566
+ }
9567
+ function groupChosenLang(store, conversationId) {
9568
+ const lang = conversationId ? store.groupLanguage?.groupLanguage(conversationId) : null;
9569
+ return lang === "zh" || lang === "en" ? lang : void 0;
9570
+ }
9521
9571
  var Chat = class {
9522
- constructor(store, sender, conversation, scope, locale) {
9572
+ constructor(store, sender, conversation, scope, locale, written3 = "") {
9523
9573
  this.store = store;
9524
9574
  this.sender = sender;
9525
9575
  this.conversation = conversation;
9526
9576
  this.scope = scope;
9527
9577
  this.locale = locale;
9578
+ this.written = written3;
9528
9579
  }
9529
9580
  store;
9530
9581
  sender;
9531
9582
  conversation;
9532
9583
  scope;
9533
9584
  locale;
9585
+ written;
9586
+ /** In a 1:1 chat the person's language (`langOf`); in a group, the group's (`groupLangOf`). */
9534
9587
  lang() {
9535
- return langOf2(this.store, this.sender.id, this.locale);
9588
+ return this.scope ? groupLangOf(this.store, this.scope, this.sender.id, this.locale, this.written) : langOf2(this.store, this.sender.id, this.locale);
9536
9589
  }
9537
9590
  zh() {
9538
9591
  return this.lang() === "zh";
@@ -10275,7 +10328,7 @@ var CHAT_ENTRIES = [
10275
10328
  category: "chat",
10276
10329
  zh: "\u8981\u4F60\u62CD\u677F\u65F6\u6211\u53D1\u5361\u7247\uFF1A\u70B9\u6309\u94AE\u56DE\u7B54\u3001\u6279\u51C6\u6216\u62D2\u7EDD",
10277
10330
  en: "When I need you, I send a card: tap to answer, approve or deny",
10278
- covers: ["card:decision", "card:answer", "card:assistant_action"],
10331
+ covers: ["card:decision", "card:answer", "card:assistant_action", "card:assistant_choice"],
10279
10332
  where: "both"
10280
10333
  }),
10281
10334
  capability({
@@ -10978,7 +11031,12 @@ function renderHelp(language, options = {}) {
10978
11031
  const oneToOne = chat.group ? oneToOneBlock(lang, chat, bot) : void 0;
10979
11032
  if (oneToOne !== void 0) out.push(oneToOne);
10980
11033
  out.push(authorityText(zh, chat.parts));
10981
- if (chat.group) out.push(zh ? "\u8BBE\u7F6E\u53EA\u80FD\u5728\u548C\u6211\u7684 1:1 \u804A\u5929\u91CC\u6539\u3002" : "Settings are changed in a 1:1 chat with me.");
11034
+ if (chat.group)
11035
+ out.push(
11036
+ zh ? `\u8BBE\u7F6E\u53EA\u80FD\u5728\u548C\u6211\u7684 1:1 \u804A\u5929\u91CC\u6539\u3002
11037
+ \u53EA\u6709\u8FD9\u4E2A\u7FA4\u7684\u56DE\u590D\u8BED\u8A00\u5728\u7FA4\u91CC\u8BBE\uFF1A@${bot} \u8BF4\u300C\u8FD9\u4E2A\u7FA4\u7528\u82F1\u6587\u300D\uFF08\u6216\u4E2D\u6587\u3001\u8DDF\u968F\u63D0\u95EE\u8005\uFF09\u3002` : `Settings are changed in a 1:1 chat with me.
11038
+ This group's language is set here: @${bot} "use English in this group" (or Chinese, or "follow the asker").`
11039
+ );
10982
11040
  out.push(dataStatement(lang, !chat.group && chat.parts.has("remote"), options.groupContext));
10983
11041
  out.push(
10984
11042
  zh ? "\u968F\u65F6\u95EE\u6211\u300C\u4F60\u80FD\u505A\u4EC0\u4E48\u300D\u770B\u8FD9\u4EFD\u8BF4\u660E\u3002\u5E95\u90E8\u83DC\u5355\u91CC\u4E5F\u6709\u5E38\u7528\u7684\u6309\u94AE\u3002" : 'Ask "what can you do" any time to see this again. The menu by the compose box has the common ones too.'
@@ -11561,8 +11619,8 @@ var VALUED = [
11561
11619
  function personal(text8, flat2, key) {
11562
11620
  if (STOP_IT2.has(key)) return "/stop";
11563
11621
  if (DETACH2.has(detachKey(key))) return "/agent detach";
11564
- const phrase = lookUp(key, PERSONAL_PHRASES);
11565
- if (phrase !== null) return phrase;
11622
+ const phrase2 = lookUp(key, PERSONAL_PHRASES);
11623
+ if (phrase2 !== null) return phrase2;
11566
11624
  for (const read of VALUED) {
11567
11625
  const command = read(flat2);
11568
11626
  if (command !== null) return command;
@@ -12056,11 +12114,13 @@ var RouterContext = class {
12056
12114
  /** The chat an event is answered in: its conversation, as the person who sent it. */
12057
12115
  chatOf(event, scope) {
12058
12116
  const conversation = this.deps.surface.conversation(event.conversation);
12059
- return new Chat(this.deps.store, event.sender, conversation, scope, event.locale);
12117
+ const written3 = event.type === "message" ? event.text : "";
12118
+ return new Chat(this.deps.store, event.sender, conversation, scope, event.locale, written3);
12060
12119
  }
12061
12120
  /** A chat in another conversation of the same person (their 1:1 chat, a group a request waited in). */
12062
12121
  chatIn(chat, ref, scope = "") {
12063
- return new Chat(this.deps.store, chat.sender, this.deps.surface.conversation(ref), scope, chat.locale);
12122
+ const written3 = scope ? chat.written : "";
12123
+ return new Chat(this.deps.store, chat.sender, this.deps.surface.conversation(ref), scope, chat.locale, written3);
12064
12124
  }
12065
12125
  /** A click as a feature handler is given it. */
12066
12126
  clickRoute(action, chat) {
@@ -12113,8 +12173,8 @@ var groupText = defineCatalog({
12113
12173
  stopMany: (p) => `#${String(p.number)}: more than one repository has it; write it as owner/repo#${String(p.number)}.`
12114
12174
  },
12115
12175
  zh: {
12116
- githubOneToOne: "`/github` works only in a 1:1 chat. Please do this in a 1:1 chat with me.",
12117
- oneToOneOnly: (p) => `\`${p.word}\` works only in a 1:1 chat. Please do this in a 1:1 chat with me.`,
12176
+ githubOneToOne: "`/github` \u53EA\u80FD\u5728 1:1 \u804A\u5929\u91CC\u7528\uFF0C\u8BF7\u5728\u548C\u6211\u7684 1:1 \u804A\u5929\u91CC\u64CD\u4F5C\u3002",
12177
+ oneToOneOnly: (p) => `\`${p.word}\` \u53EA\u80FD\u5728 1:1 \u804A\u5929\u91CC\u7528\uFF0C\u8BF7\u5728\u548C\u6211\u7684 1:1 \u804A\u5929\u91CC\u64CD\u4F5C\u3002`,
12118
12178
  agentInGroup: "`/agent` \u5728\u7FA4\u91CC\u53EA\u80FD\u7528 attach \u548C detach\uFF0C\u5176\u4F59\u8BF7\u5728 1:1 \u804A\u5929\u91CC\u7528\u3002",
12119
12179
  whoami: (p) => `${p.name || "\u4F60"} \u5DF2\u8FDE\u63A5\u5230 SaFE \u7528\u6237 ${p.who}\u3002`,
12120
12180
  whoamiUnknown: "\u4F60\u5DF2\u8FDE\u63A5\uFF0C\u4F46 SaFE \u6CA1\u544A\u8BC9\u6211\u662F\u8C01\u7684\u8D26\u53F7\u3002",
@@ -12280,7 +12340,8 @@ var Onboarding = class {
12280
12340
  try {
12281
12341
  const conversation = await this.openPersonal(chat);
12282
12342
  if (conversation === void 0) throw new Error("no 1:1 conversation");
12283
- await this.ctx.chatIn(chat, conversation.ref).say(groupText.in(chat.lang()).targeted({ bot: config.botName }));
12343
+ const dm = this.ctx.chatIn(chat, conversation.ref);
12344
+ await dm.say(groupText.in(dm.lang()).targeted({ bot: config.botName }));
12284
12345
  } catch (error) {
12285
12346
  logger.warn("group: the 1:1 line about a private message did not go out", { error: errorName(error) });
12286
12347
  return;
@@ -13031,6 +13092,208 @@ var SettingsCommands = class {
13031
13092
  }
13032
13093
  };
13033
13094
 
13095
+ // packages/core/src/language/group.ts
13096
+ var WORDS = /* @__PURE__ */ new Map([
13097
+ ["zh", "zh"],
13098
+ ["cn", "zh"],
13099
+ ["chinese", "zh"],
13100
+ ["\u4E2D\u6587", "zh"],
13101
+ ["\u6C49\u8BED", "zh"],
13102
+ ["en", "en"],
13103
+ ["english", "en"],
13104
+ ["\u82F1\u6587", "en"],
13105
+ ["\u82F1\u8BED", "en"],
13106
+ ["auto", "auto"],
13107
+ ["follow", "auto"],
13108
+ ["\u81EA\u52A8", "auto"],
13109
+ ["\u8DDF\u968F", "auto"],
13110
+ ["\u8DDF\u968F\u63D0\u95EE\u8005", "auto"],
13111
+ ["followtheasker", "auto"]
13112
+ ]);
13113
+ var PUNCT2 = ` \r
13114
+ \u3002\uFF0E.\uFF01!\uFF1F?\uFF5E~\uFF0C,\u3001\uFF1B;\uFF1A:"'\u201C\u201D\u2018\u2019\uFF08\uFF09()`;
13115
+ var GROUP = "(?:\u8FD9\u4E2A\u7FA4\u804A|\u8FD9\u4E2A\u7FA4|\u672C\u7FA4|\u6B64\u7FA4|\u7FA4\u804A|\u7FA4)(?:\u91CC\u9762|\u91CC|\u5185|\u4E2D)?";
13116
+ var LANG_ZH = "(\u4E2D\u6587|\u6C49\u8BED|\u82F1\u6587|\u82F1\u8BED)";
13117
+ var ASKER = "(?:\u63D0\u95EE\u8005|\u63D0\u95EE\u4EBA|\u63D0\u95EE\u7684\u4EBA|\u95EE\u95EE\u9898\u7684\u4EBA|\u95EE\u7684\u4EBA|\u53D1\u95EE\u7684\u4EBA)";
13118
+ var ZH_SET = [
13119
+ new RegExp(
13120
+ `^(?:\u4EE5\u540E|\u4E4B\u540E|\u4ECA\u540E|\u4ECE\u73B0\u5728\u8D77|\u4ECE\u73B0\u5728\u5F00\u59CB)?(?:\u8BF7|\u9EBB\u70E6)?(?:\u5728)?${GROUP}(?:\u4EE5\u540E|\u4E4B\u540E|\u4ECA\u540E|\u4ECE\u73B0\u5728\u8D77|\u4ECE\u73B0\u5728\u5F00\u59CB)?(?:\u8BF7)?(?:\u90FD|\u4E00\u5F8B|\u7EDF\u4E00)?(?:\u7528|\u8BF4|\u4F7F\u7528)${LANG_ZH}(?:\u56DE\u590D|\u56DE\u7B54|\u4EA4\u6D41|\u8BF4\u8BDD|\u804A\u5929|\u804A)?(?:\u5427|\u597D\u5417|\u53EF\u4EE5\u5417)?$`,
13121
+ "u"
13122
+ ),
13123
+ new RegExp(
13124
+ `^(?:\u628A)?${GROUP}(?:\u7684)?(?:\u56DE\u590D)?\u8BED\u8A00(?:\u8BBE\u4E3A|\u8BBE\u6210|\u6539\u6210|\u6539\u4E3A|\u8BBE\u7F6E\u4E3A|\u8BBE\u7F6E\u6210|\u5207\u6362\u5230|\u5207\u6362\u6210|\u6362\u6210)${LANG_ZH}$`,
13125
+ "u"
13126
+ )
13127
+ ];
13128
+ var ZH_AUTO = [
13129
+ new RegExp(
13130
+ `^(?:\u4EE5\u540E|\u4E4B\u540E|\u4ECA\u540E)?(?:\u5728)?${GROUP}(?:\u4EE5\u540E|\u4E4B\u540E|\u4ECA\u540E)?(?:\u90FD)?(?:\u8DDF\u968F|\u8DDF\u7740|\u6309|\u6309\u7167|\u7528)${ASKER}(?:\u7684)?(?:\u8BED\u8A00)?(?:\u56DE\u590D|\u56DE\u7B54)?(?:\u5427)?$`,
13131
+ "u"
13132
+ ),
13133
+ new RegExp(
13134
+ `^(?:\u628A)?${GROUP}(?:\u7684)?(?:\u56DE\u590D)?\u8BED\u8A00(?:\u8BBE\u4E3A|\u8BBE\u6210|\u6539\u6210|\u6539\u4E3A|\u8BBE\u7F6E\u4E3A|\u8BBE\u7F6E\u6210|\u5207\u6362\u5230|\u5207\u6362\u6210|\u6362\u6210)(?:\u81EA\u52A8|\u8DDF\u968F${ASKER})$`,
13135
+ "u"
13136
+ )
13137
+ ];
13138
+ var LANG_EN = "(english|chinese)";
13139
+ var THIS_GROUP = "(?:this|the|our) (?:group chat|group|chat)";
13140
+ var ANSWER = "(?:use|speak|reply in|answer in|respond in|talk in|write in)";
13141
+ var EN_SET = [
13142
+ new RegExp(`^(?:please )?${ANSWER} ${LANG_EN} (?:in|for) ${THIS_GROUP}(?: from now on)?(?: please)?$`),
13143
+ new RegExp(`^(?:in|for) ${THIS_GROUP},? (?:please )?${ANSWER} ${LANG_EN}(?: from now on)?(?: please)?$`),
13144
+ new RegExp(`^${THIS_GROUP} (?:uses|speaks|is in) ${LANG_EN}$`),
13145
+ new RegExp(
13146
+ `^(?:please )?(?:set|change|switch) (?:the |this )?(?:group|chat)(?:'s)? language to ${LANG_EN}(?: please)?$`
13147
+ )
13148
+ ];
13149
+ var EN_AUTO = [
13150
+ new RegExp(
13151
+ `^(?:please )?(?:follow the asker|use the asker'?s language|reply in the asker'?s language|answer in the language of each question)(?: (?:in|for) ${THIS_GROUP})?(?: please)?$`
13152
+ ),
13153
+ new RegExp(
13154
+ `^(?:please )?(?:set|change|switch) (?:the |this )?(?:group|chat)(?:'s)? language to (?:auto|follow the asker)(?: please)?$`
13155
+ )
13156
+ ];
13157
+ function phrase(text8) {
13158
+ return pySplit(pyLower(pyStrip(text8, PUNCT2))).join("");
13159
+ }
13160
+ function sentence(text8) {
13161
+ return pySplit(pyLower(pyStrip(text8, PUNCT2))).join(" ");
13162
+ }
13163
+ var MAX_SENTENCE_CHARS = 120;
13164
+ function sentenceCommand(text8) {
13165
+ if (text8.length > MAX_SENTENCE_CHARS) return void 0;
13166
+ const zh = phrase(text8);
13167
+ const en2 = sentence(text8);
13168
+ for (const pattern of ZH_SET) {
13169
+ const word2 = pattern.exec(zh)?.[1];
13170
+ const lang = word2 === void 0 ? void 0 : WORDS.get(word2);
13171
+ if (lang !== void 0) return { kind: "set", lang };
13172
+ }
13173
+ if (ZH_AUTO.some((p) => p.test(zh))) return { kind: "set", lang: "auto" };
13174
+ for (const pattern of EN_SET) {
13175
+ const word2 = pattern.exec(en2)?.[1];
13176
+ const lang = word2 === void 0 ? void 0 : WORDS.get(word2);
13177
+ if (lang !== void 0) return { kind: "set", lang };
13178
+ }
13179
+ if (EN_AUTO.some((p) => p.test(en2))) return { kind: "set", lang: "auto" };
13180
+ return void 0;
13181
+ }
13182
+ function parseGroupLang(text8) {
13183
+ const words3 = pySplit(text8);
13184
+ if (pyLower(words3[0] ?? "") !== "/lang") return sentenceCommand(pyStrip(text8));
13185
+ if (words3.length === 1) return { kind: "status" };
13186
+ const lang = WORDS.get(pyLower(words3.slice(1).join("")));
13187
+ return lang === void 0 ? { kind: "usage" } : { kind: "set", lang };
13188
+ }
13189
+
13190
+ // packages/core/src/locales/group-language.ts
13191
+ var NAMES2 = {
13192
+ zh: ["\u4E2D\u6587", "Chinese"],
13193
+ en: ["\u82F1\u6587", "English"],
13194
+ auto: ["\u8DDF\u968F\u63D0\u95EE\u8005\u7684\u8BED\u8A00", "the language each question is asked in"]
13195
+ };
13196
+ function groupLanguageSet(lang, name) {
13197
+ const [zh, en2] = NAMES2[lang];
13198
+ return `\u5DF2\u6539\u4E3A${zh}\u56DE\u590D\uFF0C\u7531 ${name || "\u7FA4\u6210\u5458"} \u8BBE\u7F6E\u3002
13199
+ I answer in ${en2} in this group from now on (set by ${name || "a member"}).`;
13200
+ }
13201
+ var GROUP_LANGUAGE_CARD = {
13202
+ title: "\u8FD9\u4E2A\u7FA4\u7528\u54EA\u79CD\u8BED\u8A00\u56DE\u590D\uFF1F/ Which language should I use in this group?",
13203
+ text: (p) => `\u7FA4\u91CC\u4EFB\u4F55\u4EBA\u90FD\u53EF\u4EE5\u9009\uFF0C\u4E4B\u540E\u4E5F\u80FD\u7528 @${p.bot} /lang zh | en | auto \u518D\u6539\u3002
13204
+ Anyone here may pick; @${p.bot} /lang zh | en | auto changes it later.`,
13205
+ buttons: { zh: "\u4E2D\u6587", en: "English", auto: "\u8DDF\u968F\u63D0\u95EE\u8005 (Follow the asker)" }
13206
+ };
13207
+ var groupLanguageText = defineCatalog({
13208
+ en: {
13209
+ isZh: (p) => `This group is answered in Chinese. @${p.bot} /lang en or /lang auto changes it.`,
13210
+ isEn: (p) => `This group is answered in English. @${p.bot} /lang zh or /lang auto changes it.`,
13211
+ isAuto: (p) => `This group is answered in the language each question is asked in. @${p.bot} /lang zh or /lang en sets one.`,
13212
+ isNone: (p) => `This group has no language set: each question is answered in the language it is asked in. @${p.bot} /lang zh, /lang en or /lang auto sets it.`,
13213
+ usage: (p) => `Usage: @${p.bot} /lang zh | en | auto (follow the asker).`
13214
+ },
13215
+ zh: {
13216
+ isZh: (p) => `\u8FD9\u4E2A\u7FA4\u7528\u4E2D\u6587\u56DE\u590D\u3002@${p.bot} /lang en \u6216 /lang auto \u53EF\u6539\u3002`,
13217
+ isEn: (p) => `\u8FD9\u4E2A\u7FA4\u7528\u82F1\u6587\u56DE\u590D\u3002@${p.bot} /lang zh \u6216 /lang auto \u53EF\u6539\u3002`,
13218
+ isAuto: (p) => `\u8FD9\u4E2A\u7FA4\u6309\u6BCF\u4E2A\u95EE\u9898\u6240\u7528\u7684\u8BED\u8A00\u56DE\u590D\u3002@${p.bot} /lang zh \u6216 /lang en \u53EF\u8BBE\u4E3A\u56FA\u5B9A\u8BED\u8A00\u3002`,
13219
+ isNone: (p) => `\u8FD9\u4E2A\u7FA4\u8FD8\u6CA1\u8BBE\u8BED\u8A00\uFF1A\u6BCF\u4E2A\u95EE\u9898\u6309\u5B83\u6240\u7528\u7684\u8BED\u8A00\u56DE\u590D\u3002@${p.bot} /lang zh\u3001/lang en \u6216 /lang auto \u53EF\u8BBE\u7F6E\u3002`,
13220
+ usage: (p) => `\u7528\u6CD5\uFF1A@${p.bot} /lang zh | en | auto\uFF08\u8DDF\u968F\u63D0\u95EE\u8005\uFF09\u3002`
13221
+ }
13222
+ });
13223
+
13224
+ // packages/core/src/routing/group-language.ts
13225
+ var GROUP_LANG_VERB = "group_lang";
13226
+ var LANGS2 = ["zh", "en", "auto"];
13227
+ function isGroupLanguage(value) {
13228
+ return typeof value === "string" && LANGS2.includes(value);
13229
+ }
13230
+ var GroupLanguages = class {
13231
+ constructor(ctx) {
13232
+ this.ctx = ctx;
13233
+ }
13234
+ ctx;
13235
+ /** `/lang …` or a sentence that sets the group's language: answered here; false when `text` is neither. */
13236
+ async command(chat, text8) {
13237
+ const command = parseGroupLang(text8);
13238
+ if (command === void 0) return false;
13239
+ const bot = this.ctx.deps.config.botName;
13240
+ if (command.kind === "set") {
13241
+ this.ctx.deps.store.groupLanguage.setGroupLanguage(chat.scope, command.lang);
13242
+ await chat.say(groupLanguageSet(command.lang, chat.sender.name));
13243
+ return true;
13244
+ }
13245
+ const said2 = groupLanguageText.in(chat.lang());
13246
+ if (command.kind === "usage") {
13247
+ await chat.say(said2.usage({ bot }));
13248
+ return true;
13249
+ }
13250
+ const lang = this.ctx.deps.store.groupLanguage.groupLanguage(chat.scope);
13251
+ const state = lang === "zh" ? said2.isZh : lang === "en" ? said2.isEn : lang === "auto" ? said2.isAuto : said2.isNone;
13252
+ await chat.say(state({ bot }));
13253
+ return true;
13254
+ }
13255
+ /**
13256
+ * The first @mention in a chat that has no language: the card that asks its members which, once (the chat is recorded as asked
13257
+ * before the card goes, so two mentions at once put one; a card that could not be posted is asked again next time).
13258
+ */
13259
+ async askOnce(chat) {
13260
+ const { store, logger, config } = this.ctx.deps;
13261
+ if (!store.groupLanguage.markGroupLanguageAsked(chat.scope)) return;
13262
+ const buttons2 = LANGS2.map(
13263
+ (lang) => viewButtonOf({ title: GROUP_LANGUAGE_CARD.buttons[lang], verb: GROUP_LANG_VERB, data: { lang } })
13264
+ );
13265
+ try {
13266
+ await chat.send(
13267
+ noteMessage({
13268
+ title: GROUP_LANGUAGE_CARD.title,
13269
+ text: GROUP_LANGUAGE_CARD.text({ bot: config.botName }),
13270
+ tone: "accent",
13271
+ buttons: buttons2
13272
+ })
13273
+ );
13274
+ } catch (error) {
13275
+ store.groupLanguage.unmarkGroupLanguageAsked(chat.scope);
13276
+ logger.warn("group: the language card could not be posted; it is asked again next time", {
13277
+ error: errorName(error)
13278
+ });
13279
+ }
13280
+ }
13281
+ /** A pick on the language card, by anyone in the chat: the language is set, the card says so, and so does the chat. */
13282
+ async click(action, chat) {
13283
+ const lang = action.data["lang"];
13284
+ if (!isGroupLanguage(lang)) {
13285
+ await this.ctx.settle(
13286
+ action,
13287
+ chat,
13288
+ groupLanguageText.in(chat.lang()).usage({ bot: this.ctx.deps.config.botName })
13289
+ );
13290
+ return;
13291
+ }
13292
+ this.ctx.deps.store.groupLanguage.setGroupLanguage(chat.scope, lang);
13293
+ await this.ctx.settle(action, chat, groupLanguageSet(lang, chat.sender.name));
13294
+ }
13295
+ };
13296
+
13034
13297
  // packages/core/src/routing/show-here.ts
13035
13298
  var ShowHere = class {
13036
13299
  constructor(ctx) {
@@ -13206,11 +13469,12 @@ var PersonalRouter = class {
13206
13469
  const { assistant, remote } = this.ctx.deps;
13207
13470
  const userId = chat.sender.id;
13208
13471
  const understood = assistant.understands(userId);
13209
- const switching = understood && remote.enabled && switchCommand(message.text) !== null;
13210
- const forAssistant = understood && !exactCommand(message.text, { menuCommand, isKnownCommand }) && !switching;
13211
- const text8 = understood ? menuCommand(message.text) : this.text.textOf(message);
13472
+ const choice = message.choice === true;
13473
+ const switching = understood && !choice && remote.enabled && switchCommand(message.text) !== null;
13474
+ const forAssistant = understood && (choice || !exactCommand(message.text, { menuCommand, isKnownCommand })) && !switching;
13475
+ const text8 = understood && !choice ? menuCommand(message.text) : this.text.textOf(message);
13212
13476
  learnLang(this.ctx.deps.store, userId, message.text);
13213
- if (await this.beforeCredential(message, chat, text8, forAssistant)) return;
13477
+ if (!choice && await this.beforeCredential(message, chat, text8, forAssistant)) return;
13214
13478
  const key = await this.ctx.credentials.credential(chat, true);
13215
13479
  if (key === void 0) return;
13216
13480
  if (forAssistant && !this.text.talksToAgent(message)) await this.text.understand(message, chat, key);
@@ -13306,7 +13570,7 @@ var GroupRemote = class {
13306
13570
  const { remote } = this.ctx.deps;
13307
13571
  const bound = this.chatOf(chat);
13308
13572
  const attached = remote.attached(bound.id);
13309
- const args = this.agentArgs(words3.text, attached);
13573
+ const args = message.choice === true ? null : this.agentArgs(words3.text, attached);
13310
13574
  const provider = this.ctx.credentials.provider(chat.sender.id, key);
13311
13575
  if (args !== null) {
13312
13576
  const parsed = parseAgentCommand(args);
@@ -13327,11 +13591,12 @@ var GroupRemote = class {
13327
13591
  const { remote } = this.ctx.deps;
13328
13592
  const bound = this.chatOf(chat);
13329
13593
  const provider = this.ctx.credentials.provider(chat.sender.id, key);
13330
- if (naturalCommand(words3.text) === "/stop" || parseCommand(words3.text) === "stop") {
13594
+ const choice = message.choice === true;
13595
+ if (!choice && (naturalCommand(words3.text) === "/stop" || parseCommand(words3.text) === "stop")) {
13331
13596
  await remote.stop(bound, key, chat.conversation);
13332
13597
  return true;
13333
13598
  }
13334
- if (parseCommand(words3.text) !== null) return false;
13599
+ if (!choice && parseCommand(words3.text) !== null) return false;
13335
13600
  let quote = words3.quote;
13336
13601
  if (remote.windowMode(bound.id) === "card") {
13337
13602
  if (!remote.quotesWindow(bound.id, message)) return false;
@@ -13552,7 +13817,8 @@ async function answerWaitingQuestion(ctx, reply) {
13552
13817
  questions: questions.length,
13553
13818
  card: pending.cardId ? "edited" : "not editable"
13554
13819
  });
13555
- await settleCard(ctx, chat, pending.cardId, [...answers.answers], pickLang2(reply.text, questions[0]?.question));
13820
+ const lang = answerLang(store.prefs.getPrefs(chat.sender.id).lang, chat.locale, questions[0]?.question, reply.text);
13821
+ await settleCard(ctx, chat, pending.cardId, [...answers.answers], lang);
13556
13822
  return true;
13557
13823
  }
13558
13824
  function waitingQuestion(ctx, reply) {
@@ -13621,7 +13887,8 @@ var TurnStarter = class {
13621
13887
  return Promise.all([...this.running]).then(() => void 0);
13622
13888
  }
13623
13889
  /** The turn that runs (its end is `done`), or undefined when none was started (the person was told why). */
13624
- async start(request) {
13890
+ async start(started) {
13891
+ const request = { ...started, ...this.replyLang(started) };
13625
13892
  const { chat, review } = request;
13626
13893
  if (await this.answersQuestion(request)) return void 0;
13627
13894
  const key = review ? reviewTurnKey(chat.sender.id, chat.scope, review.itemId, review.pull) : chatTurnKey(chat.sender.id, chat.scope);
@@ -13778,11 +14045,22 @@ var TurnStarter = class {
13778
14045
  sent: request.sent ?? "",
13779
14046
  files: request.files ?? [],
13780
14047
  lang: review?.lang ?? chat.lang(),
14048
+ ...request.replyLang === void 0 ? {} : { replyLang: request.replyLang },
13781
14049
  ownThumbs: this.ownThumbs(request),
13782
14050
  notifies: this.ctx.deps.subscriptions !== void 0 && store.notifySubs.getNotifySub(chat.sender.id) !== null,
13783
14051
  review: review?.fields
13784
14052
  };
13785
14053
  }
14054
+ /**
14055
+ * The language a chat turn is to be answered in, when one was chosen (a review's answer follows its request's language): in a
14056
+ * group the group's (`groupChosenLang`; the sender's own choice is not read there), in a 1:1 chat the person's.
14057
+ */
14058
+ replyLang(request) {
14059
+ if (request.review !== void 0) return {};
14060
+ const { store } = this.ctx.deps;
14061
+ const lang = request.chat.scope ? groupChosenLang(store, request.chat.scope) : chosenLang(store.prefs.getPrefs(request.chat.sender.id));
14062
+ return lang === void 0 ? {} : { replyLang: lang };
14063
+ }
13786
14064
  hooks(request, run, turn, turnId) {
13787
14065
  const { review, chat } = request;
13788
14066
  const store = this.ctx.deps.store;
@@ -13866,9 +14144,11 @@ var PersonalText = class {
13866
14144
  starter;
13867
14145
  /**
13868
14146
  * What the person wrote, with a word of the commands menu read as its command and a sentence help teaches read as the
13869
- * command it means; a sentence for what this chat cannot do right now stays words.
14147
+ * command it means; a sentence for what this chat cannot do right now stays words. A choice picked on the assistant's card
14148
+ * is the words picked, never a command.
13870
14149
  */
13871
14150
  textOf(message, group = false) {
14151
+ if (message.choice === true) return message.text;
13872
14152
  const { github, assistant, remote, config } = this.ctx.deps;
13873
14153
  const userId = group ? void 0 : message.sender.id;
13874
14154
  const on = (test) => userId !== void 0 && test(userId);
@@ -13894,6 +14174,10 @@ var PersonalText = class {
13894
14174
  }
13895
14175
  async onText(message, chat, key) {
13896
14176
  const text8 = this.textOf(message);
14177
+ if (message.choice === true) {
14178
+ await this.sentOn(message, chat, key);
14179
+ return;
14180
+ }
13897
14181
  if (parseCommand(text8) === "unlink" && !message.text.startsWith("/")) {
13898
14182
  await chat.say(routingText.in(chat.lang()).unlinkAsk);
13899
14183
  return;
@@ -13945,7 +14229,7 @@ var PersonalText = class {
13945
14229
  const sent = messageText.withLinks(text8, message);
13946
14230
  const prompt = messageText.withQuote(sent, quote);
13947
14231
  const files = await this.attachments(message, chat, toAgent);
13948
- const control = this.sessionControl(userId, prompt, toAgent, quote, files, text8);
14232
+ const control = message.choice !== true && this.sessionControl(userId, prompt, toAgent, quote, files, text8);
13949
14233
  if (control && assistant.understands(userId)) {
13950
14234
  await this.understand(message, chat, key);
13951
14235
  } else if (prompt && toAgent && !control) {
@@ -14120,13 +14404,14 @@ var STOP_SAID = {
14120
14404
  item_closed: ["\u5DF2\u7ECF\u7ED3\u675F\u4E86\u3002", "it had already ended."]
14121
14405
  };
14122
14406
  var GroupRouter = class {
14123
- constructor(ctx, text8, sessions, onboarding, remote, starter) {
14407
+ constructor(ctx, text8, sessions, onboarding, remote, starter, languages) {
14124
14408
  this.ctx = ctx;
14125
14409
  this.text = text8;
14126
14410
  this.sessions = sessions;
14127
14411
  this.onboarding = onboarding;
14128
14412
  this.remote = remote;
14129
14413
  this.starter = starter;
14414
+ this.languages = languages;
14130
14415
  }
14131
14416
  ctx;
14132
14417
  text;
@@ -14134,6 +14419,7 @@ var GroupRouter = class {
14134
14419
  onboarding;
14135
14420
  remote;
14136
14421
  starter;
14422
+ languages;
14137
14423
  async route(event, chat) {
14138
14424
  if (!chat.scope) return;
14139
14425
  if (event.type === "action") await this.click(event, chat);
@@ -14147,6 +14433,10 @@ var GroupRouter = class {
14147
14433
  await this.onboarding.click(action, chat);
14148
14434
  return;
14149
14435
  }
14436
+ if (action.verb === GROUP_LANG_VERB) {
14437
+ await this.languages.click(action, chat);
14438
+ return;
14439
+ }
14150
14440
  const handler = this.ctx.deps.handlers.clicks.find((h) => h.verbs.includes(action.verb));
14151
14441
  if (handler?.inGroup !== void 0) await handler.inGroup(this.ctx.clickRoute(action, chat));
14152
14442
  }
@@ -14158,7 +14448,11 @@ var GroupRouter = class {
14158
14448
  async message(message, chat) {
14159
14449
  const words3 = this.words(message);
14160
14450
  learnLang(this.ctx.deps.store, chat.sender.id, message.text);
14161
- if (await this.groupOwn(message, chat, words3)) return;
14451
+ const choice = message.choice === true;
14452
+ const language = !choice && await this.languages.command(chat, words3.text);
14453
+ await this.languages.askOnce(chat);
14454
+ if (language) return;
14455
+ if (!choice && await this.groupOwn(message, chat, words3)) return;
14162
14456
  const route = {
14163
14457
  sender: chat.sender,
14164
14458
  conversation: chat.conversation,
@@ -14167,10 +14461,13 @@ var GroupRouter = class {
14167
14461
  message,
14168
14462
  text: words3.text
14169
14463
  };
14170
- for (const interceptor of this.ctx.deps.handlers.groupInterceptors) if (await interceptor.intercept(route)) return;
14464
+ if (!choice) {
14465
+ for (const interceptor of this.ctx.deps.handlers.groupInterceptors)
14466
+ if (await interceptor.intercept(route)) return;
14467
+ }
14171
14468
  const key = await this.ctx.credentials.credential(chat, false);
14172
14469
  if (key === void 0) {
14173
- const keep = parseCommand(words3.text) === null;
14470
+ const keep = choice || parseCommand(words3.text) === null;
14174
14471
  await this.onboarding.member(message, chat, { ...words3, keep, identity: false });
14175
14472
  return;
14176
14473
  }
@@ -14184,7 +14481,7 @@ var GroupRouter = class {
14184
14481
  const [first = ""] = pySplit(words3.text);
14185
14482
  const word2 = pyLower(first);
14186
14483
  if (empty || word2 === "/help") {
14187
- const lang = helpLang(this.ctx.deps.store, chat.sender.id, chat.locale, message.text);
14484
+ const lang = chat.lang();
14188
14485
  const assistant = this.ctx.deps.assistant.on(chat.sender.id);
14189
14486
  const groupContext = this.ctx.deps.groupHistory.kept(message.conversation.id);
14190
14487
  await chat.say(
@@ -14199,11 +14496,11 @@ var GroupRouter = class {
14199
14496
  return true;
14200
14497
  }
14201
14498
  if (github !== void 0 && word2 === "/github") {
14202
- await chat.say(groupText.in("en").githubOneToOne);
14499
+ await chat.say(groupText.in(chat.lang()).githubOneToOne);
14203
14500
  return true;
14204
14501
  }
14205
14502
  if (!this.oneToOneOnly(word2, words3.text, remote.enabled)) return false;
14206
- await chat.say(groupText.in("en").oneToOneOnly({ word: word2 }));
14503
+ await chat.say(groupText.in(chat.lang()).oneToOneOnly({ word: word2 }));
14207
14504
  return true;
14208
14505
  }
14209
14506
  /** A command of a 1:1 chat (`/agent attach|detach` binds the group: it stays the group's). */
@@ -14213,7 +14510,7 @@ var GroupRouter = class {
14213
14510
  return !(word2 === "/agent" && remoteOn && (verb === "attach" || verb === "detach"));
14214
14511
  }
14215
14512
  async withKey(message, chat, key, words3) {
14216
- const command = parseCommand(words3.text);
14513
+ const command = message.choice === true ? null : parseCommand(words3.text);
14217
14514
  if (command === "new") await this.sessions.new(chat, chat.scope);
14218
14515
  else if (command === "stop") await this.sessions.stop(chat, key, chat.scope);
14219
14516
  else if (command === "whoami") await this.whoami(chat, key);
@@ -14270,7 +14567,7 @@ ${quote}` : history || quote;
14270
14567
  };
14271
14568
  }
14272
14569
  async stopReviews(chat, pulls, text8) {
14273
- const zh = hasCjk2(text8);
14570
+ const zh = (groupChosenLang(this.ctx.deps.store, chat.scope) ?? (hasCjk2(text8) ? "zh" : "en")) === "zh";
14274
14571
  const said2 = groupText.in(zh ? "zh" : "en");
14275
14572
  const key = await this.ctx.credentials.credential(chat, false);
14276
14573
  let items;
@@ -14440,7 +14737,8 @@ var Router = class _Router {
14440
14737
  sessions,
14441
14738
  this.onboarding,
14442
14739
  new GroupRemote(this.ctx, this.text),
14443
- this.starter
14740
+ this.starter,
14741
+ new GroupLanguages(this.ctx)
14444
14742
  );
14445
14743
  }
14446
14744
  async handle(event) {
@@ -14517,7 +14815,7 @@ var Router = class _Router {
14517
14815
  */
14518
14816
  async withinRate(message, chat) {
14519
14817
  const group = message.conversation.kind === "group";
14520
- if (group && !message.mentioned || _Router.isStop(message.text, group)) return true;
14818
+ if (group && !message.mentioned || message.choice !== true && _Router.isStop(message.text, group)) return true;
14521
14819
  const { limits, securityEvent, metrics } = this.ctx.deps;
14522
14820
  const verdict = limits.checkMessage(chat.sender.id);
14523
14821
  if (verdict.allowed) return true;
@@ -14575,7 +14873,7 @@ var Router = class _Router {
14575
14873
  };
14576
14874
 
14577
14875
  // packages/core/src/approvals/sensitive.ts
14578
- var WORDS = /* @__PURE__ */ new Set([
14876
+ var WORDS2 = /* @__PURE__ */ new Set([
14579
14877
  "token",
14580
14878
  "secret",
14581
14879
  "secrets",
@@ -14680,7 +14978,7 @@ function isSensitiveName(name) {
14680
14978
  const parts2 = nameWords(String(name));
14681
14979
  if (parts2.length === 0 || COUNTERS.has(parts2.join(" "))) return false;
14682
14980
  return parts2.some((word2, i) => {
14683
- if (WORDS.has(word2)) return true;
14981
+ if (WORDS2.has(word2)) return true;
14684
14982
  const next = parts2[i + 1];
14685
14983
  if (next === void 0) return false;
14686
14984
  const pair = `${word2} ${next}`;
@@ -15539,6 +15837,10 @@ var AssistantChat = class {
15539
15837
  filesOnly(lang) {
15540
15838
  return assistantChatText.in(lang).filesOnly;
15541
15839
  }
15840
+ /** The language the person chose to be answered in, as the message's `channel_context` carries it (none: nothing is sent). */
15841
+ chosenLanguage(userId) {
15842
+ return replyLanguageContext(this.o.store.prefs.getPrefs(userId));
15843
+ }
15542
15844
  langOf(userId, text8) {
15543
15845
  return answerLang(this.o.store.prefs.getPrefs(userId).lang, this.o.clientLocale?.() ?? "", text8);
15544
15846
  }
@@ -15558,10 +15860,11 @@ var AssistantChat = class {
15558
15860
  const handing = { ...request };
15559
15861
  const sent = await this.handTo(handing, async (sessionId, ready) => {
15560
15862
  const folderId = typeof request.context["channel_message_id"] === "string" ? request.context["channel_message_id"] : "";
15561
- const context2 = request.files.length ? {
15863
+ const context2 = {
15562
15864
  ...request.context,
15563
- files: await this.handOverFiles(handing, sessionId, folderId || this.o.random.hexId(), request.files)
15564
- } : request.context;
15865
+ ...request.files.length ? { files: await this.handOverFiles(handing, sessionId, folderId || this.o.random.hexId(), request.files) } : {},
15866
+ ...this.chosenLanguage(request.userId)
15867
+ };
15565
15868
  await ready();
15566
15869
  return this.o.claw.assistant.send(request.credential, sessionId, request.prompt, {
15567
15870
  ...request.modelKey ? { modelKey: request.modelKey } : {},
@@ -15633,7 +15936,7 @@ var AssistantChat = class {
15633
15936
  async (_sessionId, ready) => {
15634
15937
  const files = request.files.length ? { files: await this.handOverFiles(handing, sessionId, this.o.random.hexId(), request.files) } : {};
15635
15938
  const attached = request.attached === void 0 ? {} : { attached: attachedOf(request.attached) };
15636
- const context2 = { ...files, ...attached };
15939
+ const context2 = { ...files, ...attached, ...this.chosenLanguage(request.userId) };
15637
15940
  await ready();
15638
15941
  return this.o.claw.assistant.send(request.credential, sessionId, request.prompt, {
15639
15942
  ...Object.keys(context2).length ? { context: context2 } : {},
@@ -16134,6 +16437,117 @@ var AssistantActions = class {
16134
16437
  }
16135
16438
  };
16136
16439
 
16440
+ // packages/core/src/assistant/choice-clicks.ts
16441
+ var CHOICE_VERB = "assistant_choice";
16442
+ var CHOICE_INPUT = "choice";
16443
+ function pickedOn(card, value) {
16444
+ const places = new Set(
16445
+ pyStr(value).split(",").map((p) => p.trim()).filter((p) => /^\d{1,2}$/.test(p)).map(Number)
16446
+ );
16447
+ return card.options.filter((_, i) => places.has(i));
16448
+ }
16449
+ function typedMessage(click, text8, id) {
16450
+ const { action } = click;
16451
+ return {
16452
+ type: "message",
16453
+ id,
16454
+ sender: click.sender,
16455
+ conversation: action.conversation,
16456
+ locale: action.locale,
16457
+ clientPlatform: action.clientPlatform,
16458
+ text: text8,
16459
+ mentioned: action.conversation.kind !== "personal",
16460
+ targeted: false,
16461
+ namesBotFirst: false,
16462
+ conversationName: "",
16463
+ mentions: [],
16464
+ links: [],
16465
+ quotes: [],
16466
+ files: [],
16467
+ unreadableFiles: [],
16468
+ provenance: "typed",
16469
+ choice: true
16470
+ // (an option that is a menu word or a help phrase is an answer, never that command)
16471
+ };
16472
+ }
16473
+ function langOf3(card, click) {
16474
+ if (card?.lang === "zh" || card?.lang === "en") return card.lang;
16475
+ return click.lang();
16476
+ }
16477
+ var AssistantChoiceClicks = class {
16478
+ constructor(deps) {
16479
+ this.deps = deps;
16480
+ }
16481
+ deps;
16482
+ verbs = [CHOICE_VERB];
16483
+ /** A 1:1 chat: answered with no Claw key (the message it sends is checked as any typed one is). */
16484
+ async beforeCredential(click) {
16485
+ await this.press(click);
16486
+ return true;
16487
+ }
16488
+ async inGroup(click) {
16489
+ await this.press(click);
16490
+ return true;
16491
+ }
16492
+ async press(click) {
16493
+ const buttons2 = this.deps.store.assistantButtons;
16494
+ const card = buttons2.getChoiceCard(pyStr(click.action.data["card"]));
16495
+ const lang = langOf3(card, click);
16496
+ const t4 = assistantCardText.in(lang);
16497
+ if (card === null || click.conversation.ref.kind === "personal" && card.userId !== click.sender.id) {
16498
+ await finish(click, t4.choiceGone);
16499
+ return;
16500
+ }
16501
+ if (card.state === "done") {
16502
+ await this.sent(click, card, card.chosen, lang, false);
16503
+ return;
16504
+ }
16505
+ if (card.state !== "open") {
16506
+ await click.settle(t4.choiceSending({ answer: card.chosen.join(t4.choiceJoin) }), { announce: false });
16507
+ return;
16508
+ }
16509
+ const chosen = pickedOn(card, click.action.data[CHOICE_INPUT]);
16510
+ if (!chosen.length || !card.multiple && chosen.length > 1) {
16511
+ await this.again(click, card, lang, t4.choiceNone);
16512
+ return;
16513
+ }
16514
+ if (!buttons2.claimChoiceCard(card.cardId, chosen)) {
16515
+ const now = buttons2.getChoiceCard(card.cardId) ?? card;
16516
+ if (now.state === "done") await this.sent(click, now, now.chosen, lang, false);
16517
+ else await click.settle(t4.choiceSending({ answer: now.chosen.join(t4.choiceJoin) }), { announce: false });
16518
+ return;
16519
+ }
16520
+ const text8 = chosen.join(t4.choiceJoin);
16521
+ try {
16522
+ await this.deps.deliver(typedMessage(click, text8, `${CHOICE_VERB}:${card.cardId}`));
16523
+ } catch (error) {
16524
+ buttons2.moveChoiceCard(card.cardId, "open", { onlyFrom: "busy" });
16525
+ this.deps.logger.warn("a choice could not be sent; its card is given back", { error: errorName(error) });
16526
+ await this.again(click, card, lang, t4.choiceFailed);
16527
+ return;
16528
+ }
16529
+ buttons2.moveChoiceCard(card.cardId, "done", { onlyFrom: "busy" });
16530
+ await this.sent(click, card, chosen, lang, true);
16531
+ }
16532
+ /** The card back, open, with `note` under its options. */
16533
+ async again(click, card, lang, note) {
16534
+ const model = { ...this.model(card, lang), note };
16535
+ await click.settle(viewMessage2({ name: "choiceCard", model }), { announce: false });
16536
+ }
16537
+ /** The card replaced by what was picked and sent (said as a line too when `announce`). */
16538
+ async sent(click, card, chosen, lang, announce) {
16539
+ const t4 = assistantCardText.in(lang);
16540
+ const view7 = viewMessage2({ name: "choiceChosen", model: { text: card.text, chosen, lang } });
16541
+ await click.settle(
16542
+ view7,
16543
+ announce ? { fallback: t4.choiceSent({ answer: chosen.join(t4.choiceJoin) }) } : { announce: false }
16544
+ );
16545
+ }
16546
+ model(card, lang) {
16547
+ return { text: card.text, cardId: card.cardId, options: card.options, multiple: card.multiple, note: "", lang };
16548
+ }
16549
+ };
16550
+
16137
16551
  // packages/core/src/assistant/auto-reply.ts
16138
16552
  function answersUnasked(watch) {
16139
16553
  return watch["state"] === "active" && watch["reply_allowed"] === true;
@@ -17142,6 +17556,24 @@ function suggestedRepliesOf(event, text8, styleOn, lang) {
17142
17556
  if (replies.length) return replies.slice(0, SUGGESTIONS_MAX);
17143
17557
  return [...ASSISTANT_SUGGESTED_REPLIES[lang ?? pickLang2(text8)]];
17144
17558
  }
17559
+ var CHOICE_OPTIONS_MIN = 2;
17560
+ var CHOICE_OPTIONS_MAX = 10;
17561
+ var CHOICE_OPTION_MAX = 80;
17562
+ function choiceOf(event) {
17563
+ const choice = record2(event["choices"]);
17564
+ if (event["type"] !== "assistant.say" || choice === void 0) return void 0;
17565
+ const raw = choice["options"];
17566
+ if (!Array.isArray(raw)) return void 0;
17567
+ const options = [];
17568
+ for (const entry of raw) {
17569
+ if (typeof entry !== "string" || /[\u0000-\u001f\u007f\u2028\u2029]/u.test(entry)) return void 0;
17570
+ const option = pySplit(entry).join(" ");
17571
+ if (!option || cpLength(option) > CHOICE_OPTION_MAX) return void 0;
17572
+ if (!options.includes(option)) options.push(option);
17573
+ }
17574
+ if (options.length < CHOICE_OPTIONS_MIN || options.length > CHOICE_OPTIONS_MAX) return void 0;
17575
+ return { options, multiple: choice["multiple"] === true };
17576
+ }
17145
17577
  var REMOTE_KINDS = /* @__PURE__ */ new Set(["done", "needs_you", "error", "resolved"]);
17146
17578
  function resolvedOf(remote) {
17147
17579
  const resolved = record2(remote["resolved"]);
@@ -17278,14 +17710,19 @@ ${text8}`;
17278
17710
  return all.length ? [...said2.slice(0, -1), { ...last, suggestions: toReplies(all.slice(0, SUGGESTIONS_MAX)) }] : said2;
17279
17711
  }
17280
17712
  /**
17281
- * The card with buttons the event asks for, recorded first: a result that waits, a write Claw holds, or a command put to the
17282
- * person; undefined for none. `holdsWords`: the card shows the words, which are then not said apart.
17713
+ * The card with buttons the event asks for, recorded first: a result that waits, a write Claw holds, a command or a choice put
17714
+ * to the person; undefined for none. `holdsWords`: the card shows the words, which are then not said apart.
17283
17715
  */
17284
17716
  card(userId, event, item, said2) {
17285
17717
  const inCard = said2.inCard ? said2.text : "";
17286
17718
  if (item?.canAccept)
17287
17719
  return { message: this.workCard(userId, item, said2.text, inCard, said2.lang), holdsWords: said2.inCard };
17288
- if (item !== null) return void 0;
17720
+ const asked3 = item === null ? this.askCard(userId, event, inCard, said2.lang) : void 0;
17721
+ if (asked3 !== void 0) return { message: asked3, holdsWords: said2.inCard };
17722
+ return this.choiceCard(userId, event, inCard, said2.lang, said2.inCard);
17723
+ }
17724
+ /** A write Claw holds for the person (Allow, Deny) or a command put to them (Run, Cancel), recorded first; undefined for none. */
17725
+ askCard(userId, event, inCard, lang) {
17289
17726
  const held = confirmWriteOf(event);
17290
17727
  if (held !== void 0) {
17291
17728
  const cardId2 = this.deps.store.assistantButtons.addConfirmCard(
@@ -17294,8 +17731,8 @@ ${text8}`;
17294
17731
  held.kind,
17295
17732
  held.allowLabel
17296
17733
  );
17297
- const model = { text: inCard || held.allowLabel, cardId: cardId2, allowLabel: held.allowLabel, lang: said2.lang };
17298
- return { message: view3({ name: "confirmWrite", model }), holdsWords: said2.inCard };
17734
+ const model = { text: inCard || held.allowLabel, cardId: cardId2, allowLabel: held.allowLabel, lang };
17735
+ return view3({ name: "confirmWrite", model });
17299
17736
  }
17300
17737
  const confirm = confirmActionOf(event, this.deps.exactCommand);
17301
17738
  if (confirm === void 0) return void 0;
@@ -17306,10 +17743,19 @@ ${text8}`;
17306
17743
  confirm.name,
17307
17744
  confirm.args
17308
17745
  );
17309
- return {
17310
- message: view3({ name: "actionCard", model: { text: inCard || command, cardId, command, lang: said2.lang } }),
17311
- holdsWords: said2.inCard
17312
- };
17746
+ return view3({ name: "actionCard", model: { text: inCard || command, cardId, command, lang } });
17747
+ }
17748
+ /**
17749
+ * A choice put to the person (`choices`): its options and Send, recorded first (what a press sends is read from the record).
17750
+ * Its Send sends what was picked as the person's own message.
17751
+ */
17752
+ choiceCard(userId, event, inCard, lang, holdsWords) {
17753
+ const choice = choiceOf(event);
17754
+ if (choice === void 0) return void 0;
17755
+ const { options, multiple } = choice;
17756
+ const cardId = this.deps.store.assistantButtons.addChoiceCard(userId, { text: inCard, options, multiple, lang });
17757
+ const model = { text: inCard, cardId, options, multiple, note: "", lang };
17758
+ return { message: view3({ name: "choiceCard", model }, inCard), holdsWords };
17313
17759
  }
17314
17760
  /** What was said, as AI answers with thumbs, each with the citations its own words cite (when the whole text cites at all). */
17315
17761
  said(event, text8, parts2) {
@@ -19311,7 +19757,11 @@ var terminalApprovalText = defineCatalog({
19311
19757
  newText: "With:",
19312
19758
  alwaysOption: (p) => `"Always allow (this session)" means: ${p.name}`,
19313
19759
  firstAnswerWins: "The terminal is asking too: the first answer, here or there, counts.",
19760
+ afterCard: "The terminal asks only after this card is answered or its wait is over: decline it here, or hand it back to the terminal and approve it there.",
19761
+ onInput: "To answer at the terminal instead, type anything there: the question goes back to it and this card is withdrawn.",
19314
19762
  tooLong: "Too long to show in full here: it can only be turned down from the phone; approve it at the terminal.",
19763
+ tooLongAfterCard: "Too long to show in full here: it can only be declined from the phone; the terminal asks once this card's wait is over.",
19764
+ tooLongOnInput: "Too long to show in full here: it can only be turned down from the phone; to approve it, type anything at the terminal and answer it there.",
19315
19765
  questionOf: (p) => `Question ${String(p.n)} of ${String(p.of)}`,
19316
19766
  optionYes: "Yes",
19317
19767
  optionSession: "Always allow (this session)",
@@ -19319,6 +19769,7 @@ var terminalApprovalText = defineCatalog({
19319
19769
  optionNoStop: "No, and stop",
19320
19770
  optionPlanApprove: "Approve (ask before edits)",
19321
19771
  optionPlanKeep: "Keep planning",
19772
+ optionToTerminal: "Ask me at the terminal",
19322
19773
  answeredInTerminal: "Answered in the terminal",
19323
19774
  noLongerWaiting: "No longer waiting for an answer",
19324
19775
  answeredInTerminalLine: "This request was answered in the terminal.",
@@ -19342,7 +19793,11 @@ var terminalApprovalText = defineCatalog({
19342
19793
  newText: "\u6539\u4E3A\uFF1A",
19343
19794
  alwaysOption: (p) => `\u201C\u672C\u4F1A\u8BDD\u59CB\u7EC8\u5141\u8BB8\u201D\u5373\uFF1A${p.name}`,
19344
19795
  firstAnswerWins: "\u7EC8\u7AEF\u91CC\u4E5F\u5728\u7B49\u4F60\u56DE\u7B54\uFF0C\u5148\u7B54\u7684\u7B97\u6570\u3002",
19796
+ afterCard: "\u7EC8\u7AEF\u8981\u7B49\u8FD9\u5F20\u5361\u7247\u7B54\u5B8C\u6216\u7B49\u5F85\u65F6\u95F4\u5230\u4E86\u624D\u4F1A\u95EE\uFF1A\u5728\u8FD9\u91CC\u62D2\u7EDD\uFF0C\u6216\u4EA4\u56DE\u7EC8\u7AEF\u3001\u5230\u7EC8\u7AEF\u91CC\u6279\u51C6\u3002",
19797
+ onInput: "\u60F3\u6539\u5728\u7EC8\u7AEF\u91CC\u56DE\u7B54\uFF0C\u5C31\u5728\u7EC8\u7AEF\u91CC\u968F\u4FBF\u8F93\u5165\u70B9\u4EC0\u4E48\uFF1A\u95EE\u9898\u56DE\u5230\u7EC8\u7AEF\uFF0C\u8FD9\u5F20\u5361\u7247\u968F\u4E4B\u64A4\u56DE\u3002",
19345
19798
  tooLong: "\u5185\u5BB9\u8FC7\u957F\uFF0C\u8FD9\u91CC\u663E\u793A\u4E0D\u5168\uFF1A\u624B\u673A\u4E0A\u53EA\u80FD\u62D2\u7EDD\uFF0C\u8BF7\u5230\u7EC8\u7AEF\u6279\u51C6\u3002",
19799
+ tooLongAfterCard: "\u5185\u5BB9\u8FC7\u957F\uFF0C\u8FD9\u91CC\u663E\u793A\u4E0D\u5168\uFF1A\u624B\u673A\u4E0A\u53EA\u80FD\u62D2\u7EDD\uFF1B\u8FD9\u5F20\u5361\u7247\u7684\u7B49\u5F85\u65F6\u95F4\u4E00\u8FC7\uFF0C\u7EC8\u7AEF\u5C31\u4F1A\u95EE\u4F60\u3002",
19800
+ tooLongOnInput: "\u5185\u5BB9\u8FC7\u957F\uFF0C\u8FD9\u91CC\u663E\u793A\u4E0D\u5168\uFF1A\u624B\u673A\u4E0A\u53EA\u80FD\u62D2\u7EDD\uFF1B\u8981\u6279\u51C6\uFF0C\u5728\u7EC8\u7AEF\u91CC\u968F\u4FBF\u8F93\u5165\u70B9\u4EC0\u4E48\uFF0C\u5230\u7EC8\u7AEF\u91CC\u56DE\u7B54\u3002",
19346
19801
  questionOf: (p) => `\u7B2C ${String(p.n)} \u9898\uFF0C\u5171 ${String(p.of)} \u9898`,
19347
19802
  optionYes: "\u5141\u8BB8",
19348
19803
  optionSession: "\u672C\u4F1A\u8BDD\u59CB\u7EC8\u5141\u8BB8",
@@ -19350,6 +19805,7 @@ var terminalApprovalText = defineCatalog({
19350
19805
  optionNoStop: "\u62D2\u7EDD\u5E76\u505C\u6B62",
19351
19806
  optionPlanApprove: "\u6279\u51C6\uFF08\u7F16\u8F91\u524D\u9010\u4E2A\u8BE2\u95EE\uFF09",
19352
19807
  optionPlanKeep: "\u7EE7\u7EED\u89C4\u5212",
19808
+ optionToTerminal: "\u4EA4\u56DE\u7EC8\u7AEF",
19353
19809
  answeredInTerminal: "\u5DF2\u5728\u7EC8\u7AEF\u91CC\u56DE\u7B54",
19354
19810
  noLongerWaiting: "\u4E0D\u518D\u7B49\u5F85\u56DE\u7B54",
19355
19811
  answeredInTerminalLine: "\u8FD9\u4E2A\u8BF7\u6C42\u5DF2\u5728\u7EC8\u7AEF\u91CC\u56DE\u7B54\u3002",
@@ -19481,6 +19937,9 @@ var TERMINAL_FALLBACK_BYTES = 4e3;
19481
19937
  var LINE_MAX = 400;
19482
19938
  var LABEL_MAX2 = 200;
19483
19939
  var OPTION_NAME_MAX = 200;
19940
+ function terminalAsksOf(ask3) {
19941
+ return TERMINAL_ASKS.find((t4) => t4 === ask3["terminal_asks"]);
19942
+ }
19484
19943
  function isTerminalAsk(ask3) {
19485
19944
  return ask3 !== void 0 && ask3["origin"] === "terminal";
19486
19945
  }
@@ -19503,7 +19962,8 @@ function optionTitle(option, lang) {
19503
19962
  ["deny", words3.optionNo],
19504
19963
  ["deny-stop", words3.optionNoStop],
19505
19964
  ["plan-approve", words3.optionPlanApprove],
19506
- ["plan-keep", words3.optionPlanKeep]
19965
+ ["plan-keep", words3.optionPlanKeep],
19966
+ ["terminal", words3.optionToTerminal]
19507
19967
  ]);
19508
19968
  return titles.get(option.optionId);
19509
19969
  }
@@ -19622,10 +20082,15 @@ function notesOf(ask3, complete, options, words3) {
19622
20082
  const index = Number(ask3["question_index"]);
19623
20083
  if (Number.isInteger(total) && total > 1 && Number.isInteger(index))
19624
20084
  notes.push(words3.questionOf({ n: index + 1, of: total }));
19625
- if (!complete) notes.push(words3.tooLong);
20085
+ const asks = terminalAsksOf(ask3);
20086
+ if (!complete)
20087
+ notes.push(
20088
+ asks === "after_card" ? words3.tooLongAfterCard : asks === "on_input" ? words3.tooLongOnInput : words3.tooLong
20089
+ );
19626
20090
  for (const o of shownOptions(options))
19627
20091
  if (o.kind === "allow_always") notes.push(words3.alwaysOption({ name: lit(o.name, OPTION_NAME_MAX) }));
19628
- notes.push(words3.firstAnswerWins);
20092
+ if (asks === void 0) notes.push(words3.firstAnswerWins);
20093
+ else if (complete) notes.push(asks === "after_card" ? words3.afterCard : words3.onInput);
19629
20094
  return notes;
19630
20095
  }
19631
20096
  function terminalAskView(input, lang = "en") {
@@ -22031,9 +22496,9 @@ var SUMMARY_CHARS = 1200;
22031
22496
  var DM_SUMMARY_CHARS = 700;
22032
22497
  var VERDICT = pyRegExp(String.raw`^\s*VERDICT:\s*(CLEAN|BLOCKING|NOT_REVIEWED)\s*$`, "i");
22033
22498
  var CJK5 = /[一-鿿]/u;
22034
- var NAMES2 = Object.keys(PLAIN_WORDS).sort((a, b3) => b3.length - a.length).map((n) => n.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&")).join("|");
22499
+ var NAMES3 = Object.keys(PLAIN_WORDS).sort((a, b3) => b3.length - a.length).map((n) => n.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&")).join("|");
22035
22500
  var RAW_NAME = new RegExp(
22036
- `\`(?<![A-Za-z0-9_])(${NAMES2})(?![A-Za-z0-9_])\`|(?<![A-Za-z0-9_])(${NAMES2})(?![A-Za-z0-9_])`,
22501
+ `\`(?<![A-Za-z0-9_])(${NAMES3})(?![A-Za-z0-9_])\`|(?<![A-Za-z0-9_])(${NAMES3})(?![A-Za-z0-9_])`,
22037
22502
  "gu"
22038
22503
  );
22039
22504
  function plainWords(text8) {
@@ -26142,6 +26607,7 @@ __export(remote_view_exports, {
26142
26607
  statusView: () => statusView,
26143
26608
  takeoverView: () => takeoverView,
26144
26609
  terminalAskView: () => terminalAskView,
26610
+ terminalAsksOf: () => terminalAsksOf,
26145
26611
  windowView: () => windowView
26146
26612
  });
26147
26613
 
@@ -28257,8 +28723,8 @@ var STATUS_REFUSALS = /* @__PURE__ */ new Map([
28257
28723
  [403, "keyRejected"]
28258
28724
  ]);
28259
28725
  var ERROR_CODE = /^[a-z][a-z0-9_]{0,59}$/u;
28260
- function saidIn(lang, sentence2) {
28261
- return remoteAgentText.in(lang)[sentence2];
28726
+ function saidIn(lang, sentence3) {
28727
+ return remoteAgentText.in(lang)[sentence3];
28262
28728
  }
28263
28729
  function failureText2(err2, errors, now, lang = "en") {
28264
28730
  if (err2 instanceof Reconnecting) return offlineText(err2.connector, now, lang);
@@ -28497,9 +28963,9 @@ var Targets = class {
28497
28963
  */
28498
28964
  takeoverOf(chatKey, connector, agent, data, now, lang) {
28499
28965
  const entry = resumableOf(agent).find((e) => e.ref === textOf6(data["resume"]));
28500
- const no = (sentence2) => ({
28966
+ const no = (sentence3) => ({
28501
28967
  entry: null,
28502
- problem: saidIn(lang, sentence2)
28968
+ problem: saidIn(lang, sentence3)
28503
28969
  });
28504
28970
  if (entry === void 0) return no("conversationGone");
28505
28971
  if (entry.state === WATCH_STATE && data["takeover"]) {
@@ -28520,9 +28986,9 @@ var Targets = class {
28520
28986
  * process kept when it was typed (the card carries only a token).
28521
28987
  */
28522
28988
  folderOf(chatKey, agent, data, now, lang) {
28523
- const no = (sentence2) => ({
28989
+ const no = (sentence3) => ({
28524
28990
  path: "",
28525
- problem: saidIn(lang, sentence2)
28991
+ problem: saidIn(lang, sentence3)
28526
28992
  });
28527
28993
  if ("typed" in data) {
28528
28994
  const path = this.chats.typedPath(chatKey, textOf6(data["typed"]), now);
@@ -29333,7 +29799,7 @@ function older(version, than) {
29333
29799
  function isCode(reason) {
29334
29800
  return CODE4.test(reason);
29335
29801
  }
29336
- function sentence(code3, lang, setup) {
29802
+ function sentence2(code3, lang, setup) {
29337
29803
  const text8 = own(connectorReasonText.in(lang), code3) ?? "";
29338
29804
  return code3 === "revoked" ? text8.replaceAll("{login}", setup.login()) : text8;
29339
29805
  }
@@ -29357,7 +29823,7 @@ function explain(code3, version = null, setup = new Setup("", null)) {
29357
29823
  if (!KNOWN.has(code3)) return both((lang) => connectorReasonHints.in(lang).unknown({ code: code3 }));
29358
29824
  return both((lang) => {
29359
29825
  const after = hintAfter(code3, lang, version);
29360
- const own3 = sentence(code3, lang, setup);
29826
+ const own3 = sentence2(code3, lang, setup);
29361
29827
  return after ? `${own3}${after.sep}${after.hint}` : own3;
29362
29828
  });
29363
29829
  }
@@ -32756,6 +33222,7 @@ export {
32756
33222
  AssistantActionClicks,
32757
33223
  AssistantActions,
32758
33224
  AssistantChat as AssistantChatService,
33225
+ AssistantChoiceClicks,
32759
33226
  BOARD_CARDS,
32760
33227
  BOARD_LINES,
32761
33228
  BOARD_RETRY_MAX,
@@ -32989,6 +33456,7 @@ export {
32989
33456
  TAG,
32990
33457
  TARGET_LINES,
32991
33458
  TARGET_MAX,
33459
+ TERMINAL_ASKS,
32992
33460
  TERMINAL_HOOK_COMMANDS,
32993
33461
  TERMINAL_SESSION_LIMITS,
32994
33462
  THUMB_COMMENT_MAX,
@@ -33105,8 +33573,10 @@ export {
33105
33573
  githubConfirmButtons,
33106
33574
  githubConfirmMessage,
33107
33575
  githubConnectMessage,
33576
+ groupChosenLang,
33108
33577
  groupContextLines,
33109
33578
  groupContextText,
33579
+ groupLangOf,
33110
33580
  guardrailsText,
33111
33581
  hasCjk2 as hasCjk,
33112
33582
  hasControl,