@llblab/pi-kit 0.35.0 → 0.35.2
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 +2 -0
- package/CHANGELOG.md +11 -0
- package/README.md +1 -1
- package/node_modules/@llblab/pi-telegram/AGENTS.md +3 -2
- package/node_modules/@llblab/pi-telegram/BACKLOG.md +1 -6
- package/node_modules/@llblab/pi-telegram/CHANGELOG.md +18 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/bindings.d.ts +3 -2
- package/node_modules/@llblab/pi-telegram/dist/lib/bindings.js +11 -3
- package/node_modules/@llblab/pi-telegram/dist/lib/bus-api.js +2 -2
- package/node_modules/@llblab/pi-telegram/dist/lib/bus-follower.d.ts +22 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/bus-follower.js +75 -16
- package/node_modules/@llblab/pi-telegram/dist/lib/bus-leader.d.ts +1 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/bus-leader.js +11 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/bus.js +30 -19
- package/node_modules/@llblab/pi-telegram/dist/lib/commands.d.ts +2 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/commands.js +21 -8
- package/node_modules/@llblab/pi-telegram/dist/lib/extension.js +61 -46
- package/node_modules/@llblab/pi-telegram/dist/lib/journal.d.ts +7 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/journal.js +80 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/logging.d.ts +36 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/logging.js +80 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/outbound-attachments.d.ts +12 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/outbound-attachments.js +33 -3
- package/node_modules/@llblab/pi-telegram/dist/lib/paths.d.ts +2 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/paths.js +18 -3
- package/node_modules/@llblab/pi-telegram/dist/lib/polling.d.ts +13 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/polling.js +33 -12
- package/node_modules/@llblab/pi-telegram/dist/lib/queue.d.ts +6 -2
- package/node_modules/@llblab/pi-telegram/dist/lib/queue.js +46 -4
- package/node_modules/@llblab/pi-telegram/dist/lib/replies.d.ts +5 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/replies.js +2 -2
- package/node_modules/@llblab/pi-telegram/dist/lib/routing.d.ts +2 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/routing.js +137 -13
- package/node_modules/@llblab/pi-telegram/dist/lib/runtime.d.ts +11 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/runtime.js +31 -8
- package/node_modules/@llblab/pi-telegram/dist/lib/telegram-api.d.ts +12 -5
- package/node_modules/@llblab/pi-telegram/dist/lib/telegram-api.js +21 -3
- package/node_modules/@llblab/pi-telegram/dist/lib/thread-reconciler.js +5 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/threads.d.ts +5 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/threads.js +26 -13
- package/node_modules/@llblab/pi-telegram/dist/lib/workspace-retirement.d.ts +6 -2
- package/node_modules/@llblab/pi-telegram/dist/lib/workspace-retirement.js +57 -12
- package/node_modules/@llblab/pi-telegram/dist/package.json +1 -1
- package/node_modules/@llblab/pi-telegram/docs/architecture.md +5 -1
- package/node_modules/@llblab/pi-telegram/docs/multi-instance-bus.md +6 -2
- package/node_modules/@llblab/pi-telegram/docs/outbound.md +4 -2
- package/node_modules/@llblab/pi-telegram/lib/bindings.ts +13 -2
- package/node_modules/@llblab/pi-telegram/lib/bus-api.ts +2 -1
- package/node_modules/@llblab/pi-telegram/lib/bus-follower.ts +111 -15
- package/node_modules/@llblab/pi-telegram/lib/bus-leader.ts +15 -0
- package/node_modules/@llblab/pi-telegram/lib/bus.ts +30 -21
- package/node_modules/@llblab/pi-telegram/lib/commands.ts +21 -8
- package/node_modules/@llblab/pi-telegram/lib/extension.ts +64 -56
- package/node_modules/@llblab/pi-telegram/lib/journal.ts +80 -0
- package/node_modules/@llblab/pi-telegram/lib/logging.ts +122 -0
- package/node_modules/@llblab/pi-telegram/lib/outbound-attachments.ts +36 -3
- package/node_modules/@llblab/pi-telegram/lib/paths.ts +22 -3
- package/node_modules/@llblab/pi-telegram/lib/polling.ts +44 -10
- package/node_modules/@llblab/pi-telegram/lib/queue.ts +43 -4
- package/node_modules/@llblab/pi-telegram/lib/replies.ts +3 -2
- package/node_modules/@llblab/pi-telegram/lib/routing.ts +140 -18
- package/node_modules/@llblab/pi-telegram/lib/runtime.ts +40 -7
- package/node_modules/@llblab/pi-telegram/lib/telegram-api.ts +30 -10
- package/node_modules/@llblab/pi-telegram/lib/thread-reconciler.ts +6 -1
- package/node_modules/@llblab/pi-telegram/lib/threads.ts +37 -20
- package/node_modules/@llblab/pi-telegram/lib/workspace-retirement.ts +69 -17
- package/node_modules/@llblab/pi-telegram/package.json +1 -1
- package/package.json +2 -2
|
@@ -1667,7 +1667,7 @@ export function createTelegramCommandHandler(deps) {
|
|
|
1667
1667
|
const captured = { ...deps }, parsed = { ...command }, message = parsed.name === "status"
|
|
1668
1668
|
? copySelectedMenuCarrier(original)
|
|
1669
1669
|
: copySelectedReplyAnchor(original);
|
|
1670
|
-
const assertRecipientCurrent = admission.assertRecipientCurrent, report = admission.reportCompleted;
|
|
1670
|
+
const assertRecipientCurrent = admission.assertRecipientCurrent, report = admission.reportCompleted, beforeEffects = admission.beforeEffects;
|
|
1671
1671
|
const effects = parsed.name === "status"
|
|
1672
1672
|
? undefined
|
|
1673
1673
|
: {
|
|
@@ -1693,8 +1693,13 @@ export function createTelegramCommandHandler(deps) {
|
|
|
1693
1693
|
throw new Error(`Selected ${parsed.name} plan owner is unavailable.`);
|
|
1694
1694
|
};
|
|
1695
1695
|
return issueSelectedOnce(assertCurrent, async () => {
|
|
1696
|
-
if (plan.completion === "before-effect"
|
|
1697
|
-
|
|
1696
|
+
if (plan.completion === "before-effect") {
|
|
1697
|
+
if (!report.call(admission))
|
|
1698
|
+
return false;
|
|
1699
|
+
if (beforeEffects)
|
|
1700
|
+
await beforeEffects();
|
|
1701
|
+
assertRecipientCurrent();
|
|
1702
|
+
}
|
|
1698
1703
|
return prepared.run({
|
|
1699
1704
|
message,
|
|
1700
1705
|
command: parsed,
|
|
@@ -1720,7 +1725,7 @@ export function createTelegramCommandHandler(deps) {
|
|
|
1720
1725
|
...copySelectedReplyAnchor(original),
|
|
1721
1726
|
from: { ...original.from },
|
|
1722
1727
|
};
|
|
1723
|
-
const assertRecipientCurrent = admission.assertRecipientCurrent, reportCompleted = admission.reportCompleted;
|
|
1728
|
+
const assertRecipientCurrent = admission.assertRecipientCurrent, reportCompleted = admission.reportCompleted, beforeEffects = admission.beforeEffects;
|
|
1724
1729
|
const assertAdmissionCurrent = createSelectedSemanticGuard(admission, () => captured.assertExecutionCurrent?.(original), false);
|
|
1725
1730
|
const assertSemanticCurrent = () => {
|
|
1726
1731
|
assertAdmissionCurrent();
|
|
@@ -1732,7 +1737,7 @@ export function createTelegramCommandHandler(deps) {
|
|
|
1732
1737
|
persistAllowedUserId: async () => {
|
|
1733
1738
|
throw new Error(`Selected ${name} cannot acquire pairing authority.`);
|
|
1734
1739
|
},
|
|
1735
|
-
}, { assertSemanticCurrent, assertRecipientCurrent, reportCompleted }));
|
|
1740
|
+
}, { assertSemanticCurrent, assertRecipientCurrent, reportCompleted, beforeEffects }));
|
|
1736
1741
|
};
|
|
1737
1742
|
const prepareSelectedConfirmation = (name) => (command, messages, ctx, admission) => {
|
|
1738
1743
|
const original = messages[0], captured = { ...deps };
|
|
@@ -2036,7 +2041,7 @@ export function createTelegramCommandHandler(deps) {
|
|
|
2036
2041
|
typeof admission.reportCompleted !== "function")
|
|
2037
2042
|
return undefined;
|
|
2038
2043
|
const assertSourceCurrent = admission.assertSourceCurrent, assertRecipientCurrent = admission.assertRecipientCurrent;
|
|
2039
|
-
const reportCompleted = admission.reportCompleted, assertExecutionCurrent = deps.assertExecutionCurrent;
|
|
2044
|
+
const reportCompleted = admission.reportCompleted, beforeEffects = admission.beforeEffects, assertExecutionCurrent = deps.assertExecutionCurrent;
|
|
2040
2045
|
const assertIssuanceCurrent = () => {
|
|
2041
2046
|
assertSourceCurrent.call(admission);
|
|
2042
2047
|
assertExecutionCurrent?.call(deps, original);
|
|
@@ -2060,8 +2065,14 @@ export function createTelegramCommandHandler(deps) {
|
|
|
2060
2065
|
openSettingsMenu: menuDeps.openSettingsMenu &&
|
|
2061
2066
|
((value, context) => menuDeps.openSettingsMenu(value, context, recipientOptions)),
|
|
2062
2067
|
};
|
|
2063
|
-
return issueSelectedOnce(assertIssuanceCurrent, async () =>
|
|
2064
|
-
|
|
2068
|
+
return issueSelectedOnce(assertIssuanceCurrent, async () => {
|
|
2069
|
+
if (!reportCompleted.call(admission))
|
|
2070
|
+
return false;
|
|
2071
|
+
if (beforeEffects)
|
|
2072
|
+
await beforeEffects();
|
|
2073
|
+
assertRecipientCurrent();
|
|
2074
|
+
return handleTelegramCommandRuntime(name, message, ctx, recipientDeps, args);
|
|
2075
|
+
});
|
|
2065
2076
|
},
|
|
2066
2077
|
});
|
|
2067
2078
|
}
|
|
@@ -2157,6 +2168,8 @@ async function handleTelegramHelpCommand(commandName, message, ctx, deps, admiss
|
|
|
2157
2168
|
assertSemanticCurrent();
|
|
2158
2169
|
if (!admission.reportCompleted())
|
|
2159
2170
|
return false;
|
|
2171
|
+
if (admission.beforeEffects)
|
|
2172
|
+
await admission.beforeEffects();
|
|
2160
2173
|
admission.assertRecipientCurrent();
|
|
2161
2174
|
}
|
|
2162
2175
|
const assertEffectCurrent = admission?.assertRecipientCurrent ??
|
|
@@ -658,6 +658,12 @@ export default function (pi) {
|
|
|
658
658
|
const promptDispatchRuntime = Runtime.createTelegramPromptDispatchRuntime({
|
|
659
659
|
lifecycle,
|
|
660
660
|
typing,
|
|
661
|
+
failureLog: runtimeDiagnostics.createFailureLog({
|
|
662
|
+
category: "typing",
|
|
663
|
+
failurePhase: "typing-presence",
|
|
664
|
+
outcomePhase: "typing-presence-recovered",
|
|
665
|
+
label: "typing presence",
|
|
666
|
+
}),
|
|
661
667
|
getDefaultChatId: proactivePushChatIdGetter,
|
|
662
668
|
isThreadedMode() {
|
|
663
669
|
return deliveryTargetPolicyRuntime.getTargetPolicyView().threaded === true;
|
|
@@ -1033,6 +1039,29 @@ export default function (pi) {
|
|
|
1033
1039
|
delivery: deliveryLifecycleRuntime,
|
|
1034
1040
|
api: telegramApiTargetActivityRuntime,
|
|
1035
1041
|
});
|
|
1042
|
+
const prepareTemporaryThreadJournalInspection = function (target, requiredJournalBindingKeys, ownInputs) {
|
|
1043
|
+
const binding = resolveTelegramUpdateJournalBinding();
|
|
1044
|
+
const botToken = configStore.getBotToken();
|
|
1045
|
+
if (!binding || !botToken)
|
|
1046
|
+
return undefined;
|
|
1047
|
+
const profile = configStore.getActiveProfileName() ?? "default";
|
|
1048
|
+
const botIdentity = Journal.createTelegramUpdateJournalBotIdentity({ botToken, botId: getTelegramBotId() });
|
|
1049
|
+
return {
|
|
1050
|
+
directory: Paths.resolveTelegramTempDir(), profile, botIdentity,
|
|
1051
|
+
pollingPath: telegramLeaderJournalPath.resolve(configStore.getActiveProfileName()),
|
|
1052
|
+
requiredJournalBindingKeys: [binding.recoveryKey, ...requiredJournalBindingKeys],
|
|
1053
|
+
...(target.threadId !== undefined && ownInputs
|
|
1054
|
+
? { cleanup: { target: { chatId: target.chatId, threadId: target.threadId }, ownInputs } } : {}),
|
|
1055
|
+
limits: { maxDirectoryEntries: 10_000, maxFiles: 4096, maxBytes: 64 * 1024 * 1024,
|
|
1056
|
+
maxEntries: 10_000, maxWork: 100_000 },
|
|
1057
|
+
withSourceReference(path, operation) {
|
|
1058
|
+
return telegramJournalReferenceRegistry.withReference({
|
|
1059
|
+
referenceClass: "operator-disposition",
|
|
1060
|
+
recoveryKey: Journal.createTelegramUpdateJournalBindingKey({ path, profileName: profile, botIdentity }),
|
|
1061
|
+
}, operation);
|
|
1062
|
+
},
|
|
1063
|
+
};
|
|
1064
|
+
};
|
|
1036
1065
|
const inboundRouteRuntime = Routing.createTelegramInboundRouteRuntime({
|
|
1037
1066
|
configStore,
|
|
1038
1067
|
callApi: callTelegramApi,
|
|
@@ -1112,53 +1141,24 @@ export default function (pi) {
|
|
|
1112
1141
|
});
|
|
1113
1142
|
},
|
|
1114
1143
|
inspectTemporaryThreadSources(target, requiredJournalBindingKeys, ownInputs) {
|
|
1115
|
-
const
|
|
1116
|
-
|
|
1117
|
-
if (!binding || !botToken)
|
|
1144
|
+
const input = prepareTemporaryThreadJournalInspection(target, requiredJournalBindingKeys, ownInputs);
|
|
1145
|
+
if (!input)
|
|
1118
1146
|
return undefined;
|
|
1119
|
-
const profile = configStore.getActiveProfileName() ?? "default";
|
|
1120
|
-
const botIdentity = Journal.createTelegramUpdateJournalBotIdentity({
|
|
1121
|
-
botToken,
|
|
1122
|
-
botId: getTelegramBotId(),
|
|
1123
|
-
});
|
|
1124
1147
|
return withTelegramJournalSourceSerialization(function () {
|
|
1125
|
-
return Journal.isTelegramThreadCleanupJournalNamespaceClear(
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
},
|
|
1140
|
-
}
|
|
1141
|
-
: {}),
|
|
1142
|
-
limits: {
|
|
1143
|
-
maxDirectoryEntries: 10_000,
|
|
1144
|
-
maxFiles: 4096,
|
|
1145
|
-
maxBytes: 64 * 1024 * 1024,
|
|
1146
|
-
maxEntries: 10_000,
|
|
1147
|
-
maxWork: 100_000,
|
|
1148
|
-
},
|
|
1149
|
-
withSourceReference(path, operation) {
|
|
1150
|
-
return telegramJournalReferenceRegistry.withReference({
|
|
1151
|
-
referenceClass: "operator-disposition",
|
|
1152
|
-
recoveryKey: Journal.createTelegramUpdateJournalBindingKey({
|
|
1153
|
-
path,
|
|
1154
|
-
profileName: profile,
|
|
1155
|
-
botIdentity,
|
|
1156
|
-
}),
|
|
1157
|
-
}, operation);
|
|
1158
|
-
},
|
|
1159
|
-
})
|
|
1160
|
-
? []
|
|
1161
|
-
: undefined;
|
|
1148
|
+
return Journal.isTelegramThreadCleanupJournalNamespaceClear(input) ? [] : undefined;
|
|
1149
|
+
});
|
|
1150
|
+
},
|
|
1151
|
+
recoverTemporaryThreadDeadQueueOwners(target, requiredJournalBindingKeys, ownInputs, isCurrent) {
|
|
1152
|
+
const input = prepareTemporaryThreadJournalInspection(target, requiredJournalBindingKeys, ownInputs);
|
|
1153
|
+
if (!input || !isCurrent())
|
|
1154
|
+
return 0;
|
|
1155
|
+
return Journal.recoverTelegramThreadCleanupDeadQueueOwners({
|
|
1156
|
+
...input, isCurrent, withSourceSerialization: withTelegramJournalSourceSerialization,
|
|
1157
|
+
recoveryOwner: {
|
|
1158
|
+
instanceId: telegramInstanceId, processId: telegramProcessId,
|
|
1159
|
+
processBirthId: telegramQueueProcessBirthId,
|
|
1160
|
+
sessionGeneration: telegramSessionContextStore.getGeneration(),
|
|
1161
|
+
},
|
|
1162
1162
|
});
|
|
1163
1163
|
},
|
|
1164
1164
|
hasWorkspaceRestoreAuthority() {
|
|
@@ -1252,12 +1252,14 @@ export default function (pi) {
|
|
|
1252
1252
|
if (telegramBusFollowerRegistrationState.isRegistered() ||
|
|
1253
1253
|
target.threadId === undefined)
|
|
1254
1254
|
return undefined;
|
|
1255
|
+
// Pi creates a fresh context object per event and command, so authority follows the session, not the object.
|
|
1256
|
+
const sessionGeneration = telegramSessionContextStore.getGeneration();
|
|
1255
1257
|
return Threads.createTelegramWorkspaceThreadRenameRecipient({
|
|
1256
1258
|
store: threadStore,
|
|
1257
1259
|
instanceId: telegramInstanceId,
|
|
1258
1260
|
target,
|
|
1259
1261
|
assertAuthority() {
|
|
1260
|
-
if (telegramSessionContextStore.
|
|
1262
|
+
if (!telegramSessionContextStore.isCurrent(ctx, sessionGeneration))
|
|
1261
1263
|
throw new Error("Telegram Thread name dialog lost Pi context.");
|
|
1262
1264
|
},
|
|
1263
1265
|
getAuthority: threadNameRecipientAuthority.getAuthority,
|
|
@@ -1571,6 +1573,12 @@ export default function (pi) {
|
|
|
1571
1573
|
});
|
|
1572
1574
|
const telegramBusFollowerAssembly = BusFollower.createTelegramBusFollowerRuntimeAssembly({
|
|
1573
1575
|
instanceId: telegramInstanceId,
|
|
1576
|
+
failureLog: runtimeDiagnostics.createFailureLog({
|
|
1577
|
+
category: "bus",
|
|
1578
|
+
failurePhase: "follower-heartbeat",
|
|
1579
|
+
outcomePhase: "follower-heartbeat-recovered",
|
|
1580
|
+
label: "follower heartbeat",
|
|
1581
|
+
}),
|
|
1574
1582
|
registrationState: telegramBusFollowerRegistrationState,
|
|
1575
1583
|
recordRuntimeEvent,
|
|
1576
1584
|
receiver: {
|
|
@@ -1900,6 +1908,12 @@ export default function (pi) {
|
|
|
1900
1908
|
});
|
|
1901
1909
|
const telegramThreadCapabilityRuntime = Polling.createTelegramThreadCapabilityOrchestration({
|
|
1902
1910
|
state: telegramThreadCapabilityState,
|
|
1911
|
+
failureLog: runtimeDiagnostics.createFailureLog({
|
|
1912
|
+
category: "bus",
|
|
1913
|
+
failurePhase: "capability-monitor",
|
|
1914
|
+
outcomePhase: "capability-monitor-recovered",
|
|
1915
|
+
label: "Threaded Mode capability probe",
|
|
1916
|
+
}),
|
|
1903
1917
|
getAllowedUserId: configStore.getAllowedUserId,
|
|
1904
1918
|
callApi: callTelegramApi,
|
|
1905
1919
|
topicTargetStore: threadStore,
|
|
@@ -2485,6 +2499,7 @@ export default function (pi) {
|
|
|
2485
2499
|
deleteMessage: deleteTelegramMessage,
|
|
2486
2500
|
sendGuestReply,
|
|
2487
2501
|
editGuestReply,
|
|
2502
|
+
editGuestInlineMessage: telegramApiRuntime.editGuestInlineMessage,
|
|
2488
2503
|
stopGuestPlaceholder: guestPlaceholderRuntime.stop,
|
|
2489
2504
|
finalizeMarkdownPreview,
|
|
2490
2505
|
preparePreviewDelivery,
|
|
@@ -751,6 +751,13 @@ export declare function isTelegramThreadCleanupJournalNamespaceClear(input: Tele
|
|
|
751
751
|
}[];
|
|
752
752
|
};
|
|
753
753
|
}): boolean;
|
|
754
|
+
/** Demand-driven recovery only; caller holds temporary-cleanup admission. Never a deletion grant. */
|
|
755
|
+
export declare function recoverTelegramThreadCleanupDeadQueueOwners(input: Parameters<typeof isTelegramThreadCleanupJournalNamespaceClear>[0] & {
|
|
756
|
+
recoveryOwner: TelegramUpdateJournalQueueOwnerIdentity;
|
|
757
|
+
isCurrent: () => boolean;
|
|
758
|
+
withSourceSerialization: NonNullable<TelegramUpdateJournalStoreOptions["withSourceSerialization"]>;
|
|
759
|
+
getQueueProcessLiveness?: TelegramUpdateJournalStoreOptions["getQueueProcessLiveness"];
|
|
760
|
+
}): number;
|
|
754
761
|
export declare function publishTelegramUpdateJournalSegment(path: string, segment: TelegramUpdateJournalSegment): TelegramUpdateJournalSegmentPublicationResult;
|
|
755
762
|
export declare function createTelegramUpdateQueueHandoffToken(): string;
|
|
756
763
|
export declare function createTelegramUpdateJournalBotIdentity(input: {
|
|
@@ -1988,6 +1988,86 @@ export function isTelegramThreadCleanupJournalNamespaceClear(input) {
|
|
|
1988
1988
|
return namespace.sources.every((source) => source.evidence.kind === "absent" ||
|
|
1989
1989
|
source.evidence.file.entries.every((entry) => isUnrelatedPlainInput(entry, source.path)));
|
|
1990
1990
|
}
|
|
1991
|
+
/** Demand-driven recovery only; caller holds temporary-cleanup admission. Never a deletion grant. */
|
|
1992
|
+
export function recoverTelegramThreadCleanupDeadQueueOwners(input) {
|
|
1993
|
+
return input.withSourceSerialization(() => {
|
|
1994
|
+
const assertCurrent = () => {
|
|
1995
|
+
if (!input.isCurrent())
|
|
1996
|
+
throw new Error("Temporary cleanup queue recovery lost authority.");
|
|
1997
|
+
};
|
|
1998
|
+
assertCurrent();
|
|
1999
|
+
if (!input.cleanup)
|
|
2000
|
+
return 0;
|
|
2001
|
+
// Validate exact references and complete namespace coverage before considering any mutation.
|
|
2002
|
+
if (isTelegramThreadCleanupJournalNamespaceClear(input))
|
|
2003
|
+
return 0;
|
|
2004
|
+
const namespace = inspectTelegramSessionJournalNamespace(input);
|
|
2005
|
+
if (namespace.retainedInputs?.some(value => value.state !== "committed") ||
|
|
2006
|
+
input.requiredJournalBindingKeys.some(key => !namespace.sources.some(source => source.path === getTelegramUpdateJournalBindingPath(key) && source.evidence.kind === "present")))
|
|
2007
|
+
return 0;
|
|
2008
|
+
const plans = [];
|
|
2009
|
+
for (const source of namespace.sources) {
|
|
2010
|
+
if (source.evidence.kind !== "present")
|
|
2011
|
+
continue;
|
|
2012
|
+
const file = source.evidence.file;
|
|
2013
|
+
if (file.version !== TELEGRAM_UPDATE_JOURNAL_VERSION)
|
|
2014
|
+
continue;
|
|
2015
|
+
const groups = new Map();
|
|
2016
|
+
for (const entry of file.entries) {
|
|
2017
|
+
if (!entry.queueReceiptId)
|
|
2018
|
+
continue;
|
|
2019
|
+
const group = groups.get(entry.queueReceiptId) ?? [];
|
|
2020
|
+
group.push(entry);
|
|
2021
|
+
groups.set(entry.queueReceiptId, group);
|
|
2022
|
+
}
|
|
2023
|
+
for (const group of groups.values()) {
|
|
2024
|
+
const entry = group[0];
|
|
2025
|
+
if (entry.state !== "queued" || !entry.queueReceiptId || !entry.queueOwner ||
|
|
2026
|
+
!entry.queueKind || entry.queueHandoff || entry.inputClaim)
|
|
2027
|
+
continue;
|
|
2028
|
+
if (group.some(value => value.state !== "queued" || value.queueHandoff || value.inputClaim ||
|
|
2029
|
+
value.queueKind !== entry.queueKind || !isDeepStrictEqual(value.queueOwner, entry.queueOwner)))
|
|
2030
|
+
continue;
|
|
2031
|
+
plans.push({ path: source.path, recovery: {
|
|
2032
|
+
queueKind: entry.queueKind, receiptId: entry.queueReceiptId,
|
|
2033
|
+
sourceUpdateIds: group.map(value => value.updateId).sort((a, b) => a - b),
|
|
2034
|
+
deadOwner: cloneJournalQueueOwner(entry.queueOwner), recoveryOwner: { ...input.recoveryOwner },
|
|
2035
|
+
} });
|
|
2036
|
+
}
|
|
2037
|
+
}
|
|
2038
|
+
const liveness = input.getQueueProcessLiveness ?? getTelegramProcessLiveness;
|
|
2039
|
+
const dead = plans.filter(plan => {
|
|
2040
|
+
assertCurrent();
|
|
2041
|
+
const result = liveness(plan.recovery.deadOwner);
|
|
2042
|
+
assertCurrent();
|
|
2043
|
+
return result === "dead";
|
|
2044
|
+
});
|
|
2045
|
+
let removed = 0;
|
|
2046
|
+
for (const plan of dead) {
|
|
2047
|
+
assertCurrent();
|
|
2048
|
+
removed += input.withSourceReference(plan.path, () => {
|
|
2049
|
+
const journal = createTelegramUpdateJournalStore({
|
|
2050
|
+
path: plan.path, profileName: input.profile, botIdentity: input.botIdentity,
|
|
2051
|
+
queueRuntimeIdentity: input.recoveryOwner,
|
|
2052
|
+
// This private synchronous store stays inside the cross-family serializer already held above.
|
|
2053
|
+
withSourceSerialization(operation) { assertCurrent(); return operation(); },
|
|
2054
|
+
sourceAccess: { directory: dirname(plan.path), limits: input.limits },
|
|
2055
|
+
getQueueProcessLiveness(owner) {
|
|
2056
|
+
assertCurrent();
|
|
2057
|
+
const result = liveness(owner);
|
|
2058
|
+
assertCurrent();
|
|
2059
|
+
return result;
|
|
2060
|
+
},
|
|
2061
|
+
withWriterAdmission(operation) { assertCurrent(); return operation(); },
|
|
2062
|
+
});
|
|
2063
|
+
// The journal transaction rechecks whole-group identity, absence of handoff and fresh process-death proof.
|
|
2064
|
+
return journal.recoverDeadQueueOwner(plan.recovery).recoveredUpdateIds.length;
|
|
2065
|
+
});
|
|
2066
|
+
}
|
|
2067
|
+
assertCurrent();
|
|
2068
|
+
return removed;
|
|
2069
|
+
});
|
|
2070
|
+
}
|
|
1991
2071
|
function cloneEntry(entry) {
|
|
1992
2072
|
return {
|
|
1993
2073
|
...entry,
|
|
@@ -4,6 +4,40 @@
|
|
|
4
4
|
* Owns bounded JSONL runtime evidence files, previous-log preservation, and profile-aware log paths without becoming routing state
|
|
5
5
|
*/
|
|
6
6
|
import * as Status from "./status.ts";
|
|
7
|
+
/** Sink shape shared with the runtime event recorder this domain already owns. */
|
|
8
|
+
export type TelegramFailureRecordSink = (category: string, errorOrMessage: unknown, details: Record<string, unknown>) => void;
|
|
9
|
+
export interface TelegramFailureRunSummary {
|
|
10
|
+
signature: string;
|
|
11
|
+
failedAttempts: number;
|
|
12
|
+
suppressedFailures: number;
|
|
13
|
+
failedForMs: number;
|
|
14
|
+
}
|
|
15
|
+
export interface TelegramFailureRunLog {
|
|
16
|
+
/** Records one failure; a repeated identical failure only extends the current run and returns false. */
|
|
17
|
+
record: (error: unknown, details?: Record<string, unknown>) => boolean;
|
|
18
|
+
/** Closes the run after a success, emitting its single outcome record. */
|
|
19
|
+
settled: () => TelegramFailureRunSummary | undefined;
|
|
20
|
+
/** Ends the run without an outcome record, for a stop or replacement that reports its own outcome. */
|
|
21
|
+
reset: () => void;
|
|
22
|
+
}
|
|
23
|
+
export interface TelegramFailureRunLogInput {
|
|
24
|
+
category: string;
|
|
25
|
+
failurePhase: string;
|
|
26
|
+
outcomePhase: string;
|
|
27
|
+
/** Human-readable subject for the outcome record, e.g. `follower heartbeat`. */
|
|
28
|
+
label: string;
|
|
29
|
+
record?: TelegramFailureRecordSink;
|
|
30
|
+
/** Static context reapplied to every record of this phase, e.g. a session or profile key. */
|
|
31
|
+
context?: () => Record<string, unknown>;
|
|
32
|
+
getNowMs?: () => number;
|
|
33
|
+
}
|
|
34
|
+
/** Stable identity of one failure kind: errno when present, otherwise the failure text. */
|
|
35
|
+
export declare function getTelegramFailureSignature(error: unknown): string;
|
|
36
|
+
/**
|
|
37
|
+
* Consecutive identical failure runs for one diagnostic phase: a repeat extends the run instead of logging again,
|
|
38
|
+
* and a closing success or reset emits at most one outcome record. This never gates detection or recovery.
|
|
39
|
+
*/
|
|
40
|
+
export declare function createTelegramFailureRunLog(input: TelegramFailureRunLogInput): TelegramFailureRunLog;
|
|
7
41
|
type TelegramLogPathInput = string | (() => string);
|
|
8
42
|
interface TelegramRuntimeJsonlEvent {
|
|
9
43
|
at: number;
|
|
@@ -47,6 +81,8 @@ interface TelegramRuntimeDiagnosticsStatusPorts<TContext> {
|
|
|
47
81
|
interface TelegramRuntimeDiagnosticsRuntime<TContext> {
|
|
48
82
|
events: Status.TelegramRuntimeEventRecorder;
|
|
49
83
|
recordRuntimeEvent(category: string, error: unknown, details?: Record<string, unknown>): void;
|
|
84
|
+
/** A phase-scoped failure log whose records travel through this runtime's own event pipeline. */
|
|
85
|
+
createFailureLog(input: Omit<TelegramFailureRunLogInput, "record">): TelegramFailureRunLog;
|
|
50
86
|
bindStorage(ports: {
|
|
51
87
|
getBotToken(): string | undefined;
|
|
52
88
|
getProfileName(): string | undefined;
|
|
@@ -8,6 +8,82 @@ import { basename, dirname, join } from "node:path";
|
|
|
8
8
|
import { withTelegramFileTransaction } from "./locks.js";
|
|
9
9
|
import { resolveAgentDir, resolveTelegramPreviousSharedRuntimeLogPath, resolveTelegramProfileTempFilePath, resolveTelegramRuntimeLogPath, } from "./paths.js";
|
|
10
10
|
import * as Status from "./status.js";
|
|
11
|
+
/** Stable identity of one failure kind: errno when present, otherwise the failure text. */
|
|
12
|
+
export function getTelegramFailureSignature(error) {
|
|
13
|
+
const code = error?.code;
|
|
14
|
+
if (typeof code === "string" && code)
|
|
15
|
+
return code;
|
|
16
|
+
return error instanceof Error ? error.message : String(error);
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Consecutive identical failure runs for one diagnostic phase: a repeat extends the run instead of logging again,
|
|
20
|
+
* and a closing success or reset emits at most one outcome record. This never gates detection or recovery.
|
|
21
|
+
*/
|
|
22
|
+
export function createTelegramFailureRunLog(input) {
|
|
23
|
+
const getNowMs = input.getNowMs ?? Date.now;
|
|
24
|
+
let run;
|
|
25
|
+
const emit = (errorOrMessage, details) => {
|
|
26
|
+
try {
|
|
27
|
+
input.record?.(input.category, errorOrMessage, {
|
|
28
|
+
...(input.context?.() ?? {}),
|
|
29
|
+
...details,
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
// A diagnostic sink never converts a recorded failure into another failure.
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
return {
|
|
37
|
+
record(error, details) {
|
|
38
|
+
const signature = getTelegramFailureSignature(error);
|
|
39
|
+
const admitted = { ...(details ?? {}) };
|
|
40
|
+
if (run?.signature === signature) {
|
|
41
|
+
run.suppressed += 1;
|
|
42
|
+
Object.assign(run.details, admitted);
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
const previous = run;
|
|
46
|
+
run = { signature, suppressed: 0, startedAtMs: getNowMs(), details: admitted };
|
|
47
|
+
emit(error, {
|
|
48
|
+
...admitted,
|
|
49
|
+
phase: input.failurePhase,
|
|
50
|
+
failureSignature: signature,
|
|
51
|
+
...(previous
|
|
52
|
+
? {
|
|
53
|
+
previousFailureSignature: previous.signature,
|
|
54
|
+
previousFailedAttempts: previous.suppressed + 1,
|
|
55
|
+
previousFailedForMs: getNowMs() - previous.startedAtMs,
|
|
56
|
+
}
|
|
57
|
+
: {}),
|
|
58
|
+
});
|
|
59
|
+
return true;
|
|
60
|
+
},
|
|
61
|
+
settled() {
|
|
62
|
+
const closed = run;
|
|
63
|
+
if (!closed)
|
|
64
|
+
return undefined;
|
|
65
|
+
run = undefined;
|
|
66
|
+
const summary = {
|
|
67
|
+
signature: closed.signature,
|
|
68
|
+
failedAttempts: closed.suppressed + 1,
|
|
69
|
+
suppressedFailures: closed.suppressed,
|
|
70
|
+
failedForMs: getNowMs() - closed.startedAtMs,
|
|
71
|
+
};
|
|
72
|
+
emit(`Telegram ${input.label} recovered after ${summary.failedAttempts} failed attempts.`, {
|
|
73
|
+
...closed.details,
|
|
74
|
+
phase: input.outcomePhase,
|
|
75
|
+
failureSignature: summary.signature,
|
|
76
|
+
failedAttempts: summary.failedAttempts,
|
|
77
|
+
suppressedFailures: summary.suppressedFailures,
|
|
78
|
+
failedForMs: summary.failedForMs,
|
|
79
|
+
});
|
|
80
|
+
return summary;
|
|
81
|
+
},
|
|
82
|
+
reset() {
|
|
83
|
+
run = undefined;
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
}
|
|
11
87
|
const DEFAULT_MAX_LOG_BYTES = 5 * 1024 * 1024;
|
|
12
88
|
export function getTelegramRuntimeLogPath(agentDir = resolveAgentDir(), profileName) {
|
|
13
89
|
return resolveTelegramProfileTempFilePath("logs", "jsonl", agentDir, profileName);
|
|
@@ -333,6 +409,10 @@ export function createTelegramRuntimeDiagnosticsRuntime(options = {}) {
|
|
|
333
409
|
};
|
|
334
410
|
return {
|
|
335
411
|
events,
|
|
412
|
+
createFailureLog: (input) => createTelegramFailureRunLog({
|
|
413
|
+
...input,
|
|
414
|
+
record: (category, errorOrMessage, details) => recordRuntimeEvent(category, errorOrMessage, details),
|
|
415
|
+
}),
|
|
336
416
|
recordRuntimeEvent,
|
|
337
417
|
bindStorage(ports) {
|
|
338
418
|
getBotToken = ports.getBotToken;
|
|
@@ -73,6 +73,7 @@ export interface TelegramQueuedOutboundAttachmentView {
|
|
|
73
73
|
export interface TelegramOutboundAttachmentQueueTargetView {
|
|
74
74
|
queuedAttachments: TelegramQueuedOutboundAttachmentView[];
|
|
75
75
|
guestQueryId?: string;
|
|
76
|
+
guestInlineMessageId?: string;
|
|
76
77
|
}
|
|
77
78
|
export interface TelegramQueuedOutboundAttachmentTurnView extends TelegramOutboundAttachmentQueueTargetView {
|
|
78
79
|
chatId: number;
|
|
@@ -161,6 +162,17 @@ export declare function queueTelegramOutboundAttachments(options: {
|
|
|
161
162
|
}): Promise<TelegramOutboundAttachmentToolResult>;
|
|
162
163
|
export declare function deliverTelegramGuestCachedAttachment(options: {
|
|
163
164
|
guestQueryId: string;
|
|
165
|
+
guestInlineMessageId?: string;
|
|
166
|
+
editGuestInlineMessage?: (inlineMessageId: string, content: {
|
|
167
|
+
media: {
|
|
168
|
+
type: "photo" | "document" | "audio";
|
|
169
|
+
media: string;
|
|
170
|
+
caption?: string;
|
|
171
|
+
};
|
|
172
|
+
}, options: {
|
|
173
|
+
assertAuthority: () => void;
|
|
174
|
+
}) => Promise<void>;
|
|
175
|
+
isDeliveryActive?: () => boolean;
|
|
164
176
|
stagingChatId: number;
|
|
165
177
|
stagingTarget?: TelegramTarget;
|
|
166
178
|
attachment: TelegramQueuedOutboundAttachmentView;
|
|
@@ -375,6 +375,9 @@ export async function queueTelegramOutboundAttachments(options) {
|
|
|
375
375
|
maxAttachmentSizeBytes: options.maxAttachmentSizeBytes,
|
|
376
376
|
statPath: options.statPath,
|
|
377
377
|
});
|
|
378
|
+
if (options.activeTurn.guestInlineMessageId && pendingAttachments.some((attachment) => getTelegramGuestAttachmentTransport(attachment.path).fileField === "voice")) {
|
|
379
|
+
throw new Error("Telegram Guest Mode cannot replace an inline placeholder with a voice note; no attachment was queued. Use MP3 audio or a normal bot chat.");
|
|
380
|
+
}
|
|
378
381
|
options.activeTurn.queuedAttachments.push(...pendingAttachments);
|
|
379
382
|
const added = pendingAttachments.map((attachment) => attachment.path);
|
|
380
383
|
return {
|
|
@@ -389,6 +392,16 @@ export async function queueTelegramOutboundAttachments(options) {
|
|
|
389
392
|
}
|
|
390
393
|
export async function deliverTelegramGuestCachedAttachment(options) {
|
|
391
394
|
const transport = getTelegramGuestAttachmentTransport(options.attachment.path);
|
|
395
|
+
const isActive = () => options.isDeliveryActive?.() !== false;
|
|
396
|
+
const assertActive = () => {
|
|
397
|
+
if (!isActive())
|
|
398
|
+
throw new Error("Guest attachment delivery lost authority.");
|
|
399
|
+
};
|
|
400
|
+
if (!isActive())
|
|
401
|
+
return;
|
|
402
|
+
if (options.guestInlineMessageId && (!options.editGuestInlineMessage || transport.fileField === "voice")) {
|
|
403
|
+
throw new Error("Guest inline attachment editing is unavailable for this media.");
|
|
404
|
+
}
|
|
392
405
|
let stagingMessageId;
|
|
393
406
|
let answerAttempted = false;
|
|
394
407
|
try {
|
|
@@ -397,6 +410,8 @@ export async function deliverTelegramGuestCachedAttachment(options) {
|
|
|
397
410
|
...getTelegramMultipartTargetFields(options.stagingTarget),
|
|
398
411
|
}, transport.fileField, options.attachment.path, options.attachment.fileName));
|
|
399
412
|
stagingMessageId = message.message_id;
|
|
413
|
+
if (!isActive())
|
|
414
|
+
return;
|
|
400
415
|
const caption = options.caption
|
|
401
416
|
? Array.from(options.caption).slice(0, 1024).join("")
|
|
402
417
|
: undefined;
|
|
@@ -446,11 +461,26 @@ export async function deliverTelegramGuestCachedAttachment(options) {
|
|
|
446
461
|
...(caption ? { caption } : {}),
|
|
447
462
|
};
|
|
448
463
|
}
|
|
464
|
+
if (!isActive())
|
|
465
|
+
return;
|
|
449
466
|
answerAttempted = true;
|
|
450
|
-
|
|
467
|
+
if (options.guestInlineMessageId && options.editGuestInlineMessage) {
|
|
468
|
+
if (result.type === "voice")
|
|
469
|
+
throw new Error("Voice notes cannot replace inline placeholders.");
|
|
470
|
+
const media = result.type === "photo" ? result.photo_file_id
|
|
471
|
+
: result.type === "audio" ? result.audio_file_id : result.document_file_id;
|
|
472
|
+
await options.editGuestInlineMessage(options.guestInlineMessageId, {
|
|
473
|
+
media: { type: result.type, media, ...(caption ? { caption } : {}) },
|
|
474
|
+
}, { assertAuthority: assertActive });
|
|
475
|
+
}
|
|
476
|
+
else {
|
|
477
|
+
await options.answerGuestQuery(options.guestQueryId, result);
|
|
478
|
+
}
|
|
451
479
|
}
|
|
452
480
|
catch (error) {
|
|
453
|
-
if (!
|
|
481
|
+
if (!isActive())
|
|
482
|
+
return;
|
|
483
|
+
if (!options.guestInlineMessageId && !answerAttempted && options.answerGuestText && options.fallbackText) {
|
|
454
484
|
answerAttempted = true;
|
|
455
485
|
await options.answerGuestText(options.guestQueryId, options.fallbackText);
|
|
456
486
|
}
|
|
@@ -459,7 +489,7 @@ export async function deliverTelegramGuestCachedAttachment(options) {
|
|
|
459
489
|
}
|
|
460
490
|
}
|
|
461
491
|
finally {
|
|
462
|
-
if (stagingMessageId !== undefined) {
|
|
492
|
+
if (stagingMessageId !== undefined && isActive()) {
|
|
463
493
|
try {
|
|
464
494
|
await options.deleteMessage(options.stagingChatId, stagingMessageId);
|
|
465
495
|
}
|
|
@@ -43,7 +43,8 @@ export declare function resolveLegacyTelegramOwnersPath(agentDir?: string): stri
|
|
|
43
43
|
export declare function resolveTelegramOwnersPath(): string;
|
|
44
44
|
export declare function getTelegramProfilePathSuffix(profileName?: string): string;
|
|
45
45
|
export declare function resolveTelegramProfileTempFilePath(baseName: string, extension: string, agentDir?: string, profileName?: string): string;
|
|
46
|
-
|
|
46
|
+
/** Shared state/log paths for every profile, under the resolved agent dir (honors `PI_CODING_AGENT_DIR`). */
|
|
47
|
+
export declare function getTelegramDiagnosticsDisplayPaths(_profileName?: string, agentDir?: string): {
|
|
47
48
|
state: string;
|
|
48
49
|
logs: string;
|
|
49
50
|
};
|
|
@@ -111,10 +111,25 @@ export function getTelegramProfilePathSuffix(profileName) {
|
|
|
111
111
|
export function resolveTelegramProfileTempFilePath(baseName, extension, agentDir = resolveAgentDir(), profileName) {
|
|
112
112
|
return join(resolveTelegramTempDir(agentDir), `${baseName}${getTelegramProfilePathSuffix(profileName)}.${extension}`);
|
|
113
113
|
}
|
|
114
|
-
|
|
114
|
+
/** Shared state/log paths for every profile, under the resolved agent dir (honors `PI_CODING_AGENT_DIR`). */
|
|
115
|
+
export function getTelegramDiagnosticsDisplayPaths(_profileName, agentDir = resolveAgentDir()) {
|
|
116
|
+
// Storage paths are canonical, so also match a canonical home (e.g. /home -> /var/home).
|
|
117
|
+
const homes = [homedir()];
|
|
118
|
+
try {
|
|
119
|
+
homes.push(realpathSync(homes[0]));
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
122
|
+
/* An unreadable home only loses the ~ abbreviation. */
|
|
123
|
+
}
|
|
124
|
+
const display = (path) => {
|
|
125
|
+
const home = homes.find((candidate) => path === candidate || path.startsWith(`${candidate}${sep}`));
|
|
126
|
+
return home
|
|
127
|
+
? `~${path.slice(home.length).split(sep).join("/")}`
|
|
128
|
+
: path;
|
|
129
|
+
};
|
|
115
130
|
return {
|
|
116
|
-
state:
|
|
117
|
-
logs:
|
|
131
|
+
state: display(resolveTelegramStatePath(agentDir)),
|
|
132
|
+
logs: display(resolveTelegramRuntimeLogPath(agentDir)),
|
|
118
133
|
};
|
|
119
134
|
}
|
|
120
135
|
/** Durable Workspace admission ledger (<agentDir>/tmp/pi-telegram/workspace-admission[.<profile>].json). */
|
|
@@ -4,6 +4,15 @@
|
|
|
4
4
|
* Owns polling request builders, stop conditions, and the long-poll loop runtime for Telegram updates
|
|
5
5
|
*/
|
|
6
6
|
type MaybePromise<T> = T | Promise<T>;
|
|
7
|
+
/**
|
|
8
|
+
* Structural port for run-collapsing diagnostics: a repeated identical failure extends its run instead of logging
|
|
9
|
+
* again, and the closing success records one outcome. The composition root owns the collapsed implementation.
|
|
10
|
+
*/
|
|
11
|
+
export interface TelegramRepeatedFailureLogPort {
|
|
12
|
+
record: (error: unknown, details?: Record<string, unknown>) => void;
|
|
13
|
+
settled: () => void;
|
|
14
|
+
reset: () => void;
|
|
15
|
+
}
|
|
7
16
|
export interface TelegramPollingConfig {
|
|
8
17
|
botToken?: string;
|
|
9
18
|
}
|
|
@@ -174,6 +183,8 @@ export interface TelegramStartupThreadCapabilityProbeDeps extends TelegramThread
|
|
|
174
183
|
getNowMs?: () => number;
|
|
175
184
|
}
|
|
176
185
|
export interface TelegramThreadCapabilityRuntimeDeps<TContext> extends TelegramThreadCapabilityReaderDeps {
|
|
186
|
+
/** Collapses one sustained probe failure run; the caller supplies its owner. */
|
|
187
|
+
failureLog: TelegramRepeatedFailureLogPort;
|
|
177
188
|
lifecycle?: TelegramThreadCapabilityLifecycle;
|
|
178
189
|
topicTargetStore: TelegramThreadCapabilityStore;
|
|
179
190
|
ownsLock: (ctx: TContext) => boolean;
|
|
@@ -251,6 +262,8 @@ export interface TelegramThreadAwarePollingDeps<TContext, TOwner> extends Telegr
|
|
|
251
262
|
setForceFreshLeaderThreadOnNextStart: (forceFresh: boolean) => void;
|
|
252
263
|
}
|
|
253
264
|
export interface TelegramThreadCapabilityOrchestrationDeps<TContext, TOwner> extends TelegramThreadCapabilityReaderDeps, TelegramThreadAwarePollingControls<TContext, TOwner> {
|
|
265
|
+
/** Collapses one sustained probe failure run; the composition root supplies its owner. */
|
|
266
|
+
failureLog: TelegramRepeatedFailureLogPort;
|
|
254
267
|
state: TelegramThreadCapabilityStateRuntime;
|
|
255
268
|
topicTargetStore: TelegramThreadCapabilityStore;
|
|
256
269
|
ownsLock: (ctx: TContext) => boolean;
|