@grinev/opencode-telegram-bot 0.11.4 → 0.12.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/.env.example +3 -0
- package/README.md +16 -0
- package/dist/app/start-bot-app.js +2 -0
- package/dist/bot/commands/abort.js +2 -0
- package/dist/bot/commands/commands.js +5 -0
- package/dist/bot/commands/definitions.js +2 -0
- package/dist/bot/commands/start.js +2 -0
- package/dist/bot/commands/task.js +399 -0
- package/dist/bot/commands/tasklist.js +220 -0
- package/dist/bot/handlers/prompt.js +8 -0
- package/dist/bot/index.js +27 -1
- package/dist/bot/middleware/interaction-guard.js +11 -0
- package/dist/config.js +1 -0
- package/dist/i18n/de.js +38 -1
- package/dist/i18n/en.js +38 -1
- package/dist/i18n/es.js +38 -1
- package/dist/i18n/fr.js +38 -1
- package/dist/i18n/ru.js +38 -1
- package/dist/i18n/zh.js +38 -1
- package/dist/interaction/cleanup.js +10 -2
- package/dist/interaction/guard.js +7 -0
- package/dist/scheduled-task/creation-manager.js +113 -0
- package/dist/scheduled-task/display.js +239 -0
- package/dist/scheduled-task/executor.js +87 -0
- package/dist/scheduled-task/foreground-state.js +32 -0
- package/dist/scheduled-task/next-run.js +207 -0
- package/dist/scheduled-task/runtime.js +362 -0
- package/dist/scheduled-task/schedule-parser.js +169 -0
- package/dist/scheduled-task/store.js +65 -0
- package/dist/scheduled-task/types.js +19 -0
- package/dist/settings/manager.js +12 -0
- package/package.json +1 -1
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import { InlineKeyboard } from "grammy";
|
|
2
|
+
import { getDateLocale, t } from "../../i18n/index.js";
|
|
3
|
+
import { interactionManager } from "../../interaction/manager.js";
|
|
4
|
+
import { formatTaskListBadge } from "../../scheduled-task/display.js";
|
|
5
|
+
import { scheduledTaskRuntime } from "../../scheduled-task/runtime.js";
|
|
6
|
+
import { getScheduledTask, listScheduledTasks, removeScheduledTask, } from "../../scheduled-task/store.js";
|
|
7
|
+
import { logger } from "../../utils/logger.js";
|
|
8
|
+
const TASKLIST_CALLBACK_PREFIX = "tasklist:";
|
|
9
|
+
const TASKLIST_OPEN_PREFIX = `${TASKLIST_CALLBACK_PREFIX}open:`;
|
|
10
|
+
const TASKLIST_DELETE_PREFIX = `${TASKLIST_CALLBACK_PREFIX}delete:`;
|
|
11
|
+
const TASKLIST_CANCEL_CALLBACK = `${TASKLIST_CALLBACK_PREFIX}cancel`;
|
|
12
|
+
const MAX_INLINE_BUTTON_LABEL_LENGTH = 64;
|
|
13
|
+
function getCallbackMessageId(ctx) {
|
|
14
|
+
const message = ctx.callbackQuery?.message;
|
|
15
|
+
if (!message || !("message_id" in message)) {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
const messageId = message.message_id;
|
|
19
|
+
return typeof messageId === "number" ? messageId : null;
|
|
20
|
+
}
|
|
21
|
+
function parseTaskListMetadata(state) {
|
|
22
|
+
if (!state || state.kind !== "custom") {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
const flow = state.metadata.flow;
|
|
26
|
+
const stage = state.metadata.stage;
|
|
27
|
+
const messageId = state.metadata.messageId;
|
|
28
|
+
if (flow !== "tasklist" || typeof messageId !== "number") {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
if (stage === "list") {
|
|
32
|
+
return {
|
|
33
|
+
flow,
|
|
34
|
+
stage,
|
|
35
|
+
messageId,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
if (stage === "detail") {
|
|
39
|
+
const taskId = state.metadata.taskId;
|
|
40
|
+
if (typeof taskId !== "string" || !taskId) {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
return {
|
|
44
|
+
flow,
|
|
45
|
+
stage,
|
|
46
|
+
messageId,
|
|
47
|
+
taskId,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
function clearTaskListInteraction(reason) {
|
|
53
|
+
const metadata = parseTaskListMetadata(interactionManager.getSnapshot());
|
|
54
|
+
if (metadata) {
|
|
55
|
+
interactionManager.clear(reason);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
function truncateText(text, maxLength) {
|
|
59
|
+
if (text.length <= maxLength) {
|
|
60
|
+
return text;
|
|
61
|
+
}
|
|
62
|
+
return `${text.slice(0, Math.max(0, maxLength - 3)).trimEnd()}...`;
|
|
63
|
+
}
|
|
64
|
+
function formatDateTime(dateIso, timezone) {
|
|
65
|
+
if (!dateIso) {
|
|
66
|
+
return "-";
|
|
67
|
+
}
|
|
68
|
+
try {
|
|
69
|
+
return new Intl.DateTimeFormat(getDateLocale(), {
|
|
70
|
+
dateStyle: "medium",
|
|
71
|
+
timeStyle: "short",
|
|
72
|
+
timeZone: timezone,
|
|
73
|
+
}).format(new Date(dateIso));
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
return dateIso;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
function formatTaskButtonPrefix(task) {
|
|
80
|
+
return formatTaskListBadge(task);
|
|
81
|
+
}
|
|
82
|
+
function formatTaskButtonLabel(task) {
|
|
83
|
+
const prefix = `[${formatTaskButtonPrefix(task)}]`;
|
|
84
|
+
const prompt = task.prompt.replace(/\s+/g, " ").trim();
|
|
85
|
+
return truncateText(`${prefix} ${prompt}`, MAX_INLINE_BUTTON_LABEL_LENGTH);
|
|
86
|
+
}
|
|
87
|
+
function buildTaskListKeyboard(tasks) {
|
|
88
|
+
const keyboard = new InlineKeyboard();
|
|
89
|
+
tasks.forEach((task) => {
|
|
90
|
+
keyboard.text(formatTaskButtonLabel(task), `${TASKLIST_OPEN_PREFIX}${task.id}`).row();
|
|
91
|
+
});
|
|
92
|
+
keyboard.text(t("tasklist.button.cancel"), TASKLIST_CANCEL_CALLBACK);
|
|
93
|
+
return keyboard;
|
|
94
|
+
}
|
|
95
|
+
function buildTaskDetailsKeyboard(taskId) {
|
|
96
|
+
return new InlineKeyboard()
|
|
97
|
+
.text(t("tasklist.button.delete"), `${TASKLIST_DELETE_PREFIX}${taskId}`)
|
|
98
|
+
.text(t("tasklist.button.cancel"), TASKLIST_CANCEL_CALLBACK);
|
|
99
|
+
}
|
|
100
|
+
function sortTasks(tasks) {
|
|
101
|
+
return [...tasks].sort((left, right) => {
|
|
102
|
+
const leftNextRun = left.nextRunAt ? Date.parse(left.nextRunAt) : Number.POSITIVE_INFINITY;
|
|
103
|
+
const rightNextRun = right.nextRunAt ? Date.parse(right.nextRunAt) : Number.POSITIVE_INFINITY;
|
|
104
|
+
if (leftNextRun !== rightNextRun) {
|
|
105
|
+
return leftNextRun - rightNextRun;
|
|
106
|
+
}
|
|
107
|
+
return left.createdAt.localeCompare(right.createdAt);
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
function formatTaskDetails(task) {
|
|
111
|
+
const cronLine = task.kind === "cron" ? `${t("tasklist.details.cron", { cron: task.cron })}\n` : "";
|
|
112
|
+
return t("tasklist.details", {
|
|
113
|
+
prompt: task.prompt,
|
|
114
|
+
project: task.projectWorktree,
|
|
115
|
+
schedule: task.scheduleSummary,
|
|
116
|
+
cronLine,
|
|
117
|
+
timezone: task.timezone,
|
|
118
|
+
nextRunAt: formatDateTime(task.nextRunAt, task.timezone),
|
|
119
|
+
lastRunAt: formatDateTime(task.lastRunAt, task.timezone),
|
|
120
|
+
runCount: String(task.runCount),
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
export async function taskListCommand(ctx) {
|
|
124
|
+
try {
|
|
125
|
+
const tasks = sortTasks(listScheduledTasks());
|
|
126
|
+
if (tasks.length === 0) {
|
|
127
|
+
await ctx.reply(t("tasklist.empty"));
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
const message = await ctx.reply(t("tasklist.select"), {
|
|
131
|
+
reply_markup: buildTaskListKeyboard(tasks),
|
|
132
|
+
});
|
|
133
|
+
interactionManager.start({
|
|
134
|
+
kind: "custom",
|
|
135
|
+
expectedInput: "callback",
|
|
136
|
+
metadata: {
|
|
137
|
+
flow: "tasklist",
|
|
138
|
+
stage: "list",
|
|
139
|
+
messageId: message.message_id,
|
|
140
|
+
},
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
catch (error) {
|
|
144
|
+
logger.error("[TaskList] Failed to open task list", error);
|
|
145
|
+
await ctx.reply(t("tasklist.load_error"));
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
export async function handleTaskListCallback(ctx) {
|
|
149
|
+
const data = ctx.callbackQuery?.data;
|
|
150
|
+
if (!data || !data.startsWith(TASKLIST_CALLBACK_PREFIX)) {
|
|
151
|
+
return false;
|
|
152
|
+
}
|
|
153
|
+
const metadata = parseTaskListMetadata(interactionManager.getSnapshot());
|
|
154
|
+
const callbackMessageId = getCallbackMessageId(ctx);
|
|
155
|
+
if (!metadata || callbackMessageId === null || metadata.messageId !== callbackMessageId) {
|
|
156
|
+
await ctx.answerCallbackQuery({ text: t("tasklist.inactive_callback"), show_alert: true });
|
|
157
|
+
return true;
|
|
158
|
+
}
|
|
159
|
+
try {
|
|
160
|
+
if (data === TASKLIST_CANCEL_CALLBACK) {
|
|
161
|
+
clearTaskListInteraction("tasklist_cancelled");
|
|
162
|
+
await ctx.answerCallbackQuery({ text: t("tasklist.cancelled_callback") });
|
|
163
|
+
await ctx.deleteMessage().catch(() => { });
|
|
164
|
+
return true;
|
|
165
|
+
}
|
|
166
|
+
if (data.startsWith(TASKLIST_OPEN_PREFIX)) {
|
|
167
|
+
if (metadata.stage !== "list") {
|
|
168
|
+
await ctx.answerCallbackQuery({ text: t("tasklist.inactive_callback"), show_alert: true });
|
|
169
|
+
return true;
|
|
170
|
+
}
|
|
171
|
+
const taskId = data.slice(TASKLIST_OPEN_PREFIX.length);
|
|
172
|
+
const task = getScheduledTask(taskId);
|
|
173
|
+
if (!task) {
|
|
174
|
+
clearTaskListInteraction("tasklist_selected_task_missing");
|
|
175
|
+
await ctx.answerCallbackQuery({ text: t("tasklist.inactive_callback"), show_alert: true });
|
|
176
|
+
await ctx.deleteMessage().catch(() => { });
|
|
177
|
+
return true;
|
|
178
|
+
}
|
|
179
|
+
await ctx.answerCallbackQuery();
|
|
180
|
+
await ctx.editMessageText(formatTaskDetails(task), {
|
|
181
|
+
reply_markup: buildTaskDetailsKeyboard(task.id),
|
|
182
|
+
});
|
|
183
|
+
interactionManager.transition({
|
|
184
|
+
expectedInput: "callback",
|
|
185
|
+
metadata: {
|
|
186
|
+
flow: "tasklist",
|
|
187
|
+
stage: "detail",
|
|
188
|
+
messageId: metadata.messageId,
|
|
189
|
+
taskId: task.id,
|
|
190
|
+
},
|
|
191
|
+
});
|
|
192
|
+
return true;
|
|
193
|
+
}
|
|
194
|
+
if (data.startsWith(TASKLIST_DELETE_PREFIX)) {
|
|
195
|
+
if (metadata.stage !== "detail") {
|
|
196
|
+
await ctx.answerCallbackQuery({ text: t("tasklist.inactive_callback"), show_alert: true });
|
|
197
|
+
return true;
|
|
198
|
+
}
|
|
199
|
+
const taskId = data.slice(TASKLIST_DELETE_PREFIX.length);
|
|
200
|
+
if (taskId !== metadata.taskId) {
|
|
201
|
+
await ctx.answerCallbackQuery({ text: t("tasklist.inactive_callback"), show_alert: true });
|
|
202
|
+
return true;
|
|
203
|
+
}
|
|
204
|
+
await removeScheduledTask(taskId);
|
|
205
|
+
scheduledTaskRuntime.removeTask(taskId);
|
|
206
|
+
clearTaskListInteraction("tasklist_deleted");
|
|
207
|
+
await ctx.answerCallbackQuery({ text: t("tasklist.deleted_callback") });
|
|
208
|
+
await ctx.deleteMessage().catch(() => { });
|
|
209
|
+
return true;
|
|
210
|
+
}
|
|
211
|
+
await ctx.answerCallbackQuery({ text: t("callback.processing_error"), show_alert: true });
|
|
212
|
+
return true;
|
|
213
|
+
}
|
|
214
|
+
catch (error) {
|
|
215
|
+
logger.error("[TaskList] Failed to handle task list callback", error);
|
|
216
|
+
clearTaskListInteraction("tasklist_callback_error");
|
|
217
|
+
await ctx.answerCallbackQuery({ text: t("callback.processing_error") }).catch(() => { });
|
|
218
|
+
return true;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
@@ -16,6 +16,7 @@ import { safeBackgroundTask } from "../../utils/safe-background-task.js";
|
|
|
16
16
|
import { formatErrorDetails } from "../../utils/error-format.js";
|
|
17
17
|
import { logger } from "../../utils/logger.js";
|
|
18
18
|
import { t } from "../../i18n/index.js";
|
|
19
|
+
import { foregroundSessionState } from "../../scheduled-task/foreground-state.js";
|
|
19
20
|
/** Module-level references for async callbacks that don't have ctx. */
|
|
20
21
|
let botInstance = null;
|
|
21
22
|
let chatIdInstance = null;
|
|
@@ -47,6 +48,7 @@ async function isSessionBusy(sessionId, directory) {
|
|
|
47
48
|
async function resetMismatchedSessionContext() {
|
|
48
49
|
stopEventListening();
|
|
49
50
|
summaryAggregator.clear();
|
|
51
|
+
foregroundSessionState.clearAll("session_mismatch_reset");
|
|
50
52
|
clearAllInteractionState("session_mismatch_reset");
|
|
51
53
|
clearSession();
|
|
52
54
|
keyboardManager.clearContext();
|
|
@@ -192,6 +194,7 @@ export async function processUserPrompt(ctx, text, deps, fileParts = []) {
|
|
|
192
194
|
fileCount: fileParts.length,
|
|
193
195
|
};
|
|
194
196
|
logger.info(`[Bot] Calling session.prompt (fire-and-forget) with agent=${currentAgent}, fileCount=${fileParts.length}...`);
|
|
197
|
+
foregroundSessionState.markBusy(currentSession.id);
|
|
195
198
|
// CRITICAL: DO NOT wait for session.prompt to complete.
|
|
196
199
|
// If we wait, the handler will not finish and grammY will not call getUpdates,
|
|
197
200
|
// which blocks receiving button callback_query updates.
|
|
@@ -201,6 +204,7 @@ export async function processUserPrompt(ctx, text, deps, fileParts = []) {
|
|
|
201
204
|
task: () => opencodeClient.session.prompt(promptOptions),
|
|
202
205
|
onSuccess: ({ error }) => {
|
|
203
206
|
if (error) {
|
|
207
|
+
foregroundSessionState.markIdle(currentSession.id);
|
|
204
208
|
const details = formatErrorDetails(error, 6000);
|
|
205
209
|
logger.error("[Bot] OpenCode API returned an error for session.prompt", promptErrorLogContext);
|
|
206
210
|
logger.error("[Bot] session.prompt error details:", details);
|
|
@@ -212,6 +216,7 @@ export async function processUserPrompt(ctx, text, deps, fileParts = []) {
|
|
|
212
216
|
logger.info("[Bot] session.prompt completed");
|
|
213
217
|
},
|
|
214
218
|
onError: (error) => {
|
|
219
|
+
foregroundSessionState.markIdle(currentSession.id);
|
|
215
220
|
const details = formatErrorDetails(error, 6000);
|
|
216
221
|
logger.error("[Bot] session.prompt background task failed", promptErrorLogContext);
|
|
217
222
|
logger.error("[Bot] session.prompt background failure details:", details);
|
|
@@ -222,6 +227,9 @@ export async function processUserPrompt(ctx, text, deps, fileParts = []) {
|
|
|
222
227
|
return true;
|
|
223
228
|
}
|
|
224
229
|
catch (err) {
|
|
230
|
+
if (currentSession) {
|
|
231
|
+
foregroundSessionState.markIdle(currentSession.id);
|
|
232
|
+
}
|
|
225
233
|
logger.error("Error in prompt handler:", err);
|
|
226
234
|
if (interactionManager.getSnapshot()) {
|
|
227
235
|
clearAllInteractionState("message_handler_error");
|
package/dist/bot/index.js
CHANGED
|
@@ -20,6 +20,8 @@ import { abortCommand } from "./commands/abort.js";
|
|
|
20
20
|
import { opencodeStartCommand } from "./commands/opencode-start.js";
|
|
21
21
|
import { opencodeStopCommand } from "./commands/opencode-stop.js";
|
|
22
22
|
import { renameCommand, handleRenameCancel, handleRenameTextAnswer } from "./commands/rename.js";
|
|
23
|
+
import { handleTaskCallback, handleTaskTextInput, taskCommand } from "./commands/task.js";
|
|
24
|
+
import { handleTaskListCallback, taskListCommand } from "./commands/tasklist.js";
|
|
23
25
|
import { commandsCommand, handleCommandsCallback, handleCommandTextArguments, } from "./commands/commands.js";
|
|
24
26
|
import { handleQuestionCallback, showCurrentQuestion, handleQuestionTextAnswer, } from "./handlers/question.js";
|
|
25
27
|
import { handlePermissionCallback, showPermissionRequest } from "./handlers/permission.js";
|
|
@@ -49,6 +51,8 @@ import { downloadTelegramFile, toDataUri } from "./utils/file-download.js";
|
|
|
49
51
|
import { sendBotText } from "./utils/telegram-text.js";
|
|
50
52
|
import { getModelCapabilities, supportsInput } from "../model/capabilities.js";
|
|
51
53
|
import { getStoredModel } from "../model/manager.js";
|
|
54
|
+
import { foregroundSessionState } from "../scheduled-task/foreground-state.js";
|
|
55
|
+
import { scheduledTaskRuntime } from "../scheduled-task/runtime.js";
|
|
52
56
|
let botInstance = null;
|
|
53
57
|
let chatIdInstance = null;
|
|
54
58
|
let commandsInitialized = false;
|
|
@@ -141,10 +145,13 @@ async function ensureEventSubscription(directory) {
|
|
|
141
145
|
summaryAggregator.setOnComplete(async (sessionId, messageText) => {
|
|
142
146
|
if (!botInstance || !chatIdInstance) {
|
|
143
147
|
logger.error("Bot or chat ID not available for sending message");
|
|
148
|
+
foregroundSessionState.markIdle(sessionId);
|
|
144
149
|
return;
|
|
145
150
|
}
|
|
146
151
|
const currentSession = getCurrentSession();
|
|
147
152
|
if (currentSession?.id !== sessionId) {
|
|
153
|
+
foregroundSessionState.markIdle(sessionId);
|
|
154
|
+
await scheduledTaskRuntime.flushDeferredDeliveries();
|
|
148
155
|
return;
|
|
149
156
|
}
|
|
150
157
|
await toolMessageBatcher.flushSession(sessionId, "assistant_message_completed");
|
|
@@ -172,6 +179,10 @@ async function ensureEventSubscription(directory) {
|
|
|
172
179
|
logger.error("[Bot] CRITICAL: Stopping event processing due to error");
|
|
173
180
|
summaryAggregator.clear();
|
|
174
181
|
}
|
|
182
|
+
finally {
|
|
183
|
+
foregroundSessionState.markIdle(sessionId);
|
|
184
|
+
await scheduledTaskRuntime.flushDeferredDeliveries();
|
|
185
|
+
}
|
|
175
186
|
});
|
|
176
187
|
summaryAggregator.setOnTool(async (toolInfo) => {
|
|
177
188
|
if (!botInstance || !chatIdInstance) {
|
|
@@ -308,10 +319,13 @@ async function ensureEventSubscription(directory) {
|
|
|
308
319
|
});
|
|
309
320
|
summaryAggregator.setOnSessionError(async (sessionId, message) => {
|
|
310
321
|
if (!botInstance || !chatIdInstance) {
|
|
322
|
+
foregroundSessionState.markIdle(sessionId);
|
|
311
323
|
return;
|
|
312
324
|
}
|
|
313
325
|
const currentSession = getCurrentSession();
|
|
314
326
|
if (!currentSession || currentSession.id !== sessionId) {
|
|
327
|
+
foregroundSessionState.markIdle(sessionId);
|
|
328
|
+
await scheduledTaskRuntime.flushDeferredDeliveries();
|
|
315
329
|
return;
|
|
316
330
|
}
|
|
317
331
|
await toolMessageBatcher.flushSession(sessionId, "session_error");
|
|
@@ -324,6 +338,8 @@ async function ensureEventSubscription(directory) {
|
|
|
324
338
|
.catch((err) => {
|
|
325
339
|
logger.error("[Bot] Failed to send session.error message:", err);
|
|
326
340
|
});
|
|
341
|
+
foregroundSessionState.markIdle(sessionId);
|
|
342
|
+
await scheduledTaskRuntime.flushDeferredDeliveries();
|
|
327
343
|
});
|
|
328
344
|
summaryAggregator.setOnSessionRetry(async ({ sessionId, message }) => {
|
|
329
345
|
if (!botInstance || !chatIdInstance) {
|
|
@@ -458,6 +474,8 @@ export function createBot() {
|
|
|
458
474
|
bot.command("sessions", sessionsCommand);
|
|
459
475
|
bot.command("new", newCommand);
|
|
460
476
|
bot.command("abort", abortCommand);
|
|
477
|
+
bot.command("task", taskCommand);
|
|
478
|
+
bot.command("tasklist", taskListCommand);
|
|
461
479
|
bot.command("rename", renameCommand);
|
|
462
480
|
bot.command("commands", commandsCommand);
|
|
463
481
|
bot.on("message:text", unknownCommandMiddleware);
|
|
@@ -478,9 +496,11 @@ export function createBot() {
|
|
|
478
496
|
const handledModel = await handleModelSelect(ctx);
|
|
479
497
|
const handledVariant = await handleVariantSelect(ctx);
|
|
480
498
|
const handledCompactConfirm = await handleCompactConfirm(ctx);
|
|
499
|
+
const handledTask = await handleTaskCallback(ctx);
|
|
500
|
+
const handledTaskList = await handleTaskListCallback(ctx);
|
|
481
501
|
const handledRenameCancel = await handleRenameCancel(ctx);
|
|
482
502
|
const handledCommands = await handleCommandsCallback(ctx, { bot, ensureEventSubscription });
|
|
483
|
-
logger.debug(`[Bot] Callback handled: inlineCancel=${handledInlineCancel}, session=${handledSession}, project=${handledProject}, question=${handledQuestion}, permission=${handledPermission}, agent=${handledAgent}, model=${handledModel}, variant=${handledVariant}, compactConfirm=${handledCompactConfirm}, rename=${handledRenameCancel}, commands=${handledCommands}`);
|
|
503
|
+
logger.debug(`[Bot] Callback handled: inlineCancel=${handledInlineCancel}, session=${handledSession}, project=${handledProject}, question=${handledQuestion}, permission=${handledPermission}, agent=${handledAgent}, model=${handledModel}, variant=${handledVariant}, compactConfirm=${handledCompactConfirm}, task=${handledTask}, taskList=${handledTaskList}, rename=${handledRenameCancel}, commands=${handledCommands}`);
|
|
484
504
|
if (!handledInlineCancel &&
|
|
485
505
|
!handledSession &&
|
|
486
506
|
!handledProject &&
|
|
@@ -490,6 +510,8 @@ export function createBot() {
|
|
|
490
510
|
!handledModel &&
|
|
491
511
|
!handledVariant &&
|
|
492
512
|
!handledCompactConfirm &&
|
|
513
|
+
!handledTask &&
|
|
514
|
+
!handledTaskList &&
|
|
493
515
|
!handledRenameCancel &&
|
|
494
516
|
!handledCommands) {
|
|
495
517
|
logger.debug("Unknown callback query:", ctx.callbackQuery?.data);
|
|
@@ -677,6 +699,10 @@ export function createBot() {
|
|
|
677
699
|
await handleQuestionTextAnswer(ctx);
|
|
678
700
|
return;
|
|
679
701
|
}
|
|
702
|
+
const handledTask = await handleTaskTextInput(ctx);
|
|
703
|
+
if (handledTask) {
|
|
704
|
+
return;
|
|
705
|
+
}
|
|
680
706
|
const handledRename = await handleRenameTextAnswer(ctx);
|
|
681
707
|
if (handledRename) {
|
|
682
708
|
return;
|
|
@@ -46,6 +46,17 @@ function getInteractionBlockedMessage(reason, interactionKind) {
|
|
|
46
46
|
return t("rename.blocked.expected_name");
|
|
47
47
|
}
|
|
48
48
|
}
|
|
49
|
+
if (interactionKind === "task") {
|
|
50
|
+
switch (reason) {
|
|
51
|
+
case "command_not_allowed":
|
|
52
|
+
return t("task.blocked.command_not_allowed");
|
|
53
|
+
case "expected_callback":
|
|
54
|
+
case "expected_command":
|
|
55
|
+
case "expected_text":
|
|
56
|
+
default:
|
|
57
|
+
return t("task.blocked.expected_input");
|
|
58
|
+
}
|
|
59
|
+
}
|
|
49
60
|
switch (reason) {
|
|
50
61
|
case "expired":
|
|
51
62
|
return t("interaction.blocked.expired");
|
package/dist/config.js
CHANGED
|
@@ -85,6 +85,7 @@ export const config = {
|
|
|
85
85
|
bot: {
|
|
86
86
|
sessionsListLimit: getOptionalPositiveIntEnvVar("SESSIONS_LIST_LIMIT", 10),
|
|
87
87
|
projectsListLimit: getOptionalPositiveIntEnvVar("PROJECTS_LIST_LIMIT", 10),
|
|
88
|
+
taskLimit: getOptionalPositiveIntEnvVar("TASK_LIMIT", 10),
|
|
88
89
|
locale: getOptionalLocaleEnvVar("BOT_LOCALE", "en"),
|
|
89
90
|
serviceMessagesIntervalSec: getOptionalNonNegativeIntEnvVarFromKeys(["SERVICE_MESSAGES_INTERVAL_SEC", "TOOL_MESSAGES_INTERVAL_SEC"], 5),
|
|
90
91
|
hideThinkingMessages: getOptionalBooleanEnvVar("HIDE_THINKING_MESSAGES", false),
|
package/dist/i18n/de.js
CHANGED
|
@@ -4,6 +4,8 @@ export const de = {
|
|
|
4
4
|
"cmd.description.stop": "Aktuelle Aktion stoppen",
|
|
5
5
|
"cmd.description.sessions": "Sitzungen auflisten",
|
|
6
6
|
"cmd.description.projects": "Projekte auflisten",
|
|
7
|
+
"cmd.description.task": "Geplante Aufgabe erstellen",
|
|
8
|
+
"cmd.description.tasklist": "Geplante Aufgaben anzeigen",
|
|
7
9
|
"cmd.description.commands": "Benutzerdefinierte Befehle",
|
|
8
10
|
"cmd.description.opencode_start": "OpenCode-Server starten",
|
|
9
11
|
"cmd.description.opencode_stop": "OpenCode-Server stoppen",
|
|
@@ -30,7 +32,7 @@ export const de = {
|
|
|
30
32
|
"inline.cancelled_callback": "Abgebrochen",
|
|
31
33
|
"common.unknown": "unbekannt",
|
|
32
34
|
"common.unknown_error": "unbekannter Fehler",
|
|
33
|
-
"start.welcome": "👋 Willkommen beim OpenCode Telegram Bot!\n\nNutze Befehle:\n/projects — Projekt auswählen\n/sessions — Sitzungsliste\n/new — neue Sitzung\n/status — Status\n/help — Hilfe\n\nNutze die unteren Buttons, um Modus, Modell und Variante zu wählen.",
|
|
35
|
+
"start.welcome": "👋 Willkommen beim OpenCode Telegram Bot!\n\nNutze Befehle:\n/projects — Projekt auswählen\n/sessions — Sitzungsliste\n/new — neue Sitzung\n/task — geplante Aufgabe\n/tasklist — geplante Aufgaben\n/status — Status\n/help — Hilfe\n\nNutze die unteren Buttons, um Modus, Modell und Variante zu wählen.",
|
|
34
36
|
"help.keyboard_hint": "💡 Nutze die unteren Buttons für Modus, Modell, Variante und Kontextaktionen.",
|
|
35
37
|
"help.text": "📖 **Hilfe**\n\n/status - Serverstatus prüfen\n/sessions - Sitzungsliste\n/new - Neue Sitzung erstellen\n/help - Hilfe",
|
|
36
38
|
"bot.thinking": "💭 Denke...",
|
|
@@ -247,6 +249,41 @@ export const de = {
|
|
|
247
249
|
"rename.blocked.expected_name": "⚠️ Sende den neuen Sitzungsnamen als Text oder tippe in der Umbenennen-Nachricht auf Abbrechen.",
|
|
248
250
|
"rename.blocked.command_not_allowed": "⚠️ Dieser Befehl ist nicht verfügbar, solange beim Umbenennen auf einen neuen Namen gewartet wird.",
|
|
249
251
|
"rename.button.cancel": "❌ Abbrechen",
|
|
252
|
+
"task.prompt.schedule": "⏰ Sende den Zeitplan der Aufgabe in natürlicher Sprache.\n\nBeispiele:\n- alle 5 Minuten\n- jeden Tag um 17:00\n- morgen um 12:00",
|
|
253
|
+
"task.schedule_empty": "⚠️ Der Zeitplan darf nicht leer sein.",
|
|
254
|
+
"task.parse.in_progress": "⏳ Zeitplan wird verarbeitet...",
|
|
255
|
+
"task.parse_error": "🔴 Zeitplan konnte nicht erkannt werden.\n\n{message}\n\nSende den Zeitraum bitte noch einmal klarer formuliert.",
|
|
256
|
+
"task.schedule_preview": "✅ Zeitplan erkannt\n\nVerstanden als: {summary}\n{cronLine}Zeitzone: {timezone}\nTyp: {kind}\nNächster Lauf: {nextRunAt}",
|
|
257
|
+
"task.schedule_preview.cron": "Cron: {cron}",
|
|
258
|
+
"task.prompt.body": "📝 Sende jetzt, was der Bot nach Zeitplan tun soll.",
|
|
259
|
+
"task.prompt_empty": "⚠️ Der Aufgabentext darf nicht leer sein.",
|
|
260
|
+
"task.created": "✅ Geplante Aufgabe erstellt\n\nAufgabe: {description}\nProjekt: {project}\nModell: {model}\nZeitplan: {schedule}\n{cronLine}Nächster Lauf: {nextRunAt}",
|
|
261
|
+
"task.created.cron": "Cron: {cron}",
|
|
262
|
+
"task.button.retry_schedule": "🔁 Zeitplan neu eingeben",
|
|
263
|
+
"task.button.cancel": "❌ Abbrechen",
|
|
264
|
+
"task.retry_schedule_callback": "Zeitplaneingabe wird zurückgesetzt...",
|
|
265
|
+
"task.cancel_callback": "Abbruch...",
|
|
266
|
+
"task.cancelled": "❌ Erstellung der geplanten Aufgabe abgebrochen.",
|
|
267
|
+
"task.inactive_callback": "Dieser Ablauf für geplante Aufgaben ist nicht mehr aktiv",
|
|
268
|
+
"task.inactive": "⚠️ Die Erstellung geplanter Aufgaben ist nicht aktiv. Starte /task erneut.",
|
|
269
|
+
"task.blocked.expected_input": "⚠️ Schließe zuerst die aktuelle geplante Aufgabe ab: Sende Text oder nutze die Schaltfläche in der Zeitplan-Nachricht.",
|
|
270
|
+
"task.blocked.command_not_allowed": "⚠️ Dieser Befehl ist nicht verfügbar, solange die Erstellung einer geplanten Aufgabe aktiv ist.",
|
|
271
|
+
"task.limit_reached": "⚠️ Aufgabenlimit erreicht ({limit}). Lösche zuerst eine bestehende geplante Aufgabe.",
|
|
272
|
+
"task.schedule_too_frequent": "Der wiederkehrende Zeitplan ist zu häufig. Das minimale erlaubte Intervall ist einmal alle 5 Minuten.",
|
|
273
|
+
"task.kind.cron": "wiederkehrend",
|
|
274
|
+
"task.kind.once": "einmalig",
|
|
275
|
+
"task.run.success": "⏰ Geplante Aufgabe abgeschlossen: {description}\n\n{result}",
|
|
276
|
+
"task.run.error": "🔴 Geplante Aufgabe fehlgeschlagen: {description}\n\nFehler: {error}",
|
|
277
|
+
"tasklist.empty": "📭 Noch keine geplanten Aufgaben.",
|
|
278
|
+
"tasklist.select": "Wähle eine geplante Aufgabe:",
|
|
279
|
+
"tasklist.details": "⏰ Geplante Aufgabe\n\nAufgabe: {prompt}\nProjekt: {project}\nZeitplan: {schedule}\n{cronLine}Zeitzone: {timezone}\nNächster Lauf: {nextRunAt}\nLetzter Lauf: {lastRunAt}\nAnzahl Läufe: {runCount}",
|
|
280
|
+
"tasklist.details.cron": "Cron: {cron}",
|
|
281
|
+
"tasklist.button.delete": "🗑 Löschen",
|
|
282
|
+
"tasklist.button.cancel": "❌ Abbrechen",
|
|
283
|
+
"tasklist.deleted_callback": "Gelöscht",
|
|
284
|
+
"tasklist.cancelled_callback": "Abgebrochen",
|
|
285
|
+
"tasklist.inactive_callback": "Dieses Menü für geplante Aufgaben ist inaktiv",
|
|
286
|
+
"tasklist.load_error": "🔴 Geplante Aufgaben konnten nicht geladen werden.",
|
|
250
287
|
"commands.select": "Wähle einen OpenCode-Befehl:",
|
|
251
288
|
"commands.empty": "📭 Für dieses Projekt sind keine OpenCode-Befehle verfügbar.",
|
|
252
289
|
"commands.fetch_error": "🔴 OpenCode-Befehle konnten nicht geladen werden.",
|
package/dist/i18n/en.js
CHANGED
|
@@ -4,6 +4,8 @@ export const en = {
|
|
|
4
4
|
"cmd.description.stop": "Stop current action",
|
|
5
5
|
"cmd.description.sessions": "List sessions",
|
|
6
6
|
"cmd.description.projects": "List projects",
|
|
7
|
+
"cmd.description.task": "Create a scheduled task",
|
|
8
|
+
"cmd.description.tasklist": "List scheduled tasks",
|
|
7
9
|
"cmd.description.commands": "Custom commands",
|
|
8
10
|
"cmd.description.opencode_start": "Start OpenCode server",
|
|
9
11
|
"cmd.description.opencode_stop": "Stop OpenCode server",
|
|
@@ -30,7 +32,7 @@ export const en = {
|
|
|
30
32
|
"inline.cancelled_callback": "Cancelled",
|
|
31
33
|
"common.unknown": "unknown",
|
|
32
34
|
"common.unknown_error": "unknown error",
|
|
33
|
-
"start.welcome": "👋 Welcome to OpenCode Telegram Bot!\n\nUse commands:\n/projects — select project\n/sessions — session list\n/new — new session\n/status — status\n/help — help\n\nUse the bottom buttons to select agent mode, model, and variant.",
|
|
35
|
+
"start.welcome": "👋 Welcome to OpenCode Telegram Bot!\n\nUse commands:\n/projects — select project\n/sessions — session list\n/new — new session\n/task — scheduled task\n/tasklist — scheduled tasks\n/status — status\n/help — help\n\nUse the bottom buttons to select agent mode, model, and variant.",
|
|
34
36
|
"help.keyboard_hint": "💡 Use the bottom keyboard buttons for agent mode, model, variant, and context actions.",
|
|
35
37
|
"help.text": "📖 **Help**\n\n/status - Check server status\n/sessions - Session list\n/new - Create new session\n/help - Help",
|
|
36
38
|
"bot.thinking": "💭 Thinking...",
|
|
@@ -247,6 +249,41 @@ export const en = {
|
|
|
247
249
|
"rename.blocked.expected_name": "⚠️ Enter a new session name as text or tap Cancel in rename message.",
|
|
248
250
|
"rename.blocked.command_not_allowed": "⚠️ This command is not available while rename is waiting for a new name.",
|
|
249
251
|
"rename.button.cancel": "❌ Cancel",
|
|
252
|
+
"task.prompt.schedule": "⏰ Send the task schedule in natural language.\n\nExamples:\n- every 5 minutes\n- every day at 17:00\n- tomorrow at 12:00",
|
|
253
|
+
"task.schedule_empty": "⚠️ Schedule cannot be empty.",
|
|
254
|
+
"task.parse.in_progress": "⏳ Parsing schedule...",
|
|
255
|
+
"task.parse_error": "🔴 Failed to parse schedule.\n\n{message}\n\nSend the schedule again in a clearer form.",
|
|
256
|
+
"task.schedule_preview": "✅ Schedule parsed\n\nHow I understood it: {summary}\n{cronLine}Timezone: {timezone}\nType: {kind}\nNext run: {nextRunAt}",
|
|
257
|
+
"task.schedule_preview.cron": "Cron: {cron}",
|
|
258
|
+
"task.prompt.body": "📝 Now send what the bot should do on schedule.",
|
|
259
|
+
"task.prompt_empty": "⚠️ Task text cannot be empty.",
|
|
260
|
+
"task.created": "✅ Scheduled task created\n\nTask: {description}\nProject: {project}\nModel: {model}\nSchedule: {schedule}\n{cronLine}Next run: {nextRunAt}",
|
|
261
|
+
"task.created.cron": "Cron: {cron}",
|
|
262
|
+
"task.button.retry_schedule": "🔁 Re-enter schedule",
|
|
263
|
+
"task.button.cancel": "❌ Cancel",
|
|
264
|
+
"task.retry_schedule_callback": "Re-entering schedule...",
|
|
265
|
+
"task.cancel_callback": "Cancelling...",
|
|
266
|
+
"task.cancelled": "❌ Scheduled task creation cancelled.",
|
|
267
|
+
"task.inactive_callback": "This scheduled task flow is inactive",
|
|
268
|
+
"task.inactive": "⚠️ Scheduled task creation is not active. Run /task again.",
|
|
269
|
+
"task.blocked.expected_input": "⚠️ Finish the current scheduled task setup first by sending text or using the button in the schedule message.",
|
|
270
|
+
"task.blocked.command_not_allowed": "⚠️ This command is not available while scheduled task creation is active.",
|
|
271
|
+
"task.limit_reached": "⚠️ Task limit reached ({limit}). Delete an existing scheduled task first.",
|
|
272
|
+
"task.schedule_too_frequent": "Recurring schedule is too frequent. The minimum allowed interval is once every 5 minutes.",
|
|
273
|
+
"task.kind.cron": "recurring",
|
|
274
|
+
"task.kind.once": "one-time",
|
|
275
|
+
"task.run.success": "⏰ Scheduled task completed: {description}\n\n{result}",
|
|
276
|
+
"task.run.error": "🔴 Scheduled task failed: {description}\n\nError: {error}",
|
|
277
|
+
"tasklist.empty": "📭 No scheduled tasks yet.",
|
|
278
|
+
"tasklist.select": "Select a scheduled task:",
|
|
279
|
+
"tasklist.details": "⏰ Scheduled task\n\nTask: {prompt}\nProject: {project}\nSchedule: {schedule}\n{cronLine}Timezone: {timezone}\nNext run: {nextRunAt}\nLast run: {lastRunAt}\nRun count: {runCount}",
|
|
280
|
+
"tasklist.details.cron": "Cron: {cron}",
|
|
281
|
+
"tasklist.button.delete": "🗑 Delete",
|
|
282
|
+
"tasklist.button.cancel": "❌ Cancel",
|
|
283
|
+
"tasklist.deleted_callback": "Deleted",
|
|
284
|
+
"tasklist.cancelled_callback": "Cancelled",
|
|
285
|
+
"tasklist.inactive_callback": "This scheduled task menu is inactive",
|
|
286
|
+
"tasklist.load_error": "🔴 Failed to load scheduled tasks.",
|
|
250
287
|
"commands.select": "Choose an OpenCode command:",
|
|
251
288
|
"commands.empty": "📭 No OpenCode commands are available for this project.",
|
|
252
289
|
"commands.fetch_error": "🔴 Failed to load OpenCode commands.",
|
package/dist/i18n/es.js
CHANGED
|
@@ -4,6 +4,8 @@ export const es = {
|
|
|
4
4
|
"cmd.description.stop": "Detener la acción actual",
|
|
5
5
|
"cmd.description.sessions": "Listar sesiones",
|
|
6
6
|
"cmd.description.projects": "Listar proyectos",
|
|
7
|
+
"cmd.description.task": "Crear tarea programada",
|
|
8
|
+
"cmd.description.tasklist": "Ver tareas programadas",
|
|
7
9
|
"cmd.description.commands": "Comandos personalizados",
|
|
8
10
|
"cmd.description.opencode_start": "Iniciar servidor OpenCode",
|
|
9
11
|
"cmd.description.opencode_stop": "Detener servidor OpenCode",
|
|
@@ -30,7 +32,7 @@ export const es = {
|
|
|
30
32
|
"inline.cancelled_callback": "Cancelado",
|
|
31
33
|
"common.unknown": "desconocido",
|
|
32
34
|
"common.unknown_error": "error desconocido",
|
|
33
|
-
"start.welcome": "👋 ¡Bienvenido a OpenCode Telegram Bot!\n\nUsa los comandos:\n/projects — seleccionar proyecto\n/sessions — lista de sesiones\n/new — sesión nueva\n/status — estado\n/help — ayuda\n\nUsa los botones inferiores para elegir modo, modelo y variante.",
|
|
35
|
+
"start.welcome": "👋 ¡Bienvenido a OpenCode Telegram Bot!\n\nUsa los comandos:\n/projects — seleccionar proyecto\n/sessions — lista de sesiones\n/new — sesión nueva\n/task — tarea programada\n/tasklist — tareas programadas\n/status — estado\n/help — ayuda\n\nUsa los botones inferiores para elegir modo, modelo y variante.",
|
|
34
36
|
"help.keyboard_hint": "💡 Usa los botones inferiores para modo del agente, modelo, variante y acciones de contexto.",
|
|
35
37
|
"help.text": "📖 **Ayuda**\n\n/status - Ver estado del servidor\n/sessions - Lista de sesiones\n/new - Crear una sesión nueva\n/help - Ayuda",
|
|
36
38
|
"bot.thinking": "💭 Pensando...",
|
|
@@ -247,6 +249,41 @@ export const es = {
|
|
|
247
249
|
"rename.blocked.expected_name": "⚠️ Introduce el nuevo nombre de la sesión como texto o toca Cancelar en el mensaje de cambio de nombre.",
|
|
248
250
|
"rename.blocked.command_not_allowed": "⚠️ Este comando no está disponible mientras el cambio de nombre espera un nuevo nombre.",
|
|
249
251
|
"rename.button.cancel": "❌ Cancelar",
|
|
252
|
+
"task.prompt.schedule": "⏰ Envía el horario de la tarea en lenguaje natural.\n\nEjemplos:\n- cada 5 minutos\n- cada día a las 17:00\n- mañana a las 12:00",
|
|
253
|
+
"task.schedule_empty": "⚠️ El horario no puede estar vacío.",
|
|
254
|
+
"task.parse.in_progress": "⏳ Analizando horario...",
|
|
255
|
+
"task.parse_error": "🔴 No se pudo interpretar el horario.\n\n{message}\n\nEnvía el periodo otra vez de forma más clara.",
|
|
256
|
+
"task.schedule_preview": "✅ Horario interpretado\n\nEntendido como: {summary}\n{cronLine}Zona horaria: {timezone}\nTipo: {kind}\nPróxima ejecución: {nextRunAt}",
|
|
257
|
+
"task.schedule_preview.cron": "Cron: {cron}",
|
|
258
|
+
"task.prompt.body": "📝 Ahora envía lo que el bot debe hacer según este horario.",
|
|
259
|
+
"task.prompt_empty": "⚠️ El texto de la tarea no puede estar vacío.",
|
|
260
|
+
"task.created": "✅ Tarea programada creada\n\nTarea: {description}\nProyecto: {project}\nModelo: {model}\nHorario: {schedule}\n{cronLine}Próxima ejecución: {nextRunAt}",
|
|
261
|
+
"task.created.cron": "Cron: {cron}",
|
|
262
|
+
"task.button.retry_schedule": "🔁 Volver a introducir horario",
|
|
263
|
+
"task.button.cancel": "❌ Cancelar",
|
|
264
|
+
"task.retry_schedule_callback": "Volviendo a introducir el horario...",
|
|
265
|
+
"task.cancel_callback": "Cancelando...",
|
|
266
|
+
"task.cancelled": "❌ Creación de la tarea programada cancelada.",
|
|
267
|
+
"task.inactive_callback": "Este flujo de tarea programada ya no está activo",
|
|
268
|
+
"task.inactive": "⚠️ La creación de la tarea programada no está activa. Ejecuta /task otra vez.",
|
|
269
|
+
"task.blocked.expected_input": "⚠️ Primero termina la configuración actual de la tarea programada: envía texto o usa el botón del mensaje del horario.",
|
|
270
|
+
"task.blocked.command_not_allowed": "⚠️ Este comando no está disponible mientras la creación de la tarea programada está activa.",
|
|
271
|
+
"task.limit_reached": "⚠️ Se alcanzó el límite de tareas ({limit}). Primero elimina una tarea programada existente.",
|
|
272
|
+
"task.schedule_too_frequent": "El horario recurrente es demasiado frecuente. El intervalo mínimo permitido es una vez cada 5 minutos.",
|
|
273
|
+
"task.kind.cron": "recurrente",
|
|
274
|
+
"task.kind.once": "única",
|
|
275
|
+
"task.run.success": "⏰ Tarea programada completada: {description}\n\n{result}",
|
|
276
|
+
"task.run.error": "🔴 La tarea programada falló: {description}\n\nError: {error}",
|
|
277
|
+
"tasklist.empty": "📭 Aún no hay tareas programadas.",
|
|
278
|
+
"tasklist.select": "Elige una tarea programada:",
|
|
279
|
+
"tasklist.details": "⏰ Tarea programada\n\nTarea: {prompt}\nProyecto: {project}\nHorario: {schedule}\n{cronLine}Zona horaria: {timezone}\nPróxima ejecución: {nextRunAt}\nÚltima ejecución: {lastRunAt}\nNúmero de ejecuciones: {runCount}",
|
|
280
|
+
"tasklist.details.cron": "Cron: {cron}",
|
|
281
|
+
"tasklist.button.delete": "🗑 Eliminar",
|
|
282
|
+
"tasklist.button.cancel": "❌ Cancelar",
|
|
283
|
+
"tasklist.deleted_callback": "Eliminada",
|
|
284
|
+
"tasklist.cancelled_callback": "Cancelado",
|
|
285
|
+
"tasklist.inactive_callback": "Este menú de tareas programadas está inactivo",
|
|
286
|
+
"tasklist.load_error": "🔴 No se pudieron cargar las tareas programadas.",
|
|
250
287
|
"commands.select": "Elige un comando de OpenCode:",
|
|
251
288
|
"commands.empty": "📭 No hay comandos de OpenCode disponibles para este proyecto.",
|
|
252
289
|
"commands.fetch_error": "🔴 No se pudieron cargar los comandos de OpenCode.",
|
package/dist/i18n/fr.js
CHANGED
|
@@ -4,6 +4,8 @@ export const fr = {
|
|
|
4
4
|
"cmd.description.stop": "Arrêter l'action en cours",
|
|
5
5
|
"cmd.description.sessions": "Lister les sessions",
|
|
6
6
|
"cmd.description.projects": "Lister les projets",
|
|
7
|
+
"cmd.description.task": "Créer une tâche planifiée",
|
|
8
|
+
"cmd.description.tasklist": "Afficher les tâches planifiées",
|
|
7
9
|
"cmd.description.commands": "Commandes personnalisées",
|
|
8
10
|
"cmd.description.opencode_start": "Démarrer le serveur OpenCode",
|
|
9
11
|
"cmd.description.opencode_stop": "Arrêter le serveur OpenCode",
|
|
@@ -30,7 +32,7 @@ export const fr = {
|
|
|
30
32
|
"inline.cancelled_callback": "Annulé",
|
|
31
33
|
"common.unknown": "inconnu",
|
|
32
34
|
"common.unknown_error": "erreur inconnue",
|
|
33
|
-
"start.welcome": "👋 Bienvenue dans OpenCode Telegram Bot !\n\nUtilisez les commandes :\n/projects — sélectionner un projet\n/sessions — liste des sessions\n/new — nouvelle session\n/status — statut\n/help — aide\n\nUtilisez les boutons du bas pour choisir le mode d'agent, le modèle et la variante.",
|
|
35
|
+
"start.welcome": "👋 Bienvenue dans OpenCode Telegram Bot !\n\nUtilisez les commandes :\n/projects — sélectionner un projet\n/sessions — liste des sessions\n/new — nouvelle session\n/task — tâche planifiée\n/tasklist — tâches planifiées\n/status — statut\n/help — aide\n\nUtilisez les boutons du bas pour choisir le mode d'agent, le modèle et la variante.",
|
|
34
36
|
"help.keyboard_hint": "💡 Utilisez les boutons du bas pour le mode d'agent, le modèle, la variante et les actions de contexte.",
|
|
35
37
|
"help.text": "📖 **Aide**\n\n/status - Vérifier l'état du serveur\n/sessions - Liste des sessions\n/new - Créer une nouvelle session\n/help - Aide",
|
|
36
38
|
"bot.thinking": "💭 Réflexion en cours...",
|
|
@@ -247,6 +249,41 @@ export const fr = {
|
|
|
247
249
|
"rename.blocked.expected_name": "⚠️ Entrez le nouveau nom de la session sous forme de texte ou appuyez sur Annuler dans le message de renommage.",
|
|
248
250
|
"rename.blocked.command_not_allowed": "⚠️ Cette commande n'est pas disponible tant que le renommage attend un nouveau nom.",
|
|
249
251
|
"rename.button.cancel": "❌ Annuler",
|
|
252
|
+
"task.prompt.schedule": "⏰ Envoyez le planning de la tâche en langage naturel.\n\nExemples :\n- toutes les 5 minutes\n- chaque jour à 17:00\n- demain à 12:00",
|
|
253
|
+
"task.schedule_empty": "⚠️ Le planning ne peut pas être vide.",
|
|
254
|
+
"task.parse.in_progress": "⏳ Analyse du planning...",
|
|
255
|
+
"task.parse_error": "🔴 Impossible d'interpréter le planning.\n\n{message}\n\nEnvoyez le créneau à nouveau de façon plus claire.",
|
|
256
|
+
"task.schedule_preview": "✅ Planning interprété\n\nCompris comme : {summary}\n{cronLine}Fuseau horaire : {timezone}\nType : {kind}\nProchaine exécution : {nextRunAt}",
|
|
257
|
+
"task.schedule_preview.cron": "Cron : {cron}",
|
|
258
|
+
"task.prompt.body": "📝 Envoyez maintenant ce que le bot doit faire selon ce planning.",
|
|
259
|
+
"task.prompt_empty": "⚠️ Le texte de la tâche ne peut pas être vide.",
|
|
260
|
+
"task.created": "✅ Tâche planifiée créée\n\nTâche : {description}\nProjet : {project}\nModèle : {model}\nPlanning : {schedule}\n{cronLine}Prochaine exécution : {nextRunAt}",
|
|
261
|
+
"task.created.cron": "Cron : {cron}",
|
|
262
|
+
"task.button.retry_schedule": "🔁 Ressaisir le planning",
|
|
263
|
+
"task.button.cancel": "❌ Annuler",
|
|
264
|
+
"task.retry_schedule_callback": "Retour à la saisie du planning...",
|
|
265
|
+
"task.cancel_callback": "Annulation...",
|
|
266
|
+
"task.cancelled": "❌ Création de la tâche planifiée annulée.",
|
|
267
|
+
"task.inactive_callback": "Ce flux de tâche planifiée n'est plus actif",
|
|
268
|
+
"task.inactive": "⚠️ La création de tâche planifiée n'est pas active. Relancez /task.",
|
|
269
|
+
"task.blocked.expected_input": "⚠️ Terminez d'abord la configuration de la tâche planifiée : envoyez du texte ou utilisez le bouton dans le message du planning.",
|
|
270
|
+
"task.blocked.command_not_allowed": "⚠️ Cette commande n'est pas disponible pendant la création d'une tâche planifiée.",
|
|
271
|
+
"task.limit_reached": "⚠️ Limite de tâches atteinte ({limit}). Supprimez d'abord une tâche planifiée existante.",
|
|
272
|
+
"task.schedule_too_frequent": "Le planning récurrent est trop fréquent. L'intervalle minimum autorisé est d'une fois toutes les 5 minutes.",
|
|
273
|
+
"task.kind.cron": "récurrente",
|
|
274
|
+
"task.kind.once": "ponctuelle",
|
|
275
|
+
"task.run.success": "⏰ Tâche planifiée terminée : {description}\n\n{result}",
|
|
276
|
+
"task.run.error": "🔴 Échec de la tâche planifiée : {description}\n\nErreur : {error}",
|
|
277
|
+
"tasklist.empty": "📭 Aucune tâche planifiée pour le moment.",
|
|
278
|
+
"tasklist.select": "Sélectionnez une tâche planifiée :",
|
|
279
|
+
"tasklist.details": "⏰ Tâche planifiée\n\nTâche : {prompt}\nProjet : {project}\nPlanning : {schedule}\n{cronLine}Fuseau horaire : {timezone}\nProchaine exécution : {nextRunAt}\nDernière exécution : {lastRunAt}\nNombre d'exécutions : {runCount}",
|
|
280
|
+
"tasklist.details.cron": "Cron : {cron}",
|
|
281
|
+
"tasklist.button.delete": "🗑 Supprimer",
|
|
282
|
+
"tasklist.button.cancel": "❌ Annuler",
|
|
283
|
+
"tasklist.deleted_callback": "Supprimée",
|
|
284
|
+
"tasklist.cancelled_callback": "Annulé",
|
|
285
|
+
"tasklist.inactive_callback": "Ce menu des tâches planifiées est inactif",
|
|
286
|
+
"tasklist.load_error": "🔴 Impossible de charger les tâches planifiées.",
|
|
250
287
|
"commands.select": "Choisissez une commande OpenCode :",
|
|
251
288
|
"commands.empty": "📭 Aucune commande OpenCode n'est disponible pour ce projet.",
|
|
252
289
|
"commands.fetch_error": "🔴 Impossible de charger les commandes OpenCode.",
|