@coinseeker/opencode-telegram-plugin 1.0.14 → 1.1.1
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 +2 -2
- package/dist/telegram-remote.js +353 -206
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -15,11 +15,11 @@ Configure the npm package in `~/.config/opencode/opencode.json`:
|
|
|
15
15
|
|
|
16
16
|
```json
|
|
17
17
|
{
|
|
18
|
-
"plugin": ["@coinseeker/opencode-telegram-plugin@1.
|
|
18
|
+
"plugin": ["@coinseeker/opencode-telegram-plugin@1.1.1"]
|
|
19
19
|
}
|
|
20
20
|
```
|
|
21
21
|
|
|
22
|
-
Current stable version: `@coinseeker/opencode-telegram-plugin@1.
|
|
22
|
+
Current stable version: `@coinseeker/opencode-telegram-plugin@1.1.1`.
|
|
23
23
|
|
|
24
24
|
Restart OpenCode after editing the config. OpenCode resolves npm package plugins on startup.
|
|
25
25
|
|
package/dist/telegram-remote.js
CHANGED
|
@@ -40,9 +40,10 @@ function pendingQuestionText(questions, questionIndex) {
|
|
|
40
40
|
|
|
41
41
|
// src/bot.ts
|
|
42
42
|
function createTelegramBot(opts) {
|
|
43
|
-
const { config, stateStore, logger
|
|
43
|
+
const { config, stateStore, logger } = opts;
|
|
44
44
|
const bot = new Bot(config.botToken);
|
|
45
45
|
let activeChatId = opts.initialChatId;
|
|
46
|
+
let pollingActive = false;
|
|
46
47
|
let questionDispatcher;
|
|
47
48
|
let permissionDispatcher;
|
|
48
49
|
let startWorkDispatcher;
|
|
@@ -51,103 +52,101 @@ function createTelegramBot(opts) {
|
|
|
51
52
|
let startWorkCommandDispatcher;
|
|
52
53
|
let helpDispatcher;
|
|
53
54
|
let managerObj;
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
`\u2705 Chat connected!
|
|
55
|
+
bot.use(async (ctx, next) => {
|
|
56
|
+
const userId = ctx.from?.id;
|
|
57
|
+
if (!userId || !config.allowedUserIds.includes(userId)) {
|
|
58
|
+
logger.warn("unauthorized access attempt", { userId });
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
if (ctx.chat?.type !== "private") return;
|
|
62
|
+
if (ctx.chat?.id) {
|
|
63
|
+
const newChatId = ctx.chat.id;
|
|
64
|
+
if (activeChatId !== newChatId) {
|
|
65
|
+
activeChatId = newChatId;
|
|
66
|
+
await stateStore.write({ chatId: newChatId, discoveredBy: process.pid });
|
|
67
|
+
logger.info("chat_id discovered", { chatId: newChatId });
|
|
68
|
+
await ctx.reply(
|
|
69
|
+
`\u2705 Chat connected!
|
|
70
70
|
|
|
71
71
|
Your chat_id: ${newChatId}
|
|
72
72
|
|
|
73
73
|
This chat is now active for OpenCode notifications.`
|
|
74
|
-
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
await next();
|
|
78
|
-
});
|
|
79
|
-
bot.catch((err) => {
|
|
80
|
-
const e = err.error;
|
|
81
|
-
if (e instanceof GrammyError && e.error_code === 409) {
|
|
82
|
-
logger.info("polling conflict (409) - another process took over", {
|
|
83
|
-
description: e.description
|
|
84
|
-
});
|
|
85
|
-
} else {
|
|
86
|
-
logger.error("bot error", { error: String(e) });
|
|
74
|
+
);
|
|
87
75
|
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
76
|
+
}
|
|
77
|
+
await next();
|
|
78
|
+
});
|
|
79
|
+
bot.catch((err) => {
|
|
80
|
+
const e = err.error;
|
|
81
|
+
if (e instanceof GrammyError && e.error_code === 409) {
|
|
82
|
+
logger.info("polling conflict (409) - another process took over", {
|
|
83
|
+
description: e.description
|
|
84
|
+
});
|
|
85
|
+
} else {
|
|
86
|
+
logger.error("bot error", { error: String(e) });
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
bot.callbackQuery(/^q:([^:]+):(\d+):(\d+|c|d)$/, async (ctx) => {
|
|
90
|
+
await ctx.answerCallbackQuery();
|
|
91
|
+
const data = ctx.callbackQuery.data;
|
|
92
|
+
const messageId = ctx.callbackQuery.message?.message_id;
|
|
93
|
+
const chatId = ctx.chat?.id;
|
|
94
|
+
const userId = ctx.from?.id;
|
|
95
|
+
if (!questionDispatcher || messageId === void 0 || chatId === void 0 || userId === void 0)
|
|
96
|
+
return;
|
|
97
|
+
await questionDispatcher.handleCallbackQuery(data, messageId, chatId, userId);
|
|
98
|
+
});
|
|
99
|
+
bot.callbackQuery(/^p:([^:]+):(o|a|r)$/, async (ctx) => {
|
|
100
|
+
await ctx.answerCallbackQuery();
|
|
101
|
+
const data = ctx.callbackQuery.data;
|
|
102
|
+
const messageId = ctx.callbackQuery.message?.message_id;
|
|
103
|
+
if (!permissionDispatcher || messageId === void 0) return;
|
|
104
|
+
await permissionDispatcher.handleCallbackQuery(data, messageId);
|
|
105
|
+
});
|
|
106
|
+
bot.callbackQuery(/^sw:([^:]+)$/, async (ctx) => {
|
|
107
|
+
await ctx.answerCallbackQuery();
|
|
108
|
+
const data = ctx.callbackQuery.data;
|
|
109
|
+
const messageId = ctx.callbackQuery.message?.message_id;
|
|
110
|
+
if (!startWorkDispatcher || messageId === void 0) return;
|
|
111
|
+
await startWorkDispatcher.handleCallbackQuery(data, messageId);
|
|
112
|
+
});
|
|
113
|
+
bot.command("sessions", async (ctx) => {
|
|
114
|
+
if (!sessionsDispatcher) return;
|
|
115
|
+
const chatId = ctx.chat?.id;
|
|
116
|
+
const userId = ctx.from?.id;
|
|
117
|
+
if (chatId === void 0 || userId === void 0) return;
|
|
118
|
+
await sessionsDispatcher({ chatId, userId, bot: managerObj });
|
|
119
|
+
});
|
|
120
|
+
bot.command("status", async (ctx) => {
|
|
121
|
+
if (!statusDispatcher) return;
|
|
122
|
+
const chatId = ctx.chat?.id;
|
|
123
|
+
const userId = ctx.from?.id;
|
|
124
|
+
if (chatId === void 0 || userId === void 0) return;
|
|
125
|
+
const args = ctx.match.trim().split(/\s+/).filter(Boolean);
|
|
126
|
+
await statusDispatcher({ chatId, userId, bot: managerObj, args });
|
|
127
|
+
});
|
|
128
|
+
bot.command("start_work", async (ctx) => {
|
|
129
|
+
if (!startWorkCommandDispatcher) return;
|
|
130
|
+
const chatId = ctx.chat?.id;
|
|
131
|
+
const userId = ctx.from?.id;
|
|
132
|
+
if (chatId === void 0 || userId === void 0) return;
|
|
133
|
+
const args = ctx.match.trim().split(/\s+/).filter(Boolean);
|
|
134
|
+
await startWorkCommandDispatcher({ chatId, userId, bot: managerObj, args });
|
|
135
|
+
});
|
|
136
|
+
bot.command("help", async (ctx) => {
|
|
137
|
+
if (!helpDispatcher) return;
|
|
138
|
+
const chatId = ctx.chat?.id;
|
|
139
|
+
const userId = ctx.from?.id;
|
|
140
|
+
if (chatId === void 0 || userId === void 0) return;
|
|
141
|
+
await helpDispatcher({ chatId, userId, bot: managerObj });
|
|
142
|
+
});
|
|
143
|
+
bot.on("message:text", async (ctx) => {
|
|
144
|
+
const replyToMessageId = ctx.message.reply_to_message?.message_id;
|
|
145
|
+
const chatId = ctx.chat.id;
|
|
146
|
+
const userId = ctx.from?.id;
|
|
147
|
+
if (!questionDispatcher || replyToMessageId === void 0 || userId === void 0) return;
|
|
148
|
+
await questionDispatcher.handleTextReply(ctx.message.text, chatId, userId, replyToMessageId);
|
|
149
|
+
});
|
|
151
150
|
const requireChatId = async (action) => {
|
|
152
151
|
if (activeChatId) return activeChatId;
|
|
153
152
|
const state = await stateStore.read();
|
|
@@ -159,10 +158,8 @@ This chat is now active for OpenCode notifications.`
|
|
|
159
158
|
};
|
|
160
159
|
managerObj = {
|
|
161
160
|
async start() {
|
|
162
|
-
if (
|
|
163
|
-
|
|
164
|
-
return;
|
|
165
|
-
}
|
|
161
|
+
if (pollingActive) return;
|
|
162
|
+
pollingActive = true;
|
|
166
163
|
try {
|
|
167
164
|
await bot.api.setMyCommands([
|
|
168
165
|
{ command: "sessions", description: "\uD65C\uC131 \uC138\uC158 \uBAA9\uB85D (top 20)" },
|
|
@@ -173,22 +170,30 @@ This chat is now active for OpenCode notifications.`
|
|
|
173
170
|
} catch (err) {
|
|
174
171
|
logger.warn("setMyCommands failed", { error: String(err) });
|
|
175
172
|
}
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
173
|
+
try {
|
|
174
|
+
await bot.start({
|
|
175
|
+
drop_pending_updates: true,
|
|
176
|
+
onStart: () => {
|
|
177
|
+
logger.info("polling started");
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
} catch (err) {
|
|
181
|
+
pollingActive = false;
|
|
182
|
+
throw err;
|
|
183
|
+
}
|
|
182
184
|
},
|
|
183
185
|
async stop() {
|
|
184
|
-
if (
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
}
|
|
186
|
+
if (!pollingActive) return;
|
|
187
|
+
pollingActive = false;
|
|
188
|
+
try {
|
|
189
|
+
await bot.stop();
|
|
190
|
+
} catch (err) {
|
|
191
|
+
logger.warn("bot.stop() error", { error: String(err) });
|
|
190
192
|
}
|
|
191
193
|
},
|
|
194
|
+
isPolling() {
|
|
195
|
+
return pollingActive;
|
|
196
|
+
},
|
|
192
197
|
async sendMessage(text, options) {
|
|
193
198
|
const chatId = await requireChatId("sendMessage");
|
|
194
199
|
const result = await bot.api.sendMessage(chatId, text, options);
|
|
@@ -364,7 +369,7 @@ async function claimOnce(opts) {
|
|
|
364
369
|
|
|
365
370
|
// src/lib/pending-permissions.ts
|
|
366
371
|
import { createHash as createHash2 } from "crypto";
|
|
367
|
-
import { mkdir as mkdir2,
|
|
372
|
+
import { mkdir as mkdir2, readdir as readdir2, readFile, rename, unlink as unlink2, writeFile } from "fs/promises";
|
|
368
373
|
import { tmpdir } from "os";
|
|
369
374
|
import { dirname, join as join2 } from "path";
|
|
370
375
|
function hasCode2(err, code) {
|
|
@@ -375,13 +380,17 @@ function pendingFilePath(dir, shortHash) {
|
|
|
375
380
|
}
|
|
376
381
|
function parsePending(text) {
|
|
377
382
|
const parsed = JSON.parse(text);
|
|
378
|
-
if (typeof parsed.requestID !== "string")
|
|
379
|
-
|
|
383
|
+
if (typeof parsed.requestID !== "string")
|
|
384
|
+
throw new Error("Invalid pending permission: requestID");
|
|
385
|
+
if (typeof parsed.sessionID !== "string")
|
|
386
|
+
throw new Error("Invalid pending permission: sessionID");
|
|
380
387
|
if (typeof parsed.title !== "string") throw new Error("Invalid pending permission: title");
|
|
381
|
-
if (typeof parsed.permission !== "string")
|
|
388
|
+
if (typeof parsed.permission !== "string")
|
|
389
|
+
throw new Error("Invalid pending permission: permission");
|
|
382
390
|
if (!Array.isArray(parsed.patterns)) throw new Error("Invalid pending permission: patterns");
|
|
383
391
|
if (!Array.isArray(parsed.always)) throw new Error("Invalid pending permission: always");
|
|
384
|
-
if (parsed.endpoint !== "request" && parsed.endpoint !== "session")
|
|
392
|
+
if (parsed.endpoint !== "request" && parsed.endpoint !== "session")
|
|
393
|
+
throw new Error("Invalid pending permission: endpoint");
|
|
385
394
|
return parsed;
|
|
386
395
|
}
|
|
387
396
|
async function listPendingFiles(dir) {
|
|
@@ -422,11 +431,13 @@ function createPendingPermissionStore(opts) {
|
|
|
422
431
|
if (!(err instanceof Error) || !hasCode2(err, "ENOENT")) throw err;
|
|
423
432
|
}
|
|
424
433
|
},
|
|
425
|
-
async findByRequestID(requestID) {
|
|
434
|
+
async findByRequestID(requestID, sessionID, serverUrl) {
|
|
426
435
|
for (const fileName of await listPendingFiles(dir)) {
|
|
427
436
|
const shortHash = shortHashFromFileName(fileName);
|
|
428
437
|
const data = await this.loadPending(shortHash);
|
|
429
|
-
if (data?.requestID === requestID)
|
|
438
|
+
if (data?.requestID === requestID && (sessionID === void 0 || data.sessionID === sessionID) && (serverUrl === void 0 || data.serverUrl === serverUrl)) {
|
|
439
|
+
return { shortHash, data };
|
|
440
|
+
}
|
|
430
441
|
}
|
|
431
442
|
return void 0;
|
|
432
443
|
},
|
|
@@ -444,8 +455,9 @@ function createPendingPermissionStore(opts) {
|
|
|
444
455
|
}
|
|
445
456
|
};
|
|
446
457
|
}
|
|
447
|
-
function createPermissionShortHash(requestID) {
|
|
448
|
-
|
|
458
|
+
function createPermissionShortHash(requestID, sessionID, endpoint2, serverUrl) {
|
|
459
|
+
const source = sessionID === void 0 ? requestID : `${serverUrl ?? ""}:${endpoint2 ?? ""}:${sessionID}:${requestID}`;
|
|
460
|
+
return createHash2("sha256").update(source).digest("base64url").slice(0, 10);
|
|
449
461
|
}
|
|
450
462
|
|
|
451
463
|
// src/events/permission-updated.ts
|
|
@@ -467,7 +479,8 @@ function isEventPermissionAsked(event) {
|
|
|
467
479
|
}
|
|
468
480
|
function buildCallbackData(shortHash, reply) {
|
|
469
481
|
const data = `p:${shortHash}:${reply}`;
|
|
470
|
-
if (Buffer.byteLength(data, "utf8") > 64)
|
|
482
|
+
if (Buffer.byteLength(data, "utf8") > 64)
|
|
483
|
+
throw new Error("Telegram callback_data exceeds 64 bytes");
|
|
471
484
|
return data;
|
|
472
485
|
}
|
|
473
486
|
function normalizeUpdated(permission) {
|
|
@@ -479,8 +492,7 @@ function normalizeUpdated(permission) {
|
|
|
479
492
|
permission: permission.type,
|
|
480
493
|
patterns: pattern,
|
|
481
494
|
always: [],
|
|
482
|
-
endpoint: "session"
|
|
483
|
-
claimKey: `permission.updated:${permission.id}`
|
|
495
|
+
endpoint: "session"
|
|
484
496
|
};
|
|
485
497
|
}
|
|
486
498
|
function normalizeAsked(permission) {
|
|
@@ -491,8 +503,7 @@ function normalizeAsked(permission) {
|
|
|
491
503
|
permission: permission.permission,
|
|
492
504
|
patterns: permission.patterns,
|
|
493
505
|
always: permission.always,
|
|
494
|
-
endpoint: "request"
|
|
495
|
-
claimKey: `permission.asked:${permission.id}`
|
|
506
|
+
endpoint: "request"
|
|
496
507
|
};
|
|
497
508
|
}
|
|
498
509
|
function permissionMessage(permission, sessionTitle) {
|
|
@@ -527,9 +538,15 @@ function replyLabel(reply) {
|
|
|
527
538
|
return "Rejected";
|
|
528
539
|
}
|
|
529
540
|
async function handleNormalizedPermission(permission, ctx) {
|
|
530
|
-
const
|
|
541
|
+
const permissionKey = `${ctx.serverUrl.href}:${permission.sessionID}:${permission.requestID}`;
|
|
542
|
+
const claimed = await claimOnce({ claimsDir: ctx.claimsDir, key: `permission:${permissionKey}` });
|
|
531
543
|
if (!claimed) return;
|
|
532
|
-
const shortHash = createPermissionShortHash(
|
|
544
|
+
const shortHash = createPermissionShortHash(
|
|
545
|
+
permission.requestID,
|
|
546
|
+
permission.sessionID,
|
|
547
|
+
permission.endpoint,
|
|
548
|
+
ctx.serverUrl.href
|
|
549
|
+
);
|
|
533
550
|
const sentAt = Date.now();
|
|
534
551
|
const rawSessionTitle = ctx.sessionTitleService.getSessionTitle(permission.sessionID);
|
|
535
552
|
const sessionTitle = rawSessionTitle === null ? void 0 : rawSessionTitle;
|
|
@@ -555,11 +572,6 @@ async function handleNormalizedPermission(permission, ctx) {
|
|
|
555
572
|
ctx.logger.error("failed to send permission notification", { error: String(err) });
|
|
556
573
|
}
|
|
557
574
|
}
|
|
558
|
-
async function expirePending(ctx, shortHash, pending, messageId) {
|
|
559
|
-
await ctx.bot.editMessageRemoveKeyboard(messageId, "\u23F1 Permission request expired");
|
|
560
|
-
await ctx.pendingPermissions.deletePending(shortHash);
|
|
561
|
-
ctx.logger.info("pending permission expired", { requestID: pending.requestID });
|
|
562
|
-
}
|
|
563
575
|
async function handlePermissionUpdated(event, ctx) {
|
|
564
576
|
await handleNormalizedPermission(normalizeUpdated(event.properties), ctx);
|
|
565
577
|
}
|
|
@@ -583,7 +595,11 @@ function externalReplyLabel(value) {
|
|
|
583
595
|
async function handlePermissionReplied(event, ctx) {
|
|
584
596
|
const requestID = event.properties.requestID ?? event.properties.permissionID;
|
|
585
597
|
if (!requestID) return;
|
|
586
|
-
const found = await ctx.pendingPermissions.findByRequestID(
|
|
598
|
+
const found = await ctx.pendingPermissions.findByRequestID(
|
|
599
|
+
requestID,
|
|
600
|
+
event.properties.sessionID,
|
|
601
|
+
ctx.serverUrl.href
|
|
602
|
+
);
|
|
587
603
|
if (!found) return;
|
|
588
604
|
const label = externalReplyLabel(event.properties.reply ?? event.properties.response);
|
|
589
605
|
try {
|
|
@@ -619,10 +635,6 @@ function createPermissionDispatcher(ctx) {
|
|
|
619
635
|
await ctx.bot.editMessageRemoveKeyboard(messageId, "This permission request has expired.");
|
|
620
636
|
return;
|
|
621
637
|
}
|
|
622
|
-
if (pending.expiresAt < Date.now()) {
|
|
623
|
-
await expirePending(ctx, shortHash, pending, messageId);
|
|
624
|
-
return;
|
|
625
|
-
}
|
|
626
638
|
try {
|
|
627
639
|
await ctx.replyToPermission(
|
|
628
640
|
pending.requestID,
|
|
@@ -631,13 +643,26 @@ function createPermissionDispatcher(ctx) {
|
|
|
631
643
|
pending.endpoint,
|
|
632
644
|
pending.serverUrl
|
|
633
645
|
);
|
|
634
|
-
await ctx.bot.editMessageRemoveKeyboard(
|
|
646
|
+
await ctx.bot.editMessageRemoveKeyboard(
|
|
647
|
+
messageId,
|
|
648
|
+
`\u2705 Permission ${replyLabel(reply)}
|
|
635
649
|
|
|
636
|
-
${pending.permission}: ${pending.title}`
|
|
637
|
-
|
|
650
|
+
${pending.permission}: ${pending.title}`
|
|
651
|
+
);
|
|
652
|
+
ctx.logger.info("permission reply sent", {
|
|
653
|
+
requestID: pending.requestID,
|
|
654
|
+
sessionID: pending.sessionID,
|
|
655
|
+
reply
|
|
656
|
+
});
|
|
638
657
|
} catch (err) {
|
|
639
|
-
await ctx.bot.editMessageRemoveKeyboard(
|
|
640
|
-
|
|
658
|
+
await ctx.bot.editMessageRemoveKeyboard(
|
|
659
|
+
messageId,
|
|
660
|
+
"\u26A0\uFE0F Failed to send permission reply to opencode"
|
|
661
|
+
);
|
|
662
|
+
ctx.logger.error("failed to send permission reply", {
|
|
663
|
+
error: String(err),
|
|
664
|
+
requestID: pending.requestID
|
|
665
|
+
});
|
|
641
666
|
} finally {
|
|
642
667
|
await ctx.pendingPermissions.deletePending(shortHash);
|
|
643
668
|
}
|
|
@@ -647,7 +672,7 @@ ${pending.permission}: ${pending.title}`);
|
|
|
647
672
|
|
|
648
673
|
// src/lib/pending-questions.ts
|
|
649
674
|
import { createHash as createHash3 } from "crypto";
|
|
650
|
-
import { mkdir as mkdir3,
|
|
675
|
+
import { mkdir as mkdir3, readdir as readdir3, readFile as readFile2, rename as rename2, unlink as unlink3, writeFile as writeFile2 } from "fs/promises";
|
|
651
676
|
import { tmpdir as tmpdir2 } from "os";
|
|
652
677
|
import { dirname as dirname2, join as join3 } from "path";
|
|
653
678
|
function hasCode3(err, code) {
|
|
@@ -661,8 +686,10 @@ function parsePending2(text) {
|
|
|
661
686
|
if (typeof parsed.requestID !== "string") throw new Error("Invalid pending question: requestID");
|
|
662
687
|
if (typeof parsed.sessionID !== "string") throw new Error("Invalid pending question: sessionID");
|
|
663
688
|
if (!Array.isArray(parsed.questions)) throw new Error("Invalid pending question: questions");
|
|
664
|
-
if (!Array.isArray(parsed.telegramMessageIds))
|
|
665
|
-
|
|
689
|
+
if (!Array.isArray(parsed.telegramMessageIds))
|
|
690
|
+
throw new Error("Invalid pending question: telegramMessageIds");
|
|
691
|
+
if (!Array.isArray(parsed.answersInProgress))
|
|
692
|
+
throw new Error("Invalid pending question: answersInProgress");
|
|
666
693
|
parsed.answersInProgress = parsed.answersInProgress.map((answer) => answer ?? null);
|
|
667
694
|
return parsed;
|
|
668
695
|
}
|
|
@@ -716,11 +743,13 @@ function createPendingQuestionStore(opts) {
|
|
|
716
743
|
}
|
|
717
744
|
return expired;
|
|
718
745
|
},
|
|
719
|
-
async findByRequestID(requestID) {
|
|
746
|
+
async findByRequestID(requestID, sessionID, serverUrl) {
|
|
720
747
|
for (const fileName of await listPendingFiles2(dir)) {
|
|
721
748
|
const shortHash = shortHashFromFileName2(fileName);
|
|
722
749
|
const data = await this.loadPending(shortHash);
|
|
723
|
-
if (data?.requestID === requestID)
|
|
750
|
+
if (data?.requestID === requestID && (sessionID === void 0 || data.sessionID === sessionID) && (serverUrl === void 0 || data.serverUrl === serverUrl)) {
|
|
751
|
+
return { shortHash, data };
|
|
752
|
+
}
|
|
724
753
|
}
|
|
725
754
|
return void 0;
|
|
726
755
|
},
|
|
@@ -729,14 +758,16 @@ function createPendingQuestionStore(opts) {
|
|
|
729
758
|
const shortHash = shortHashFromFileName2(fileName);
|
|
730
759
|
const data = await this.loadPending(shortHash);
|
|
731
760
|
const awaiting = data?.awaitingCustomFor;
|
|
732
|
-
if (awaiting && awaiting.chatId === chatId && awaiting.userId === userId)
|
|
761
|
+
if (awaiting && awaiting.chatId === chatId && awaiting.userId === userId)
|
|
762
|
+
return { shortHash, data };
|
|
733
763
|
}
|
|
734
764
|
return void 0;
|
|
735
765
|
}
|
|
736
766
|
};
|
|
737
767
|
}
|
|
738
|
-
function createQuestionShortHash(requestID) {
|
|
739
|
-
|
|
768
|
+
function createQuestionShortHash(requestID, sessionID, serverUrl) {
|
|
769
|
+
const source = sessionID === void 0 ? requestID : `${serverUrl ?? ""}:${sessionID}:${requestID}`;
|
|
770
|
+
return createHash3("sha256").update(source).digest("base64url").slice(0, 10);
|
|
740
771
|
}
|
|
741
772
|
|
|
742
773
|
// src/events/question-asked.ts
|
|
@@ -855,21 +886,16 @@ ${answerSummary(pending.questions, answers)}`
|
|
|
855
886
|
await ctx.pendingQuestions.deletePending(shortHash);
|
|
856
887
|
}
|
|
857
888
|
}
|
|
858
|
-
async function expirePending2(ctx, shortHash, pending, messageId) {
|
|
859
|
-
await ctx.bot.editMessageRemoveKeyboard(messageId, "\u23F1 Question expired");
|
|
860
|
-
await ctx.pendingQuestions.deletePending(shortHash);
|
|
861
|
-
ctx.logger.info("pending question expired", { requestID: pending.requestID });
|
|
862
|
-
}
|
|
863
889
|
async function handleQuestionAsked(event, ctx) {
|
|
864
890
|
const request = event.properties;
|
|
865
891
|
if (request.questions.length === 0) return;
|
|
866
892
|
const claimed = await claimOnce({
|
|
867
893
|
claimsDir: ctx.claimsDir,
|
|
868
|
-
key: `question.
|
|
894
|
+
key: `question:${ctx.serverUrl.href}:${request.sessionID}:${request.id}`,
|
|
869
895
|
ttlMs: 5e3
|
|
870
896
|
});
|
|
871
897
|
if (!claimed) return;
|
|
872
|
-
const shortHash = createQuestionShortHash(request.id);
|
|
898
|
+
const shortHash = createQuestionShortHash(request.id, request.sessionID, ctx.serverUrl.href);
|
|
873
899
|
const firstQuestion = request.questions[0];
|
|
874
900
|
const sentAt = Date.now();
|
|
875
901
|
const pending = {
|
|
@@ -919,10 +945,7 @@ function createQuestionDispatcher(ctx) {
|
|
|
919
945
|
await ctx.bot.editMessageRemoveKeyboard(messageId, "This question has expired.");
|
|
920
946
|
return;
|
|
921
947
|
}
|
|
922
|
-
|
|
923
|
-
await expirePending2(ctx, shortHash, pending, messageId);
|
|
924
|
-
return;
|
|
925
|
-
}
|
|
948
|
+
pending.expiresAt = Date.now() + QUESTION_EXPIRY_MS;
|
|
926
949
|
const question = pending.questions[questionIndex];
|
|
927
950
|
if (!question) return;
|
|
928
951
|
if (selection === "c") {
|
|
@@ -976,10 +999,7 @@ function createQuestionDispatcher(ctx) {
|
|
|
976
999
|
if (!match) return;
|
|
977
1000
|
const awaiting = match.data.awaitingCustomFor;
|
|
978
1001
|
if (!awaiting || awaiting.promptMessageId !== replyToMessageId) return;
|
|
979
|
-
|
|
980
|
-
await expirePending2(ctx, match.shortHash, match.data, match.data.telegramMessageIds[0]);
|
|
981
|
-
return;
|
|
982
|
-
}
|
|
1002
|
+
match.data.expiresAt = Date.now() + QUESTION_EXPIRY_MS;
|
|
983
1003
|
const question = match.data.questions[awaiting.questionIndex];
|
|
984
1004
|
if (question?.multiple === true) {
|
|
985
1005
|
const current = selectedAnswers(match.data, awaiting.questionIndex);
|
|
@@ -1002,16 +1022,31 @@ function createQuestionDispatcher(ctx) {
|
|
|
1002
1022
|
function isEventQuestionReplied(event) {
|
|
1003
1023
|
if (event.type !== "question.replied") return false;
|
|
1004
1024
|
const props = event.properties;
|
|
1005
|
-
return Boolean(
|
|
1025
|
+
return Boolean(
|
|
1026
|
+
props && typeof props.requestID === "string" && typeof props.sessionID === "string"
|
|
1027
|
+
);
|
|
1006
1028
|
}
|
|
1007
1029
|
async function handleQuestionReplied(event, ctx) {
|
|
1008
|
-
const found = await ctx.pendingQuestions.findByRequestID(
|
|
1009
|
-
|
|
1030
|
+
const found = await ctx.pendingQuestions.findByRequestID(
|
|
1031
|
+
event.properties.requestID,
|
|
1032
|
+
event.properties.sessionID,
|
|
1033
|
+
ctx.serverUrl.href
|
|
1034
|
+
);
|
|
1035
|
+
if (!found) {
|
|
1036
|
+
ctx.logger.info("question.replied no pending match", {
|
|
1037
|
+
requestID: event.properties.requestID,
|
|
1038
|
+
sessionID: event.properties.sessionID
|
|
1039
|
+
});
|
|
1040
|
+
return;
|
|
1041
|
+
}
|
|
1010
1042
|
const messageId = found.data.telegramMessageIds[0];
|
|
1011
1043
|
try {
|
|
1012
1044
|
await ctx.bot.editMessageRemoveKeyboard(messageId, "\u2705 Already answered in opencode.");
|
|
1013
1045
|
} catch (err) {
|
|
1014
|
-
ctx.logger.error("failed to edit externally answered question", {
|
|
1046
|
+
ctx.logger.error("failed to edit externally answered question", {
|
|
1047
|
+
error: String(err),
|
|
1048
|
+
requestID: event.properties.requestID
|
|
1049
|
+
});
|
|
1015
1050
|
} finally {
|
|
1016
1051
|
await ctx.pendingQuestions.deletePending(found.shortHash);
|
|
1017
1052
|
}
|
|
@@ -1474,7 +1509,7 @@ function createPendingStartWork(sessionID, title, serverUrl, telegramMessageId)
|
|
|
1474
1509
|
function startWorkShortHash(sessionID) {
|
|
1475
1510
|
return createStartWorkShortHash(sessionID);
|
|
1476
1511
|
}
|
|
1477
|
-
async function
|
|
1512
|
+
async function expirePending(ctx, shortHash, pending, messageId) {
|
|
1478
1513
|
await ctx.bot.editMessageRemoveKeyboard(messageId, "\u23F1 /start-work request expired");
|
|
1479
1514
|
await ctx.pendingStartWorks.deletePending(shortHash);
|
|
1480
1515
|
ctx.logger.info("pending start-work expired", { sessionID: pending.sessionID });
|
|
@@ -1491,7 +1526,7 @@ function createStartWorkDispatcher(ctx) {
|
|
|
1491
1526
|
return;
|
|
1492
1527
|
}
|
|
1493
1528
|
if (pending.expiresAt < Date.now()) {
|
|
1494
|
-
await
|
|
1529
|
+
await expirePending(ctx, shortHash, pending, messageId);
|
|
1495
1530
|
return;
|
|
1496
1531
|
}
|
|
1497
1532
|
try {
|
|
@@ -1522,9 +1557,21 @@ Session: ${label}`
|
|
|
1522
1557
|
|
|
1523
1558
|
// src/events/session-idle.ts
|
|
1524
1559
|
var ROOT_IDLE_RECHECK_DELAY_MS = 2500;
|
|
1560
|
+
var DEFERRED_PARENT_CONFIRM_DELAY_MS = 2500;
|
|
1561
|
+
var deferredConfirmTimers = /* @__PURE__ */ new Map();
|
|
1525
1562
|
function sleep(ms) {
|
|
1526
1563
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
1527
1564
|
}
|
|
1565
|
+
function agentFinishedMessage(title, agent) {
|
|
1566
|
+
const base = title ? `Agent has finished: ${title}` : "Agent has finished.";
|
|
1567
|
+
return agent ? `${base} (${agent})` : base;
|
|
1568
|
+
}
|
|
1569
|
+
function cancelDeferredParentConfirm(sessionId) {
|
|
1570
|
+
const timer = deferredConfirmTimers.get(sessionId);
|
|
1571
|
+
if (timer === void 0) return;
|
|
1572
|
+
clearTimeout(timer);
|
|
1573
|
+
deferredConfirmTimers.delete(sessionId);
|
|
1574
|
+
}
|
|
1528
1575
|
async function resolveParentID(sessionId, ctx) {
|
|
1529
1576
|
const cachedParentID = ctx.sessionTitleService.getParentID(sessionId);
|
|
1530
1577
|
if (cachedParentID !== void 0) return cachedParentID;
|
|
@@ -1576,8 +1623,9 @@ async function sendIdleNotification(sessionId, ctx) {
|
|
|
1576
1623
|
});
|
|
1577
1624
|
if (!claimed) return;
|
|
1578
1625
|
const title = ctx.sessionTitleService.getSessionTitle(sessionId);
|
|
1579
|
-
const
|
|
1580
|
-
const
|
|
1626
|
+
const agent = ctx.sessionTitleService.getSessionAgent(sessionId);
|
|
1627
|
+
const isPlanSession = agent === "plan";
|
|
1628
|
+
const text = isPlanSession ? planCompleteMessage(title) : agentFinishedMessage(title, agent);
|
|
1581
1629
|
try {
|
|
1582
1630
|
if (isPlanSession) {
|
|
1583
1631
|
const shortHash = startWorkShortHash(sessionId);
|
|
@@ -1601,18 +1649,49 @@ async function flushDeferredParentIfReady(parentID, ctx) {
|
|
|
1601
1649
|
if (!ctx.sessionTitleService.hasDeferredIdleNotification(parentID)) return;
|
|
1602
1650
|
if (ctx.sessionTitleService.hasUnfinishedDescendants(parentID)) return;
|
|
1603
1651
|
const parentStatus = ctx.sessionTitleService.getSessionStatus(parentID);
|
|
1604
|
-
if (parentStatus
|
|
1605
|
-
|
|
1652
|
+
if (parentStatus !== "idle") {
|
|
1653
|
+
if (parentStatus !== void 0) {
|
|
1654
|
+
cancelDeferredParentConfirm(parentID);
|
|
1655
|
+
ctx.sessionTitleService.clearDeferredIdleNotification(parentID);
|
|
1656
|
+
ctx.logger.info("clearing deferred parent idle notification - parent resumed", {
|
|
1657
|
+
sessionId: parentID
|
|
1658
|
+
});
|
|
1659
|
+
}
|
|
1660
|
+
return;
|
|
1661
|
+
}
|
|
1662
|
+
scheduleDeferredParentConfirm(parentID, ctx);
|
|
1663
|
+
}
|
|
1664
|
+
function scheduleDeferredParentConfirm(parentID, ctx) {
|
|
1665
|
+
if (deferredConfirmTimers.has(parentID)) return;
|
|
1666
|
+
const delay = ctx.deferredConfirmDelayMs ?? DEFERRED_PARENT_CONFIRM_DELAY_MS;
|
|
1667
|
+
const timer = setTimeout(() => {
|
|
1668
|
+
deferredConfirmTimers.delete(parentID);
|
|
1669
|
+
void confirmDeferredParentIdle(parentID, ctx);
|
|
1670
|
+
}, delay);
|
|
1671
|
+
timer.unref?.();
|
|
1672
|
+
deferredConfirmTimers.set(parentID, timer);
|
|
1673
|
+
ctx.logger.info("parent idle and descendants finished - confirming deferred notification", {
|
|
1674
|
+
sessionId: parentID
|
|
1675
|
+
});
|
|
1676
|
+
}
|
|
1677
|
+
async function confirmDeferredParentIdle(parentID, ctx) {
|
|
1678
|
+
if (!ctx.sessionTitleService.hasDeferredIdleNotification(parentID)) return;
|
|
1679
|
+
if (ctx.sessionTitleService.getSessionStatus(parentID) !== "idle") {
|
|
1680
|
+
ctx.sessionTitleService.clearDeferredIdleNotification(parentID);
|
|
1681
|
+
ctx.logger.info("clearing deferred parent idle notification - parent resumed during confirm", {
|
|
1606
1682
|
sessionId: parentID
|
|
1607
1683
|
});
|
|
1608
1684
|
return;
|
|
1609
1685
|
}
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
ctx.logger.info("
|
|
1686
|
+
await hydrateDescendants(parentID, ctx);
|
|
1687
|
+
if (ctx.sessionTitleService.hasUnfinishedDescendants(parentID)) {
|
|
1688
|
+
ctx.logger.info("keeping deferred parent idle notification - descendants active again", {
|
|
1613
1689
|
sessionId: parentID
|
|
1614
1690
|
});
|
|
1691
|
+
return;
|
|
1615
1692
|
}
|
|
1693
|
+
ctx.logger.info("sending deferred parent idle notification", { sessionId: parentID });
|
|
1694
|
+
await sendIdleNotification(parentID, ctx);
|
|
1616
1695
|
}
|
|
1617
1696
|
async function deferParentIdleIfDescendantsRunning(sessionId, ctx) {
|
|
1618
1697
|
await hydrateDescendants(sessionId, ctx);
|
|
@@ -1989,7 +2068,7 @@ function createStatusDispatcher(deps) {
|
|
|
1989
2068
|
return async ({ chatId, bot, args }) => {
|
|
1990
2069
|
const rawN = args[0];
|
|
1991
2070
|
if (rawN === void 0 || rawN === "") {
|
|
1992
|
-
await bot.sendMessage("\uC0AC\uC6A9\uBC95: /status
|
|
2071
|
+
await bot.sendMessage("\uC0AC\uC6A9\uBC95: /status <\uBC88\uD638>. \uBA3C\uC800 /sessions \uB85C \uBAA9\uB85D \uD655\uC778", {
|
|
1993
2072
|
parse_mode: "HTML"
|
|
1994
2073
|
});
|
|
1995
2074
|
return;
|
|
@@ -2031,7 +2110,11 @@ function createStatusDispatcher(deps) {
|
|
|
2031
2110
|
let messages = [];
|
|
2032
2111
|
if (sourceServerUrl && useRemoteServer) {
|
|
2033
2112
|
try {
|
|
2034
|
-
const getResult = await getRemoteSession(
|
|
2113
|
+
const getResult = await getRemoteSession(
|
|
2114
|
+
sourceServerUrl,
|
|
2115
|
+
entry.sessionId,
|
|
2116
|
+
deps.opencodeFetch
|
|
2117
|
+
);
|
|
2035
2118
|
session = getResult.data;
|
|
2036
2119
|
responseStatus = getResult.response.status;
|
|
2037
2120
|
if (!session || responseStatus === 404) {
|
|
@@ -2050,7 +2133,11 @@ function createStatusDispatcher(deps) {
|
|
|
2050
2133
|
await bot.sendMessage("\uC138\uC158 \uC0C1\uD0DC\uB97C \uBD88\uB7EC\uC624\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4. /sessions \uC7AC\uC2E4\uD589 \uD544\uC694", {
|
|
2051
2134
|
parse_mode: "HTML"
|
|
2052
2135
|
});
|
|
2053
|
-
deps.logger.error("status remote lookup failed", {
|
|
2136
|
+
deps.logger.error("status remote lookup failed", {
|
|
2137
|
+
chatId,
|
|
2138
|
+
sessionId: entry.sessionId,
|
|
2139
|
+
error: String(err)
|
|
2140
|
+
});
|
|
2054
2141
|
return;
|
|
2055
2142
|
}
|
|
2056
2143
|
} else {
|
|
@@ -2285,7 +2372,7 @@ function loadPluginEnv(opts) {
|
|
|
2285
2372
|
}
|
|
2286
2373
|
|
|
2287
2374
|
// src/lib/lock.ts
|
|
2288
|
-
import { open as open2, readFile as readFile6, stat as stat3, unlink as unlink6 } from "fs/promises";
|
|
2375
|
+
import { open as open2, readFile as readFile6, stat as stat3, unlink as unlink6, utimes } from "fs/promises";
|
|
2289
2376
|
import { hostname } from "os";
|
|
2290
2377
|
var DEFAULT_TTL_MS2 = 5 * 60 * 1e3;
|
|
2291
2378
|
function hasCode6(err, code) {
|
|
@@ -2331,6 +2418,18 @@ async function createLock(lockPath, pid) {
|
|
|
2331
2418
|
await unlink6(lockPath);
|
|
2332
2419
|
} catch {
|
|
2333
2420
|
}
|
|
2421
|
+
},
|
|
2422
|
+
async refresh() {
|
|
2423
|
+
if (released) return false;
|
|
2424
|
+
try {
|
|
2425
|
+
const data2 = parseLockData(await readFile6(lockPath, "utf8"));
|
|
2426
|
+
if (!data2 || data2.pid !== pid || data2.hostname !== hostname()) return false;
|
|
2427
|
+
const now = /* @__PURE__ */ new Date();
|
|
2428
|
+
await utimes(lockPath, now, now);
|
|
2429
|
+
return true;
|
|
2430
|
+
} catch {
|
|
2431
|
+
return false;
|
|
2432
|
+
}
|
|
2334
2433
|
}
|
|
2335
2434
|
};
|
|
2336
2435
|
}
|
|
@@ -2374,7 +2473,11 @@ async function acquireLock(opts) {
|
|
|
2374
2473
|
try {
|
|
2375
2474
|
await unlink6(opts.lockPath);
|
|
2376
2475
|
} catch {
|
|
2377
|
-
return {
|
|
2476
|
+
return {
|
|
2477
|
+
acquired: false,
|
|
2478
|
+
reason: "failed to remove stale lock",
|
|
2479
|
+
ownerPid: existing.ownerPid
|
|
2480
|
+
};
|
|
2378
2481
|
}
|
|
2379
2482
|
}
|
|
2380
2483
|
}
|
|
@@ -2819,11 +2922,14 @@ var TelegramRemote = async (input) => {
|
|
|
2819
2922
|
const pendingPermissions = createPendingPermissionStore({ tokenHash });
|
|
2820
2923
|
const pendingStartWorks = createPendingStartWorkStore({ tokenHash });
|
|
2821
2924
|
const lockResult = await acquireLock({ lockPath });
|
|
2822
|
-
const
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
2925
|
+
const leadership = { isLeader: false };
|
|
2926
|
+
if (lockResult.acquired) {
|
|
2927
|
+
leadership.isLeader = true;
|
|
2928
|
+
leadership.handle = lockResult.handle;
|
|
2929
|
+
logger.info("lock acquired - leader mode");
|
|
2930
|
+
} else {
|
|
2931
|
+
logger.info("lock held by other - pass-through mode", { reason: lockResult.reason });
|
|
2932
|
+
}
|
|
2827
2933
|
logger.info("server url", {
|
|
2828
2934
|
url: input.serverUrl.toString(),
|
|
2829
2935
|
href: input.serverUrl.href,
|
|
@@ -2888,21 +2994,57 @@ var TelegramRemote = async (input) => {
|
|
|
2888
2994
|
config,
|
|
2889
2995
|
stateStore,
|
|
2890
2996
|
logger,
|
|
2891
|
-
initialChatId: initialState.chatId ?? config.chatId
|
|
2892
|
-
polling: isLeader
|
|
2997
|
+
initialChatId: initialState.chatId ?? config.chatId
|
|
2893
2998
|
});
|
|
2894
|
-
|
|
2895
|
-
bot.start().catch((err) => {
|
|
2999
|
+
const startLeaderPolling = () => {
|
|
3000
|
+
bot.start().catch(async (err) => {
|
|
2896
3001
|
logger.error("bot polling stopped", { error: String(err) });
|
|
3002
|
+
leadership.isLeader = false;
|
|
3003
|
+
if (leadership.handle) {
|
|
3004
|
+
await leadership.handle.release();
|
|
3005
|
+
leadership.handle = void 0;
|
|
3006
|
+
}
|
|
2897
3007
|
});
|
|
2898
|
-
}
|
|
3008
|
+
};
|
|
3009
|
+
let electionRunning = false;
|
|
3010
|
+
const runElection = async () => {
|
|
3011
|
+
if (electionRunning) return;
|
|
3012
|
+
electionRunning = true;
|
|
3013
|
+
try {
|
|
3014
|
+
if (leadership.isLeader && leadership.handle) {
|
|
3015
|
+
if (await leadership.handle.refresh()) return;
|
|
3016
|
+
leadership.isLeader = false;
|
|
3017
|
+
leadership.handle = void 0;
|
|
3018
|
+
await bot.stop();
|
|
3019
|
+
logger.info("leadership lost - demoted to pass-through");
|
|
3020
|
+
return;
|
|
3021
|
+
}
|
|
3022
|
+
if (bot.isPolling()) return;
|
|
3023
|
+
const result = await acquireLock({ lockPath });
|
|
3024
|
+
if (result.acquired) {
|
|
3025
|
+
leadership.isLeader = true;
|
|
3026
|
+
leadership.handle = result.handle;
|
|
3027
|
+
logger.info("leadership acquired - promoting to leader");
|
|
3028
|
+
startLeaderPolling();
|
|
3029
|
+
}
|
|
3030
|
+
} catch (err) {
|
|
3031
|
+
logger.warn("election cycle failed", { error: String(err) });
|
|
3032
|
+
} finally {
|
|
3033
|
+
electionRunning = false;
|
|
3034
|
+
}
|
|
3035
|
+
};
|
|
3036
|
+
const electionTimer = setInterval(() => {
|
|
3037
|
+
void runElection();
|
|
3038
|
+
}, 3e4);
|
|
3039
|
+
if (typeof electionTimer.unref === "function") electionTimer.unref();
|
|
2899
3040
|
const cleanup = async () => {
|
|
3041
|
+
clearInterval(electionTimer);
|
|
2900
3042
|
try {
|
|
2901
3043
|
await bot.stop();
|
|
2902
3044
|
} catch {
|
|
2903
3045
|
}
|
|
2904
|
-
if (
|
|
2905
|
-
await
|
|
3046
|
+
if (leadership.handle) {
|
|
3047
|
+
await leadership.handle.release();
|
|
2906
3048
|
}
|
|
2907
3049
|
await logger.close();
|
|
2908
3050
|
};
|
|
@@ -2934,34 +3076,41 @@ var TelegramRemote = async (input) => {
|
|
|
2934
3076
|
replyToPermission,
|
|
2935
3077
|
runSessionCommand
|
|
2936
3078
|
};
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
|
|
2940
|
-
|
|
2941
|
-
|
|
3079
|
+
bot.setQuestionDispatcher(createQuestionDispatcher(ctx));
|
|
3080
|
+
bot.setPermissionDispatcher(createPermissionDispatcher(ctx));
|
|
3081
|
+
bot.setStartWorkDispatcher(createStartWorkDispatcher(ctx));
|
|
3082
|
+
bot.setSessionsDispatcher(
|
|
3083
|
+
createSessionsDispatcher({
|
|
2942
3084
|
client: input.client,
|
|
2943
3085
|
sessionTitleService,
|
|
2944
3086
|
sessionRegistry,
|
|
2945
3087
|
snapshotStore,
|
|
2946
3088
|
serverUrl: input.serverUrl.href,
|
|
2947
3089
|
logger
|
|
2948
|
-
})
|
|
2949
|
-
|
|
3090
|
+
})
|
|
3091
|
+
);
|
|
3092
|
+
bot.setStatusDispatcher(
|
|
3093
|
+
createStatusDispatcher({
|
|
2950
3094
|
snapshotStore,
|
|
2951
3095
|
sessionTitleService,
|
|
2952
3096
|
client: input.client,
|
|
2953
3097
|
logger,
|
|
2954
3098
|
serverUrl: input.serverUrl.href
|
|
2955
|
-
})
|
|
2956
|
-
|
|
3099
|
+
})
|
|
3100
|
+
);
|
|
3101
|
+
bot.setStartWorkCommandDispatcher(
|
|
3102
|
+
createStartWorkCommandDispatcher({
|
|
2957
3103
|
snapshotStore,
|
|
2958
3104
|
sessionTitleService,
|
|
2959
3105
|
client: input.client,
|
|
2960
3106
|
serverUrl: input.serverUrl.href,
|
|
2961
3107
|
runSessionCommand,
|
|
2962
3108
|
logger
|
|
2963
|
-
})
|
|
2964
|
-
|
|
3109
|
+
})
|
|
3110
|
+
);
|
|
3111
|
+
bot.setHelpDispatcher(createHelpDispatcher({ logger }));
|
|
3112
|
+
if (leadership.isLeader) {
|
|
3113
|
+
startLeaderPolling();
|
|
2965
3114
|
}
|
|
2966
3115
|
return {
|
|
2967
3116
|
event: async ({ event }) => {
|
|
@@ -3012,14 +3161,12 @@ var TelegramRemote = async (input) => {
|
|
|
3012
3161
|
return;
|
|
3013
3162
|
}
|
|
3014
3163
|
if (isEventPermissionAsked(extEvent)) {
|
|
3015
|
-
if (!isLeader) return;
|
|
3016
3164
|
return handlePermissionAsked(extEvent, ctx);
|
|
3017
3165
|
}
|
|
3018
3166
|
if (isEventSessionError(extEvent)) {
|
|
3019
3167
|
return handleSessionError(extEvent, ctx);
|
|
3020
3168
|
}
|
|
3021
3169
|
if (isEventQuestionAsked(extEvent)) {
|
|
3022
|
-
if (!isLeader) return;
|
|
3023
3170
|
return handleQuestionAsked(extEvent, ctx);
|
|
3024
3171
|
}
|
|
3025
3172
|
if (isEventQuestionReplied(extEvent)) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@coinseeker/opencode-telegram-plugin",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.1",
|
|
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",
|