@llblab/pi-telegram 0.54.4 → 0.55.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +4 -2
- package/BACKLOG.md +1 -1
- package/CHANGELOG.md +11 -0
- package/README.md +5 -3
- package/dist/lib/activity.d.ts +5 -1
- package/dist/lib/activity.js +31 -2
- package/dist/lib/bindings.d.ts +3 -1
- package/dist/lib/bindings.js +8 -2
- package/dist/lib/bus-api.d.ts +2 -2
- package/dist/lib/bus-api.js +7 -2
- package/dist/lib/bus-follower.d.ts +49 -3
- package/dist/lib/bus-follower.js +313 -146
- package/dist/lib/bus-leader.d.ts +3 -0
- package/dist/lib/bus-leader.js +99 -18
- package/dist/lib/bus.d.ts +5 -3
- package/dist/lib/bus.js +13 -10
- package/dist/lib/delivery.d.ts +4 -0
- package/dist/lib/delivery.js +48 -15
- package/dist/lib/extension.js +48 -6
- package/dist/lib/lifecycle.d.ts +68 -0
- package/dist/lib/lifecycle.js +178 -58
- package/dist/lib/machine.d.ts +23 -0
- package/dist/lib/machine.js +37 -0
- package/dist/lib/media.d.ts +114 -28
- package/dist/lib/media.js +292 -119
- package/dist/lib/model.d.ts +54 -15
- package/dist/lib/model.js +98 -53
- package/dist/lib/polling.js +2 -0
- package/dist/lib/preview.d.ts +128 -113
- package/dist/lib/preview.js +448 -366
- package/dist/lib/queue.d.ts +2 -2
- package/dist/lib/queue.js +3 -3
- package/dist/lib/routing.d.ts +6 -1
- package/dist/lib/routing.js +334 -210
- package/dist/lib/runtime.d.ts +77 -11
- package/dist/lib/runtime.js +174 -92
- package/dist/lib/sections.js +0 -1
- package/dist/lib/telegram-api.d.ts +11 -9
- package/dist/lib/telegram-api.js +53 -4
- package/dist/lib/text-groups.d.ts +0 -26
- package/dist/lib/text-groups.js +51 -98
- package/dist/lib/thread-display.js +14 -6
- package/dist/lib/threads.d.ts +18 -5
- package/dist/lib/threads.js +107 -30
- package/dist/lib/turns.d.ts +2 -0
- package/dist/lib/turns.js +6 -1
- package/dist/lib/updates.d.ts +11 -4
- package/dist/lib/updates.js +54 -19
- package/dist/package.json +1 -1
- package/docs/activity.md +1 -1
- package/docs/architecture.md +12 -11
- package/docs/delivery.md +7 -5
- package/docs/multi-instance-bus.md +18 -8
- package/docs/public-api.md +2 -2
- package/docs/ui-style.md +2 -2
- package/lib/activity.ts +29 -5
- package/lib/bindings.ts +9 -1
- package/lib/bus-api.ts +11 -3
- package/lib/bus-follower.ts +417 -203
- package/lib/bus-leader.ts +88 -14
- package/lib/bus.ts +17 -15
- package/lib/delivery.ts +47 -19
- package/lib/extension.ts +51 -9
- package/lib/lifecycle.ts +229 -55
- package/lib/machine.ts +60 -0
- package/lib/media.ts +438 -172
- package/lib/model.ts +169 -74
- package/lib/polling.ts +2 -0
- package/lib/preview.ts +715 -566
- package/lib/queue.ts +5 -5
- package/lib/routing.ts +325 -238
- package/lib/runtime.ts +260 -104
- package/lib/sections.ts +0 -2
- package/lib/telegram-api.ts +61 -8
- package/lib/text-groups.ts +75 -136
- package/lib/thread-display.ts +15 -4
- package/lib/threads.ts +120 -36
- package/lib/turns.ts +8 -1
- package/lib/updates.ts +58 -21
- package/package.json +1 -1
package/lib/threads.ts
CHANGED
|
@@ -349,6 +349,10 @@ export type TelegramWorkspaceLiveRebindIntent = Pick<
|
|
|
349
349
|
| "updatedAtMs"
|
|
350
350
|
> & {
|
|
351
351
|
kind: "live-rebind";
|
|
352
|
+
/** Versioned native input gate: terminal cleanup does not itself authorize or settle input. */
|
|
353
|
+
inputRelease?: "held" | "issued" | "accepted";
|
|
354
|
+
/** A session-replacing producer must not drop earlier old-target protection or carry its delete grant forward. */
|
|
355
|
+
oldTargetProtected?: true;
|
|
352
356
|
recipient: TelegramWorkspaceRestoreRecipient;
|
|
353
357
|
} & (
|
|
354
358
|
| { phase: "rebound"; cleanup?: never }
|
|
@@ -418,6 +422,7 @@ export interface TelegramWorkspaceRestore {
|
|
|
418
422
|
request: TelegramWorkspaceRestoreRequest,
|
|
419
423
|
recipient: TelegramWorkspaceRestoreRecipient,
|
|
420
424
|
authority: TelegramWorkspaceRestoreAuthority,
|
|
425
|
+
options?: { holdInput: true },
|
|
421
426
|
): Promise<TelegramWorkspaceLiveRebindIntent | undefined>;
|
|
422
427
|
/** Caller proves local apply for release or current live clearance for cleanup; unknown issuance never repeats. */
|
|
423
428
|
advanceLiveRebind(
|
|
@@ -425,6 +430,9 @@ export interface TelegramWorkspaceRestore {
|
|
|
425
430
|
step:
|
|
426
431
|
| "release"
|
|
427
432
|
| "issue-cleanup"
|
|
433
|
+
| "issue-input"
|
|
434
|
+
| "accept-input"
|
|
435
|
+
| "protect-old-target"
|
|
428
436
|
| Extract<
|
|
429
437
|
TelegramWorkspaceLiveRebindIntent,
|
|
430
438
|
{ phase: "finished" }
|
|
@@ -501,11 +509,15 @@ export interface TelegramWorkspaceRestore {
|
|
|
501
509
|
expected: TelegramTemporaryThreadEntry,
|
|
502
510
|
authority: TelegramWorkspaceRestoreAuthority,
|
|
503
511
|
): boolean;
|
|
504
|
-
/**
|
|
512
|
+
/**
|
|
513
|
+
* Publishes `creating` before the caller's single creation request; an existing source entry is returned, never
|
|
514
|
+
* recreated. `group` records an album as the tab's one creating input; it must start at the source update.
|
|
515
|
+
*/
|
|
505
516
|
reserveTemporaryThread(
|
|
506
517
|
source: TelegramTemporaryThreadEntry["source"],
|
|
507
518
|
token: string,
|
|
508
519
|
authority: TelegramWorkspaceRestoreAuthority,
|
|
520
|
+
group?: TelegramTemporaryThreadInput,
|
|
509
521
|
): { reserved: boolean; entry: TelegramTemporaryThreadEntry } | undefined;
|
|
510
522
|
/** Caller proves a fresh owner-authenticated implicit Telegram creation and exact live source under profile admission.
|
|
511
523
|
* Registers that observed unbound target without issuing or fabricating a Bot API creation. */
|
|
@@ -745,6 +757,8 @@ export interface TelegramTopicTargetStore {
|
|
|
745
757
|
) => undefined,
|
|
746
758
|
) => void;
|
|
747
759
|
/** Current live-origin ownership veto; effect callers still require Workspace admission. False is not work/recipient/deletion authority. */
|
|
760
|
+
hasPreDispatchRebindEvidence: () => boolean;
|
|
761
|
+
listProtectedLiveRebindTargets: () => TelegramTarget[];
|
|
748
762
|
isWorkspaceLiveRebindCleanupTargetProtected: (
|
|
749
763
|
expected: TelegramWorkspaceLiveRebindIntent,
|
|
750
764
|
) => boolean;
|
|
@@ -948,6 +962,7 @@ export function createTelegramCleanupTargetProtection(
|
|
|
948
962
|
| "listPendingProvisions"
|
|
949
963
|
| "listPendingCleanups"
|
|
950
964
|
| "listTemporaryThreadTargets"
|
|
965
|
+
| "listProtectedLiveRebindTargets"
|
|
951
966
|
>
|
|
952
967
|
>,
|
|
953
968
|
departingRecord?: TelegramTopicTargetRecord,
|
|
@@ -966,6 +981,9 @@ export function createTelegramCleanupTargetProtection(
|
|
|
966
981
|
JSON.parse(JSON.stringify(right)),
|
|
967
982
|
);
|
|
968
983
|
return (target, action) => {
|
|
984
|
+
try {
|
|
985
|
+
if (store.listProtectedLiveRebindTargets?.().some(candidate => targetMatches(candidate, target))) return true;
|
|
986
|
+
} catch { return true; }
|
|
969
987
|
// A source-bound temporary tab is removed only by its own Forward/Cancel, never by generic reconciliation.
|
|
970
988
|
if (store.listTemporaryThreadTargets) {
|
|
971
989
|
try {
|
|
@@ -2366,6 +2384,8 @@ function isWorkspaceLiveRebindIntent(
|
|
|
2366
2384
|
"recipient",
|
|
2367
2385
|
"phase",
|
|
2368
2386
|
"cleanup",
|
|
2387
|
+
"inputRelease",
|
|
2388
|
+
"oldTargetProtected",
|
|
2369
2389
|
]) ||
|
|
2370
2390
|
value.kind !== "live-rebind" ||
|
|
2371
2391
|
!isTelegramWorkspaceRestoreRequest(value.request) ||
|
|
@@ -2383,6 +2403,11 @@ function isWorkspaceLiveRebindIntent(
|
|
|
2383
2403
|
(value.request.owner.owner?.kind === "leader" ? "leader" : "follower")
|
|
2384
2404
|
)
|
|
2385
2405
|
return false;
|
|
2406
|
+
if (value.inputRelease !== undefined &&
|
|
2407
|
+
(!["held", "issued", "accepted"].includes(value.inputRelease as string) ||
|
|
2408
|
+
(value.inputRelease !== "held" && !["released", "finished"].includes(value.phase as string)))) return false;
|
|
2409
|
+
if (value.oldTargetProtected !== undefined &&
|
|
2410
|
+
(value.oldTargetProtected !== true || value.inputRelease === undefined || value.phase !== "finished" || value.cleanup !== "not-issued")) return false;
|
|
2386
2411
|
if (value.phase === "rebound") return value.cleanup === undefined;
|
|
2387
2412
|
if (value.phase === "released")
|
|
2388
2413
|
return value.cleanup === undefined || value.cleanup === "issued";
|
|
@@ -2675,13 +2700,37 @@ function conflictsWithWorkspaceRestoreProvision(
|
|
|
2675
2700
|
);
|
|
2676
2701
|
}
|
|
2677
2702
|
|
|
2703
|
+
/** Canonical issuance is distinct from cleanup completion, source disposition and peer dispatch readiness. */
|
|
2704
|
+
export function isTelegramLiveRebindInputAuthorized(value: TelegramWorkspaceLiveRebindIntent): boolean {
|
|
2705
|
+
return value.inputRelease === undefined
|
|
2706
|
+
? value.phase === "released" && value.cleanup === undefined
|
|
2707
|
+
: ["issued", "accepted"].includes(value.inputRelease) &&
|
|
2708
|
+
["released", "finished"].includes(value.phase) && value.cleanup !== "issued";
|
|
2709
|
+
}
|
|
2710
|
+
function liveRebindInputPending(value: TelegramWorkspaceLiveRebindIntent): boolean {
|
|
2711
|
+
return value.inputRelease !== undefined && value.inputRelease !== "accepted";
|
|
2712
|
+
}
|
|
2713
|
+
function liveRebindActive(value: TelegramWorkspaceLiveRebindIntent): boolean {
|
|
2714
|
+
return value.phase !== "finished" || liveRebindInputPending(value);
|
|
2715
|
+
}
|
|
2716
|
+
function liveRebindOldTargetUnknown(value: TelegramWorkspaceLiveRebindIntent): boolean {
|
|
2717
|
+
return value.phase === "finished" && value.cleanup === "unknown" && value.inputRelease !== undefined;
|
|
2718
|
+
}
|
|
2719
|
+
function retainLiveRebind(value: TelegramWorkspaceLiveRebindIntent): boolean {
|
|
2720
|
+
return liveRebindActive(value) || liveRebindOldTargetUnknown(value) || value.oldTargetProtected === true;
|
|
2721
|
+
}
|
|
2722
|
+
function protectedLiveRebindOldTargets(snapshot: TelegramWorkspaceRestoreSnapshot | undefined): TelegramTarget[] {
|
|
2723
|
+
return (snapshot?.liveRebindings ?? []).filter(value =>
|
|
2724
|
+
value.oldTargetProtected === true || liveRebindOldTargetUnknown(value) || value.inputRelease !== undefined && value.cleanup === "issued",
|
|
2725
|
+
).map(value => ({ ...value.request.binding.target }));
|
|
2726
|
+
}
|
|
2678
2727
|
function pendingWorkspaceRelocationRequests(
|
|
2679
2728
|
snapshot: TelegramWorkspaceRestoreSnapshot | undefined,
|
|
2680
2729
|
): TelegramWorkspaceRestoreRequest[] {
|
|
2681
2730
|
return [
|
|
2682
2731
|
...(snapshot?.operations ?? []).map((value) => value.request),
|
|
2683
2732
|
...(snapshot?.liveRebindings ?? [])
|
|
2684
|
-
.filter(
|
|
2733
|
+
.filter(liveRebindActive)
|
|
2685
2734
|
.map((value) => value.request),
|
|
2686
2735
|
];
|
|
2687
2736
|
}
|
|
@@ -2749,7 +2798,7 @@ function assertWorkspaceRestoreBindingProtection(
|
|
|
2749
2798
|
);
|
|
2750
2799
|
const live = file.workspaceRestore?.liveRebindings?.some(
|
|
2751
2800
|
(value) =>
|
|
2752
|
-
value
|
|
2801
|
+
liveRebindActive(value) &&
|
|
2753
2802
|
value.request.operationId === request.operationId,
|
|
2754
2803
|
);
|
|
2755
2804
|
if (
|
|
@@ -2768,6 +2817,13 @@ function assertWorkspaceRestoreBindingProtection(
|
|
|
2768
2817
|
throw new Error("Protected Workspace Restore owner target changed.");
|
|
2769
2818
|
}
|
|
2770
2819
|
}
|
|
2820
|
+
for (const old of protectedLiveRebindOldTargets(file.workspaceRestore)) {
|
|
2821
|
+
if (file.workspaceBindings?.some(value => targetMatches(value.target, old)) ||
|
|
2822
|
+
file.threads?.some(value => targetMatches(value.target, old) && ["active", "starting", "pending", "probe-required"].includes(value.status)) ||
|
|
2823
|
+
file.pendingProvisions?.some(value => !value.target || targetMatches(value.target, old)) ||
|
|
2824
|
+
file.reservations?.some(value => targetMatches(value.target, old)))
|
|
2825
|
+
throw new Error("Unknown live cleanup target remains protected.");
|
|
2826
|
+
}
|
|
2771
2827
|
const operations = [
|
|
2772
2828
|
...(file.workspaceRestore?.operations ?? []),
|
|
2773
2829
|
...(file.workspaceRestore?.liveRebindings ?? []),
|
|
@@ -2875,14 +2931,14 @@ function parseWorkspaceRestore(
|
|
|
2875
2931
|
.some(
|
|
2876
2932
|
(other) =>
|
|
2877
2933
|
operation.request.operationId === other.request.operationId ||
|
|
2878
|
-
(operation
|
|
2879
|
-
other
|
|
2934
|
+
(liveRebindActive(operation) &&
|
|
2935
|
+
liveRebindActive(other) &&
|
|
2880
2936
|
workspaceRestoresConflict(operation.request, other.request)),
|
|
2881
2937
|
) ||
|
|
2882
2938
|
snapshot.operations.some(
|
|
2883
2939
|
(other) =>
|
|
2884
2940
|
operation.request.operationId === other.request.operationId ||
|
|
2885
|
-
(operation
|
|
2941
|
+
(liveRebindActive(operation) &&
|
|
2886
2942
|
workspaceRestoresConflict(operation.request, other.request)),
|
|
2887
2943
|
),
|
|
2888
2944
|
)
|
|
@@ -2915,7 +2971,7 @@ function parseWorkspaceRestore(
|
|
|
2915
2971
|
!!entry.target &&
|
|
2916
2972
|
[
|
|
2917
2973
|
...snapshot.operations,
|
|
2918
|
-
...liveOperations.filter(
|
|
2974
|
+
...liveOperations.filter(liveRebindActive),
|
|
2919
2975
|
].some(
|
|
2920
2976
|
({ request }) =>
|
|
2921
2977
|
targetMatches(request.binding.target, entry.target!) ||
|
|
@@ -4424,9 +4480,7 @@ export function createTelegramTopicTargetStore(
|
|
|
4424
4480
|
) ||
|
|
4425
4481
|
!isDeepStrictEqual(
|
|
4426
4482
|
transition.restore.liveRebindings?.slice(0, -1),
|
|
4427
|
-
(workspaceRestore?.liveRebindings ?? []).filter(
|
|
4428
|
-
(value) => value.phase !== "finished",
|
|
4429
|
-
),
|
|
4483
|
+
(workspaceRestore?.liveRebindings ?? []).filter(retainLiveRebind),
|
|
4430
4484
|
))
|
|
4431
4485
|
)
|
|
4432
4486
|
return false;
|
|
@@ -5006,6 +5060,14 @@ export function createTelegramTopicTargetStore(
|
|
|
5006
5060
|
)
|
|
5007
5061
|
throw new Error("Workspace live-rebind observation changed.");
|
|
5008
5062
|
},
|
|
5063
|
+
hasPreDispatchRebindEvidence() {
|
|
5064
|
+
const file = parseTopicTargetFile(readStoreSnapshot(getPath(), false) ?? { version: 1, records: [] });
|
|
5065
|
+
return file.workspaceRestore?.liveRebindings?.some(value => value.inputRelease !== undefined) === true;
|
|
5066
|
+
},
|
|
5067
|
+
listProtectedLiveRebindTargets() {
|
|
5068
|
+
const file = parseTopicTargetFile(readStoreSnapshot(getPath(), false) ?? { version: 1, records: [] });
|
|
5069
|
+
return protectedLiveRebindOldTargets(file.workspaceRestore);
|
|
5070
|
+
},
|
|
5009
5071
|
isWorkspaceLiveRebindCleanupTargetProtected(input) {
|
|
5010
5072
|
try {
|
|
5011
5073
|
const expected = structuredClone(input),
|
|
@@ -5130,8 +5192,8 @@ export function createTelegramTopicTargetStore(
|
|
|
5130
5192
|
file.workspaceRestore!.liveRebindings?.some(
|
|
5131
5193
|
(other) =>
|
|
5132
5194
|
other.request.operationId !== request.operationId &&
|
|
5133
|
-
other
|
|
5134
|
-
|
|
5195
|
+
(liveRebindActive(other) && touchesOld(other) ||
|
|
5196
|
+
(liveRebindOldTargetUnknown(other) || other.oldTargetProtected) && targetMatches(other.request.binding.target, old)),
|
|
5135
5197
|
)
|
|
5136
5198
|
)
|
|
5137
5199
|
return true;
|
|
@@ -5357,6 +5419,8 @@ export function createTelegramTopicTargetStore(
|
|
|
5357
5419
|
)
|
|
5358
5420
|
return false;
|
|
5359
5421
|
const state = read().state;
|
|
5422
|
+
if (protectedLiveRebindOldTargets(state).some(old => targetMatches(old, intent.request.binding.target) || targetMatches(old, intent.request.target)))
|
|
5423
|
+
return false;
|
|
5360
5424
|
if (
|
|
5361
5425
|
pendingWorkspaceRelocationRequests(state).some((request) =>
|
|
5362
5426
|
workspaceRestoresConflict(request, intent.request),
|
|
@@ -5369,9 +5433,7 @@ export function createTelegramTopicTargetStore(
|
|
|
5369
5433
|
return false;
|
|
5370
5434
|
if (state.revision === Number.MAX_SAFE_INTEGER)
|
|
5371
5435
|
throw new Error("Workspace Restore revision exhausted.");
|
|
5372
|
-
const pendingLive = (state.liveRebindings ?? []).filter(
|
|
5373
|
-
(value) => value.phase !== "finished",
|
|
5374
|
-
);
|
|
5436
|
+
const pendingLive = (state.liveRebindings ?? []).filter(retainLiveRebind);
|
|
5375
5437
|
if ((live ? pendingLive.length : state.operations.length) >= 26)
|
|
5376
5438
|
throw new Error("Workspace Restore operation capacity reached.");
|
|
5377
5439
|
const restore: TelegramWorkspaceRestoreSnapshot = {
|
|
@@ -5694,7 +5756,7 @@ export function createTelegramTopicTargetStore(
|
|
|
5694
5756
|
list: () => structuredClone(storage.read().operations),
|
|
5695
5757
|
listLiveRebindings: () =>
|
|
5696
5758
|
structuredClone(storage.read().liveRebindings ?? []),
|
|
5697
|
-
async commitLiveRebind(requestInput, recipientInput, authority) {
|
|
5759
|
+
async commitLiveRebind(requestInput, recipientInput, authority, options) {
|
|
5698
5760
|
const request = structuredClone(requestInput),
|
|
5699
5761
|
recipient = structuredClone(recipientInput);
|
|
5700
5762
|
const executor = structuredClone(authority.executor),
|
|
@@ -5715,6 +5777,7 @@ export function createTelegramTopicTargetStore(
|
|
|
5715
5777
|
createdAtMs: at,
|
|
5716
5778
|
updatedAtMs: at,
|
|
5717
5779
|
phase: "rebound",
|
|
5780
|
+
...(options?.holdInput ? { inputRelease: "held" as const } : {}),
|
|
5718
5781
|
};
|
|
5719
5782
|
if (!current() || !isWorkspaceLiveRebindIntent(intent))
|
|
5720
5783
|
return undefined;
|
|
@@ -5729,7 +5792,8 @@ export function createTelegramTopicTargetStore(
|
|
|
5729
5792
|
isDeepStrictEqual(value.request, request) &&
|
|
5730
5793
|
isDeepStrictEqual(value.recipient, recipient) &&
|
|
5731
5794
|
isDeepStrictEqual(value.executor, executor) &&
|
|
5732
|
-
value.operatorUserId === operatorUserId
|
|
5795
|
+
value.operatorUserId === operatorUserId &&
|
|
5796
|
+
(value.inputRelease !== undefined) === (options?.holdInput === true)
|
|
5733
5797
|
? structuredClone(value)
|
|
5734
5798
|
: undefined;
|
|
5735
5799
|
};
|
|
@@ -5760,17 +5824,23 @@ export function createTelegramTopicTargetStore(
|
|
|
5760
5824
|
!entry ||
|
|
5761
5825
|
!isDeepStrictEqual(entry, expected) ||
|
|
5762
5826
|
entry.operatorUserId !== operator ||
|
|
5763
|
-
entry.phase === "finished"
|
|
5827
|
+
(entry.phase === "finished" && step !== "issue-input" && step !== "accept-input")
|
|
5764
5828
|
)
|
|
5765
5829
|
return undefined;
|
|
5766
|
-
const issuing = step === "release" || step === "issue-cleanup";
|
|
5830
|
+
const issuing = step === "release" || step === "issue-cleanup" || step === "issue-input" || step === "accept-input" || step === "protect-old-target";
|
|
5767
5831
|
if (
|
|
5768
5832
|
(issuing || step === "confirmed") &&
|
|
5769
5833
|
!isDeepStrictEqual(entry.executor, executor)
|
|
5770
5834
|
)
|
|
5771
5835
|
return undefined;
|
|
5772
5836
|
let next: TelegramWorkspaceLiveRebindIntent;
|
|
5773
|
-
if (step === "
|
|
5837
|
+
if (step === "issue-input" || step === "accept-input") {
|
|
5838
|
+
if (!["released", "finished"].includes(entry.phase) || entry.cleanup === "issued" || entry.inputRelease !== (step === "issue-input" ? "held" : "issued")) return undefined;
|
|
5839
|
+
next = { ...entry, inputRelease: step === "issue-input" ? "issued" : "accepted" };
|
|
5840
|
+
} else if (step === "protect-old-target") {
|
|
5841
|
+
if (entry.phase !== "released" || entry.cleanup !== undefined || entry.inputRelease !== "held") return undefined;
|
|
5842
|
+
next = { ...entry, phase: "finished", cleanup: "not-issued", oldTargetProtected: true };
|
|
5843
|
+
} else if (step === "release") {
|
|
5774
5844
|
if (entry.phase !== "rebound") return undefined;
|
|
5775
5845
|
next = { ...entry, phase: "released" };
|
|
5776
5846
|
} else if (step === "issue-cleanup") {
|
|
@@ -6086,8 +6156,20 @@ export function createTelegramTopicTargetStore(
|
|
|
6086
6156
|
},
|
|
6087
6157
|
listTemporaryThreads: () =>
|
|
6088
6158
|
structuredClone(storage.read().temporaryThreads ?? []),
|
|
6089
|
-
reserveTemporaryThread(sourceInput, token, authority) {
|
|
6159
|
+
reserveTemporaryThread(sourceInput, token, authority, groupInput) {
|
|
6090
6160
|
const source = structuredClone(sourceInput);
|
|
6161
|
+
const group: TelegramTemporaryThreadInput = groupInput
|
|
6162
|
+
? structuredClone(groupInput)
|
|
6163
|
+
: {
|
|
6164
|
+
journalBindingKey: source.journalBindingKey,
|
|
6165
|
+
updateIds: [source.updateId],
|
|
6166
|
+
};
|
|
6167
|
+
if (
|
|
6168
|
+
!isTemporaryThreadInput(group) ||
|
|
6169
|
+
group.journalBindingKey !== source.journalBindingKey ||
|
|
6170
|
+
Math.min(...group.updateIds) !== source.updateId
|
|
6171
|
+
)
|
|
6172
|
+
return undefined;
|
|
6091
6173
|
let reserved = false;
|
|
6092
6174
|
const entry = mutate<TelegramTemporaryThreadEntry>(
|
|
6093
6175
|
authority,
|
|
@@ -6108,12 +6190,7 @@ export function createTelegramTopicTargetStore(
|
|
|
6108
6190
|
throw new Error("Invalid Workspace Restore clock.");
|
|
6109
6191
|
const next: TelegramTemporaryThreadEntry = {
|
|
6110
6192
|
source,
|
|
6111
|
-
inputs: [
|
|
6112
|
-
{
|
|
6113
|
-
journalBindingKey: source.journalBindingKey,
|
|
6114
|
-
updateIds: [source.updateId],
|
|
6115
|
-
},
|
|
6116
|
-
],
|
|
6193
|
+
inputs: [group],
|
|
6117
6194
|
operatorUserId: operator,
|
|
6118
6195
|
executor,
|
|
6119
6196
|
token,
|
|
@@ -6131,7 +6208,9 @@ export function createTelegramTopicTargetStore(
|
|
|
6131
6208
|
getTelegramTemporaryThreadInputs(value).some(
|
|
6132
6209
|
(input) =>
|
|
6133
6210
|
input.journalBindingKey === source.journalBindingKey &&
|
|
6134
|
-
input.updateIds.
|
|
6211
|
+
input.updateIds.some((id) =>
|
|
6212
|
+
group.updateIds.includes(id),
|
|
6213
|
+
),
|
|
6135
6214
|
),
|
|
6136
6215
|
)
|
|
6137
6216
|
)
|
|
@@ -6659,7 +6738,8 @@ export function createTelegramTopicTargetStore(
|
|
|
6659
6738
|
pendingWorkspaceRelocationRequests(workspaceRestore).some((request) =>
|
|
6660
6739
|
conflictsWithWorkspaceRestoreProvision(request, reservation),
|
|
6661
6740
|
) ||
|
|
6662
|
-
conflictsWithTemporaryThread(workspaceRestore, reservation.target)
|
|
6741
|
+
conflictsWithTemporaryThread(workspaceRestore, reservation.target) ||
|
|
6742
|
+
protectedLiveRebindOldTargets(workspaceRestore).some(old => targetMatches(old, reservation.target))
|
|
6663
6743
|
) {
|
|
6664
6744
|
throw new Error("Protected Workspace Restore provisioning conflict.");
|
|
6665
6745
|
}
|
|
@@ -6677,7 +6757,8 @@ export function createTelegramTopicTargetStore(
|
|
|
6677
6757
|
pendingWorkspaceRelocationRequests(workspaceRestore).some((request) =>
|
|
6678
6758
|
conflictsWithWorkspaceRestoreProvision(request, provision),
|
|
6679
6759
|
) ||
|
|
6680
|
-
conflictsWithTemporaryThread(workspaceRestore, provision.target)
|
|
6760
|
+
conflictsWithTemporaryThread(workspaceRestore, provision.target) ||
|
|
6761
|
+
protectedLiveRebindOldTargets(workspaceRestore).some(old => !provision.target || targetMatches(old, provision.target))
|
|
6681
6762
|
) {
|
|
6682
6763
|
throw new Error("Protected Workspace Restore provisioning conflict.");
|
|
6683
6764
|
}
|
|
@@ -7692,7 +7773,7 @@ export function createTelegramTopicTargetStore(
|
|
|
7692
7773
|
if (workspaceRetirementCommitInFlight) return undefined;
|
|
7693
7774
|
const next = normalizeWorkspaceBindingRecord(binding);
|
|
7694
7775
|
if (next && hasWorkspaceRetirementConflict(next)) return undefined;
|
|
7695
|
-
if (!next) return undefined;
|
|
7776
|
+
if (!next || protectedLiveRebindOldTargets(workspaceRestore).some(old => targetMatches(old, next.target))) return undefined;
|
|
7696
7777
|
const nextMapKey = getWorkspaceBindingMapKey(next);
|
|
7697
7778
|
let claim = workspaceClaims.get(nextMapKey);
|
|
7698
7779
|
const replacedTargetBinding = Array.from(workspaceBindings.values()).find(
|
|
@@ -7762,18 +7843,21 @@ export function createTelegramTopicTargetStore(
|
|
|
7762
7843
|
if (previous?.manualThreadName && !next.manualThreadName) {
|
|
7763
7844
|
next.manualThreadName = previous.manualThreadName;
|
|
7764
7845
|
}
|
|
7765
|
-
if (previous) {
|
|
7846
|
+
if (previous || replacedTargetBinding) {
|
|
7766
7847
|
const journalBindingKeys = Array.from(
|
|
7767
7848
|
new Set([
|
|
7768
|
-
...(previous
|
|
7849
|
+
...(previous?.journalBindingKeys ?? []),
|
|
7850
|
+
...(replacedTargetBinding?.journalBindingKeys ?? []),
|
|
7769
7851
|
...(next.journalBindingKeys ?? []),
|
|
7770
7852
|
]),
|
|
7771
7853
|
);
|
|
7772
|
-
|
|
7854
|
+
const retainedComplete =
|
|
7855
|
+
(!previous || previous.journalBindingsComplete === true) &&
|
|
7856
|
+
(!replacedTargetBinding || replacedTargetBinding.journalBindingsComplete === true);
|
|
7857
|
+
if (journalBindingKeys.length || retainedComplete)
|
|
7773
7858
|
next.journalBindingKeys = journalBindingKeys;
|
|
7774
7859
|
else delete next.journalBindingKeys;
|
|
7775
|
-
if (
|
|
7776
|
-
next.journalBindingsComplete = true;
|
|
7860
|
+
if (retainedComplete) next.journalBindingsComplete = true;
|
|
7777
7861
|
else delete next.journalBindingsComplete;
|
|
7778
7862
|
}
|
|
7779
7863
|
if (previous && !targetMatches(previous.target, next.target)) {
|
package/lib/turns.ts
CHANGED
|
@@ -44,6 +44,13 @@ import {
|
|
|
44
44
|
|
|
45
45
|
export const TELEGRAM_PREFIX = "[telegram]";
|
|
46
46
|
|
|
47
|
+
const REPLY_ANCHOR_SUPPRESSED = Symbol.for("@llblab/pi-telegram:reply-anchor-suppressed");
|
|
48
|
+
|
|
49
|
+
/** A routed source keeps its unique message/file/group identity, but cannot quote an anchor in its former tab. */
|
|
50
|
+
export function withoutTelegramTurnReplyAnchor<TMessage extends object>(message: TMessage): TMessage {
|
|
51
|
+
return { ...message, [REPLY_ANCHOR_SUPPRESSED]: true };
|
|
52
|
+
}
|
|
53
|
+
|
|
47
54
|
export const TELEGRAM_GUEST_TURN_NOTE =
|
|
48
55
|
"[guest] delivery: answer quickly with one concise, self-contained reply";
|
|
49
56
|
|
|
@@ -733,7 +740,7 @@ function buildPreparedTelegramPromptTurn(
|
|
|
733
740
|
kind: "prompt",
|
|
734
741
|
chatId: firstMessage.chat.id,
|
|
735
742
|
target: getTelegramTurnTarget(firstMessage),
|
|
736
|
-
replyToMessageId: firstMessage.message_id,
|
|
743
|
+
replyToMessageId: Reflect.get(firstMessage, REPLY_ANCHOR_SUPPRESSED) === true ? 0 : firstMessage.message_id,
|
|
737
744
|
sourceMessageIds: collectTelegramMessageIds(options.messages),
|
|
738
745
|
queueOrder: options.queueOrder,
|
|
739
746
|
queueLane: "default",
|
package/lib/updates.ts
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
import { randomUUID } from "node:crypto";
|
|
8
8
|
import { isDeepStrictEqual } from "node:util";
|
|
9
|
+
import { withoutTelegramTurnReplyAnchor } from "./turns.ts";
|
|
9
10
|
|
|
10
11
|
import type {
|
|
11
12
|
TelegramBusEnvelope,
|
|
@@ -552,7 +553,10 @@ interface TelegramUpdateAdmissionBinding {
|
|
|
552
553
|
prepareLiveInput?: (
|
|
553
554
|
sourceUpdateIds: readonly number[],
|
|
554
555
|
isCurrent: () => boolean,
|
|
556
|
+
onTransferred?: () => void,
|
|
555
557
|
) => TelegramLiveDeferredInputPreparation | undefined;
|
|
558
|
+
/** Internal notification after the worker's exact donor-removal ACK; never issues completion itself. */
|
|
559
|
+
acknowledgeTransferred?: () => void;
|
|
556
560
|
prepareQueueAdmission?: () =>
|
|
557
561
|
TelegramDeferredQueueAdmissionPreparation | undefined;
|
|
558
562
|
prepareSourceCompletion?: () =>
|
|
@@ -614,6 +618,7 @@ export function bindTelegramUpdateAdmissionSource<
|
|
|
614
618
|
| "inspectSourceSnapshot"
|
|
615
619
|
| "inspectCompletion"
|
|
616
620
|
| "isSourceUnsettled"
|
|
621
|
+
| "acknowledgeTransferred"
|
|
617
622
|
| "prepareLiveInput"
|
|
618
623
|
| "prepareQueueAdmission"
|
|
619
624
|
| "prepareSourceCompletion"
|
|
@@ -761,20 +766,24 @@ export function prepareTelegramLiveDeferredInput(
|
|
|
761
766
|
)
|
|
762
767
|
)
|
|
763
768
|
return undefined;
|
|
764
|
-
const
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
);
|
|
769
|
+
const bindings = carriers.map(getTelegramUpdateAdmissionBinding);
|
|
770
|
+
const acknowledgers = bindings.map(binding => binding?.acknowledgeTransferred);
|
|
771
|
+
const bindingsCurrent = () => carriers.every((value, index) =>
|
|
772
|
+
getTelegramUpdateAdmissionBinding(value) === bindings[index] && bindings[index]?.acknowledgeTransferred === acknowledgers[index]);
|
|
773
|
+
const unsettled = () => bindingsCurrent() && bindings.every(binding => binding?.isSourceUnsettled?.() === true);
|
|
770
774
|
if (!unsettled()) return undefined;
|
|
775
|
+
let transferred = false;
|
|
771
776
|
const prepared = getTelegramUpdateAdmissionBinding(first)?.prepareLiveInput?.(
|
|
772
777
|
ids,
|
|
773
778
|
() =>
|
|
774
|
-
isCurrent() &&
|
|
779
|
+
bindingsCurrent() && isCurrent() &&
|
|
775
780
|
carriers.every(
|
|
776
781
|
(value) => getTelegramUpdateExecutionFence(value)?.isCurrent() === true,
|
|
777
782
|
),
|
|
783
|
+
() => {
|
|
784
|
+
transferred = true;
|
|
785
|
+
for (let index = 0; index < bindings.length; index++) acknowledgers[index]?.call(bindings[index]);
|
|
786
|
+
},
|
|
778
787
|
);
|
|
779
788
|
return (
|
|
780
789
|
prepared && {
|
|
@@ -785,7 +794,7 @@ export function prepareTelegramLiveDeferredInput(
|
|
|
785
794
|
unsettled() && prepared.beginRelease(() => unsettled() && canRelease()),
|
|
786
795
|
settleTransferred: (canSettle) =>
|
|
787
796
|
unsettled()
|
|
788
|
-
? prepared.settleTransferred(() => unsettled() && canSettle())
|
|
797
|
+
? prepared.settleTransferred(() => (transferred || unsettled()) && canSettle())
|
|
789
798
|
: "protected",
|
|
790
799
|
cancel: () => unsettled() && prepared.cancel(),
|
|
791
800
|
}
|
|
@@ -2350,6 +2359,7 @@ export interface TelegramUpdateWorkerRuntimeDeps<TContext> {
|
|
|
2350
2359
|
update: TelegramJournaledUpdate,
|
|
2351
2360
|
ctx: TContext,
|
|
2352
2361
|
signal: AbortSignal,
|
|
2362
|
+
target?: TelegramTarget & { threadId: number },
|
|
2353
2363
|
) => Promise<TelegramUpdateAdmissionOutcome> | TelegramUpdateAdmissionOutcome;
|
|
2354
2364
|
/** Native source-only binding, installed by the admission-worker assembly rather than ordinary dispatch. */
|
|
2355
2365
|
admitPreparedLiveInput?: (
|
|
@@ -2466,7 +2476,7 @@ export interface TelegramLiveInputPreparation {
|
|
|
2466
2476
|
prepareStatusConsumption?(input: TelegramLiveStatusConsumption): boolean;
|
|
2467
2477
|
/** Native owner availability before recipient append; not admission or future target activation. */
|
|
2468
2478
|
canPrepareStatusConsumption?(): boolean;
|
|
2469
|
-
release(canRelease: () => boolean): boolean;
|
|
2479
|
+
release(canRelease: () => boolean, target?: TelegramTarget & { threadId: number }): boolean;
|
|
2470
2480
|
/** Ends a failed pre-append attempt only when no selected input was saved. */
|
|
2471
2481
|
cancelEmpty(): boolean;
|
|
2472
2482
|
/** One fenced removal of this never-dispatched recipient copy; unknown issuance cannot be retried. */
|
|
@@ -2532,6 +2542,7 @@ export interface TelegramUpdateWorkerRuntime<TContext> {
|
|
|
2532
2542
|
sourceUpdateIds: readonly number[];
|
|
2533
2543
|
signal: AbortSignal;
|
|
2534
2544
|
isCurrent(): boolean;
|
|
2545
|
+
onTransferred?(): void;
|
|
2535
2546
|
}) => TelegramLiveDeferredInputPreparation | undefined;
|
|
2536
2547
|
prepareDeferredQueueAdmission?: (input: {
|
|
2537
2548
|
updateId: number;
|
|
@@ -2652,6 +2663,7 @@ interface TelegramLiveInputPreparationState<TContext> {
|
|
|
2652
2663
|
saved?: TelegramUpdateWorkerJournalSnapshot["entries"][number][];
|
|
2653
2664
|
discardIssued?: true;
|
|
2654
2665
|
released?: true;
|
|
2666
|
+
target?: TelegramTarget & { threadId: number };
|
|
2655
2667
|
status?: {
|
|
2656
2668
|
current(): boolean;
|
|
2657
2669
|
assertCurrent(): void;
|
|
@@ -3707,6 +3719,7 @@ export function createTelegramUpdateWorkerRuntime<TContext>(
|
|
|
3707
3719
|
expectedOwner: TelegramUpdateWorkerOwner<TContext>,
|
|
3708
3720
|
update: TelegramJournaledUpdate,
|
|
3709
3721
|
preparedExecute?: TelegramUpdateWorkerRuntimeDeps<TContext>["executeUpdate"],
|
|
3722
|
+
target?: TelegramTarget & { threadId: number },
|
|
3710
3723
|
): Promise<
|
|
3711
3724
|
| TelegramUpdateWorkerExecutionSettlement
|
|
3712
3725
|
| typeof TELEGRAM_UPDATE_WORKER_EXECUTION_ABORTED
|
|
@@ -3735,6 +3748,7 @@ export function createTelegramUpdateWorkerRuntime<TContext>(
|
|
|
3735
3748
|
update,
|
|
3736
3749
|
expectedOwner.ctx,
|
|
3737
3750
|
expectedOwner.controller.signal,
|
|
3751
|
+
target,
|
|
3738
3752
|
),
|
|
3739
3753
|
);
|
|
3740
3754
|
const settlement: Promise<TelegramUpdateWorkerExecutionSettlement> =
|
|
@@ -4739,6 +4753,7 @@ export function createTelegramUpdateWorkerRuntime<TContext>(
|
|
|
4739
4753
|
return admitted.outcome;
|
|
4740
4754
|
}
|
|
4741
4755
|
: undefined,
|
|
4756
|
+
prepared?.target,
|
|
4742
4757
|
);
|
|
4743
4758
|
if (execution === TELEGRAM_UPDATE_WORKER_EXECUTION_ABORTED) {
|
|
4744
4759
|
return "aborted";
|
|
@@ -4821,6 +4836,11 @@ export function createTelegramUpdateWorkerRuntime<TContext>(
|
|
|
4821
4836
|
snapshotInvalidated = true;
|
|
4822
4837
|
break;
|
|
4823
4838
|
}
|
|
4839
|
+
if (prepared?.target) {
|
|
4840
|
+
prepared.ids = new Set([...prepared.ids].filter(id => id !== entry.updateId));
|
|
4841
|
+
if (!prepared.ids.size && livePreparation === prepared) livePreparation = undefined;
|
|
4842
|
+
updateClaimCounts();
|
|
4843
|
+
}
|
|
4824
4844
|
const postExecutionAuthority = checkAuthority(
|
|
4825
4845
|
expectedOwner,
|
|
4826
4846
|
entry.updateId,
|
|
@@ -5270,6 +5290,7 @@ export function createTelegramUpdateWorkerRuntime<TContext>(
|
|
|
5270
5290
|
prepareDeferredLiveInput(input) {
|
|
5271
5291
|
const expectedOwner = owner,
|
|
5272
5292
|
binding = deps.getJournalBindingKey?.(),
|
|
5293
|
+
onTransferred = input.onTransferred,
|
|
5273
5294
|
ids = [...input.sourceUpdateIds];
|
|
5274
5295
|
const sources = ids.map((id) => deferredSources.get(id));
|
|
5275
5296
|
const ownerCurrent = () =>
|
|
@@ -5282,7 +5303,7 @@ export function createTelegramUpdateWorkerRuntime<TContext>(
|
|
|
5282
5303
|
deps.hasAuthority(expectedOwner.ctx) &&
|
|
5283
5304
|
deps.isContextCurrent?.(expectedOwner.ctx) !== false &&
|
|
5284
5305
|
isQueueOwnerIdentityCurrent(expectedOwner) &&
|
|
5285
|
-
input.isCurrent();
|
|
5306
|
+
input.onTransferred === onTransferred && input.isCurrent();
|
|
5286
5307
|
const sourcesCurrent = () =>
|
|
5287
5308
|
ownerCurrent() &&
|
|
5288
5309
|
sources.every(
|
|
@@ -5381,13 +5402,11 @@ export function createTelegramUpdateWorkerRuntime<TContext>(
|
|
|
5381
5402
|
undefined,
|
|
5382
5403
|
() => current() && canSettle(),
|
|
5383
5404
|
);
|
|
5384
|
-
if (
|
|
5385
|
-
!current() ||
|
|
5386
|
-
!canSettle() ||
|
|
5387
|
-
result.removedUpdateIds.length !== ids.length ||
|
|
5388
|
-
!ids.every((id) => result.removedUpdateIds.includes(id))
|
|
5389
|
-
)
|
|
5405
|
+
if (result.removedUpdateIds.length !== ids.length || !ids.every(id => result.removedUpdateIds.includes(id)))
|
|
5390
5406
|
return "unknown";
|
|
5407
|
+
// A consumed routing clock is expected only after this exact ACK, never after uncertain removal.
|
|
5408
|
+
onTransferred?.call(input);
|
|
5409
|
+
if (!current() || !canSettle()) return "unknown";
|
|
5391
5410
|
} catch (error) {
|
|
5392
5411
|
recordRuntimeEvent(error, {
|
|
5393
5412
|
phase: "live-deferred-transfer-settlement",
|
|
@@ -5511,7 +5530,7 @@ export function createTelegramUpdateWorkerRuntime<TContext>(
|
|
|
5511
5530
|
}
|
|
5512
5531
|
};
|
|
5513
5532
|
const finish = () => {
|
|
5514
|
-
if (!held.released || !held.status) livePreparation = undefined;
|
|
5533
|
+
if (!held.released || !held.status && !held.target) livePreparation = undefined;
|
|
5515
5534
|
updateClaimCounts();
|
|
5516
5535
|
notifyStateChange();
|
|
5517
5536
|
blocked = false;
|
|
@@ -5659,7 +5678,7 @@ export function createTelegramUpdateWorkerRuntime<TContext>(
|
|
|
5659
5678
|
};
|
|
5660
5679
|
return held.status.current();
|
|
5661
5680
|
},
|
|
5662
|
-
release(canRelease) {
|
|
5681
|
+
release(canRelease, target) {
|
|
5663
5682
|
if (
|
|
5664
5683
|
!current() ||
|
|
5665
5684
|
held.discardIssued ||
|
|
@@ -5676,6 +5695,14 @@ export function createTelegramUpdateWorkerRuntime<TContext>(
|
|
|
5676
5695
|
!current()
|
|
5677
5696
|
)
|
|
5678
5697
|
return false;
|
|
5698
|
+
if (target) {
|
|
5699
|
+
if (!Number.isSafeInteger(target.chatId) || target.chatId === 0 || !Number.isSafeInteger(target.threadId) || target.threadId <= 0 ||
|
|
5700
|
+
entries.some(entry => {
|
|
5701
|
+
const message = (entry.update.message ?? entry.update.edited_message) as { chat?: { id?: number } } | undefined;
|
|
5702
|
+
return message?.chat?.id !== target.chatId;
|
|
5703
|
+
})) return false;
|
|
5704
|
+
if (!held.status) held.target = { ...target };
|
|
5705
|
+
}
|
|
5679
5706
|
released = true;
|
|
5680
5707
|
held.released = true;
|
|
5681
5708
|
finish();
|
|
@@ -8846,7 +8873,8 @@ function createTelegramUpdateSourceAdmissionHandle<
|
|
|
8846
8873
|
signal,
|
|
8847
8874
|
})
|
|
8848
8875
|
: undefined,
|
|
8849
|
-
|
|
8876
|
+
acknowledgeTransferred: () => { settlementReported = true; },
|
|
8877
|
+
prepareLiveInput: (sourceUpdateIds, isCurrent, onTransferred) =>
|
|
8850
8878
|
execution.isCurrent() &&
|
|
8851
8879
|
!immediate &&
|
|
8852
8880
|
!settlementReported &&
|
|
@@ -8856,6 +8884,7 @@ function createTelegramUpdateSourceAdmissionHandle<
|
|
|
8856
8884
|
sourceUpdateIds,
|
|
8857
8885
|
signal,
|
|
8858
8886
|
isCurrent: () => execution.isCurrent() && isCurrent(),
|
|
8887
|
+
onTransferred,
|
|
8859
8888
|
})
|
|
8860
8889
|
: undefined,
|
|
8861
8890
|
isHistorical: (matchesOriginal) =>
|
|
@@ -10599,11 +10628,19 @@ export function createTelegramUpdateAdmissionWorkerRuntime<
|
|
|
10599
10628
|
),
|
|
10600
10629
|
}
|
|
10601
10630
|
: {}),
|
|
10602
|
-
async executeUpdate(update, ctx, signal) {
|
|
10631
|
+
async executeUpdate(update, ctx, signal, target) {
|
|
10603
10632
|
const typedUpdate = update as TUpdate;
|
|
10633
|
+
const prepared = deps.prepareUpdateForExecution?.(typedUpdate) ?? typedUpdate;
|
|
10634
|
+
// Journal/source evidence remains the original; only the warm released handler projection changes destination.
|
|
10635
|
+
const routed = target ? { ...prepared,
|
|
10636
|
+
...(prepared.message ? { message: prepared.message.message_thread_id === target.threadId ? { ...prepared.message }
|
|
10637
|
+
: withoutTelegramTurnReplyAnchor({ ...prepared.message, message_thread_id: target.threadId, reply_to_message: undefined }) } : {}),
|
|
10638
|
+
...(prepared.edited_message ? { edited_message: prepared.edited_message.message_thread_id === target.threadId ? { ...prepared.edited_message }
|
|
10639
|
+
: withoutTelegramTurnReplyAnchor({ ...prepared.edited_message, message_thread_id: target.threadId, reply_to_message: undefined }) } : {}),
|
|
10640
|
+
} as TUpdate : prepared;
|
|
10604
10641
|
try {
|
|
10605
10642
|
return await executeUpdate(
|
|
10606
|
-
|
|
10643
|
+
routed,
|
|
10607
10644
|
ctx,
|
|
10608
10645
|
signal,
|
|
10609
10646
|
);
|