@llblab/pi-kit 0.35.0 → 0.35.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 +1 -0
- package/CHANGELOG.md +5 -0
- package/README.md +1 -1
- package/node_modules/@llblab/pi-telegram/AGENTS.md +1 -1
- package/node_modules/@llblab/pi-telegram/BACKLOG.md +6 -1
- package/node_modules/@llblab/pi-telegram/CHANGELOG.md +6 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/bindings.d.ts +3 -2
- package/node_modules/@llblab/pi-telegram/dist/lib/bindings.js +11 -3
- package/node_modules/@llblab/pi-telegram/dist/lib/bus-api.js +2 -2
- package/node_modules/@llblab/pi-telegram/dist/lib/commands.d.ts +2 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/commands.js +21 -8
- package/node_modules/@llblab/pi-telegram/dist/lib/extension.js +40 -45
- package/node_modules/@llblab/pi-telegram/dist/lib/journal.d.ts +7 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/journal.js +80 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/outbound-attachments.d.ts +12 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/outbound-attachments.js +33 -3
- package/node_modules/@llblab/pi-telegram/dist/lib/queue.d.ts +6 -2
- package/node_modules/@llblab/pi-telegram/dist/lib/queue.js +46 -4
- package/node_modules/@llblab/pi-telegram/dist/lib/replies.d.ts +5 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/replies.js +2 -2
- package/node_modules/@llblab/pi-telegram/dist/lib/routing.d.ts +2 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/routing.js +137 -13
- package/node_modules/@llblab/pi-telegram/dist/lib/telegram-api.d.ts +10 -5
- package/node_modules/@llblab/pi-telegram/dist/lib/telegram-api.js +19 -2
- package/node_modules/@llblab/pi-telegram/dist/lib/threads.js +23 -11
- package/node_modules/@llblab/pi-telegram/dist/package.json +1 -1
- package/node_modules/@llblab/pi-telegram/docs/architecture.md +5 -1
- package/node_modules/@llblab/pi-telegram/docs/multi-instance-bus.md +3 -1
- package/node_modules/@llblab/pi-telegram/docs/outbound.md +4 -2
- package/node_modules/@llblab/pi-telegram/lib/bindings.ts +13 -2
- package/node_modules/@llblab/pi-telegram/lib/bus-api.ts +2 -1
- package/node_modules/@llblab/pi-telegram/lib/commands.ts +21 -8
- package/node_modules/@llblab/pi-telegram/lib/extension.ts +43 -55
- package/node_modules/@llblab/pi-telegram/lib/journal.ts +80 -0
- package/node_modules/@llblab/pi-telegram/lib/outbound-attachments.ts +36 -3
- package/node_modules/@llblab/pi-telegram/lib/queue.ts +43 -4
- package/node_modules/@llblab/pi-telegram/lib/replies.ts +3 -2
- package/node_modules/@llblab/pi-telegram/lib/routing.ts +140 -18
- package/node_modules/@llblab/pi-telegram/lib/telegram-api.ts +22 -5
- package/node_modules/@llblab/pi-telegram/lib/threads.ts +25 -15
- package/node_modules/@llblab/pi-telegram/package.json +1 -1
- package/package.json +2 -2
|
@@ -1568,6 +1568,13 @@ export interface TelegramInboundRouteRuntimeDeps<
|
|
|
1568
1568
|
requiredJournalBindingKeys: readonly string[],
|
|
1569
1569
|
ownInputs?: readonly Threads.TelegramTemporaryThreadInput[],
|
|
1570
1570
|
) => readonly number[] | undefined;
|
|
1571
|
+
/** Explicit cleanup-only recovery; never called by a read-only protection observer. */
|
|
1572
|
+
recoverTemporaryThreadDeadQueueOwners?: (
|
|
1573
|
+
target: Queue.TelegramQueueTarget,
|
|
1574
|
+
requiredJournalBindingKeys: readonly string[],
|
|
1575
|
+
ownInputs: readonly Threads.TelegramTemporaryThreadInput[],
|
|
1576
|
+
isCurrent: () => boolean,
|
|
1577
|
+
) => number;
|
|
1571
1578
|
/** Quiet period after the last cancelled input before one cleanup attempt; defaults to 0 ms. */
|
|
1572
1579
|
temporaryThreadCleanupDelayMs?: number;
|
|
1573
1580
|
/** Live-rebind cleanup attempt pacing; the bounded window ends in `not-issued`, never a time-based deletion. */
|
|
@@ -6837,6 +6844,93 @@ export function createTelegramInboundRouteRuntime<
|
|
|
6837
6844
|
}
|
|
6838
6845
|
await promptEnqueue(messages, ctx);
|
|
6839
6846
|
};
|
|
6847
|
+
/** Local menu acceptance owns a one-shot effect; source removal and cleanup precede its release, never its replay. */
|
|
6848
|
+
const stageTemporaryRerouteMenu = async (
|
|
6849
|
+
pending: PendingUnboundReroute,
|
|
6850
|
+
command: Commands.ParsedTelegramCommand,
|
|
6851
|
+
messages: TMessage[],
|
|
6852
|
+
ctx: TContext,
|
|
6853
|
+
assertRecipientCurrent: () => void,
|
|
6854
|
+
completion: NonNullable<ReturnType<typeof Updates.prepareTelegramDeferredSourceCompletion>>,
|
|
6855
|
+
): Promise<(() => Promise<void>) | undefined> => {
|
|
6856
|
+
const original = pending.messages[0];
|
|
6857
|
+
const cap = captureTemporaryThreadAuthority(ctx);
|
|
6858
|
+
const temporary = pending.temporaryThread ?? findCreatedTemporaryThread(pending.sourceTarget);
|
|
6859
|
+
if (!cap?.isCurrent() || !temporary) return undefined;
|
|
6860
|
+
let accept!: (value: boolean) => void, resume!: (value: boolean) => void;
|
|
6861
|
+
const accepted = new Promise<boolean>(resolve => { accept = resolve; });
|
|
6862
|
+
const released = new Promise<boolean>(resolve => { resume = resolve; });
|
|
6863
|
+
const admission: Commands.TelegramSelectedCommandAdmission = {
|
|
6864
|
+
assertSourceCurrent() {
|
|
6865
|
+
if (!completion.isCurrent()) throw new Error("Rerouted menu source authority changed.");
|
|
6866
|
+
},
|
|
6867
|
+
assertRecipientCurrent,
|
|
6868
|
+
reportCompleted() {
|
|
6869
|
+
const result = completion.reportCompleted();
|
|
6870
|
+
accept(result);
|
|
6871
|
+
return result;
|
|
6872
|
+
},
|
|
6873
|
+
async beforeEffects() {
|
|
6874
|
+
if (!(await released)) throw new Error("Rerouted menu execution was not released.");
|
|
6875
|
+
},
|
|
6876
|
+
};
|
|
6877
|
+
const prepare = command.name === "start" ? commandHandler.prepareSelectedStartCommand
|
|
6878
|
+
: command.name === "help" ? commandHandler.prepareSelectedHelpCommand : commandHandler.prepareSelectedMenuCommand;
|
|
6879
|
+
const dispatch = prepare(command, messages, ctx, admission);
|
|
6880
|
+
if (!dispatch) return undefined;
|
|
6881
|
+
const execution = dispatch().catch(error => {
|
|
6882
|
+
deps.recordRuntimeEvent?.("routing", error, { phase: "reroute-menu-execution" });
|
|
6883
|
+
return false;
|
|
6884
|
+
});
|
|
6885
|
+
if (!(await Promise.race([accepted, execution.then(() => false)]))) {
|
|
6886
|
+
resume(false);
|
|
6887
|
+
await execution;
|
|
6888
|
+
return undefined;
|
|
6889
|
+
}
|
|
6890
|
+
return async () => {
|
|
6891
|
+
let allowEffects = false;
|
|
6892
|
+
try {
|
|
6893
|
+
const confirmed = await new Promise<boolean>(resolve => {
|
|
6894
|
+
let done = false, unsubscribe: (() => void) | undefined;
|
|
6895
|
+
const signal = Updates.getTelegramUpdateExecutionFence(original)?.signal;
|
|
6896
|
+
const finish = (result: boolean) => {
|
|
6897
|
+
if (done) return;
|
|
6898
|
+
done = true;
|
|
6899
|
+
clearTimeout(timer);
|
|
6900
|
+
unsubscribe?.();
|
|
6901
|
+
signal?.removeEventListener("abort", abort);
|
|
6902
|
+
resolve(result);
|
|
6903
|
+
};
|
|
6904
|
+
const abort = () => finish(false);
|
|
6905
|
+
const observe = () => {
|
|
6906
|
+
if (!cap.isCurrent() || !completion.isCurrent()) finish(false);
|
|
6907
|
+
else if (completion.inspectCompletion()) finish(true);
|
|
6908
|
+
};
|
|
6909
|
+
const timer = setTimeout(abort, deps.liveRebindCommandCompletionTimeoutMs ?? 8_000);
|
|
6910
|
+
timer.unref?.();
|
|
6911
|
+
unsubscribe = Updates.subscribeTelegramDeferredSourceCompletion(original, observe);
|
|
6912
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
6913
|
+
if (signal?.aborted) abort(); else observe();
|
|
6914
|
+
});
|
|
6915
|
+
if (!confirmed) throw new Error("Rerouted menu source removal is unconfirmed; execution remains suppressed.");
|
|
6916
|
+
assertRecipientCurrent();
|
|
6917
|
+
cancelTemporaryThreadCleanup(temporary.token);
|
|
6918
|
+
try {
|
|
6919
|
+
await runTemporaryThreadCleanup(temporary.token, cap, false);
|
|
6920
|
+
} catch (error) {
|
|
6921
|
+
// Cleanup uncertainty retains its canonical grant, not command execution authority or a retry license.
|
|
6922
|
+
deps.recordRuntimeEvent?.("routing", error, { phase: "reroute-menu-cleanup" });
|
|
6923
|
+
}
|
|
6924
|
+
assertRecipientCurrent();
|
|
6925
|
+
allowEffects = true;
|
|
6926
|
+
} catch (error) {
|
|
6927
|
+
deps.recordRuntimeEvent?.("routing", error, { phase: "reroute-menu-release" });
|
|
6928
|
+
} finally {
|
|
6929
|
+
resume(allowEffects);
|
|
6930
|
+
await execution;
|
|
6931
|
+
}
|
|
6932
|
+
};
|
|
6933
|
+
};
|
|
6840
6934
|
const finalizePendingReroute = async (
|
|
6841
6935
|
rerouteId: string,
|
|
6842
6936
|
pending: PendingUnboundReroute,
|
|
@@ -7258,12 +7352,20 @@ export function createTelegramInboundRouteRuntime<
|
|
|
7258
7352
|
});
|
|
7259
7353
|
if (!cancelledCurrent()) return;
|
|
7260
7354
|
entry = cap.adopt(entry);
|
|
7261
|
-
if (
|
|
7262
|
-
|
|
7263
|
-
|
|
7264
|
-
|
|
7265
|
-
|
|
7266
|
-
|
|
7355
|
+
if (!entry || !cap.isCurrent()) return;
|
|
7356
|
+
if (isRerouteTargetProtected(target, undefined, entry) && deps.recoverTemporaryThreadDeadQueueOwners &&
|
|
7357
|
+
store.inspectTemporaryThreadTarget(entry, cap.authority)?.kind === "temporary") {
|
|
7358
|
+
const inputs = Threads.getTelegramTemporaryThreadInputs(entry);
|
|
7359
|
+
const removed = deps.recoverTemporaryThreadDeadQueueOwners(
|
|
7360
|
+
target, [cap.journalBindingKey, ...inputs.map(input => input.journalBindingKey)],
|
|
7361
|
+
inputs, cap.isCurrent,
|
|
7362
|
+
);
|
|
7363
|
+
if (removed > 0) deps.recordRuntimeEvent?.("routing",
|
|
7364
|
+
"Discarded historical queue receipts from confirmed-dead owners before temporary Thread cleanup.",
|
|
7365
|
+
{ phase: "temporary-thread-dead-owner-recovery", removedUpdateCount: removed });
|
|
7366
|
+
}
|
|
7367
|
+
// Recovery is not deletion authority: recapture every ordinary protection after the journal CAS.
|
|
7368
|
+
if (!cap.isCurrent() || isRerouteTargetProtected(target, undefined, entry)) return;
|
|
7267
7369
|
const issued = store.issueTemporaryThreadCleanup(entry, cap.authority);
|
|
7268
7370
|
if (!issued || !cap.isCurrent()) return;
|
|
7269
7371
|
entry = issued.entry;
|
|
@@ -8658,6 +8760,7 @@ export function createTelegramInboundRouteRuntime<
|
|
|
8658
8760
|
const executeUnboundRerouteCallbackOperation = async (
|
|
8659
8761
|
query: TCallbackQuery,
|
|
8660
8762
|
ctx: TContext,
|
|
8763
|
+
afterAdmission: (release: () => Promise<void>) => void,
|
|
8661
8764
|
): Promise<boolean> => {
|
|
8662
8765
|
const parsed = parseTelegramUnboundRerouteCallbackData(query.data);
|
|
8663
8766
|
if (!parsed) return false;
|
|
@@ -9349,7 +9452,20 @@ export function createTelegramInboundRouteRuntime<
|
|
|
9349
9452
|
const localMessages = pending.rerouteCopies
|
|
9350
9453
|
? cloneTelegramMessagesForThread(pending.messages, parsed.threadId, pending.rerouteCopies.bySourceUpdateId)
|
|
9351
9454
|
: reroutedMessages;
|
|
9352
|
-
|
|
9455
|
+
const command = pending.dispatchKind === "command"
|
|
9456
|
+
? getLiveRebindMenu(localMessages) ?? getLiveRebindHelp(localMessages) : undefined;
|
|
9457
|
+
const completion = pending.rerouteCopies && command
|
|
9458
|
+
? Updates.prepareTelegramDeferredSourceCompletion(pending.messages[0]) : undefined;
|
|
9459
|
+
if (command && completion) {
|
|
9460
|
+
const release = await stageTemporaryRerouteMenu(pending, command, localMessages, ctx, assertRerouteCurrent, completion);
|
|
9461
|
+
if (!release) {
|
|
9462
|
+
await deps.answerCallbackQuery(query.id, "Command acceptance unconfirmed; not resending");
|
|
9463
|
+
return true;
|
|
9464
|
+
}
|
|
9465
|
+
afterAdmission(release);
|
|
9466
|
+
} else {
|
|
9467
|
+
await dispatchPendingRerouteMessages(pending, localMessages, ctx);
|
|
9468
|
+
}
|
|
9353
9469
|
assertRerouteCurrent();
|
|
9354
9470
|
pending.messages = [];
|
|
9355
9471
|
return completeRoutedReroute();
|
|
@@ -9360,17 +9476,23 @@ export function createTelegramInboundRouteRuntime<
|
|
|
9360
9476
|
): Promise<boolean> => {
|
|
9361
9477
|
const parsed = parseTelegramUnboundRerouteCallbackData(query.data);
|
|
9362
9478
|
if (!parsed) return false;
|
|
9363
|
-
return withPendingRerouteSelection(parsed.rerouteId, query, () => {
|
|
9364
|
-
|
|
9365
|
-
|
|
9366
|
-
|
|
9367
|
-
|
|
9368
|
-
|
|
9369
|
-
|
|
9370
|
-
|
|
9371
|
-
|
|
9372
|
-
|
|
9373
|
-
|
|
9479
|
+
return withPendingRerouteSelection(parsed.rerouteId, query, async () => {
|
|
9480
|
+
let release: (() => Promise<void>) | undefined;
|
|
9481
|
+
const operation = () => executeUnboundRerouteCallbackOperation(query, ctx, value => { release = value; });
|
|
9482
|
+
try {
|
|
9483
|
+
if (!deps.runWorkspaceOperation) return await operation();
|
|
9484
|
+
return await deps.runWorkspaceOperation(
|
|
9485
|
+
{
|
|
9486
|
+
operationId: `workspace-reroute:${query.id}`,
|
|
9487
|
+
operationKind: "workspace.route-unbound-thread",
|
|
9488
|
+
scopes: [{ kind: "profile" }],
|
|
9489
|
+
},
|
|
9490
|
+
operation,
|
|
9491
|
+
);
|
|
9492
|
+
} finally {
|
|
9493
|
+
// Keep the exact source references, but release the non-reentrant Workspace gate before cleanup.
|
|
9494
|
+
await release?.();
|
|
9495
|
+
}
|
|
9374
9496
|
});
|
|
9375
9497
|
};
|
|
9376
9498
|
const handleUnboundRerouteCallback = async (
|
|
@@ -473,6 +473,8 @@ export interface TelegramAnswerGuestQueryOptions {
|
|
|
473
473
|
}
|
|
474
474
|
|
|
475
475
|
export interface TelegramEditGuestInlineMessageContent {
|
|
476
|
+
/** Cached media only: inline edits cannot upload a new local file. */
|
|
477
|
+
media?: { type: "photo" | "document" | "audio"; media: string; caption?: string };
|
|
476
478
|
text?: string;
|
|
477
479
|
richMessage?: TelegramInputRichMessage;
|
|
478
480
|
parseMode?: "HTML";
|
|
@@ -917,10 +919,9 @@ export interface TelegramBridgeApiRuntime extends Pick<
|
|
|
917
919
|
options?: TelegramAnswerGuestQueryOptions,
|
|
918
920
|
) => Promise<void>;
|
|
919
921
|
/**
|
|
920
|
-
*
|
|
921
|
-
*
|
|
922
|
-
*
|
|
923
|
-
* guest answers, so this exists only to falsify that behavior live.
|
|
922
|
+
* Answers the guest query and retains the sent inline message id for final
|
|
923
|
+
* text or cached-media replacement. Requires direct transport ownership;
|
|
924
|
+
* actual guest-media rendering remains live-client gated.
|
|
924
925
|
*/
|
|
925
926
|
answerGuestQueryForInlineMessage: (
|
|
926
927
|
guestQueryId: string,
|
|
@@ -930,6 +931,7 @@ export interface TelegramBridgeApiRuntime extends Pick<
|
|
|
930
931
|
editGuestInlineMessage: (
|
|
931
932
|
inlineMessageId: string,
|
|
932
933
|
content: TelegramEditGuestInlineMessageContent,
|
|
934
|
+
options?: Pick<TelegramApiCallOptions, "assertAuthority">,
|
|
933
935
|
) => Promise<void>;
|
|
934
936
|
deleteMessage: (chatId: number, messageId: number) => Promise<void>;
|
|
935
937
|
prepareTempDir: () => Promise<number>;
|
|
@@ -2404,14 +2406,29 @@ export function createTelegramBridgeApiRuntime(
|
|
|
2404
2406
|
editGuestInlineMessage: async (
|
|
2405
2407
|
inlineMessageId: string,
|
|
2406
2408
|
content: TelegramEditGuestInlineMessageContent,
|
|
2409
|
+
options?: Pick<TelegramApiCallOptions, "assertAuthority">,
|
|
2407
2410
|
) => {
|
|
2411
|
+
if (content.media) {
|
|
2412
|
+
try {
|
|
2413
|
+
const result = await callRecorded("editMessageMedia", {
|
|
2414
|
+
inline_message_id: inlineMessageId,
|
|
2415
|
+
media: content.media,
|
|
2416
|
+
}, { ...options, maxAttempts: 1, retryRateLimit: false });
|
|
2417
|
+
if (result !== true) throw new Error("Missing inline edit acknowledgement.");
|
|
2418
|
+
} catch (error) {
|
|
2419
|
+
// Only a parsed rejection permits replacing this attempt with a failure notice.
|
|
2420
|
+
if (isTelegramApiRequestRejected(error, "editMessageMedia")) throw error;
|
|
2421
|
+
throw new TelegramApiCommitUnknownError("editMessageMedia", error);
|
|
2422
|
+
}
|
|
2423
|
+
return;
|
|
2424
|
+
}
|
|
2408
2425
|
await callRecorded("editMessageText", {
|
|
2409
2426
|
inline_message_id: inlineMessageId,
|
|
2410
2427
|
...(content.richMessage
|
|
2411
2428
|
? { rich_message: content.richMessage }
|
|
2412
2429
|
: { text: content.text }),
|
|
2413
2430
|
...(content.parseMode ? { parse_mode: content.parseMode } : {}),
|
|
2414
|
-
});
|
|
2431
|
+
}, options);
|
|
2415
2432
|
},
|
|
2416
2433
|
prepareTempDir: () =>
|
|
2417
2434
|
prepareTelegramTempDir(deps.tempDir, deps.tempFileMaxAgeMs),
|
|
@@ -2723,10 +2723,16 @@ function liveRebindOldTargetUnknown(value: TelegramWorkspaceLiveRebindIntent): b
|
|
|
2723
2723
|
function retainLiveRebind(value: TelegramWorkspaceLiveRebindIntent): boolean {
|
|
2724
2724
|
return liveRebindActive(value) || liveRebindOldTargetUnknown(value) || value.oldTargetProtected === true;
|
|
2725
2725
|
}
|
|
2726
|
-
function
|
|
2726
|
+
function provisioningProtectionIntents(snapshot: TelegramWorkspaceRestoreSnapshot): readonly Pick<TelegramWorkspaceRestoreIntent, "request">[] {
|
|
2727
|
+
return [...snapshot.operations, ...(snapshot.liveRebindings ?? []).filter(retainLiveRebind)];
|
|
2728
|
+
}
|
|
2729
|
+
function protectedLiveRebindOldTargetIntents(snapshot: TelegramWorkspaceRestoreSnapshot | undefined): TelegramWorkspaceLiveRebindIntent[] {
|
|
2727
2730
|
return (snapshot?.liveRebindings ?? []).filter(value =>
|
|
2728
2731
|
value.oldTargetProtected === true || liveRebindOldTargetUnknown(value) || value.inputRelease !== undefined && value.cleanup === "issued",
|
|
2729
|
-
)
|
|
2732
|
+
);
|
|
2733
|
+
}
|
|
2734
|
+
function protectedLiveRebindOldTargets(snapshot: TelegramWorkspaceRestoreSnapshot | undefined): TelegramTarget[] {
|
|
2735
|
+
return protectedLiveRebindOldTargetIntents(snapshot).map(value => ({ ...value.request.binding.target }));
|
|
2730
2736
|
}
|
|
2731
2737
|
function pendingWorkspaceRelocationRequests(
|
|
2732
2738
|
snapshot: TelegramWorkspaceRestoreSnapshot | undefined,
|
|
@@ -2821,10 +2827,11 @@ function assertWorkspaceRestoreBindingProtection(
|
|
|
2821
2827
|
throw new Error("Protected Workspace Restore owner target changed.");
|
|
2822
2828
|
}
|
|
2823
2829
|
}
|
|
2824
|
-
for (const
|
|
2830
|
+
for (const { request } of protectedLiveRebindOldTargetIntents(file.workspaceRestore)) {
|
|
2831
|
+
const old = request.binding.target;
|
|
2825
2832
|
if (file.workspaceBindings?.some(value => targetMatches(value.target, old)) ||
|
|
2826
2833
|
file.threads?.some(value => targetMatches(value.target, old) && ["active", "starting", "pending", "probe-required"].includes(value.status)) ||
|
|
2827
|
-
file.pendingProvisions?.some(value =>
|
|
2834
|
+
file.pendingProvisions?.some(value => conflictsWithWorkspaceRestoreProvision(request, value)) ||
|
|
2828
2835
|
file.reservations?.some(value => targetMatches(value.target, old)))
|
|
2829
2836
|
throw new Error("Unknown live cleanup target remains protected.");
|
|
2830
2837
|
}
|
|
@@ -3840,7 +3847,7 @@ export function createTelegramTopicTargetStore(
|
|
|
3840
3847
|
};
|
|
3841
3848
|
const assertRestoreRecoveryProtection = (
|
|
3842
3849
|
path: string,
|
|
3843
|
-
operations: readonly TelegramWorkspaceRestoreIntent[],
|
|
3850
|
+
operations: readonly Pick<TelegramWorkspaceRestoreIntent, "request">[],
|
|
3844
3851
|
provisions: readonly TelegramThreadPendingProvision[],
|
|
3845
3852
|
requireKnownTargets = false,
|
|
3846
3853
|
recoveryEvidence?: TelegramProvisionRecoveryFile,
|
|
@@ -5061,7 +5068,7 @@ export function createTelegramTopicTargetStore(
|
|
|
5061
5068
|
assertWorkspaceRestoreBindingProtection(file);
|
|
5062
5069
|
assertRestoreRecoveryProtection(
|
|
5063
5070
|
path,
|
|
5064
|
-
file.workspaceRestore
|
|
5071
|
+
provisioningProtectionIntents(file.workspaceRestore!),
|
|
5065
5072
|
file.pendingProvisions ?? [],
|
|
5066
5073
|
);
|
|
5067
5074
|
observe({
|
|
@@ -5117,7 +5124,7 @@ export function createTelegramTopicTargetStore(
|
|
|
5117
5124
|
assertWorkspaceRestoreBindingProtection(file);
|
|
5118
5125
|
assertRestoreRecoveryProtection(
|
|
5119
5126
|
path,
|
|
5120
|
-
file.workspaceRestore
|
|
5127
|
+
provisioningProtectionIntents(file.workspaceRestore!),
|
|
5121
5128
|
file.pendingProvisions ?? [],
|
|
5122
5129
|
);
|
|
5123
5130
|
const canonical = file.workspaceBindings ?? [],
|
|
@@ -5175,9 +5182,9 @@ export function createTelegramTopicTargetStore(
|
|
|
5175
5182
|
(row) => currentRow(row) && targetMatches(row.target, old),
|
|
5176
5183
|
) ||
|
|
5177
5184
|
reserved.some((value) => targetMatches(value.target, old)) ||
|
|
5178
|
-
|
|
5179
|
-
|
|
5180
|
-
) ||
|
|
5185
|
+
// Use the same binding/slot/owner/target conflicts as rebind admission. An unrelated unlocated
|
|
5186
|
+
// create must not globally veto cleanup of this exact former binding (nor its issued marker).
|
|
5187
|
+
provisions.some((value) => conflictsWithWorkspaceRestoreProvision(request, value)) ||
|
|
5181
5188
|
cleanups.some((value) => targetMatches(value.target, old));
|
|
5182
5189
|
if (
|
|
5183
5190
|
protectedTarget(
|
|
@@ -5390,8 +5397,11 @@ export function createTelegramTopicTargetStore(
|
|
|
5390
5397
|
file.reservations?.some((value) =>
|
|
5391
5398
|
targetMatches(value.target, target),
|
|
5392
5399
|
) ||
|
|
5400
|
+
// This exact created frame already proves a separately created routing tab (API ACK or authenticated
|
|
5401
|
+
// implicit creation). An unlocated Workspace create is not its owner; keep that intent unresolved,
|
|
5402
|
+
// but veto a concrete target collision. Generic unknown-target/Restore cleanup keeps its own guards.
|
|
5393
5403
|
file.pendingProvisions?.some(
|
|
5394
|
-
(value) =>
|
|
5404
|
+
(value) => !!value.target && targetMatches(value.target, target),
|
|
5395
5405
|
) ||
|
|
5396
5406
|
file.pendingCleanups?.some((value) =>
|
|
5397
5407
|
targetMatches(value.target, target),
|
|
@@ -5557,7 +5567,7 @@ export function createTelegramTopicTargetStore(
|
|
|
5557
5567
|
parseTopicTargetFile(before.file).pendingProvisions ?? [];
|
|
5558
5568
|
assertRestoreRecoveryProtection(
|
|
5559
5569
|
path,
|
|
5560
|
-
before.state
|
|
5570
|
+
provisioningProtectionIntents(before.state),
|
|
5561
5571
|
provisions,
|
|
5562
5572
|
);
|
|
5563
5573
|
const after = { ...before.file, workspaceRestore: next };
|
|
@@ -5573,7 +5583,7 @@ export function createTelegramTopicTargetStore(
|
|
|
5573
5583
|
return false;
|
|
5574
5584
|
assertRestoreRecoveryProtection(
|
|
5575
5585
|
path,
|
|
5576
|
-
[...before.state
|
|
5586
|
+
[...provisioningProtectionIntents(before.state), ...provisioningProtectionIntents(next)],
|
|
5577
5587
|
provisions,
|
|
5578
5588
|
);
|
|
5579
5589
|
committedBefore = before;
|
|
@@ -5598,7 +5608,7 @@ export function createTelegramTopicTargetStore(
|
|
|
5598
5608
|
);
|
|
5599
5609
|
assertRestoreRecoveryProtection(
|
|
5600
5610
|
path,
|
|
5601
|
-
[...before.state
|
|
5611
|
+
[...provisioningProtectionIntents(before.state), ...provisioningProtectionIntents(next)],
|
|
5602
5612
|
provisions,
|
|
5603
5613
|
);
|
|
5604
5614
|
if (!currentScope() || !isCurrent()) return;
|
|
@@ -6775,7 +6785,7 @@ export function createTelegramTopicTargetStore(
|
|
|
6775
6785
|
conflictsWithWorkspaceRestoreProvision(request, provision),
|
|
6776
6786
|
) ||
|
|
6777
6787
|
conflictsWithTemporaryThread(workspaceRestore, provision.target) ||
|
|
6778
|
-
|
|
6788
|
+
protectedLiveRebindOldTargetIntents(workspaceRestore).some(({ request }) => conflictsWithWorkspaceRestoreProvision(request, provision))
|
|
6779
6789
|
) {
|
|
6780
6790
|
throw new Error("Protected Workspace Restore provisioning conflict.");
|
|
6781
6791
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@llblab/pi-kit",
|
|
3
|
-
"version": "0.35.
|
|
3
|
+
"version": "0.35.1",
|
|
4
4
|
"private": false,
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
"@llblab/pi-codex-usage": "0.13.1",
|
|
48
48
|
"@llblab/pi-grow-loop": "0.9.0",
|
|
49
49
|
"@llblab/pi-state-flow": "0.26.4",
|
|
50
|
-
"@llblab/pi-telegram": "0.57.
|
|
50
|
+
"@llblab/pi-telegram": "0.57.1",
|
|
51
51
|
"@llblab/skills": "1.15.0"
|
|
52
52
|
},
|
|
53
53
|
"bundledDependencies": [
|