@llblab/pi-telegram 0.45.11 → 0.47.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/BACKLOG.md +5 -1
- package/CHANGELOG.md +12 -0
- package/README.md +6 -6
- package/dist/api/activity.d.ts +6 -0
- package/dist/api/activity.js +6 -0
- package/dist/api/commands.d.ts +6 -0
- package/dist/api/commands.js +6 -0
- package/dist/api/delivery.d.ts +6 -0
- package/dist/api/delivery.js +6 -0
- package/dist/api/inbound.d.ts +6 -0
- package/dist/api/inbound.js +6 -0
- package/dist/api/keyboard.d.ts +6 -0
- package/dist/api/keyboard.js +6 -0
- package/dist/api/outbound.d.ts +6 -0
- package/dist/api/outbound.js +6 -0
- package/dist/api/sections.d.ts +7 -0
- package/dist/api/sections.js +6 -0
- package/dist/api/status.d.ts +6 -0
- package/dist/api/status.js +6 -0
- package/dist/api/updates.d.ts +6 -0
- package/dist/api/updates.js +6 -0
- package/dist/api/voice.d.ts +6 -0
- package/dist/api/voice.js +6 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +6 -0
- package/dist/lib/activity-verbosity.d.ts +52 -0
- package/dist/lib/activity-verbosity.js +596 -0
- package/dist/lib/activity.d.ts +220 -0
- package/dist/lib/activity.js +574 -0
- package/dist/lib/agent-messages.d.ts +28 -0
- package/dist/lib/agent-messages.js +86 -0
- package/dist/lib/bindings.d.ts +250 -0
- package/dist/lib/bindings.js +936 -0
- package/dist/lib/bus-api.d.ts +18 -0
- package/dist/lib/bus-api.js +254 -0
- package/dist/lib/bus-follower.d.ts +464 -0
- package/dist/lib/bus-follower.js +1686 -0
- package/dist/lib/bus-leader.d.ts +292 -0
- package/dist/lib/bus-leader.js +2242 -0
- package/dist/lib/bus-transport.d.ts +64 -0
- package/dist/lib/bus-transport.js +140 -0
- package/dist/lib/bus.d.ts +518 -0
- package/dist/lib/bus.js +2055 -0
- package/dist/lib/channel-posts.d.ts +170 -0
- package/dist/lib/channel-posts.js +611 -0
- package/dist/lib/command-templates.d.ts +70 -0
- package/dist/lib/command-templates.js +744 -0
- package/dist/lib/commands.d.ts +541 -0
- package/dist/lib/commands.js +1155 -0
- package/dist/lib/config.d.ts +252 -0
- package/dist/lib/config.js +889 -0
- package/dist/lib/delivery.d.ts +163 -0
- package/dist/lib/delivery.js +542 -0
- package/dist/lib/extension.d.ts +7 -0
- package/dist/lib/extension.js +1608 -0
- package/dist/lib/generative-app-worker.mjs +104 -0
- package/dist/lib/generative-apps.d.ts +212 -0
- package/dist/lib/generative-apps.js +1006 -0
- package/dist/lib/inbound.d.ts +91 -0
- package/dist/lib/inbound.js +502 -0
- package/dist/lib/journal.d.ts +665 -0
- package/dist/lib/journal.js +3701 -0
- package/dist/lib/keyboard.d.ts +23 -0
- package/dist/lib/keyboard.js +37 -0
- package/dist/lib/lifecycle.d.ts +157 -0
- package/dist/lib/lifecycle.js +395 -0
- package/dist/lib/locks.d.ts +164 -0
- package/dist/lib/locks.js +1208 -0
- package/dist/lib/logging.d.ts +50 -0
- package/dist/lib/logging.js +274 -0
- package/dist/lib/media.d.ts +181 -0
- package/dist/lib/media.js +602 -0
- package/dist/lib/menu-model.d.ts +217 -0
- package/dist/lib/menu-model.js +663 -0
- package/dist/lib/menu-queue.d.ts +37 -0
- package/dist/lib/menu-queue.js +408 -0
- package/dist/lib/menu-settings.d.ts +116 -0
- package/dist/lib/menu-settings.js +595 -0
- package/dist/lib/menu-status.d.ts +32 -0
- package/dist/lib/menu-status.js +112 -0
- package/dist/lib/menu-thinking.d.ts +29 -0
- package/dist/lib/menu-thinking.js +82 -0
- package/dist/lib/menu.d.ts +171 -0
- package/dist/lib/menu.js +323 -0
- package/dist/lib/model.d.ts +127 -0
- package/dist/lib/model.js +409 -0
- package/dist/lib/outbound-attachments.d.ts +241 -0
- package/dist/lib/outbound-attachments.js +639 -0
- package/dist/lib/outbound-buttons.d.ts +69 -0
- package/dist/lib/outbound-buttons.js +248 -0
- package/dist/lib/outbound-markup.d.ts +42 -0
- package/dist/lib/outbound-markup.js +678 -0
- package/dist/lib/outbound-voice.d.ts +55 -0
- package/dist/lib/outbound-voice.js +152 -0
- package/dist/lib/outbound.d.ts +185 -0
- package/dist/lib/outbound.js +516 -0
- package/dist/lib/ownership.d.ts +77 -0
- package/dist/lib/ownership.js +174 -0
- package/dist/lib/paths.d.ts +40 -0
- package/dist/lib/paths.js +94 -0
- package/dist/lib/pi.d.ts +72 -0
- package/dist/lib/pi.js +121 -0
- package/dist/lib/polling.d.ts +351 -0
- package/dist/lib/polling.js +1196 -0
- package/dist/lib/preview.d.ts +192 -0
- package/dist/lib/preview.js +614 -0
- package/dist/lib/prompt-templates.d.ts +24 -0
- package/dist/lib/prompt-templates.js +118 -0
- package/dist/lib/prompts.d.ts +57 -0
- package/dist/lib/prompts.js +167 -0
- package/dist/lib/queue.d.ts +766 -0
- package/dist/lib/queue.js +1965 -0
- package/dist/lib/recovery.d.ts +86 -0
- package/dist/lib/recovery.js +285 -0
- package/dist/lib/rendering.d.ts +20 -0
- package/dist/lib/rendering.js +983 -0
- package/dist/lib/replies.d.ts +214 -0
- package/dist/lib/replies.js +737 -0
- package/dist/lib/routing.d.ts +207 -0
- package/dist/lib/routing.js +2282 -0
- package/dist/lib/runtime.d.ts +172 -0
- package/dist/lib/runtime.js +402 -0
- package/dist/lib/sections.d.ts +165 -0
- package/dist/lib/sections.js +327 -0
- package/dist/lib/setup.d.ts +82 -0
- package/dist/lib/setup.js +150 -0
- package/dist/lib/skills.d.ts +8 -0
- package/dist/lib/skills.js +12 -0
- package/dist/lib/status.d.ts +442 -0
- package/dist/lib/status.js +1006 -0
- package/dist/lib/sync.d.ts +179 -0
- package/dist/lib/sync.js +782 -0
- package/dist/lib/target.d.ts +20 -0
- package/dist/lib/target.js +27 -0
- package/dist/lib/telegram-api.d.ts +541 -0
- package/dist/lib/telegram-api.js +1159 -0
- package/dist/lib/text-groups.d.ts +89 -0
- package/dist/lib/text-groups.js +317 -0
- package/dist/lib/thread-cleanup-manager.d.ts +288 -0
- package/dist/lib/thread-cleanup-manager.js +560 -0
- package/dist/lib/thread-display.d.ts +46 -0
- package/dist/lib/thread-display.js +257 -0
- package/dist/lib/thread-naming.d.ts +46 -0
- package/dist/lib/thread-naming.js +78 -0
- package/dist/lib/thread-reconciler.d.ts +239 -0
- package/dist/lib/thread-reconciler.js +644 -0
- package/dist/lib/threads.d.ts +621 -0
- package/dist/lib/threads.js +3679 -0
- package/dist/lib/time-injection.d.ts +15 -0
- package/dist/lib/time-injection.js +56 -0
- package/dist/lib/turns.d.ts +109 -0
- package/dist/lib/turns.js +500 -0
- package/dist/lib/updates.d.ts +1290 -0
- package/dist/lib/updates.js +3634 -0
- package/dist/lib/voice.d.ts +117 -0
- package/dist/lib/voice.js +174 -0
- package/dist/lib/workspace-admission.d.ts +264 -0
- package/dist/lib/workspace-admission.js +1136 -0
- package/dist/lib/workspace-retirement.d.ts +219 -0
- package/dist/lib/workspace-retirement.js +587 -0
- package/dist/lib/workspace-slots.d.ts +30 -0
- package/dist/lib/workspace-slots.js +54 -0
- package/dist/package.json +126 -0
- package/dist/pi-telegram/index.js +1 -0
- package/dist/skills/generated-control-surface/SKILL.md +107 -0
- package/dist/skills/generated-control-surface/references/capability-adapters.md +27 -0
- package/dist/skills/generated-control-surface/references/layout-and-state.md +37 -0
- package/dist/skills/generative-apps/SKILL.md +115 -0
- package/dist/skills/show-me/SKILL.md +166 -0
- package/dist/skills/show-me/references/telegram-surfaces.md +43 -0
- package/dist/skills/telegram-bridge/SKILL.md +129 -0
- package/dist/skills/telegram-bridge/references/configuration.md +15 -0
- package/dist/skills/telegram-bridge/references/delivery-and-threads.md +27 -0
- package/dist/skills/telegram-bridge/references/diagnosis.md +18 -0
- package/docs/architecture.md +1 -1
- package/docs/compact-matrix-literal.md +10 -3
- package/docs/generative-apps.md +15 -13
- package/docs/multi-instance-bus.md +17 -3
- package/docs/outbound.md +1 -1
- package/docs/public-api.md +2 -2
- package/lib/bindings.ts +89 -6
- package/lib/bus-follower.ts +11 -3
- package/lib/bus-leader.ts +57 -20
- package/lib/bus.ts +12 -2
- package/lib/commands.ts +2 -2
- package/lib/config.ts +14 -7
- package/lib/delivery.ts +38 -6
- package/lib/extension.ts +26 -1
- package/lib/generative-apps.ts +379 -13
- package/lib/menu-settings.ts +21 -5
- package/lib/outbound-markup.ts +16 -7
- package/lib/pi.ts +4 -0
- package/lib/sync.ts +3 -0
- package/lib/telegram-api.ts +12 -1
- package/lib/thread-cleanup-manager.ts +36 -6
- package/lib/thread-display.ts +96 -18
- package/lib/threads.ts +150 -30
- package/package.json +56 -13
- package/scripts/build-dist.mjs +44 -0
- package/scripts/measure-bus.mjs +8 -1
- package/scripts/measure-workspace.mjs +8 -1
- package/skills/generative-apps/SKILL.md +1 -1
- package/skills/show-me/SKILL.md +1 -1
- package/skills/telegram-bridge/SKILL.md +1 -1
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Telegram text-group coalescing helpers
|
|
3
|
+
* Zones: telegram inbound, queue admission, split-message recovery
|
|
4
|
+
* Owns conservative delayed grouping for Telegram text messages that look like automatic long-message splits
|
|
5
|
+
*/
|
|
6
|
+
import { type TelegramMessageForwardOrigin, type TelegramMessageUser, type TelegramRichMessage } from "./media.ts";
|
|
7
|
+
export interface TelegramTextGroupMessage {
|
|
8
|
+
message_id: number;
|
|
9
|
+
media_group_id?: string;
|
|
10
|
+
chat: {
|
|
11
|
+
id: number;
|
|
12
|
+
};
|
|
13
|
+
message_thread_id?: number;
|
|
14
|
+
from?: {
|
|
15
|
+
id?: number;
|
|
16
|
+
is_bot?: boolean;
|
|
17
|
+
};
|
|
18
|
+
text?: string;
|
|
19
|
+
caption?: string;
|
|
20
|
+
rich_message?: TelegramRichMessage;
|
|
21
|
+
forward_origin?: TelegramMessageForwardOrigin;
|
|
22
|
+
forward_from?: TelegramMessageUser;
|
|
23
|
+
forward_sender_name?: string;
|
|
24
|
+
}
|
|
25
|
+
export interface TelegramTextGroupState<TMessage, TContext = unknown> {
|
|
26
|
+
messages: TMessage[];
|
|
27
|
+
context?: TContext;
|
|
28
|
+
flushTimer?: ReturnType<typeof setTimeout>;
|
|
29
|
+
dispatching?: boolean;
|
|
30
|
+
suspended?: boolean;
|
|
31
|
+
reschedule?: (delayMs?: number) => void;
|
|
32
|
+
dispatchNow?: () => Promise<void>;
|
|
33
|
+
dispatchPromise?: Promise<void>;
|
|
34
|
+
dispatchLimit?: number;
|
|
35
|
+
forwardPairCandidate?: TelegramForwardCommentBatchPosition;
|
|
36
|
+
}
|
|
37
|
+
export type TelegramForwardCommentBatchPosition = "comment" | "forward";
|
|
38
|
+
export interface TelegramTextGroupController<TMessage, TContext = unknown> {
|
|
39
|
+
prepareUpdateBatch: (updates: readonly {
|
|
40
|
+
message?: TMessage;
|
|
41
|
+
}[]) => void;
|
|
42
|
+
getPreparedForwardingPosition: (message: TelegramTextGroupMessage) => TelegramForwardCommentBatchPosition | undefined;
|
|
43
|
+
prepareForwardedMessage: (message: TelegramTextGroupMessage, position: TelegramForwardCommentBatchPosition) => void;
|
|
44
|
+
queueMessage: (options: {
|
|
45
|
+
message: TMessage;
|
|
46
|
+
context: TContext;
|
|
47
|
+
dispatchMessages: (messages: TMessage[], ctx: TContext) => unknown | Promise<unknown>;
|
|
48
|
+
}) => boolean;
|
|
49
|
+
flushMessage: (messageId: number) => Promise<boolean>;
|
|
50
|
+
suspend: () => void;
|
|
51
|
+
resume: (context: TContext) => void;
|
|
52
|
+
clear: () => void;
|
|
53
|
+
}
|
|
54
|
+
export interface TelegramTextGroupControllerOptions {
|
|
55
|
+
debounceMs?: number;
|
|
56
|
+
forwardCommentWaitMs?: number | false;
|
|
57
|
+
minSplitLength?: number;
|
|
58
|
+
setTimer?: (callback: () => void, ms: number) => ReturnType<typeof setTimeout>;
|
|
59
|
+
clearTimer?: (timer: ReturnType<typeof setTimeout>) => void;
|
|
60
|
+
}
|
|
61
|
+
export interface TelegramTextGroupDispatchRuntime<TMessage extends TelegramTextGroupMessage, TContext> {
|
|
62
|
+
handleMessage: (message: TMessage, ctx: TContext) => Promise<void>;
|
|
63
|
+
}
|
|
64
|
+
export interface TelegramGroupedInputClearerDeps {
|
|
65
|
+
clearMediaGroups: () => void;
|
|
66
|
+
clearTextGroups: () => void;
|
|
67
|
+
}
|
|
68
|
+
export declare function queueTelegramTextGroupMessage<TMessage extends TelegramTextGroupMessage, TContext = unknown>(options: {
|
|
69
|
+
message: TMessage;
|
|
70
|
+
context: TContext;
|
|
71
|
+
groups: Map<string, TelegramTextGroupState<TMessage, TContext>>;
|
|
72
|
+
debounceMs: number;
|
|
73
|
+
minSplitLength: number;
|
|
74
|
+
setTimer: (callback: () => void, ms: number) => ReturnType<typeof setTimeout>;
|
|
75
|
+
clearTimer: (timer: ReturnType<typeof setTimeout>) => void;
|
|
76
|
+
dispatchMessages: (messages: TMessage[], ctx: TContext) => unknown | Promise<unknown>;
|
|
77
|
+
forceStart?: boolean;
|
|
78
|
+
dispatchImmediately?: boolean;
|
|
79
|
+
forwardPairCandidate?: TelegramForwardCommentBatchPosition;
|
|
80
|
+
delayMs?: number;
|
|
81
|
+
}): boolean;
|
|
82
|
+
export declare function createTelegramTextGroupController<TMessage extends TelegramTextGroupMessage, TContext = unknown>(options?: TelegramTextGroupControllerOptions): TelegramTextGroupController<TMessage, TContext>;
|
|
83
|
+
export declare function createTelegramTextGroupDispatchRuntime<TMessage extends TelegramTextGroupMessage, TContext>(deps: {
|
|
84
|
+
textGroups: TelegramTextGroupController<TMessage, TContext>;
|
|
85
|
+
dispatchMessages: (messages: TMessage[], ctx: TContext) => Promise<void>;
|
|
86
|
+
dispatchSingleMessage: (message: TMessage, ctx: TContext) => Promise<void>;
|
|
87
|
+
onDeferredMessage?: (message: TMessage) => void;
|
|
88
|
+
}): TelegramTextGroupDispatchRuntime<TMessage, TContext>;
|
|
89
|
+
export declare function createTelegramGroupedInputClearer(deps: TelegramGroupedInputClearerDeps): () => void;
|
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Telegram text-group coalescing helpers
|
|
3
|
+
* Zones: telegram inbound, queue admission, split-message recovery
|
|
4
|
+
* Owns conservative delayed grouping for Telegram text messages that look like automatic long-message splits
|
|
5
|
+
*/
|
|
6
|
+
import { setTimeout as waitForTimeout } from "node:timers/promises";
|
|
7
|
+
import { extractTelegramMessageText, } from "./media.js";
|
|
8
|
+
const TELEGRAM_TEXT_GROUP_DEBOUNCE_MS = 1000;
|
|
9
|
+
const TELEGRAM_TEXT_GROUP_MIN_SPLIT_LENGTH = 3600;
|
|
10
|
+
const TELEGRAM_TEXT_GROUP_MAX_MESSAGE_ID_GAP = 12;
|
|
11
|
+
function extractTelegramTextGroupText(message) {
|
|
12
|
+
return extractTelegramMessageText(message);
|
|
13
|
+
}
|
|
14
|
+
function isTelegramForwardedMessage(message) {
|
|
15
|
+
return (message.forward_origin !== undefined ||
|
|
16
|
+
message.forward_from !== undefined ||
|
|
17
|
+
typeof message.forward_sender_name === "string");
|
|
18
|
+
}
|
|
19
|
+
function isTelegramTextGroupCommand(text) {
|
|
20
|
+
return text.trimStart().startsWith("/");
|
|
21
|
+
}
|
|
22
|
+
function isTelegramTextGroupClearingCommand(text) {
|
|
23
|
+
const command = text.trimStart().split(/\s+/, 1)[0]?.split("@", 1)[0];
|
|
24
|
+
return command === "/stop";
|
|
25
|
+
}
|
|
26
|
+
function getTelegramTextGroupMessageIdentity(message) {
|
|
27
|
+
const threadKey = typeof message.message_thread_id === "number"
|
|
28
|
+
? `thread:${message.message_thread_id}`
|
|
29
|
+
: "private";
|
|
30
|
+
return `${message.chat.id}:${threadKey}:${message.message_id}`;
|
|
31
|
+
}
|
|
32
|
+
function getTelegramTextGroupKey(message) {
|
|
33
|
+
if (message.media_group_id)
|
|
34
|
+
return undefined;
|
|
35
|
+
if (!message.from || message.from.is_bot)
|
|
36
|
+
return undefined;
|
|
37
|
+
if (!extractTelegramTextGroupText(message) &&
|
|
38
|
+
!isTelegramForwardedMessage(message)) {
|
|
39
|
+
return undefined;
|
|
40
|
+
}
|
|
41
|
+
const threadKey = typeof message.message_thread_id === "number"
|
|
42
|
+
? `thread:${message.message_thread_id}`
|
|
43
|
+
: "private";
|
|
44
|
+
return `${message.chat.id}:${threadKey}:${message.from.id}`;
|
|
45
|
+
}
|
|
46
|
+
function canStartTelegramTextGroup(message, minSplitLength) {
|
|
47
|
+
const text = extractTelegramTextGroupText(message);
|
|
48
|
+
return text.length >= minSplitLength && !isTelegramTextGroupCommand(text);
|
|
49
|
+
}
|
|
50
|
+
function canAppendTelegramTextGroupMessage(state, message) {
|
|
51
|
+
const text = extractTelegramTextGroupText(message);
|
|
52
|
+
const previous = state.messages.at(-1);
|
|
53
|
+
return (!!previous &&
|
|
54
|
+
message.message_id > previous.message_id &&
|
|
55
|
+
message.message_id <=
|
|
56
|
+
previous.message_id + TELEGRAM_TEXT_GROUP_MAX_MESSAGE_ID_GAP &&
|
|
57
|
+
(text.length > 0 || isTelegramForwardedMessage(message)) &&
|
|
58
|
+
!isTelegramTextGroupCommand(text));
|
|
59
|
+
}
|
|
60
|
+
export function queueTelegramTextGroupMessage(options) {
|
|
61
|
+
const key = getTelegramTextGroupKey(options.message);
|
|
62
|
+
if (!key)
|
|
63
|
+
return false;
|
|
64
|
+
const existing = options.groups.get(key);
|
|
65
|
+
const duplicateIndex = existing?.messages.findIndex((message) => message.message_id === options.message.message_id);
|
|
66
|
+
if (existing && duplicateIndex !== undefined && duplicateIndex >= 0) {
|
|
67
|
+
existing.messages[duplicateIndex] = options.message;
|
|
68
|
+
existing.context = options.context;
|
|
69
|
+
existing.forwardPairCandidate = options.forwardPairCandidate;
|
|
70
|
+
if (!existing.suspended && !existing.dispatching && !existing.flushTimer) {
|
|
71
|
+
existing.reschedule?.();
|
|
72
|
+
}
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
75
|
+
if (!existing &&
|
|
76
|
+
!options.forceStart &&
|
|
77
|
+
!canStartTelegramTextGroup(options.message, options.minSplitLength))
|
|
78
|
+
return false;
|
|
79
|
+
if (existing && !canAppendTelegramTextGroupMessage(existing, options.message))
|
|
80
|
+
return false;
|
|
81
|
+
const state = existing ?? { messages: [] };
|
|
82
|
+
state.messages.push(options.message);
|
|
83
|
+
state.context = options.context;
|
|
84
|
+
state.forwardPairCandidate = options.forwardPairCandidate;
|
|
85
|
+
const dispatchQueued = () => {
|
|
86
|
+
state.flushTimer = undefined;
|
|
87
|
+
const queued = options.groups.get(key);
|
|
88
|
+
if (!queued || queued.context === undefined)
|
|
89
|
+
return Promise.resolve();
|
|
90
|
+
if (queued.dispatching)
|
|
91
|
+
return queued.dispatchPromise ?? Promise.resolve();
|
|
92
|
+
const dispatchCount = queued.dispatchLimit ?? queued.messages.length;
|
|
93
|
+
queued.dispatchLimit = undefined;
|
|
94
|
+
const dispatchedMessages = queued.messages.slice(0, dispatchCount);
|
|
95
|
+
const dispatchedIds = new Set(dispatchedMessages.map((message) => message.message_id));
|
|
96
|
+
queued.dispatching = true;
|
|
97
|
+
const operation = Promise.resolve(options.dispatchMessages(dispatchedMessages, queued.context)).then(() => {
|
|
98
|
+
if (options.groups.get(key) !== queued)
|
|
99
|
+
return;
|
|
100
|
+
queued.messages = queued.messages.filter((message) => !dispatchedIds.has(message.message_id));
|
|
101
|
+
queued.dispatching = false;
|
|
102
|
+
queued.dispatchPromise = undefined;
|
|
103
|
+
if (queued.messages.length === 0)
|
|
104
|
+
options.groups.delete(key);
|
|
105
|
+
else if (!queued.flushTimer)
|
|
106
|
+
scheduleDispatch();
|
|
107
|
+
}, (error) => {
|
|
108
|
+
if (options.groups.get(key) === queued) {
|
|
109
|
+
queued.dispatching = false;
|
|
110
|
+
queued.dispatchPromise = undefined;
|
|
111
|
+
if (!queued.flushTimer)
|
|
112
|
+
scheduleDispatch();
|
|
113
|
+
}
|
|
114
|
+
throw error;
|
|
115
|
+
});
|
|
116
|
+
queued.dispatchPromise = operation;
|
|
117
|
+
return operation;
|
|
118
|
+
};
|
|
119
|
+
const scheduleDispatch = (delayMs = options.debounceMs) => {
|
|
120
|
+
if (state.suspended)
|
|
121
|
+
return;
|
|
122
|
+
state.flushTimer = options.setTimer(() => {
|
|
123
|
+
void dispatchQueued().catch(() => undefined);
|
|
124
|
+
}, delayMs);
|
|
125
|
+
state.flushTimer.unref?.();
|
|
126
|
+
};
|
|
127
|
+
state.reschedule = scheduleDispatch;
|
|
128
|
+
state.dispatchNow = dispatchQueued;
|
|
129
|
+
if (state.flushTimer)
|
|
130
|
+
options.clearTimer(state.flushTimer);
|
|
131
|
+
scheduleDispatch(options.dispatchImmediately ? 0 : (options.delayMs ?? options.debounceMs));
|
|
132
|
+
options.groups.set(key, state);
|
|
133
|
+
return true;
|
|
134
|
+
}
|
|
135
|
+
export function createTelegramTextGroupController(options = {}) {
|
|
136
|
+
const groups = new Map();
|
|
137
|
+
const plannedForwardCommentStarts = new Set();
|
|
138
|
+
const plannedForwardCommentEnds = new Set();
|
|
139
|
+
const debounceMs = options.debounceMs ?? TELEGRAM_TEXT_GROUP_DEBOUNCE_MS;
|
|
140
|
+
const minSplitLength = options.minSplitLength ?? TELEGRAM_TEXT_GROUP_MIN_SPLIT_LENGTH;
|
|
141
|
+
const forwardCommentWaitMs = options.forwardCommentWaitMs === undefined
|
|
142
|
+
? debounceMs
|
|
143
|
+
: options.forwardCommentWaitMs;
|
|
144
|
+
const setTimer = options.setTimer ??
|
|
145
|
+
((callback, ms) => {
|
|
146
|
+
const controller = new AbortController();
|
|
147
|
+
void waitForTimeout(ms, undefined, {
|
|
148
|
+
signal: controller.signal,
|
|
149
|
+
}).then(callback, () => undefined);
|
|
150
|
+
return controller;
|
|
151
|
+
});
|
|
152
|
+
const clearTimer = options.clearTimer ??
|
|
153
|
+
(options.setTimer
|
|
154
|
+
? clearTimeout
|
|
155
|
+
: (timer) => {
|
|
156
|
+
timer.abort();
|
|
157
|
+
});
|
|
158
|
+
return {
|
|
159
|
+
prepareUpdateBatch(updates) {
|
|
160
|
+
for (let index = 0; index + 1 < updates.length; index += 1) {
|
|
161
|
+
const comment = updates[index]?.message;
|
|
162
|
+
const forwarded = updates[index + 1]?.message;
|
|
163
|
+
if (!comment || !forwarded)
|
|
164
|
+
continue;
|
|
165
|
+
const commentText = extractTelegramTextGroupText(comment);
|
|
166
|
+
const commentKey = getTelegramTextGroupKey(comment);
|
|
167
|
+
const forwardedKey = getTelegramTextGroupKey(forwarded);
|
|
168
|
+
if (!commentKey ||
|
|
169
|
+
commentKey !== forwardedKey ||
|
|
170
|
+
!commentText ||
|
|
171
|
+
isTelegramTextGroupCommand(commentText) ||
|
|
172
|
+
isTelegramForwardedMessage(comment) ||
|
|
173
|
+
!isTelegramForwardedMessage(forwarded) ||
|
|
174
|
+
forwarded.message_id <= comment.message_id ||
|
|
175
|
+
forwarded.message_id >
|
|
176
|
+
comment.message_id + TELEGRAM_TEXT_GROUP_MAX_MESSAGE_ID_GAP) {
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
plannedForwardCommentStarts.add(getTelegramTextGroupMessageIdentity(comment));
|
|
180
|
+
plannedForwardCommentEnds.add(getTelegramTextGroupMessageIdentity(forwarded));
|
|
181
|
+
}
|
|
182
|
+
},
|
|
183
|
+
getPreparedForwardingPosition(message) {
|
|
184
|
+
const identity = getTelegramTextGroupMessageIdentity(message);
|
|
185
|
+
if (plannedForwardCommentStarts.has(identity))
|
|
186
|
+
return "comment";
|
|
187
|
+
if (plannedForwardCommentEnds.has(identity))
|
|
188
|
+
return "forward";
|
|
189
|
+
return undefined;
|
|
190
|
+
},
|
|
191
|
+
prepareForwardedMessage(message, position) {
|
|
192
|
+
const identity = getTelegramTextGroupMessageIdentity(message);
|
|
193
|
+
if (position === "comment")
|
|
194
|
+
plannedForwardCommentStarts.add(identity);
|
|
195
|
+
else
|
|
196
|
+
plannedForwardCommentEnds.add(identity);
|
|
197
|
+
},
|
|
198
|
+
queueMessage: ({ message, context, dispatchMessages }) => {
|
|
199
|
+
const identity = getTelegramTextGroupMessageIdentity(message);
|
|
200
|
+
const key = getTelegramTextGroupKey(message);
|
|
201
|
+
const plannedStart = plannedForwardCommentStarts.delete(identity);
|
|
202
|
+
const forwarded = isTelegramForwardedMessage(message);
|
|
203
|
+
const existing = key ? groups.get(key) : undefined;
|
|
204
|
+
const text = extractTelegramTextGroupText(message);
|
|
205
|
+
if (existing && isTelegramTextGroupClearingCommand(text)) {
|
|
206
|
+
if (existing.flushTimer)
|
|
207
|
+
clearTimer(existing.flushTimer);
|
|
208
|
+
groups.delete(key);
|
|
209
|
+
}
|
|
210
|
+
const candidatePosition = forwarded
|
|
211
|
+
? "forward"
|
|
212
|
+
: "comment";
|
|
213
|
+
const existingCandidate = existing?.forwardPairCandidate;
|
|
214
|
+
const pairCompleted = existingCandidate !== undefined &&
|
|
215
|
+
existingCandidate !== candidatePosition;
|
|
216
|
+
const separateFromCandidate = existingCandidate === candidatePosition &&
|
|
217
|
+
!isTelegramTextGroupCommand(text);
|
|
218
|
+
if (separateFromCandidate && existing) {
|
|
219
|
+
existing.dispatchLimit = existing.messages.length;
|
|
220
|
+
}
|
|
221
|
+
const forceStart = plannedStart ||
|
|
222
|
+
(forwardCommentWaitMs !== false &&
|
|
223
|
+
!!key &&
|
|
224
|
+
(forwarded ||
|
|
225
|
+
(typeof message.text === "string" &&
|
|
226
|
+
!isTelegramTextGroupCommand(extractTelegramTextGroupText(message)))));
|
|
227
|
+
const dispatchImmediately = pairCompleted ||
|
|
228
|
+
separateFromCandidate ||
|
|
229
|
+
plannedForwardCommentEnds.delete(identity) ||
|
|
230
|
+
(forwarded && !!key && groups.has(key));
|
|
231
|
+
return queueTelegramTextGroupMessage({
|
|
232
|
+
message,
|
|
233
|
+
context,
|
|
234
|
+
groups,
|
|
235
|
+
debounceMs,
|
|
236
|
+
minSplitLength,
|
|
237
|
+
setTimer,
|
|
238
|
+
clearTimer,
|
|
239
|
+
dispatchMessages,
|
|
240
|
+
forceStart,
|
|
241
|
+
dispatchImmediately,
|
|
242
|
+
forwardPairCandidate: forceStart && !canStartTelegramTextGroup(message, minSplitLength)
|
|
243
|
+
? candidatePosition
|
|
244
|
+
: undefined,
|
|
245
|
+
delayMs: forceStart && !canStartTelegramTextGroup(message, minSplitLength)
|
|
246
|
+
? forwardCommentWaitMs === false
|
|
247
|
+
? undefined
|
|
248
|
+
: forwardCommentWaitMs
|
|
249
|
+
: undefined,
|
|
250
|
+
});
|
|
251
|
+
},
|
|
252
|
+
async flushMessage(messageId) {
|
|
253
|
+
for (const state of groups.values()) {
|
|
254
|
+
if (!state.messages.some((message) => message.message_id === messageId)) {
|
|
255
|
+
continue;
|
|
256
|
+
}
|
|
257
|
+
if (state.flushTimer)
|
|
258
|
+
clearTimer(state.flushTimer);
|
|
259
|
+
state.flushTimer = undefined;
|
|
260
|
+
await state.dispatchNow?.();
|
|
261
|
+
if (state.messages.some((message) => message.message_id === messageId) &&
|
|
262
|
+
!state.dispatching) {
|
|
263
|
+
await state.dispatchNow?.();
|
|
264
|
+
}
|
|
265
|
+
return true;
|
|
266
|
+
}
|
|
267
|
+
return false;
|
|
268
|
+
},
|
|
269
|
+
suspend: () => {
|
|
270
|
+
for (const state of groups.values()) {
|
|
271
|
+
state.suspended = true;
|
|
272
|
+
if (state.flushTimer)
|
|
273
|
+
clearTimer(state.flushTimer);
|
|
274
|
+
state.flushTimer = undefined;
|
|
275
|
+
}
|
|
276
|
+
},
|
|
277
|
+
resume: (context) => {
|
|
278
|
+
for (const state of groups.values()) {
|
|
279
|
+
state.context = context;
|
|
280
|
+
state.suspended = false;
|
|
281
|
+
if (!state.dispatching && !state.flushTimer)
|
|
282
|
+
state.reschedule?.();
|
|
283
|
+
}
|
|
284
|
+
},
|
|
285
|
+
clear: () => {
|
|
286
|
+
for (const state of groups.values()) {
|
|
287
|
+
if (state.flushTimer)
|
|
288
|
+
clearTimer(state.flushTimer);
|
|
289
|
+
}
|
|
290
|
+
groups.clear();
|
|
291
|
+
plannedForwardCommentStarts.clear();
|
|
292
|
+
plannedForwardCommentEnds.clear();
|
|
293
|
+
},
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
export function createTelegramTextGroupDispatchRuntime(deps) {
|
|
297
|
+
return {
|
|
298
|
+
handleMessage: async (message, ctx) => {
|
|
299
|
+
const queuedTextGroup = deps.textGroups.queueMessage({
|
|
300
|
+
message,
|
|
301
|
+
context: ctx,
|
|
302
|
+
dispatchMessages: (messages, queuedCtx) => deps.dispatchMessages(messages, queuedCtx),
|
|
303
|
+
});
|
|
304
|
+
if (queuedTextGroup) {
|
|
305
|
+
deps.onDeferredMessage?.(message);
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
await deps.dispatchSingleMessage(message, ctx);
|
|
309
|
+
},
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
export function createTelegramGroupedInputClearer(deps) {
|
|
313
|
+
return () => {
|
|
314
|
+
deps.clearMediaGroups();
|
|
315
|
+
deps.clearTextGroups();
|
|
316
|
+
};
|
|
317
|
+
}
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Proof-only candidate planning for inactive Telegram Workspace Thread cleanup
|
|
3
|
+
* Zones: telegram threads, workspace lifecycle
|
|
4
|
+
* Owns fail-closed cleanup eligibility projection without persistence or Bot API effects
|
|
5
|
+
*/
|
|
6
|
+
import type { TelegramWorkspaceDeletionPermit, TelegramWorkspaceDestructiveFence, TelegramWorkspaceRetirementFence } from "./workspace-admission.ts";
|
|
7
|
+
export interface TelegramThreadCleanupBindingSnapshot {
|
|
8
|
+
cwd: string;
|
|
9
|
+
workspaceKey: string;
|
|
10
|
+
sessionId?: string;
|
|
11
|
+
sessionKey?: string;
|
|
12
|
+
instanceSlot: string;
|
|
13
|
+
slot?: string;
|
|
14
|
+
bindingKey: string;
|
|
15
|
+
target: {
|
|
16
|
+
chatId: number;
|
|
17
|
+
threadId: number;
|
|
18
|
+
};
|
|
19
|
+
inactiveSinceMs?: number;
|
|
20
|
+
updatedAtMs: number;
|
|
21
|
+
}
|
|
22
|
+
export type TelegramThreadCleanupProtectionState = "clear" | "protected" | "unknown";
|
|
23
|
+
export interface TelegramThreadCleanupProtectionEvidence {
|
|
24
|
+
bindingKey: string;
|
|
25
|
+
target: {
|
|
26
|
+
chatId: number;
|
|
27
|
+
threadId: number;
|
|
28
|
+
};
|
|
29
|
+
liveOwner: TelegramThreadCleanupProtectionState;
|
|
30
|
+
acceptedWork: TelegramThreadCleanupProtectionState;
|
|
31
|
+
deliveryAuthority: TelegramThreadCleanupProtectionState;
|
|
32
|
+
}
|
|
33
|
+
export interface TelegramThreadCleanupCandidate {
|
|
34
|
+
profileName: string;
|
|
35
|
+
bindingKey: string;
|
|
36
|
+
cwd: string;
|
|
37
|
+
workspaceKey: string;
|
|
38
|
+
sessionId?: string;
|
|
39
|
+
sessionKey?: string;
|
|
40
|
+
instanceSlot: string;
|
|
41
|
+
slot: string;
|
|
42
|
+
target: {
|
|
43
|
+
chatId: number;
|
|
44
|
+
threadId: number;
|
|
45
|
+
};
|
|
46
|
+
inactiveSinceMs: number;
|
|
47
|
+
bindingUpdatedAtMs: number;
|
|
48
|
+
}
|
|
49
|
+
/** Returns no candidates when any identity/evidence ambiguity exists. */
|
|
50
|
+
export declare function planTelegramInactiveThreadCleanup(input: {
|
|
51
|
+
profileName: string;
|
|
52
|
+
bindings: readonly TelegramThreadCleanupBindingSnapshot[];
|
|
53
|
+
protection: readonly TelegramThreadCleanupProtectionEvidence[];
|
|
54
|
+
reservedTargets?: readonly {
|
|
55
|
+
chatId: number;
|
|
56
|
+
threadId: number;
|
|
57
|
+
}[];
|
|
58
|
+
provisioningTargets?: readonly {
|
|
59
|
+
chatId: number;
|
|
60
|
+
threadId: number;
|
|
61
|
+
}[];
|
|
62
|
+
cleanupTargets?: readonly {
|
|
63
|
+
chatId: number;
|
|
64
|
+
threadId: number;
|
|
65
|
+
}[];
|
|
66
|
+
}): TelegramThreadCleanupCandidate[];
|
|
67
|
+
export declare function captureTelegramInactiveThreadCleanupEvidence<TBinding extends TelegramThreadCleanupBindingSnapshot>(input: {
|
|
68
|
+
profileName: string;
|
|
69
|
+
listBindings(): readonly TBinding[];
|
|
70
|
+
getProtection(binding: TBinding): {
|
|
71
|
+
liveOwner: TelegramThreadCleanupProtectionState;
|
|
72
|
+
acceptedWork: TelegramThreadCleanupProtectionState;
|
|
73
|
+
deliveryAuthority: TelegramThreadCleanupProtectionState;
|
|
74
|
+
};
|
|
75
|
+
listReservations(): readonly {
|
|
76
|
+
target: {
|
|
77
|
+
chatId: number;
|
|
78
|
+
threadId: number;
|
|
79
|
+
};
|
|
80
|
+
}[];
|
|
81
|
+
listPendingProvisions(): readonly {
|
|
82
|
+
target?: {
|
|
83
|
+
chatId: number;
|
|
84
|
+
threadId: number;
|
|
85
|
+
};
|
|
86
|
+
}[];
|
|
87
|
+
listPendingCleanups(): readonly {
|
|
88
|
+
target: {
|
|
89
|
+
chatId: number;
|
|
90
|
+
threadId: number;
|
|
91
|
+
};
|
|
92
|
+
}[];
|
|
93
|
+
}): Parameters<typeof planTelegramInactiveThreadCleanup>[0];
|
|
94
|
+
export declare function createTelegramInactiveThreadCleanupReviewRuntime<TBinding extends TelegramThreadCleanupBindingSnapshot>(deps: {
|
|
95
|
+
getProfileName(): string;
|
|
96
|
+
listBindings(): readonly TBinding[];
|
|
97
|
+
getProtection(binding: TBinding): {
|
|
98
|
+
liveOwner: TelegramThreadCleanupProtectionState;
|
|
99
|
+
acceptedWork: TelegramThreadCleanupProtectionState;
|
|
100
|
+
deliveryAuthority: TelegramThreadCleanupProtectionState;
|
|
101
|
+
};
|
|
102
|
+
listReservations(): readonly {
|
|
103
|
+
target: {
|
|
104
|
+
chatId: number;
|
|
105
|
+
threadId: number;
|
|
106
|
+
};
|
|
107
|
+
}[];
|
|
108
|
+
listPendingProvisions(): readonly {
|
|
109
|
+
target?: {
|
|
110
|
+
chatId: number;
|
|
111
|
+
threadId: number;
|
|
112
|
+
};
|
|
113
|
+
}[];
|
|
114
|
+
listPendingCleanups(): readonly {
|
|
115
|
+
target: {
|
|
116
|
+
chatId: number;
|
|
117
|
+
threadId: number;
|
|
118
|
+
};
|
|
119
|
+
}[];
|
|
120
|
+
getWorkStore(): TelegramThreadCleanupWorkStore;
|
|
121
|
+
runWorkspaceOperation<T>(input: {
|
|
122
|
+
operationId: string;
|
|
123
|
+
operationKind: string;
|
|
124
|
+
scopes: readonly [{
|
|
125
|
+
kind: "profile";
|
|
126
|
+
}];
|
|
127
|
+
}, operation: () => Promise<T>): Promise<T>;
|
|
128
|
+
}): {
|
|
129
|
+
review(): Promise<{
|
|
130
|
+
count: number;
|
|
131
|
+
operationId?: string;
|
|
132
|
+
}>;
|
|
133
|
+
};
|
|
134
|
+
export type TelegramThreadCleanupWorkState = "prepared" | "outcome-unknown" | "deleted";
|
|
135
|
+
export type TelegramThreadCleanupWorkEntry = TelegramThreadCleanupCandidate & {
|
|
136
|
+
state: TelegramThreadCleanupWorkState;
|
|
137
|
+
updatedAtMs: number;
|
|
138
|
+
issuedAtMs?: number;
|
|
139
|
+
permitOperationId?: string;
|
|
140
|
+
permitIntentId?: string;
|
|
141
|
+
permitLeaderEpoch?: number | string;
|
|
142
|
+
deletedAtMs?: number;
|
|
143
|
+
};
|
|
144
|
+
export interface TelegramThreadCleanupWorkSet {
|
|
145
|
+
operationId: string;
|
|
146
|
+
createdAtMs: number;
|
|
147
|
+
entries: TelegramThreadCleanupWorkEntry[];
|
|
148
|
+
}
|
|
149
|
+
export type TelegramThreadCleanupDeletionPermit = TelegramWorkspaceDeletionPermit;
|
|
150
|
+
export type TelegramThreadCleanupFence = TelegramWorkspaceRetirementFence;
|
|
151
|
+
export interface TelegramThreadCleanupWorkStore {
|
|
152
|
+
prepare(operationId: string, candidates: readonly TelegramThreadCleanupCandidate[]): {
|
|
153
|
+
prepared: boolean;
|
|
154
|
+
workSet: TelegramThreadCleanupWorkSet;
|
|
155
|
+
};
|
|
156
|
+
recordDeletionIssued(input: {
|
|
157
|
+
operationId: string;
|
|
158
|
+
bindingKey: string;
|
|
159
|
+
bindingUpdatedAtMs: number;
|
|
160
|
+
permit: TelegramThreadCleanupDeletionPermit;
|
|
161
|
+
}): {
|
|
162
|
+
recorded: boolean;
|
|
163
|
+
entry: TelegramThreadCleanupWorkEntry;
|
|
164
|
+
};
|
|
165
|
+
confirmDeleted(input: {
|
|
166
|
+
operationId: string;
|
|
167
|
+
bindingKey: string;
|
|
168
|
+
}): {
|
|
169
|
+
confirmed: boolean;
|
|
170
|
+
entry: TelegramThreadCleanupWorkEntry;
|
|
171
|
+
};
|
|
172
|
+
list(): TelegramThreadCleanupWorkSet[];
|
|
173
|
+
}
|
|
174
|
+
export declare function createTelegramThreadCleanupWorkStore(options: {
|
|
175
|
+
path: string;
|
|
176
|
+
profileName: string;
|
|
177
|
+
tokenSha256: string;
|
|
178
|
+
maxWorkSets?: number;
|
|
179
|
+
maxBytes?: number;
|
|
180
|
+
getNowMs?: () => number;
|
|
181
|
+
onPublicationBoundary?: (boundary: "after-write-before-rename" | "after-rename") => void;
|
|
182
|
+
}): TelegramThreadCleanupWorkStore;
|
|
183
|
+
export declare function commitTelegramInactiveThreadCleanup(input: {
|
|
184
|
+
store: TelegramThreadCleanupWorkStore;
|
|
185
|
+
operationId: string;
|
|
186
|
+
bindingKey: string;
|
|
187
|
+
commitBinding(): Promise<boolean>;
|
|
188
|
+
}): Promise<boolean>;
|
|
189
|
+
export declare function executeTelegramInactiveThreadCleanup(input: {
|
|
190
|
+
store: TelegramThreadCleanupWorkStore;
|
|
191
|
+
operationId: string;
|
|
192
|
+
bindingKey: string;
|
|
193
|
+
withWorkspaceDeletionBoundary<T>(operation: () => Promise<T>): Promise<T>;
|
|
194
|
+
loadFreshEvidence(): Promise<Parameters<typeof planTelegramInactiveThreadCleanup>[0]>;
|
|
195
|
+
acquireDeletionPermit(candidate: TelegramThreadCleanupCandidate): Promise<{
|
|
196
|
+
kind: "issued";
|
|
197
|
+
permit: TelegramThreadCleanupDeletionPermit;
|
|
198
|
+
} | {
|
|
199
|
+
kind: "blocked" | "already-issued";
|
|
200
|
+
}>;
|
|
201
|
+
deleteWithPermit(permit: TelegramThreadCleanupDeletionPermit, candidate: TelegramThreadCleanupCandidate): Promise<void>;
|
|
202
|
+
}): Promise<{
|
|
203
|
+
status: "deleted" | "blocked" | "outcome-unknown";
|
|
204
|
+
entry?: TelegramThreadCleanupWorkEntry;
|
|
205
|
+
}>;
|
|
206
|
+
export declare function createTelegramThreadCleanupPermitRuntime(deps: {
|
|
207
|
+
ledger: {
|
|
208
|
+
read(): {
|
|
209
|
+
fence?: TelegramWorkspaceDestructiveFence;
|
|
210
|
+
};
|
|
211
|
+
acquireThreadCleanupFence(input: {
|
|
212
|
+
operationId: string;
|
|
213
|
+
cleanupWorkSetId: string;
|
|
214
|
+
bindingKey: string;
|
|
215
|
+
slot: string;
|
|
216
|
+
target: {
|
|
217
|
+
chatId: number;
|
|
218
|
+
threadId: number;
|
|
219
|
+
};
|
|
220
|
+
leaderEpoch: number | string;
|
|
221
|
+
cleanupRequestedAtMs: number;
|
|
222
|
+
}): {
|
|
223
|
+
kind: "acquired";
|
|
224
|
+
fence: TelegramThreadCleanupFence;
|
|
225
|
+
resumed: boolean;
|
|
226
|
+
} | {
|
|
227
|
+
kind: "blocked";
|
|
228
|
+
reason: string;
|
|
229
|
+
};
|
|
230
|
+
adoptThreadCleanupFence(fence: TelegramThreadCleanupFence, replacement: {
|
|
231
|
+
owner: {
|
|
232
|
+
processId: number;
|
|
233
|
+
processBirthId: string;
|
|
234
|
+
};
|
|
235
|
+
leaderEpoch: number | string;
|
|
236
|
+
}): TelegramThreadCleanupFence;
|
|
237
|
+
issueThreadCleanupDeletionPermit(fence: TelegramThreadCleanupFence): {
|
|
238
|
+
kind: "issued";
|
|
239
|
+
fence: TelegramThreadCleanupFence;
|
|
240
|
+
permit: TelegramThreadCleanupDeletionPermit;
|
|
241
|
+
} | {
|
|
242
|
+
kind: "already-issued";
|
|
243
|
+
fence: TelegramThreadCleanupFence;
|
|
244
|
+
};
|
|
245
|
+
confirmThreadCleanupAbsence(fence: TelegramThreadCleanupFence): TelegramThreadCleanupFence;
|
|
246
|
+
releaseUnissuedThreadCleanupFence(fence: TelegramThreadCleanupFence): boolean;
|
|
247
|
+
completeThreadCleanupFence(fence: TelegramThreadCleanupFence): boolean;
|
|
248
|
+
};
|
|
249
|
+
getLeaderEpoch(): number | string | undefined;
|
|
250
|
+
getProfileName(): string;
|
|
251
|
+
getOwner(): {
|
|
252
|
+
processId: number;
|
|
253
|
+
processBirthId: string;
|
|
254
|
+
};
|
|
255
|
+
canAdoptFence(fence: TelegramThreadCleanupFence): boolean;
|
|
256
|
+
revalidateUnderFence(candidate: TelegramThreadCleanupCandidate): Promise<boolean>;
|
|
257
|
+
getNowMs?: () => number;
|
|
258
|
+
}): {
|
|
259
|
+
acquire(candidate: TelegramThreadCleanupCandidate, workSetId: string): Promise<{
|
|
260
|
+
kind: "issued";
|
|
261
|
+
fence: TelegramThreadCleanupFence;
|
|
262
|
+
permit: TelegramThreadCleanupDeletionPermit;
|
|
263
|
+
} | {
|
|
264
|
+
kind: "blocked";
|
|
265
|
+
} | {
|
|
266
|
+
kind: "already-issued";
|
|
267
|
+
}>;
|
|
268
|
+
diagnoseRecovery(candidate: TelegramThreadCleanupCandidate, workSetId: string): "none" | "fenced" | "deletion-issued" | "commit-ready" | "authority-blocked";
|
|
269
|
+
findCommitReady(candidate: TelegramThreadCleanupCandidate, workSetId: string): TelegramThreadCleanupFence | undefined;
|
|
270
|
+
settleDeleted(fence: TelegramThreadCleanupFence, commit: () => Promise<boolean>): Promise<"completed" | "commit-pending">;
|
|
271
|
+
};
|
|
272
|
+
export interface TelegramInactiveThreadCleanupCoordinatorDeps<TBinding> {
|
|
273
|
+
store: TelegramThreadCleanupWorkStore;
|
|
274
|
+
permitRuntime: ReturnType<typeof createTelegramThreadCleanupPermitRuntime>;
|
|
275
|
+
resolveFullBinding(candidate: TelegramThreadCleanupCandidate): Promise<TBinding | undefined>;
|
|
276
|
+
deleteWithPermit(permit: TelegramThreadCleanupDeletionPermit, candidate: TelegramThreadCleanupCandidate): Promise<void>;
|
|
277
|
+
commitBinding(candidate: TelegramThreadCleanupCandidate, currentBinding?: TBinding): Promise<boolean>;
|
|
278
|
+
}
|
|
279
|
+
export declare function createTelegramInactiveThreadCleanupSettingsPort<TBinding>(deps: TelegramInactiveThreadCleanupCoordinatorDeps<TBinding>): (operationId: string) => Promise<TelegramInactiveThreadCleanupResult>;
|
|
280
|
+
export interface TelegramInactiveThreadCleanupResult {
|
|
281
|
+
deleted: number;
|
|
282
|
+
outcomeUnknown: number;
|
|
283
|
+
blocked: number;
|
|
284
|
+
recovery?: "commit-ready" | "deletion-outcome-unknown" | "authority-blocked";
|
|
285
|
+
}
|
|
286
|
+
export declare function cleanReviewedInactiveThreads<TBinding>(input: {
|
|
287
|
+
operationId: string;
|
|
288
|
+
} & TelegramInactiveThreadCleanupCoordinatorDeps<TBinding>): Promise<TelegramInactiveThreadCleanupResult>;
|