@llblab/pi-telegram 0.54.3 → 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 +21 -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 +5 -1
- package/dist/lib/bindings.js +9 -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 +56 -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 +14 -2
- package/dist/lib/routing.js +546 -207
- 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 +20 -5
- package/dist/lib/threads.js +111 -32
- 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 +16 -15
- 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 +8 -6
- package/lib/activity.ts +29 -5
- package/lib/bindings.ts +13 -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 +59 -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 +644 -283
- 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 +130 -38
- 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)) {
|
|
@@ -9229,6 +9313,10 @@ export interface TelegramCurrentThreadAssemblyDeps {
|
|
|
9229
9313
|
instanceId: string;
|
|
9230
9314
|
listRecords: TelegramCurrentInstanceThreadRuntimeDeps["listRecords"];
|
|
9231
9315
|
listWorkspaceBindings?: () => readonly TelegramWorkspaceThreadBinding[];
|
|
9316
|
+
/** Display-mode title for a binding whose tab has no acknowledged title yet (fresh, moved or reclaimed). */
|
|
9317
|
+
resolveAutomaticDisplayTitle?: (
|
|
9318
|
+
binding: TelegramWorkspaceThreadBinding,
|
|
9319
|
+
) => string | undefined;
|
|
9232
9320
|
getFollowerDisplayTitle?: () => string | undefined;
|
|
9233
9321
|
getActiveTurnTarget(): TelegramTarget | undefined;
|
|
9234
9322
|
getFollowerTarget(): TelegramTarget | undefined;
|
|
@@ -9290,9 +9378,13 @@ export function createTelegramCurrentThreadAssembly(
|
|
|
9290
9378
|
) {
|
|
9291
9379
|
return deps.getFollowerDisplayTitle?.();
|
|
9292
9380
|
}
|
|
9293
|
-
|
|
9381
|
+
const binding = deps
|
|
9294
9382
|
.listWorkspaceBindings?.()
|
|
9295
|
-
.find((
|
|
9383
|
+
.find((value) => targetMatches(value.target, target));
|
|
9384
|
+
return (
|
|
9385
|
+
binding?.displayTitle ??
|
|
9386
|
+
(binding ? deps.resolveAutomaticDisplayTitle?.(binding) : undefined)
|
|
9387
|
+
);
|
|
9296
9388
|
};
|
|
9297
9389
|
const displayIdentity = (
|
|
9298
9390
|
identity: TelegramInstanceThreadIdentityCandidate,
|
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",
|