@primusclaw/personal-agent 0.5.0 → 0.6.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 = {
@@ -6076,6 +6097,19 @@ function deliveryLine(delivery, url) {
6076
6097
  return turnEnglish.deliveryLine(plain(delivery.name), sizeText(delivery.size), caption, url);
6077
6098
  }
6078
6099
 
6100
+ // packages/core/src/language/chosen.ts
6101
+ function chosenLang(prefs) {
6102
+ if (!prefs.langSet) return void 0;
6103
+ return prefs.lang === "zh" || prefs.lang === "en" ? prefs.lang : void 0;
6104
+ }
6105
+ function replyLanguageContext(prefs) {
6106
+ const lang = chosenLang(prefs);
6107
+ return lang === void 0 ? {} : { reply_language: lang };
6108
+ }
6109
+ function replyLanguageLine(lang) {
6110
+ return `[Reply language: ${lang === "zh" ? "Chinese" : "English"}]`;
6111
+ }
6112
+
6079
6113
  // packages/core/src/language/detect.ts
6080
6114
  var CJK2 = /[㐀-䶿一-鿿豈-﫿]/u;
6081
6115
  function hasCjk2(text8) {
@@ -6209,9 +6243,13 @@ ${text8}` : quote || text8;
6209
6243
  function messageOf(ctx) {
6210
6244
  const said2 = withQuote(ctx.request.sent || ctx.request.text, ctx.request.quote ?? "");
6211
6245
  const note = ctx.record.filesNote;
6212
- return said2 && note ? `${said2}
6246
+ const message = said2 && note ? `${said2}
6213
6247
 
6214
6248
  ${note}` : said2 || note;
6249
+ const chosen = ctx.request.replyLang;
6250
+ return message && chosen !== void 0 ? `${message}
6251
+
6252
+ ${replyLanguageLine(chosen)}` : message;
6215
6253
  }
6216
6254
 
6217
6255
  // packages/core/src/locales/claw.ts
@@ -8837,6 +8875,7 @@ var TurnEngine = class {
8837
8875
  quote: run.quote,
8838
8876
  sent: run.sent,
8839
8877
  lang: run.lang,
8878
+ ...run.replyLang === void 0 ? {} : { replyLang: run.replyLang },
8840
8879
  files: run.files,
8841
8880
  ...review ? reviewRequest(review) : {}
8842
8881
  };
@@ -10275,7 +10314,7 @@ var CHAT_ENTRIES = [
10275
10314
  category: "chat",
10276
10315
  zh: "\u8981\u4F60\u62CD\u677F\u65F6\u6211\u53D1\u5361\u7247\uFF1A\u70B9\u6309\u94AE\u56DE\u7B54\u3001\u6279\u51C6\u6216\u62D2\u7EDD",
10277
10316
  en: "When I need you, I send a card: tap to answer, approve or deny",
10278
- covers: ["card:decision", "card:answer", "card:assistant_action"],
10317
+ covers: ["card:decision", "card:answer", "card:assistant_action", "card:assistant_choice"],
10279
10318
  where: "both"
10280
10319
  }),
10281
10320
  capability({
@@ -13206,11 +13245,12 @@ var PersonalRouter = class {
13206
13245
  const { assistant, remote } = this.ctx.deps;
13207
13246
  const userId = chat.sender.id;
13208
13247
  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);
13248
+ const choice = message.choice === true;
13249
+ const switching = understood && !choice && remote.enabled && switchCommand(message.text) !== null;
13250
+ const forAssistant = understood && (choice || !exactCommand(message.text, { menuCommand, isKnownCommand })) && !switching;
13251
+ const text8 = understood && !choice ? menuCommand(message.text) : this.text.textOf(message);
13212
13252
  learnLang(this.ctx.deps.store, userId, message.text);
13213
- if (await this.beforeCredential(message, chat, text8, forAssistant)) return;
13253
+ if (!choice && await this.beforeCredential(message, chat, text8, forAssistant)) return;
13214
13254
  const key = await this.ctx.credentials.credential(chat, true);
13215
13255
  if (key === void 0) return;
13216
13256
  if (forAssistant && !this.text.talksToAgent(message)) await this.text.understand(message, chat, key);
@@ -13306,7 +13346,7 @@ var GroupRemote = class {
13306
13346
  const { remote } = this.ctx.deps;
13307
13347
  const bound = this.chatOf(chat);
13308
13348
  const attached = remote.attached(bound.id);
13309
- const args = this.agentArgs(words3.text, attached);
13349
+ const args = message.choice === true ? null : this.agentArgs(words3.text, attached);
13310
13350
  const provider = this.ctx.credentials.provider(chat.sender.id, key);
13311
13351
  if (args !== null) {
13312
13352
  const parsed = parseAgentCommand(args);
@@ -13327,11 +13367,12 @@ var GroupRemote = class {
13327
13367
  const { remote } = this.ctx.deps;
13328
13368
  const bound = this.chatOf(chat);
13329
13369
  const provider = this.ctx.credentials.provider(chat.sender.id, key);
13330
- if (naturalCommand(words3.text) === "/stop" || parseCommand(words3.text) === "stop") {
13370
+ const choice = message.choice === true;
13371
+ if (!choice && (naturalCommand(words3.text) === "/stop" || parseCommand(words3.text) === "stop")) {
13331
13372
  await remote.stop(bound, key, chat.conversation);
13332
13373
  return true;
13333
13374
  }
13334
- if (parseCommand(words3.text) !== null) return false;
13375
+ if (!choice && parseCommand(words3.text) !== null) return false;
13335
13376
  let quote = words3.quote;
13336
13377
  if (remote.windowMode(bound.id) === "card") {
13337
13378
  if (!remote.quotesWindow(bound.id, message)) return false;
@@ -13552,7 +13593,8 @@ async function answerWaitingQuestion(ctx, reply) {
13552
13593
  questions: questions.length,
13553
13594
  card: pending.cardId ? "edited" : "not editable"
13554
13595
  });
13555
- await settleCard(ctx, chat, pending.cardId, [...answers.answers], pickLang2(reply.text, questions[0]?.question));
13596
+ const lang = answerLang(store.prefs.getPrefs(chat.sender.id).lang, chat.locale, questions[0]?.question, reply.text);
13597
+ await settleCard(ctx, chat, pending.cardId, [...answers.answers], lang);
13556
13598
  return true;
13557
13599
  }
13558
13600
  function waitingQuestion(ctx, reply) {
@@ -13621,7 +13663,8 @@ var TurnStarter = class {
13621
13663
  return Promise.all([...this.running]).then(() => void 0);
13622
13664
  }
13623
13665
  /** The turn that runs (its end is `done`), or undefined when none was started (the person was told why). */
13624
- async start(request) {
13666
+ async start(started) {
13667
+ const request = { ...started, ...this.replyLang(started) };
13625
13668
  const { chat, review } = request;
13626
13669
  if (await this.answersQuestion(request)) return void 0;
13627
13670
  const key = review ? reviewTurnKey(chat.sender.id, chat.scope, review.itemId, review.pull) : chatTurnKey(chat.sender.id, chat.scope);
@@ -13778,11 +13821,18 @@ var TurnStarter = class {
13778
13821
  sent: request.sent ?? "",
13779
13822
  files: request.files ?? [],
13780
13823
  lang: review?.lang ?? chat.lang(),
13824
+ ...request.replyLang === void 0 ? {} : { replyLang: request.replyLang },
13781
13825
  ownThumbs: this.ownThumbs(request),
13782
13826
  notifies: this.ctx.deps.subscriptions !== void 0 && store.notifySubs.getNotifySub(chat.sender.id) !== null,
13783
13827
  review: review?.fields
13784
13828
  };
13785
13829
  }
13830
+ /** The language the sender chose to be answered in, for a chat turn (a review's answer follows its request's language). */
13831
+ replyLang(request) {
13832
+ if (request.review !== void 0) return {};
13833
+ const lang = chosenLang(this.ctx.deps.store.prefs.getPrefs(request.chat.sender.id));
13834
+ return lang === void 0 ? {} : { replyLang: lang };
13835
+ }
13786
13836
  hooks(request, run, turn, turnId) {
13787
13837
  const { review, chat } = request;
13788
13838
  const store = this.ctx.deps.store;
@@ -13866,9 +13916,11 @@ var PersonalText = class {
13866
13916
  starter;
13867
13917
  /**
13868
13918
  * 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.
13919
+ * command it means; a sentence for what this chat cannot do right now stays words. A choice picked on the assistant's card
13920
+ * is the words picked, never a command.
13870
13921
  */
13871
13922
  textOf(message, group = false) {
13923
+ if (message.choice === true) return message.text;
13872
13924
  const { github, assistant, remote, config } = this.ctx.deps;
13873
13925
  const userId = group ? void 0 : message.sender.id;
13874
13926
  const on = (test) => userId !== void 0 && test(userId);
@@ -13894,6 +13946,10 @@ var PersonalText = class {
13894
13946
  }
13895
13947
  async onText(message, chat, key) {
13896
13948
  const text8 = this.textOf(message);
13949
+ if (message.choice === true) {
13950
+ await this.sentOn(message, chat, key);
13951
+ return;
13952
+ }
13897
13953
  if (parseCommand(text8) === "unlink" && !message.text.startsWith("/")) {
13898
13954
  await chat.say(routingText.in(chat.lang()).unlinkAsk);
13899
13955
  return;
@@ -13945,7 +14001,7 @@ var PersonalText = class {
13945
14001
  const sent = messageText.withLinks(text8, message);
13946
14002
  const prompt = messageText.withQuote(sent, quote);
13947
14003
  const files = await this.attachments(message, chat, toAgent);
13948
- const control = this.sessionControl(userId, prompt, toAgent, quote, files, text8);
14004
+ const control = message.choice !== true && this.sessionControl(userId, prompt, toAgent, quote, files, text8);
13949
14005
  if (control && assistant.understands(userId)) {
13950
14006
  await this.understand(message, chat, key);
13951
14007
  } else if (prompt && toAgent && !control) {
@@ -14158,7 +14214,8 @@ var GroupRouter = class {
14158
14214
  async message(message, chat) {
14159
14215
  const words3 = this.words(message);
14160
14216
  learnLang(this.ctx.deps.store, chat.sender.id, message.text);
14161
- if (await this.groupOwn(message, chat, words3)) return;
14217
+ const choice = message.choice === true;
14218
+ if (!choice && await this.groupOwn(message, chat, words3)) return;
14162
14219
  const route = {
14163
14220
  sender: chat.sender,
14164
14221
  conversation: chat.conversation,
@@ -14167,10 +14224,13 @@ var GroupRouter = class {
14167
14224
  message,
14168
14225
  text: words3.text
14169
14226
  };
14170
- for (const interceptor of this.ctx.deps.handlers.groupInterceptors) if (await interceptor.intercept(route)) return;
14227
+ if (!choice) {
14228
+ for (const interceptor of this.ctx.deps.handlers.groupInterceptors)
14229
+ if (await interceptor.intercept(route)) return;
14230
+ }
14171
14231
  const key = await this.ctx.credentials.credential(chat, false);
14172
14232
  if (key === void 0) {
14173
- const keep = parseCommand(words3.text) === null;
14233
+ const keep = choice || parseCommand(words3.text) === null;
14174
14234
  await this.onboarding.member(message, chat, { ...words3, keep, identity: false });
14175
14235
  return;
14176
14236
  }
@@ -14213,7 +14273,7 @@ var GroupRouter = class {
14213
14273
  return !(word2 === "/agent" && remoteOn && (verb === "attach" || verb === "detach"));
14214
14274
  }
14215
14275
  async withKey(message, chat, key, words3) {
14216
- const command = parseCommand(words3.text);
14276
+ const command = message.choice === true ? null : parseCommand(words3.text);
14217
14277
  if (command === "new") await this.sessions.new(chat, chat.scope);
14218
14278
  else if (command === "stop") await this.sessions.stop(chat, key, chat.scope);
14219
14279
  else if (command === "whoami") await this.whoami(chat, key);
@@ -14517,7 +14577,7 @@ var Router = class _Router {
14517
14577
  */
14518
14578
  async withinRate(message, chat) {
14519
14579
  const group = message.conversation.kind === "group";
14520
- if (group && !message.mentioned || _Router.isStop(message.text, group)) return true;
14580
+ if (group && !message.mentioned || message.choice !== true && _Router.isStop(message.text, group)) return true;
14521
14581
  const { limits, securityEvent, metrics } = this.ctx.deps;
14522
14582
  const verdict = limits.checkMessage(chat.sender.id);
14523
14583
  if (verdict.allowed) return true;
@@ -15539,6 +15599,10 @@ var AssistantChat = class {
15539
15599
  filesOnly(lang) {
15540
15600
  return assistantChatText.in(lang).filesOnly;
15541
15601
  }
15602
+ /** The language the person chose to be answered in, as the message's `channel_context` carries it (none: nothing is sent). */
15603
+ chosenLanguage(userId) {
15604
+ return replyLanguageContext(this.o.store.prefs.getPrefs(userId));
15605
+ }
15542
15606
  langOf(userId, text8) {
15543
15607
  return answerLang(this.o.store.prefs.getPrefs(userId).lang, this.o.clientLocale?.() ?? "", text8);
15544
15608
  }
@@ -15558,10 +15622,11 @@ var AssistantChat = class {
15558
15622
  const handing = { ...request };
15559
15623
  const sent = await this.handTo(handing, async (sessionId, ready) => {
15560
15624
  const folderId = typeof request.context["channel_message_id"] === "string" ? request.context["channel_message_id"] : "";
15561
- const context2 = request.files.length ? {
15625
+ const context2 = {
15562
15626
  ...request.context,
15563
- files: await this.handOverFiles(handing, sessionId, folderId || this.o.random.hexId(), request.files)
15564
- } : request.context;
15627
+ ...request.files.length ? { files: await this.handOverFiles(handing, sessionId, folderId || this.o.random.hexId(), request.files) } : {},
15628
+ ...this.chosenLanguage(request.userId)
15629
+ };
15565
15630
  await ready();
15566
15631
  return this.o.claw.assistant.send(request.credential, sessionId, request.prompt, {
15567
15632
  ...request.modelKey ? { modelKey: request.modelKey } : {},
@@ -15633,7 +15698,7 @@ var AssistantChat = class {
15633
15698
  async (_sessionId, ready) => {
15634
15699
  const files = request.files.length ? { files: await this.handOverFiles(handing, sessionId, this.o.random.hexId(), request.files) } : {};
15635
15700
  const attached = request.attached === void 0 ? {} : { attached: attachedOf(request.attached) };
15636
- const context2 = { ...files, ...attached };
15701
+ const context2 = { ...files, ...attached, ...this.chosenLanguage(request.userId) };
15637
15702
  await ready();
15638
15703
  return this.o.claw.assistant.send(request.credential, sessionId, request.prompt, {
15639
15704
  ...Object.keys(context2).length ? { context: context2 } : {},
@@ -16134,6 +16199,117 @@ var AssistantActions = class {
16134
16199
  }
16135
16200
  };
16136
16201
 
16202
+ // packages/core/src/assistant/choice-clicks.ts
16203
+ var CHOICE_VERB = "assistant_choice";
16204
+ var CHOICE_INPUT = "choice";
16205
+ function pickedOn(card, value) {
16206
+ const places = new Set(
16207
+ pyStr(value).split(",").map((p) => p.trim()).filter((p) => /^\d{1,2}$/.test(p)).map(Number)
16208
+ );
16209
+ return card.options.filter((_, i) => places.has(i));
16210
+ }
16211
+ function typedMessage(click, text8, id) {
16212
+ const { action } = click;
16213
+ return {
16214
+ type: "message",
16215
+ id,
16216
+ sender: click.sender,
16217
+ conversation: action.conversation,
16218
+ locale: action.locale,
16219
+ clientPlatform: action.clientPlatform,
16220
+ text: text8,
16221
+ mentioned: action.conversation.kind !== "personal",
16222
+ targeted: false,
16223
+ namesBotFirst: false,
16224
+ conversationName: "",
16225
+ mentions: [],
16226
+ links: [],
16227
+ quotes: [],
16228
+ files: [],
16229
+ unreadableFiles: [],
16230
+ provenance: "typed",
16231
+ choice: true
16232
+ // (an option that is a menu word or a help phrase is an answer, never that command)
16233
+ };
16234
+ }
16235
+ function langOf3(card, click) {
16236
+ if (card?.lang === "zh" || card?.lang === "en") return card.lang;
16237
+ return click.lang();
16238
+ }
16239
+ var AssistantChoiceClicks = class {
16240
+ constructor(deps) {
16241
+ this.deps = deps;
16242
+ }
16243
+ deps;
16244
+ verbs = [CHOICE_VERB];
16245
+ /** A 1:1 chat: answered with no Claw key (the message it sends is checked as any typed one is). */
16246
+ async beforeCredential(click) {
16247
+ await this.press(click);
16248
+ return true;
16249
+ }
16250
+ async inGroup(click) {
16251
+ await this.press(click);
16252
+ return true;
16253
+ }
16254
+ async press(click) {
16255
+ const buttons2 = this.deps.store.assistantButtons;
16256
+ const card = buttons2.getChoiceCard(pyStr(click.action.data["card"]));
16257
+ const lang = langOf3(card, click);
16258
+ const t4 = assistantCardText.in(lang);
16259
+ if (card === null || click.conversation.ref.kind === "personal" && card.userId !== click.sender.id) {
16260
+ await finish(click, t4.choiceGone);
16261
+ return;
16262
+ }
16263
+ if (card.state === "done") {
16264
+ await this.sent(click, card, card.chosen, lang, false);
16265
+ return;
16266
+ }
16267
+ if (card.state !== "open") {
16268
+ await click.settle(t4.choiceSending({ answer: card.chosen.join(t4.choiceJoin) }), { announce: false });
16269
+ return;
16270
+ }
16271
+ const chosen = pickedOn(card, click.action.data[CHOICE_INPUT]);
16272
+ if (!chosen.length || !card.multiple && chosen.length > 1) {
16273
+ await this.again(click, card, lang, t4.choiceNone);
16274
+ return;
16275
+ }
16276
+ if (!buttons2.claimChoiceCard(card.cardId, chosen)) {
16277
+ const now = buttons2.getChoiceCard(card.cardId) ?? card;
16278
+ if (now.state === "done") await this.sent(click, now, now.chosen, lang, false);
16279
+ else await click.settle(t4.choiceSending({ answer: now.chosen.join(t4.choiceJoin) }), { announce: false });
16280
+ return;
16281
+ }
16282
+ const text8 = chosen.join(t4.choiceJoin);
16283
+ try {
16284
+ await this.deps.deliver(typedMessage(click, text8, `${CHOICE_VERB}:${card.cardId}`));
16285
+ } catch (error) {
16286
+ buttons2.moveChoiceCard(card.cardId, "open", { onlyFrom: "busy" });
16287
+ this.deps.logger.warn("a choice could not be sent; its card is given back", { error: errorName(error) });
16288
+ await this.again(click, card, lang, t4.choiceFailed);
16289
+ return;
16290
+ }
16291
+ buttons2.moveChoiceCard(card.cardId, "done", { onlyFrom: "busy" });
16292
+ await this.sent(click, card, chosen, lang, true);
16293
+ }
16294
+ /** The card back, open, with `note` under its options. */
16295
+ async again(click, card, lang, note) {
16296
+ const model = { ...this.model(card, lang), note };
16297
+ await click.settle(viewMessage2({ name: "choiceCard", model }), { announce: false });
16298
+ }
16299
+ /** The card replaced by what was picked and sent (said as a line too when `announce`). */
16300
+ async sent(click, card, chosen, lang, announce) {
16301
+ const t4 = assistantCardText.in(lang);
16302
+ const view7 = viewMessage2({ name: "choiceChosen", model: { text: card.text, chosen, lang } });
16303
+ await click.settle(
16304
+ view7,
16305
+ announce ? { fallback: t4.choiceSent({ answer: chosen.join(t4.choiceJoin) }) } : { announce: false }
16306
+ );
16307
+ }
16308
+ model(card, lang) {
16309
+ return { text: card.text, cardId: card.cardId, options: card.options, multiple: card.multiple, note: "", lang };
16310
+ }
16311
+ };
16312
+
16137
16313
  // packages/core/src/assistant/auto-reply.ts
16138
16314
  function answersUnasked(watch) {
16139
16315
  return watch["state"] === "active" && watch["reply_allowed"] === true;
@@ -17142,6 +17318,24 @@ function suggestedRepliesOf(event, text8, styleOn, lang) {
17142
17318
  if (replies.length) return replies.slice(0, SUGGESTIONS_MAX);
17143
17319
  return [...ASSISTANT_SUGGESTED_REPLIES[lang ?? pickLang2(text8)]];
17144
17320
  }
17321
+ var CHOICE_OPTIONS_MIN = 2;
17322
+ var CHOICE_OPTIONS_MAX = 10;
17323
+ var CHOICE_OPTION_MAX = 80;
17324
+ function choiceOf(event) {
17325
+ const choice = record2(event["choices"]);
17326
+ if (event["type"] !== "assistant.say" || choice === void 0) return void 0;
17327
+ const raw = choice["options"];
17328
+ if (!Array.isArray(raw)) return void 0;
17329
+ const options = [];
17330
+ for (const entry of raw) {
17331
+ if (typeof entry !== "string" || /[\u0000-\u001f\u007f\u2028\u2029]/u.test(entry)) return void 0;
17332
+ const option = pySplit(entry).join(" ");
17333
+ if (!option || cpLength(option) > CHOICE_OPTION_MAX) return void 0;
17334
+ if (!options.includes(option)) options.push(option);
17335
+ }
17336
+ if (options.length < CHOICE_OPTIONS_MIN || options.length > CHOICE_OPTIONS_MAX) return void 0;
17337
+ return { options, multiple: choice["multiple"] === true };
17338
+ }
17145
17339
  var REMOTE_KINDS = /* @__PURE__ */ new Set(["done", "needs_you", "error", "resolved"]);
17146
17340
  function resolvedOf(remote) {
17147
17341
  const resolved = record2(remote["resolved"]);
@@ -17278,14 +17472,19 @@ ${text8}`;
17278
17472
  return all.length ? [...said2.slice(0, -1), { ...last, suggestions: toReplies(all.slice(0, SUGGESTIONS_MAX)) }] : said2;
17279
17473
  }
17280
17474
  /**
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.
17475
+ * The card with buttons the event asks for, recorded first: a result that waits, a write Claw holds, a command or a choice put
17476
+ * to the person; undefined for none. `holdsWords`: the card shows the words, which are then not said apart.
17283
17477
  */
17284
17478
  card(userId, event, item, said2) {
17285
17479
  const inCard = said2.inCard ? said2.text : "";
17286
17480
  if (item?.canAccept)
17287
17481
  return { message: this.workCard(userId, item, said2.text, inCard, said2.lang), holdsWords: said2.inCard };
17288
- if (item !== null) return void 0;
17482
+ const asked3 = item === null ? this.askCard(userId, event, inCard, said2.lang) : void 0;
17483
+ if (asked3 !== void 0) return { message: asked3, holdsWords: said2.inCard };
17484
+ return this.choiceCard(userId, event, inCard, said2.lang, said2.inCard);
17485
+ }
17486
+ /** A write Claw holds for the person (Allow, Deny) or a command put to them (Run, Cancel), recorded first; undefined for none. */
17487
+ askCard(userId, event, inCard, lang) {
17289
17488
  const held = confirmWriteOf(event);
17290
17489
  if (held !== void 0) {
17291
17490
  const cardId2 = this.deps.store.assistantButtons.addConfirmCard(
@@ -17294,8 +17493,8 @@ ${text8}`;
17294
17493
  held.kind,
17295
17494
  held.allowLabel
17296
17495
  );
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 };
17496
+ const model = { text: inCard || held.allowLabel, cardId: cardId2, allowLabel: held.allowLabel, lang };
17497
+ return view3({ name: "confirmWrite", model });
17299
17498
  }
17300
17499
  const confirm = confirmActionOf(event, this.deps.exactCommand);
17301
17500
  if (confirm === void 0) return void 0;
@@ -17306,10 +17505,19 @@ ${text8}`;
17306
17505
  confirm.name,
17307
17506
  confirm.args
17308
17507
  );
17309
- return {
17310
- message: view3({ name: "actionCard", model: { text: inCard || command, cardId, command, lang: said2.lang } }),
17311
- holdsWords: said2.inCard
17312
- };
17508
+ return view3({ name: "actionCard", model: { text: inCard || command, cardId, command, lang } });
17509
+ }
17510
+ /**
17511
+ * A choice put to the person (`choices`): its options and Send, recorded first (what a press sends is read from the record).
17512
+ * Its Send sends what was picked as the person's own message.
17513
+ */
17514
+ choiceCard(userId, event, inCard, lang, holdsWords) {
17515
+ const choice = choiceOf(event);
17516
+ if (choice === void 0) return void 0;
17517
+ const { options, multiple } = choice;
17518
+ const cardId = this.deps.store.assistantButtons.addChoiceCard(userId, { text: inCard, options, multiple, lang });
17519
+ const model = { text: inCard, cardId, options, multiple, note: "", lang };
17520
+ return { message: view3({ name: "choiceCard", model }, inCard), holdsWords };
17313
17521
  }
17314
17522
  /** What was said, as AI answers with thumbs, each with the citations its own words cite (when the whole text cites at all). */
17315
17523
  said(event, text8, parts2) {
@@ -32756,6 +32964,7 @@ export {
32756
32964
  AssistantActionClicks,
32757
32965
  AssistantActions,
32758
32966
  AssistantChat as AssistantChatService,
32967
+ AssistantChoiceClicks,
32759
32968
  BOARD_CARDS,
32760
32969
  BOARD_LINES,
32761
32970
  BOARD_RETRY_MAX,
@@ -89,6 +89,8 @@ export declare class AssistantChat {
89
89
  forget(userId: string): void;
90
90
  /** What a message of files and no words says to the assistant. */
91
91
  filesOnly(lang: Lang): string;
92
+ /** The language the person chose to be answered in, as the message's `channel_context` carries it (none: nothing is sent). */
93
+ private chosenLanguage;
92
94
  private langOf;
93
95
  /**
94
96
  * A message of a person the assistant understands, with the chat's context. True when the assistant has it; otherwise the
@@ -0,0 +1,35 @@
1
+ import type { ClickHandler, ClickRoute } from "../ports/routing.js";
2
+ import type { ChoiceCard } from "../ports/store/assistant.js";
3
+ import type { StorePort } from "../ports/store/index.js";
4
+ import type { Logger } from "../ports/logger.js";
5
+ import type { InboundMessage } from "../ports/surface.js";
6
+ export declare const CHOICE_VERB = "assistant_choice";
7
+ /** The id of a choice card's input, whose value is the picked options' places on the card ("0", "0,2"). */
8
+ export declare const CHOICE_INPUT = "choice";
9
+ export interface ChoiceClickDeps {
10
+ readonly store: Pick<StorePort, "assistantButtons">;
11
+ /** Handles `message` as the router handles one the person typed (`Router.handle`); throws when it was not handled. */
12
+ readonly deliver: (message: InboundMessage) => Promise<void>;
13
+ readonly logger: Logger;
14
+ }
15
+ /** The options `value` picks on `card` (each named by its place), in the card's order; a place it never had is no pick. */
16
+ export declare function pickedOn(card: Pick<ChoiceCard, "options">, value: unknown): string[];
17
+ /**
18
+ * The message a press sends: the presser's, in the chat it was pressed in, saying `text`, as a typed one is (to the bot, in a
19
+ * group), but marked `choice`, so no word of it is read as a command.
20
+ */
21
+ export declare function typedMessage(click: Pick<ClickRoute, "action" | "sender">, text: string, id: string): InboundMessage;
22
+ export declare class AssistantChoiceClicks implements ClickHandler {
23
+ private readonly deps;
24
+ readonly verbs: readonly ["assistant_choice"];
25
+ constructor(deps: ChoiceClickDeps);
26
+ /** A 1:1 chat: answered with no Claw key (the message it sends is checked as any typed one is). */
27
+ beforeCredential(click: ClickRoute): Promise<boolean>;
28
+ inGroup(click: ClickRoute): Promise<boolean>;
29
+ press(click: ClickRoute): Promise<void>;
30
+ /** The card back, open, with `note` under its options. */
31
+ private again;
32
+ /** The card replaced by what was picked and sent (said as a line too when `announce`). */
33
+ private sent;
34
+ private model;
35
+ }
@@ -41,6 +41,23 @@ export declare function confirmWriteOf(event: Event): ConfirmWrite | undefined;
41
41
  * line that is no command, else the defaults in the person's language (`lang`; without it, the language of `text`).
42
42
  */
43
43
  export declare function suggestedRepliesOf(event: Event, text: string, styleOn: boolean, lang?: Lang): SuggestedReply[];
44
+ /** The fewest options a choice card shows (Claw sends from 2). */
45
+ export declare const CHOICE_OPTIONS_MIN = 2;
46
+ /** The most options a choice card shows (Claw sends at most 10). */
47
+ export declare const CHOICE_OPTIONS_MAX = 10;
48
+ /** Code points of one option (Claw sends at most 80): one line beside a radio button or a check box. */
49
+ export declare const CHOICE_OPTION_MAX = 80;
50
+ /** A choice an `assistant.say` puts to the person: the options, and whether several may be picked. */
51
+ export interface SayChoice {
52
+ readonly options: readonly string[];
53
+ readonly multiple: boolean;
54
+ }
55
+ /**
56
+ * The choice an `assistant.say` puts to the person (`choices`); undefined when it carries none this worker draws. Read whole or not
57
+ * at all: an option that is not one plain line of text, or too many or too few of them, and there is no card (the words still name
58
+ * the options, for the person to type).
59
+ */
60
+ export declare function choiceOf(event: Event): SayChoice | undefined;
44
61
  export interface RemoteNotice {
45
62
  /** `resolved`: a question a terminal asked no longer waits here (answered there, or given up); `resolved` says which. */
46
63
  readonly kind: "done" | "needs_you" | "error" | "resolved";
@@ -1,6 +1,7 @@
1
1
  /** What the assembly wires of the assistant (one line each; the views and work-item text are exported from the package root). */
2
2
  export { AssistantActionClicks } from "./action-handler.js";
3
3
  export { AssistantActions } from "./actions.js";
4
+ export { AssistantChoiceClicks } from "./choice-clicks.js";
4
5
  export { Board } from "./board.js";
5
6
  export { AssistantChat as AssistantChatService } from "./chat.js";
6
7
  export type { AssistantChatOptions } from "./chat.js";
@@ -5,8 +5,9 @@
5
5
  * receipt already shows of it (`shown`). It is an AI answer: the AI label and the thumbs under it, a citation for each source it
6
6
  * cites as [n], and the replies to tap under the last of its messages when no card follows.
7
7
  * - When the event's item holds a result that waits, a card with Accept and Stop; when it holds a write for the person, a card
8
- * that lets it through or not; when it puts a command to the person, a card with Run and Cancel (each recorded first, so its
9
- * buttons mean something). A card holds the words only when they are short and not shown already.
8
+ * that lets it through or not; when it puts a command to the person, a card with Run and Cancel; when it puts a choice to the
9
+ * person, a card with the options and Send (each recorded first, so its buttons mean something). A card holds the words only
10
+ * when they are short and not shown already.
10
11
  * - Then a card for each file the item's worker delivered, with Claw's own download link.
11
12
  *
12
13
  * Every word of this worker's own is in the person's language, never in that of what was said.
@@ -51,10 +52,17 @@ export declare class SayPlanner {
51
52
  /** The replies to tap, under the last of what was said, when no card follows. */
52
53
  private withReplies;
53
54
  /**
54
- * The card with buttons the event asks for, recorded first: a result that waits, a write Claw holds, or a command put to the
55
- * person; undefined for none. `holdsWords`: the card shows the words, which are then not said apart.
55
+ * The card with buttons the event asks for, recorded first: a result that waits, a write Claw holds, a command or a choice put
56
+ * to the person; undefined for none. `holdsWords`: the card shows the words, which are then not said apart.
56
57
  */
57
58
  private card;
59
+ /** A write Claw holds for the person (Allow, Deny) or a command put to them (Run, Cancel), recorded first; undefined for none. */
60
+ private askCard;
61
+ /**
62
+ * A choice put to the person (`choices`): its options and Send, recorded first (what a press sends is read from the record).
63
+ * Its Send sends what was picked as the person's own message.
64
+ */
65
+ private choiceCard;
58
66
  /** What was said, as AI answers with thumbs, each with the citations its own words cite (when the whole text cites at all). */
59
67
  private said;
60
68
  /** A result that waits: Accept and Stop, and (a task whose result names a pull request) auto-reply on or off. */
@@ -68,9 +68,31 @@ export interface ConfirmWriteModel {
68
68
  readonly allowLabel: string;
69
69
  readonly lang: string;
70
70
  }
71
+ /** A choice the assistant put to the person: its options as radio buttons (or check boxes) and Send. */
72
+ export interface ChoiceCardModel {
73
+ /** The words, when the card holds them ("" when they were said apart). */
74
+ readonly text: string;
75
+ readonly cardId: string;
76
+ readonly options: readonly string[];
77
+ /** Several may be picked (check boxes); else one (radio buttons). */
78
+ readonly multiple: boolean;
79
+ /** One line under the options when the card comes back unanswered ("" none). */
80
+ readonly note: string;
81
+ /** "zh" or "en"; null to follow what was said. */
82
+ readonly lang: string | null;
83
+ }
84
+ /** What replaces a choice card once the person sent what they picked: what they sent, under the words it answered. */
85
+ export interface ChoiceChosenModel {
86
+ readonly text: string;
87
+ /** The options picked, in the card's order. */
88
+ readonly chosen: readonly string[];
89
+ readonly lang: string | null;
90
+ }
71
91
  declare module "../ports/surface.js" {
72
92
  interface ViewMap {
73
93
  actionCard: ActionCardModel;
94
+ choiceCard: ChoiceCardModel;
95
+ choiceChosen: ChoiceChosenModel;
74
96
  board: BoardModel;
75
97
  confirmWrite: ConfirmWriteModel;
76
98
  workCard: WorkCardModel;
@@ -0,0 +1,23 @@
1
+ /**
2
+ * The language a person chose to be answered in (`/lang zh|en`), as it goes to Claw with each of their messages, so that the
3
+ * model's answer is in it as well as the agent's own texts.
4
+ *
5
+ * Only a choice the person said counts: a language learnt from what they write (`learnLang`), and `/lang auto`, are no
6
+ * choice, nothing is sent, and Claw reads the language from the message as before. To the assistant the choice goes in the
7
+ * message's `channel_context` (`reply_language`); to a plain session (a group's turn, or a 1:1 chat without the assistant) as
8
+ * the line Claw's assistant uses for it, after the message (`replyLanguageLine`).
9
+ */
10
+ import type { Lang } from "../locales/lang.js";
11
+ /** What of a person's preferences says the choice (`Prefs.lang`, `Prefs.langSet`). */
12
+ export interface LangPrefs {
13
+ readonly lang: string;
14
+ readonly langSet: boolean;
15
+ }
16
+ /** The language the person chose, or undefined when they chose none. */
17
+ export declare function chosenLang(prefs: LangPrefs): Lang | undefined;
18
+ /** The choice as `channel_context` carries it (`{ reply_language: "zh" }`); empty when there is none. */
19
+ export declare function replyLanguageContext(prefs: LangPrefs): {
20
+ reply_language?: Lang;
21
+ };
22
+ /** The line after a message to a plain session that names the language to answer in: `[Reply language: English]`. */
23
+ export declare function replyLanguageLine(lang: Lang): string;
@@ -67,6 +67,21 @@ export declare const assistantCardText: import("./catalog.js").Catalog<{
67
67
  askedTitle: (p: {
68
68
  n: number;
69
69
  }) => string;
70
+ choiceTitle: string;
71
+ choiceTitleMany: string;
72
+ choiceSend: string;
73
+ choiceTyped: string;
74
+ choiceNone: string;
75
+ choiceSent: (p: {
76
+ answer: string;
77
+ }) => string;
78
+ choiceGone: string;
79
+ choiceSending: (p: {
80
+ answer: string;
81
+ }) => string;
82
+ choiceFailed: string;
83
+ /** How the options picked are joined into the person's message. */
84
+ choiceJoin: string;
70
85
  version: (p: {
71
86
  version: number;
72
87
  }) => string;
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Cards and messages of the assistant (tables `assistant_cards`, `auto_replies`, `assistant_action_cards`,
3
- * `assistant_confirm_cards`, `assistant_messages`). A card's buttons carry only its id; what a press means is read
3
+ * `assistant_confirm_cards`, `assistant_choice_cards`, `assistant_messages`). A card's buttons carry only its id; what a press means is read
4
4
  * from here, so a forged, old or someone else's card changes nothing.
5
5
  */
6
6
  import type { JsonObject } from "./json.js";
@@ -56,6 +56,21 @@ export interface ConfirmCard {
56
56
  readonly state: CardState;
57
57
  readonly createdAt: number;
58
58
  }
59
+ /** A choice the assistant put to the person: what was asked, the options, and (once sent) what was picked. */
60
+ export interface ChoiceCard {
61
+ readonly cardId: string;
62
+ readonly userId: string;
63
+ /** The words the card holds ("" when they were said apart). */
64
+ readonly text: string;
65
+ readonly options: readonly string[];
66
+ readonly multiple: boolean;
67
+ /** What was picked, in the card's order: being sent ('busy') or sent ('done'); empty before the first press. */
68
+ readonly chosen: readonly string[];
69
+ /** 'open'; 'busy' while a press sends it; 'done' once the router took it. */
70
+ readonly state: CardState;
71
+ readonly lang: string;
72
+ readonly createdAt: number;
73
+ }
59
74
  export interface AssistantMessage {
60
75
  readonly userId: string;
61
76
  /** The actions (and handoffs) the message declared. */
@@ -94,6 +109,17 @@ export interface AssistantButtonRepository {
94
109
  /** Null once it is older than CONFIRM_CARD_KEEP_SECONDS. */
95
110
  getConfirmCard(cardId: string): ConfirmCard | null;
96
111
  moveConfirmCard(cardId: string, state: CardState, options?: MoveOptions): boolean;
112
+ addChoiceCard(userId: string, choice: {
113
+ readonly text: string;
114
+ readonly options: readonly string[];
115
+ readonly multiple: boolean;
116
+ readonly lang: string;
117
+ }): string;
118
+ /** Null once it is older than ASSISTANT_MESSAGE_KEEP_SECONDS. */
119
+ getChoiceCard(cardId: string): ChoiceCard | null;
120
+ /** An open card is claimed by a press to send `chosen` (it moves to 'busy'); true if this call claimed it, so of two presses exactly one sends. */
121
+ claimChoiceCard(cardId: string, chosen: readonly string[]): boolean;
122
+ moveChoiceCard(cardId: string, state: CardState, options?: MoveOptions): boolean;
97
123
  }
98
124
  export interface AssistantMessageRepository {
99
125
  /** A message this worker sent to the person's assistant, with the actions it declared and the Claw users it named. */
@@ -134,6 +134,12 @@ export interface InboundMessage extends InboundBase {
134
134
  */
135
135
  readonly unreadableFiles: readonly string[];
136
136
  readonly provenance: Provenance;
137
+ /**
138
+ * What was picked on a choice card the assistant put (`assistant_choice`): an answer, never read as a command. The menu's
139
+ * words and help's phrases ("New", "重新开始", "Stop", "关闭记忆") stay the words picked; it goes where a typed message that
140
+ * is no command goes (the assistant, or the agent a 1:1 chat is attached to).
141
+ */
142
+ readonly choice?: true;
137
143
  }
138
144
  /** A button pressed on a card the bot posted. */
139
145
  export interface InboundAction extends InboundBase {
@@ -140,6 +140,11 @@ export interface AssistantRemoteAsk {
140
140
  question_index?: number;
141
141
  question_count?: number;
142
142
  }
143
+ /** A choice a say puts to the person: from 2 to 10 options, one plain line each; `multiple` when several may be picked. */
144
+ export interface AssistantSayChoice {
145
+ options: string[];
146
+ multiple: boolean;
147
+ }
143
148
  /** Something the assistant chose to say to the user. */
144
149
  export interface AssistantSayPayload extends AssistantEventBase {
145
150
  type: "assistant.say";
@@ -151,6 +156,12 @@ export interface AssistantSayPayload extends AssistantEventBase {
151
156
  * next, each one plain line. A channel shows them as buttons that send the words as the user's message; one that cannot ignores them.
152
157
  */
153
158
  suggested_replies?: string[];
159
+ /**
160
+ * Present only when the router put a choice to the person (`CLAW_ASSISTANT_CHOICES`): the options they pick from, each one plain
161
+ * line, and whether they may pick several. A channel shows them as a card whose Send sends the picked options as the person's own
162
+ * message (their words, joined); one that cannot ignores it, and the text still names the options.
163
+ */
164
+ choices?: AssistantSayChoice;
154
165
  confirm_write?: AssistantConfirmWrite;
155
166
  /** Files the user gets with the message (a worker's deliveries, `deliveries.ts`): one download each. */
156
167
  files?: AssistantFile[];
@@ -15,7 +15,8 @@ export declare class PersonalText {
15
15
  constructor(ctx: RouterContext, commands: CommandTable, starter: TurnStarter);
16
16
  /**
17
17
  * What the person wrote, with a word of the commands menu read as its command and a sentence help teaches read as the
18
- * command it means; a sentence for what this chat cannot do right now stays words.
18
+ * command it means; a sentence for what this chat cannot do right now stays words. A choice picked on the assistant's card
19
+ * is the words picked, never a command.
19
20
  */
20
21
  textOf(message: InboundMessage, group?: boolean): string;
21
22
  onText(message: InboundMessage, chat: Chat, key: string): Promise<void>;
@@ -43,6 +43,11 @@ export interface TurnRun {
43
43
  readonly sent: string;
44
44
  readonly files: readonly InboundFile[];
45
45
  readonly lang: Lang;
46
+ /**
47
+ * The language the person chose to be answered in (`/lang zh|en`, `chosenLang`), told to Claw after the message; undefined: they
48
+ * chose none, and the answer is in the language of what they wrote. A chat turn's only: a group's is the sender's.
49
+ */
50
+ readonly replyLang?: Lang;
46
51
  /** The answer gets thumbs of the worker's own (a phone, whose own thumbs never reach the worker). */
47
52
  readonly ownThumbs: boolean;
48
53
  /** Claw pushes this person a run's end and its approvals. */
@@ -72,7 +72,7 @@ export declare class TurnStarter {
72
72
  /** The turns that run now (a test waits for them). */
73
73
  settled(): Promise<void>;
74
74
  /** The turn that runs (its end is `done`), or undefined when none was started (the person was told why). */
75
- start(request: StartTurn): Promise<RunningTurn | undefined>;
75
+ start(started: StartTurn): Promise<RunningTurn | undefined>;
76
76
  /** A typed message the waiting question card of the chat's session took as its answer (`question-reply.ts`). */
77
77
  private answersQuestion;
78
78
  private track;
@@ -97,6 +97,8 @@ export declare class TurnStarter {
97
97
  /** What the run reads of the chat's session: its id, the model it has, the repository a new one is bound to. */
98
98
  private sessionPart;
99
99
  private runOf;
100
+ /** The language the sender chose to be answered in, for a chat turn (a review's answer follows its request's language). */
101
+ private replyLang;
100
102
  private hooks;
101
103
  private guarded;
102
104
  private tell;
@@ -1,4 +1,3 @@
1
- /** Everything one turn runs with: who it acts for, what it sends, where it answers, and what it reports back. */
2
1
  import type { Lang } from "../../locales/lang.js";
3
2
  import type { PullRef } from "../../claw/model.js";
4
3
  import type { Integrations } from "../../plugins/integrations.js";
@@ -60,6 +59,8 @@ export interface TurnRequest {
60
59
  readonly sent?: string;
61
60
  /** The language the person is answered in (undefined: the language of `text`). */
62
61
  readonly lang?: Lang;
62
+ /** The language the person chose to be answered in, told to Claw after the message (`replyLanguageLine`); undefined: none chosen. */
63
+ readonly replyLang?: Lang;
63
64
  /** What the user sent with the message. */
64
65
  readonly files?: readonly InboundFile[];
65
66
  }
@@ -154,6 +155,7 @@ export declare function credential(ctx: TurnContext): Promise<string>;
154
155
  export declare function langOf(ctx: TurnContext): Lang;
155
156
  /**
156
157
  * What Claw is sent: the quoted messages, then what the user wrote (with the addresses behind its links), then where the
157
- * files they sent are. `text` stays the words alone: the session's title, the language of the answer.
158
+ * files they sent are, then the language they chose to be answered in (`replyLanguageLine`), when they chose one. `text` stays the
159
+ * words alone: the session's title, the language of the answer.
158
160
  */
159
161
  export declare function messageOf(ctx: TurnContext): string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@primusclaw/personal-agent",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "The personal agent's channel-neutral core: domain logic, ports and plugin interfaces.",
5
5
  "license": "MIT",
6
6
  "repository": {