@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 +242 -33
- package/dist/types/assistant/chat.d.ts +2 -0
- package/dist/types/assistant/choice-clicks.d.ts +35 -0
- package/dist/types/assistant/events.d.ts +17 -0
- package/dist/types/assistant/index.d.ts +1 -0
- package/dist/types/assistant/say.d.ts +12 -4
- package/dist/types/assistant/views.d.ts +22 -0
- package/dist/types/language/chosen.d.ts +23 -0
- package/dist/types/locales/assistant-cards.d.ts +15 -0
- package/dist/types/ports/store/assistant.d.ts +27 -1
- package/dist/types/ports/surface.d.ts +6 -0
- package/dist/types/protocol/claw-api/assistant-events.d.ts +11 -0
- package/dist/types/routing/personal-text.d.ts +2 -1
- package/dist/types/routing/turn-runner.d.ts +5 -0
- package/dist/types/routing/turn-start.d.ts +3 -1
- package/dist/types/turn/engine/context.d.ts +4 -2
- package/package.json +1 -1
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
|
-
|
|
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
|
|
13210
|
-
const
|
|
13211
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
15625
|
+
const context2 = {
|
|
15562
15626
|
...request.context,
|
|
15563
|
-
files: await this.handOverFiles(handing, sessionId, folderId || this.o.random.hexId(), request.files)
|
|
15564
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
17298
|
-
return
|
|
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
|
-
|
|
17311
|
-
|
|
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
|
|
9
|
-
* buttons mean something). A card holds the words only
|
|
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
|
|
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(
|
|
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
|
|
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;
|