@llblab/pi-telegram 0.19.3 → 0.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +5 -3
- package/BACKLOG.md +15 -2
- package/CHANGELOG.md +9 -3
- package/README.md +57 -57
- package/docs/architecture.md +4 -1
- package/docs/public-api.md +1 -1
- package/index.ts +67 -12
- package/lib/bindings.ts +61 -11
- package/lib/bus-follower.ts +27 -8
- package/lib/bus-leader.ts +26 -42
- package/lib/bus.ts +19 -22
- package/lib/commands.ts +41 -8
- package/lib/config.ts +165 -26
- package/lib/locks.ts +78 -25
- package/lib/{runtime-log.ts → logs.ts} +58 -27
- package/lib/media.ts +97 -4
- package/lib/outbound-buttons.ts +1 -1
- package/lib/outbound.ts +5 -8
- package/lib/paths.ts +77 -0
- package/lib/prompt-templates.ts +3 -1
- package/lib/prompts.ts +1 -1
- package/lib/queue.ts +32 -1
- package/lib/routing.ts +106 -41
- package/lib/status.ts +6 -1
- package/lib/sync.ts +22 -17
- package/lib/telegram-api.ts +13 -14
- package/lib/threads.ts +142 -55
- package/lib/turns.ts +47 -13
- package/package.json +3 -3
- /package/{banner.png → screenshot.png} +0 -0
package/lib/bindings.ts
CHANGED
|
@@ -73,8 +73,8 @@ export function registerTelegramCommandsAndTools({
|
|
|
73
73
|
getDefaultChatId,
|
|
74
74
|
getDefaultTarget,
|
|
75
75
|
canSendDirect,
|
|
76
|
-
updateStatus,
|
|
77
76
|
recordRuntimeEvent,
|
|
77
|
+
updateStatus,
|
|
78
78
|
}: TelegramCommandsAndToolsBindingDeps): void {
|
|
79
79
|
OutboundAttachments.registerTelegramOutboundAttachmentTool(pi, {
|
|
80
80
|
getActiveTurn: activeTurnRuntime.get,
|
|
@@ -96,22 +96,72 @@ export function registerTelegramCommandsAndTools({
|
|
|
96
96
|
});
|
|
97
97
|
Prompts.registerTelegramHelpTool(pi);
|
|
98
98
|
Commands.registerTelegramBridgeCommands(pi, {
|
|
99
|
-
promptForConfig:
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
99
|
+
promptForConfig: async (ctx, profileName) => {
|
|
100
|
+
const nextProfileName = profileName ?? undefined;
|
|
101
|
+
if (profileName && !Config.isValidTelegramProfileName(profileName)) {
|
|
102
|
+
ctx.ui.notify(`Invalid Telegram profile name: ${profileName}`, "error");
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
const previousProfileName = configStore.getActiveProfileName();
|
|
106
|
+
if (previousProfileName !== nextProfileName) {
|
|
107
|
+
await (stopPolling ?? lockedPollingRuntime.stop)();
|
|
108
|
+
}
|
|
109
|
+
if (!profileName) {
|
|
110
|
+
configStore.activateProfile(undefined);
|
|
111
|
+
} else {
|
|
112
|
+
const storedConfig = configStore.getStoredConfig();
|
|
113
|
+
if (!storedConfig.profiles?.[profileName]) {
|
|
114
|
+
configStore.set({
|
|
115
|
+
...storedConfig,
|
|
116
|
+
profiles: {
|
|
117
|
+
...(storedConfig.profiles ?? {}),
|
|
118
|
+
[profileName]: { botToken: "" },
|
|
119
|
+
},
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
configStore.activateProfile(profileName);
|
|
123
|
+
}
|
|
124
|
+
const runSetup = Setup.createTelegramSetupPromptRuntime({
|
|
125
|
+
getConfig: configStore.get,
|
|
126
|
+
setConfig: configStore.set,
|
|
127
|
+
setupGuard: setup,
|
|
128
|
+
getMe: TelegramApi.fetchTelegramBotIdentity,
|
|
129
|
+
persistConfig,
|
|
130
|
+
startPolling: lockedPollingRuntime.start,
|
|
131
|
+
updateStatus,
|
|
132
|
+
recordRuntimeEvent,
|
|
133
|
+
});
|
|
134
|
+
await runSetup(ctx);
|
|
135
|
+
if (profileName) {
|
|
136
|
+
ctx.ui.notify(`Profile "${profileName}" saved and connected.`, "info");
|
|
137
|
+
}
|
|
138
|
+
},
|
|
109
139
|
getStatusLines,
|
|
110
140
|
reloadConfig: configStore.load,
|
|
111
141
|
hasBotToken: configStore.hasBotToken,
|
|
112
142
|
startPolling: lockedPollingRuntime.start,
|
|
113
143
|
stopPolling: stopPolling ?? lockedPollingRuntime.stop,
|
|
114
144
|
updateStatus,
|
|
145
|
+
getProfileNames: () =>
|
|
146
|
+
Config.getTelegramProfileNames(configStore.getStoredConfig()),
|
|
147
|
+
activateDefaultProfileConfig: async () => {
|
|
148
|
+
await configStore.load();
|
|
149
|
+
const previousProfileName = configStore.getActiveProfileName();
|
|
150
|
+
if (previousProfileName) {
|
|
151
|
+
await (stopPolling ?? lockedPollingRuntime.stop)();
|
|
152
|
+
}
|
|
153
|
+
configStore.activateProfile(undefined);
|
|
154
|
+
},
|
|
155
|
+
activateProfileConfig: async (_ctx, profileName) => {
|
|
156
|
+
await configStore.load();
|
|
157
|
+
if (!Config.isValidTelegramProfileName(profileName)) return false;
|
|
158
|
+
const previousProfileName = configStore.getActiveProfileName();
|
|
159
|
+
if (previousProfileName !== profileName) {
|
|
160
|
+
await (stopPolling ?? lockedPollingRuntime.stop)();
|
|
161
|
+
}
|
|
162
|
+
if (!configStore.activateProfile(profileName)) return false;
|
|
163
|
+
return true;
|
|
164
|
+
},
|
|
115
165
|
});
|
|
116
166
|
}
|
|
117
167
|
|
package/lib/bus-follower.ts
CHANGED
|
@@ -90,6 +90,7 @@ export interface TelegramBusFollowerSessionReplacementSuspenderDeps {
|
|
|
90
90
|
registrationState: Pick<TelegramBusFollowerRegistrationState, "isRegistered">;
|
|
91
91
|
instanceId: string;
|
|
92
92
|
suspendPolling: () => Promise<void>;
|
|
93
|
+
onFollowerSessionDisconnect?: () => Promise<void> | void;
|
|
93
94
|
recordRuntimeEvent: (
|
|
94
95
|
category: string,
|
|
95
96
|
error: unknown,
|
|
@@ -387,24 +388,42 @@ function isTelegramStaleContextError(error: unknown): boolean {
|
|
|
387
388
|
);
|
|
388
389
|
}
|
|
389
390
|
|
|
391
|
+
export async function markTelegramFollowerThreadStaleForSessionDisconnect(input: {
|
|
392
|
+
topicTargetStore: Threads.TelegramTopicTargetStore;
|
|
393
|
+
target: TelegramTarget;
|
|
394
|
+
}): Promise<void> {
|
|
395
|
+
await input.topicTargetStore.load();
|
|
396
|
+
const record = input.topicTargetStore.list().find(
|
|
397
|
+
(candidate) =>
|
|
398
|
+
candidate.target.chatId === input.target.chatId &&
|
|
399
|
+
candidate.target.threadId === input.target.threadId,
|
|
400
|
+
);
|
|
401
|
+
const marked = input.topicTargetStore.markStaleByTarget(
|
|
402
|
+
input.target,
|
|
403
|
+
"unknown",
|
|
404
|
+
"Follower session reloaded before reconnect.",
|
|
405
|
+
);
|
|
406
|
+
const forgotIdentity = record?.profileKey
|
|
407
|
+
? input.topicTargetStore.forgetIdentityByProfileKey(record.profileKey)
|
|
408
|
+
: false;
|
|
409
|
+
if (marked || forgotIdentity) await input.topicTargetStore.persist();
|
|
410
|
+
}
|
|
411
|
+
|
|
390
412
|
export function createTelegramBusFollowerSessionReplacementSuspender(
|
|
391
413
|
deps: TelegramBusFollowerSessionReplacementSuspenderDeps,
|
|
392
414
|
): () => Promise<void> {
|
|
393
415
|
return async () => {
|
|
394
416
|
if (deps.registrationState.isRegistered()) {
|
|
395
|
-
setTelegramFollowerSessionHandoff(
|
|
396
|
-
|
|
397
|
-
instanceId: deps.instanceId,
|
|
398
|
-
createdAtMs: (deps.getNowMs ?? Date.now)(),
|
|
399
|
-
});
|
|
417
|
+
setTelegramFollowerSessionHandoff(undefined);
|
|
418
|
+
await deps.onFollowerSessionDisconnect?.();
|
|
400
419
|
deps.recordRuntimeEvent(
|
|
401
420
|
"bus",
|
|
402
|
-
"Telegram follower registration
|
|
421
|
+
"Telegram follower registration stopped for session replacement",
|
|
403
422
|
{
|
|
404
|
-
phase: "follower-session-
|
|
423
|
+
phase: "follower-session-disconnect",
|
|
424
|
+
instanceId: deps.instanceId,
|
|
405
425
|
},
|
|
406
426
|
);
|
|
407
|
-
return;
|
|
408
427
|
}
|
|
409
428
|
await deps.suspendPolling();
|
|
410
429
|
};
|
package/lib/bus-leader.ts
CHANGED
|
@@ -54,6 +54,7 @@ export interface TelegramBusLeaderTargetProvisionerDeps<TContext> {
|
|
|
54
54
|
getAllowedUserId: () => number | undefined;
|
|
55
55
|
instanceId: string;
|
|
56
56
|
getCwd?: (ctx: TContext) => string | undefined;
|
|
57
|
+
getTelegramProfile?: () => string | undefined;
|
|
57
58
|
shouldForceFreshUnnamed?: () => boolean;
|
|
58
59
|
topicTargetStore: Threads.TelegramTopicTargetStore;
|
|
59
60
|
callApi: <TResponse>(
|
|
@@ -315,6 +316,10 @@ export function createTelegramBusFollowerTargetProvisioner(
|
|
|
315
316
|
(TelegramTarget & { slot?: string; threadName?: string }) | undefined
|
|
316
317
|
> {
|
|
317
318
|
const getNowMs = deps.getNowMs ?? Date.now;
|
|
319
|
+
const pendingRegistrations = new Map<
|
|
320
|
+
string,
|
|
321
|
+
Promise<(TelegramTarget & { slot?: string; threadName?: string }) | undefined>
|
|
322
|
+
>();
|
|
318
323
|
return async (registration) => {
|
|
319
324
|
const registrationStartedAtMs = Date.now();
|
|
320
325
|
const chatId = deps.getAllowedUserId();
|
|
@@ -337,6 +342,9 @@ export function createTelegramBusFollowerTargetProvisioner(
|
|
|
337
342
|
registration.profileKey ?? `manual:${registration.instanceId}`;
|
|
338
343
|
const followerOwner =
|
|
339
344
|
Threads.getTelegramThreadOwnerFromProfileKey(followerProfileKey);
|
|
345
|
+
const registrationKey = followerProfileKey || registration.instanceId;
|
|
346
|
+
const pendingRegistration = pendingRegistrations.get(registrationKey);
|
|
347
|
+
if (pendingRegistration) return pendingRegistration;
|
|
340
348
|
const provisionTarget = async () => {
|
|
341
349
|
deps.onProvisioningStart?.();
|
|
342
350
|
try {
|
|
@@ -356,6 +364,9 @@ export function createTelegramBusFollowerTargetProvisioner(
|
|
|
356
364
|
deps.onProvisioningEnd?.();
|
|
357
365
|
}
|
|
358
366
|
};
|
|
367
|
+
const runRegistration = async (): Promise<
|
|
368
|
+
(TelegramTarget & { slot?: string; threadName?: string }) | undefined
|
|
369
|
+
> => {
|
|
359
370
|
let result = await provisionTarget();
|
|
360
371
|
deps.setSyncState(
|
|
361
372
|
Sync.markTelegramSyncSliceFresh(deps.getSyncState(), "target-bindings", {
|
|
@@ -363,48 +374,14 @@ export function createTelegramBusFollowerTargetProvisioner(
|
|
|
363
374
|
action: "follower-register",
|
|
364
375
|
}),
|
|
365
376
|
);
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
let connectedAnnouncementSent = false;
|
|
374
|
-
if (result.reused && connectedAnnouncement) {
|
|
375
|
-
try {
|
|
376
|
-
await deps.callApi("sendMessage", {
|
|
377
|
-
chat_id: connectedAnnouncement.target.chatId,
|
|
378
|
-
message_thread_id: connectedAnnouncement.target.threadId,
|
|
379
|
-
text: connectedAnnouncement.text,
|
|
380
|
-
parse_mode: connectedAnnouncement.parseMode,
|
|
381
|
-
});
|
|
382
|
-
connectedAnnouncementSent = true;
|
|
383
|
-
} catch (error) {
|
|
384
|
-
deps.recordRuntimeEvent("telegram", error, {
|
|
385
|
-
phase: "follower-topic-reuse-probe",
|
|
386
|
-
instanceId: registration.instanceId,
|
|
387
|
-
chatId: result.target.chatId,
|
|
388
|
-
threadId: result.target.threadId,
|
|
377
|
+
const connectedAnnouncement = result.reused
|
|
378
|
+
? undefined
|
|
379
|
+
: createTelegramBusInstanceLifecycleAnnouncement({
|
|
380
|
+
target: result.target,
|
|
381
|
+
threadName: result.record.threadName,
|
|
382
|
+
slot: result.record.slot,
|
|
383
|
+
state: "connected",
|
|
389
384
|
});
|
|
390
|
-
if (Threads.isTelegramTopicTargetStaleError(error)) {
|
|
391
|
-
deps.topicTargetStore.markStaleByTarget(
|
|
392
|
-
result.target,
|
|
393
|
-
"deleted",
|
|
394
|
-
"Follower registration found the reusable thread target stale.",
|
|
395
|
-
);
|
|
396
|
-
await deps.topicTargetStore.persist();
|
|
397
|
-
result = await provisionTarget();
|
|
398
|
-
connectedAnnouncement = createTelegramBusInstanceLifecycleAnnouncement({
|
|
399
|
-
target: result.target,
|
|
400
|
-
threadName: result.record.threadName,
|
|
401
|
-
slot: result.record.slot,
|
|
402
|
-
state: "connected",
|
|
403
|
-
});
|
|
404
|
-
connectedAnnouncementSent = false;
|
|
405
|
-
}
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
385
|
recordSlowTelegramBusFollowerRegistrationStep(deps, {
|
|
409
386
|
phase: "follower-register-critical",
|
|
410
387
|
elapsedMs: Date.now() - registrationStartedAtMs,
|
|
@@ -414,7 +391,7 @@ export function createTelegramBusFollowerTargetProvisioner(
|
|
|
414
391
|
});
|
|
415
392
|
scheduleTelegramBusLeaderBackgroundTask(async () => {
|
|
416
393
|
const backgroundStartedAtMs = Date.now();
|
|
417
|
-
if (connectedAnnouncement
|
|
394
|
+
if (connectedAnnouncement) {
|
|
418
395
|
try {
|
|
419
396
|
await deps.callApi("sendMessage", {
|
|
420
397
|
chat_id: connectedAnnouncement.target.chatId,
|
|
@@ -486,6 +463,12 @@ export function createTelegramBusFollowerTargetProvisioner(
|
|
|
486
463
|
slot: result.record.slot,
|
|
487
464
|
threadName: result.record.threadName,
|
|
488
465
|
};
|
|
466
|
+
};
|
|
467
|
+
const registrationPromise = runRegistration().finally(() => {
|
|
468
|
+
pendingRegistrations.delete(registrationKey);
|
|
469
|
+
});
|
|
470
|
+
pendingRegistrations.set(registrationKey, registrationPromise);
|
|
471
|
+
return registrationPromise;
|
|
489
472
|
};
|
|
490
473
|
}
|
|
491
474
|
|
|
@@ -516,6 +499,7 @@ export function createTelegramBusLeaderTargetProvisioner<TContext>(
|
|
|
516
499
|
getAllowedUserId: deps.getAllowedUserId,
|
|
517
500
|
instanceId: deps.instanceId,
|
|
518
501
|
cwd: deps.getCwd?.(ctx),
|
|
502
|
+
telegramProfile: deps.getTelegramProfile?.(),
|
|
519
503
|
forceFreshUnnamed: deps.shouldForceFreshUnnamed?.(),
|
|
520
504
|
getNowMs,
|
|
521
505
|
getCurrentLeaderEpoch: deps.getCurrentLeaderEpoch,
|
package/lib/bus.ts
CHANGED
|
@@ -13,8 +13,8 @@ import {
|
|
|
13
13
|
type Server,
|
|
14
14
|
type Socket,
|
|
15
15
|
} from "node:net";
|
|
16
|
-
import {
|
|
17
|
-
import { dirname
|
|
16
|
+
import { platform as getPlatform } from "node:os";
|
|
17
|
+
import { dirname } from "node:path";
|
|
18
18
|
|
|
19
19
|
import {
|
|
20
20
|
classifyTelegramBusTransportError,
|
|
@@ -30,21 +30,16 @@ import {
|
|
|
30
30
|
type TelegramBusTransportRetryPolicy,
|
|
31
31
|
} from "./bus-transport.ts";
|
|
32
32
|
import type { TelegramTarget } from "./target.ts";
|
|
33
|
+
import { resolveAgentDir } from "./paths.ts";
|
|
33
34
|
|
|
34
35
|
export type TelegramBusRole = "leader" | "follower";
|
|
35
36
|
|
|
36
|
-
function getAgentDir(): string {
|
|
37
|
-
return process.env.PI_CODING_AGENT_DIR
|
|
38
|
-
? resolve(process.env.PI_CODING_AGENT_DIR)
|
|
39
|
-
: join(homedir(), ".pi", "agent");
|
|
40
|
-
}
|
|
41
|
-
|
|
42
37
|
export function createTelegramBusAuthSecret(): string {
|
|
43
38
|
return randomBytes(32).toString("base64url");
|
|
44
39
|
}
|
|
45
40
|
|
|
46
41
|
export function getTelegramBusSocketPath(
|
|
47
|
-
agentDir =
|
|
42
|
+
agentDir = resolveAgentDir(),
|
|
48
43
|
platform = getPlatform(),
|
|
49
44
|
): string {
|
|
50
45
|
return getTelegramBusLeaderEndpoint({ agentDir, platform });
|
|
@@ -52,7 +47,7 @@ export function getTelegramBusSocketPath(
|
|
|
52
47
|
|
|
53
48
|
export function getTelegramBusFollowerSocketPath(
|
|
54
49
|
instanceId: string,
|
|
55
|
-
agentDir =
|
|
50
|
+
agentDir = resolveAgentDir(),
|
|
56
51
|
platform = getPlatform(),
|
|
57
52
|
): string {
|
|
58
53
|
return getTelegramBusFollowerEndpoint({ agentDir, platform, instanceId });
|
|
@@ -155,7 +150,9 @@ export function isTelegramFollowerApiCallAllowed(input: {
|
|
|
155
150
|
const messageId = (body as Record<string, unknown>).message_id;
|
|
156
151
|
const parsedMessageId =
|
|
157
152
|
typeof messageId === "number" ? messageId : Number(messageId);
|
|
158
|
-
return
|
|
153
|
+
return (
|
|
154
|
+
Number.isInteger(parsedMessageId) && matchesId(messageId, parsedMessageId)
|
|
155
|
+
);
|
|
159
156
|
};
|
|
160
157
|
const isBotCommandRegistration = (body: unknown): boolean => {
|
|
161
158
|
if (!body || typeof body !== "object" || Array.isArray(body)) return false;
|
|
@@ -185,7 +182,8 @@ export function isTelegramFollowerApiCallAllowed(input: {
|
|
|
185
182
|
if (apiMethod === "getMe") return true;
|
|
186
183
|
if (apiMethod === "setMyCommands")
|
|
187
184
|
return isBotCommandRegistration(input.args[1]);
|
|
188
|
-
if (apiMethod === "sendChatAction")
|
|
185
|
+
if (apiMethod === "sendChatAction")
|
|
186
|
+
return isTargetChatScoped(input.args[1]);
|
|
189
187
|
if (apiMethod === "deleteMessage" || apiMethod === "editMessageText") {
|
|
190
188
|
return isTargetMessageScoped(input.args[1]);
|
|
191
189
|
}
|
|
@@ -351,9 +349,7 @@ export interface TelegramBusLocalServerDeps {
|
|
|
351
349
|
handleEnvelope: (
|
|
352
350
|
envelope: TelegramBusEnvelope,
|
|
353
351
|
) =>
|
|
354
|
-
|
|
355
|
-
| TelegramBusEnvelope
|
|
356
|
-
| undefined;
|
|
352
|
+
Promise<TelegramBusEnvelope | undefined> | TelegramBusEnvelope | undefined;
|
|
357
353
|
recordTransportEvent?: TelegramBusTransportEventRecorder;
|
|
358
354
|
}
|
|
359
355
|
|
|
@@ -454,7 +450,9 @@ export function createTelegramBusForeignOwnedUpdateForwarder<
|
|
|
454
450
|
|
|
455
451
|
export interface TelegramBusFollowerThreadRestoreHandlerDeps {
|
|
456
452
|
followerRegistry: Pick<TelegramBusFollowerRegistry, "get" | "register">;
|
|
457
|
-
followerTargetController: ReturnType<
|
|
453
|
+
followerTargetController: ReturnType<
|
|
454
|
+
typeof createTelegramBusFollowerTargetController
|
|
455
|
+
>;
|
|
458
456
|
onRestored?: () => void;
|
|
459
457
|
}
|
|
460
458
|
|
|
@@ -517,11 +515,7 @@ export function createTelegramBusFollowerThreadRestoreHandler(
|
|
|
517
515
|
target: TelegramTarget & { threadId: number };
|
|
518
516
|
oldTarget?: TelegramTarget & { threadId: number };
|
|
519
517
|
}) => Promise<boolean> {
|
|
520
|
-
return async ({
|
|
521
|
-
record,
|
|
522
|
-
target,
|
|
523
|
-
oldTarget,
|
|
524
|
-
}) => {
|
|
518
|
+
return async ({ record, target, oldTarget }) => {
|
|
525
519
|
if (!record.instanceId) return false;
|
|
526
520
|
const follower = deps.followerRegistry.get(record.instanceId);
|
|
527
521
|
if (!follower) return false;
|
|
@@ -637,7 +631,10 @@ export function createTelegramBusLocalServer(
|
|
|
637
631
|
activeServer.close(() => resolve()),
|
|
638
632
|
);
|
|
639
633
|
}
|
|
640
|
-
if (
|
|
634
|
+
if (
|
|
635
|
+
!isTelegramBusPipePath(deps.socketPath) &&
|
|
636
|
+
existsSync(deps.socketPath)
|
|
637
|
+
) {
|
|
641
638
|
unlinkSync(deps.socketPath);
|
|
642
639
|
}
|
|
643
640
|
deps.recordTransportEvent?.(
|
package/lib/commands.ts
CHANGED
|
@@ -295,7 +295,7 @@ export interface TelegramBridgeCommandStartPollingResult {
|
|
|
295
295
|
}
|
|
296
296
|
|
|
297
297
|
export interface TelegramBridgeCommandRegistrationDeps {
|
|
298
|
-
promptForConfig: (ctx: ExtensionCommandContext) => Promise<void>;
|
|
298
|
+
promptForConfig: (ctx: ExtensionCommandContext, profileName?: string) => Promise<void>;
|
|
299
299
|
getStatusLines: (options?: TelegramBridgeStatusLineOptions) => string[];
|
|
300
300
|
reloadConfig: () => Promise<void>;
|
|
301
301
|
hasBotToken: () => boolean;
|
|
@@ -308,6 +308,19 @@ export interface TelegramBridgeCommandRegistrationDeps {
|
|
|
308
308
|
| TelegramBridgeCommandStartPollingResult;
|
|
309
309
|
stopPolling: () => Promise<void | string>;
|
|
310
310
|
updateStatus: (ctx: ExtensionCommandContext) => void;
|
|
311
|
+
getProfileNames?: () => string[];
|
|
312
|
+
activateDefaultProfileConfig?: (ctx: ExtensionCommandContext) => Promise<void>;
|
|
313
|
+
activateProfileConfig?: (
|
|
314
|
+
ctx: ExtensionCommandContext,
|
|
315
|
+
profileName: string,
|
|
316
|
+
) => Promise<boolean>;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
function parseTelegramProfileArg(args: string): string | undefined {
|
|
320
|
+
const word = args.trim().split(/\s+/)[0];
|
|
321
|
+
if (!word || word.length === 0) return undefined;
|
|
322
|
+
if (word.startsWith("-")) return undefined;
|
|
323
|
+
return word;
|
|
311
324
|
}
|
|
312
325
|
|
|
313
326
|
function formatTelegramTakeoverTitle(ctx: ExtensionCommandContext): string {
|
|
@@ -331,9 +344,9 @@ export function registerTelegramBridgeCommands(
|
|
|
331
344
|
deps: TelegramBridgeCommandRegistrationDeps,
|
|
332
345
|
): void {
|
|
333
346
|
pi.registerCommand("telegram-setup", {
|
|
334
|
-
description: "Configure Telegram bot token",
|
|
335
|
-
handler: async (
|
|
336
|
-
await deps.promptForConfig(ctx);
|
|
347
|
+
description: "Configure Telegram bot token. Use /telegram-setup <name> for named profiles.",
|
|
348
|
+
handler: async (args, ctx) => {
|
|
349
|
+
await deps.promptForConfig(ctx, parseTelegramProfileArg(args));
|
|
337
350
|
},
|
|
338
351
|
});
|
|
339
352
|
pi.registerCommand("telegram-status", {
|
|
@@ -346,11 +359,31 @@ export function registerTelegramBridgeCommands(
|
|
|
346
359
|
},
|
|
347
360
|
});
|
|
348
361
|
pi.registerCommand("telegram-connect", {
|
|
349
|
-
description: "Start the Telegram bridge
|
|
350
|
-
handler: async (
|
|
351
|
-
|
|
362
|
+
description: "Start the Telegram bridge. Use /telegram-connect <name> for named profiles.",
|
|
363
|
+
handler: async (args, ctx) => {
|
|
364
|
+
const profileName = parseTelegramProfileArg(args);
|
|
365
|
+
if (profileName && deps.activateProfileConfig) {
|
|
366
|
+
const ok = await deps.activateProfileConfig(ctx, profileName);
|
|
367
|
+
if (!ok) {
|
|
368
|
+
ctx.ui.notify(`Profile "${profileName}" not found.`, "error");
|
|
369
|
+
deps.updateStatus(ctx);
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
ctx.ui.notify(`Activated profile "${profileName}".`, "info");
|
|
373
|
+
} else {
|
|
374
|
+
await (deps.activateDefaultProfileConfig?.(ctx) ?? deps.reloadConfig());
|
|
375
|
+
}
|
|
352
376
|
if (!deps.hasBotToken()) {
|
|
353
|
-
|
|
377
|
+
const profileNames = deps.getProfileNames?.() ?? [];
|
|
378
|
+
if (!profileName && profileNames.length > 0) {
|
|
379
|
+
ctx.ui.notify(
|
|
380
|
+
`No default Telegram profile configured. Available profiles: ${profileNames.join(", ")}. Use /telegram-connect <profileName> or /telegram-setup to create a default profile.`,
|
|
381
|
+
"info",
|
|
382
|
+
);
|
|
383
|
+
deps.updateStatus(ctx);
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
await deps.promptForConfig(ctx, profileName);
|
|
354
387
|
return;
|
|
355
388
|
}
|
|
356
389
|
let result = await deps.startPolling(ctx, {
|