@llblab/pi-kit 0.6.0 → 0.7.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/CHANGELOG.md +8 -0
- package/README.md +4 -4
- package/node_modules/@llblab/pi-actors/AGENTS.md +1 -1
- package/node_modules/@llblab/pi-actors/CHANGELOG.md +6 -0
- package/node_modules/@llblab/pi-actors/README.md +1 -1
- package/node_modules/@llblab/pi-actors/dist/lib/async-runs.d.ts +3 -0
- package/node_modules/@llblab/pi-actors/dist/lib/async-runs.js +14 -1
- package/node_modules/@llblab/pi-actors/dist/lib/command-templates.js +45 -3
- package/node_modules/@llblab/pi-actors/dist/lib/extension-runtime.js +1 -1
- package/node_modules/@llblab/pi-actors/dist/lib/observability.d.ts +16 -3
- package/node_modules/@llblab/pi-actors/dist/lib/observability.js +92 -7
- package/node_modules/@llblab/pi-actors/dist/lib/pi.d.ts +0 -1
- package/node_modules/@llblab/pi-actors/dist/lib/pi.js +15 -24
- package/node_modules/@llblab/pi-actors/dist/lib/run-delivery-lineage.d.ts +17 -0
- package/node_modules/@llblab/pi-actors/dist/lib/run-delivery-lineage.js +44 -0
- package/node_modules/@llblab/pi-actors/dist/lib/run-delivery.d.ts +4 -0
- package/node_modules/@llblab/pi-actors/dist/lib/run-delivery.js +102 -4
- package/node_modules/@llblab/pi-actors/dist/lib/run-ui-runtime.js +58 -39
- package/node_modules/@llblab/pi-actors/dist/lib/runtime.js +14 -6
- package/node_modules/@llblab/pi-actors/dist/skills/actors/SKILL.md +1 -1
- package/node_modules/@llblab/pi-actors/docs/async-runs.md +1 -1
- package/node_modules/@llblab/pi-actors/docs/coordinator-delivery.md +18 -23
- package/node_modules/@llblab/pi-actors/lib/async-runs.ts +18 -1
- package/node_modules/@llblab/pi-actors/lib/command-templates.ts +41 -3
- package/node_modules/@llblab/pi-actors/lib/extension-runtime.ts +1 -1
- package/node_modules/@llblab/pi-actors/lib/observability.ts +119 -5
- package/node_modules/@llblab/pi-actors/lib/pi.ts +15 -28
- package/node_modules/@llblab/pi-actors/lib/run-delivery-lineage.ts +68 -0
- package/node_modules/@llblab/pi-actors/lib/run-delivery.ts +120 -4
- package/node_modules/@llblab/pi-actors/lib/run-ui-runtime.ts +69 -44
- package/node_modules/@llblab/pi-actors/lib/runtime.ts +17 -6
- package/node_modules/@llblab/pi-actors/package.json +1 -1
- package/node_modules/@llblab/pi-actors/skills/actors/SKILL.md +1 -1
- package/node_modules/@llblab/pi-state-flow/AGENTS.md +33 -12
- package/node_modules/@llblab/pi-state-flow/BACKLOG.md +125 -2
- package/node_modules/@llblab/pi-state-flow/CHANGELOG.md +87 -45
- package/node_modules/@llblab/pi-state-flow/README.md +203 -107
- package/node_modules/@llblab/pi-state-flow/docs/README.md +4 -0
- package/node_modules/@llblab/pi-state-flow/docs/architecture.md +188 -0
- package/node_modules/@llblab/pi-state-flow/docs/temporal-acceptance.md +42 -0
- package/node_modules/@llblab/pi-state-flow/index.ts +164 -2
- package/node_modules/@llblab/pi-state-flow/lib/acquisition.ts +138 -0
- package/node_modules/@llblab/pi-state-flow/lib/artifact.ts +273 -0
- package/node_modules/@llblab/pi-state-flow/lib/config.ts +48 -0
- package/node_modules/@llblab/pi-state-flow/lib/context.ts +18 -5
- package/node_modules/@llblab/pi-state-flow/lib/continuation.ts +268 -0
- package/node_modules/@llblab/pi-state-flow/lib/discovery.ts +117 -0
- package/node_modules/@llblab/pi-state-flow/lib/durable.ts +562 -0
- package/node_modules/@llblab/pi-state-flow/lib/episode.ts +24 -12
- package/node_modules/@llblab/pi-state-flow/lib/extension.ts +606 -70
- package/node_modules/@llblab/pi-state-flow/lib/git.ts +666 -0
- package/node_modules/@llblab/pi-state-flow/lib/history.ts +95 -0
- package/node_modules/@llblab/pi-state-flow/lib/json.ts +24 -0
- package/node_modules/@llblab/pi-state-flow/lib/maintenance.ts +141 -0
- package/node_modules/@llblab/pi-state-flow/lib/memory.ts +52 -0
- package/node_modules/@llblab/pi-state-flow/lib/migration.ts +88 -0
- package/node_modules/@llblab/pi-state-flow/lib/publication.ts +296 -0
- package/node_modules/@llblab/pi-state-flow/lib/recovery.ts +23 -7
- package/node_modules/@llblab/pi-state-flow/lib/rehydration.ts +79 -0
- package/node_modules/@llblab/pi-state-flow/lib/runtime.ts +264 -0
- package/node_modules/@llblab/pi-state-flow/lib/session.ts +6 -0
- package/node_modules/@llblab/pi-state-flow/lib/skills.ts +99 -7
- package/node_modules/@llblab/pi-state-flow/lib/snapshot.ts +305 -48
- package/node_modules/@llblab/pi-state-flow/lib/state.ts +74 -7
- package/node_modules/@llblab/pi-state-flow/lib/status.ts +125 -6
- package/node_modules/@llblab/pi-state-flow/lib/storage.ts +196 -0
- package/node_modules/@llblab/pi-state-flow/lib/temporal.ts +233 -0
- package/node_modules/@llblab/pi-state-flow/lib/terminal.ts +70 -24
- package/node_modules/@llblab/pi-state-flow/lib/transition.ts +254 -29
- package/node_modules/@llblab/pi-state-flow/package.json +8 -2
- package/node_modules/@llblab/pi-state-flow/skills/state-flow-memory/SKILL.md +128 -0
- package/node_modules/@llblab/pi-telegram/AGENTS.md +14 -9
- package/node_modules/@llblab/pi-telegram/BACKLOG.md +23 -5
- package/node_modules/@llblab/pi-telegram/CHANGELOG.md +18 -0
- package/node_modules/@llblab/pi-telegram/README.md +13 -9
- package/node_modules/@llblab/pi-telegram/docs/README.md +1 -0
- package/node_modules/@llblab/pi-telegram/docs/architecture.md +227 -23
- package/node_modules/@llblab/pi-telegram/docs/generative-apps.md +1 -1
- package/node_modules/@llblab/pi-telegram/docs/multi-instance-bus.md +70 -19
- package/node_modules/@llblab/pi-telegram/docs/outbound.md +6 -6
- package/node_modules/@llblab/pi-telegram/docs/public-api.md +13 -6
- package/node_modules/@llblab/pi-telegram/docs/ui-style.md +3 -1
- package/node_modules/@llblab/pi-telegram/index.ts +4 -1418
- package/node_modules/@llblab/pi-telegram/lib/agent-messages.ts +6 -3
- package/node_modules/@llblab/pi-telegram/lib/bindings.ts +46 -1
- package/node_modules/@llblab/pi-telegram/lib/bus-api.ts +32 -19
- package/node_modules/@llblab/pi-telegram/lib/bus-follower.ts +600 -135
- package/node_modules/@llblab/pi-telegram/lib/bus-leader.ts +962 -55
- package/node_modules/@llblab/pi-telegram/lib/bus.ts +355 -26
- package/node_modules/@llblab/pi-telegram/lib/channel-posts.ts +718 -0
- package/node_modules/@llblab/pi-telegram/lib/commands.ts +237 -11
- package/node_modules/@llblab/pi-telegram/lib/config.ts +242 -26
- package/node_modules/@llblab/pi-telegram/lib/extension.ts +1851 -0
- package/node_modules/@llblab/pi-telegram/lib/generative-apps.ts +20 -2
- package/node_modules/@llblab/pi-telegram/lib/journal.ts +2184 -126
- package/node_modules/@llblab/pi-telegram/lib/locks.ts +44 -2
- package/node_modules/@llblab/pi-telegram/lib/menu-settings.ts +152 -13
- package/node_modules/@llblab/pi-telegram/lib/outbound-attachments.ts +97 -10
- package/node_modules/@llblab/pi-telegram/lib/paths.ts +29 -0
- package/node_modules/@llblab/pi-telegram/lib/polling.ts +85 -17
- package/node_modules/@llblab/pi-telegram/lib/preview.ts +17 -0
- package/node_modules/@llblab/pi-telegram/lib/prompts.ts +6 -2
- package/node_modules/@llblab/pi-telegram/lib/queue.ts +118 -26
- package/node_modules/@llblab/pi-telegram/lib/rendering.ts +4 -1
- package/node_modules/@llblab/pi-telegram/lib/replies.ts +21 -2
- package/node_modules/@llblab/pi-telegram/lib/routing.ts +344 -112
- package/node_modules/@llblab/pi-telegram/lib/setup.ts +44 -4
- package/node_modules/@llblab/pi-telegram/lib/status.ts +51 -4
- package/node_modules/@llblab/pi-telegram/lib/sync.ts +308 -39
- package/node_modules/@llblab/pi-telegram/lib/telegram-api.ts +353 -22
- package/node_modules/@llblab/pi-telegram/lib/thread-cleanup-manager.ts +664 -0
- package/node_modules/@llblab/pi-telegram/lib/thread-display.ts +226 -0
- package/node_modules/@llblab/pi-telegram/lib/thread-naming.ts +118 -0
- package/node_modules/@llblab/pi-telegram/lib/threads.ts +1686 -129
- package/node_modules/@llblab/pi-telegram/lib/turns.ts +7 -0
- package/node_modules/@llblab/pi-telegram/lib/updates.ts +1319 -97
- package/node_modules/@llblab/pi-telegram/lib/workspace-admission.ts +1643 -0
- package/node_modules/@llblab/pi-telegram/lib/workspace-retirement.ts +968 -0
- package/node_modules/@llblab/pi-telegram/lib/workspace-slots.ts +84 -0
- package/node_modules/@llblab/pi-telegram/package.json +1 -1
- package/node_modules/@llblab/pi-telegram/screenshot.png +0 -0
- package/node_modules/@llblab/pi-telegram/scripts/measure-bus.mjs +83 -0
- package/node_modules/@llblab/pi-telegram/scripts/measure-workspace.mjs +101 -0
- package/node_modules/@llblab/{skills → pi-telegram/skills}/show-me/SKILL.md +28 -6
- package/node_modules/@llblab/pi-telegram/skills/show-me/references/telegram-surfaces.md +43 -0
- package/node_modules/@llblab/pi-telegram/skills/telegram-bridge/references/delivery-and-threads.md +1 -1
- package/node_modules/@llblab/skills/package.json +2 -3
- package/package.json +6 -5
- /package/node_modules/@llblab/pi-telegram/lib/{logs.ts → logging.ts} +0 -0
|
@@ -19,6 +19,7 @@ import * as Queue from "./queue.ts";
|
|
|
19
19
|
import type { TelegramBridgeRuntime } from "./runtime.ts";
|
|
20
20
|
import type { TelegramSectionRegistry } from "./sections.ts";
|
|
21
21
|
import * as TextGroups from "./text-groups.ts";
|
|
22
|
+
import * as ThreadNaming from "./thread-naming.ts";
|
|
22
23
|
import * as ThreadReconciler from "./thread-reconciler.ts";
|
|
23
24
|
import type {
|
|
24
25
|
TelegramInstanceThreadIdentityCandidate,
|
|
@@ -175,6 +176,8 @@ function escapeHtml(text: string): string {
|
|
|
175
176
|
const TELEGRAM_UNBOUND_REROUTE_CALLBACK_PREFIX = "reroute:";
|
|
176
177
|
const TELEGRAM_UNBOUND_REROUTE_RESTORE_MENU_CALLBACK_PREFIX = "rerouterestore:";
|
|
177
178
|
const TELEGRAM_UNBOUND_REROUTE_NEW_SLOT_CALLBACK_PREFIX = "reroutenew:";
|
|
179
|
+
const TELEGRAM_SLOT_CAPACITY_MESSAGE =
|
|
180
|
+
"No Telegram instance slot is available. Automatic reclamation is disabled for safety.";
|
|
178
181
|
|
|
179
182
|
function formatTelegramUnboundRerouteCallbackData(
|
|
180
183
|
rerouteId: string,
|
|
@@ -218,20 +221,9 @@ function parseTelegramUnboundRerouteCallbackData(
|
|
|
218
221
|
|
|
219
222
|
function getTelegramThreadRecordLabel(
|
|
220
223
|
record: Threads.TelegramTopicTargetRecord,
|
|
224
|
+
getDisplayTitle?: Threads.TelegramCurrentThreadAssembly["getDisplayTitle"],
|
|
221
225
|
): string {
|
|
222
|
-
return getRestoredThreadName(record, record.slot ?? "");
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
function getTelegramRouteThreadButtonLabel(
|
|
226
|
-
record: Threads.TelegramTopicTargetRecord,
|
|
227
|
-
): string {
|
|
228
|
-
return `↪️ ${getTelegramThreadRecordLabel(record)}`;
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
function getTelegramReplaceThreadButtonLabel(
|
|
232
|
-
record: Threads.TelegramTopicTargetRecord,
|
|
233
|
-
): string {
|
|
234
|
-
return `➡️ ${getTelegramThreadRecordLabel(record)}`;
|
|
226
|
+
return getDisplayTitle?.(record.target) ?? getRestoredThreadName(record, record.slot ?? "");
|
|
235
227
|
}
|
|
236
228
|
|
|
237
229
|
function getNextTelegramSlotPreference(
|
|
@@ -288,13 +280,16 @@ function formatTelegramAllTabMenuChooserText(command: string): string {
|
|
|
288
280
|
function buildTelegramUnboundRerouteChooserMarkup(
|
|
289
281
|
rerouteId: string,
|
|
290
282
|
records: readonly Threads.TelegramTopicTargetRecord[],
|
|
291
|
-
options: {
|
|
283
|
+
options: {
|
|
284
|
+
canRestore: boolean;
|
|
285
|
+
getDisplayTitle?: Threads.TelegramCurrentThreadAssembly["getDisplayTitle"];
|
|
286
|
+
},
|
|
292
287
|
): Menu.TelegramReplyMarkup {
|
|
293
288
|
const activeRecords = records.filter((record) => record.status === "active");
|
|
294
289
|
const canRestoreAnyLiveThread = options.canRestore && activeRecords.length > 0;
|
|
295
290
|
const rows = activeRecords.map((record) => [
|
|
296
291
|
{
|
|
297
|
-
text:
|
|
292
|
+
text: `↪️ ${getTelegramThreadRecordLabel(record, options.getDisplayTitle)}`,
|
|
298
293
|
callback_data: formatTelegramUnboundRerouteCallbackData(
|
|
299
294
|
rerouteId,
|
|
300
295
|
record.target.threadId,
|
|
@@ -320,13 +315,14 @@ function buildTelegramUnboundRerouteChooserMarkup(
|
|
|
320
315
|
function buildTelegramUnboundRerouteRestoreChooserMarkup(
|
|
321
316
|
rerouteId: string,
|
|
322
317
|
records: readonly Threads.TelegramTopicTargetRecord[],
|
|
318
|
+
getDisplayTitle?: Threads.TelegramCurrentThreadAssembly["getDisplayTitle"],
|
|
323
319
|
): Menu.TelegramReplyMarkup {
|
|
324
320
|
return {
|
|
325
321
|
inline_keyboard: records
|
|
326
322
|
.filter((record) => record.status === "active")
|
|
327
323
|
.map((record) => [
|
|
328
324
|
{
|
|
329
|
-
text:
|
|
325
|
+
text: `➡️ ${getTelegramThreadRecordLabel(record, getDisplayTitle)}`,
|
|
330
326
|
callback_data: formatTelegramUnboundRerouteNewSlotCallbackData(
|
|
331
327
|
rerouteId,
|
|
332
328
|
record.target.threadId,
|
|
@@ -527,7 +523,7 @@ export interface TelegramInboundRouteRuntimeDeps<
|
|
|
527
523
|
> {
|
|
528
524
|
configStore: Pick<
|
|
529
525
|
TelegramConfigStore,
|
|
530
|
-
"get" | "getAllowedUserId" | "
|
|
526
|
+
"get" | "getAllowedUserId" | "persistAllowedUserId" | "persist"
|
|
531
527
|
> & { set?: TelegramConfigStore["set"] };
|
|
532
528
|
callApi?: <TResponse>(
|
|
533
529
|
method: string,
|
|
@@ -540,6 +536,7 @@ export interface TelegramInboundRouteRuntimeDeps<
|
|
|
540
536
|
getTargetOwnership?: Updates.TelegramTargetOwnershipLookup;
|
|
541
537
|
recordMessageOwnership?: Updates.TelegramMessageOwnershipRecorder;
|
|
542
538
|
getLiveThreadTargets?: () => Queue.TelegramQueueTarget[];
|
|
539
|
+
getDisplayTitle?: Threads.TelegramCurrentThreadAssembly["getDisplayTitle"];
|
|
543
540
|
getLocalThreadLabelForTarget?: (
|
|
544
541
|
target: Queue.TelegramQueueTarget,
|
|
545
542
|
) => string | undefined;
|
|
@@ -616,6 +613,14 @@ export interface TelegramInboundRouteRuntimeDeps<
|
|
|
616
613
|
) => Promise<false | "new" | "edit">;
|
|
617
614
|
inboundHandlerRuntime: TelegramInboundHandlerRuntime<TContext>;
|
|
618
615
|
threadStore?: Threads.TelegramTopicTargetStore;
|
|
616
|
+
runWorkspaceOperation?: <T>(
|
|
617
|
+
input: {
|
|
618
|
+
operationId: string;
|
|
619
|
+
operationKind: string;
|
|
620
|
+
scopes: readonly [{ kind: "profile" }];
|
|
621
|
+
},
|
|
622
|
+
operation: () => Promise<T>,
|
|
623
|
+
) => Promise<T>;
|
|
619
624
|
updateStatus: (ctx: TContext, error?: string) => void;
|
|
620
625
|
isContextActive?: (ctx: TContext) => boolean;
|
|
621
626
|
dispatchNextQueuedTelegramTurn: (ctx: TContext) => void;
|
|
@@ -654,6 +659,12 @@ export interface TelegramInboundRouteRuntimeDeps<
|
|
|
654
659
|
) => Promise<number | undefined>;
|
|
655
660
|
deleteMessage?: (chatId: number, messageId: number) => Promise<void>;
|
|
656
661
|
answerGuestQuery: (guestQueryId: string, text?: string) => Promise<void>;
|
|
662
|
+
/** Answers the guest query immediately and returns its inline message id. */
|
|
663
|
+
answerGuestQueryForInlineMessage?: (
|
|
664
|
+
guestQueryId: string,
|
|
665
|
+
text: string,
|
|
666
|
+
options?: { parseMode?: "HTML" },
|
|
667
|
+
) => Promise<string | undefined>;
|
|
657
668
|
sendTextReply: (
|
|
658
669
|
chatId: number,
|
|
659
670
|
replyToMessageId: number,
|
|
@@ -661,6 +672,9 @@ export interface TelegramInboundRouteRuntimeDeps<
|
|
|
661
672
|
options?: { parseMode?: "HTML"; target?: Queue.TelegramQueueTarget },
|
|
662
673
|
) => Promise<number | undefined>;
|
|
663
674
|
setMyCommands: Commands.TelegramBotCommandRegistrationDeps["setMyCommands"];
|
|
675
|
+
validateThreadName?: (threadName: string) => string | undefined;
|
|
676
|
+
renameCurrentThread?: Commands.TelegramThreadDisplayNameRenamePort;
|
|
677
|
+
resetCurrentThreadName?: Commands.TelegramThreadDisplayNameResetPort;
|
|
664
678
|
getCommands: () => Parameters<
|
|
665
679
|
typeof PromptTemplates.getTelegramPromptTemplateCommands
|
|
666
680
|
>[0];
|
|
@@ -785,7 +799,8 @@ export function createTelegramInboundRouteRuntime<
|
|
|
785
799
|
const chatId = message.chat.id;
|
|
786
800
|
const threadId = message.message_thread_id;
|
|
787
801
|
if (!threadId) return undefined;
|
|
788
|
-
const localLabel = deps.
|
|
802
|
+
const localLabel = deps.getDisplayTitle?.({ chatId, threadId }) ??
|
|
803
|
+
deps.getLocalThreadLabelForTarget?.({ chatId, threadId });
|
|
789
804
|
if (localLabel) return localLabel;
|
|
790
805
|
if (!deps.threadStore) return undefined;
|
|
791
806
|
const records = deps.threadStore.list();
|
|
@@ -964,6 +979,8 @@ export function createTelegramInboundRouteRuntime<
|
|
|
964
979
|
timer: ReturnType<typeof setTimeout>;
|
|
965
980
|
}
|
|
966
981
|
>();
|
|
982
|
+
const threadNameDialog = ThreadNaming.createTelegramThreadNameDialogRuntime();
|
|
983
|
+
const getThreadNameDialogScope = () => deps.getCurrentInstanceId?.() ?? "local";
|
|
967
984
|
const menuCallbackHandler = Menu.createTelegramMenuCallbackHandlerForContext<
|
|
968
985
|
TCallbackQuery,
|
|
969
986
|
TContext,
|
|
@@ -1328,6 +1345,7 @@ export function createTelegramInboundRouteRuntime<
|
|
|
1328
1345
|
const replyMarkup = buildTelegramUnboundRerouteRestoreChooserMarkup(
|
|
1329
1346
|
parsed.rerouteId,
|
|
1330
1347
|
activeRecords,
|
|
1348
|
+
deps.getDisplayTitle,
|
|
1331
1349
|
);
|
|
1332
1350
|
if (deps.editInteractiveMessage) {
|
|
1333
1351
|
await deps.editInteractiveMessage(
|
|
@@ -1352,7 +1370,7 @@ export function createTelegramInboundRouteRuntime<
|
|
|
1352
1370
|
await deps.answerCallbackQuery(query.id, "Choose instance to restore.");
|
|
1353
1371
|
return true;
|
|
1354
1372
|
};
|
|
1355
|
-
const
|
|
1373
|
+
const executeUnboundRerouteCallbackOperation = async (
|
|
1356
1374
|
query: TCallbackQuery,
|
|
1357
1375
|
ctx: TContext,
|
|
1358
1376
|
): Promise<boolean> => {
|
|
@@ -1471,6 +1489,13 @@ export function createTelegramInboundRouteRuntime<
|
|
|
1471
1489
|
currentInstanceId,
|
|
1472
1490
|
);
|
|
1473
1491
|
if (parsed.useNewSlot && !isCurrentLeaderRecord) {
|
|
1492
|
+
if (!record.slot || !/^[A-Z]$/u.test(record.slot)) {
|
|
1493
|
+
await deps.answerCallbackQuery(
|
|
1494
|
+
query.id,
|
|
1495
|
+
"Follower thread slot authority is unavailable.",
|
|
1496
|
+
);
|
|
1497
|
+
return true;
|
|
1498
|
+
}
|
|
1474
1499
|
if (
|
|
1475
1500
|
!sourceTarget ||
|
|
1476
1501
|
!deps.replaceFollowerThreadTarget ||
|
|
@@ -1497,7 +1522,7 @@ export function createTelegramInboundRouteRuntime<
|
|
|
1497
1522
|
return true;
|
|
1498
1523
|
}
|
|
1499
1524
|
const nowMs = Date.now();
|
|
1500
|
-
const slot = record.slot
|
|
1525
|
+
const slot = record.slot;
|
|
1501
1526
|
const threadName = getRestoredThreadName(record, slot);
|
|
1502
1527
|
assertExecutionCurrent();
|
|
1503
1528
|
deps.threadStore.markStaleByTarget(
|
|
@@ -1640,18 +1665,21 @@ export function createTelegramInboundRouteRuntime<
|
|
|
1640
1665
|
(record.target.chatId !== sourceTarget.chatId ||
|
|
1641
1666
|
record.target.threadId !== sourceTarget.threadId)
|
|
1642
1667
|
) {
|
|
1668
|
+
const slot = deps.threadStore.allocateSlot(
|
|
1669
|
+
leaderProfileKey ?? record.profileKey,
|
|
1670
|
+
getNextTelegramSlotPreference(record.slot),
|
|
1671
|
+
undefined,
|
|
1672
|
+
{ excludeCurrentRecord: true },
|
|
1673
|
+
);
|
|
1674
|
+
if (!slot) {
|
|
1675
|
+
await deps.answerCallbackQuery(query.id, TELEGRAM_SLOT_CAPACITY_MESSAGE);
|
|
1676
|
+
return true;
|
|
1677
|
+
}
|
|
1643
1678
|
deps.threadStore.markStaleByTarget(
|
|
1644
1679
|
record.target,
|
|
1645
1680
|
"deleted",
|
|
1646
1681
|
"Current leader thread was replaced by a new-slot reroute source.",
|
|
1647
1682
|
);
|
|
1648
|
-
const slot =
|
|
1649
|
-
deps.threadStore.allocateSlot(
|
|
1650
|
-
leaderProfileKey ?? record.profileKey,
|
|
1651
|
-
getNextTelegramSlotPreference(record.slot),
|
|
1652
|
-
) ??
|
|
1653
|
-
record.slot ??
|
|
1654
|
-
"?";
|
|
1655
1683
|
const nowMs = Date.now();
|
|
1656
1684
|
const threadName = getRestoredThreadName(record, slot);
|
|
1657
1685
|
deps.threadStore.upsert({
|
|
@@ -1759,6 +1787,23 @@ export function createTelegramInboundRouteRuntime<
|
|
|
1759
1787
|
);
|
|
1760
1788
|
return true;
|
|
1761
1789
|
};
|
|
1790
|
+
const executeUnboundRerouteCallback = (
|
|
1791
|
+
query: TCallbackQuery,
|
|
1792
|
+
ctx: TContext,
|
|
1793
|
+
): Promise<boolean> => {
|
|
1794
|
+
if (!parseTelegramUnboundRerouteCallbackData(query.data) ||
|
|
1795
|
+
!deps.runWorkspaceOperation) {
|
|
1796
|
+
return executeUnboundRerouteCallbackOperation(query, ctx);
|
|
1797
|
+
}
|
|
1798
|
+
return deps.runWorkspaceOperation(
|
|
1799
|
+
{
|
|
1800
|
+
operationId: `workspace-reroute:${query.id}`,
|
|
1801
|
+
operationKind: "workspace.route-unbound-thread",
|
|
1802
|
+
scopes: [{ kind: "profile" }],
|
|
1803
|
+
},
|
|
1804
|
+
() => executeUnboundRerouteCallbackOperation(query, ctx),
|
|
1805
|
+
);
|
|
1806
|
+
};
|
|
1762
1807
|
const handleUnboundRerouteCallback = async (
|
|
1763
1808
|
query: TCallbackQuery,
|
|
1764
1809
|
ctx: TContext,
|
|
@@ -1881,6 +1926,70 @@ export function createTelegramInboundRouteRuntime<
|
|
|
1881
1926
|
assertExecutionCurrent();
|
|
1882
1927
|
if (handled) return;
|
|
1883
1928
|
}
|
|
1929
|
+
if (query.data?.startsWith("thread-name:")) {
|
|
1930
|
+
const chatId = query.message?.chat?.id;
|
|
1931
|
+
const dialogMessageId = query.message?.message_id;
|
|
1932
|
+
if (typeof chatId !== "number" || typeof dialogMessageId !== "number") {
|
|
1933
|
+
await deps.answerCallbackQuery(query.id, "⌛ Rename dialog expired.");
|
|
1934
|
+
return;
|
|
1935
|
+
}
|
|
1936
|
+
const target = typeof query.message?.message_thread_id === "number"
|
|
1937
|
+
? { chatId, threadId: query.message.message_thread_id }
|
|
1938
|
+
: { chatId };
|
|
1939
|
+
const action = query.data.slice("thread-name:".length);
|
|
1940
|
+
if (action !== "reset" && action !== "cancel") {
|
|
1941
|
+
await deps.answerCallbackQuery(query.id, "⌛ Rename dialog expired.");
|
|
1942
|
+
return;
|
|
1943
|
+
}
|
|
1944
|
+
const selected = threadNameDialog.select({
|
|
1945
|
+
scope: getThreadNameDialogScope(),
|
|
1946
|
+
target,
|
|
1947
|
+
dialogMessageId,
|
|
1948
|
+
action,
|
|
1949
|
+
});
|
|
1950
|
+
if (selected.kind === "expired") {
|
|
1951
|
+
await deps.answerCallbackQuery(query.id, "⌛ Rename dialog expired.");
|
|
1952
|
+
return;
|
|
1953
|
+
}
|
|
1954
|
+
if (selected.kind === "cancel") {
|
|
1955
|
+
await deps.editInteractiveMessage?.(
|
|
1956
|
+
chatId,
|
|
1957
|
+
dialogMessageId,
|
|
1958
|
+
"<b>✖ Rename cancelled. Returning to normal agent mode.</b>",
|
|
1959
|
+
"html",
|
|
1960
|
+
{ inline_keyboard: [] },
|
|
1961
|
+
);
|
|
1962
|
+
await deps.answerCallbackQuery(query.id);
|
|
1963
|
+
return;
|
|
1964
|
+
}
|
|
1965
|
+
try {
|
|
1966
|
+
const result = await deps.resetCurrentThreadName?.(target);
|
|
1967
|
+
if (!result?.ok) {
|
|
1968
|
+
throw new Error(result?.message ?? "Thread display name reset is unavailable.");
|
|
1969
|
+
}
|
|
1970
|
+
await deps.editInteractiveMessage?.(
|
|
1971
|
+
chatId,
|
|
1972
|
+
dialogMessageId,
|
|
1973
|
+
result.message
|
|
1974
|
+
? Commands.formatTelegramInformationHeading("✅", result.message)
|
|
1975
|
+
: Commands.formatTelegramAutomaticThreadDisplayNameRestoredHeading(
|
|
1976
|
+
result.threadName ?? "automatic",
|
|
1977
|
+
),
|
|
1978
|
+
"html",
|
|
1979
|
+
{ inline_keyboard: [] },
|
|
1980
|
+
);
|
|
1981
|
+
await deps.answerCallbackQuery(query.id);
|
|
1982
|
+
} catch (error) {
|
|
1983
|
+
threadNameDialog.open({
|
|
1984
|
+
scope: getThreadNameDialogScope(), target, dialogMessageId,
|
|
1985
|
+
});
|
|
1986
|
+
deps.recordRuntimeEvent?.("telegram-command", error, {
|
|
1987
|
+
command: "name", phase: "reset",
|
|
1988
|
+
});
|
|
1989
|
+
await deps.answerCallbackQuery(query.id, "⚠️ Thread name reset failed.");
|
|
1990
|
+
}
|
|
1991
|
+
return;
|
|
1992
|
+
}
|
|
1884
1993
|
const handledByCompact =
|
|
1885
1994
|
await Commands.handleTelegramCompactConfirmationCallback(query, {
|
|
1886
1995
|
ctx,
|
|
@@ -2083,10 +2192,54 @@ export function createTelegramInboundRouteRuntime<
|
|
|
2083
2192
|
},
|
|
2084
2193
|
openSettingsMenu: deps.openSettingsMenu,
|
|
2085
2194
|
getAllowedUserId: deps.configStore.getAllowedUserId,
|
|
2086
|
-
|
|
2195
|
+
persistAllowedUserId: deps.configStore.persistAllowedUserId,
|
|
2087
2196
|
setMyCommands: deps.setMyCommands,
|
|
2197
|
+
validateThreadName: deps.validateThreadName,
|
|
2198
|
+
renameCurrentThread: deps.renameCurrentThread,
|
|
2199
|
+
resetCurrentThreadName: deps.resetCurrentThreadName,
|
|
2200
|
+
openThreadNameDialog: async (message) => {
|
|
2201
|
+
const target = Updates.getTelegramMessageTarget(message);
|
|
2202
|
+
if (!deps.sendInteractiveMessage || !target) {
|
|
2203
|
+
await deps.sendTextReply(
|
|
2204
|
+
message.chat.id,
|
|
2205
|
+
message.message_id,
|
|
2206
|
+
Commands.formatTelegramInformationHeading(
|
|
2207
|
+
"🏷️",
|
|
2208
|
+
"Usage: /name Navigator",
|
|
2209
|
+
),
|
|
2210
|
+
{ parseMode: "HTML", target },
|
|
2211
|
+
);
|
|
2212
|
+
return;
|
|
2213
|
+
}
|
|
2214
|
+
const hasManualName = deps.threadStore?.listWorkspaceBindings().some(
|
|
2215
|
+
(binding) => binding.target.chatId === target.chatId &&
|
|
2216
|
+
binding.target.threadId === target.threadId &&
|
|
2217
|
+
typeof binding.manualThreadName === "string",
|
|
2218
|
+
) ?? false;
|
|
2219
|
+
const instructions = hasManualName
|
|
2220
|
+
? "<b>🏷️ Send a new Thread name using printable ASCII, reset to automatic, or cancel.</b>"
|
|
2221
|
+
: "<b>🏷️ Send a Thread name using printable ASCII, or cancel.</b>";
|
|
2222
|
+
const buttons = hasManualName
|
|
2223
|
+
? [
|
|
2224
|
+
{ text: "↩️ Reset to automatic", callback_data: "thread-name:reset" },
|
|
2225
|
+
{ text: "✖ Cancel rename", callback_data: "thread-name:cancel" },
|
|
2226
|
+
]
|
|
2227
|
+
: [{ text: "✖ Cancel rename", callback_data: "thread-name:cancel" }];
|
|
2228
|
+
const dialogMessageId = await deps.sendInteractiveMessage(
|
|
2229
|
+
target.chatId,
|
|
2230
|
+
instructions,
|
|
2231
|
+
"html",
|
|
2232
|
+
{ inline_keyboard: buttons.map((button) => [button]) },
|
|
2233
|
+
{ target },
|
|
2234
|
+
);
|
|
2235
|
+
if (typeof dialogMessageId !== "number") return;
|
|
2236
|
+
threadNameDialog.open({
|
|
2237
|
+
scope: getThreadNameDialogScope(),
|
|
2238
|
+
target,
|
|
2239
|
+
dialogMessageId,
|
|
2240
|
+
});
|
|
2241
|
+
},
|
|
2088
2242
|
getPromptTemplateCommands,
|
|
2089
|
-
persistConfig: deps.configStore.persist,
|
|
2090
2243
|
sendTextReply: deps.sendTextReply,
|
|
2091
2244
|
getActiveTurnReply: () => {
|
|
2092
2245
|
const activeTurn = deps.activeTurnRuntime.get();
|
|
@@ -2158,7 +2311,7 @@ export function createTelegramInboundRouteRuntime<
|
|
|
2158
2311
|
message.message_id,
|
|
2159
2312
|
[
|
|
2160
2313
|
includeGuidance ? formatTelegramUnboundTopicGuidance() : undefined,
|
|
2161
|
-
|
|
2314
|
+
`This thread is not bound to a Pi instance. Open an active Pi thread or run ${Commands.formatTelegramPiCommandHtml("/telegram-connect")} from a Pi session to bind one.`,
|
|
2162
2315
|
]
|
|
2163
2316
|
.filter((line): line is string => typeof line === "string")
|
|
2164
2317
|
.join("\n\n"),
|
|
@@ -2178,7 +2331,7 @@ export function createTelegramInboundRouteRuntime<
|
|
|
2178
2331
|
const replyMarkup = buildTelegramUnboundRerouteChooserMarkup(
|
|
2179
2332
|
rerouteId,
|
|
2180
2333
|
activeRecords,
|
|
2181
|
-
{ canRestore: sourceTarget !== undefined },
|
|
2334
|
+
{ canRestore: sourceTarget !== undefined, getDisplayTitle: deps.getDisplayTitle },
|
|
2182
2335
|
);
|
|
2183
2336
|
if (deps.sendInteractiveMessage) {
|
|
2184
2337
|
const chooserId = await deps.sendInteractiveMessage(
|
|
@@ -2262,7 +2415,7 @@ export function createTelegramInboundRouteRuntime<
|
|
|
2262
2415
|
const replyMarkup = buildTelegramUnboundRerouteChooserMarkup(
|
|
2263
2416
|
rerouteId,
|
|
2264
2417
|
activeRecords,
|
|
2265
|
-
{ canRestore: typeof message.message_thread_id === "number" },
|
|
2418
|
+
{ canRestore: typeof message.message_thread_id === "number", getDisplayTitle: deps.getDisplayTitle },
|
|
2266
2419
|
);
|
|
2267
2420
|
let chooserId: number | undefined;
|
|
2268
2421
|
try {
|
|
@@ -2311,6 +2464,7 @@ export function createTelegramInboundRouteRuntime<
|
|
|
2311
2464
|
throw error;
|
|
2312
2465
|
}
|
|
2313
2466
|
rememberRerouteChooser(rerouteId, chooserId);
|
|
2467
|
+
Updates.reportTelegramUpdateCompleted(commandMessage);
|
|
2314
2468
|
return true;
|
|
2315
2469
|
};
|
|
2316
2470
|
const commandOrPrompt = Commands.createTelegramCommandOrPromptRuntime<
|
|
@@ -2323,6 +2477,84 @@ export function createTelegramInboundRouteRuntime<
|
|
|
2323
2477
|
},
|
|
2324
2478
|
shouldIgnoreMessages: (messages) =>
|
|
2325
2479
|
!Media.hasTelegramMessagesPromptContent(messages),
|
|
2480
|
+
consumeThreadNameInput: async (messages) => {
|
|
2481
|
+
const message = messages[0];
|
|
2482
|
+
if (!message || messages.length !== 1) return false;
|
|
2483
|
+
const target = Updates.getTelegramMessageTarget(message);
|
|
2484
|
+
if (!target) return false;
|
|
2485
|
+
const candidate = threadNameDialog.inspect(target);
|
|
2486
|
+
if (!candidate || candidate.scope !== getThreadNameDialogScope() ||
|
|
2487
|
+
candidate.phase !== "input") return false;
|
|
2488
|
+
const name = Media.extractFirstTelegramMessageText(messages).trim();
|
|
2489
|
+
if (/^[A-Z]$/.test(name) && deps.resetCurrentThreadName) {
|
|
2490
|
+
const consumed = threadNameDialog.consumeName({
|
|
2491
|
+
scope: getThreadNameDialogScope(), target, text: name,
|
|
2492
|
+
});
|
|
2493
|
+
if (consumed.kind !== "name") return false;
|
|
2494
|
+
const result = await deps.resetCurrentThreadName(target);
|
|
2495
|
+
if (!result.ok) {
|
|
2496
|
+
threadNameDialog.open({
|
|
2497
|
+
scope: getThreadNameDialogScope(), target,
|
|
2498
|
+
dialogMessageId: candidate.dialogMessageId,
|
|
2499
|
+
});
|
|
2500
|
+
}
|
|
2501
|
+
await deps.sendTextReply(
|
|
2502
|
+
target.chatId,
|
|
2503
|
+
message.message_id,
|
|
2504
|
+
result.ok && !result.message
|
|
2505
|
+
? Commands.formatTelegramAutomaticThreadDisplayNameRestoredHeading(
|
|
2506
|
+
result.threadName ?? name,
|
|
2507
|
+
)
|
|
2508
|
+
: Commands.formatTelegramInformationHeading(
|
|
2509
|
+
result.ok ? "✅" : "⚠️",
|
|
2510
|
+
result.message ?? "Thread display name reset failed.",
|
|
2511
|
+
),
|
|
2512
|
+
{ parseMode: "HTML", target },
|
|
2513
|
+
);
|
|
2514
|
+
return true;
|
|
2515
|
+
}
|
|
2516
|
+
const validationError = deps.validateThreadName?.(name);
|
|
2517
|
+
if (!name || validationError) {
|
|
2518
|
+
await deps.sendTextReply(
|
|
2519
|
+
target.chatId,
|
|
2520
|
+
message.message_id,
|
|
2521
|
+
validationError
|
|
2522
|
+
? Commands.formatTelegramInvalidInstanceName(validationError)
|
|
2523
|
+
: Commands.formatTelegramInformationHeading(
|
|
2524
|
+
"⚠️",
|
|
2525
|
+
"Send 1–96 printable ASCII characters.",
|
|
2526
|
+
),
|
|
2527
|
+
{ parseMode: "HTML", target },
|
|
2528
|
+
);
|
|
2529
|
+
return true;
|
|
2530
|
+
}
|
|
2531
|
+
const consumed = threadNameDialog.consumeName({
|
|
2532
|
+
scope: getThreadNameDialogScope(), target, text: name,
|
|
2533
|
+
});
|
|
2534
|
+
if (consumed.kind !== "name") return false;
|
|
2535
|
+
const result = await deps.renameCurrentThread?.(target, consumed.name);
|
|
2536
|
+
if (!result?.ok) {
|
|
2537
|
+
threadNameDialog.open({
|
|
2538
|
+
scope: getThreadNameDialogScope(),
|
|
2539
|
+
target,
|
|
2540
|
+
dialogMessageId: candidate.dialogMessageId,
|
|
2541
|
+
});
|
|
2542
|
+
}
|
|
2543
|
+
await deps.sendTextReply(
|
|
2544
|
+
target.chatId,
|
|
2545
|
+
message.message_id,
|
|
2546
|
+
result?.ok && !result.message
|
|
2547
|
+
? Commands.formatTelegramThreadDisplayNameSavedHeading(
|
|
2548
|
+
result.threadName ?? consumed.name,
|
|
2549
|
+
)
|
|
2550
|
+
: Commands.formatTelegramInformationHeading(
|
|
2551
|
+
result?.ok ? "✅" : "⚠️",
|
|
2552
|
+
result?.message ?? "Thread display name update failed.",
|
|
2553
|
+
),
|
|
2554
|
+
{ parseMode: "HTML", target },
|
|
2555
|
+
);
|
|
2556
|
+
return true;
|
|
2557
|
+
},
|
|
2326
2558
|
handleCommand: commandHandler,
|
|
2327
2559
|
executeExtensionCommand: async (command, message, ctx) => {
|
|
2328
2560
|
const extensionCommand = Commands.findTelegramExtensionCommand(
|
|
@@ -2429,6 +2661,9 @@ export function createTelegramInboundRouteRuntime<
|
|
|
2429
2661
|
await deps.threadStore.load();
|
|
2430
2662
|
assertExecutionCurrent();
|
|
2431
2663
|
};
|
|
2664
|
+
// Answer the guest query immediately so the agent-end edit can replace the
|
|
2665
|
+
// early ACK once the turn settles. See BACKLOG.md for live acceptance.
|
|
2666
|
+
const TELEGRAM_GUEST_ACK_HTML = "<b>⚙️ Received. Working on it…</b>";
|
|
2432
2667
|
const handleAuthorizedTelegramGuestMessage = async (
|
|
2433
2668
|
guestMessage: Updates.TelegramGuestMessage & { from: TelegramUser },
|
|
2434
2669
|
ctx: TContext,
|
|
@@ -2436,6 +2671,31 @@ export function createTelegramInboundRouteRuntime<
|
|
|
2436
2671
|
const assertExecutionCurrent =
|
|
2437
2672
|
Updates.createTelegramUpdateExecutionFenceGuard(guestMessage);
|
|
2438
2673
|
assertExecutionCurrent();
|
|
2674
|
+
let guestInlineMessageId: string | undefined;
|
|
2675
|
+
if (deps.answerGuestQueryForInlineMessage) {
|
|
2676
|
+
try {
|
|
2677
|
+
guestInlineMessageId = await deps.answerGuestQueryForInlineMessage(
|
|
2678
|
+
guestMessage.guest_query_id,
|
|
2679
|
+
TELEGRAM_GUEST_ACK_HTML,
|
|
2680
|
+
{ parseMode: "HTML" },
|
|
2681
|
+
);
|
|
2682
|
+
deps.recordRuntimeEvent?.(
|
|
2683
|
+
"guest",
|
|
2684
|
+
new Error("Guest ACK experiment answered the guest query"),
|
|
2685
|
+
{
|
|
2686
|
+
phase: "guest-ack-sent",
|
|
2687
|
+
guestQueryId: guestMessage.guest_query_id,
|
|
2688
|
+
hasInlineMessageId: !!guestInlineMessageId,
|
|
2689
|
+
},
|
|
2690
|
+
);
|
|
2691
|
+
} catch (error) {
|
|
2692
|
+
deps.recordRuntimeEvent?.("guest", error, {
|
|
2693
|
+
phase: "guest-ack-failed",
|
|
2694
|
+
guestQueryId: guestMessage.guest_query_id,
|
|
2695
|
+
});
|
|
2696
|
+
}
|
|
2697
|
+
assertExecutionCurrent();
|
|
2698
|
+
}
|
|
2439
2699
|
const text = guestMessage.text ?? "";
|
|
2440
2700
|
const gm = guestMessage as unknown as Record<string, unknown>;
|
|
2441
2701
|
// Build telegram prefix with guest context
|
|
@@ -2526,6 +2786,10 @@ export function createTelegramInboundRouteRuntime<
|
|
|
2526
2786
|
promptFiles: processed.promptFiles,
|
|
2527
2787
|
handlerOutputs: processed.handlerOutputs,
|
|
2528
2788
|
sourceContext,
|
|
2789
|
+
// Guest Mode allows exactly one reply within Telegram's limited response
|
|
2790
|
+
// window; the note travels with the turn text so the agent sees it at
|
|
2791
|
+
// execution time without a guest-specific system prompt variant.
|
|
2792
|
+
guestTurn: true,
|
|
2529
2793
|
});
|
|
2530
2794
|
const order = deps.bridgeRuntime.queue.allocateItemOrder();
|
|
2531
2795
|
const content: Queue.TelegramPromptContent[] = [
|
|
@@ -2552,6 +2816,7 @@ export function createTelegramInboundRouteRuntime<
|
|
|
2552
2816
|
chatId: 0,
|
|
2553
2817
|
replyToMessageId: 0,
|
|
2554
2818
|
guestQueryId: guestMessage.guest_query_id,
|
|
2819
|
+
...(guestInlineMessageId ? { guestInlineMessageId } : {}),
|
|
2555
2820
|
sourceMessageIds: [],
|
|
2556
2821
|
queueOrder: order,
|
|
2557
2822
|
queueLane: "default",
|
|
@@ -2585,8 +2850,7 @@ export function createTelegramInboundRouteRuntime<
|
|
|
2585
2850
|
recordMessageOwnership: deps.recordMessageOwnership,
|
|
2586
2851
|
handleTelegramTopicLifecycleUpdate,
|
|
2587
2852
|
foreignOwnedUpdateForwarder: deps.foreignOwnedUpdateForwarder,
|
|
2588
|
-
|
|
2589
|
-
persistConfig: deps.configStore.persist,
|
|
2853
|
+
persistAllowedUserId: deps.configStore.persistAllowedUserId,
|
|
2590
2854
|
updateStatus: deps.updateStatus,
|
|
2591
2855
|
removePendingMediaGroupMessages: deps.mediaGroupRuntime.removeMessages,
|
|
2592
2856
|
flushPendingMediaGroupMessage: deps.mediaGroupRuntime.flushMessage,
|
|
@@ -2684,7 +2948,8 @@ export function createTelegramInboundRouteRuntime<
|
|
|
2684
2948
|
},
|
|
2685
2949
|
handleAuthorizedTelegramEditedMessage: editRuntime.updateFromEditedMessage,
|
|
2686
2950
|
handleAuthorizedTelegramGuestMessage,
|
|
2687
|
-
handleUnboundTelegramTopicMessage:
|
|
2951
|
+
handleUnboundTelegramTopicMessage: (message, ctx) => {
|
|
2952
|
+
const operation = async (): Promise<void> => {
|
|
2688
2953
|
const assertExecutionCurrent =
|
|
2689
2954
|
Updates.createTelegramUpdateExecutionFenceGuard(message);
|
|
2690
2955
|
assertExecutionCurrent();
|
|
@@ -2743,7 +3008,7 @@ export function createTelegramInboundRouteRuntime<
|
|
|
2743
3008
|
target.chatId,
|
|
2744
3009
|
message.message_id,
|
|
2745
3010
|
"Instance " +
|
|
2746
|
-
getTelegramThreadRecordLabel(existing) +
|
|
3011
|
+
getTelegramThreadRecordLabel(existing, deps.getDisplayTitle) +
|
|
2747
3012
|
" is starting. Please wait…",
|
|
2748
3013
|
{ target },
|
|
2749
3014
|
);
|
|
@@ -2754,76 +3019,12 @@ export function createTelegramInboundRouteRuntime<
|
|
|
2754
3019
|
target.chatId,
|
|
2755
3020
|
message.message_id,
|
|
2756
3021
|
"Instance " +
|
|
2757
|
-
getTelegramThreadRecordLabel(existing) +
|
|
2758
|
-
|
|
2759
|
-
{ target },
|
|
3022
|
+
escapeHtml(getTelegramThreadRecordLabel(existing, deps.getDisplayTitle)) +
|
|
3023
|
+
` is not currently registered with the Telegram bus. This thread is preserved; retry shortly. If it does not recover, run ${Commands.formatTelegramPiCommandHtml("/telegram-connect")} in that Pi instance.`,
|
|
3024
|
+
{ parseMode: "HTML", target },
|
|
2760
3025
|
);
|
|
2761
3026
|
return;
|
|
2762
3027
|
}
|
|
2763
|
-
if (
|
|
2764
|
-
(existing.status === "stale" || existing.status === "offline") &&
|
|
2765
|
-
leaderProfileKey &&
|
|
2766
|
-
!hasActiveLeaderTopic(
|
|
2767
|
-
deps.threadStore.list(),
|
|
2768
|
-
leaderProfileKey,
|
|
2769
|
-
instanceId,
|
|
2770
|
-
) &&
|
|
2771
|
-
!hasAnyRoutableThread
|
|
2772
|
-
) {
|
|
2773
|
-
const priorLeaderRecord =
|
|
2774
|
-
deps.threadStore.getByProfileKey(leaderProfileKey);
|
|
2775
|
-
const slot =
|
|
2776
|
-
deps.threadStore.allocateSlot(
|
|
2777
|
-
leaderProfileKey,
|
|
2778
|
-
priorLeaderRecord?.slot ?? existing.slot,
|
|
2779
|
-
) ??
|
|
2780
|
-
priorLeaderRecord?.slot ??
|
|
2781
|
-
existing.slot ??
|
|
2782
|
-
"A";
|
|
2783
|
-
const threadName =
|
|
2784
|
-
priorLeaderRecord?.threadName ??
|
|
2785
|
-
existing.threadName ??
|
|
2786
|
-
Threads.chooseTelegramThreadName({ slot }) ??
|
|
2787
|
-
"Pi";
|
|
2788
|
-
deps.threadStore.upsert({
|
|
2789
|
-
profileKey: leaderProfileKey,
|
|
2790
|
-
owner: {
|
|
2791
|
-
kind: "leader",
|
|
2792
|
-
cwd:
|
|
2793
|
-
typeof (ctx as { cwd?: unknown }).cwd === "string"
|
|
2794
|
-
? (ctx as { cwd?: string }).cwd
|
|
2795
|
-
: undefined,
|
|
2796
|
-
instanceId,
|
|
2797
|
-
},
|
|
2798
|
-
target: { chatId: target.chatId, threadId: target.threadId },
|
|
2799
|
-
status: "active",
|
|
2800
|
-
createdAtMs: priorLeaderRecord?.createdAtMs ?? existing.createdAtMs,
|
|
2801
|
-
updatedAtMs: Date.now(),
|
|
2802
|
-
threadName,
|
|
2803
|
-
instanceId,
|
|
2804
|
-
slot,
|
|
2805
|
-
});
|
|
2806
|
-
await deps.threadStore.persist();
|
|
2807
|
-
assertExecutionCurrent();
|
|
2808
|
-
deps.setCurrentLeaderIdentity?.({
|
|
2809
|
-
target: { chatId: target.chatId, threadId: target.threadId },
|
|
2810
|
-
slot,
|
|
2811
|
-
threadName,
|
|
2812
|
-
});
|
|
2813
|
-
deps.recordRuntimeEvent?.(
|
|
2814
|
-
"bus",
|
|
2815
|
-
"Bus leader reclaimed unbound thread",
|
|
2816
|
-
{
|
|
2817
|
-
phase: "leader-topic-reclaim",
|
|
2818
|
-
chatId: target.chatId,
|
|
2819
|
-
threadId: target.threadId,
|
|
2820
|
-
slot,
|
|
2821
|
-
profileKey: leaderProfileKey,
|
|
2822
|
-
},
|
|
2823
|
-
);
|
|
2824
|
-
await textDispatch.handleMessage(message as TMessage, ctx);
|
|
2825
|
-
return;
|
|
2826
|
-
}
|
|
2827
3028
|
await deps.sendTextReply(
|
|
2828
3029
|
target.chatId,
|
|
2829
3030
|
message.message_id,
|
|
@@ -2914,12 +3115,28 @@ export function createTelegramInboundRouteRuntime<
|
|
|
2914
3115
|
if (!currentLeaderIsStale) throw error;
|
|
2915
3116
|
}
|
|
2916
3117
|
if (currentLeaderIsStale) {
|
|
3118
|
+
const slot = deps.threadStore.allocateSlot(leaderProfileKey);
|
|
3119
|
+
if (!slot) {
|
|
3120
|
+
deps.threadStore.markStaleByTarget(
|
|
3121
|
+
currentLeaderRecord.target,
|
|
3122
|
+
"deleted",
|
|
3123
|
+
"Current leader thread is stale during unbound prompt routing.",
|
|
3124
|
+
);
|
|
3125
|
+
await deps.threadStore.persist();
|
|
3126
|
+
assertExecutionCurrent();
|
|
3127
|
+
await deps.sendTextReply(
|
|
3128
|
+
target.chatId,
|
|
3129
|
+
message.message_id,
|
|
3130
|
+
TELEGRAM_SLOT_CAPACITY_MESSAGE,
|
|
3131
|
+
{ target },
|
|
3132
|
+
);
|
|
3133
|
+
return;
|
|
3134
|
+
}
|
|
2917
3135
|
deps.threadStore.markStaleByTarget(
|
|
2918
3136
|
currentLeaderRecord.target,
|
|
2919
3137
|
"deleted",
|
|
2920
3138
|
"Current leader thread is stale during unbound prompt routing.",
|
|
2921
3139
|
);
|
|
2922
|
-
const slot = currentLeaderRecord.slot ?? "A";
|
|
2923
3140
|
const threadName = getRestoredThreadName(currentLeaderRecord, slot);
|
|
2924
3141
|
deps.threadStore.upsert({
|
|
2925
3142
|
...currentLeaderRecord,
|
|
@@ -2972,14 +3189,19 @@ export function createTelegramInboundRouteRuntime<
|
|
|
2972
3189
|
deps.threadStore.getByProfileKey(leaderProfileKey);
|
|
2973
3190
|
const priorLeaderIdentity =
|
|
2974
3191
|
deps.threadStore.getIdentityByProfileKey(leaderProfileKey);
|
|
2975
|
-
const slot =
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
3192
|
+
const slot = deps.threadStore.allocateSlot(
|
|
3193
|
+
leaderProfileKey,
|
|
3194
|
+
priorLeaderRecord?.slot ?? priorLeaderIdentity?.slot,
|
|
3195
|
+
);
|
|
3196
|
+
if (!slot) {
|
|
3197
|
+
await deps.sendTextReply(
|
|
3198
|
+
target.chatId,
|
|
3199
|
+
message.message_id,
|
|
3200
|
+
TELEGRAM_SLOT_CAPACITY_MESSAGE,
|
|
3201
|
+
{ target },
|
|
3202
|
+
);
|
|
3203
|
+
return;
|
|
3204
|
+
}
|
|
2983
3205
|
const identityThreadName =
|
|
2984
3206
|
priorLeaderIdentity?.threadName &&
|
|
2985
3207
|
Threads.isTelegramTopicThreadNameValidForSlot(
|
|
@@ -3034,6 +3256,16 @@ export function createTelegramInboundRouteRuntime<
|
|
|
3034
3256
|
}
|
|
3035
3257
|
await sendUnboundRerouteChooser(message as TMessage, ctx);
|
|
3036
3258
|
return;
|
|
3259
|
+
};
|
|
3260
|
+
if (!deps.runWorkspaceOperation) return operation();
|
|
3261
|
+
return deps.runWorkspaceOperation(
|
|
3262
|
+
{
|
|
3263
|
+
operationId: `workspace-unbound:${message.chat.id}:${message.message_id}`,
|
|
3264
|
+
operationKind: "workspace.route-unbound-thread",
|
|
3265
|
+
scopes: [{ kind: "profile" }],
|
|
3266
|
+
},
|
|
3267
|
+
operation,
|
|
3268
|
+
);
|
|
3037
3269
|
},
|
|
3038
3270
|
});
|
|
3039
3271
|
}
|