pockcode 0.0.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/build/client/assets/addon-fit-DthTIhi3.js +1 -0
- package/build/client/assets/geist-cyrillic-ext-wght-normal-DjL33-gN.woff2 +0 -0
- package/build/client/assets/geist-cyrillic-wght-normal-BEAKL7Jp.woff2 +0 -0
- package/build/client/assets/geist-latin-ext-wght-normal-DC-KSUi6.woff2 +0 -0
- package/build/client/assets/geist-latin-wght-normal-BgDaEnEv.woff2 +0 -0
- package/build/client/assets/geist-vietnamese-wght-normal-6IgcOCM7.woff2 +0 -0
- package/build/client/assets/index-D7-eRInW.css +2 -0
- package/build/client/assets/index-DQn6eSCt.js +31 -0
- package/build/client/assets/xterm-DooSxjI5.js +36 -0
- package/build/client/index.html +27 -0
- package/dist/assets/api.server-D6ONM6ZJ.js +1736 -0
- package/dist/assets/auth.server-B_P8Jm6O.js +87 -0
- package/dist/assets/chat-status-monitor.server-D2VG_D8P.js +136 -0
- package/dist/assets/chats.service-H6m23Fna.js +4684 -0
- package/dist/assets/manager.server-Cru4ZxHo.js +1334 -0
- package/dist/assets/message-schedule-monitor.server-C4NZg62k.js +32 -0
- package/dist/assets/message-schedules.service-Be9t4LDg.js +407 -0
- package/dist/assets/runtime-paths.server-D6rRjGVb.js +32 -0
- package/dist/assets/socket.server-7dC-9Fnw.js +436 -0
- package/dist/pockcode.js +305 -0
- package/package.json +71 -0
- package/prisma/schema.prisma +234 -0
|
@@ -0,0 +1,1334 @@
|
|
|
1
|
+
import { c as readWorkspaceTree, n as onProviderEvent, p as __exportAll } from "./socket.server-7dC-9Fnw.js";
|
|
2
|
+
import { E as listAccounts, H as ensureDatabase, T as listAccountModels, U as prisma, _ as serializeChat, a as executeMessage, b as updateChat, h as respondToServerRequest, l as listChats, s as getChat, u as listMessages } from "./chats.service-H6m23Fna.js";
|
|
3
|
+
import { randomBytes, randomUUID } from "node:crypto";
|
|
4
|
+
import { Bot, InlineKeyboard } from "grammy";
|
|
5
|
+
//#region app/server/workspace-history.service.ts
|
|
6
|
+
async function listWorkspaceHistory() {
|
|
7
|
+
await ensureDatabase();
|
|
8
|
+
return (await prisma.$queryRaw`
|
|
9
|
+
SELECT "id", "path", "name", "lastOpenedAt", "createdAt", "updatedAt"
|
|
10
|
+
FROM "WorkspaceHistory"
|
|
11
|
+
ORDER BY "lastOpenedAt" DESC
|
|
12
|
+
`).map(serializeWorkspaceHistory);
|
|
13
|
+
}
|
|
14
|
+
async function saveWorkspaceHistory(inputPath) {
|
|
15
|
+
await ensureDatabase();
|
|
16
|
+
const tree = await readWorkspaceTree(inputPath, false);
|
|
17
|
+
const id = randomUUID();
|
|
18
|
+
await prisma.$executeRaw`
|
|
19
|
+
INSERT INTO "WorkspaceHistory" ("id", "path", "name", "lastOpenedAt", "createdAt", "updatedAt")
|
|
20
|
+
VALUES (${id}, ${tree.path}, ${tree.name}, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
|
|
21
|
+
ON CONFLICT("path") DO UPDATE SET
|
|
22
|
+
"name" = excluded."name",
|
|
23
|
+
"lastOpenedAt" = CURRENT_TIMESTAMP,
|
|
24
|
+
"updatedAt" = CURRENT_TIMESTAMP
|
|
25
|
+
`;
|
|
26
|
+
return readWorkspaceHistory(tree.path);
|
|
27
|
+
}
|
|
28
|
+
async function deleteWorkspaceHistory(inputPath) {
|
|
29
|
+
await ensureDatabase();
|
|
30
|
+
await prisma.$executeRaw`DELETE FROM "WorkspaceHistory" WHERE "path" = ${inputPath}`;
|
|
31
|
+
return { path: inputPath };
|
|
32
|
+
}
|
|
33
|
+
async function readWorkspaceHistory(path) {
|
|
34
|
+
const row = (await prisma.$queryRaw`
|
|
35
|
+
SELECT "id", "path", "name", "lastOpenedAt", "createdAt", "updatedAt"
|
|
36
|
+
FROM "WorkspaceHistory"
|
|
37
|
+
WHERE "path" = ${path}
|
|
38
|
+
LIMIT 1
|
|
39
|
+
`)[0];
|
|
40
|
+
if (!row) throw new Error("Workspace history was not saved.");
|
|
41
|
+
return serializeWorkspaceHistory(row);
|
|
42
|
+
}
|
|
43
|
+
function serializeWorkspaceHistory(row) {
|
|
44
|
+
return {
|
|
45
|
+
id: row.id,
|
|
46
|
+
path: row.path,
|
|
47
|
+
name: row.name,
|
|
48
|
+
lastOpenedAt: toIsoString(row.lastOpenedAt),
|
|
49
|
+
createdAt: toIsoString(row.createdAt),
|
|
50
|
+
updatedAt: toIsoString(row.updatedAt)
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
function toIsoString(value) {
|
|
54
|
+
return value instanceof Date ? value.toISOString() : new Date(value).toISOString();
|
|
55
|
+
}
|
|
56
|
+
//#endregion
|
|
57
|
+
//#region app/server/plugins/telegram.server.ts
|
|
58
|
+
var telegramPluginId = "telegram";
|
|
59
|
+
var callbackTtlMs = 3600 * 1e3;
|
|
60
|
+
var telegramListPageSize = 4;
|
|
61
|
+
var telegramMessageMaxLength = 3900;
|
|
62
|
+
var telegramMessageTailLines = 20;
|
|
63
|
+
async function verifyTelegramBotToken(token) {
|
|
64
|
+
if (!token.trim()) throw new Error("Telegram bot token is required.");
|
|
65
|
+
try {
|
|
66
|
+
await new Bot(token.trim()).api.getMe();
|
|
67
|
+
} catch (error) {
|
|
68
|
+
throw new Error(`Telegram bot token is invalid: ${readErrorMessage$1(error)}`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
var telegramPluginRegistration = {
|
|
72
|
+
definition: {
|
|
73
|
+
id: telegramPluginId,
|
|
74
|
+
label: "Telegram",
|
|
75
|
+
description: "Receive chat updates, browse workspaces, and reply from Telegram.",
|
|
76
|
+
icon: "telegram",
|
|
77
|
+
defaultSettings: {},
|
|
78
|
+
settingsFields: [],
|
|
79
|
+
secretFields: [{
|
|
80
|
+
key: "botToken",
|
|
81
|
+
label: "Bot token",
|
|
82
|
+
placeholder: "123456:ABC...",
|
|
83
|
+
required: true,
|
|
84
|
+
secret: true,
|
|
85
|
+
type: "secret"
|
|
86
|
+
}]
|
|
87
|
+
},
|
|
88
|
+
runtime: () => new TelegramPluginRuntime(),
|
|
89
|
+
actions: {
|
|
90
|
+
async clearOwner(context) {
|
|
91
|
+
const pairingCode = createPairingCode();
|
|
92
|
+
await context.updateState((state) => serializeTelegramState({
|
|
93
|
+
botUsername: readTelegramState(state).botUsername,
|
|
94
|
+
owner: null,
|
|
95
|
+
pairingCode,
|
|
96
|
+
selectedChats: {},
|
|
97
|
+
subscriptions: {}
|
|
98
|
+
}));
|
|
99
|
+
return { message: "Telegram owner cleared." };
|
|
100
|
+
},
|
|
101
|
+
async refreshPairingCode(context) {
|
|
102
|
+
const pairingCode = createPairingCode();
|
|
103
|
+
await context.updateState((state) => serializeTelegramState({
|
|
104
|
+
...readTelegramState(state),
|
|
105
|
+
pairingCode
|
|
106
|
+
}));
|
|
107
|
+
return { message: "Pairing code refreshed." };
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
summarizeState(state) {
|
|
111
|
+
const telegramState = readTelegramState(state);
|
|
112
|
+
return {
|
|
113
|
+
ownerChatId: telegramState.owner?.chatId ?? null,
|
|
114
|
+
ownerLabel: telegramState.owner?.displayName ?? null,
|
|
115
|
+
ownerUserId: telegramState.owner?.userId ?? null,
|
|
116
|
+
botUsername: telegramState.botUsername,
|
|
117
|
+
pairingCode: telegramState.pairingCode,
|
|
118
|
+
subscriptionCount: Object.keys(telegramState.subscriptions).length
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
var TelegramPluginRuntime = class {
|
|
123
|
+
constructor() {
|
|
124
|
+
this.bot = null;
|
|
125
|
+
this.callbackTokens = /* @__PURE__ */ new Map();
|
|
126
|
+
this.context = null;
|
|
127
|
+
this.pollingPromise = null;
|
|
128
|
+
this.replyTargetsByTelegramMessageId = /* @__PURE__ */ new Map();
|
|
129
|
+
this.subscriptionTailUpdateQueue = /* @__PURE__ */ new Map();
|
|
130
|
+
}
|
|
131
|
+
async start(context) {
|
|
132
|
+
this.context = context;
|
|
133
|
+
const token = readRecordString(context.secrets, "botToken");
|
|
134
|
+
await verifyTelegramBotToken(token);
|
|
135
|
+
await this.ensurePairingCode();
|
|
136
|
+
const bot = new Bot(token);
|
|
137
|
+
this.bot = bot;
|
|
138
|
+
this.installHandlers(bot);
|
|
139
|
+
bot.catch((error) => {
|
|
140
|
+
context.setStatus({
|
|
141
|
+
state: "error",
|
|
142
|
+
message: error.error instanceof Error ? error.error.message : "Telegram bot failed."
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
const me = await bot.api.getMe();
|
|
146
|
+
await context.updateState((state) => serializeTelegramState({
|
|
147
|
+
...readTelegramState(state),
|
|
148
|
+
botUsername: me.username ?? null
|
|
149
|
+
}));
|
|
150
|
+
await bot.api.setMyCommands([
|
|
151
|
+
{
|
|
152
|
+
command: "start",
|
|
153
|
+
description: "Pair or open Pockcode"
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
command: "workspaces",
|
|
157
|
+
description: "List workspaces"
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
command: "subscriptions",
|
|
161
|
+
description: "List chat subscriptions"
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
command: "help",
|
|
165
|
+
description: "Show actions"
|
|
166
|
+
}
|
|
167
|
+
]).catch(() => void 0);
|
|
168
|
+
await this.rememberSubscriptionReplyTargets();
|
|
169
|
+
context.setStatus({
|
|
170
|
+
state: "running",
|
|
171
|
+
message: `Connected as @${me.username ?? me.first_name}`
|
|
172
|
+
});
|
|
173
|
+
this.pollingPromise = this.runPolling(bot, context);
|
|
174
|
+
}
|
|
175
|
+
async stop() {
|
|
176
|
+
const bot = this.bot;
|
|
177
|
+
const pollingPromise = this.pollingPromise;
|
|
178
|
+
this.bot = null;
|
|
179
|
+
this.context = null;
|
|
180
|
+
this.pollingPromise = null;
|
|
181
|
+
this.callbackTokens.clear();
|
|
182
|
+
this.replyTargetsByTelegramMessageId.clear();
|
|
183
|
+
this.subscriptionTailUpdateQueue.clear();
|
|
184
|
+
if (!bot) return;
|
|
185
|
+
try {
|
|
186
|
+
if (bot.isRunning()) await bot.stop();
|
|
187
|
+
} catch {}
|
|
188
|
+
await pollingPromise?.catch(() => void 0);
|
|
189
|
+
}
|
|
190
|
+
async runPolling(bot, context) {
|
|
191
|
+
try {
|
|
192
|
+
await bot.start({ drop_pending_updates: true });
|
|
193
|
+
} catch (error) {
|
|
194
|
+
if (this.bot === bot) {
|
|
195
|
+
this.bot = null;
|
|
196
|
+
context.setStatus({
|
|
197
|
+
state: "error",
|
|
198
|
+
message: readTelegramPollingError(error)
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
async handleProviderEvent(event) {
|
|
204
|
+
if (!this.bot || !this.context) return;
|
|
205
|
+
if (event.type === "message.created") {
|
|
206
|
+
const message = readChatMessage(event.payload);
|
|
207
|
+
if (message && shouldUpdateSubscriptionTailForMessage(message)) await this.queueSubscribedChatTailUpdate(message.chatId);
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
installHandlers(bot) {
|
|
212
|
+
bot.command("start", (ctx) => this.handleStart(ctx));
|
|
213
|
+
bot.command("help", (ctx) => this.showMainMenu(ctx));
|
|
214
|
+
bot.command("workspaces", (ctx) => this.showWorkspaces(ctx));
|
|
215
|
+
bot.command("subscriptions", (ctx) => this.showSubscriptions(ctx));
|
|
216
|
+
bot.on("callback_query:data", (ctx) => this.handleCallback(ctx));
|
|
217
|
+
bot.on("message:text", (ctx) => this.handleTextMessage(ctx));
|
|
218
|
+
}
|
|
219
|
+
async handleStart(ctx) {
|
|
220
|
+
const payload = (ctx.message?.text ?? "").split(/\s+/)[1]?.trim();
|
|
221
|
+
const subscribeChatId = readSubscribeStartPayload(payload);
|
|
222
|
+
const from = ctx.from;
|
|
223
|
+
const chat = ctx.chat;
|
|
224
|
+
if (!from || !chat) return;
|
|
225
|
+
const state = await this.readState();
|
|
226
|
+
if (!state.owner) {
|
|
227
|
+
if (!payload || subscribeChatId || payload.toUpperCase() !== state.pairingCode?.toUpperCase()) {
|
|
228
|
+
await ctx.reply("Open Pockcode Plugins and send /start followed by the current pairing code.");
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
const owner = {
|
|
232
|
+
chatId: chat.id,
|
|
233
|
+
displayName: displayNameForTelegramUser(from),
|
|
234
|
+
pairedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
235
|
+
userId: from.id,
|
|
236
|
+
username: from.username ?? null
|
|
237
|
+
};
|
|
238
|
+
await this.writeState({
|
|
239
|
+
...state,
|
|
240
|
+
owner,
|
|
241
|
+
pairingCode: null
|
|
242
|
+
});
|
|
243
|
+
await ctx.reply("Telegram is paired with Pockcode.", { reply_markup: this.mainKeyboard() });
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
if (!this.isOwner(from.id, state)) {
|
|
247
|
+
await ctx.reply("This bot is already paired.");
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
const nextState = await this.rememberOwnerChat(ctx, state);
|
|
251
|
+
if (subscribeChatId) {
|
|
252
|
+
try {
|
|
253
|
+
await this.subscribeChatForTelegramChat(ctx, subscribeChatId, nextState);
|
|
254
|
+
} catch (error) {
|
|
255
|
+
await ctx.reply(readErrorMessage$1(error), { reply_markup: this.mainKeyboard() });
|
|
256
|
+
}
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
await this.showMainMenu(ctx);
|
|
260
|
+
}
|
|
261
|
+
async handleTextMessage(ctx) {
|
|
262
|
+
const text = ctx.message?.text?.trim();
|
|
263
|
+
if (!text || text.startsWith("/")) return;
|
|
264
|
+
const state = await this.requireOwner(ctx);
|
|
265
|
+
if (!state) return;
|
|
266
|
+
const replyMessageId = ctx.message?.reply_to_message?.message_id;
|
|
267
|
+
const chatId = (replyMessageId ? this.replyTargetsByTelegramMessageId.get(replyMessageId) : null) ?? state.selectedChats[String(ctx.chat?.id ?? state.owner?.chatId ?? "")];
|
|
268
|
+
if (!chatId) {
|
|
269
|
+
await ctx.reply("Choose a chat first.", { reply_markup: this.mainKeyboard() });
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
try {
|
|
273
|
+
await executeMessage(chatId, { content: text });
|
|
274
|
+
await ctx.reply("Sent to chat.");
|
|
275
|
+
} catch (error) {
|
|
276
|
+
await ctx.reply(readErrorMessage$1(error));
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
async handleCallback(ctx) {
|
|
280
|
+
const state = await this.requireOwner(ctx);
|
|
281
|
+
if (!state) {
|
|
282
|
+
await ctx.answerCallbackQuery().catch(() => void 0);
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
const payload = this.readCallbackPayload(ctx.callbackQuery?.data);
|
|
286
|
+
if (!payload) {
|
|
287
|
+
await ctx.answerCallbackQuery({ text: "This action expired." }).catch(() => void 0);
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
await ctx.answerCallbackQuery().catch(() => void 0);
|
|
291
|
+
await this.rememberOwnerChat(ctx, state);
|
|
292
|
+
try {
|
|
293
|
+
await this.runCallback(ctx, payload);
|
|
294
|
+
} catch (error) {
|
|
295
|
+
await this.replyOrEdit(ctx, readErrorMessage$1(error), this.mainKeyboard());
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
async runCallback(ctx, payload) {
|
|
299
|
+
if (payload.type === "main") {
|
|
300
|
+
await this.showMainMenu(ctx);
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
if (payload.type === "workspaces") {
|
|
304
|
+
await this.showWorkspaces(ctx, payload.page);
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
if (payload.type === "workspace") {
|
|
308
|
+
await this.showWorkspaceChats(ctx, payload.path, payload.page);
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
if (payload.type === "subscriptions") {
|
|
312
|
+
await this.showSubscriptions(ctx, payload.page);
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
if (payload.type === "chat") {
|
|
316
|
+
await this.showChat(ctx, payload.chatId);
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
if (payload.type === "subscribe") {
|
|
320
|
+
await this.toggleSubscription(ctx, payload.chatId);
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
if (payload.type === "reply") {
|
|
324
|
+
await this.selectReplyChat(ctx, payload.chatId);
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
if (payload.type === "settings") {
|
|
328
|
+
await this.showChatSettings(ctx, payload.chatId);
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
if (payload.type === "accountMenu") {
|
|
332
|
+
await this.showAccountMenu(ctx, payload.chatId, payload.page);
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
if (payload.type === "setAccount") {
|
|
336
|
+
await this.updateChatSetting(ctx, payload.chatId, { accountId: payload.accountId }, "Provider account updated.");
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
if (payload.type === "modelMenu") {
|
|
340
|
+
await this.showModelMenu(ctx, payload.chatId, payload.page);
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
if (payload.type === "model") {
|
|
344
|
+
await this.updateChatSetting(ctx, payload.chatId, { model: payload.value }, "Model updated.");
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
if (payload.type === "reasoningMenu") {
|
|
348
|
+
await this.showReasoningMenu(ctx, payload.chatId);
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
if (payload.type === "reasoning") {
|
|
352
|
+
await this.updateChatSetting(ctx, payload.chatId, { reasoningEffort: payload.value }, "Reasoning updated.");
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
if (payload.type === "serviceTierMenu") {
|
|
356
|
+
await this.showServiceTierMenu(ctx, payload.chatId);
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
if (payload.type === "serviceTier") {
|
|
360
|
+
await this.updateChatSetting(ctx, payload.chatId, { serviceTier: payload.value }, "Speed updated.");
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
if (payload.type === "permissionMenu") {
|
|
364
|
+
await this.showPermissionMenu(ctx, payload.chatId);
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
if (payload.type === "permission") {
|
|
368
|
+
await this.updateChatSetting(ctx, payload.chatId, { permissionMode: payload.value }, "Security mode updated.");
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
if (payload.type === "mode") {
|
|
372
|
+
await this.updateChatSetting(ctx, payload.chatId, { collaborationMode: payload.value }, "Mode updated.");
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
if (payload.type === "serverRequest") {
|
|
376
|
+
await this.respondToServerRequest(ctx, payload);
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
if (payload.type === "userInput") await this.respondToUserInput(ctx, payload);
|
|
380
|
+
}
|
|
381
|
+
async showMainMenu(ctx) {
|
|
382
|
+
if (!await this.requireOwner(ctx)) return;
|
|
383
|
+
await this.replyOrEdit(ctx, "Pockcode Telegram", this.mainKeyboard());
|
|
384
|
+
}
|
|
385
|
+
async showWorkspaces(ctx, requestedPage = 0) {
|
|
386
|
+
if (!await this.requireOwner(ctx)) return;
|
|
387
|
+
const workspaces = await listWorkspaceHistory();
|
|
388
|
+
const page = paginatedItems(workspaces, requestedPage);
|
|
389
|
+
const keyboard = new InlineKeyboard();
|
|
390
|
+
for (const workspace of page.items) keyboard.text(workspace.name, this.callbackData({
|
|
391
|
+
type: "workspace",
|
|
392
|
+
path: workspace.path
|
|
393
|
+
})).row();
|
|
394
|
+
this.addPaginationControls(keyboard, page, (nextPage) => ({
|
|
395
|
+
type: "workspaces",
|
|
396
|
+
page: nextPage
|
|
397
|
+
}));
|
|
398
|
+
keyboard.text("Subscriptions", this.callbackData({ type: "subscriptions" })).text("Back", this.callbackData({ type: "main" }));
|
|
399
|
+
await this.replyOrEdit(ctx, workspaces.length ? listTitle("Workspaces", page) : "No workspaces yet.", keyboard);
|
|
400
|
+
}
|
|
401
|
+
async showWorkspaceChats(ctx, workspacePath, requestedPage = 0) {
|
|
402
|
+
const chats = await listChats(workspacePath);
|
|
403
|
+
const page = paginatedItems(chats, requestedPage);
|
|
404
|
+
const keyboard = new InlineKeyboard();
|
|
405
|
+
for (const chat of page.items) keyboard.text(compactLabel(chat.title, 36), this.callbackData({
|
|
406
|
+
type: "chat",
|
|
407
|
+
chatId: chat.id
|
|
408
|
+
})).row();
|
|
409
|
+
this.addPaginationControls(keyboard, page, (nextPage) => ({
|
|
410
|
+
type: "workspace",
|
|
411
|
+
path: workspacePath,
|
|
412
|
+
page: nextPage
|
|
413
|
+
}));
|
|
414
|
+
keyboard.text("Workspaces", this.callbackData({ type: "workspaces" })).text("Back", this.callbackData({ type: "main" }));
|
|
415
|
+
await this.replyOrEdit(ctx, chats.length ? `${listTitle("Chats", page)}\n${workspacePath}` : `No chats\n${workspacePath}`, keyboard);
|
|
416
|
+
}
|
|
417
|
+
async showSubscriptions(ctx, requestedPage = 0) {
|
|
418
|
+
const state = await this.readState();
|
|
419
|
+
const keyboard = new InlineKeyboard();
|
|
420
|
+
const chatIds = Object.keys(state.subscriptions);
|
|
421
|
+
const page = paginatedItems(chatIds, requestedPage);
|
|
422
|
+
for (const chatId of page.items) {
|
|
423
|
+
const chat = await this.readChatResponse(chatId).catch(() => null);
|
|
424
|
+
keyboard.text(compactLabel(chat?.title ?? chatId, 36), this.callbackData({
|
|
425
|
+
type: "chat",
|
|
426
|
+
chatId
|
|
427
|
+
})).row();
|
|
428
|
+
}
|
|
429
|
+
this.addPaginationControls(keyboard, page, (nextPage) => ({
|
|
430
|
+
type: "subscriptions",
|
|
431
|
+
page: nextPage
|
|
432
|
+
}));
|
|
433
|
+
keyboard.text("Workspaces", this.callbackData({ type: "workspaces" })).text("Back", this.callbackData({ type: "main" }));
|
|
434
|
+
await this.replyOrEdit(ctx, chatIds.length ? listTitle("Subscriptions", page) : "No subscriptions.", keyboard);
|
|
435
|
+
}
|
|
436
|
+
async showChat(ctx, chatId) {
|
|
437
|
+
const chat = await this.readChatResponse(chatId);
|
|
438
|
+
const state = await this.readState();
|
|
439
|
+
await this.writeState({
|
|
440
|
+
...state,
|
|
441
|
+
selectedChats: {
|
|
442
|
+
...state.selectedChats,
|
|
443
|
+
[String(ctx.chat?.id ?? state.owner?.chatId ?? "")]: chatId
|
|
444
|
+
}
|
|
445
|
+
});
|
|
446
|
+
const recent = (await listMessages(chatId, 5).catch(() => ({ data: [] }))).data.filter(isAssistantChatMessage).slice(-3).map(formatAssistantTailBlock).join("\n\n");
|
|
447
|
+
await this.replyOrEdit(ctx, [
|
|
448
|
+
chat.title,
|
|
449
|
+
chat.workingDirectory ?? "",
|
|
450
|
+
`Status: ${chat.status}`,
|
|
451
|
+
`Model: ${chat.model ?? "default"}`,
|
|
452
|
+
`Security: ${chat.permissionMode}`,
|
|
453
|
+
recent ? `\nRecent\n${recent}` : ""
|
|
454
|
+
].filter(Boolean).join("\n"), this.chatKeyboard(chat, state));
|
|
455
|
+
}
|
|
456
|
+
async showChatSettings(ctx, chatId) {
|
|
457
|
+
const chat = await this.readChatResponse(chatId);
|
|
458
|
+
const keyboard = new InlineKeyboard().text("Account", this.callbackData({
|
|
459
|
+
type: "accountMenu",
|
|
460
|
+
chatId
|
|
461
|
+
})).text("Model", this.callbackData({
|
|
462
|
+
type: "modelMenu",
|
|
463
|
+
chatId
|
|
464
|
+
})).row().text("Reasoning", this.callbackData({
|
|
465
|
+
type: "reasoningMenu",
|
|
466
|
+
chatId
|
|
467
|
+
})).text("Speed", this.callbackData({
|
|
468
|
+
type: "serviceTierMenu",
|
|
469
|
+
chatId
|
|
470
|
+
})).row().text(chat.collaborationMode === "plan" ? "Mode: Plan" : "Mode: Default", this.callbackData({
|
|
471
|
+
type: "mode",
|
|
472
|
+
chatId,
|
|
473
|
+
value: chat.collaborationMode === "plan" ? "default" : "plan"
|
|
474
|
+
})).row().text("Security", this.callbackData({
|
|
475
|
+
type: "permissionMenu",
|
|
476
|
+
chatId
|
|
477
|
+
})).text("Back", this.callbackData({
|
|
478
|
+
type: "chat",
|
|
479
|
+
chatId
|
|
480
|
+
}));
|
|
481
|
+
await this.replyOrEdit(ctx, [
|
|
482
|
+
"Chat settings",
|
|
483
|
+
`Account: ${chat.accountId ?? "none"}`,
|
|
484
|
+
`Model: ${chat.model ?? "default"}`,
|
|
485
|
+
`Reasoning: ${chat.reasoningEffort ?? "default"}`,
|
|
486
|
+
`Speed: ${chat.serviceTier ?? "default"}`,
|
|
487
|
+
`Mode: ${chat.collaborationMode}`,
|
|
488
|
+
`Security: ${chat.permissionMode}`
|
|
489
|
+
].join("\n"), keyboard);
|
|
490
|
+
}
|
|
491
|
+
async showAccountMenu(ctx, chatId, requestedPage = 0) {
|
|
492
|
+
const chat = await this.readChatResponse(chatId);
|
|
493
|
+
const accounts = (await listAccounts()).filter((account) => account.status === "CONNECTED" && account.providerId === chat.providerId);
|
|
494
|
+
const page = paginatedItems(accounts, requestedPage);
|
|
495
|
+
const keyboard = new InlineKeyboard();
|
|
496
|
+
for (const account of page.items) keyboard.text(`${account.id === chat.accountId ? "* " : ""}${account.displayName}`, this.callbackData({
|
|
497
|
+
type: "setAccount",
|
|
498
|
+
chatId,
|
|
499
|
+
accountId: account.id
|
|
500
|
+
})).row();
|
|
501
|
+
this.addPaginationControls(keyboard, page, (nextPage) => ({
|
|
502
|
+
type: "accountMenu",
|
|
503
|
+
chatId,
|
|
504
|
+
page: nextPage
|
|
505
|
+
}));
|
|
506
|
+
keyboard.text("Back", this.callbackData({
|
|
507
|
+
type: "settings",
|
|
508
|
+
chatId
|
|
509
|
+
}));
|
|
510
|
+
await this.replyOrEdit(ctx, accounts.length ? listTitle("Provider accounts", page) : "No connected accounts for this provider.", keyboard);
|
|
511
|
+
}
|
|
512
|
+
async showModelMenu(ctx, chatId, requestedPage = 0) {
|
|
513
|
+
const accountId = (await this.readChatResponse(chatId)).accountId;
|
|
514
|
+
const keyboard = new InlineKeyboard();
|
|
515
|
+
let page = null;
|
|
516
|
+
if (accountId) {
|
|
517
|
+
page = paginatedItems((await listAccountModels(accountId).catch(() => ({ data: [] }))).data.filter((item) => !item.hidden), requestedPage);
|
|
518
|
+
for (const option of page.items) keyboard.text(option.displayName, this.callbackData({
|
|
519
|
+
type: "model",
|
|
520
|
+
chatId,
|
|
521
|
+
value: option.model
|
|
522
|
+
})).row();
|
|
523
|
+
this.addPaginationControls(keyboard, page, (nextPage) => ({
|
|
524
|
+
type: "modelMenu",
|
|
525
|
+
chatId,
|
|
526
|
+
page: nextPage
|
|
527
|
+
}));
|
|
528
|
+
}
|
|
529
|
+
keyboard.text("Back", this.callbackData({
|
|
530
|
+
type: "settings",
|
|
531
|
+
chatId
|
|
532
|
+
}));
|
|
533
|
+
await this.replyOrEdit(ctx, accountId ? page && page.total ? listTitle("Models", page) : "No models." : "Choose a provider account first.", keyboard);
|
|
534
|
+
}
|
|
535
|
+
async showReasoningMenu(ctx, chatId) {
|
|
536
|
+
const keyboard = new InlineKeyboard();
|
|
537
|
+
for (const option of [
|
|
538
|
+
"none",
|
|
539
|
+
"minimal",
|
|
540
|
+
"low",
|
|
541
|
+
"medium",
|
|
542
|
+
"high",
|
|
543
|
+
"xhigh"
|
|
544
|
+
]) keyboard.text(option, this.callbackData({
|
|
545
|
+
type: "reasoning",
|
|
546
|
+
chatId,
|
|
547
|
+
value: option
|
|
548
|
+
})).row();
|
|
549
|
+
keyboard.text("Back", this.callbackData({
|
|
550
|
+
type: "settings",
|
|
551
|
+
chatId
|
|
552
|
+
}));
|
|
553
|
+
await this.replyOrEdit(ctx, "Reasoning", keyboard);
|
|
554
|
+
}
|
|
555
|
+
async showServiceTierMenu(ctx, chatId) {
|
|
556
|
+
const keyboard = new InlineKeyboard().text("standard", this.callbackData({
|
|
557
|
+
type: "serviceTier",
|
|
558
|
+
chatId,
|
|
559
|
+
value: "standard"
|
|
560
|
+
})).row().text("fast", this.callbackData({
|
|
561
|
+
type: "serviceTier",
|
|
562
|
+
chatId,
|
|
563
|
+
value: "fast"
|
|
564
|
+
})).row().text("Back", this.callbackData({
|
|
565
|
+
type: "settings",
|
|
566
|
+
chatId
|
|
567
|
+
}));
|
|
568
|
+
await this.replyOrEdit(ctx, "Speed", keyboard);
|
|
569
|
+
}
|
|
570
|
+
async showPermissionMenu(ctx, chatId) {
|
|
571
|
+
const keyboard = new InlineKeyboard().text("Ask for approval", this.callbackData({
|
|
572
|
+
type: "permission",
|
|
573
|
+
chatId,
|
|
574
|
+
value: "askForApproval"
|
|
575
|
+
})).row().text("Full access", this.callbackData({
|
|
576
|
+
type: "permission",
|
|
577
|
+
chatId,
|
|
578
|
+
value: "fullAccess"
|
|
579
|
+
})).row().text("Back", this.callbackData({
|
|
580
|
+
type: "settings",
|
|
581
|
+
chatId
|
|
582
|
+
}));
|
|
583
|
+
await this.replyOrEdit(ctx, "Security mode", keyboard);
|
|
584
|
+
}
|
|
585
|
+
async updateChatSetting(ctx, chatId, data, message) {
|
|
586
|
+
await updateChat(chatId, data);
|
|
587
|
+
await this.replyOrEdit(ctx, message, new InlineKeyboard().text("Back", this.callbackData({
|
|
588
|
+
type: "settings",
|
|
589
|
+
chatId
|
|
590
|
+
})));
|
|
591
|
+
}
|
|
592
|
+
async toggleSubscription(ctx, chatId) {
|
|
593
|
+
const state = await this.readState();
|
|
594
|
+
const subscriptions = { ...state.subscriptions };
|
|
595
|
+
const existingSubscription = subscriptions[chatId];
|
|
596
|
+
if (existingSubscription) {
|
|
597
|
+
delete subscriptions[chatId];
|
|
598
|
+
await this.writeState({
|
|
599
|
+
...state,
|
|
600
|
+
subscriptions
|
|
601
|
+
});
|
|
602
|
+
if (existingSubscription.messageId) this.replyTargetsByTelegramMessageId.delete(existingSubscription.messageId);
|
|
603
|
+
await this.replyOrEdit(ctx, "Unsubscribed.", new InlineKeyboard().text("Subscribe", this.callbackData({
|
|
604
|
+
type: "subscribe",
|
|
605
|
+
chatId
|
|
606
|
+
})));
|
|
607
|
+
} else await this.subscribeChatForTelegramChat(ctx, chatId, state);
|
|
608
|
+
}
|
|
609
|
+
async subscribeChatForTelegramChat(ctx, chatId, state) {
|
|
610
|
+
const chat = await this.readChatResponse(chatId);
|
|
611
|
+
const telegramChatId = ctx.chat?.id ?? state.owner?.chatId ?? 0;
|
|
612
|
+
const startedAt = state.subscriptions[chatId]?.tailStartedAt ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
613
|
+
const nextState = {
|
|
614
|
+
...state,
|
|
615
|
+
selectedChats: {
|
|
616
|
+
...state.selectedChats,
|
|
617
|
+
[String(telegramChatId)]: chatId
|
|
618
|
+
},
|
|
619
|
+
subscriptions: {
|
|
620
|
+
...state.subscriptions,
|
|
621
|
+
[chatId]: {
|
|
622
|
+
messageId: state.subscriptions[chatId]?.messageId ?? null,
|
|
623
|
+
subscribedAt: state.subscriptions[chatId]?.subscribedAt ?? startedAt,
|
|
624
|
+
tailStartedAt: startedAt,
|
|
625
|
+
telegramChatId,
|
|
626
|
+
workspacePath: chat.workingDirectory ?? null
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
};
|
|
630
|
+
await this.writeState(nextState);
|
|
631
|
+
return this.queueSubscribedChatTailUpdate(chatId, nextState, ctx, chat);
|
|
632
|
+
}
|
|
633
|
+
async selectReplyChat(ctx, chatId) {
|
|
634
|
+
const state = await this.readState();
|
|
635
|
+
await this.writeState({
|
|
636
|
+
...state,
|
|
637
|
+
selectedChats: {
|
|
638
|
+
...state.selectedChats,
|
|
639
|
+
[String(ctx.chat?.id ?? state.owner?.chatId ?? "")]: chatId
|
|
640
|
+
}
|
|
641
|
+
});
|
|
642
|
+
await this.replyOrEdit(ctx, "Send a Telegram message now, or reply to a chat update.", new InlineKeyboard().text("Back", this.callbackData({
|
|
643
|
+
type: "chat",
|
|
644
|
+
chatId
|
|
645
|
+
})));
|
|
646
|
+
}
|
|
647
|
+
async respondToServerRequest(ctx, payload) {
|
|
648
|
+
const requestId = payload.message.requestId;
|
|
649
|
+
if (!requestId) {
|
|
650
|
+
await this.replyOrEdit(ctx, "Request is missing an id.", this.mainKeyboard());
|
|
651
|
+
return;
|
|
652
|
+
}
|
|
653
|
+
await respondToServerRequest(payload.chatId, requestId, serverRequestResponseFor(payload.message, payload.approved));
|
|
654
|
+
await this.replyOrEdit(ctx, payload.approved ? "Approved." : "Declined.", new InlineKeyboard().text("Chat", this.callbackData({
|
|
655
|
+
type: "chat",
|
|
656
|
+
chatId: payload.chatId
|
|
657
|
+
})));
|
|
658
|
+
}
|
|
659
|
+
async respondToUserInput(ctx, payload) {
|
|
660
|
+
const requestId = payload.message.requestId;
|
|
661
|
+
if (!requestId) {
|
|
662
|
+
await this.replyOrEdit(ctx, "Request is missing an id.", this.mainKeyboard());
|
|
663
|
+
return;
|
|
664
|
+
}
|
|
665
|
+
await respondToServerRequest(payload.chatId, requestId, {
|
|
666
|
+
kind: "userInput",
|
|
667
|
+
result: { answers: payload.answers }
|
|
668
|
+
});
|
|
669
|
+
await this.replyOrEdit(ctx, "Submitted.", new InlineKeyboard().text("Chat", this.callbackData({
|
|
670
|
+
type: "chat",
|
|
671
|
+
chatId: payload.chatId
|
|
672
|
+
})));
|
|
673
|
+
}
|
|
674
|
+
async updateSubscribedChatTail(chatId, state, ctx, chat) {
|
|
675
|
+
const currentState = state ?? await this.readState();
|
|
676
|
+
const subscription = currentState.subscriptions[chatId];
|
|
677
|
+
if (!subscription) return currentState;
|
|
678
|
+
const targetChatId = subscription.telegramChatId || currentState.owner?.chatId;
|
|
679
|
+
if (!targetChatId || !this.bot) return currentState;
|
|
680
|
+
let activeState = currentState;
|
|
681
|
+
let activeSubscription = subscription;
|
|
682
|
+
const tailStartedAt = activeSubscription.tailStartedAt ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
683
|
+
if (!activeSubscription.tailStartedAt) {
|
|
684
|
+
activeSubscription = {
|
|
685
|
+
...activeSubscription,
|
|
686
|
+
tailStartedAt
|
|
687
|
+
};
|
|
688
|
+
activeState = {
|
|
689
|
+
...currentState,
|
|
690
|
+
subscriptions: {
|
|
691
|
+
...currentState.subscriptions,
|
|
692
|
+
[chatId]: activeSubscription
|
|
693
|
+
}
|
|
694
|
+
};
|
|
695
|
+
await this.writeState(activeState);
|
|
696
|
+
}
|
|
697
|
+
const text = await this.formatSubscribedChatTail(chatId, tailStartedAt, chat);
|
|
698
|
+
const messageId = await this.sendOrEditSubscriptionMessage(ctx, targetChatId, subscription.messageId ?? null, text, this.subscriptionTailKeyboard(chatId));
|
|
699
|
+
this.replyTargetsByTelegramMessageId.set(messageId, chatId);
|
|
700
|
+
if (messageId === subscription.messageId) return activeState;
|
|
701
|
+
const latestState = await this.readState();
|
|
702
|
+
const latestSubscription = latestState.subscriptions[chatId];
|
|
703
|
+
if (!latestSubscription) return latestState;
|
|
704
|
+
const nextState = {
|
|
705
|
+
...latestState,
|
|
706
|
+
subscriptions: {
|
|
707
|
+
...latestState.subscriptions,
|
|
708
|
+
[chatId]: {
|
|
709
|
+
...latestSubscription,
|
|
710
|
+
messageId
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
};
|
|
714
|
+
await this.writeState(nextState);
|
|
715
|
+
return nextState;
|
|
716
|
+
}
|
|
717
|
+
async queueSubscribedChatTailUpdate(chatId, state, ctx, chat) {
|
|
718
|
+
const previous = this.subscriptionTailUpdateQueue.get(chatId);
|
|
719
|
+
const queued = (async () => {
|
|
720
|
+
await previous?.catch(() => void 0);
|
|
721
|
+
return this.updateSubscribedChatTail(chatId, previous ? void 0 : state, ctx, chat);
|
|
722
|
+
})();
|
|
723
|
+
this.subscriptionTailUpdateQueue.set(chatId, queued);
|
|
724
|
+
try {
|
|
725
|
+
return await queued;
|
|
726
|
+
} finally {
|
|
727
|
+
if (this.subscriptionTailUpdateQueue.get(chatId) === queued) this.subscriptionTailUpdateQueue.delete(chatId);
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
async sendOrEditSubscriptionMessage(ctx, telegramChatId, messageId, text, keyboard) {
|
|
731
|
+
const bot = this.bot;
|
|
732
|
+
if (!bot) throw new Error("Telegram bot is not running.");
|
|
733
|
+
const messageText = fitTelegramMessage(text, telegramMessageMaxLength);
|
|
734
|
+
const callbackMessage = ctx?.callbackQuery?.message;
|
|
735
|
+
const callbackMessageId = typeof callbackMessage?.chat.id === "number" && callbackMessage.chat.id === telegramChatId ? callbackMessage.message_id : null;
|
|
736
|
+
const editableMessageId = messageId ?? callbackMessageId;
|
|
737
|
+
if (editableMessageId) try {
|
|
738
|
+
await bot.api.editMessageText(telegramChatId, editableMessageId, messageText, { reply_markup: keyboard });
|
|
739
|
+
return editableMessageId;
|
|
740
|
+
} catch (error) {
|
|
741
|
+
if (readErrorMessage$1(error).toLowerCase().includes("message is not modified")) return editableMessageId;
|
|
742
|
+
if (messageId) await bot.api.deleteMessage(telegramChatId, messageId).catch(() => void 0);
|
|
743
|
+
}
|
|
744
|
+
return (await bot.api.sendMessage(telegramChatId, messageText, { reply_markup: keyboard })).message_id;
|
|
745
|
+
}
|
|
746
|
+
async formatSubscribedChatTail(chatId, since, chat) {
|
|
747
|
+
const chatResponse = chat ?? await this.readChatResponse(chatId);
|
|
748
|
+
const tail = tailLines((await listMessages(chatId, 50).catch(() => ({ data: [] }))).data.filter((message) => isAssistantChatMessage(message) && isMessageAtOrAfter(message, since)).map(formatAssistantTailBlock).flatMap((block, index) => [...index > 0 ? [""] : [], ...block.split("\n")]), telegramMessageTailLines).join("\n").trim();
|
|
749
|
+
return [compactLabel(chatResponse.title, 80), tail || "(no assistant messages yet)"].join("\n");
|
|
750
|
+
}
|
|
751
|
+
async replyOrEdit(ctx, text, keyboard) {
|
|
752
|
+
const options = keyboard ? { reply_markup: keyboard } : void 0;
|
|
753
|
+
if (ctx.callbackQuery?.message) try {
|
|
754
|
+
await ctx.editMessageText(text, options);
|
|
755
|
+
return;
|
|
756
|
+
} catch {}
|
|
757
|
+
await ctx.reply(text, options);
|
|
758
|
+
}
|
|
759
|
+
chatKeyboard(chat, state) {
|
|
760
|
+
const subscribed = Boolean(state.subscriptions[chat.id]);
|
|
761
|
+
return new InlineKeyboard().text(subscribed ? "Unsubscribe" : "Subscribe", this.callbackData({
|
|
762
|
+
type: "subscribe",
|
|
763
|
+
chatId: chat.id
|
|
764
|
+
})).text("Reply", this.callbackData({
|
|
765
|
+
type: "reply",
|
|
766
|
+
chatId: chat.id
|
|
767
|
+
})).row().text("Settings", this.callbackData({
|
|
768
|
+
type: "settings",
|
|
769
|
+
chatId: chat.id
|
|
770
|
+
})).text("Chats", this.callbackData({
|
|
771
|
+
type: "workspace",
|
|
772
|
+
path: chat.workingDirectory ?? ""
|
|
773
|
+
}));
|
|
774
|
+
}
|
|
775
|
+
subscriptionTailKeyboard(chatId) {
|
|
776
|
+
return new InlineKeyboard().text("Unsubscribe", this.callbackData({
|
|
777
|
+
type: "subscribe",
|
|
778
|
+
chatId
|
|
779
|
+
}));
|
|
780
|
+
}
|
|
781
|
+
mainKeyboard() {
|
|
782
|
+
return new InlineKeyboard().text("Workspaces", this.callbackData({ type: "workspaces" })).text("Subscriptions", this.callbackData({ type: "subscriptions" }));
|
|
783
|
+
}
|
|
784
|
+
addPaginationControls(keyboard, page, payloadForPage) {
|
|
785
|
+
if (page.totalPages <= 1) return;
|
|
786
|
+
if (page.page > 0) keyboard.text("Prev", this.callbackData(payloadForPage(page.page - 1)));
|
|
787
|
+
if (page.page < page.totalPages - 1) keyboard.text("Next", this.callbackData(payloadForPage(page.page + 1)));
|
|
788
|
+
keyboard.row();
|
|
789
|
+
}
|
|
790
|
+
serverRequestKeyboard(message) {
|
|
791
|
+
if (message.kind === "USER_INPUT_PROMPT") {
|
|
792
|
+
const question = readUserInputQuestions(message.rawPayload)[0];
|
|
793
|
+
const keyboard = new InlineKeyboard();
|
|
794
|
+
if (question) for (const option of question.options.slice(0, 8)) keyboard.text(option.label, this.callbackData({
|
|
795
|
+
answers: { [question.id]: { answers: [option.label] } },
|
|
796
|
+
chatId: message.chatId,
|
|
797
|
+
message,
|
|
798
|
+
type: "userInput"
|
|
799
|
+
})).row();
|
|
800
|
+
keyboard.text("Submit empty", this.callbackData({
|
|
801
|
+
answers: {},
|
|
802
|
+
chatId: message.chatId,
|
|
803
|
+
message,
|
|
804
|
+
type: "userInput"
|
|
805
|
+
}));
|
|
806
|
+
keyboard.text("Chat", this.callbackData({
|
|
807
|
+
type: "chat",
|
|
808
|
+
chatId: message.chatId
|
|
809
|
+
}));
|
|
810
|
+
return keyboard;
|
|
811
|
+
}
|
|
812
|
+
return new InlineKeyboard().text("Approve", this.callbackData({
|
|
813
|
+
type: "serverRequest",
|
|
814
|
+
chatId: message.chatId,
|
|
815
|
+
message,
|
|
816
|
+
approved: true
|
|
817
|
+
})).text("Decline", this.callbackData({
|
|
818
|
+
type: "serverRequest",
|
|
819
|
+
chatId: message.chatId,
|
|
820
|
+
message,
|
|
821
|
+
approved: false
|
|
822
|
+
})).row().text("Chat", this.callbackData({
|
|
823
|
+
type: "chat",
|
|
824
|
+
chatId: message.chatId
|
|
825
|
+
}));
|
|
826
|
+
}
|
|
827
|
+
callbackData(payload) {
|
|
828
|
+
const direct = directCallbackData(payload);
|
|
829
|
+
if (direct && direct.length <= 64) return direct;
|
|
830
|
+
this.collectExpiredCallbackTokens();
|
|
831
|
+
const token = randomToken(8);
|
|
832
|
+
this.callbackTokens.set(token, {
|
|
833
|
+
expiresAt: Date.now() + callbackTtlMs,
|
|
834
|
+
payload
|
|
835
|
+
});
|
|
836
|
+
return `t:${token}`;
|
|
837
|
+
}
|
|
838
|
+
readCallbackPayload(data) {
|
|
839
|
+
if (!data) return null;
|
|
840
|
+
if (data.startsWith("t:")) {
|
|
841
|
+
const token = this.callbackTokens.get(data.slice(2));
|
|
842
|
+
if (!token || token.expiresAt < Date.now()) return null;
|
|
843
|
+
return token.payload;
|
|
844
|
+
}
|
|
845
|
+
if (data === "m") return { type: "main" };
|
|
846
|
+
if (data === "wl") return { type: "workspaces" };
|
|
847
|
+
if (data === "subs") return { type: "subscriptions" };
|
|
848
|
+
if (data.startsWith("c:")) return {
|
|
849
|
+
type: "chat",
|
|
850
|
+
chatId: data.slice(2)
|
|
851
|
+
};
|
|
852
|
+
return null;
|
|
853
|
+
}
|
|
854
|
+
collectExpiredCallbackTokens() {
|
|
855
|
+
const now = Date.now();
|
|
856
|
+
for (const [token, value] of this.callbackTokens) if (value.expiresAt < now) this.callbackTokens.delete(token);
|
|
857
|
+
}
|
|
858
|
+
async requireOwner(ctx) {
|
|
859
|
+
const from = ctx.from;
|
|
860
|
+
if (!from) return null;
|
|
861
|
+
const state = await this.readState();
|
|
862
|
+
if (!state.owner) {
|
|
863
|
+
await ctx.reply("Open Pockcode Plugins and pair this bot with /start <code>.").catch(() => void 0);
|
|
864
|
+
return null;
|
|
865
|
+
}
|
|
866
|
+
if (!this.isOwner(from.id, state)) {
|
|
867
|
+
await ctx.reply("Only the paired Telegram owner can control Pockcode.").catch(() => void 0);
|
|
868
|
+
return null;
|
|
869
|
+
}
|
|
870
|
+
return state;
|
|
871
|
+
}
|
|
872
|
+
isOwner(userId, state) {
|
|
873
|
+
return state.owner?.userId === userId;
|
|
874
|
+
}
|
|
875
|
+
async rememberOwnerChat(ctx, state) {
|
|
876
|
+
if (!ctx.chat || !state.owner || state.owner.chatId === ctx.chat.id) return state;
|
|
877
|
+
const nextState = {
|
|
878
|
+
...state,
|
|
879
|
+
owner: {
|
|
880
|
+
...state.owner,
|
|
881
|
+
chatId: ctx.chat.id
|
|
882
|
+
}
|
|
883
|
+
};
|
|
884
|
+
await this.writeState(nextState);
|
|
885
|
+
return nextState;
|
|
886
|
+
}
|
|
887
|
+
async ensurePairingCode() {
|
|
888
|
+
const state = await this.readState();
|
|
889
|
+
if (!state.owner && !state.pairingCode) await this.writeState({
|
|
890
|
+
...state,
|
|
891
|
+
pairingCode: createPairingCode()
|
|
892
|
+
});
|
|
893
|
+
}
|
|
894
|
+
async readState() {
|
|
895
|
+
return this.context ? readTelegramState(await this.context.getState()) : emptyTelegramState();
|
|
896
|
+
}
|
|
897
|
+
async writeState(state) {
|
|
898
|
+
await this.context?.setState(serializeTelegramState(state));
|
|
899
|
+
}
|
|
900
|
+
async rememberSubscriptionReplyTargets() {
|
|
901
|
+
const state = await this.readState();
|
|
902
|
+
for (const [chatId, subscription] of Object.entries(state.subscriptions)) if (subscription.messageId) this.replyTargetsByTelegramMessageId.set(subscription.messageId, chatId);
|
|
903
|
+
}
|
|
904
|
+
async readChatResponse(chatId) {
|
|
905
|
+
return serializeChat(await getChat(chatId));
|
|
906
|
+
}
|
|
907
|
+
};
|
|
908
|
+
function directCallbackData(payload) {
|
|
909
|
+
if (payload.type === "main") return "m";
|
|
910
|
+
if (payload.type === "workspaces" && !payload.page) return "wl";
|
|
911
|
+
if (payload.type === "subscriptions" && !payload.page) return "subs";
|
|
912
|
+
if (payload.type === "chat") return `c:${payload.chatId}`;
|
|
913
|
+
return null;
|
|
914
|
+
}
|
|
915
|
+
function paginatedItems(items, requestedPage) {
|
|
916
|
+
const total = items.length;
|
|
917
|
+
const totalPages = Math.max(1, Math.ceil(total / telegramListPageSize));
|
|
918
|
+
const page = Math.min(Math.max(typeof requestedPage === "number" && Number.isFinite(requestedPage) ? Math.trunc(requestedPage) : 0, 0), totalPages - 1);
|
|
919
|
+
const start = page * telegramListPageSize;
|
|
920
|
+
return {
|
|
921
|
+
items: items.slice(start, start + telegramListPageSize),
|
|
922
|
+
page,
|
|
923
|
+
total,
|
|
924
|
+
totalPages
|
|
925
|
+
};
|
|
926
|
+
}
|
|
927
|
+
function listTitle(title, page) {
|
|
928
|
+
return page.totalPages > 1 ? `${title}\nPage ${page.page + 1}/${page.totalPages}` : title;
|
|
929
|
+
}
|
|
930
|
+
function readTelegramState(value) {
|
|
931
|
+
const record = readRecord(value);
|
|
932
|
+
return {
|
|
933
|
+
botUsername: readRecordString(record, "botUsername") || null,
|
|
934
|
+
owner: readOwner(record.owner),
|
|
935
|
+
pairingCode: readRecordString(record, "pairingCode") || null,
|
|
936
|
+
selectedChats: readStringRecord(record.selectedChats),
|
|
937
|
+
subscriptions: readSubscriptions(record.subscriptions)
|
|
938
|
+
};
|
|
939
|
+
}
|
|
940
|
+
function emptyTelegramState() {
|
|
941
|
+
return {
|
|
942
|
+
botUsername: null,
|
|
943
|
+
owner: null,
|
|
944
|
+
pairingCode: null,
|
|
945
|
+
selectedChats: {},
|
|
946
|
+
subscriptions: {}
|
|
947
|
+
};
|
|
948
|
+
}
|
|
949
|
+
function serializeTelegramState(state) {
|
|
950
|
+
return {
|
|
951
|
+
botUsername: state.botUsername,
|
|
952
|
+
owner: state.owner,
|
|
953
|
+
pairingCode: state.pairingCode,
|
|
954
|
+
selectedChats: state.selectedChats,
|
|
955
|
+
subscriptions: state.subscriptions
|
|
956
|
+
};
|
|
957
|
+
}
|
|
958
|
+
function readSubscribeStartPayload(value) {
|
|
959
|
+
if (!value?.startsWith("sub_")) return null;
|
|
960
|
+
return value.slice(4).trim() || null;
|
|
961
|
+
}
|
|
962
|
+
function readOwner(value) {
|
|
963
|
+
const record = readRecord(value);
|
|
964
|
+
const userId = readNumber(record.userId);
|
|
965
|
+
const chatId = readNumber(record.chatId);
|
|
966
|
+
if (!userId || !chatId) return null;
|
|
967
|
+
return {
|
|
968
|
+
chatId,
|
|
969
|
+
displayName: readRecordString(record, "displayName") || String(userId),
|
|
970
|
+
pairedAt: readRecordString(record, "pairedAt") || (/* @__PURE__ */ new Date()).toISOString(),
|
|
971
|
+
userId,
|
|
972
|
+
username: readRecordString(record, "username") || null
|
|
973
|
+
};
|
|
974
|
+
}
|
|
975
|
+
function readSubscriptions(value) {
|
|
976
|
+
const record = readRecord(value);
|
|
977
|
+
const subscriptions = {};
|
|
978
|
+
for (const [chatId, subscriptionValue] of Object.entries(record)) {
|
|
979
|
+
const subscription = readRecord(subscriptionValue);
|
|
980
|
+
const telegramChatId = readNumber(subscription.telegramChatId);
|
|
981
|
+
if (!telegramChatId) continue;
|
|
982
|
+
subscriptions[chatId] = {
|
|
983
|
+
messageId: readNumber(subscription.messageId),
|
|
984
|
+
subscribedAt: readRecordString(subscription, "subscribedAt") || (/* @__PURE__ */ new Date()).toISOString(),
|
|
985
|
+
tailStartedAt: readRecordString(subscription, "tailStartedAt") || null,
|
|
986
|
+
telegramChatId,
|
|
987
|
+
workspacePath: readRecordString(subscription, "workspacePath") || null
|
|
988
|
+
};
|
|
989
|
+
}
|
|
990
|
+
return subscriptions;
|
|
991
|
+
}
|
|
992
|
+
function readStringRecord(value) {
|
|
993
|
+
const record = readRecord(value);
|
|
994
|
+
const result = {};
|
|
995
|
+
for (const [key, recordValue] of Object.entries(record)) if (typeof recordValue === "string") result[key] = recordValue;
|
|
996
|
+
return result;
|
|
997
|
+
}
|
|
998
|
+
function readChatMessage(value) {
|
|
999
|
+
const record = readRecord(value);
|
|
1000
|
+
const chatId = readRecordString(record, "chatId");
|
|
1001
|
+
const id = readRecordString(record, "id");
|
|
1002
|
+
const role = readRecordString(record, "role");
|
|
1003
|
+
if (!chatId || !id || !isMessageRole(role)) return null;
|
|
1004
|
+
return {
|
|
1005
|
+
chatId,
|
|
1006
|
+
completedAt: readRecordString(record, "completedAt") || null,
|
|
1007
|
+
content: readRecordString(record, "content"),
|
|
1008
|
+
createdAt: readRecordString(record, "createdAt") || (/* @__PURE__ */ new Date()).toISOString(),
|
|
1009
|
+
id,
|
|
1010
|
+
itemId: readRecordString(record, "itemId") || null,
|
|
1011
|
+
kind: readMessageKind(readRecordString(record, "kind")),
|
|
1012
|
+
metadata: readRecord(record.metadata),
|
|
1013
|
+
rawPayload: readRecord(record.rawPayload),
|
|
1014
|
+
requestId: readRecordString(record, "requestId") || null,
|
|
1015
|
+
role,
|
|
1016
|
+
runId: readRecordString(record, "runId") || null,
|
|
1017
|
+
sequence: readNumber(record.sequence) ?? 0,
|
|
1018
|
+
status: readMessageStatus(readRecordString(record, "status")),
|
|
1019
|
+
turnId: readRecordString(record, "turnId") || null
|
|
1020
|
+
};
|
|
1021
|
+
}
|
|
1022
|
+
function serverRequestResponseFor(message, approved) {
|
|
1023
|
+
const method = readRecordString(readRecord(message.metadata), "serverRequestMethod");
|
|
1024
|
+
if (method === "item/permissions/requestApproval") return {
|
|
1025
|
+
kind: "permissions",
|
|
1026
|
+
result: {
|
|
1027
|
+
permissions: approved ? grantedPermissionsFromRequest(message) : {},
|
|
1028
|
+
scope: "turn"
|
|
1029
|
+
}
|
|
1030
|
+
};
|
|
1031
|
+
if (method === "item/tool/requestUserInput") return {
|
|
1032
|
+
kind: "userInput",
|
|
1033
|
+
result: { answers: {} }
|
|
1034
|
+
};
|
|
1035
|
+
return {
|
|
1036
|
+
decision: approved ? "accept" : "decline",
|
|
1037
|
+
kind: "approval",
|
|
1038
|
+
result: { decision: approved ? "accept" : "decline" }
|
|
1039
|
+
};
|
|
1040
|
+
}
|
|
1041
|
+
function grantedPermissionsFromRequest(message) {
|
|
1042
|
+
const requested = readRecord(readRecord(message.rawPayload).permissions);
|
|
1043
|
+
const granted = {};
|
|
1044
|
+
const network = readRecord(requested.network);
|
|
1045
|
+
const fileSystem = readRecord(requested.fileSystem);
|
|
1046
|
+
if (Object.keys(network).length) granted.network = network;
|
|
1047
|
+
if (Object.keys(fileSystem).length) granted.fileSystem = fileSystem;
|
|
1048
|
+
return granted;
|
|
1049
|
+
}
|
|
1050
|
+
function readUserInputQuestions(value) {
|
|
1051
|
+
const questions = readRecord(value).questions;
|
|
1052
|
+
if (!Array.isArray(questions)) return [];
|
|
1053
|
+
return questions.flatMap((questionValue) => {
|
|
1054
|
+
const question = readRecord(questionValue);
|
|
1055
|
+
const id = readRecordString(question, "id");
|
|
1056
|
+
if (!id) return [];
|
|
1057
|
+
return [{
|
|
1058
|
+
id,
|
|
1059
|
+
options: readUserInputOptions(question.options)
|
|
1060
|
+
}];
|
|
1061
|
+
});
|
|
1062
|
+
}
|
|
1063
|
+
function readUserInputOptions(value) {
|
|
1064
|
+
if (!Array.isArray(value)) return [];
|
|
1065
|
+
return value.flatMap((optionValue) => {
|
|
1066
|
+
const label = readRecordString(optionValue, "label");
|
|
1067
|
+
return label ? [{ label }] : [];
|
|
1068
|
+
});
|
|
1069
|
+
}
|
|
1070
|
+
function formatAssistantTailBlock(message) {
|
|
1071
|
+
return `Assistant:\n${normalizeTailText(message.content) || "(empty)"}`;
|
|
1072
|
+
}
|
|
1073
|
+
function isAssistantChatMessage(message) {
|
|
1074
|
+
return message.role === "ASSISTANT" && message.kind === "CHAT";
|
|
1075
|
+
}
|
|
1076
|
+
function shouldUpdateSubscriptionTailForMessage(message) {
|
|
1077
|
+
return isAssistantChatMessage(message);
|
|
1078
|
+
}
|
|
1079
|
+
function isMessageAtOrAfter(message, since) {
|
|
1080
|
+
if (message.status === "STREAMING") return true;
|
|
1081
|
+
const messageTime = Date.parse(message.createdAt);
|
|
1082
|
+
const completedTime = Date.parse(message.completedAt ?? "");
|
|
1083
|
+
const sinceTime = Date.parse(since);
|
|
1084
|
+
if (!Number.isFinite(sinceTime)) return true;
|
|
1085
|
+
if (Number.isFinite(completedTime) && completedTime >= sinceTime) return true;
|
|
1086
|
+
return Number.isFinite(messageTime) ? messageTime >= sinceTime : true;
|
|
1087
|
+
}
|
|
1088
|
+
function tailLines(lines, maxLines) {
|
|
1089
|
+
return lines.slice(-maxLines);
|
|
1090
|
+
}
|
|
1091
|
+
function normalizeTailText(value) {
|
|
1092
|
+
return value.trim().replace(/\r\n?/gu, "\n").replace(/\n{3,}/g, "\n\n");
|
|
1093
|
+
}
|
|
1094
|
+
function fitTelegramMessage(value, maxLength) {
|
|
1095
|
+
const normalized = value.trim();
|
|
1096
|
+
if (normalized.length <= maxLength) return normalized;
|
|
1097
|
+
return "...\n" + normalized.slice(-(maxLength - 4));
|
|
1098
|
+
}
|
|
1099
|
+
function compactLabel(value, maxLength) {
|
|
1100
|
+
const normalized = value.trim().replace(/\s+/g, " ");
|
|
1101
|
+
return normalized.length > maxLength ? `${normalized.slice(0, maxLength - 1)}...` : normalized || "Untitled";
|
|
1102
|
+
}
|
|
1103
|
+
function displayNameForTelegramUser(user) {
|
|
1104
|
+
return [user.first_name, user.last_name].filter(Boolean).join(" ") || user.username || String(user.id);
|
|
1105
|
+
}
|
|
1106
|
+
function createPairingCode() {
|
|
1107
|
+
return randomToken(4).toUpperCase();
|
|
1108
|
+
}
|
|
1109
|
+
function randomToken(bytes) {
|
|
1110
|
+
const length = Math.max(8, bytes);
|
|
1111
|
+
let token = "";
|
|
1112
|
+
while (token.length < length) token += randomBytes(bytes).toString("base64url").replace(/[^a-z0-9]/giu, "");
|
|
1113
|
+
return token.slice(0, length);
|
|
1114
|
+
}
|
|
1115
|
+
function readRecord(value) {
|
|
1116
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
1117
|
+
}
|
|
1118
|
+
function readRecordString(value, key) {
|
|
1119
|
+
const recordValue = readRecord(value)[key];
|
|
1120
|
+
return typeof recordValue === "string" ? recordValue.trim() : "";
|
|
1121
|
+
}
|
|
1122
|
+
function readNumber(value) {
|
|
1123
|
+
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
1124
|
+
}
|
|
1125
|
+
function isMessageRole(value) {
|
|
1126
|
+
return value === "USER" || value === "ASSISTANT" || value === "SYSTEM" || value === "TOOL";
|
|
1127
|
+
}
|
|
1128
|
+
function readMessageKind(value) {
|
|
1129
|
+
return value === "THINKING" || value === "TOOL_ACTIVITY" || value === "COMMAND_EXECUTION" || value === "FILE_CHANGE" || value === "PLAN" || value === "APPROVAL" || value === "USER_INPUT_PROMPT" || value === "ERROR" ? value : "CHAT";
|
|
1130
|
+
}
|
|
1131
|
+
function readMessageStatus(value) {
|
|
1132
|
+
return value === "PENDING" || value === "STREAMING" || value === "FAILED" ? value : "COMPLETED";
|
|
1133
|
+
}
|
|
1134
|
+
function readErrorMessage$1(error) {
|
|
1135
|
+
return error instanceof Error ? error.message : "Telegram action failed.";
|
|
1136
|
+
}
|
|
1137
|
+
function readTelegramPollingError(error) {
|
|
1138
|
+
const message = readErrorMessage$1(error);
|
|
1139
|
+
return message.includes("409") ? "Telegram polling conflict. Stop the other bot instance, then restart this plugin." : message || "Telegram polling stopped.";
|
|
1140
|
+
}
|
|
1141
|
+
//#endregion
|
|
1142
|
+
//#region app/server/plugins/registry.server.ts
|
|
1143
|
+
var registrations = new Map([[telegramPluginRegistration.definition.id, telegramPluginRegistration]]);
|
|
1144
|
+
function listPluginRegistrations() {
|
|
1145
|
+
return [...registrations.values()];
|
|
1146
|
+
}
|
|
1147
|
+
function readPluginRegistration(pluginId) {
|
|
1148
|
+
const registration = registrations.get(pluginId);
|
|
1149
|
+
if (!registration) throw new Error(`Unknown plugin: ${pluginId}`);
|
|
1150
|
+
return registration;
|
|
1151
|
+
}
|
|
1152
|
+
//#endregion
|
|
1153
|
+
//#region app/server/plugins/storage.server.ts
|
|
1154
|
+
async function readPluginSetting(pluginId, defaults = {}) {
|
|
1155
|
+
await ensureDatabase();
|
|
1156
|
+
const row = (await prisma.$queryRawUnsafe(`SELECT "pluginId", "enabled", "settings", "secrets" FROM "PluginSetting" WHERE "pluginId" = ? LIMIT 1`, pluginId))[0];
|
|
1157
|
+
if (!row) return {
|
|
1158
|
+
enabled: false,
|
|
1159
|
+
pluginId,
|
|
1160
|
+
secrets: {},
|
|
1161
|
+
settings: defaults
|
|
1162
|
+
};
|
|
1163
|
+
return {
|
|
1164
|
+
enabled: row.enabled === true || row.enabled === 1,
|
|
1165
|
+
pluginId: row.pluginId,
|
|
1166
|
+
secrets: readJsonObject(row.secrets),
|
|
1167
|
+
settings: {
|
|
1168
|
+
...defaults,
|
|
1169
|
+
...readJsonObject(row.settings)
|
|
1170
|
+
}
|
|
1171
|
+
};
|
|
1172
|
+
}
|
|
1173
|
+
async function writePluginSetting(record) {
|
|
1174
|
+
await ensureDatabase();
|
|
1175
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
1176
|
+
await prisma.$executeRawUnsafe(`INSERT INTO "PluginSetting" ("pluginId", "enabled", "settings", "secrets", "createdAt", "updatedAt")
|
|
1177
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
1178
|
+
ON CONFLICT("pluginId") DO UPDATE SET
|
|
1179
|
+
"enabled" = excluded."enabled",
|
|
1180
|
+
"settings" = excluded."settings",
|
|
1181
|
+
"secrets" = excluded."secrets",
|
|
1182
|
+
"updatedAt" = excluded."updatedAt"`, record.pluginId, record.enabled ? 1 : 0, JSON.stringify(record.settings), JSON.stringify(record.secrets), timestamp, timestamp);
|
|
1183
|
+
return record;
|
|
1184
|
+
}
|
|
1185
|
+
async function readPluginState(pluginId) {
|
|
1186
|
+
await ensureDatabase();
|
|
1187
|
+
return readJsonObject((await prisma.$queryRawUnsafe(`SELECT "pluginId", "state" FROM "PluginState" WHERE "pluginId" = ? LIMIT 1`, pluginId))[0]?.state);
|
|
1188
|
+
}
|
|
1189
|
+
async function writePluginState(pluginId, state) {
|
|
1190
|
+
await ensureDatabase();
|
|
1191
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
1192
|
+
await prisma.$executeRawUnsafe(`INSERT INTO "PluginState" ("pluginId", "state", "createdAt", "updatedAt")
|
|
1193
|
+
VALUES (?, ?, ?, ?)
|
|
1194
|
+
ON CONFLICT("pluginId") DO UPDATE SET
|
|
1195
|
+
"state" = excluded."state",
|
|
1196
|
+
"updatedAt" = excluded."updatedAt"`, pluginId, JSON.stringify(state), timestamp, timestamp);
|
|
1197
|
+
return state;
|
|
1198
|
+
}
|
|
1199
|
+
async function updatePluginState(pluginId, updater) {
|
|
1200
|
+
return writePluginState(pluginId, await updater(await readPluginState(pluginId)));
|
|
1201
|
+
}
|
|
1202
|
+
function readJsonObject(value) {
|
|
1203
|
+
if (typeof value === "string") try {
|
|
1204
|
+
return readJsonObject(JSON.parse(value));
|
|
1205
|
+
} catch {
|
|
1206
|
+
return {};
|
|
1207
|
+
}
|
|
1208
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return {};
|
|
1209
|
+
return value;
|
|
1210
|
+
}
|
|
1211
|
+
//#endregion
|
|
1212
|
+
//#region app/server/plugins/manager.server.ts
|
|
1213
|
+
var manager_server_exports = /* @__PURE__ */ __exportAll({
|
|
1214
|
+
createPluginContext: () => createPluginContext,
|
|
1215
|
+
getPluginStatus: () => getPluginStatus,
|
|
1216
|
+
reconcilePlugins: () => reconcilePlugins,
|
|
1217
|
+
restartPlugin: () => restartPlugin,
|
|
1218
|
+
setPluginStatus: () => setPluginStatus,
|
|
1219
|
+
startPluginRuntimeManager: () => startPluginRuntimeManager,
|
|
1220
|
+
stopPlugin: () => stopPlugin
|
|
1221
|
+
});
|
|
1222
|
+
var globalForPlugins = globalThis;
|
|
1223
|
+
var managerState = globalForPlugins.pockcodePluginRuntimeManager ?? {
|
|
1224
|
+
activePlugins: /* @__PURE__ */ new Map(),
|
|
1225
|
+
pluginStatuses: /* @__PURE__ */ new Map(),
|
|
1226
|
+
unsubscribeProviderEvents: null
|
|
1227
|
+
};
|
|
1228
|
+
globalForPlugins.pockcodePluginRuntimeManager = managerState;
|
|
1229
|
+
var activePlugins = managerState.activePlugins;
|
|
1230
|
+
var pluginStatuses = managerState.pluginStatuses;
|
|
1231
|
+
function startPluginRuntimeManager() {
|
|
1232
|
+
if (managerState.unsubscribeProviderEvents) managerState.unsubscribeProviderEvents();
|
|
1233
|
+
managerState.unsubscribeProviderEvents = onProviderEvent((event) => {
|
|
1234
|
+
dispatchProviderEvent(event).catch(() => void 0);
|
|
1235
|
+
});
|
|
1236
|
+
reconcilePlugins().catch((error) => {
|
|
1237
|
+
for (const registration of listPluginRegistrations()) setPluginStatus(registration.definition.id, {
|
|
1238
|
+
state: "error",
|
|
1239
|
+
message: readErrorMessage(error)
|
|
1240
|
+
});
|
|
1241
|
+
});
|
|
1242
|
+
}
|
|
1243
|
+
async function reconcilePlugins() {
|
|
1244
|
+
for (const registration of listPluginRegistrations()) await restartPlugin(registration.definition.id);
|
|
1245
|
+
}
|
|
1246
|
+
async function restartPlugin(pluginId) {
|
|
1247
|
+
const registration = readPluginRegistration(pluginId);
|
|
1248
|
+
await stopPlugin(pluginId);
|
|
1249
|
+
const setting = await readPluginSetting(pluginId, registration.definition.defaultSettings);
|
|
1250
|
+
if (!setting.enabled) {
|
|
1251
|
+
setPluginStatus(pluginId, {
|
|
1252
|
+
state: "disabled",
|
|
1253
|
+
message: null
|
|
1254
|
+
});
|
|
1255
|
+
return;
|
|
1256
|
+
}
|
|
1257
|
+
setPluginStatus(pluginId, {
|
|
1258
|
+
state: "starting",
|
|
1259
|
+
message: null
|
|
1260
|
+
});
|
|
1261
|
+
const runtime = registration.runtime();
|
|
1262
|
+
activePlugins.set(pluginId, { runtime });
|
|
1263
|
+
try {
|
|
1264
|
+
await runtime.start(createPluginContext(pluginId, setting));
|
|
1265
|
+
if (getPluginStatus(pluginId).state === "starting") setPluginStatus(pluginId, {
|
|
1266
|
+
state: "running",
|
|
1267
|
+
message: null
|
|
1268
|
+
});
|
|
1269
|
+
} catch (error) {
|
|
1270
|
+
activePlugins.delete(pluginId);
|
|
1271
|
+
setPluginStatus(pluginId, {
|
|
1272
|
+
state: "error",
|
|
1273
|
+
message: readErrorMessage(error)
|
|
1274
|
+
});
|
|
1275
|
+
await Promise.resolve(runtime.stop()).catch(() => void 0);
|
|
1276
|
+
}
|
|
1277
|
+
}
|
|
1278
|
+
async function stopPlugin(pluginId) {
|
|
1279
|
+
const active = activePlugins.get(pluginId);
|
|
1280
|
+
activePlugins.delete(pluginId);
|
|
1281
|
+
if (!active) return;
|
|
1282
|
+
try {
|
|
1283
|
+
await active.runtime.stop();
|
|
1284
|
+
} catch (error) {
|
|
1285
|
+
setPluginStatus(pluginId, {
|
|
1286
|
+
state: "error",
|
|
1287
|
+
message: readErrorMessage(error)
|
|
1288
|
+
});
|
|
1289
|
+
}
|
|
1290
|
+
}
|
|
1291
|
+
function getPluginStatus(pluginId) {
|
|
1292
|
+
return pluginStatuses.get(pluginId) ?? {
|
|
1293
|
+
state: "disabled",
|
|
1294
|
+
message: null,
|
|
1295
|
+
updatedAt: null
|
|
1296
|
+
};
|
|
1297
|
+
}
|
|
1298
|
+
function setPluginStatus(pluginId, status) {
|
|
1299
|
+
pluginStatuses.set(pluginId, {
|
|
1300
|
+
...status,
|
|
1301
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1302
|
+
});
|
|
1303
|
+
}
|
|
1304
|
+
function createPluginContext(pluginId, setting) {
|
|
1305
|
+
return {
|
|
1306
|
+
async getState() {
|
|
1307
|
+
return readPluginState(pluginId);
|
|
1308
|
+
},
|
|
1309
|
+
pluginId,
|
|
1310
|
+
secrets: setting.secrets,
|
|
1311
|
+
async setState(state) {
|
|
1312
|
+
await writePluginState(pluginId, state);
|
|
1313
|
+
},
|
|
1314
|
+
setStatus(status) {
|
|
1315
|
+
setPluginStatus(pluginId, status);
|
|
1316
|
+
},
|
|
1317
|
+
settings: setting.settings,
|
|
1318
|
+
updateState(updater) {
|
|
1319
|
+
return updatePluginState(pluginId, updater);
|
|
1320
|
+
}
|
|
1321
|
+
};
|
|
1322
|
+
}
|
|
1323
|
+
async function dispatchProviderEvent(event) {
|
|
1324
|
+
await Promise.all([...activePlugins.values()].map(async (active) => {
|
|
1325
|
+
try {
|
|
1326
|
+
await active.runtime.handleProviderEvent?.(event);
|
|
1327
|
+
} catch {}
|
|
1328
|
+
}));
|
|
1329
|
+
}
|
|
1330
|
+
function readErrorMessage(error) {
|
|
1331
|
+
return error instanceof Error ? error.message : "Plugin failed.";
|
|
1332
|
+
}
|
|
1333
|
+
//#endregion
|
|
1334
|
+
export { readPluginSetting as a, listPluginRegistrations as c, deleteWorkspaceHistory as d, listWorkspaceHistory as f, restartPlugin as i, readPluginRegistration as l, getPluginStatus as n, readPluginState as o, saveWorkspaceHistory as p, manager_server_exports as r, writePluginSetting as s, createPluginContext as t, verifyTelegramBotToken as u };
|