@coinseeker/opencode-telegram-plugin 1.0.1 → 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 +1 -0
- package/dist/telegram-remote.js +57 -19
- package/package.json +1 -1
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.
|
package/dist/telegram-remote.js
CHANGED
|
@@ -238,6 +238,7 @@ function parsePending(text) {
|
|
|
238
238
|
if (!Array.isArray(parsed.questions)) throw new Error("Invalid pending question: questions");
|
|
239
239
|
if (!Array.isArray(parsed.telegramMessageIds)) throw new Error("Invalid pending question: telegramMessageIds");
|
|
240
240
|
if (!Array.isArray(parsed.answersInProgress)) throw new Error("Invalid pending question: answersInProgress");
|
|
241
|
+
parsed.answersInProgress = parsed.answersInProgress.map((answer) => answer ?? null);
|
|
241
242
|
return parsed;
|
|
242
243
|
}
|
|
243
244
|
async function listPendingFiles(dir) {
|
|
@@ -323,7 +324,7 @@ function loadPluginEnv(opts) {
|
|
|
323
324
|
join3(opts.pluginDir, "../../.env"),
|
|
324
325
|
join3(opts.pluginDir, "..", ".env"),
|
|
325
326
|
join3(opts.pluginDir, ".env"),
|
|
326
|
-
join3(homedir2(), ".config/opencode/telegram-remote/.env")
|
|
327
|
+
join3(opts.homeDir ?? homedir2(), ".config/opencode/telegram-remote/.env")
|
|
327
328
|
];
|
|
328
329
|
const loadedFrom = [];
|
|
329
330
|
const values = {};
|
|
@@ -414,7 +415,7 @@ This chat is now active for OpenCode notifications.`);
|
|
|
414
415
|
logger.error("bot error", { error: String(e) });
|
|
415
416
|
}
|
|
416
417
|
});
|
|
417
|
-
bot.callbackQuery(/^q:([^:]+):(\d+):(\d+|c)$/, async (ctx) => {
|
|
418
|
+
bot.callbackQuery(/^q:([^:]+):(\d+):(\d+|c|d)$/, async (ctx) => {
|
|
418
419
|
await ctx.answerCallbackQuery();
|
|
419
420
|
const data = ctx.callbackQuery.data;
|
|
420
421
|
const messageId = ctx.callbackQuery.message?.message_id;
|
|
@@ -784,7 +785,7 @@ Detail: ${permission.title}`;
|
|
|
784
785
|
|
|
785
786
|
// src/events/question-asked.ts
|
|
786
787
|
var QUESTION_EXPIRY_MS = 5 * 6e4;
|
|
787
|
-
var CALLBACK_RE = /^q:([^:]+):(\d+):(\d+|c)$/;
|
|
788
|
+
var CALLBACK_RE = /^q:([^:]+):(\d+):(\d+|c|d)$/;
|
|
788
789
|
function isQuestionOption(value) {
|
|
789
790
|
return typeof value.label === "string" && typeof value.description === "string";
|
|
790
791
|
}
|
|
@@ -813,6 +814,26 @@ function callbackDataForQuestion(shortHash, questionIndex, question) {
|
|
|
813
814
|
if (question.custom !== false) data.push(buildCallbackData(shortHash, questionIndex, "c"));
|
|
814
815
|
return data;
|
|
815
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
|
+
}
|
|
816
837
|
function questionPromptText(pending, questionIndex) {
|
|
817
838
|
const question = pending.questions[questionIndex];
|
|
818
839
|
const prefix = pending.questions.length > 1 ? `Question ${questionIndex + 1}/${pending.questions.length}
|
|
@@ -832,17 +853,11 @@ function answerSummary(questions, answers) {
|
|
|
832
853
|
async function editPromptForQuestion(ctx, pending, shortHash, questionIndex) {
|
|
833
854
|
const messageId = pending.telegramMessageIds[0];
|
|
834
855
|
const question = pending.questions[questionIndex];
|
|
835
|
-
const inlineKeyboard = question
|
|
836
|
-
text: option.label,
|
|
837
|
-
callback_data: buildCallbackData(shortHash, questionIndex, optionIndex)
|
|
838
|
-
}]);
|
|
839
|
-
if (question.custom !== false) {
|
|
840
|
-
inlineKeyboard.push([{ text: "\u270F\uFE0F Custom answer", callback_data: buildCallbackData(shortHash, questionIndex, "c") }]);
|
|
841
|
-
}
|
|
856
|
+
const inlineKeyboard = questionInlineKeyboard(shortHash, questionIndex, question, selectedAnswers(pending, questionIndex));
|
|
842
857
|
await ctx.bot.editMessageText(messageId, questionPromptText(pending, questionIndex), { reply_markup: { inline_keyboard: inlineKeyboard } });
|
|
843
858
|
}
|
|
844
859
|
async function completeIfReady(ctx, pending, shortHash) {
|
|
845
|
-
const nextIndex = pending.answersInProgress.findIndex((answer) => answer ===
|
|
860
|
+
const nextIndex = pending.answersInProgress.findIndex((answer) => answer === null);
|
|
846
861
|
if (nextIndex >= 0) {
|
|
847
862
|
pending.currentQuestionIndex = nextIndex;
|
|
848
863
|
await ctx.pendingQuestions.savePending(shortHash, pending);
|
|
@@ -884,15 +899,12 @@ async function handleQuestionAsked(event, ctx) {
|
|
|
884
899
|
expiresAt: sentAt + QUESTION_EXPIRY_MS,
|
|
885
900
|
telegramMessageIds: [],
|
|
886
901
|
currentQuestionIndex: 0,
|
|
887
|
-
answersInProgress: request.questions.map(() =>
|
|
902
|
+
answersInProgress: request.questions.map(() => null)
|
|
888
903
|
};
|
|
889
904
|
try {
|
|
890
|
-
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), {
|
|
891
906
|
reply_markup: {
|
|
892
|
-
inline_keyboard:
|
|
893
|
-
text: option.label,
|
|
894
|
-
callback_data: buildCallbackData(shortHash, 0, optionIndex)
|
|
895
|
-
}]).concat(firstQuestion.custom !== false ? [[{ text: "\u270F\uFE0F Custom answer", callback_data: buildCallbackData(shortHash, 0, "c") }]] : [])
|
|
907
|
+
inline_keyboard: questionInlineKeyboard(shortHash, 0, firstQuestion, [])
|
|
896
908
|
}
|
|
897
909
|
});
|
|
898
910
|
pending.telegramMessageIds = [message.message_id];
|
|
@@ -922,16 +934,32 @@ function createQuestionDispatcher(ctx) {
|
|
|
922
934
|
const question = pending.questions[questionIndex];
|
|
923
935
|
if (!question) return;
|
|
924
936
|
if (selection === "c") {
|
|
925
|
-
|
|
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
|
+
}
|
|
926
942
|
const prompt = await ctx.bot.replyWithForceReply("Type your custom answer", "Type your answer");
|
|
927
943
|
pending.awaitingCustomFor = { shortHash, questionIndex, chatId, userId, promptMessageId: prompt.message_id };
|
|
928
944
|
await ctx.pendingQuestions.savePending(shortHash, pending);
|
|
929
945
|
return;
|
|
930
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
|
+
}
|
|
931
954
|
const option = question.options[Number(selection)];
|
|
932
955
|
if (!option) return;
|
|
933
956
|
if (question.multiple === true) {
|
|
934
|
-
|
|
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;
|
|
935
963
|
}
|
|
936
964
|
pending.answersInProgress[questionIndex] = [option.label];
|
|
937
965
|
pending.awaitingCustomFor = void 0;
|
|
@@ -946,6 +974,16 @@ function createQuestionDispatcher(ctx) {
|
|
|
946
974
|
await expirePending(ctx, match.shortHash, match.data, match.data.telegramMessageIds[0]);
|
|
947
975
|
return;
|
|
948
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
|
+
}
|
|
949
987
|
match.data.answersInProgress[awaiting.questionIndex] = [text];
|
|
950
988
|
match.data.awaitingCustomFor = void 0;
|
|
951
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.
|
|
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",
|