@coinseeker/opencode-telegram-plugin 1.0.2 → 1.0.3

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/README.md CHANGED
@@ -52,6 +52,7 @@ Keep this file private. Never commit or share your Telegram bot token.
52
52
  - Root session completion notifications.
53
53
  - Background subagent-aware completion: child session messages are suppressed and parent completion waits until children finish.
54
54
  - OpenCode question prompts via Telegram inline buttons.
55
+ - Multi-select question prompts with toggle buttons and **Done** submission.
55
56
  - Custom free-text answers from Telegram.
56
57
  - Permission alerts.
57
58
  - Multi-session-safe Telegram polling through a file-lock leader model.
@@ -415,7 +415,7 @@ This chat is now active for OpenCode notifications.`);
415
415
  logger.error("bot error", { error: String(e) });
416
416
  }
417
417
  });
418
- bot.callbackQuery(/^q:([^:]+):(\d+):(\d+|c)$/, async (ctx) => {
418
+ bot.callbackQuery(/^q:([^:]+):(\d+):(\d+|c|d)$/, async (ctx) => {
419
419
  await ctx.answerCallbackQuery();
420
420
  const data = ctx.callbackQuery.data;
421
421
  const messageId = ctx.callbackQuery.message?.message_id;
@@ -785,7 +785,7 @@ Detail: ${permission.title}`;
785
785
 
786
786
  // src/events/question-asked.ts
787
787
  var QUESTION_EXPIRY_MS = 5 * 6e4;
788
- var CALLBACK_RE = /^q:([^:]+):(\d+):(\d+|c)$/;
788
+ var CALLBACK_RE = /^q:([^:]+):(\d+):(\d+|c|d)$/;
789
789
  function isQuestionOption(value) {
790
790
  return typeof value.label === "string" && typeof value.description === "string";
791
791
  }
@@ -814,6 +814,26 @@ function callbackDataForQuestion(shortHash, questionIndex, question) {
814
814
  if (question.custom !== false) data.push(buildCallbackData(shortHash, questionIndex, "c"));
815
815
  return data;
816
816
  }
817
+ function useSimpleQuestionKeyboard(question) {
818
+ return question.multiple !== true;
819
+ }
820
+ function selectedAnswers(pending, questionIndex) {
821
+ return pending.answersInProgress[questionIndex] ?? [];
822
+ }
823
+ function questionInlineKeyboard(shortHash, questionIndex, question, selected) {
824
+ const multiple = question.multiple === true;
825
+ const inlineKeyboard = question.options.map((option, optionIndex) => [{
826
+ text: multiple && selected.includes(option.label) ? `\u2705 ${option.label}` : option.label,
827
+ callback_data: buildCallbackData(shortHash, questionIndex, optionIndex)
828
+ }]);
829
+ if (question.custom !== false) {
830
+ inlineKeyboard.push([{ text: "\u270F\uFE0F Custom answer", callback_data: buildCallbackData(shortHash, questionIndex, "c") }]);
831
+ }
832
+ if (multiple) {
833
+ inlineKeyboard.push([{ text: "\u2705 Done", callback_data: buildCallbackData(shortHash, questionIndex, "d") }]);
834
+ }
835
+ return inlineKeyboard;
836
+ }
817
837
  function questionPromptText(pending, questionIndex) {
818
838
  const question = pending.questions[questionIndex];
819
839
  const prefix = pending.questions.length > 1 ? `Question ${questionIndex + 1}/${pending.questions.length}
@@ -833,13 +853,7 @@ function answerSummary(questions, answers) {
833
853
  async function editPromptForQuestion(ctx, pending, shortHash, questionIndex) {
834
854
  const messageId = pending.telegramMessageIds[0];
835
855
  const question = pending.questions[questionIndex];
836
- const inlineKeyboard = question.options.map((option, optionIndex) => [{
837
- text: option.label,
838
- callback_data: buildCallbackData(shortHash, questionIndex, optionIndex)
839
- }]);
840
- if (question.custom !== false) {
841
- inlineKeyboard.push([{ text: "\u270F\uFE0F Custom answer", callback_data: buildCallbackData(shortHash, questionIndex, "c") }]);
842
- }
856
+ const inlineKeyboard = questionInlineKeyboard(shortHash, questionIndex, question, selectedAnswers(pending, questionIndex));
843
857
  await ctx.bot.editMessageText(messageId, questionPromptText(pending, questionIndex), { reply_markup: { inline_keyboard: inlineKeyboard } });
844
858
  }
845
859
  async function completeIfReady(ctx, pending, shortHash) {
@@ -888,12 +902,9 @@ async function handleQuestionAsked(event, ctx) {
888
902
  answersInProgress: request.questions.map(() => null)
889
903
  };
890
904
  try {
891
- const message = request.questions.length === 1 ? await ctx.bot.sendQuestionWithKeyboard(firstQuestion, callbackDataForQuestion(shortHash, 0, firstQuestion)) : await ctx.bot.sendMessage(questionPromptText(pending, 0), {
905
+ const message = request.questions.length === 1 && useSimpleQuestionKeyboard(firstQuestion) ? await ctx.bot.sendQuestionWithKeyboard(firstQuestion, callbackDataForQuestion(shortHash, 0, firstQuestion)) : await ctx.bot.sendMessage(questionPromptText(pending, 0), {
892
906
  reply_markup: {
893
- inline_keyboard: firstQuestion.options.map((option, optionIndex) => [{
894
- text: option.label,
895
- callback_data: buildCallbackData(shortHash, 0, optionIndex)
896
- }]).concat(firstQuestion.custom !== false ? [[{ text: "\u270F\uFE0F Custom answer", callback_data: buildCallbackData(shortHash, 0, "c") }]] : [])
907
+ inline_keyboard: questionInlineKeyboard(shortHash, 0, firstQuestion, [])
897
908
  }
898
909
  });
899
910
  pending.telegramMessageIds = [message.message_id];
@@ -923,16 +934,32 @@ function createQuestionDispatcher(ctx) {
923
934
  const question = pending.questions[questionIndex];
924
935
  if (!question) return;
925
936
  if (selection === "c") {
926
- await ctx.bot.editMessageRemoveKeyboard(messageId, "\u270F\uFE0F Reply to the next message with your custom answer.");
937
+ if (question.multiple === true) {
938
+ await ctx.bot.editMessageText(messageId, questionPromptText(pending, questionIndex), { reply_markup: { inline_keyboard: [] } });
939
+ } else {
940
+ await ctx.bot.editMessageRemoveKeyboard(messageId, "\u270F\uFE0F Reply to the next message with your custom answer.");
941
+ }
927
942
  const prompt = await ctx.bot.replyWithForceReply("Type your custom answer", "Type your answer");
928
943
  pending.awaitingCustomFor = { shortHash, questionIndex, chatId, userId, promptMessageId: prompt.message_id };
929
944
  await ctx.pendingQuestions.savePending(shortHash, pending);
930
945
  return;
931
946
  }
947
+ if (selection === "d") {
948
+ if (question.multiple !== true) return;
949
+ pending.answersInProgress[questionIndex] = selectedAnswers(pending, questionIndex);
950
+ pending.awaitingCustomFor = void 0;
951
+ await completeIfReady(ctx, pending, shortHash);
952
+ return;
953
+ }
932
954
  const option = question.options[Number(selection)];
933
955
  if (!option) return;
934
956
  if (question.multiple === true) {
935
- ctx.logger.info("multiple-choice question handled as single-select", { requestID: pending.requestID, questionIndex });
957
+ const current = selectedAnswers(pending, questionIndex);
958
+ pending.answersInProgress[questionIndex] = current.includes(option.label) ? current.filter((answer) => answer !== option.label) : [...current, option.label];
959
+ pending.awaitingCustomFor = void 0;
960
+ await ctx.pendingQuestions.savePending(shortHash, pending);
961
+ await editPromptForQuestion(ctx, pending, shortHash, questionIndex);
962
+ return;
936
963
  }
937
964
  pending.answersInProgress[questionIndex] = [option.label];
938
965
  pending.awaitingCustomFor = void 0;
@@ -947,6 +974,16 @@ function createQuestionDispatcher(ctx) {
947
974
  await expirePending(ctx, match.shortHash, match.data, match.data.telegramMessageIds[0]);
948
975
  return;
949
976
  }
977
+ const question = match.data.questions[awaiting.questionIndex];
978
+ if (question?.multiple === true) {
979
+ const current = selectedAnswers(match.data, awaiting.questionIndex);
980
+ match.data.answersInProgress[awaiting.questionIndex] = current.includes(text) ? current : [...current, text];
981
+ match.data.awaitingCustomFor = void 0;
982
+ await ctx.bot.sendMessage("\u2705 Custom answer added. Tap Done when finished.");
983
+ await ctx.pendingQuestions.savePending(match.shortHash, match.data);
984
+ await editPromptForQuestion(ctx, match.data, match.shortHash, awaiting.questionIndex);
985
+ return;
986
+ }
950
987
  match.data.answersInProgress[awaiting.questionIndex] = [text];
951
988
  match.data.awaitingCustomFor = void 0;
952
989
  await ctx.bot.sendMessage("\u2705 Custom answer sent.");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@coinseeker/opencode-telegram-plugin",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
4
4
  "description": "Control and monitor OpenCode from Telegram with notifications, question replies, and subagent-aware completion.",
5
5
  "type": "module",
6
6
  "main": "dist/telegram-remote.js",