@llblab/pi-kit 0.33.1 → 0.34.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/BACKLOG.md +2 -1
- package/CHANGELOG.md +9 -0
- package/README.md +3 -3
- package/node_modules/@llblab/pi-telegram/AGENTS.md +6 -5
- 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 +28 -17
- 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 +1 -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 +8 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/bus.js +21 -7
- 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 +100 -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 +4 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/routing.js +62 -1
- 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/updates.d.ts +13 -32
- package/node_modules/@llblab/pi-telegram/dist/lib/updates.js +44 -15
- 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 +25 -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 -6
- package/node_modules/@llblab/pi-telegram/docs/ui-style.md +17 -17
- 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 +23 -7
- 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 +114 -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 +51 -1
- 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/updates.ts +44 -21
- package/node_modules/@llblab/pi-telegram/package.json +1 -1
- package/package.json +2 -2
|
@@ -605,9 +605,7 @@ function getTelegramStatusBarLabel(state) {
|
|
|
605
605
|
}
|
|
606
606
|
export function buildTelegramStatusBarText(theme, state) {
|
|
607
607
|
const label = theme.fg("accent", getTelegramStatusBarLabel(state));
|
|
608
|
-
const queued = state.queuedStatus
|
|
609
|
-
? theme.fg("warning", state.queuedStatus)
|
|
610
|
-
: "";
|
|
608
|
+
const queued = state.queuedStatus ? theme.fg("warning", state.queuedStatus) : "";
|
|
611
609
|
if (!state.hasBotToken)
|
|
612
610
|
return `${label} ${theme.fg("muted", "not configured")}${queued}`;
|
|
613
611
|
if (state.pollingStopReason === "persistent-conflict" &&
|
|
@@ -949,6 +947,7 @@ function buildTelegramInboundWorkerDiagnosticLines(worker) {
|
|
|
949
947
|
function countTelegramQueueLanes(items) {
|
|
950
948
|
const counts = {
|
|
951
949
|
control: 0,
|
|
950
|
+
steer: 0,
|
|
952
951
|
priority: 0,
|
|
953
952
|
default: 0,
|
|
954
953
|
};
|
|
@@ -957,9 +956,9 @@ function countTelegramQueueLanes(items) {
|
|
|
957
956
|
return counts;
|
|
958
957
|
}
|
|
959
958
|
function buildTelegramBridgeCompactStatusLines(state) {
|
|
960
|
-
const { control: controlQueueCount, priority: priorityQueueCount, default: defaultQueueCount, } = countTelegramQueueLanes(state.queuedItems);
|
|
959
|
+
const { control: controlQueueCount, priority: priorityQueueCount, default: defaultQueueCount, steer: steerQueueCount, } = countTelegramQueueLanes(state.queuedItems);
|
|
961
960
|
const queueLine = `- queued turns: ${state.queuedItems.length}${state.queuedItems.length > 0
|
|
962
|
-
? ` (control=${controlQueueCount}, priority=${priorityQueueCount}, default=${defaultQueueCount})`
|
|
961
|
+
? ` (control=${controlQueueCount}${steerQueueCount > 0 ? `, steer=${steerQueueCount}` : ""}, priority=${priorityQueueCount}, default=${defaultQueueCount})`
|
|
963
962
|
: ""}`;
|
|
964
963
|
const executionState = state.pendingDispatch
|
|
965
964
|
? "pending dispatch"
|
|
@@ -1017,7 +1016,7 @@ export function buildTelegramBridgeStatusLines(state, options = {}) {
|
|
|
1017
1016
|
return buildTelegramBridgeCompactStatusLines(state);
|
|
1018
1017
|
}
|
|
1019
1018
|
function buildTelegramBridgeDiagnosticStatusLines(state) {
|
|
1020
|
-
const { control: controlQueueCount, priority: priorityQueueCount, default: defaultQueueCount, } = countTelegramQueueLanes(state.queuedItems);
|
|
1019
|
+
const { control: controlQueueCount, priority: priorityQueueCount, default: defaultQueueCount, steer: steerQueueCount, } = countTelegramQueueLanes(state.queuedItems);
|
|
1021
1020
|
return [
|
|
1022
1021
|
"connection:",
|
|
1023
1022
|
`- bot: ${formatTelegramBridgeBotStatus(state)}`,
|
|
@@ -1063,7 +1062,7 @@ function buildTelegramBridgeDiagnosticStatusLines(state) {
|
|
|
1063
1062
|
"",
|
|
1064
1063
|
"queue:",
|
|
1065
1064
|
`- queued turns: ${state.queuedItems.length}`,
|
|
1066
|
-
`- lanes: control=${controlQueueCount}, priority=${priorityQueueCount}, default=${defaultQueueCount}`,
|
|
1065
|
+
`- lanes: control=${controlQueueCount}${steerQueueCount > 0 ? `, steer=${steerQueueCount}` : ""}, priority=${priorityQueueCount}, default=${defaultQueueCount}`,
|
|
1067
1066
|
...buildTelegramBusFollowerLines(state),
|
|
1068
1067
|
...buildTelegramLocalBusLines(state, { verbose: true }),
|
|
1069
1068
|
...buildTelegramTopicTargetDiagnosticLines(state),
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
import { getTelegramTargetKey } from "./target.js";
|
|
7
7
|
import { getTelegramApiErrorRequestTarget, isTelegramStaleTargetHttpError, } from "./telegram-api.js";
|
|
8
8
|
import * as ThreadReconciler from "./thread-reconciler.js";
|
|
9
|
-
import { commitTelegramWorkspaceProvisionBinding, createTelegramCleanupTargetProtection, getTelegramTargetFromApiBody, getTelegramThreadOwnerKey, isSameTelegramProcessInstance, isTelegramTopicTargetStaleError, provisionOwnBusTopic, } from "./threads.js";
|
|
9
|
+
import { commitTelegramWorkspaceProvisionBinding, createTelegramCleanupTargetProtection, getTelegramTargetFromApiBody, getTelegramThreadOwnerKey, isSameTelegramProcessInstance, isTelegramTopicTargetStaleError, provisionOwnBusTopic, resetTelegramConnectionProvisioning, } from "./threads.js";
|
|
10
10
|
import { createTelegramWorkspaceAdmissionOperationId, runWithTelegramWorkspaceAdmissionsAsync, } from "./workspace-admission.js";
|
|
11
11
|
import { normalizeTelegramWorkspacePath } from "./workspace-identity.js";
|
|
12
12
|
import { TelegramWorkspaceSlotUnavailableError } from "./workspace-slots.js";
|
|
@@ -463,11 +463,21 @@ export async function ensureTelegramLeaderThreadBinding(deps) {
|
|
|
463
463
|
...(deps.telegramProfile ? { telegramProfile: deps.telegramProfile } : {}),
|
|
464
464
|
};
|
|
465
465
|
const leaderProfileKey = getTelegramThreadOwnerKey(leaderOwner);
|
|
466
|
+
if (deps.forceFreshUnnamed) {
|
|
467
|
+
await resetTelegramConnectionProvisioning({
|
|
468
|
+
store: deps.topicTargetStore, owner: "leader", instanceId: deps.instanceId,
|
|
469
|
+
profileKey: leaderProfileKey, isCurrent: () => {
|
|
470
|
+
assertLeaderEpoch("connect-recovery");
|
|
471
|
+
return true;
|
|
472
|
+
}, recordEvent: deps.recordEvent,
|
|
473
|
+
});
|
|
474
|
+
}
|
|
466
475
|
const legacyLeaderRecord = deps.topicTargetStore.getByProfileKey(leaderProfileKey);
|
|
467
476
|
let capacityUnavailable = false;
|
|
468
477
|
const workspaceIdentity = normalizedLeaderCwd
|
|
469
478
|
? deps.topicTargetStore.claimWorkspaceIdentity(normalizedLeaderCwd, deps.instanceId, legacyLeaderRecord?.instanceId, {
|
|
470
479
|
sessionId: deps.sessionId,
|
|
480
|
+
provisionProfileKey: leaderProfileKey,
|
|
471
481
|
onCapacityUnavailable() {
|
|
472
482
|
capacityUnavailable = true;
|
|
473
483
|
},
|
|
@@ -495,6 +495,7 @@ export interface TelegramTopicTargetStore {
|
|
|
495
495
|
} | undefined;
|
|
496
496
|
listReservations: () => TelegramThreadReservation[];
|
|
497
497
|
listPendingProvisions: () => TelegramThreadPendingProvision[];
|
|
498
|
+
resetPendingProvisionForConnect: (expected: TelegramThreadPendingProvision, isCurrent: () => boolean) => Promise<boolean>;
|
|
498
499
|
listPendingCleanups: () => TelegramThreadCleanupIntent[];
|
|
499
500
|
listSyncObservations: () => TelegramTopicSyncObservation[];
|
|
500
501
|
reserveThread: (reservation: TelegramThreadReservation) => void;
|
|
@@ -559,6 +560,7 @@ export interface TelegramTopicTargetStore {
|
|
|
559
560
|
claimWorkspaceIdentity: (cwd: string, instanceId: string, previousInstanceId?: string, options?: {
|
|
560
561
|
existingBindingOnly?: boolean;
|
|
561
562
|
sessionId?: string;
|
|
563
|
+
provisionProfileKey?: string;
|
|
562
564
|
onCapacityUnavailable?: () => void;
|
|
563
565
|
}) => TelegramWorkspaceBindingIdentity | undefined;
|
|
564
566
|
releaseWorkspaceClaim: (instanceId: string) => boolean;
|
|
@@ -957,4 +959,13 @@ export declare function getTelegramTargetFromApiBody(body: unknown): (TelegramTa
|
|
|
957
959
|
export declare function isTelegramTopicTargetStaleError(error: unknown): boolean;
|
|
958
960
|
export declare function isTelegramTopicModeUnavailableError(error: unknown): boolean;
|
|
959
961
|
export declare function createTelegramTopicTargetRenamer(deps: TelegramTopicTargetRenamerDeps): (request: TelegramTopicTargetRenameRequest) => Promise<TelegramTopicTargetRecord | undefined>;
|
|
962
|
+
/** Caller holds profile Workspace admission; only an explicit connect grants a fresh creation attempt. */
|
|
963
|
+
export declare function resetTelegramConnectionProvisioning(input: {
|
|
964
|
+
store: TelegramTopicTargetStore;
|
|
965
|
+
owner: TelegramThreadPendingProvision["owner"];
|
|
966
|
+
instanceId: string;
|
|
967
|
+
profileKey: string;
|
|
968
|
+
isCurrent: () => boolean;
|
|
969
|
+
recordEvent: (category: string, message: unknown, details?: Record<string, unknown>) => void;
|
|
970
|
+
}): Promise<void>;
|
|
960
971
|
export declare function createTelegramTopicTargetProvisioner(deps: TelegramTopicTargetProvisionerDeps): (request: TelegramTopicTargetProvisionRequest) => Promise<TelegramTopicTargetProvisionResult>;
|
|
@@ -2304,7 +2304,7 @@ export function createTelegramTopicTargetStore(options) {
|
|
|
2304
2304
|
}
|
|
2305
2305
|
return false;
|
|
2306
2306
|
};
|
|
2307
|
-
const persistSnapshot = (transition, isCurrentPublication) => {
|
|
2307
|
+
const persistSnapshot = (transition, isCurrentPublication, resetProvisions) => {
|
|
2308
2308
|
const submittedPath = getPath(), submittedProfile = activeProfile();
|
|
2309
2309
|
const publishWorkspace = workspaceIO?.capturePublication();
|
|
2310
2310
|
const persist = persistQueue.then(async () => {
|
|
@@ -2437,6 +2437,12 @@ export function createTelegramTopicTargetStore(options) {
|
|
|
2437
2437
|
: readStoreSnapshot(path, transition?.kind === "relocate" ||
|
|
2438
2438
|
transition?.kind === "live-rebind" ||
|
|
2439
2439
|
!!expectedRestore));
|
|
2440
|
+
if (resetProvisions) {
|
|
2441
|
+
const recoveries = readProvisionRecoveries(path, true);
|
|
2442
|
+
if (mutationRevision !== persistedRevision || resetProvisions.some(expected => expected.target || Object.hasOwn(recoveries, expected.id) ||
|
|
2443
|
+
!isDeepStrictEqual(disk.pendingProvisions?.find(value => value.id === expected.id), expected)))
|
|
2444
|
+
throw new Error("Telegram connection recovery provisioning evidence changed.");
|
|
2445
|
+
}
|
|
2440
2446
|
if (!isDeepStrictEqual(disk.workspaceRestore, expectedRestore))
|
|
2441
2447
|
throw new Error("Workspace Restore evidence changed before publication.");
|
|
2442
2448
|
if (!manualNameMatches(disk) ||
|
|
@@ -2471,7 +2477,7 @@ export function createTelegramTopicTargetStore(options) {
|
|
|
2471
2477
|
}
|
|
2472
2478
|
: {}),
|
|
2473
2479
|
reservations: reservations.map((reservation) => ({ ...reservation })),
|
|
2474
|
-
pendingProvisions: pendingProvisions.map((provision) => ({
|
|
2480
|
+
pendingProvisions: pendingProvisions.filter(provision => !resetProvisions?.some(expected => expected.id === provision.id)).map((provision) => ({
|
|
2475
2481
|
...provision,
|
|
2476
2482
|
...(provision.target ? { target: { ...provision.target } } : {}),
|
|
2477
2483
|
})),
|
|
@@ -2626,6 +2632,10 @@ export function createTelegramTopicTargetStore(options) {
|
|
|
2626
2632
|
loadedProfile = submittedProfile;
|
|
2627
2633
|
loaded = true;
|
|
2628
2634
|
observedWorkspaceSemantic = getTelegramStateSemanticSnapshot(wire);
|
|
2635
|
+
if (resetProvisions) {
|
|
2636
|
+
pendingProvisions = file.pendingProvisions;
|
|
2637
|
+
mutationRevision += 1;
|
|
2638
|
+
}
|
|
2629
2639
|
if (transition) {
|
|
2630
2640
|
records = new Map(file.threads.map((record) => {
|
|
2631
2641
|
const normalized = normalizeRecord(record);
|
|
@@ -2765,6 +2775,11 @@ export function createTelegramTopicTargetStore(options) {
|
|
|
2765
2775
|
return false;
|
|
2766
2776
|
loadedPath = path;
|
|
2767
2777
|
loaded = true;
|
|
2778
|
+
if (resetProvisions) {
|
|
2779
|
+
if (mutationRevision !== persistedRevision)
|
|
2780
|
+
return false;
|
|
2781
|
+
pendingProvisions = file.pendingProvisions;
|
|
2782
|
+
}
|
|
2768
2783
|
if (mutationRevision === persistedRevision)
|
|
2769
2784
|
dirty = false;
|
|
2770
2785
|
return true;
|
|
@@ -4209,6 +4224,12 @@ export function createTelegramTopicTargetStore(options) {
|
|
|
4209
4224
|
current.status = "ambiguous";
|
|
4210
4225
|
return true;
|
|
4211
4226
|
},
|
|
4227
|
+
resetPendingProvisionForConnect(expected, isCurrent) {
|
|
4228
|
+
// Stage no in-memory deletion: refusal or a stale publication must keep its protective hold.
|
|
4229
|
+
if (expected.target || !isCurrent())
|
|
4230
|
+
return Promise.resolve(false);
|
|
4231
|
+
return persistSnapshot(undefined, isCurrent, [structuredClone(expected)]);
|
|
4232
|
+
},
|
|
4212
4233
|
removePendingProvision(id) {
|
|
4213
4234
|
const before = pendingProvisions.length;
|
|
4214
4235
|
pendingProvisions = pendingProvisions.filter((provision) => provision.id !== id);
|
|
@@ -4865,8 +4886,14 @@ export function createTelegramTopicTargetStore(options) {
|
|
|
4865
4886
|
liveRecord.instanceId !== effectivePreviousInstanceId) {
|
|
4866
4887
|
return undefined;
|
|
4867
4888
|
}
|
|
4868
|
-
const
|
|
4869
|
-
|
|
4889
|
+
const ownedProvisions = pendingProvisions.filter(value => value.workspaceBindingKey === identity.bindingKey &&
|
|
4890
|
+
(value.instanceId === instanceId || value.instanceId === effectivePreviousInstanceId ||
|
|
4891
|
+
(options?.provisionProfileKey !== undefined && value.profileKey === options.provisionProfileKey)));
|
|
4892
|
+
if (ownedProvisions.length > 1)
|
|
4893
|
+
return undefined;
|
|
4894
|
+
const ownProvision = ownedProvisions[0];
|
|
4895
|
+
const retainedTarget = ownProvision?.target ?? binding?.target ?? legacyRecord?.target;
|
|
4896
|
+
const retainedSlot = ownProvision?.slot ?? binding?.slot ?? legacyRecord?.slot;
|
|
4870
4897
|
const otherBindings = Array.from(workspaceBindings.values())
|
|
4871
4898
|
.filter((other) => other.bindingKey !== identity.bindingKey && other.slot)
|
|
4872
4899
|
.map((other) => ({
|
|
@@ -4886,7 +4913,8 @@ export function createTelegramTopicTargetStore(options) {
|
|
|
4886
4913
|
reservation.expiresAtMs > nowMs)
|
|
4887
4914
|
.map((reservation) => reservation.slot),
|
|
4888
4915
|
...pendingProvisions
|
|
4889
|
-
.filter((provision) =>
|
|
4916
|
+
.filter((provision) => provision !== ownProvision &&
|
|
4917
|
+
isPendingProvisionLiveOrTargeted(provision, nowMs))
|
|
4890
4918
|
.map((provision) => provision.slot),
|
|
4891
4919
|
...externalReservedSlots,
|
|
4892
4920
|
]
|
|
@@ -6349,6 +6377,29 @@ export function createTelegramTopicTargetRenamer(deps) {
|
|
|
6349
6377
|
return renamed;
|
|
6350
6378
|
};
|
|
6351
6379
|
}
|
|
6380
|
+
/** Caller holds profile Workspace admission; only an explicit connect grants a fresh creation attempt. */
|
|
6381
|
+
export async function resetTelegramConnectionProvisioning(input) {
|
|
6382
|
+
if (!input.isCurrent())
|
|
6383
|
+
throw new Error("Telegram connection recovery authority changed.");
|
|
6384
|
+
const removed = [];
|
|
6385
|
+
for (const pending of input.store.listPendingProvisions()) {
|
|
6386
|
+
if (pending.owner !== input.owner || pending.target ||
|
|
6387
|
+
(pending.profileKey !== input.profileKey && pending.instanceId !== input.instanceId))
|
|
6388
|
+
continue;
|
|
6389
|
+
if (!input.isCurrent())
|
|
6390
|
+
throw new Error("Telegram connection recovery authority changed.");
|
|
6391
|
+
if (!await input.store.resetPendingProvisionForConnect(pending, input.isCurrent))
|
|
6392
|
+
throw new Error("Telegram connection recovery lost publication authority.");
|
|
6393
|
+
if (!input.isCurrent())
|
|
6394
|
+
throw new Error("Telegram connection recovery authority changed.");
|
|
6395
|
+
removed.push(pending);
|
|
6396
|
+
}
|
|
6397
|
+
if (removed.length === 0)
|
|
6398
|
+
return;
|
|
6399
|
+
input.recordEvent("connection", "Explicit connect reset unfinished Thread creation; an unconfirmed Telegram Thread may remain.", {
|
|
6400
|
+
phase: "connect-provision-reset", count: removed.length, owner: input.owner,
|
|
6401
|
+
});
|
|
6402
|
+
}
|
|
6352
6403
|
export function createTelegramTopicTargetProvisioner(deps) {
|
|
6353
6404
|
const getNowMs = deps.getNowMs ?? (() => 0);
|
|
6354
6405
|
const getRandom = deps.getRandom;
|
|
@@ -6554,7 +6605,7 @@ export function createTelegramTopicTargetProvisioner(deps) {
|
|
|
6554
6605
|
}
|
|
6555
6606
|
displayTitle = buildTelegramTopicName({ ...request, threadName: projectedTitle }, "{threadName}", slot);
|
|
6556
6607
|
}
|
|
6557
|
-
const pendingId = `provision:${request.instanceId}:${slot}:${nowMs}`;
|
|
6608
|
+
const pendingId = `provision:${request.instanceId}:${slot}:${nowMs}:${randomUUID()}`;
|
|
6558
6609
|
const pendingOwner = request.owner?.kind === "leader" ? "leader" : "manual-follower";
|
|
6559
6610
|
assertLeaderEpoch("before-pending-intent");
|
|
6560
6611
|
const pendingBase = {
|
|
@@ -22,19 +22,8 @@ export declare const TELEGRAM_PRIORITY_REACTIONS: readonly [{
|
|
|
22
22
|
readonly id: 10;
|
|
23
23
|
readonly name: "like";
|
|
24
24
|
readonly emoji: "👍";
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
readonly name: "lightning";
|
|
28
|
-
readonly emoji: "⚡";
|
|
29
|
-
}, {
|
|
30
|
-
readonly id: 12;
|
|
31
|
-
readonly name: "heart";
|
|
32
|
-
readonly emoji: "❤";
|
|
33
|
-
}, {
|
|
34
|
-
readonly id: 13;
|
|
35
|
-
readonly name: "dove";
|
|
36
|
-
readonly emoji: "🕊";
|
|
37
|
-
}, {
|
|
25
|
+
}];
|
|
26
|
+
export declare const TELEGRAM_STEER_REACTIONS: readonly [{
|
|
38
27
|
readonly id: 14;
|
|
39
28
|
readonly name: "fire";
|
|
40
29
|
readonly emoji: "🔥";
|
|
@@ -43,25 +32,9 @@ export declare const TELEGRAM_REMOVAL_REACTIONS: readonly [{
|
|
|
43
32
|
readonly id: 20;
|
|
44
33
|
readonly name: "dislike";
|
|
45
34
|
readonly emoji: "👎";
|
|
46
|
-
}, {
|
|
47
|
-
readonly id: 21;
|
|
48
|
-
readonly name: "ghost";
|
|
49
|
-
readonly emoji: "👻";
|
|
50
|
-
}, {
|
|
51
|
-
readonly id: 22;
|
|
52
|
-
readonly name: "broken-heart";
|
|
53
|
-
readonly emoji: "💔";
|
|
54
|
-
}, {
|
|
55
|
-
readonly id: 23;
|
|
56
|
-
readonly name: "poop";
|
|
57
|
-
readonly emoji: "💩";
|
|
58
|
-
}, {
|
|
59
|
-
readonly id: 24;
|
|
60
|
-
readonly name: "wastebasket";
|
|
61
|
-
readonly emoji: "🗑";
|
|
62
35
|
}];
|
|
63
|
-
export declare const TELEGRAM_PRIORITY_REACTION_EMOJIS:
|
|
64
|
-
export declare const TELEGRAM_REMOVAL_REACTION_EMOJIS:
|
|
36
|
+
export declare const TELEGRAM_PRIORITY_REACTION_EMOJIS: "👍"[];
|
|
37
|
+
export declare const TELEGRAM_REMOVAL_REACTION_EMOJIS: "👎"[];
|
|
65
38
|
export interface TelegramUpdateDeletion {
|
|
66
39
|
deleted_business_messages?: {
|
|
67
40
|
message_ids?: unknown;
|
|
@@ -230,6 +203,7 @@ interface TelegramUpdateAdmissionBinding {
|
|
|
230
203
|
inspectSource?: () => TelegramDeferredSourceEvidence | undefined;
|
|
231
204
|
inspectSourceSnapshot?: () => TelegramDeferredSourceSnapshot | undefined;
|
|
232
205
|
inspectCompletion?: () => TelegramDeferredSourceEvidence | undefined;
|
|
206
|
+
subscribeCompletion?: (notify: () => void) => () => void;
|
|
233
207
|
isSourceUnsettled?: () => boolean;
|
|
234
208
|
prepareLiveInput?: (sourceUpdateIds: readonly number[], isCurrent: () => boolean, onTransferred?: () => void) => TelegramLiveDeferredInputPreparation | undefined;
|
|
235
209
|
/** Internal notification after the worker's exact donor-removal ACK; never issues completion itself. */
|
|
@@ -245,7 +219,7 @@ interface TelegramUpdateAdmissionBinding {
|
|
|
245
219
|
export type TelegramQueueAdmissionReceiptLike = TelegramQueueAdmissionReceipt;
|
|
246
220
|
export declare function bindTelegramUpdateAdmissionSource<TUpdate extends TelegramUpdateFlow & {
|
|
247
221
|
update_id: number;
|
|
248
|
-
}>(update: TUpdate, report: TelegramUpdateAdmissionBinding["report"], controls?: Pick<TelegramUpdateAdmissionBinding, "abandon" | "armRoutingInput" | "getRoutingInput" | "acquireRouting" | "inspectSource" | "inspectSourceSnapshot" | "inspectCompletion" | "isSourceUnsettled" | "acknowledgeTransferred" | "prepareLiveInput" | "prepareQueueAdmission" | "prepareSourceCompletion" | "supportsAbandonment" | "inspectAbandoning" | "inspectHistorical" | "isHistorical" | "isHistoricalReviewHeld">): TUpdate;
|
|
222
|
+
}>(update: TUpdate, report: TelegramUpdateAdmissionBinding["report"], controls?: Pick<TelegramUpdateAdmissionBinding, "abandon" | "armRoutingInput" | "getRoutingInput" | "acquireRouting" | "inspectSource" | "inspectSourceSnapshot" | "inspectCompletion" | "subscribeCompletion" | "isSourceUnsettled" | "acknowledgeTransferred" | "prepareLiveInput" | "prepareQueueAdmission" | "prepareSourceCompletion" | "supportsAbandonment" | "inspectAbandoning" | "inspectHistorical" | "isHistorical" | "isHistoricalReviewHeld">): TUpdate;
|
|
249
223
|
/**
|
|
250
224
|
* Conservative read-only census: any retained entry naming this Thread, in any nested Telegram object or state,
|
|
251
225
|
* is unresolved custody, except a lone button tap, which carries no input (the in-flight Cancel itself).
|
|
@@ -265,6 +239,8 @@ export declare function inspectTelegramDeferredSource(value: unknown): TelegramD
|
|
|
265
239
|
export declare function inspectTelegramDeferredSourceSnapshot(value: unknown): TelegramDeferredSourceSnapshot | undefined;
|
|
266
240
|
/** Exact warm removal ACK only; reporting, source absence and a replaced worker cannot supply it. */
|
|
267
241
|
export declare function inspectTelegramDeferredSourceCompletion(value: unknown): TelegramDeferredSourceEvidence | undefined;
|
|
242
|
+
/** Warm ACK notification only; subscribers must independently recheck exact completion and recipient authority. */
|
|
243
|
+
export declare function subscribeTelegramDeferredSourceCompletion(value: unknown, notify: () => void): (() => void) | undefined;
|
|
268
244
|
/** Reuse only live bound deferred originals; raw IDs or cold/adopted input cannot mint this carrier. */
|
|
269
245
|
export declare function prepareTelegramLiveDeferredInput(values: readonly unknown[], isCurrent: () => boolean): TelegramLiveDeferredInputPreparation | undefined;
|
|
270
246
|
/** A route-owned pre-disposition acceptance publisher; wraps only this carrier, never the shared worker binding. */
|
|
@@ -1319,6 +1295,7 @@ export interface TelegramQueueHandoffControlExecutionDeps<TContext> {
|
|
|
1319
1295
|
export declare function createTelegramQueueHandoffControlExecutionFactory<TContext>(deps: TelegramQueueHandoffControlExecutionDeps<TContext>): (payload: TelegramControlQueueHandoffPayload) => PendingTelegramControlItem<TContext>["execute"];
|
|
1320
1296
|
export interface TelegramQueueHandoffCoordinatorInput<TContext> {
|
|
1321
1297
|
item: TelegramQueueItem<TContext>;
|
|
1298
|
+
steerSupported?: boolean;
|
|
1322
1299
|
expectedOwner: TelegramUpdateJournalQueueOwner;
|
|
1323
1300
|
recipientOwner: TelegramUpdateJournalQueueOwnerIdentity;
|
|
1324
1301
|
handoffToken: string;
|
|
@@ -1358,6 +1335,8 @@ export declare function createTelegramQueueHandoffRecipientRuntime<TContext>(dep
|
|
|
1358
1335
|
kind: "leader.offerQueueHandoff";
|
|
1359
1336
|
}>, ctx: TContext) => Promise<TelegramQueueHandoffStageResult>;
|
|
1360
1337
|
export interface TelegramQueueHandoffReconcilerDeps<TContext> {
|
|
1338
|
+
canHandoffSteerWithRecipient?: (recipient: TelegramBusFollowerView) => boolean;
|
|
1339
|
+
hasPendingDispatch?: () => boolean;
|
|
1361
1340
|
ownsDirect: () => boolean;
|
|
1362
1341
|
isFollowerRegistered: () => boolean;
|
|
1363
1342
|
isBusEnabled: () => boolean;
|
|
@@ -1382,6 +1361,8 @@ export interface TelegramQueueHandoffReconcilerDeps<TContext> {
|
|
|
1382
1361
|
recordFailure?: (error: unknown, details: Record<string, unknown>) => void;
|
|
1383
1362
|
}
|
|
1384
1363
|
export interface TelegramQueueHandoffReconciliationRuntimeAssemblyDeps<TContext> {
|
|
1364
|
+
canHandoffSteerWithRecipient?: (recipient: TelegramBusFollowerView) => boolean;
|
|
1365
|
+
hasPendingDispatch?: () => boolean;
|
|
1385
1366
|
ownsDirect: () => boolean;
|
|
1386
1367
|
isFollowerRegistered: () => boolean;
|
|
1387
1368
|
isBusEnabled: () => boolean;
|
|
@@ -14,17 +14,10 @@ import { createTelegramPrivateTarget, createTelegramThreadTarget, } from "./targ
|
|
|
14
14
|
import { isWireRecord as isTelegramUpdateAdmissionRecord, isNonEmptyWireString as isTelegramUpdateAdmissionString, } from "./wire.js";
|
|
15
15
|
export const TELEGRAM_PRIORITY_REACTIONS = [
|
|
16
16
|
{ id: 10, name: "like", emoji: "👍" },
|
|
17
|
-
{ id: 11, name: "lightning", emoji: "⚡" },
|
|
18
|
-
{ id: 12, name: "heart", emoji: "❤" },
|
|
19
|
-
{ id: 13, name: "dove", emoji: "🕊" },
|
|
20
|
-
{ id: 14, name: "fire", emoji: "🔥" },
|
|
21
17
|
];
|
|
18
|
+
export const TELEGRAM_STEER_REACTIONS = [{ id: 14, name: "fire", emoji: "🔥" }];
|
|
22
19
|
export const TELEGRAM_REMOVAL_REACTIONS = [
|
|
23
20
|
{ id: 20, name: "dislike", emoji: "👎" },
|
|
24
|
-
{ id: 21, name: "ghost", emoji: "👻" },
|
|
25
|
-
{ id: 22, name: "broken-heart", emoji: "💔" },
|
|
26
|
-
{ id: 23, name: "poop", emoji: "💩" },
|
|
27
|
-
{ id: 24, name: "wastebasket", emoji: "🗑" },
|
|
28
21
|
];
|
|
29
22
|
export const TELEGRAM_PRIORITY_REACTION_EMOJIS = TELEGRAM_PRIORITY_REACTIONS.map((reaction) => reaction.emoji);
|
|
30
23
|
export const TELEGRAM_REMOVAL_REACTION_EMOJIS = TELEGRAM_REMOVAL_REACTIONS.map((reaction) => reaction.emoji);
|
|
@@ -47,8 +40,10 @@ function getTelegramReactionEmoji(emojis, candidates) {
|
|
|
47
40
|
function getTelegramQueueReactionTransition(oldReactions, newReactions) {
|
|
48
41
|
const oldEmojis = collectTelegramReactionEmojis(oldReactions);
|
|
49
42
|
const newEmojis = collectTelegramReactionEmojis(newReactions);
|
|
50
|
-
|
|
51
|
-
const
|
|
43
|
+
// Fire selects Steer; thumbs-up beneath it does not reorder the lane. Other emoji carry no queue intent.
|
|
44
|
+
const steerEmoji = TELEGRAM_STEER_REACTIONS[0].emoji;
|
|
45
|
+
const oldPriorityEmoji = oldEmojis.has(steerEmoji) ? steerEmoji : getTelegramReactionEmoji(oldEmojis, TELEGRAM_PRIORITY_REACTION_EMOJIS);
|
|
46
|
+
const newPriorityEmoji = newEmojis.has(steerEmoji) ? steerEmoji : getTelegramReactionEmoji(newEmojis, TELEGRAM_PRIORITY_REACTION_EMOJIS);
|
|
52
47
|
const oldSuppressionEmoji = getTelegramReactionEmoji(oldEmojis, TELEGRAM_REMOVAL_REACTION_EMOJIS);
|
|
53
48
|
const newSuppressionEmoji = getTelegramReactionEmoji(newEmojis, TELEGRAM_REMOVAL_REACTION_EMOJIS);
|
|
54
49
|
if (oldPriorityEmoji === newPriorityEmoji &&
|
|
@@ -58,6 +53,8 @@ function getTelegramQueueReactionTransition(oldReactions, newReactions) {
|
|
|
58
53
|
const transition = { kind: "reaction-transition" };
|
|
59
54
|
if (oldPriorityEmoji !== newPriorityEmoji) {
|
|
60
55
|
transition.priorityEmoji = newPriorityEmoji ?? null;
|
|
56
|
+
if (newPriorityEmoji === steerEmoji)
|
|
57
|
+
transition.promptLane = "steer";
|
|
61
58
|
}
|
|
62
59
|
if (oldSuppressionEmoji !== newSuppressionEmoji) {
|
|
63
60
|
transition.suppressionEmoji = newSuppressionEmoji ?? null;
|
|
@@ -252,6 +249,16 @@ export function inspectTelegramDeferredSourceCompletion(value) {
|
|
|
252
249
|
const source = getTelegramUpdateAdmissionBinding(value)?.inspectCompletion?.();
|
|
253
250
|
return execution.isCurrent() && source ? { ...source } : undefined;
|
|
254
251
|
}
|
|
252
|
+
/** Warm ACK notification only; subscribers must independently recheck exact completion and recipient authority. */
|
|
253
|
+
export function subscribeTelegramDeferredSourceCompletion(value, notify) {
|
|
254
|
+
const execution = getTelegramUpdateExecutionFence(value);
|
|
255
|
+
if (execution?.isCurrent() !== true)
|
|
256
|
+
return undefined;
|
|
257
|
+
return getTelegramUpdateAdmissionBinding(value)?.subscribeCompletion?.(() => {
|
|
258
|
+
if (execution.isCurrent())
|
|
259
|
+
notify();
|
|
260
|
+
});
|
|
261
|
+
}
|
|
255
262
|
/** Reuse only live bound deferred originals; raw IDs or cold/adopted input cannot mint this carrier. */
|
|
256
263
|
export function prepareTelegramLiveDeferredInput(values, isCurrent) {
|
|
257
264
|
const carriers = [...values], first = carriers[0], execution = getTelegramUpdateExecutionFence(first);
|
|
@@ -5152,6 +5159,7 @@ function createTelegramUpdateSourceAdmissionHandle(deps, capture) {
|
|
|
5152
5159
|
let outcome;
|
|
5153
5160
|
let completionAttempted = false;
|
|
5154
5161
|
let completedSource;
|
|
5162
|
+
const completionObservers = new Set();
|
|
5155
5163
|
const boundUpdate = bindTelegramUpdateExecutionFence(bindTelegramUpdateAdmissionSource(update, (next) => {
|
|
5156
5164
|
if (!execution.isCurrent())
|
|
5157
5165
|
return;
|
|
@@ -5196,11 +5204,15 @@ function createTelegramUpdateSourceAdmissionHandle(deps, capture) {
|
|
|
5196
5204
|
completion &&
|
|
5197
5205
|
execution.isCurrent() &&
|
|
5198
5206
|
completion.isCurrent() &&
|
|
5199
|
-
isDeepStrictEqual(completion.source, expectedSource))
|
|
5200
|
-
completedSource = {
|
|
5201
|
-
|
|
5202
|
-
|
|
5203
|
-
|
|
5207
|
+
isDeepStrictEqual(completion.source, expectedSource)) {
|
|
5208
|
+
completedSource = { ...completion, source: { ...expectedSource } };
|
|
5209
|
+
for (const notify of completionObservers) {
|
|
5210
|
+
try {
|
|
5211
|
+
notify();
|
|
5212
|
+
}
|
|
5213
|
+
catch { /* Observation cannot invalidate an exact removal ACK. */ }
|
|
5214
|
+
}
|
|
5215
|
+
}
|
|
5204
5216
|
})
|
|
5205
5217
|
.catch((error) => {
|
|
5206
5218
|
try {
|
|
@@ -5257,6 +5269,10 @@ function createTelegramUpdateSourceAdmissionHandle(deps, capture) {
|
|
|
5257
5269
|
signal,
|
|
5258
5270
|
})
|
|
5259
5271
|
: undefined,
|
|
5272
|
+
subscribeCompletion: (notify) => {
|
|
5273
|
+
completionObservers.add(notify);
|
|
5274
|
+
return () => { completionObservers.delete(notify); };
|
|
5275
|
+
},
|
|
5260
5276
|
inspectCompletion: () => execution.isCurrent() && completedSource?.isCurrent()
|
|
5261
5277
|
? { ...completedSource.source }
|
|
5262
5278
|
: undefined,
|
|
@@ -5533,6 +5549,7 @@ export async function coordinateTelegramQueueHandoff(input) {
|
|
|
5533
5549
|
const handoff = createTelegramQueueHandoff({
|
|
5534
5550
|
handoffToken: input.handoffToken,
|
|
5535
5551
|
item: input.item,
|
|
5552
|
+
steerSupported: input.steerSupported,
|
|
5536
5553
|
});
|
|
5537
5554
|
const receipt = handoff.payload.admissionReceipts[0];
|
|
5538
5555
|
if (!receipt || handoff.payload.admissionReceipts.length !== 1) {
|
|
@@ -5670,6 +5687,8 @@ export function createTelegramQueueHandoffRecipientRuntime(deps) {
|
|
|
5670
5687
|
/** Own queue-handoff projections over journals, admission, IPC, and live queue state. */
|
|
5671
5688
|
export function createTelegramQueueHandoffReconciliationRuntimeAssembly(deps) {
|
|
5672
5689
|
return createTelegramQueueHandoffReconciler({
|
|
5690
|
+
canHandoffSteerWithRecipient: deps.canHandoffSteerWithRecipient,
|
|
5691
|
+
hasPendingDispatch: deps.hasPendingDispatch,
|
|
5673
5692
|
ownsDirect: deps.ownsDirect,
|
|
5674
5693
|
isFollowerRegistered: deps.isFollowerRegistered,
|
|
5675
5694
|
isBusEnabled: deps.isBusEnabled,
|
|
@@ -5738,6 +5757,9 @@ export function createTelegramQueueHandoffReconciler(deps) {
|
|
|
5738
5757
|
if (followers.length === 0)
|
|
5739
5758
|
return;
|
|
5740
5759
|
for (const item of [...deps.getQueuedItems()]) {
|
|
5760
|
+
if ((deps.hasPendingDispatch?.() && deps.getQueuedItems()[0] === item) ||
|
|
5761
|
+
(item.kind === "prompt" && (item.steerAttempted || item.steerPacketTail !== undefined || item.steerPacketSourceOrders !== undefined)))
|
|
5762
|
+
continue;
|
|
5741
5763
|
const target = item.target;
|
|
5742
5764
|
const recipient = target
|
|
5743
5765
|
? followers.find((candidate) => candidate.target?.chatId === target.chatId &&
|
|
@@ -5745,6 +5767,9 @@ export function createTelegramQueueHandoffReconciler(deps) {
|
|
|
5745
5767
|
: undefined;
|
|
5746
5768
|
if (!recipient)
|
|
5747
5769
|
continue;
|
|
5770
|
+
const steerSupported = item.queueLane === "steer" && deps.canHandoffSteerWithRecipient?.(recipient) === true;
|
|
5771
|
+
if (item.queueLane === "steer" && !steerSupported)
|
|
5772
|
+
continue;
|
|
5748
5773
|
const receipt = item.admissionReceipts?.[0];
|
|
5749
5774
|
if (!receipt || item.admissionReceipts?.length !== 1)
|
|
5750
5775
|
continue;
|
|
@@ -5768,6 +5793,7 @@ export function createTelegramQueueHandoffReconciler(deps) {
|
|
|
5768
5793
|
const handoffToken = deps.createHandoffToken();
|
|
5769
5794
|
const result = await coordinateTelegramQueueHandoff({
|
|
5770
5795
|
item,
|
|
5796
|
+
steerSupported,
|
|
5771
5797
|
expectedOwner,
|
|
5772
5798
|
recipientOwner: {
|
|
5773
5799
|
instanceId: recipient.instanceId,
|
|
@@ -5778,6 +5804,9 @@ export function createTelegramQueueHandoffReconciler(deps) {
|
|
|
5778
5804
|
handoffToken,
|
|
5779
5805
|
lifecycle,
|
|
5780
5806
|
stageRemote: async ({ payload: sourcePayload }) => {
|
|
5807
|
+
if (sourcePayload.queueLane === "steer" && deps.canHandoffSteerWithRecipient?.(recipient) !== true) {
|
|
5808
|
+
throw new Error("Steer queue handoff capability changed before staging.");
|
|
5809
|
+
}
|
|
5781
5810
|
const payload = {
|
|
5782
5811
|
...sourcePayload,
|
|
5783
5812
|
admissionReceipts: [
|
|
@@ -8,7 +8,7 @@ Living index of project documentation in `/docs`.
|
|
|
8
8
|
|
|
9
9
|
## Documents
|
|
10
10
|
|
|
11
|
-
- [architecture.md](./architecture.md) — Overview of the Telegram bridge runtime, optional profile isolation, queueing model and Priority/
|
|
11
|
+
- [architecture.md](./architecture.md) — Overview of the Telegram bridge runtime, optional profile isolation, queueing model and Normal/Priority/Steer plus Keep/Skip reaction policy, native Rich Markdown delivery, UI/compat rendering, and interactive controls
|
|
12
12
|
- [public-api.md](./public-api.md) — Stable public API map: package entrypoints, commands, config, assistant markup, extension APIs, smoke examples, and compatibility boundaries
|
|
13
13
|
- [delivery.md](./delivery.md) — Target-aware companion delivery contract for operational views, logical message handles, target scopes, lifecycle fencing, and leader/follower transport
|
|
14
14
|
- [activity.md](./activity.md) — Normalized Pi lifecycle API for extension-owned reasoning, intermediate prose, tool activity, source identity, delivery contexts, and consumer policy examples
|