@llblab/pi-kit 0.33.0 → 0.34.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/BACKLOG.md +2 -1
- package/CHANGELOG.md +9 -0
- package/README.md +3 -3
- package/node_modules/@llblab/pi-telegram/AGENTS.md +5 -4
- package/node_modules/@llblab/pi-telegram/BACKLOG.md +6 -1
- package/node_modules/@llblab/pi-telegram/CHANGELOG.md +20 -0
- package/node_modules/@llblab/pi-telegram/README.md +29 -18
- package/node_modules/@llblab/pi-telegram/dist/lib/bindings.d.ts +8 -4
- package/node_modules/@llblab/pi-telegram/dist/lib/bindings.js +41 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/bus-follower.d.ts +2 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/bus-follower.js +10 -5
- package/node_modules/@llblab/pi-telegram/dist/lib/bus-leader.d.ts +2 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/bus-leader.js +34 -20
- package/node_modules/@llblab/pi-telegram/dist/lib/bus.d.ts +10 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/bus.js +32 -13
- package/node_modules/@llblab/pi-telegram/dist/lib/commands.d.ts +1 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/commands.js +33 -28
- package/node_modules/@llblab/pi-telegram/dist/lib/extension.js +18 -3
- package/node_modules/@llblab/pi-telegram/dist/lib/lifecycle.d.ts +2 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/lifecycle.js +28 -20
- package/node_modules/@llblab/pi-telegram/dist/lib/locks.d.ts +6 -2
- package/node_modules/@llblab/pi-telegram/dist/lib/locks.js +3 -3
- package/node_modules/@llblab/pi-telegram/dist/lib/menu-queue.d.ts +1 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/menu-queue.js +102 -99
- package/node_modules/@llblab/pi-telegram/dist/lib/pi.d.ts +3 -2
- package/node_modules/@llblab/pi-telegram/dist/lib/pi.js +3 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/polling.d.ts +6 -2
- package/node_modules/@llblab/pi-telegram/dist/lib/polling.js +5 -3
- package/node_modules/@llblab/pi-telegram/dist/lib/queue.d.ts +57 -5
- package/node_modules/@llblab/pi-telegram/dist/lib/queue.js +313 -25
- package/node_modules/@llblab/pi-telegram/dist/lib/routing.d.ts +5 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/routing.js +323 -30
- package/node_modules/@llblab/pi-telegram/dist/lib/status.d.ts +1 -2
- package/node_modules/@llblab/pi-telegram/dist/lib/status.js +6 -7
- package/node_modules/@llblab/pi-telegram/dist/lib/sync.js +11 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/threads.d.ts +11 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/threads.js +57 -6
- package/node_modules/@llblab/pi-telegram/dist/lib/turns.d.ts +4 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/turns.js +6 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/updates.d.ts +14 -7
- package/node_modules/@llblab/pi-telegram/dist/lib/updates.js +44 -9
- package/node_modules/@llblab/pi-telegram/dist/package.json +1 -1
- package/node_modules/@llblab/pi-telegram/docs/README.md +1 -1
- package/node_modules/@llblab/pi-telegram/docs/architecture.md +27 -14
- package/node_modules/@llblab/pi-telegram/docs/callback-namespaces.md +4 -0
- package/node_modules/@llblab/pi-telegram/docs/multi-instance-bus.md +8 -4
- package/node_modules/@llblab/pi-telegram/docs/ui-style.md +14 -10
- package/node_modules/@llblab/pi-telegram/lib/bindings.ts +52 -3
- package/node_modules/@llblab/pi-telegram/lib/bus-follower.ts +13 -5
- package/node_modules/@llblab/pi-telegram/lib/bus-leader.ts +37 -23
- package/node_modules/@llblab/pi-telegram/lib/bus.ts +36 -10
- package/node_modules/@llblab/pi-telegram/lib/commands.ts +52 -47
- package/node_modules/@llblab/pi-telegram/lib/extension.ts +18 -2
- package/node_modules/@llblab/pi-telegram/lib/lifecycle.ts +34 -28
- package/node_modules/@llblab/pi-telegram/lib/locks.ts +5 -2
- package/node_modules/@llblab/pi-telegram/lib/menu-queue.ts +116 -162
- package/node_modules/@llblab/pi-telegram/lib/pi.ts +8 -0
- package/node_modules/@llblab/pi-telegram/lib/polling.ts +7 -2
- package/node_modules/@llblab/pi-telegram/lib/queue.ts +328 -33
- package/node_modules/@llblab/pi-telegram/lib/routing.ts +350 -30
- package/node_modules/@llblab/pi-telegram/lib/status.ts +7 -7
- package/node_modules/@llblab/pi-telegram/lib/sync.ts +11 -0
- package/node_modules/@llblab/pi-telegram/lib/threads.ts +57 -5
- package/node_modules/@llblab/pi-telegram/lib/turns.ts +7 -0
- package/node_modules/@llblab/pi-telegram/lib/updates.ts +46 -15
- package/node_modules/@llblab/pi-telegram/package.json +1 -1
- package/package.json +2 -2
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
isSameTelegramProcessInstance,
|
|
19
19
|
isTelegramTopicTargetStaleError,
|
|
20
20
|
provisionOwnBusTopic,
|
|
21
|
+
resetTelegramConnectionProvisioning,
|
|
21
22
|
type TelegramOwnTopicProvisionResult,
|
|
22
23
|
type TelegramTopicTargetStore,
|
|
23
24
|
type TelegramWorkspaceDisplayBinding,
|
|
@@ -826,6 +827,15 @@ export async function ensureTelegramLeaderThreadBinding(
|
|
|
826
827
|
...(deps.telegramProfile ? { telegramProfile: deps.telegramProfile } : {}),
|
|
827
828
|
};
|
|
828
829
|
const leaderProfileKey = getTelegramThreadOwnerKey(leaderOwner);
|
|
830
|
+
if (deps.forceFreshUnnamed) {
|
|
831
|
+
await resetTelegramConnectionProvisioning({
|
|
832
|
+
store: deps.topicTargetStore, owner: "leader", instanceId: deps.instanceId,
|
|
833
|
+
profileKey: leaderProfileKey, isCurrent: () => {
|
|
834
|
+
assertLeaderEpoch("connect-recovery");
|
|
835
|
+
return true;
|
|
836
|
+
}, recordEvent: deps.recordEvent,
|
|
837
|
+
});
|
|
838
|
+
}
|
|
829
839
|
const legacyLeaderRecord =
|
|
830
840
|
deps.topicTargetStore.getByProfileKey(leaderProfileKey);
|
|
831
841
|
let capacityUnavailable = false;
|
|
@@ -836,6 +846,7 @@ export async function ensureTelegramLeaderThreadBinding(
|
|
|
836
846
|
legacyLeaderRecord?.instanceId,
|
|
837
847
|
{
|
|
838
848
|
sessionId: deps.sessionId,
|
|
849
|
+
provisionProfileKey: leaderProfileKey,
|
|
839
850
|
onCapacityUnavailable() {
|
|
840
851
|
capacityUnavailable = true;
|
|
841
852
|
},
|
|
@@ -783,6 +783,7 @@ export interface TelegramTopicTargetStore {
|
|
|
783
783
|
) => { slot?: string; threadName?: string } | undefined;
|
|
784
784
|
listReservations: () => TelegramThreadReservation[];
|
|
785
785
|
listPendingProvisions: () => TelegramThreadPendingProvision[];
|
|
786
|
+
resetPendingProvisionForConnect: (expected: TelegramThreadPendingProvision, isCurrent: () => boolean) => Promise<boolean>;
|
|
786
787
|
listPendingCleanups: () => TelegramThreadCleanupIntent[];
|
|
787
788
|
listSyncObservations: () => TelegramTopicSyncObservation[];
|
|
788
789
|
reserveThread: (reservation: TelegramThreadReservation) => void;
|
|
@@ -902,6 +903,7 @@ export interface TelegramTopicTargetStore {
|
|
|
902
903
|
options?: {
|
|
903
904
|
existingBindingOnly?: boolean;
|
|
904
905
|
sessionId?: string;
|
|
906
|
+
provisionProfileKey?: string;
|
|
905
907
|
onCapacityUnavailable?: () => void;
|
|
906
908
|
},
|
|
907
909
|
) => TelegramWorkspaceBindingIdentity | undefined;
|
|
@@ -4168,6 +4170,7 @@ export function createTelegramTopicTargetStore(
|
|
|
4168
4170
|
}
|
|
4169
4171
|
),
|
|
4170
4172
|
isCurrentPublication?: () => boolean,
|
|
4173
|
+
resetProvisions?: readonly TelegramThreadPendingProvision[],
|
|
4171
4174
|
): Promise<boolean> => {
|
|
4172
4175
|
const submittedPath = getPath(),
|
|
4173
4176
|
submittedProfile = activeProfile();
|
|
@@ -4367,6 +4370,13 @@ export function createTelegramTopicTargetStore(
|
|
|
4367
4370
|
!!expectedRestore,
|
|
4368
4371
|
),
|
|
4369
4372
|
);
|
|
4373
|
+
if (resetProvisions) {
|
|
4374
|
+
const recoveries = readProvisionRecoveries(path, true);
|
|
4375
|
+
if (mutationRevision !== persistedRevision || resetProvisions.some(expected =>
|
|
4376
|
+
expected.target || Object.hasOwn(recoveries, expected.id) ||
|
|
4377
|
+
!isDeepStrictEqual(disk.pendingProvisions?.find(value => value.id === expected.id), expected)))
|
|
4378
|
+
throw new Error("Telegram connection recovery provisioning evidence changed.");
|
|
4379
|
+
}
|
|
4370
4380
|
if (!isDeepStrictEqual(disk.workspaceRestore, expectedRestore))
|
|
4371
4381
|
throw new Error(
|
|
4372
4382
|
"Workspace Restore evidence changed before publication.",
|
|
@@ -4429,7 +4439,7 @@ export function createTelegramTopicTargetStore(
|
|
|
4429
4439
|
}
|
|
4430
4440
|
: {}),
|
|
4431
4441
|
reservations: reservations.map((reservation) => ({ ...reservation })),
|
|
4432
|
-
pendingProvisions: pendingProvisions.map((provision) => ({
|
|
4442
|
+
pendingProvisions: pendingProvisions.filter(provision => !resetProvisions?.some(expected => expected.id === provision.id)).map((provision) => ({
|
|
4433
4443
|
...provision,
|
|
4434
4444
|
...(provision.target ? { target: { ...provision.target } } : {}),
|
|
4435
4445
|
})),
|
|
@@ -4636,6 +4646,7 @@ export function createTelegramTopicTargetStore(
|
|
|
4636
4646
|
loadedProfile = submittedProfile;
|
|
4637
4647
|
loaded = true;
|
|
4638
4648
|
observedWorkspaceSemantic = getTelegramStateSemanticSnapshot(wire);
|
|
4649
|
+
if (resetProvisions) { pendingProvisions = file.pendingProvisions; mutationRevision += 1; }
|
|
4639
4650
|
if (transition) {
|
|
4640
4651
|
records = new Map(
|
|
4641
4652
|
file.threads.map((record) => {
|
|
@@ -4808,6 +4819,10 @@ export function createTelegramTopicTargetStore(
|
|
|
4808
4819
|
if (isCurrentPublication?.() === false) return false;
|
|
4809
4820
|
loadedPath = path;
|
|
4810
4821
|
loaded = true;
|
|
4822
|
+
if (resetProvisions) {
|
|
4823
|
+
if (mutationRevision !== persistedRevision) return false;
|
|
4824
|
+
pendingProvisions = file.pendingProvisions;
|
|
4825
|
+
}
|
|
4811
4826
|
if (mutationRevision === persistedRevision) dirty = false;
|
|
4812
4827
|
return true;
|
|
4813
4828
|
});
|
|
@@ -6814,6 +6829,11 @@ export function createTelegramTopicTargetStore(
|
|
|
6814
6829
|
current.status = "ambiguous";
|
|
6815
6830
|
return true;
|
|
6816
6831
|
},
|
|
6832
|
+
resetPendingProvisionForConnect(expected, isCurrent) {
|
|
6833
|
+
// Stage no in-memory deletion: refusal or a stale publication must keep its protective hold.
|
|
6834
|
+
if (expected.target || !isCurrent()) return Promise.resolve(false);
|
|
6835
|
+
return persistSnapshot(undefined, isCurrent, [structuredClone(expected)]);
|
|
6836
|
+
},
|
|
6817
6837
|
removePendingProvision(id) {
|
|
6818
6838
|
const before = pendingProvisions.length;
|
|
6819
6839
|
pendingProvisions = pendingProvisions.filter(
|
|
@@ -7608,8 +7628,14 @@ export function createTelegramTopicTargetStore(
|
|
|
7608
7628
|
) {
|
|
7609
7629
|
return undefined;
|
|
7610
7630
|
}
|
|
7611
|
-
const
|
|
7612
|
-
|
|
7631
|
+
const ownedProvisions = pendingProvisions.filter(value =>
|
|
7632
|
+
value.workspaceBindingKey === identity.bindingKey &&
|
|
7633
|
+
(value.instanceId === instanceId || value.instanceId === effectivePreviousInstanceId ||
|
|
7634
|
+
(options?.provisionProfileKey !== undefined && value.profileKey === options.provisionProfileKey)));
|
|
7635
|
+
if (ownedProvisions.length > 1) return undefined;
|
|
7636
|
+
const ownProvision = ownedProvisions[0];
|
|
7637
|
+
const retainedTarget = ownProvision?.target ?? binding?.target ?? legacyRecord?.target;
|
|
7638
|
+
const retainedSlot = ownProvision?.slot ?? binding?.slot ?? legacyRecord?.slot;
|
|
7613
7639
|
const otherBindings = Array.from(workspaceBindings.values())
|
|
7614
7640
|
.filter(
|
|
7615
7641
|
(other) => other.bindingKey !== identity.bindingKey && other.slot,
|
|
@@ -7641,7 +7667,7 @@ export function createTelegramTopicTargetStore(
|
|
|
7641
7667
|
)
|
|
7642
7668
|
.map((reservation) => reservation.slot),
|
|
7643
7669
|
...pendingProvisions
|
|
7644
|
-
.filter((provision) =>
|
|
7670
|
+
.filter((provision) => provision !== ownProvision &&
|
|
7645
7671
|
isPendingProvisionLiveOrTargeted(provision, nowMs),
|
|
7646
7672
|
)
|
|
7647
7673
|
.map((provision) => provision.slot),
|
|
@@ -9661,6 +9687,32 @@ export function createTelegramTopicTargetRenamer(
|
|
|
9661
9687
|
};
|
|
9662
9688
|
}
|
|
9663
9689
|
|
|
9690
|
+
/** Caller holds profile Workspace admission; only an explicit connect grants a fresh creation attempt. */
|
|
9691
|
+
export async function resetTelegramConnectionProvisioning(input: {
|
|
9692
|
+
store: TelegramTopicTargetStore;
|
|
9693
|
+
owner: TelegramThreadPendingProvision["owner"];
|
|
9694
|
+
instanceId: string;
|
|
9695
|
+
profileKey: string;
|
|
9696
|
+
isCurrent: () => boolean;
|
|
9697
|
+
recordEvent: (category: string, message: unknown, details?: Record<string, unknown>) => void;
|
|
9698
|
+
}): Promise<void> {
|
|
9699
|
+
if (!input.isCurrent()) throw new Error("Telegram connection recovery authority changed.");
|
|
9700
|
+
const removed: TelegramThreadPendingProvision[] = [];
|
|
9701
|
+
for (const pending of input.store.listPendingProvisions()) {
|
|
9702
|
+
if (pending.owner !== input.owner || pending.target ||
|
|
9703
|
+
(pending.profileKey !== input.profileKey && pending.instanceId !== input.instanceId)) continue;
|
|
9704
|
+
if (!input.isCurrent()) throw new Error("Telegram connection recovery authority changed.");
|
|
9705
|
+
if (!await input.store.resetPendingProvisionForConnect(pending, input.isCurrent))
|
|
9706
|
+
throw new Error("Telegram connection recovery lost publication authority.");
|
|
9707
|
+
if (!input.isCurrent()) throw new Error("Telegram connection recovery authority changed.");
|
|
9708
|
+
removed.push(pending);
|
|
9709
|
+
}
|
|
9710
|
+
if (removed.length === 0) return;
|
|
9711
|
+
input.recordEvent("connection", "Explicit connect reset unfinished Thread creation; an unconfirmed Telegram Thread may remain.", {
|
|
9712
|
+
phase: "connect-provision-reset", count: removed.length, owner: input.owner,
|
|
9713
|
+
});
|
|
9714
|
+
}
|
|
9715
|
+
|
|
9664
9716
|
export function createTelegramTopicTargetProvisioner(
|
|
9665
9717
|
deps: TelegramTopicTargetProvisionerDeps,
|
|
9666
9718
|
): (
|
|
@@ -9923,7 +9975,7 @@ export function createTelegramTopicTargetProvisioner(
|
|
|
9923
9975
|
slot,
|
|
9924
9976
|
);
|
|
9925
9977
|
}
|
|
9926
|
-
const pendingId = `provision:${request.instanceId}:${slot}:${nowMs}`;
|
|
9978
|
+
const pendingId = `provision:${request.instanceId}:${slot}:${nowMs}:${randomUUID()}`;
|
|
9927
9979
|
const pendingOwner =
|
|
9928
9980
|
request.owner?.kind === "leader" ? "leader" : "manual-follower";
|
|
9929
9981
|
assertLeaderEpoch("before-pending-intent");
|
|
@@ -51,6 +51,13 @@ export function withoutTelegramTurnReplyAnchor<TMessage extends object>(message:
|
|
|
51
51
|
return { ...message, [REPLY_ANCHOR_SUPPRESSED]: true };
|
|
52
52
|
}
|
|
53
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
|
+
|
|
54
61
|
export const TELEGRAM_GUEST_TURN_NOTE =
|
|
55
62
|
"[guest] delivery: answer quickly with one concise, self-contained reply";
|
|
56
63
|
|
|
@@ -108,11 +108,10 @@ export type TelegramReactionType =
|
|
|
108
108
|
|
|
109
109
|
export const TELEGRAM_PRIORITY_REACTIONS = [
|
|
110
110
|
{ id: 10, name: "like", emoji: "👍" },
|
|
111
|
-
{ id: 11, name: "lightning", emoji: "⚡" },
|
|
112
111
|
{ id: 12, name: "heart", emoji: "❤" },
|
|
113
112
|
{ id: 13, name: "dove", emoji: "🕊" },
|
|
114
|
-
{ id: 14, name: "fire", emoji: "🔥" },
|
|
115
113
|
] as const;
|
|
114
|
+
export const TELEGRAM_STEER_REACTIONS = [{ id: 14, name: "fire", emoji: "🔥" }] as const;
|
|
116
115
|
export const TELEGRAM_REMOVAL_REACTIONS = [
|
|
117
116
|
{ id: 20, name: "dislike", emoji: "👎" },
|
|
118
117
|
{ id: 21, name: "ghost", emoji: "👻" },
|
|
@@ -160,14 +159,10 @@ function getTelegramQueueReactionTransition(
|
|
|
160
159
|
): TelegramQueueReactionDisposition | undefined {
|
|
161
160
|
const oldEmojis = collectTelegramReactionEmojis(oldReactions);
|
|
162
161
|
const newEmojis = collectTelegramReactionEmojis(newReactions);
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
);
|
|
167
|
-
const newPriorityEmoji = getTelegramReactionEmoji(
|
|
168
|
-
newEmojis,
|
|
169
|
-
TELEGRAM_PRIORITY_REACTION_EMOJIS,
|
|
170
|
-
);
|
|
162
|
+
// Fire is effective Steer intent; weaker positive aliases under it do not reorder the lane.
|
|
163
|
+
const steerEmoji = TELEGRAM_STEER_REACTIONS[0].emoji;
|
|
164
|
+
const oldPriorityEmoji = oldEmojis.has(steerEmoji) ? steerEmoji : getTelegramReactionEmoji(oldEmojis, TELEGRAM_PRIORITY_REACTION_EMOJIS);
|
|
165
|
+
const newPriorityEmoji = newEmojis.has(steerEmoji) ? steerEmoji : getTelegramReactionEmoji(newEmojis, TELEGRAM_PRIORITY_REACTION_EMOJIS);
|
|
171
166
|
const oldSuppressionEmoji = getTelegramReactionEmoji(
|
|
172
167
|
oldEmojis,
|
|
173
168
|
TELEGRAM_REMOVAL_REACTION_EMOJIS,
|
|
@@ -188,6 +183,7 @@ function getTelegramQueueReactionTransition(
|
|
|
188
183
|
> = { kind: "reaction-transition" };
|
|
189
184
|
if (oldPriorityEmoji !== newPriorityEmoji) {
|
|
190
185
|
transition.priorityEmoji = newPriorityEmoji ?? null;
|
|
186
|
+
if (newPriorityEmoji === steerEmoji) transition.promptLane = "steer";
|
|
191
187
|
}
|
|
192
188
|
if (oldSuppressionEmoji !== newSuppressionEmoji) {
|
|
193
189
|
transition.suppressionEmoji = newSuppressionEmoji ?? null;
|
|
@@ -374,6 +370,8 @@ export interface TelegramForeignOwnedUpdateForwarder<
|
|
|
374
370
|
message: TMessage;
|
|
375
371
|
ownership: TelegramTargetOwnershipView;
|
|
376
372
|
ctx: TContext;
|
|
373
|
+
/** Local issuance/ACK fence; implementations must not serialize it into payloads. */
|
|
374
|
+
assertAuthority?: () => void;
|
|
377
375
|
}) =>
|
|
378
376
|
| Promise<TelegramBusForeignUpdateSettlement>
|
|
379
377
|
| TelegramBusForeignUpdateSettlement;
|
|
@@ -549,6 +547,7 @@ interface TelegramUpdateAdmissionBinding {
|
|
|
549
547
|
inspectSource?: () => TelegramDeferredSourceEvidence | undefined;
|
|
550
548
|
inspectSourceSnapshot?: () => TelegramDeferredSourceSnapshot | undefined;
|
|
551
549
|
inspectCompletion?: () => TelegramDeferredSourceEvidence | undefined;
|
|
550
|
+
subscribeCompletion?: (notify: () => void) => () => void;
|
|
552
551
|
isSourceUnsettled?: () => boolean;
|
|
553
552
|
prepareLiveInput?: (
|
|
554
553
|
sourceUpdateIds: readonly number[],
|
|
@@ -617,6 +616,7 @@ export function bindTelegramUpdateAdmissionSource<
|
|
|
617
616
|
| "inspectSource"
|
|
618
617
|
| "inspectSourceSnapshot"
|
|
619
618
|
| "inspectCompletion"
|
|
619
|
+
| "subscribeCompletion"
|
|
620
620
|
| "isSourceUnsettled"
|
|
621
621
|
| "acknowledgeTransferred"
|
|
622
622
|
| "prepareLiveInput"
|
|
@@ -745,6 +745,15 @@ export function inspectTelegramDeferredSourceCompletion(
|
|
|
745
745
|
return execution.isCurrent() && source ? { ...source } : undefined;
|
|
746
746
|
}
|
|
747
747
|
|
|
748
|
+
/** Warm ACK notification only; subscribers must independently recheck exact completion and recipient authority. */
|
|
749
|
+
export function subscribeTelegramDeferredSourceCompletion(value: unknown, notify: () => void): (() => void) | undefined {
|
|
750
|
+
const execution = getTelegramUpdateExecutionFence(value);
|
|
751
|
+
if (execution?.isCurrent() !== true) return undefined;
|
|
752
|
+
return getTelegramUpdateAdmissionBinding(value)?.subscribeCompletion?.(() => {
|
|
753
|
+
if (execution.isCurrent()) notify();
|
|
754
|
+
});
|
|
755
|
+
}
|
|
756
|
+
|
|
748
757
|
/** Reuse only live bound deferred originals; raw IDs or cold/adopted input cannot mint this carrier. */
|
|
749
758
|
export function prepareTelegramLiveDeferredInput(
|
|
750
759
|
values: readonly unknown[],
|
|
@@ -8723,6 +8732,7 @@ function createTelegramUpdateSourceAdmissionHandle<
|
|
|
8723
8732
|
let outcome: TelegramUpdateAdmissionOutcome | undefined;
|
|
8724
8733
|
let completionAttempted = false;
|
|
8725
8734
|
let completedSource: TelegramDeferredSourceCompletion | undefined;
|
|
8735
|
+
const completionObservers = new Set<() => void>();
|
|
8726
8736
|
const boundUpdate = bindTelegramUpdateExecutionFence(
|
|
8727
8737
|
bindTelegramUpdateAdmissionSource(
|
|
8728
8738
|
update,
|
|
@@ -8779,11 +8789,12 @@ function createTelegramUpdateSourceAdmissionHandle<
|
|
|
8779
8789
|
execution.isCurrent() &&
|
|
8780
8790
|
completion.isCurrent() &&
|
|
8781
8791
|
isDeepStrictEqual(completion.source, expectedSource)
|
|
8782
|
-
)
|
|
8783
|
-
completedSource = {
|
|
8784
|
-
|
|
8785
|
-
|
|
8786
|
-
}
|
|
8792
|
+
) {
|
|
8793
|
+
completedSource = { ...completion, source: { ...expectedSource } };
|
|
8794
|
+
for (const notify of completionObservers) {
|
|
8795
|
+
try { notify(); } catch { /* Observation cannot invalidate an exact removal ACK. */ }
|
|
8796
|
+
}
|
|
8797
|
+
}
|
|
8787
8798
|
})
|
|
8788
8799
|
.catch((error) => {
|
|
8789
8800
|
try {
|
|
@@ -8844,6 +8855,10 @@ function createTelegramUpdateSourceAdmissionHandle<
|
|
|
8844
8855
|
signal,
|
|
8845
8856
|
})
|
|
8846
8857
|
: undefined,
|
|
8858
|
+
subscribeCompletion: (notify) => {
|
|
8859
|
+
completionObservers.add(notify);
|
|
8860
|
+
return () => { completionObservers.delete(notify); };
|
|
8861
|
+
},
|
|
8847
8862
|
inspectCompletion: () =>
|
|
8848
8863
|
execution.isCurrent() && completedSource?.isCurrent()
|
|
8849
8864
|
? { ...completedSource.source }
|
|
@@ -9320,6 +9335,7 @@ export function createTelegramQueueHandoffControlExecutionFactory<TContext>(
|
|
|
9320
9335
|
|
|
9321
9336
|
export interface TelegramQueueHandoffCoordinatorInput<TContext> {
|
|
9322
9337
|
item: TelegramQueueItem<TContext>;
|
|
9338
|
+
steerSupported?: boolean;
|
|
9323
9339
|
expectedOwner: TelegramUpdateJournalQueueOwner;
|
|
9324
9340
|
recipientOwner: TelegramUpdateJournalQueueOwnerIdentity;
|
|
9325
9341
|
handoffToken: string;
|
|
@@ -9375,6 +9391,7 @@ export async function coordinateTelegramQueueHandoff<TContext>(
|
|
|
9375
9391
|
const handoff = createTelegramQueueHandoff({
|
|
9376
9392
|
handoffToken: input.handoffToken,
|
|
9377
9393
|
item: input.item,
|
|
9394
|
+
steerSupported: input.steerSupported,
|
|
9378
9395
|
});
|
|
9379
9396
|
const receipt = handoff.payload.admissionReceipts[0];
|
|
9380
9397
|
if (!receipt || handoff.payload.admissionReceipts.length !== 1) {
|
|
@@ -9551,6 +9568,8 @@ export function createTelegramQueueHandoffRecipientRuntime<TContext>(
|
|
|
9551
9568
|
}
|
|
9552
9569
|
|
|
9553
9570
|
export interface TelegramQueueHandoffReconcilerDeps<TContext> {
|
|
9571
|
+
canHandoffSteerWithRecipient?: (recipient: TelegramBusFollowerView) => boolean;
|
|
9572
|
+
hasPendingDispatch?: () => boolean;
|
|
9554
9573
|
ownsDirect: () => boolean;
|
|
9555
9574
|
isFollowerRegistered: () => boolean;
|
|
9556
9575
|
isBusEnabled: () => boolean;
|
|
@@ -9589,6 +9608,8 @@ export interface TelegramQueueHandoffReconcilerDeps<TContext> {
|
|
|
9589
9608
|
export interface TelegramQueueHandoffReconciliationRuntimeAssemblyDeps<
|
|
9590
9609
|
TContext,
|
|
9591
9610
|
> {
|
|
9611
|
+
canHandoffSteerWithRecipient?: (recipient: TelegramBusFollowerView) => boolean;
|
|
9612
|
+
hasPendingDispatch?: () => boolean;
|
|
9592
9613
|
ownsDirect: () => boolean;
|
|
9593
9614
|
isFollowerRegistered: () => boolean;
|
|
9594
9615
|
isBusEnabled: () => boolean;
|
|
@@ -9628,6 +9649,8 @@ export function createTelegramQueueHandoffReconciliationRuntimeAssembly<
|
|
|
9628
9649
|
deps: TelegramQueueHandoffReconciliationRuntimeAssemblyDeps<TContext>,
|
|
9629
9650
|
): (ctx: TContext) => Promise<void> {
|
|
9630
9651
|
return createTelegramQueueHandoffReconciler({
|
|
9652
|
+
canHandoffSteerWithRecipient: deps.canHandoffSteerWithRecipient,
|
|
9653
|
+
hasPendingDispatch: deps.hasPendingDispatch,
|
|
9631
9654
|
ownsDirect: deps.ownsDirect,
|
|
9632
9655
|
isFollowerRegistered: deps.isFollowerRegistered,
|
|
9633
9656
|
isBusEnabled: deps.isBusEnabled,
|
|
@@ -9704,6 +9727,8 @@ export function createTelegramQueueHandoffReconciler<TContext>(
|
|
|
9704
9727
|
.filter((follower) => follower.instanceId !== deps.donorInstanceId);
|
|
9705
9728
|
if (followers.length === 0) return;
|
|
9706
9729
|
for (const item of [...deps.getQueuedItems()]) {
|
|
9730
|
+
if ((deps.hasPendingDispatch?.() && deps.getQueuedItems()[0] === item) ||
|
|
9731
|
+
(item.kind === "prompt" && (item.steerAttempted || item.steerPacketTail !== undefined || item.steerPacketSourceOrders !== undefined))) continue;
|
|
9707
9732
|
const target = item.target;
|
|
9708
9733
|
const recipient = target
|
|
9709
9734
|
? followers.find(
|
|
@@ -9713,6 +9738,8 @@ export function createTelegramQueueHandoffReconciler<TContext>(
|
|
|
9713
9738
|
)
|
|
9714
9739
|
: undefined;
|
|
9715
9740
|
if (!recipient) continue;
|
|
9741
|
+
const steerSupported = item.queueLane === "steer" && deps.canHandoffSteerWithRecipient?.(recipient) === true;
|
|
9742
|
+
if (item.queueLane === "steer" && !steerSupported) continue;
|
|
9716
9743
|
const receipt = item.admissionReceipts?.[0];
|
|
9717
9744
|
if (!receipt || item.admissionReceipts?.length !== 1) continue;
|
|
9718
9745
|
if (
|
|
@@ -9739,6 +9766,7 @@ export function createTelegramQueueHandoffReconciler<TContext>(
|
|
|
9739
9766
|
const handoffToken = deps.createHandoffToken();
|
|
9740
9767
|
const result = await coordinateTelegramQueueHandoff({
|
|
9741
9768
|
item,
|
|
9769
|
+
steerSupported,
|
|
9742
9770
|
expectedOwner,
|
|
9743
9771
|
recipientOwner: {
|
|
9744
9772
|
instanceId: recipient.instanceId,
|
|
@@ -9749,6 +9777,9 @@ export function createTelegramQueueHandoffReconciler<TContext>(
|
|
|
9749
9777
|
handoffToken,
|
|
9750
9778
|
lifecycle,
|
|
9751
9779
|
stageRemote: async ({ payload: sourcePayload }) => {
|
|
9780
|
+
if (sourcePayload.queueLane === "steer" && deps.canHandoffSteerWithRecipient?.(recipient) !== true) {
|
|
9781
|
+
throw new Error("Steer queue handoff capability changed before staging.");
|
|
9782
|
+
}
|
|
9752
9783
|
const payload = {
|
|
9753
9784
|
...sourcePayload,
|
|
9754
9785
|
admissionReceipts: [
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@llblab/pi-kit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.34.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
"@llblab/pi-codex-usage": "0.13.0",
|
|
48
48
|
"@llblab/pi-grow-loop": "0.9.0",
|
|
49
49
|
"@llblab/pi-state-flow": "0.26.4",
|
|
50
|
-
"@llblab/pi-telegram": "0.
|
|
50
|
+
"@llblab/pi-telegram": "0.56.1",
|
|
51
51
|
"@llblab/skills": "1.15.0"
|
|
52
52
|
},
|
|
53
53
|
"bundledDependencies": [
|