@llblab/pi-telegram 0.54.4 → 0.55.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +4 -2
- package/BACKLOG.md +1 -1
- package/CHANGELOG.md +16 -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 +50 -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 +7 -3
- package/dist/lib/bus.js +24 -16
- 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 +7 -2
- package/dist/lib/routing.js +585 -229
- 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 +6 -0
- package/dist/lib/turns.js +12 -1
- package/dist/lib/updates.d.ts +13 -4
- package/dist/lib/updates.js +54 -19
- package/dist/package.json +1 -1
- package/docs/activity.md +1 -1
- package/docs/architecture.md +14 -11
- package/docs/delivery.md +7 -5
- package/docs/multi-instance-bus.md +20 -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 +30 -18
- 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 +614 -257
- 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 +15 -1
- package/lib/updates.ts +60 -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,20 @@ 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
|
+
|
|
54
|
+
/** A confirmed same-target copy may replace the former-tab suppression on a warm input projection. */
|
|
55
|
+
export function withTelegramTurnReplyAnchor<TMessage extends { message_id: number }>(message: TMessage): TMessage {
|
|
56
|
+
if (!Number.isSafeInteger(message.message_id) || message.message_id <= 0)
|
|
57
|
+
throw new Error("Telegram reply anchor requires a positive message ID.");
|
|
58
|
+
return { ...message, [REPLY_ANCHOR_SUPPRESSED]: false };
|
|
59
|
+
}
|
|
60
|
+
|
|
47
61
|
export const TELEGRAM_GUEST_TURN_NOTE =
|
|
48
62
|
"[guest] delivery: answer quickly with one concise, self-contained reply";
|
|
49
63
|
|
|
@@ -733,7 +747,7 @@ function buildPreparedTelegramPromptTurn(
|
|
|
733
747
|
kind: "prompt",
|
|
734
748
|
chatId: firstMessage.chat.id,
|
|
735
749
|
target: getTelegramTurnTarget(firstMessage),
|
|
736
|
-
replyToMessageId: firstMessage.message_id,
|
|
750
|
+
replyToMessageId: Reflect.get(firstMessage, REPLY_ANCHOR_SUPPRESSED) === true ? 0 : firstMessage.message_id,
|
|
737
751
|
sourceMessageIds: collectTelegramMessageIds(options.messages),
|
|
738
752
|
queueOrder: options.queueOrder,
|
|
739
753
|
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,
|
|
@@ -373,6 +374,8 @@ export interface TelegramForeignOwnedUpdateForwarder<
|
|
|
373
374
|
message: TMessage;
|
|
374
375
|
ownership: TelegramTargetOwnershipView;
|
|
375
376
|
ctx: TContext;
|
|
377
|
+
/** Local issuance/ACK fence; implementations must not serialize it into payloads. */
|
|
378
|
+
assertAuthority?: () => void;
|
|
376
379
|
}) =>
|
|
377
380
|
| Promise<TelegramBusForeignUpdateSettlement>
|
|
378
381
|
| TelegramBusForeignUpdateSettlement;
|
|
@@ -552,7 +555,10 @@ interface TelegramUpdateAdmissionBinding {
|
|
|
552
555
|
prepareLiveInput?: (
|
|
553
556
|
sourceUpdateIds: readonly number[],
|
|
554
557
|
isCurrent: () => boolean,
|
|
558
|
+
onTransferred?: () => void,
|
|
555
559
|
) => TelegramLiveDeferredInputPreparation | undefined;
|
|
560
|
+
/** Internal notification after the worker's exact donor-removal ACK; never issues completion itself. */
|
|
561
|
+
acknowledgeTransferred?: () => void;
|
|
556
562
|
prepareQueueAdmission?: () =>
|
|
557
563
|
TelegramDeferredQueueAdmissionPreparation | undefined;
|
|
558
564
|
prepareSourceCompletion?: () =>
|
|
@@ -614,6 +620,7 @@ export function bindTelegramUpdateAdmissionSource<
|
|
|
614
620
|
| "inspectSourceSnapshot"
|
|
615
621
|
| "inspectCompletion"
|
|
616
622
|
| "isSourceUnsettled"
|
|
623
|
+
| "acknowledgeTransferred"
|
|
617
624
|
| "prepareLiveInput"
|
|
618
625
|
| "prepareQueueAdmission"
|
|
619
626
|
| "prepareSourceCompletion"
|
|
@@ -761,20 +768,24 @@ export function prepareTelegramLiveDeferredInput(
|
|
|
761
768
|
)
|
|
762
769
|
)
|
|
763
770
|
return undefined;
|
|
764
|
-
const
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
);
|
|
771
|
+
const bindings = carriers.map(getTelegramUpdateAdmissionBinding);
|
|
772
|
+
const acknowledgers = bindings.map(binding => binding?.acknowledgeTransferred);
|
|
773
|
+
const bindingsCurrent = () => carriers.every((value, index) =>
|
|
774
|
+
getTelegramUpdateAdmissionBinding(value) === bindings[index] && bindings[index]?.acknowledgeTransferred === acknowledgers[index]);
|
|
775
|
+
const unsettled = () => bindingsCurrent() && bindings.every(binding => binding?.isSourceUnsettled?.() === true);
|
|
770
776
|
if (!unsettled()) return undefined;
|
|
777
|
+
let transferred = false;
|
|
771
778
|
const prepared = getTelegramUpdateAdmissionBinding(first)?.prepareLiveInput?.(
|
|
772
779
|
ids,
|
|
773
780
|
() =>
|
|
774
|
-
isCurrent() &&
|
|
781
|
+
bindingsCurrent() && isCurrent() &&
|
|
775
782
|
carriers.every(
|
|
776
783
|
(value) => getTelegramUpdateExecutionFence(value)?.isCurrent() === true,
|
|
777
784
|
),
|
|
785
|
+
() => {
|
|
786
|
+
transferred = true;
|
|
787
|
+
for (let index = 0; index < bindings.length; index++) acknowledgers[index]?.call(bindings[index]);
|
|
788
|
+
},
|
|
778
789
|
);
|
|
779
790
|
return (
|
|
780
791
|
prepared && {
|
|
@@ -785,7 +796,7 @@ export function prepareTelegramLiveDeferredInput(
|
|
|
785
796
|
unsettled() && prepared.beginRelease(() => unsettled() && canRelease()),
|
|
786
797
|
settleTransferred: (canSettle) =>
|
|
787
798
|
unsettled()
|
|
788
|
-
? prepared.settleTransferred(() => unsettled() && canSettle())
|
|
799
|
+
? prepared.settleTransferred(() => (transferred || unsettled()) && canSettle())
|
|
789
800
|
: "protected",
|
|
790
801
|
cancel: () => unsettled() && prepared.cancel(),
|
|
791
802
|
}
|
|
@@ -2350,6 +2361,7 @@ export interface TelegramUpdateWorkerRuntimeDeps<TContext> {
|
|
|
2350
2361
|
update: TelegramJournaledUpdate,
|
|
2351
2362
|
ctx: TContext,
|
|
2352
2363
|
signal: AbortSignal,
|
|
2364
|
+
target?: TelegramTarget & { threadId: number },
|
|
2353
2365
|
) => Promise<TelegramUpdateAdmissionOutcome> | TelegramUpdateAdmissionOutcome;
|
|
2354
2366
|
/** Native source-only binding, installed by the admission-worker assembly rather than ordinary dispatch. */
|
|
2355
2367
|
admitPreparedLiveInput?: (
|
|
@@ -2466,7 +2478,7 @@ export interface TelegramLiveInputPreparation {
|
|
|
2466
2478
|
prepareStatusConsumption?(input: TelegramLiveStatusConsumption): boolean;
|
|
2467
2479
|
/** Native owner availability before recipient append; not admission or future target activation. */
|
|
2468
2480
|
canPrepareStatusConsumption?(): boolean;
|
|
2469
|
-
release(canRelease: () => boolean): boolean;
|
|
2481
|
+
release(canRelease: () => boolean, target?: TelegramTarget & { threadId: number }): boolean;
|
|
2470
2482
|
/** Ends a failed pre-append attempt only when no selected input was saved. */
|
|
2471
2483
|
cancelEmpty(): boolean;
|
|
2472
2484
|
/** One fenced removal of this never-dispatched recipient copy; unknown issuance cannot be retried. */
|
|
@@ -2532,6 +2544,7 @@ export interface TelegramUpdateWorkerRuntime<TContext> {
|
|
|
2532
2544
|
sourceUpdateIds: readonly number[];
|
|
2533
2545
|
signal: AbortSignal;
|
|
2534
2546
|
isCurrent(): boolean;
|
|
2547
|
+
onTransferred?(): void;
|
|
2535
2548
|
}) => TelegramLiveDeferredInputPreparation | undefined;
|
|
2536
2549
|
prepareDeferredQueueAdmission?: (input: {
|
|
2537
2550
|
updateId: number;
|
|
@@ -2652,6 +2665,7 @@ interface TelegramLiveInputPreparationState<TContext> {
|
|
|
2652
2665
|
saved?: TelegramUpdateWorkerJournalSnapshot["entries"][number][];
|
|
2653
2666
|
discardIssued?: true;
|
|
2654
2667
|
released?: true;
|
|
2668
|
+
target?: TelegramTarget & { threadId: number };
|
|
2655
2669
|
status?: {
|
|
2656
2670
|
current(): boolean;
|
|
2657
2671
|
assertCurrent(): void;
|
|
@@ -3707,6 +3721,7 @@ export function createTelegramUpdateWorkerRuntime<TContext>(
|
|
|
3707
3721
|
expectedOwner: TelegramUpdateWorkerOwner<TContext>,
|
|
3708
3722
|
update: TelegramJournaledUpdate,
|
|
3709
3723
|
preparedExecute?: TelegramUpdateWorkerRuntimeDeps<TContext>["executeUpdate"],
|
|
3724
|
+
target?: TelegramTarget & { threadId: number },
|
|
3710
3725
|
): Promise<
|
|
3711
3726
|
| TelegramUpdateWorkerExecutionSettlement
|
|
3712
3727
|
| typeof TELEGRAM_UPDATE_WORKER_EXECUTION_ABORTED
|
|
@@ -3735,6 +3750,7 @@ export function createTelegramUpdateWorkerRuntime<TContext>(
|
|
|
3735
3750
|
update,
|
|
3736
3751
|
expectedOwner.ctx,
|
|
3737
3752
|
expectedOwner.controller.signal,
|
|
3753
|
+
target,
|
|
3738
3754
|
),
|
|
3739
3755
|
);
|
|
3740
3756
|
const settlement: Promise<TelegramUpdateWorkerExecutionSettlement> =
|
|
@@ -4739,6 +4755,7 @@ export function createTelegramUpdateWorkerRuntime<TContext>(
|
|
|
4739
4755
|
return admitted.outcome;
|
|
4740
4756
|
}
|
|
4741
4757
|
: undefined,
|
|
4758
|
+
prepared?.target,
|
|
4742
4759
|
);
|
|
4743
4760
|
if (execution === TELEGRAM_UPDATE_WORKER_EXECUTION_ABORTED) {
|
|
4744
4761
|
return "aborted";
|
|
@@ -4821,6 +4838,11 @@ export function createTelegramUpdateWorkerRuntime<TContext>(
|
|
|
4821
4838
|
snapshotInvalidated = true;
|
|
4822
4839
|
break;
|
|
4823
4840
|
}
|
|
4841
|
+
if (prepared?.target) {
|
|
4842
|
+
prepared.ids = new Set([...prepared.ids].filter(id => id !== entry.updateId));
|
|
4843
|
+
if (!prepared.ids.size && livePreparation === prepared) livePreparation = undefined;
|
|
4844
|
+
updateClaimCounts();
|
|
4845
|
+
}
|
|
4824
4846
|
const postExecutionAuthority = checkAuthority(
|
|
4825
4847
|
expectedOwner,
|
|
4826
4848
|
entry.updateId,
|
|
@@ -5270,6 +5292,7 @@ export function createTelegramUpdateWorkerRuntime<TContext>(
|
|
|
5270
5292
|
prepareDeferredLiveInput(input) {
|
|
5271
5293
|
const expectedOwner = owner,
|
|
5272
5294
|
binding = deps.getJournalBindingKey?.(),
|
|
5295
|
+
onTransferred = input.onTransferred,
|
|
5273
5296
|
ids = [...input.sourceUpdateIds];
|
|
5274
5297
|
const sources = ids.map((id) => deferredSources.get(id));
|
|
5275
5298
|
const ownerCurrent = () =>
|
|
@@ -5282,7 +5305,7 @@ export function createTelegramUpdateWorkerRuntime<TContext>(
|
|
|
5282
5305
|
deps.hasAuthority(expectedOwner.ctx) &&
|
|
5283
5306
|
deps.isContextCurrent?.(expectedOwner.ctx) !== false &&
|
|
5284
5307
|
isQueueOwnerIdentityCurrent(expectedOwner) &&
|
|
5285
|
-
input.isCurrent();
|
|
5308
|
+
input.onTransferred === onTransferred && input.isCurrent();
|
|
5286
5309
|
const sourcesCurrent = () =>
|
|
5287
5310
|
ownerCurrent() &&
|
|
5288
5311
|
sources.every(
|
|
@@ -5381,13 +5404,11 @@ export function createTelegramUpdateWorkerRuntime<TContext>(
|
|
|
5381
5404
|
undefined,
|
|
5382
5405
|
() => current() && canSettle(),
|
|
5383
5406
|
);
|
|
5384
|
-
if (
|
|
5385
|
-
!current() ||
|
|
5386
|
-
!canSettle() ||
|
|
5387
|
-
result.removedUpdateIds.length !== ids.length ||
|
|
5388
|
-
!ids.every((id) => result.removedUpdateIds.includes(id))
|
|
5389
|
-
)
|
|
5407
|
+
if (result.removedUpdateIds.length !== ids.length || !ids.every(id => result.removedUpdateIds.includes(id)))
|
|
5390
5408
|
return "unknown";
|
|
5409
|
+
// A consumed routing clock is expected only after this exact ACK, never after uncertain removal.
|
|
5410
|
+
onTransferred?.call(input);
|
|
5411
|
+
if (!current() || !canSettle()) return "unknown";
|
|
5391
5412
|
} catch (error) {
|
|
5392
5413
|
recordRuntimeEvent(error, {
|
|
5393
5414
|
phase: "live-deferred-transfer-settlement",
|
|
@@ -5511,7 +5532,7 @@ export function createTelegramUpdateWorkerRuntime<TContext>(
|
|
|
5511
5532
|
}
|
|
5512
5533
|
};
|
|
5513
5534
|
const finish = () => {
|
|
5514
|
-
if (!held.released || !held.status) livePreparation = undefined;
|
|
5535
|
+
if (!held.released || !held.status && !held.target) livePreparation = undefined;
|
|
5515
5536
|
updateClaimCounts();
|
|
5516
5537
|
notifyStateChange();
|
|
5517
5538
|
blocked = false;
|
|
@@ -5659,7 +5680,7 @@ export function createTelegramUpdateWorkerRuntime<TContext>(
|
|
|
5659
5680
|
};
|
|
5660
5681
|
return held.status.current();
|
|
5661
5682
|
},
|
|
5662
|
-
release(canRelease) {
|
|
5683
|
+
release(canRelease, target) {
|
|
5663
5684
|
if (
|
|
5664
5685
|
!current() ||
|
|
5665
5686
|
held.discardIssued ||
|
|
@@ -5676,6 +5697,14 @@ export function createTelegramUpdateWorkerRuntime<TContext>(
|
|
|
5676
5697
|
!current()
|
|
5677
5698
|
)
|
|
5678
5699
|
return false;
|
|
5700
|
+
if (target) {
|
|
5701
|
+
if (!Number.isSafeInteger(target.chatId) || target.chatId === 0 || !Number.isSafeInteger(target.threadId) || target.threadId <= 0 ||
|
|
5702
|
+
entries.some(entry => {
|
|
5703
|
+
const message = (entry.update.message ?? entry.update.edited_message) as { chat?: { id?: number } } | undefined;
|
|
5704
|
+
return message?.chat?.id !== target.chatId;
|
|
5705
|
+
})) return false;
|
|
5706
|
+
if (!held.status) held.target = { ...target };
|
|
5707
|
+
}
|
|
5679
5708
|
released = true;
|
|
5680
5709
|
held.released = true;
|
|
5681
5710
|
finish();
|
|
@@ -8846,7 +8875,8 @@ function createTelegramUpdateSourceAdmissionHandle<
|
|
|
8846
8875
|
signal,
|
|
8847
8876
|
})
|
|
8848
8877
|
: undefined,
|
|
8849
|
-
|
|
8878
|
+
acknowledgeTransferred: () => { settlementReported = true; },
|
|
8879
|
+
prepareLiveInput: (sourceUpdateIds, isCurrent, onTransferred) =>
|
|
8850
8880
|
execution.isCurrent() &&
|
|
8851
8881
|
!immediate &&
|
|
8852
8882
|
!settlementReported &&
|
|
@@ -8856,6 +8886,7 @@ function createTelegramUpdateSourceAdmissionHandle<
|
|
|
8856
8886
|
sourceUpdateIds,
|
|
8857
8887
|
signal,
|
|
8858
8888
|
isCurrent: () => execution.isCurrent() && isCurrent(),
|
|
8889
|
+
onTransferred,
|
|
8859
8890
|
})
|
|
8860
8891
|
: undefined,
|
|
8861
8892
|
isHistorical: (matchesOriginal) =>
|
|
@@ -10599,11 +10630,19 @@ export function createTelegramUpdateAdmissionWorkerRuntime<
|
|
|
10599
10630
|
),
|
|
10600
10631
|
}
|
|
10601
10632
|
: {}),
|
|
10602
|
-
async executeUpdate(update, ctx, signal) {
|
|
10633
|
+
async executeUpdate(update, ctx, signal, target) {
|
|
10603
10634
|
const typedUpdate = update as TUpdate;
|
|
10635
|
+
const prepared = deps.prepareUpdateForExecution?.(typedUpdate) ?? typedUpdate;
|
|
10636
|
+
// Journal/source evidence remains the original; only the warm released handler projection changes destination.
|
|
10637
|
+
const routed = target ? { ...prepared,
|
|
10638
|
+
...(prepared.message ? { message: prepared.message.message_thread_id === target.threadId ? { ...prepared.message }
|
|
10639
|
+
: withoutTelegramTurnReplyAnchor({ ...prepared.message, message_thread_id: target.threadId, reply_to_message: undefined }) } : {}),
|
|
10640
|
+
...(prepared.edited_message ? { edited_message: prepared.edited_message.message_thread_id === target.threadId ? { ...prepared.edited_message }
|
|
10641
|
+
: withoutTelegramTurnReplyAnchor({ ...prepared.edited_message, message_thread_id: target.threadId, reply_to_message: undefined }) } : {}),
|
|
10642
|
+
} as TUpdate : prepared;
|
|
10604
10643
|
try {
|
|
10605
10644
|
return await executeUpdate(
|
|
10606
|
-
|
|
10645
|
+
routed,
|
|
10607
10646
|
ctx,
|
|
10608
10647
|
signal,
|
|
10609
10648
|
);
|