@llblab/pi-kit 0.19.1 → 0.20.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/CHANGELOG.md +12 -0
- package/README.md +3 -3
- package/node_modules/@llblab/pi-state-flow/AGENTS.md +4 -4
- package/node_modules/@llblab/pi-state-flow/CHANGELOG.md +5 -0
- package/node_modules/@llblab/pi-state-flow/README.md +3 -1
- package/node_modules/@llblab/pi-state-flow/dist/lib/extension.js +45 -5
- package/node_modules/@llblab/pi-state-flow/dist/lib/protocol.js +9 -9
- package/node_modules/@llblab/pi-state-flow/dist/lib/skills.d.ts +19 -2
- package/node_modules/@llblab/pi-state-flow/dist/lib/skills.js +52 -11
- package/node_modules/@llblab/pi-state-flow/dist/lib/status.d.ts +1 -1
- package/node_modules/@llblab/pi-state-flow/dist/lib/status.js +0 -2
- package/node_modules/@llblab/pi-state-flow/dist/lib/telegram.js +2 -2
- package/node_modules/@llblab/pi-state-flow/dist/lib/transition.js +36 -15
- package/node_modules/@llblab/pi-state-flow/dist/package.json +1 -1
- package/node_modules/@llblab/pi-state-flow/dist/skills/state-flow-guide/SKILL.md +1 -1
- package/node_modules/@llblab/pi-state-flow/dist/skills/state-flow-memory/SKILL.md +1 -1
- package/node_modules/@llblab/pi-state-flow/docs/architecture.md +5 -5
- package/node_modules/@llblab/pi-state-flow/docs/temporal-acceptance.md +2 -1
- package/node_modules/@llblab/pi-state-flow/docs/usage.md +2 -2
- package/node_modules/@llblab/pi-state-flow/lib/extension.ts +48 -5
- package/node_modules/@llblab/pi-state-flow/lib/protocol.ts +9 -9
- package/node_modules/@llblab/pi-state-flow/lib/skills.ts +65 -11
- package/node_modules/@llblab/pi-state-flow/lib/status.ts +1 -2
- package/node_modules/@llblab/pi-state-flow/lib/telegram.ts +2 -2
- package/node_modules/@llblab/pi-state-flow/lib/transition.ts +35 -15
- package/node_modules/@llblab/pi-state-flow/package.json +1 -1
- package/node_modules/@llblab/pi-state-flow/skills/state-flow-guide/SKILL.md +1 -1
- package/node_modules/@llblab/pi-state-flow/skills/state-flow-memory/SKILL.md +1 -1
- package/node_modules/@llblab/pi-telegram/AGENTS.md +9 -7
- package/node_modules/@llblab/pi-telegram/BACKLOG.md +13 -32
- package/node_modules/@llblab/pi-telegram/CHANGELOG.md +11 -0
- package/node_modules/@llblab/pi-telegram/README.md +4 -3
- package/node_modules/@llblab/pi-telegram/dist/lib/bindings.d.ts +2 -2
- package/node_modules/@llblab/pi-telegram/dist/lib/bindings.js +3 -5
- package/node_modules/@llblab/pi-telegram/dist/lib/bus-follower.d.ts +1 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/bus-follower.js +93 -9
- package/node_modules/@llblab/pi-telegram/dist/lib/bus-leader.d.ts +9 -3
- package/node_modules/@llblab/pi-telegram/dist/lib/bus-leader.js +231 -71
- package/node_modules/@llblab/pi-telegram/dist/lib/bus.d.ts +4 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/bus.js +34 -7
- package/node_modules/@llblab/pi-telegram/dist/lib/extension.js +63 -2
- package/node_modules/@llblab/pi-telegram/dist/lib/journal.d.ts +3 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/journal.js +13 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/lifecycle.d.ts +2 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/lifecycle.js +25 -4
- package/node_modules/@llblab/pi-telegram/dist/lib/locks.d.ts +1 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/locks.js +32 -12
- package/node_modules/@llblab/pi-telegram/dist/lib/paths.d.ts +10 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/paths.js +22 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/polling.js +2 -2
- package/node_modules/@llblab/pi-telegram/dist/lib/queue.d.ts +1 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/queue.js +1 -2
- package/node_modules/@llblab/pi-telegram/dist/lib/sync.d.ts +9 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/sync.js +41 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/telegram-api.d.ts +9 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/telegram-api.js +23 -3
- package/node_modules/@llblab/pi-telegram/dist/lib/thread-cleanup-manager.js +7 -2
- package/node_modules/@llblab/pi-telegram/dist/lib/threads.d.ts +2 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/threads.js +68 -37
- package/node_modules/@llblab/pi-telegram/dist/lib/updates.d.ts +6 -4
- package/node_modules/@llblab/pi-telegram/dist/lib/updates.js +134 -61
- package/node_modules/@llblab/pi-telegram/dist/lib/workspace-admission.d.ts +9 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/workspace-admission.js +49 -3
- package/node_modules/@llblab/pi-telegram/dist/lib/workspace-retirement.d.ts +77 -6
- package/node_modules/@llblab/pi-telegram/dist/lib/workspace-retirement.js +384 -8
- package/node_modules/@llblab/pi-telegram/dist/lib/workspace-slots.d.ts +3 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/workspace-slots.js +6 -0
- package/node_modules/@llblab/pi-telegram/dist/package.json +1 -1
- package/node_modules/@llblab/pi-telegram/docs/architecture.md +21 -14
- package/node_modules/@llblab/pi-telegram/docs/multi-instance-bus.md +31 -10
- package/node_modules/@llblab/pi-telegram/docs/updates.md +2 -0
- package/node_modules/@llblab/pi-telegram/lib/bindings.ts +5 -7
- package/node_modules/@llblab/pi-telegram/lib/bus-follower.ts +69 -12
- package/node_modules/@llblab/pi-telegram/lib/bus-leader.ts +236 -94
- package/node_modules/@llblab/pi-telegram/lib/bus.ts +39 -9
- package/node_modules/@llblab/pi-telegram/lib/extension.ts +69 -2
- package/node_modules/@llblab/pi-telegram/lib/journal.ts +14 -0
- package/node_modules/@llblab/pi-telegram/lib/lifecycle.ts +23 -4
- package/node_modules/@llblab/pi-telegram/lib/locks.ts +32 -13
- package/node_modules/@llblab/pi-telegram/lib/paths.ts +29 -1
- package/node_modules/@llblab/pi-telegram/lib/polling.ts +2 -2
- package/node_modules/@llblab/pi-telegram/lib/queue.ts +2 -3
- package/node_modules/@llblab/pi-telegram/lib/sync.ts +45 -1
- package/node_modules/@llblab/pi-telegram/lib/telegram-api.ts +30 -2
- package/node_modules/@llblab/pi-telegram/lib/thread-cleanup-manager.ts +11 -3
- package/node_modules/@llblab/pi-telegram/lib/threads.ts +73 -36
- package/node_modules/@llblab/pi-telegram/lib/updates.ts +145 -78
- package/node_modules/@llblab/pi-telegram/lib/workspace-admission.ts +67 -4
- package/node_modules/@llblab/pi-telegram/lib/workspace-retirement.ts +465 -7
- package/node_modules/@llblab/pi-telegram/lib/workspace-slots.ts +7 -0
- package/node_modules/@llblab/pi-telegram/package.json +1 -1
- package/package.json +3 -3
|
@@ -34,6 +34,7 @@ import * as ThreadReconciler from "./thread-reconciler.ts";
|
|
|
34
34
|
import {
|
|
35
35
|
planTelegramWorkspaceSlotAllocation,
|
|
36
36
|
TELEGRAM_WORKSPACE_SLOTS,
|
|
37
|
+
TelegramWorkspaceSlotUnavailableError,
|
|
37
38
|
type TelegramWorkspaceSlotOccupancy,
|
|
38
39
|
} from "./workspace-slots.ts";
|
|
39
40
|
import {
|
|
@@ -458,6 +459,11 @@ export interface TelegramTopicTargetStore {
|
|
|
458
459
|
isCurrent: () => boolean,
|
|
459
460
|
lastSyncError: string,
|
|
460
461
|
) => Promise<boolean>;
|
|
462
|
+
/** Caller proves owner detachment; this does not assert Telegram Thread absence. */
|
|
463
|
+
detachTargetOwner: (
|
|
464
|
+
expected: TelegramTopicTargetRecord,
|
|
465
|
+
isCurrent: () => boolean,
|
|
466
|
+
) => Promise<boolean>;
|
|
461
467
|
list: () => TelegramTopicTargetRecord[];
|
|
462
468
|
getFollowerRecoveryHintByTarget?: (
|
|
463
469
|
target: TelegramTarget,
|
|
@@ -2108,20 +2114,33 @@ export function createTelegramTopicTargetStore(
|
|
|
2108
2114
|
mutationRevision += 1;
|
|
2109
2115
|
};
|
|
2110
2116
|
|
|
2111
|
-
const
|
|
2117
|
+
const markWorkspaceBindingInactiveByTarget = (target: TelegramTarget, inactiveSinceMs = getNowMs()): boolean => {
|
|
2118
|
+
if (!Number.isFinite(inactiveSinceMs) || inactiveSinceMs < 0) return false;
|
|
2119
|
+
for (const [key, binding] of workspaceBindings) {
|
|
2120
|
+
if (!targetMatches(binding.target, target) || binding.inactiveSinceMs !== undefined) continue;
|
|
2121
|
+
workspaceBindings.set(key, { ...binding, inactiveSinceMs });
|
|
2122
|
+
markDirty();
|
|
2123
|
+
return true;
|
|
2124
|
+
}
|
|
2125
|
+
return false;
|
|
2126
|
+
};
|
|
2127
|
+
|
|
2128
|
+
const persistSnapshot = (transition?: {
|
|
2112
2129
|
target: TelegramTarget;
|
|
2113
2130
|
isCurrent: () => boolean;
|
|
2114
|
-
|
|
2115
|
-
|
|
2131
|
+
} & (
|
|
2132
|
+
| { kind: "invalidate"; lastSyncError: string }
|
|
2133
|
+
| { kind: "detach"; owner: TelegramTopicTargetRecord }
|
|
2134
|
+
)): Promise<boolean> => {
|
|
2116
2135
|
const persist = persistQueue.then(async () => {
|
|
2117
2136
|
const path = getPath();
|
|
2118
2137
|
if (loadedPath !== path && !dirty) resetForPath(path);
|
|
2119
2138
|
if (options.canPersist && !options.canPersist()) {
|
|
2120
|
-
if (!
|
|
2139
|
+
if (!transition) await loadFromDisk();
|
|
2121
2140
|
return false;
|
|
2122
2141
|
}
|
|
2123
2142
|
if (!dirty || !loaded) await loadFromDisk();
|
|
2124
|
-
if (
|
|
2143
|
+
if (transition && !transition.isCurrent()) return false;
|
|
2125
2144
|
const nowMs = getNowMs();
|
|
2126
2145
|
reservations = reservations.filter(
|
|
2127
2146
|
(reservation) =>
|
|
@@ -2134,6 +2153,10 @@ export function createTelegramTopicTargetStore(
|
|
|
2134
2153
|
const currentRecords = Array.from(records.values())
|
|
2135
2154
|
.filter(isPersistedThreadRecord)
|
|
2136
2155
|
.map(cloneRecord);
|
|
2156
|
+
if (transition?.kind === "detach") {
|
|
2157
|
+
const owners = currentRecords.filter((record) => targetMatches(record.target, transition.target));
|
|
2158
|
+
if (owners.length !== 1 || !isDeepStrictEqual(normalizeRecord(owners[0]), transition.owner)) return false;
|
|
2159
|
+
}
|
|
2137
2160
|
records = new Map(
|
|
2138
2161
|
currentRecords.map((record) => [
|
|
2139
2162
|
getRecordOwnerKey(record),
|
|
@@ -2176,20 +2199,32 @@ export function createTelegramTopicTargetStore(
|
|
|
2176
2199
|
return serialized;
|
|
2177
2200
|
}),
|
|
2178
2201
|
};
|
|
2179
|
-
if (
|
|
2180
|
-
const record = file.threads.find((record) => targetMatches(record.target,
|
|
2202
|
+
if (transition) {
|
|
2203
|
+
const record = file.threads.find((record) => targetMatches(record.target, transition.target));
|
|
2181
2204
|
if (!record) return false;
|
|
2205
|
+
if (transition.kind === "detach") {
|
|
2206
|
+
const bindings = file.workspaceBindings.filter((binding) => targetMatches(binding.target, record.target));
|
|
2207
|
+
if (bindings.length !== 1 || !/^[A-Z]$/.test(record.slot ?? "") ||
|
|
2208
|
+
bindings[0]!.slot !== record.slot) return false;
|
|
2209
|
+
}
|
|
2182
2210
|
file.threads = file.threads.filter((candidate) => candidate !== record);
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2211
|
+
for (const binding of file.workspaceBindings) {
|
|
2212
|
+
if (targetMatches(binding.target, transition.target) && binding.inactiveSinceMs === undefined) {
|
|
2213
|
+
binding.inactiveSinceMs = nowMs;
|
|
2214
|
+
}
|
|
2215
|
+
}
|
|
2216
|
+
if (transition.kind === "invalidate") {
|
|
2217
|
+
file.syncObservations = file.syncObservations.filter((observation) => !targetMatches(observation.target, record.target));
|
|
2218
|
+
file.syncObservations.push({
|
|
2219
|
+
target: { ...record.target },
|
|
2220
|
+
syncStatus: "deleted",
|
|
2221
|
+
observedAtMs: nowMs,
|
|
2222
|
+
...(record.instanceId ? { instanceId: record.instanceId } : {}),
|
|
2223
|
+
...(record.slot ? { slot: record.slot } : {}),
|
|
2224
|
+
lastSyncError: transition.lastSyncError,
|
|
2225
|
+
lastReconcileAction: "mark-stale",
|
|
2226
|
+
});
|
|
2227
|
+
}
|
|
2193
2228
|
}
|
|
2194
2229
|
let persistedSemanticSnapshot: Record<string, unknown> | undefined;
|
|
2195
2230
|
try {
|
|
@@ -2201,7 +2236,7 @@ export function createTelegramTopicTargetStore(
|
|
|
2201
2236
|
}
|
|
2202
2237
|
// Normalize optional fields to wire JSON; object key order is not a state change.
|
|
2203
2238
|
if (
|
|
2204
|
-
isDeepStrictEqual(
|
|
2239
|
+
!transition && isDeepStrictEqual(
|
|
2205
2240
|
persistedSemanticSnapshot,
|
|
2206
2241
|
getTelegramStateSemanticSnapshot(JSON.parse(JSON.stringify(file))),
|
|
2207
2242
|
) &&
|
|
@@ -2221,17 +2256,20 @@ export function createTelegramTopicTargetStore(
|
|
|
2221
2256
|
});
|
|
2222
2257
|
await chmod(tempPath, 0o600);
|
|
2223
2258
|
try {
|
|
2224
|
-
if (
|
|
2259
|
+
if (transition) {
|
|
2225
2260
|
let applied = false;
|
|
2226
2261
|
const commit = () => {
|
|
2227
2262
|
if (getPath() !== path || mutationRevision !== persistedRevision ||
|
|
2228
|
-
statusRevision !== persistedStatusRevision || !
|
|
2263
|
+
statusRevision !== persistedStatusRevision || !transition.isCurrent()) return;
|
|
2229
2264
|
// Fence and rename share one synchronous commit boundary. No stale
|
|
2230
|
-
//
|
|
2265
|
+
// transition enters the live projection before durable commit.
|
|
2231
2266
|
renameSync(tempPath, path);
|
|
2232
2267
|
loadedPath = path;
|
|
2233
|
-
records = new Map(Array.from(records).filter(([, record]) => !targetMatches(record.target,
|
|
2268
|
+
records = new Map(Array.from(records).filter(([, record]) => !targetMatches(record.target, transition.target)));
|
|
2234
2269
|
syncObservations = file.syncObservations;
|
|
2270
|
+
workspaceBindings = new Map(file.workspaceBindings.map((binding) => [
|
|
2271
|
+
getWorkspaceBindingMapKey(binding), cloneWorkspaceBinding(binding),
|
|
2272
|
+
]));
|
|
2235
2273
|
mutationRevision += 1;
|
|
2236
2274
|
dirty = false;
|
|
2237
2275
|
applied = true;
|
|
@@ -2283,7 +2321,12 @@ export function createTelegramTopicTargetStore(
|
|
|
2283
2321
|
await persistSnapshot();
|
|
2284
2322
|
},
|
|
2285
2323
|
invalidateTarget(target, isCurrent, lastSyncError) {
|
|
2286
|
-
return persistSnapshot({ target, isCurrent, lastSyncError });
|
|
2324
|
+
return persistSnapshot({ kind: "invalidate", target, isCurrent, lastSyncError });
|
|
2325
|
+
},
|
|
2326
|
+
detachTargetOwner(expected, isCurrent) {
|
|
2327
|
+
const owner = normalizeRecord(expected);
|
|
2328
|
+
if (!owner) return Promise.resolve(false);
|
|
2329
|
+
return persistSnapshot({ kind: "detach", owner, target: owner.target, isCurrent });
|
|
2287
2330
|
},
|
|
2288
2331
|
list() {
|
|
2289
2332
|
return Array.from(records.values()).map(cloneRecord);
|
|
@@ -2838,16 +2881,7 @@ export function createTelegramTopicTargetStore(
|
|
|
2838
2881
|
markDirty();
|
|
2839
2882
|
return true;
|
|
2840
2883
|
},
|
|
2841
|
-
markWorkspaceBindingInactiveByTarget
|
|
2842
|
-
if (!Number.isFinite(inactiveSinceMs) || inactiveSinceMs < 0) return false;
|
|
2843
|
-
for (const [key, binding] of workspaceBindings) {
|
|
2844
|
-
if (!targetMatches(binding.target, target) || binding.inactiveSinceMs !== undefined) continue;
|
|
2845
|
-
workspaceBindings.set(key, { ...binding, inactiveSinceMs });
|
|
2846
|
-
markDirty();
|
|
2847
|
-
return true;
|
|
2848
|
-
}
|
|
2849
|
-
return false;
|
|
2850
|
-
},
|
|
2884
|
+
markWorkspaceBindingInactiveByTarget,
|
|
2851
2885
|
markWorkspaceBindingActiveByTarget(target) {
|
|
2852
2886
|
if (hasWorkspaceRetirementConflict({ target })) return false;
|
|
2853
2887
|
for (const [key, binding] of workspaceBindings) {
|
|
@@ -3274,7 +3308,10 @@ export function createTelegramTopicTargetStore(
|
|
|
3274
3308
|
entry.target && targetMatches(entry.target, target),
|
|
3275
3309
|
) : undefined;
|
|
3276
3310
|
const source = record ?? pending;
|
|
3277
|
-
|
|
3311
|
+
// Confirmed absence ends this binding's active target lifetime, even after
|
|
3312
|
+
// an earlier stale observation already removed its live record.
|
|
3313
|
+
const inactive = syncStatus === "deleted" && markWorkspaceBindingInactiveByTarget(target);
|
|
3314
|
+
if (!source?.target) return inactive;
|
|
3278
3315
|
syncObservations = syncObservations.filter((entry) => !targetMatches(entry.target, target));
|
|
3279
3316
|
syncObservations.push({
|
|
3280
3317
|
target: { ...source.target }, syncStatus, observedAtMs: getNowMs(),
|
|
@@ -4886,7 +4923,7 @@ export function createTelegramTopicTargetProvisioner(
|
|
|
4886
4923
|
if (existing && isCurrentThreadRecord(existing)) {
|
|
4887
4924
|
const slot = existing.slot ?? deps.store.allocateSlot(request.profileKey);
|
|
4888
4925
|
if (!slot) {
|
|
4889
|
-
throw new
|
|
4926
|
+
throw new TelegramWorkspaceSlotUnavailableError();
|
|
4890
4927
|
}
|
|
4891
4928
|
const occupied = listOccupiedTelegramThreadIdentities({
|
|
4892
4929
|
records: deps.store.list(),
|
|
@@ -5015,7 +5052,7 @@ export function createTelegramTopicTargetProvisioner(
|
|
|
5015
5052
|
request.workspaceBindingKey,
|
|
5016
5053
|
);
|
|
5017
5054
|
if (!slot) {
|
|
5018
|
-
throw new
|
|
5055
|
+
throw new TelegramWorkspaceSlotUnavailableError();
|
|
5019
5056
|
}
|
|
5020
5057
|
const uniqueCandidate =
|
|
5021
5058
|
candidateThreadName &&
|
|
@@ -698,10 +698,9 @@ export function buildTelegramUpdateFlowAction<
|
|
|
698
698
|
NonNullable<TUpdate["message"] | TUpdate["edited_message"]>,
|
|
699
699
|
NonNullable<TUpdate["guest_message"]>
|
|
700
700
|
> {
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
}
|
|
701
|
+
// Business chats are independent from bot chats even when chat/message IDs coincide.
|
|
702
|
+
// Raw handlers may own that namespace; the default DM runtime has no deletion authority.
|
|
703
|
+
if (update.deleted_business_messages !== undefined) return { kind: "ignore" };
|
|
705
704
|
if (update.message_reaction) {
|
|
706
705
|
return { kind: "reaction", reactionUpdate: update.message_reaction };
|
|
707
706
|
}
|
|
@@ -1739,6 +1738,11 @@ export interface TelegramUpdateWorkerJournalSnapshot {
|
|
|
1739
1738
|
|
|
1740
1739
|
export interface TelegramUpdateWorkerJournalPort {
|
|
1741
1740
|
read: () => TelegramUpdateWorkerJournalSnapshot;
|
|
1741
|
+
/** Optional strict observation; it must not recover, repair or grant new receipt authority. */
|
|
1742
|
+
isQueueReceiptCurrent?: (
|
|
1743
|
+
receipt: TelegramQueueAdmissionReceiptLike,
|
|
1744
|
+
owner: TelegramUpdateJournalQueueOwner,
|
|
1745
|
+
) => boolean;
|
|
1742
1746
|
markQueued: (receipt: {
|
|
1743
1747
|
queueKind: "prompt" | "control";
|
|
1744
1748
|
receiptId: string;
|
|
@@ -1858,7 +1862,7 @@ export interface TelegramUpdateWorkerRuntime<TContext> {
|
|
|
1858
1862
|
receipts: readonly TelegramQueueAdmissionReceiptLike[];
|
|
1859
1863
|
ctx: TContext;
|
|
1860
1864
|
reason: TelegramQueueReceiptCompletionReason;
|
|
1861
|
-
}) =>
|
|
1865
|
+
}) => boolean;
|
|
1862
1866
|
stop: () => Promise<void>;
|
|
1863
1867
|
waitForDrain: () => Promise<void>;
|
|
1864
1868
|
getState: () => TelegramUpdateWorkerStateSnapshot;
|
|
@@ -2268,6 +2272,23 @@ export function createTelegramUpdateWorkerRuntime<TContext>(
|
|
|
2268
2272
|
return true;
|
|
2269
2273
|
};
|
|
2270
2274
|
|
|
2275
|
+
const getCurrentQueueReceipt = (receipt: TelegramQueueAdmissionReceiptLike) => {
|
|
2276
|
+
const committed = committedQueueReceipts.get(receipt.receiptId);
|
|
2277
|
+
if (!committed || !areTelegramQueueAdmissionReceiptsEqual(
|
|
2278
|
+
committed.receipt, normalizeQueueReceipt(receipt),
|
|
2279
|
+
)) return undefined;
|
|
2280
|
+
try {
|
|
2281
|
+
if (deps.journal.isQueueReceiptCurrent &&
|
|
2282
|
+
deps.journal.isQueueReceiptCurrent(committed.receipt, committed.queueOwner) !== true) {
|
|
2283
|
+
return undefined;
|
|
2284
|
+
}
|
|
2285
|
+
} catch (error) {
|
|
2286
|
+
recordRuntimeEvent(error, { phase: "queue-receipt-observation" });
|
|
2287
|
+
return undefined;
|
|
2288
|
+
}
|
|
2289
|
+
return committed;
|
|
2290
|
+
};
|
|
2291
|
+
|
|
2271
2292
|
const clearRetryTimer = (): void => {
|
|
2272
2293
|
if (retryTimer !== undefined) cancelRetry(retryTimer);
|
|
2273
2294
|
retryTimer = undefined;
|
|
@@ -3023,11 +3044,9 @@ export function createTelegramUpdateWorkerRuntime<TContext>(
|
|
|
3023
3044
|
!expectedOwner.controller.signal.aborted
|
|
3024
3045
|
) {
|
|
3025
3046
|
pendingSignal = false;
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
|
|
3029
|
-
return;
|
|
3030
|
-
}
|
|
3047
|
+
// Keep newer wakes in this run so waitForDrain also covers them. A failure
|
|
3048
|
+
// observed after signal() still keeps its latch; this loop never clears it.
|
|
3049
|
+
await drain(expectedOwner);
|
|
3031
3050
|
}
|
|
3032
3051
|
};
|
|
3033
3052
|
const operation = run();
|
|
@@ -3265,25 +3284,10 @@ export function createTelegramUpdateWorkerRuntime<TContext>(
|
|
|
3265
3284
|
}
|
|
3266
3285
|
transition("queued", input.updateId);
|
|
3267
3286
|
},
|
|
3268
|
-
isQueueReceiptCommitted(receipt)
|
|
3269
|
-
const committed = committedQueueReceipts.get(receipt.receiptId);
|
|
3270
|
-
return (
|
|
3271
|
-
committed !== undefined &&
|
|
3272
|
-
areTelegramQueueAdmissionReceiptsEqual(
|
|
3273
|
-
committed.receipt,
|
|
3274
|
-
normalizeQueueReceipt(receipt),
|
|
3275
|
-
)
|
|
3276
|
-
);
|
|
3277
|
-
},
|
|
3287
|
+
isQueueReceiptCommitted: receipt => getCurrentQueueReceipt(receipt) !== undefined,
|
|
3278
3288
|
getQueueReceiptOwner(receipt) {
|
|
3279
|
-
const committed =
|
|
3280
|
-
return committed
|
|
3281
|
-
areTelegramQueueAdmissionReceiptsEqual(
|
|
3282
|
-
committed.receipt,
|
|
3283
|
-
normalizeQueueReceipt(receipt),
|
|
3284
|
-
)
|
|
3285
|
-
? { ...committed.queueOwner }
|
|
3286
|
-
: undefined;
|
|
3289
|
+
const committed = getCurrentQueueReceipt(receipt);
|
|
3290
|
+
return committed ? { ...committed.queueOwner } : undefined;
|
|
3287
3291
|
},
|
|
3288
3292
|
completeQueueReceipts(input) {
|
|
3289
3293
|
const expectedOwner = owner;
|
|
@@ -3292,9 +3296,9 @@ export function createTelegramUpdateWorkerRuntime<TContext>(
|
|
|
3292
3296
|
expectedOwner.controller.signal.aborted ||
|
|
3293
3297
|
!(deps.isContextCurrent?.(input.ctx) ?? expectedOwner.ctx === input.ctx)
|
|
3294
3298
|
) {
|
|
3295
|
-
return;
|
|
3299
|
+
return false;
|
|
3296
3300
|
}
|
|
3297
|
-
if (input.receipts.length === 0) return;
|
|
3301
|
+
if (input.receipts.length === 0) return true;
|
|
3298
3302
|
const normalizedReceipts = input.receipts.map(normalizeQueueReceipt);
|
|
3299
3303
|
const receiptIds = new Set<string>();
|
|
3300
3304
|
const sourceUpdateIds = new Set<number>();
|
|
@@ -3306,7 +3310,7 @@ export function createTelegramUpdateWorkerRuntime<TContext>(
|
|
|
3306
3310
|
}> = [];
|
|
3307
3311
|
for (const receipt of normalizedReceipts) {
|
|
3308
3312
|
const committed = committedQueueReceipts.get(receipt.receiptId);
|
|
3309
|
-
if (!committed)
|
|
3313
|
+
if (!committed) return false;
|
|
3310
3314
|
if (
|
|
3311
3315
|
receiptIds.has(receipt.receiptId) ||
|
|
3312
3316
|
!areTelegramQueueAdmissionReceiptsEqual(
|
|
@@ -3328,7 +3332,7 @@ export function createTelegramUpdateWorkerRuntime<TContext>(
|
|
|
3328
3332
|
`Telegram ${input.reason} requested invalid queue receipt ${receipt.receiptId}.`,
|
|
3329
3333
|
),
|
|
3330
3334
|
);
|
|
3331
|
-
return;
|
|
3335
|
+
return false;
|
|
3332
3336
|
}
|
|
3333
3337
|
receiptIds.add(receipt.receiptId);
|
|
3334
3338
|
queuedCompletions.push({
|
|
@@ -3339,7 +3343,6 @@ export function createTelegramUpdateWorkerRuntime<TContext>(
|
|
|
3339
3343
|
sourceUpdateIds.add(updateId);
|
|
3340
3344
|
}
|
|
3341
3345
|
}
|
|
3342
|
-
if (receiptIds.size === 0) return;
|
|
3343
3346
|
let removedUpdateIds: readonly number[];
|
|
3344
3347
|
try {
|
|
3345
3348
|
removedUpdateIds = deps.journal.completeQueued(
|
|
@@ -3351,7 +3354,7 @@ export function createTelegramUpdateWorkerRuntime<TContext>(
|
|
|
3351
3354
|
"queue-receipt-completion",
|
|
3352
3355
|
error,
|
|
3353
3356
|
);
|
|
3354
|
-
return;
|
|
3357
|
+
return false;
|
|
3355
3358
|
}
|
|
3356
3359
|
const removed = new Set(removedUpdateIds);
|
|
3357
3360
|
if (
|
|
@@ -3365,8 +3368,11 @@ export function createTelegramUpdateWorkerRuntime<TContext>(
|
|
|
3365
3368
|
`Telegram ${input.reason} did not complete every receipt source.`,
|
|
3366
3369
|
),
|
|
3367
3370
|
);
|
|
3368
|
-
return;
|
|
3371
|
+
return false;
|
|
3369
3372
|
}
|
|
3373
|
+
input.receipts.forEach((receipt, index) => {
|
|
3374
|
+
acknowledgedQueueReceiptCompletions.set(receipt, normalizedReceipts[index]!);
|
|
3375
|
+
});
|
|
3370
3376
|
for (const updateId of sourceUpdateIds) claims.delete(updateId);
|
|
3371
3377
|
for (const receiptId of receiptIds) {
|
|
3372
3378
|
committedQueueReceipts.delete(receiptId);
|
|
@@ -3385,6 +3391,7 @@ export function createTelegramUpdateWorkerRuntime<TContext>(
|
|
|
3385
3391
|
notifyStateChange();
|
|
3386
3392
|
pendingSignal = true;
|
|
3387
3393
|
launchDrain();
|
|
3394
|
+
return true;
|
|
3388
3395
|
},
|
|
3389
3396
|
async stop() {
|
|
3390
3397
|
const expectedOwner = owner;
|
|
@@ -3676,9 +3683,17 @@ export function createTelegramInputCustodyWorkerJournalPort(
|
|
|
3676
3683
|
};
|
|
3677
3684
|
return {
|
|
3678
3685
|
read: () => store.read() as unknown as TelegramUpdateWorkerJournalSnapshot,
|
|
3686
|
+
isQueueReceiptCurrent(receipt, owner) {
|
|
3687
|
+
const entries = store.read().entries.filter(entry => entry.queueReceiptId === receipt.receiptId);
|
|
3688
|
+
return receipt.sourceUpdateIds.length > 0 && entries.length === receipt.sourceUpdateIds.length &&
|
|
3689
|
+
entries.every((entry, index) => entry.updateId === receipt.sourceUpdateIds[index] &&
|
|
3690
|
+
entry.state === "queued" && entry.queueKind === receipt.queueKind && !entry.queueHandoff &&
|
|
3691
|
+
entry.queueOwner !== undefined && areTelegramUpdateJournalQueueOwnersEqual(entry.queueOwner, owner));
|
|
3692
|
+
},
|
|
3679
3693
|
markQueued: legacyMutation,
|
|
3680
3694
|
markExecutionFailure: legacyMutation,
|
|
3681
|
-
|
|
3695
|
+
// The common drain batches excluded IDs here; the store rejects all non-vetoed input atomically.
|
|
3696
|
+
removeCompleted: updateIds => store.removeExcluded(updateIds),
|
|
3682
3697
|
completeQueued: receipts => store.completeQueued(receipts),
|
|
3683
3698
|
inputCustody: {
|
|
3684
3699
|
acquireInput: store.acquireInput,
|
|
@@ -4511,13 +4526,20 @@ export function createTelegramInputCustodyLifecycleBindingResolver(deps: {
|
|
|
4511
4526
|
journal: TelegramInputJournalStore } | undefined;
|
|
4512
4527
|
getRecipientBindingKey(): string | undefined;
|
|
4513
4528
|
}): () => TelegramUpdateAdmissionLifecycleJournalBinding | undefined {
|
|
4529
|
+
// Stable per store, not per descriptor: a renewed source has new captured worker ports.
|
|
4530
|
+
const ports = new WeakMap<TelegramInputJournalStore,
|
|
4531
|
+
ReturnType<typeof createTelegramInputCustodyWorkerJournalPort>>();
|
|
4514
4532
|
return () => {
|
|
4515
4533
|
if (!deps.isEnabled()) return undefined;
|
|
4516
4534
|
const source = deps.resolveInputJournal();
|
|
4517
4535
|
const recipientBindingKey = deps.getRecipientBindingKey()?.trim();
|
|
4518
4536
|
if (!source || !source.runtimeKey || !source.recoveryKey || !recipientBindingKey)
|
|
4519
4537
|
return undefined;
|
|
4520
|
-
|
|
4538
|
+
let port = ports.get(source.journal);
|
|
4539
|
+
if (!port) {
|
|
4540
|
+
port = createTelegramInputCustodyWorkerJournalPort(source.journal);
|
|
4541
|
+
ports.set(source.journal, port);
|
|
4542
|
+
}
|
|
4521
4543
|
return { runtimeKey: JSON.stringify({ source: source.runtimeKey, recipientBindingKey }),
|
|
4522
4544
|
recoveryKey: source.recoveryKey,
|
|
4523
4545
|
recipientBindingKey, journal: { ...port, appendBatch: source.journal.appendBatch,
|
|
@@ -4681,6 +4703,7 @@ export function createTelegramUpdateAdmissionHandle<
|
|
|
4681
4703
|
try {
|
|
4682
4704
|
execution.assertCurrent();
|
|
4683
4705
|
await deps.defaultHandle(boundUpdate, ctx, execution);
|
|
4706
|
+
execution.assertCurrent();
|
|
4684
4707
|
} finally {
|
|
4685
4708
|
immediate = false;
|
|
4686
4709
|
}
|
|
@@ -4720,6 +4743,16 @@ export interface TelegramQueueAdmissionItemLike {
|
|
|
4720
4743
|
admissionReceipts?: readonly TelegramQueueAdmissionReceiptLike[];
|
|
4721
4744
|
}
|
|
4722
4745
|
|
|
4746
|
+
// A dispatched prompt can remain in memory until agent_start. Remember only acknowledged
|
|
4747
|
+
// removal of these exact receipt objects for later local discard, never for dispatch/replay.
|
|
4748
|
+
const acknowledgedQueueReceiptCompletions = new WeakMap<
|
|
4749
|
+
TelegramQueueAdmissionReceiptLike, TelegramQueueAdmissionReceiptLike
|
|
4750
|
+
>();
|
|
4751
|
+
const isQueueReceiptCompletionAcknowledged = (receipt: TelegramQueueAdmissionReceiptLike): boolean => {
|
|
4752
|
+
const acknowledged = acknowledgedQueueReceiptCompletions.get(receipt);
|
|
4753
|
+
return acknowledged !== undefined && areTelegramQueueAdmissionReceiptsEqual(acknowledged, receipt);
|
|
4754
|
+
};
|
|
4755
|
+
|
|
4723
4756
|
export interface TelegramQueueAdmissionSettlementRuntime<TContext> {
|
|
4724
4757
|
isItemReady: (item: TelegramQueueAdmissionItemLike) => boolean;
|
|
4725
4758
|
getQueueReceiptOwner: (
|
|
@@ -4728,26 +4761,51 @@ export interface TelegramQueueAdmissionSettlementRuntime<TContext> {
|
|
|
4728
4761
|
onPromptHandedOff: (
|
|
4729
4762
|
item: TelegramQueueAdmissionItemLike,
|
|
4730
4763
|
ctx: TContext,
|
|
4731
|
-
) =>
|
|
4764
|
+
) => boolean;
|
|
4732
4765
|
onControlSettled: (
|
|
4733
4766
|
item: TelegramQueueAdmissionItemLike,
|
|
4734
4767
|
ctx: TContext,
|
|
4735
|
-
) =>
|
|
4768
|
+
) => boolean;
|
|
4736
4769
|
onItemsDiscarded: (
|
|
4737
4770
|
items: readonly TelegramQueueAdmissionItemLike[],
|
|
4738
4771
|
ctx: TContext,
|
|
4739
|
-
) =>
|
|
4772
|
+
) => boolean;
|
|
4740
4773
|
}
|
|
4741
4774
|
|
|
4742
4775
|
export function createTelegramQueueAdmissionSettlementMuxRuntime<TContext>(
|
|
4743
4776
|
runtimes: readonly TelegramQueueAdmissionSettlementRuntime<TContext>[],
|
|
4744
4777
|
): TelegramQueueAdmissionSettlementRuntime<TContext> {
|
|
4745
|
-
|
|
4746
|
-
|
|
4747
|
-
|
|
4748
|
-
|
|
4749
|
-
|
|
4750
|
-
|
|
4778
|
+
// Plan the whole request before mutation; unready is not an acknowledgement of completion.
|
|
4779
|
+
const complete = (
|
|
4780
|
+
items: readonly TelegramQueueAdmissionItemLike[],
|
|
4781
|
+
ctx: TContext,
|
|
4782
|
+
kind: "prompt" | "control" | "discard",
|
|
4783
|
+
): boolean => {
|
|
4784
|
+
const plan = new Map<TelegramQueueAdmissionSettlementRuntime<TContext>, TelegramQueueAdmissionReceiptLike[]>();
|
|
4785
|
+
for (const receipt of items.flatMap(item => item.admissionReceipts ?? [])) {
|
|
4786
|
+
if (kind === "discard" && isQueueReceiptCompletionAcknowledged(receipt)) continue;
|
|
4787
|
+
const candidates = runtimes.filter(runtime => runtime.isItemReady({ admissionReceipts: [receipt] }));
|
|
4788
|
+
const selected = candidates[0];
|
|
4789
|
+
if (!selected) return false;
|
|
4790
|
+
if (candidates.length > 1) {
|
|
4791
|
+
const owner = selected.getQueueReceiptOwner(receipt);
|
|
4792
|
+
if (!owner || candidates.some(runtime => {
|
|
4793
|
+
const candidate = runtime.getQueueReceiptOwner(receipt);
|
|
4794
|
+
return !candidate || !areTelegramUpdateJournalQueueOwnersEqual(owner, candidate);
|
|
4795
|
+
})) return false;
|
|
4796
|
+
}
|
|
4797
|
+
const receipts = plan.get(selected) ?? [];
|
|
4798
|
+
receipts.push(receipt);
|
|
4799
|
+
plan.set(selected, receipts);
|
|
4800
|
+
}
|
|
4801
|
+
for (const [runtime, receipts] of plan) {
|
|
4802
|
+
const item = { admissionReceipts: receipts };
|
|
4803
|
+
const completed = kind === "discard" ? runtime.onItemsDiscarded([item], ctx)
|
|
4804
|
+
: kind === "prompt" ? runtime.onPromptHandedOff(item, ctx)
|
|
4805
|
+
: runtime.onControlSettled(item, ctx);
|
|
4806
|
+
if (completed !== true) return false;
|
|
4807
|
+
}
|
|
4808
|
+
return true;
|
|
4751
4809
|
};
|
|
4752
4810
|
return {
|
|
4753
4811
|
isItemReady: (item) =>
|
|
@@ -4773,12 +4831,9 @@ export function createTelegramQueueAdmissionSettlementMuxRuntime<TContext>(
|
|
|
4773
4831
|
}
|
|
4774
4832
|
return owner ? { ...owner } : undefined;
|
|
4775
4833
|
},
|
|
4776
|
-
onPromptHandedOff: (item, ctx) =>
|
|
4777
|
-
|
|
4778
|
-
|
|
4779
|
-
settle((runtime) => runtime.onControlSettled(item, ctx)),
|
|
4780
|
-
onItemsDiscarded: (items, ctx) =>
|
|
4781
|
-
settle((runtime) => runtime.onItemsDiscarded(items, ctx)),
|
|
4834
|
+
onPromptHandedOff: (item, ctx) => complete([item], ctx, "prompt"),
|
|
4835
|
+
onControlSettled: (item, ctx) => complete([item], ctx, "control"),
|
|
4836
|
+
onItemsDiscarded: (items, ctx) => complete(items, ctx, "discard"),
|
|
4782
4837
|
};
|
|
4783
4838
|
}
|
|
4784
4839
|
|
|
@@ -4789,12 +4844,14 @@ export function createTelegramQueueAdmissionSettlementRuntime<TContext>(
|
|
|
4789
4844
|
items: readonly TelegramQueueAdmissionItemLike[],
|
|
4790
4845
|
ctx: TContext,
|
|
4791
4846
|
reason: TelegramQueueReceiptCompletionReason,
|
|
4792
|
-
):
|
|
4847
|
+
): boolean => {
|
|
4793
4848
|
const receipts: TelegramQueueAdmissionReceiptLike[] = [];
|
|
4794
4849
|
for (const item of items) {
|
|
4795
|
-
|
|
4850
|
+
for (const receipt of item.admissionReceipts ?? []) {
|
|
4851
|
+
if (reason !== "discard" || !isQueueReceiptCompletionAcknowledged(receipt)) receipts.push(receipt);
|
|
4852
|
+
}
|
|
4796
4853
|
}
|
|
4797
|
-
worker.completeQueueReceipts({ receipts, ctx, reason });
|
|
4854
|
+
return receipts.length === 0 || worker.completeQueueReceipts({ receipts, ctx, reason });
|
|
4798
4855
|
};
|
|
4799
4856
|
return {
|
|
4800
4857
|
isItemReady: (item) =>
|
|
@@ -5322,10 +5379,6 @@ export function createTelegramQueueHandoffReconciler<TContext>(
|
|
|
5322
5379
|
const recipientJournalBindingKey =
|
|
5323
5380
|
deps.createRecipientJournalBindingKey(recipient);
|
|
5324
5381
|
if (!recipientJournalBindingKey) continue;
|
|
5325
|
-
const recipientItem = structuredClone(item);
|
|
5326
|
-
recipientItem.admissionReceipts = [
|
|
5327
|
-
{ ...receipt, journalBindingKey: recipientJournalBindingKey },
|
|
5328
|
-
];
|
|
5329
5382
|
const expectedOwner = deps.getReceiptOwner(receipt);
|
|
5330
5383
|
const lifecycle = deps.getLifecycleForReceipt(receipt);
|
|
5331
5384
|
if (
|
|
@@ -5350,11 +5403,11 @@ export function createTelegramQueueHandoffReconciler<TContext>(
|
|
|
5350
5403
|
},
|
|
5351
5404
|
handoffToken,
|
|
5352
5405
|
lifecycle,
|
|
5353
|
-
stageRemote: async () => {
|
|
5354
|
-
const payload =
|
|
5355
|
-
|
|
5356
|
-
|
|
5357
|
-
}
|
|
5406
|
+
stageRemote: async ({ payload: sourcePayload }) => {
|
|
5407
|
+
const payload = {
|
|
5408
|
+
...sourcePayload,
|
|
5409
|
+
admissionReceipts: [{ ...sourcePayload.admissionReceipts[0]!, journalBindingKey: recipientJournalBindingKey }],
|
|
5410
|
+
};
|
|
5358
5411
|
if (followerRegistered) {
|
|
5359
5412
|
return deps.stageThroughFollower({
|
|
5360
5413
|
recipient,
|
|
@@ -5637,11 +5690,25 @@ export function createTelegramUpdateAdmissionLifecycleRuntime<TContext>(
|
|
|
5637
5690
|
let activeRuntimeKey: string | undefined;
|
|
5638
5691
|
let journal: TelegramUpdateAdmissionLifecycleJournalBinding["journal"] | undefined;
|
|
5639
5692
|
let worker: TelegramUpdateWorkerRuntime<TContext> | undefined;
|
|
5693
|
+
let workerInputCustody: TelegramCustodyExecutionJournal | undefined;
|
|
5640
5694
|
let settlement: TelegramQueueAdmissionSettlementRuntime<TContext> | undefined;
|
|
5641
5695
|
let journalBindingKey: string | undefined;
|
|
5642
5696
|
let operation: Promise<void> = Promise.resolve();
|
|
5643
5697
|
let foreignQueueOwnerLiveness: TelegramProcessLiveness | undefined;
|
|
5644
5698
|
let releaseSourceReference: (() => void) | undefined;
|
|
5699
|
+
let sourceReferenceBinding: TelegramUpdateAdmissionLifecycleJournalBinding | undefined;
|
|
5700
|
+
|
|
5701
|
+
const withRetainedSourceReference = <T>(
|
|
5702
|
+
operation: (source: TelegramUpdateAdmissionLifecycleJournalBinding["journal"]) => T,
|
|
5703
|
+
): T => {
|
|
5704
|
+
if (!journal || !worker) throw new Error("Telegram update admission worker is not active.");
|
|
5705
|
+
// Synchronous retained-source access needs a reference, not renewed execution authority.
|
|
5706
|
+
const release = !releaseSourceReference && sourceReferenceBinding
|
|
5707
|
+
? deps.acquireSourceReference?.(sourceReferenceBinding) : undefined;
|
|
5708
|
+
try { return operation(journal); } finally { release?.(); }
|
|
5709
|
+
};
|
|
5710
|
+
const readObservedJournal = (): TelegramUpdateWorkerJournalSnapshot =>
|
|
5711
|
+
withRetainedSourceReference(source => source.read());
|
|
5645
5712
|
|
|
5646
5713
|
const stopCurrent = async (forget: boolean): Promise<void> => {
|
|
5647
5714
|
await worker?.stop();
|
|
@@ -5652,8 +5719,10 @@ export function createTelegramUpdateAdmissionLifecycleRuntime<TContext>(
|
|
|
5652
5719
|
{ phase: "source-reference-release" });
|
|
5653
5720
|
}
|
|
5654
5721
|
if (!forget) return;
|
|
5722
|
+
sourceReferenceBinding = undefined;
|
|
5655
5723
|
journal = undefined;
|
|
5656
5724
|
worker = undefined;
|
|
5725
|
+
workerInputCustody = undefined;
|
|
5657
5726
|
settlement = undefined;
|
|
5658
5727
|
journalBindingKey = undefined;
|
|
5659
5728
|
activeRuntimeKey = undefined;
|
|
@@ -5665,7 +5734,8 @@ export function createTelegramUpdateAdmissionLifecycleRuntime<TContext>(
|
|
|
5665
5734
|
await stopCurrent(true);
|
|
5666
5735
|
return;
|
|
5667
5736
|
}
|
|
5668
|
-
if (replace || !worker || activeRuntimeKey !== binding.runtimeKey
|
|
5737
|
+
if (replace || !worker || activeRuntimeKey !== binding.runtimeKey ||
|
|
5738
|
+
workerInputCustody !== binding.journal.inputCustody) {
|
|
5669
5739
|
const { runtimeKey, recoveryKey } = binding;
|
|
5670
5740
|
await stopCurrent(true);
|
|
5671
5741
|
binding = deps.resolveBinding();
|
|
@@ -5674,10 +5744,13 @@ export function createTelegramUpdateAdmissionLifecycleRuntime<TContext>(
|
|
|
5674
5744
|
throw new Error("Telegram update source binding changed during startup.");
|
|
5675
5745
|
}
|
|
5676
5746
|
const release = deps.acquireSourceReference?.(binding);
|
|
5747
|
+
const inputCustody = binding.journal.inputCustody;
|
|
5677
5748
|
try {
|
|
5678
5749
|
journal = binding.journal;
|
|
5679
5750
|
worker = deps.createWorker(binding.journal, binding);
|
|
5751
|
+
workerInputCustody = inputCustody;
|
|
5680
5752
|
settlement = createTelegramQueueAdmissionSettlementRuntime(worker);
|
|
5753
|
+
sourceReferenceBinding = { ...binding, journal };
|
|
5681
5754
|
releaseSourceReference = release;
|
|
5682
5755
|
} catch (error) {
|
|
5683
5756
|
release?.();
|
|
@@ -5843,7 +5916,7 @@ export function createTelegramUpdateAdmissionLifecycleRuntime<TContext>(
|
|
|
5843
5916
|
"Telegram update journal queue handoff cancellation is not available.",
|
|
5844
5917
|
);
|
|
5845
5918
|
}
|
|
5846
|
-
const result =
|
|
5919
|
+
const result = withRetainedSourceReference(source => source.cancelQueuedHandoff!(input));
|
|
5847
5920
|
worker.signal();
|
|
5848
5921
|
return result;
|
|
5849
5922
|
},
|
|
@@ -5897,21 +5970,15 @@ export function createTelegramUpdateAdmissionLifecycleRuntime<TContext>(
|
|
|
5897
5970
|
ownsJournalBinding: (candidate) =>
|
|
5898
5971
|
journalBindingKey !== undefined && journalBindingKey === candidate,
|
|
5899
5972
|
getForeignQueueOwnerLiveness: () => foreignQueueOwnerLiveness,
|
|
5900
|
-
getJournalEntryCount()
|
|
5901
|
-
if (!journal || !worker) {
|
|
5902
|
-
throw new Error("Telegram update admission worker is not active.");
|
|
5903
|
-
}
|
|
5904
|
-
return journal.read().entries.length;
|
|
5905
|
-
},
|
|
5973
|
+
getJournalEntryCount: () => readObservedJournal().entries.length,
|
|
5906
5974
|
hasPendingQueueMutationForItem(item) {
|
|
5907
5975
|
return Boolean(
|
|
5908
5976
|
journal &&
|
|
5909
5977
|
worker &&
|
|
5910
|
-
|
|
5911
|
-
.read()
|
|
5912
|
-
.entries.some(
|
|
5978
|
+
readObservedJournal().entries.some(
|
|
5913
5979
|
(entry) =>
|
|
5914
5980
|
entry.state !== "queued" &&
|
|
5981
|
+
entry.preApprovalExcluded !== true &&
|
|
5915
5982
|
isTelegramReactionDependencyForQueueItem(
|
|
5916
5983
|
entry.update,
|
|
5917
5984
|
item,
|
|
@@ -5934,11 +6001,11 @@ export function createTelegramUpdateAdmissionLifecycleRuntime<TContext>(
|
|
|
5934
6001
|
settlement?.isItemReady(item) ??
|
|
5935
6002
|
(item.admissionReceipts?.length ?? 0) === 0,
|
|
5936
6003
|
onPromptHandedOff: (item, ctx) =>
|
|
5937
|
-
settlement?.onPromptHandedOff(item, ctx),
|
|
6004
|
+
settlement?.onPromptHandedOff(item, ctx) ?? false,
|
|
5938
6005
|
onControlSettled: (item, ctx) =>
|
|
5939
|
-
settlement?.onControlSettled(item, ctx),
|
|
6006
|
+
settlement?.onControlSettled(item, ctx) ?? false,
|
|
5940
6007
|
onItemsDiscarded: (items, ctx) =>
|
|
5941
|
-
settlement?.onItemsDiscarded(items, ctx),
|
|
6008
|
+
settlement?.onItemsDiscarded(items, ctx) ?? false,
|
|
5942
6009
|
};
|
|
5943
6010
|
}
|
|
5944
6011
|
|