@llblab/pi-kit 0.27.6 → 0.28.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/BACKLOG.md +2 -5
- package/CHANGELOG.md +5 -0
- package/README.md +2 -2
- package/node_modules/@llblab/pi-telegram/AGENTS.md +10 -8
- package/node_modules/@llblab/pi-telegram/BACKLOG.md +1 -9
- package/node_modules/@llblab/pi-telegram/CHANGELOG.md +13 -0
- package/node_modules/@llblab/pi-telegram/LICENSE +21 -0
- package/node_modules/@llblab/pi-telegram/README.md +15 -5
- package/node_modules/@llblab/pi-telegram/dist/lib/activity-verbosity.d.ts +1 -5
- package/node_modules/@llblab/pi-telegram/dist/lib/activity-verbosity.js +5 -7
- package/node_modules/@llblab/pi-telegram/dist/lib/bindings.d.ts +5 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/bindings.js +6 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/bus-api.js +4 -2
- package/node_modules/@llblab/pi-telegram/dist/lib/bus-follower.d.ts +54 -56
- package/node_modules/@llblab/pi-telegram/dist/lib/bus-follower.js +206 -139
- package/node_modules/@llblab/pi-telegram/dist/lib/bus-leader.d.ts +21 -2
- package/node_modules/@llblab/pi-telegram/dist/lib/bus-leader.js +181 -25
- package/node_modules/@llblab/pi-telegram/dist/lib/bus-transport.d.ts +4 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/bus-transport.js +28 -4
- package/node_modules/@llblab/pi-telegram/dist/lib/bus.d.ts +52 -56
- package/node_modules/@llblab/pi-telegram/dist/lib/bus.js +78 -246
- package/node_modules/@llblab/pi-telegram/dist/lib/channel-posts.d.ts +1 -3
- package/node_modules/@llblab/pi-telegram/dist/lib/channel-posts.js +23 -26
- package/node_modules/@llblab/pi-telegram/dist/lib/command-templates.d.ts +0 -5
- package/node_modules/@llblab/pi-telegram/dist/lib/command-templates.js +5 -5
- package/node_modules/@llblab/pi-telegram/dist/lib/commands.d.ts +0 -23
- package/node_modules/@llblab/pi-telegram/dist/lib/commands.js +15 -15
- package/node_modules/@llblab/pi-telegram/dist/lib/config.d.ts +0 -17
- package/node_modules/@llblab/pi-telegram/dist/lib/config.js +12 -14
- package/node_modules/@llblab/pi-telegram/dist/lib/delivery.d.ts +0 -4
- package/node_modules/@llblab/pi-telegram/dist/lib/delivery.js +2 -5
- package/node_modules/@llblab/pi-telegram/dist/lib/extension.js +255 -61
- package/node_modules/@llblab/pi-telegram/dist/lib/generative-apps.d.ts +0 -19
- package/node_modules/@llblab/pi-telegram/dist/lib/inbound.d.ts +0 -2
- package/node_modules/@llblab/pi-telegram/dist/lib/inbound.js +2 -2
- package/node_modules/@llblab/pi-telegram/dist/lib/journal.d.ts +177 -16
- package/node_modules/@llblab/pi-telegram/dist/lib/journal.js +1115 -249
- package/node_modules/@llblab/pi-telegram/dist/lib/keyboard.d.ts +0 -2
- package/node_modules/@llblab/pi-telegram/dist/lib/keyboard.js +2 -2
- package/node_modules/@llblab/pi-telegram/dist/lib/lifecycle.js +1 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/locks.d.ts +100 -12
- package/node_modules/@llblab/pi-telegram/dist/lib/locks.js +380 -14
- package/node_modules/@llblab/pi-telegram/dist/lib/logging.d.ts +30 -12
- package/node_modules/@llblab/pi-telegram/dist/lib/logging.js +129 -72
- package/node_modules/@llblab/pi-telegram/dist/lib/media.d.ts +28 -5
- package/node_modules/@llblab/pi-telegram/dist/lib/media.js +26 -8
- package/node_modules/@llblab/pi-telegram/dist/lib/menu-model.d.ts +0 -11
- package/node_modules/@llblab/pi-telegram/dist/lib/menu-model.js +8 -8
- package/node_modules/@llblab/pi-telegram/dist/lib/menu-settings.d.ts +0 -18
- package/node_modules/@llblab/pi-telegram/dist/lib/menu-settings.js +18 -18
- package/node_modules/@llblab/pi-telegram/dist/lib/menu-status.d.ts +4 -4
- package/node_modules/@llblab/pi-telegram/dist/lib/menu-status.js +12 -7
- package/node_modules/@llblab/pi-telegram/dist/lib/menu-thinking.d.ts +1 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/menu-thinking.js +1 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/menu.d.ts +1 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/menu.js +4 -3
- package/node_modules/@llblab/pi-telegram/dist/lib/outbound-attachments.d.ts +0 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/outbound-attachments.js +1 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/outbound-buttons.js +1 -8
- package/node_modules/@llblab/pi-telegram/dist/lib/outbound-markup.d.ts +1 -6
- package/node_modules/@llblab/pi-telegram/dist/lib/outbound-markup.js +4 -21
- package/node_modules/@llblab/pi-telegram/dist/lib/outbound.d.ts +0 -8
- package/node_modules/@llblab/pi-telegram/dist/lib/outbound.js +5 -5
- package/node_modules/@llblab/pi-telegram/dist/lib/paths.d.ts +50 -7
- package/node_modules/@llblab/pi-telegram/dist/lib/paths.js +155 -17
- package/node_modules/@llblab/pi-telegram/dist/lib/pi.d.ts +1 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/pi.js +2 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/polling.d.ts +0 -5
- package/node_modules/@llblab/pi-telegram/dist/lib/polling.js +5 -5
- package/node_modules/@llblab/pi-telegram/dist/lib/preview.d.ts +0 -3
- package/node_modules/@llblab/pi-telegram/dist/lib/preview.js +3 -3
- package/node_modules/@llblab/pi-telegram/dist/lib/prompt-templates.d.ts +0 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/prompt-templates.js +1 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/queue.d.ts +3 -15
- package/node_modules/@llblab/pi-telegram/dist/lib/queue.js +69 -8
- package/node_modules/@llblab/pi-telegram/dist/lib/recovery.d.ts +29 -9
- package/node_modules/@llblab/pi-telegram/dist/lib/recovery.js +104 -33
- package/node_modules/@llblab/pi-telegram/dist/lib/replies.d.ts +0 -4
- package/node_modules/@llblab/pi-telegram/dist/lib/replies.js +1 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/routing.d.ts +73 -8
- package/node_modules/@llblab/pi-telegram/dist/lib/routing.js +2206 -307
- package/node_modules/@llblab/pi-telegram/dist/lib/sections.d.ts +0 -2
- package/node_modules/@llblab/pi-telegram/dist/lib/sections.js +1 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/status.d.ts +44 -9
- package/node_modules/@llblab/pi-telegram/dist/lib/status.js +149 -27
- package/node_modules/@llblab/pi-telegram/dist/lib/sync.d.ts +0 -5
- package/node_modules/@llblab/pi-telegram/dist/lib/sync.js +5 -4
- package/node_modules/@llblab/pi-telegram/dist/lib/telegram-api.d.ts +11 -3
- package/node_modules/@llblab/pi-telegram/dist/lib/telegram-api.js +34 -23
- package/node_modules/@llblab/pi-telegram/dist/lib/thread-cleanup-manager.d.ts +1 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/thread-cleanup-manager.js +18 -21
- package/node_modules/@llblab/pi-telegram/dist/lib/thread-naming.d.ts +32 -5
- package/node_modules/@llblab/pi-telegram/dist/lib/thread-naming.js +191 -7
- package/node_modules/@llblab/pi-telegram/dist/lib/thread-reconciler.d.ts +2 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/thread-reconciler.js +1 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/threads.d.ts +291 -59
- package/node_modules/@llblab/pi-telegram/dist/lib/threads.js +1647 -369
- package/node_modules/@llblab/pi-telegram/dist/lib/turns.d.ts +0 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/turns.js +1 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/updates.d.ts +184 -28
- package/node_modules/@llblab/pi-telegram/dist/lib/updates.js +1032 -114
- package/node_modules/@llblab/pi-telegram/dist/lib/wire.d.ts +12 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/wire.js +21 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/workspace-admission.d.ts +25 -17
- package/node_modules/@llblab/pi-telegram/dist/lib/workspace-admission.js +95 -20
- package/node_modules/@llblab/pi-telegram/dist/lib/workspace-identity.d.ts +20 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/workspace-identity.js +103 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/workspace-retirement.d.ts +43 -8
- package/node_modules/@llblab/pi-telegram/dist/lib/workspace-retirement.js +148 -52
- package/node_modules/@llblab/pi-telegram/dist/package.json +6 -6
- package/node_modules/@llblab/pi-telegram/dist/skills/generated-control-surface/SKILL.md +3 -3
- package/node_modules/@llblab/pi-telegram/dist/skills/telegram-bridge/references/diagnosis.md +3 -3
- package/node_modules/@llblab/pi-telegram/docs/README.md +1 -1
- package/node_modules/@llblab/pi-telegram/docs/activity.md +1 -1
- package/node_modules/@llblab/pi-telegram/docs/architecture.md +152 -34
- package/node_modules/@llblab/pi-telegram/docs/callback-namespaces.md +1 -1
- package/node_modules/@llblab/pi-telegram/docs/delivery.md +1 -1
- package/node_modules/@llblab/pi-telegram/docs/generative-apps.md +2 -2
- package/node_modules/@llblab/pi-telegram/docs/inbound.md +1 -1
- package/node_modules/@llblab/pi-telegram/docs/multi-instance-bus.md +125 -19
- package/node_modules/@llblab/pi-telegram/docs/public-api.md +3 -1
- package/node_modules/@llblab/pi-telegram/docs/ui-style.md +22 -8
- package/node_modules/@llblab/pi-telegram/docs/updates.md +1 -1
- package/node_modules/@llblab/pi-telegram/lib/activity-verbosity.ts +5 -9
- package/node_modules/@llblab/pi-telegram/lib/bindings.ts +6 -0
- package/node_modules/@llblab/pi-telegram/lib/bus-api.ts +5 -2
- package/node_modules/@llblab/pi-telegram/lib/bus-follower.ts +243 -224
- package/node_modules/@llblab/pi-telegram/lib/bus-leader.ts +178 -31
- package/node_modules/@llblab/pi-telegram/lib/bus-transport.ts +30 -8
- package/node_modules/@llblab/pi-telegram/lib/bus.ts +110 -334
- package/node_modules/@llblab/pi-telegram/lib/channel-posts.ts +29 -29
- package/node_modules/@llblab/pi-telegram/lib/command-templates.ts +5 -5
- package/node_modules/@llblab/pi-telegram/lib/commands.ts +15 -15
- package/node_modules/@llblab/pi-telegram/lib/config.ts +12 -17
- package/node_modules/@llblab/pi-telegram/lib/delivery.ts +2 -8
- package/node_modules/@llblab/pi-telegram/lib/extension.ts +257 -72
- package/node_modules/@llblab/pi-telegram/lib/generative-apps.ts +0 -22
- package/node_modules/@llblab/pi-telegram/lib/inbound.ts +2 -2
- package/node_modules/@llblab/pi-telegram/lib/journal.ts +1182 -326
- package/node_modules/@llblab/pi-telegram/lib/keyboard.ts +2 -2
- package/node_modules/@llblab/pi-telegram/lib/lifecycle.ts +1 -1
- package/node_modules/@llblab/pi-telegram/lib/locks.ts +402 -23
- package/node_modules/@llblab/pi-telegram/lib/logging.ts +151 -101
- package/node_modules/@llblab/pi-telegram/lib/media.ts +51 -9
- package/node_modules/@llblab/pi-telegram/lib/menu-model.ts +8 -8
- package/node_modules/@llblab/pi-telegram/lib/menu-settings.ts +18 -18
- package/node_modules/@llblab/pi-telegram/lib/menu-status.ts +11 -0
- package/node_modules/@llblab/pi-telegram/lib/menu-thinking.ts +2 -2
- package/node_modules/@llblab/pi-telegram/lib/menu.ts +5 -1
- package/node_modules/@llblab/pi-telegram/lib/outbound-attachments.ts +1 -1
- package/node_modules/@llblab/pi-telegram/lib/outbound-buttons.ts +1 -10
- package/node_modules/@llblab/pi-telegram/lib/outbound-markup.ts +5 -24
- package/node_modules/@llblab/pi-telegram/lib/outbound.ts +5 -5
- package/node_modules/@llblab/pi-telegram/lib/paths.ts +177 -17
- package/node_modules/@llblab/pi-telegram/lib/pi.ts +5 -2
- package/node_modules/@llblab/pi-telegram/lib/polling.ts +5 -5
- package/node_modules/@llblab/pi-telegram/lib/preview.ts +3 -3
- package/node_modules/@llblab/pi-telegram/lib/prompt-templates.ts +1 -1
- package/node_modules/@llblab/pi-telegram/lib/queue.ts +67 -9
- package/node_modules/@llblab/pi-telegram/lib/recovery.ts +104 -44
- package/node_modules/@llblab/pi-telegram/lib/replies.ts +1 -1
- package/node_modules/@llblab/pi-telegram/lib/routing.ts +1955 -375
- package/node_modules/@llblab/pi-telegram/lib/sections.ts +1 -1
- package/node_modules/@llblab/pi-telegram/lib/status.ts +156 -36
- package/node_modules/@llblab/pi-telegram/lib/sync.ts +4 -4
- package/node_modules/@llblab/pi-telegram/lib/telegram-api.ts +48 -33
- package/node_modules/@llblab/pi-telegram/lib/thread-cleanup-manager.ts +24 -21
- package/node_modules/@llblab/pi-telegram/lib/thread-naming.ts +267 -9
- package/node_modules/@llblab/pi-telegram/lib/thread-reconciler.ts +3 -1
- package/node_modules/@llblab/pi-telegram/lib/threads.ts +1697 -488
- package/node_modules/@llblab/pi-telegram/lib/turns.ts +1 -1
- package/node_modules/@llblab/pi-telegram/lib/updates.ts +1072 -151
- package/node_modules/@llblab/pi-telegram/lib/wire.ts +28 -0
- package/node_modules/@llblab/pi-telegram/lib/workspace-admission.ts +97 -47
- package/node_modules/@llblab/pi-telegram/lib/workspace-identity.ts +147 -0
- package/node_modules/@llblab/pi-telegram/lib/workspace-retirement.ts +188 -89
- package/node_modules/@llblab/pi-telegram/package.json +6 -6
- package/node_modules/@llblab/pi-telegram/scripts/audit-exports.mjs +100 -0
- package/node_modules/@llblab/pi-telegram/scripts/check-downgrade.mjs +80 -43
- package/node_modules/@llblab/pi-telegram/skills/generated-control-surface/SKILL.md +3 -3
- package/node_modules/@llblab/pi-telegram/skills/telegram-bridge/references/diagnosis.md +3 -3
- package/package.json +2 -2
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Non-coercing decoded wire-value inspection
|
|
3
|
+
* Zones: wire fields, record shape, integer bounds
|
|
4
|
+
* Owns shared shallow predicates; excludes schemas, lossless decoding,
|
|
5
|
+
* required fields, normalization, storage and execution authority.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/** Non-array object shape only; not a plain-object or JSON-serializability guarantee. */
|
|
9
|
+
export function isWireRecord(value: unknown): value is Record<string, unknown> {
|
|
10
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** Checks only own enumerable string keys, not required, inherited or symbol fields. */
|
|
14
|
+
export function hasOnlyWireKeys(
|
|
15
|
+
value: Record<string, unknown>,
|
|
16
|
+
allowedKeys: readonly string[],
|
|
17
|
+
): boolean {
|
|
18
|
+
const allowed = new Set(allowedKeys);
|
|
19
|
+
return Object.keys(value).every(key => allowed.has(key));
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function isNonEmptyWireString(value: unknown): value is string {
|
|
23
|
+
return typeof value === "string" && value.length > 0;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function isNonNegativeWireInteger(value: unknown): value is number {
|
|
27
|
+
return Number.isSafeInteger(value) && (value as number) >= 0;
|
|
28
|
+
}
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
type PathLike,
|
|
16
16
|
} from "node:fs";
|
|
17
17
|
import { dirname } from "node:path";
|
|
18
|
+
import { isDeepStrictEqual } from "node:util";
|
|
18
19
|
|
|
19
20
|
import {
|
|
20
21
|
getTelegramProcessLiveness,
|
|
@@ -23,8 +24,12 @@ import {
|
|
|
23
24
|
import {
|
|
24
25
|
renameTelegramPathWithRetry,
|
|
25
26
|
withTelegramFileTransaction,
|
|
27
|
+
readTelegramRuntimeState,
|
|
28
|
+
mutateTelegramRuntimeStateSection,
|
|
29
|
+
TelegramRuntimeStateError,
|
|
26
30
|
} from "./locks.ts";
|
|
27
|
-
import type
|
|
31
|
+
import { areTelegramTargetsEqual as areTargetsEqual, type TelegramTarget } from "./target.ts";
|
|
32
|
+
import { isWireRecord as isRecord, isNonNegativeWireInteger as isSafeTimestamp } from "./wire.ts";
|
|
28
33
|
|
|
29
34
|
const TELEGRAM_WORKSPACE_ADMISSION_VERSION = 1;
|
|
30
35
|
const TELEGRAM_WORKSPACE_ADMISSION_MAX_LEASES = 4096;
|
|
@@ -41,7 +46,7 @@ export type TelegramWorkspaceAdmissionScope =
|
|
|
41
46
|
| { kind: "chat"; chatId: number }
|
|
42
47
|
| { kind: "profile" };
|
|
43
48
|
|
|
44
|
-
|
|
49
|
+
interface TelegramWorkspaceAdmissionLease {
|
|
45
50
|
operationId: string;
|
|
46
51
|
operationKind: string;
|
|
47
52
|
profileKey: string;
|
|
@@ -50,7 +55,7 @@ export interface TelegramWorkspaceAdmissionLease {
|
|
|
50
55
|
acquiredAtMs: number;
|
|
51
56
|
}
|
|
52
57
|
|
|
53
|
-
|
|
58
|
+
type TelegramWorkspaceDestructiveFenceKind =
|
|
54
59
|
| "pressure-retirement"
|
|
55
60
|
| "manual-thread-cleanup"
|
|
56
61
|
| "journal-writer-closure";
|
|
@@ -58,7 +63,7 @@ export type TelegramWorkspaceDestructiveFenceKind =
|
|
|
58
63
|
export type TelegramWorkspaceDeletionFenceKind = Exclude<
|
|
59
64
|
TelegramWorkspaceDestructiveFenceKind, "journal-writer-closure">;
|
|
60
65
|
|
|
61
|
-
|
|
66
|
+
interface TelegramWorkspaceJournalWriterClosureFence {
|
|
62
67
|
destructiveKind: "journal-writer-closure";
|
|
63
68
|
phase: "fenced";
|
|
64
69
|
operationId: string;
|
|
@@ -87,7 +92,7 @@ export function normalizeTelegramWorkspaceJournalWriterClosureFence(
|
|
|
87
92
|
owner, requestedAtMs: value.requestedAtMs, acquiredAtMs: value.acquiredAtMs };
|
|
88
93
|
}
|
|
89
94
|
|
|
90
|
-
|
|
95
|
+
interface TelegramWorkspaceJournalWriterProtocolMode {
|
|
91
96
|
version: 1;
|
|
92
97
|
protocol: "custody-v3";
|
|
93
98
|
profileKey: string;
|
|
@@ -183,16 +188,11 @@ interface TelegramWorkspaceAdmissionState {
|
|
|
183
188
|
writerProtocolMode?: TelegramWorkspaceJournalWriterProtocolMode;
|
|
184
189
|
}
|
|
185
190
|
|
|
186
|
-
|
|
187
|
-
| "retirement-fenced"
|
|
188
|
-
| "admission-active"
|
|
189
|
-
| "retirement-active";
|
|
190
|
-
|
|
191
|
-
export type TelegramWorkspaceAdmissionAcquireResult =
|
|
191
|
+
type TelegramWorkspaceAdmissionAcquireResult =
|
|
192
192
|
| { kind: "acquired"; lease: TelegramWorkspaceAdmissionLease; resumed: boolean }
|
|
193
193
|
| { kind: "blocked"; reason: "retirement-fenced" };
|
|
194
194
|
|
|
195
|
-
|
|
195
|
+
type TelegramWorkspaceRetirementFenceAcquireResult =
|
|
196
196
|
| {
|
|
197
197
|
kind: "acquired";
|
|
198
198
|
fence: TelegramWorkspaceRetirementFence;
|
|
@@ -203,16 +203,16 @@ export type TelegramWorkspaceRetirementFenceAcquireResult =
|
|
|
203
203
|
reason: "admission-active" | "retirement-active";
|
|
204
204
|
};
|
|
205
205
|
|
|
206
|
-
|
|
206
|
+
type TelegramWorkspaceJournalWriterProtocolInstallResult = {
|
|
207
207
|
mode: TelegramWorkspaceJournalWriterProtocolMode;
|
|
208
208
|
resumed: boolean;
|
|
209
209
|
};
|
|
210
210
|
|
|
211
|
-
|
|
211
|
+
type TelegramWorkspaceJournalWriterClosureAcquireResult =
|
|
212
212
|
| { kind: "acquired"; fence: TelegramWorkspaceJournalWriterClosureFence; resumed: boolean }
|
|
213
213
|
| { kind: "blocked"; reason: "admission-active" | "retirement-active" };
|
|
214
214
|
|
|
215
|
-
|
|
215
|
+
type TelegramWorkspaceDeletionPermitResult =
|
|
216
216
|
| {
|
|
217
217
|
kind: "issued";
|
|
218
218
|
fence: TelegramWorkspaceRetirementFence;
|
|
@@ -220,19 +220,21 @@ export type TelegramWorkspaceDeletionPermitResult =
|
|
|
220
220
|
}
|
|
221
221
|
| { kind: "already-issued"; fence: TelegramWorkspaceRetirementFence };
|
|
222
222
|
|
|
223
|
-
|
|
223
|
+
interface TelegramWorkspaceAdmissionLedgerSnapshot {
|
|
224
224
|
profileKey: string;
|
|
225
225
|
leases: TelegramWorkspaceAdmissionLease[];
|
|
226
226
|
fence?: TelegramWorkspaceDestructiveFence;
|
|
227
227
|
writerProtocolMode?: TelegramWorkspaceJournalWriterProtocolMode;
|
|
228
228
|
}
|
|
229
229
|
|
|
230
|
-
|
|
230
|
+
type TelegramWorkspaceAdmissionPublicationBoundary =
|
|
231
231
|
| "before-write"
|
|
232
232
|
| "after-write-before-rename";
|
|
233
233
|
|
|
234
|
-
|
|
234
|
+
interface TelegramWorkspaceAdmissionLedgerOptions {
|
|
235
235
|
path: string;
|
|
236
|
+
/** Explicit logical section identity for the consolidated root; absent means the standalone ledger adapter. */
|
|
237
|
+
stateProfile?: string;
|
|
236
238
|
profileKey: string;
|
|
237
239
|
owner: TelegramWorkspaceAdmissionOwner;
|
|
238
240
|
getNowMs?: () => number;
|
|
@@ -356,14 +358,6 @@ function authorityChanged(message: string): never {
|
|
|
356
358
|
throw new TelegramWorkspaceAdmissionError("authority-changed", message);
|
|
357
359
|
}
|
|
358
360
|
|
|
359
|
-
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
360
|
-
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
function isSafeTimestamp(value: unknown): value is number {
|
|
364
|
-
return Number.isSafeInteger(value) && (value as number) >= 0;
|
|
365
|
-
}
|
|
366
|
-
|
|
367
361
|
function isBoundedText(value: unknown): value is string {
|
|
368
362
|
return (
|
|
369
363
|
typeof value === "string" &&
|
|
@@ -508,8 +502,9 @@ function normalizeFence(
|
|
|
508
502
|
if (value.phase === "deletion-rejected" &&
|
|
509
503
|
(value.destructiveKind === undefined || value.destructiveKind === "pressure-retirement") &&
|
|
510
504
|
isSafeTimestamp(value.deletionIssuedAtMs) && isSafeTimestamp(value.rejectionConfirmedAtMs)) {
|
|
511
|
-
|
|
512
|
-
|
|
505
|
+
const { destructiveKind: _kind, ...pressureBase } = base;
|
|
506
|
+
return { ...pressureBase, ...(value.destructiveKind === "pressure-retirement" ? { destructiveKind: "pressure-retirement" as const } : {}),
|
|
507
|
+
phase: "deletion-rejected", deletionIssuedAtMs: value.deletionIssuedAtMs, rejectionConfirmedAtMs: value.rejectionConfirmedAtMs };
|
|
513
508
|
}
|
|
514
509
|
if (
|
|
515
510
|
value.phase === "commit-ready" &&
|
|
@@ -587,9 +582,29 @@ function readState(
|
|
|
587
582
|
} catch (error) {
|
|
588
583
|
invalidState("Telegram Workspace admission state is malformed.", error);
|
|
589
584
|
}
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
585
|
+
return normalizeAdmissionState(parsed, profileKey);
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
function isConsolidatedAdmissionKey(value: unknown, profile: string): boolean {
|
|
589
|
+
if (!isBoundedText(value)) return false;
|
|
590
|
+
try {
|
|
591
|
+
const key: unknown = JSON.parse(value);
|
|
592
|
+
return isRecord(key) && Object.keys(key).length === 3 && key.version === TELEGRAM_WORKSPACE_ADMISSION_VERSION && key.profile === profile &&
|
|
593
|
+
isRecord(key.bot) && Object.keys(key.bot).length === 1 && typeof key.bot.tokenSha256 === "string" && /^[a-f0-9]{64}$/u.test(key.bot.tokenSha256);
|
|
594
|
+
} catch { return false; }
|
|
595
|
+
}
|
|
596
|
+
/** Pure damage check for one consolidated admission section under its own stored profile key; throws when invalid. */
|
|
597
|
+
export function assertTelegramConsolidatedAdmissionSection(value: unknown, stateProfile: string): void {
|
|
598
|
+
if (value === undefined) return;
|
|
599
|
+
normalizeAdmissionState(value, isRecord(value) && typeof value.profileKey === "string" ? value.profileKey : "", stateProfile);
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
function normalizeAdmissionState(parsed: unknown, profileKey: string, stateProfile?: string): TelegramWorkspaceAdmissionState {
|
|
603
|
+
if (parsed === undefined) return { version: TELEGRAM_WORKSPACE_ADMISSION_VERSION, profileKey, leases: [] };
|
|
604
|
+
if (!isRecord(parsed)) invalidState("Telegram Workspace admission state is invalid.");
|
|
605
|
+
if (stateProfile !== undefined && (Object.keys(parsed).some(key => !["version", "profileKey", "leases", "fence", "writerProtocolMode"].includes(key)) ||
|
|
606
|
+
!isConsolidatedAdmissionKey(parsed.profileKey, stateProfile)))
|
|
607
|
+
invalidState("Telegram consolidated admission section identity is invalid.");
|
|
593
608
|
if (
|
|
594
609
|
parsed.version !== TELEGRAM_WORKSPACE_ADMISSION_VERSION ||
|
|
595
610
|
!Array.isArray(parsed.leases) ||
|
|
@@ -627,6 +642,9 @@ function readState(
|
|
|
627
642
|
invalidState("Telegram Workspace writer protocol mode is invalid.");
|
|
628
643
|
if (fence && writerProtocolMode)
|
|
629
644
|
invalidState("Telegram Workspace destructive fence conflicts with writer protocol mode.");
|
|
645
|
+
if (stateProfile !== undefined && (!isDeepStrictEqual(parsed.leases, normalizedLeases) ||
|
|
646
|
+
!isDeepStrictEqual(parsed.fence, fence) || !isDeepStrictEqual(parsed.writerProtocolMode, writerProtocolMode)))
|
|
647
|
+
invalidState("Telegram consolidated admission evidence cannot drop unknown fields.");
|
|
630
648
|
return {
|
|
631
649
|
version: TELEGRAM_WORKSPACE_ADMISSION_VERSION,
|
|
632
650
|
profileKey,
|
|
@@ -679,13 +697,6 @@ function writeState(
|
|
|
679
697
|
}
|
|
680
698
|
}
|
|
681
699
|
|
|
682
|
-
function areTargetsEqual(
|
|
683
|
-
left: TelegramTarget & { threadId: number },
|
|
684
|
-
right: TelegramTarget & { threadId: number },
|
|
685
|
-
): boolean {
|
|
686
|
-
return left.chatId === right.chatId && left.threadId === right.threadId;
|
|
687
|
-
}
|
|
688
|
-
|
|
689
700
|
function scopesConflict(
|
|
690
701
|
scope: TelegramWorkspaceAdmissionScope,
|
|
691
702
|
target: TelegramTarget & { threadId: number },
|
|
@@ -810,6 +821,8 @@ function validateOptions(options: TelegramWorkspaceAdmissionLedgerOptions): void
|
|
|
810
821
|
if (!normalizeOwner(options.owner)) {
|
|
811
822
|
invalidInput("Telegram Workspace admission owner is invalid.");
|
|
812
823
|
}
|
|
824
|
+
if (options.stateProfile !== undefined && (!isBoundedText(options.stateProfile) || !isConsolidatedAdmissionKey(options.profileKey, options.stateProfile)))
|
|
825
|
+
invalidInput("Telegram consolidated admission requires an exact bot/profile section identity.");
|
|
813
826
|
}
|
|
814
827
|
|
|
815
828
|
function validateOperationId(operationId: string): void {
|
|
@@ -863,31 +876,34 @@ export function createTelegramWorkspaceAdmissionProfileKey(input: {
|
|
|
863
876
|
});
|
|
864
877
|
}
|
|
865
878
|
|
|
866
|
-
|
|
879
|
+
interface TelegramWorkspaceAdmissionRuntimeBinding {
|
|
867
880
|
resolve: () => TelegramWorkspaceAdmissionLedger | undefined;
|
|
868
881
|
}
|
|
869
882
|
|
|
870
883
|
export function createTelegramWorkspaceAdmissionRuntimeBinding(input: {
|
|
871
884
|
getProfileName: () => string | undefined;
|
|
872
885
|
getBotToken: () => string | undefined;
|
|
873
|
-
getPath: (profileName?: string) => string;
|
|
874
886
|
owner: TelegramWorkspaceAdmissionOwner;
|
|
875
887
|
getNowMs?: () => number;
|
|
876
888
|
getProcessLiveness?: (
|
|
877
889
|
owner: TelegramWorkspaceAdmissionOwner,
|
|
878
890
|
) => TelegramProcessLiveness;
|
|
879
|
-
}
|
|
891
|
+
} & ({ getPath: (profileName?: string) => string; getStatePath?: never } |
|
|
892
|
+
{ getStatePath: () => string; getPath?: never })): TelegramWorkspaceAdmissionRuntimeBinding {
|
|
893
|
+
const { getProfileName, getBotToken, getPath, getStatePath } = input;
|
|
894
|
+
if ((getPath === undefined) === (getStatePath === undefined)) invalidInput("Telegram admission binding must select one storage identity.");
|
|
880
895
|
return {
|
|
881
896
|
resolve() {
|
|
882
|
-
const botToken =
|
|
897
|
+
const botToken = getBotToken();
|
|
883
898
|
if (!botToken) return undefined;
|
|
884
|
-
const configuredProfileName =
|
|
899
|
+
const configuredProfileName = getProfileName();
|
|
885
900
|
const profileKey = createTelegramWorkspaceAdmissionProfileKey({
|
|
886
901
|
profileName: configuredProfileName ?? "default",
|
|
887
902
|
botToken,
|
|
888
903
|
});
|
|
889
904
|
return createTelegramWorkspaceAdmissionLedger({
|
|
890
|
-
path:
|
|
905
|
+
path: getStatePath ? getStatePath() : getPath!(configuredProfileName),
|
|
906
|
+
...(getStatePath ? { stateProfile: configuredProfileName ?? "default" } : {}),
|
|
891
907
|
profileKey,
|
|
892
908
|
owner: input.owner,
|
|
893
909
|
getNowMs: input.getNowMs,
|
|
@@ -1105,12 +1121,23 @@ export function createTelegramWorkspaceAdmissionLedger(
|
|
|
1105
1121
|
options: TelegramWorkspaceAdmissionLedgerOptions,
|
|
1106
1122
|
): TelegramWorkspaceAdmissionLedger {
|
|
1107
1123
|
validateOptions(options);
|
|
1124
|
+
const suppliedOptions = options;
|
|
1125
|
+
if (options.stateProfile !== undefined) options = { ...options, owner: { ...options.owner } };
|
|
1108
1126
|
const getNowMs = options.getNowMs ?? Date.now;
|
|
1109
1127
|
const getProcessLiveness =
|
|
1110
1128
|
options.getProcessLiveness ??
|
|
1111
1129
|
((owner: TelegramWorkspaceAdmissionOwner) =>
|
|
1112
1130
|
getTelegramProcessLiveness(owner));
|
|
1113
1131
|
const transactionPath = `${options.path}.transaction`;
|
|
1132
|
+
const path = options.path, profileKey = options.profileKey, stateProfile = options.stateProfile, boundOwner = { ...options.owner };
|
|
1133
|
+
const publication = { publishRename: options.publishRename, onPublicationBoundary: options.onPublicationBoundary };
|
|
1134
|
+
const isStorageCurrent = (): boolean => suppliedOptions.path === path && suppliedOptions.profileKey === profileKey &&
|
|
1135
|
+
suppliedOptions.stateProfile === stateProfile && areOwnersEqual(suppliedOptions.owner, boundOwner);
|
|
1136
|
+
const mapStateError = (error: unknown): never => {
|
|
1137
|
+
if (error instanceof TelegramRuntimeStateError) throw new TelegramWorkspaceAdmissionError(
|
|
1138
|
+
error.code === "invalid" ? "invalid-state" : error.code, "Telegram consolidated admission state operation failed.", { cause: error });
|
|
1139
|
+
throw error;
|
|
1140
|
+
};
|
|
1114
1141
|
|
|
1115
1142
|
function transact<T>(
|
|
1116
1143
|
operation: (state: TelegramWorkspaceAdmissionState) => {
|
|
@@ -1118,6 +1145,20 @@ export function createTelegramWorkspaceAdmissionLedger(
|
|
|
1118
1145
|
changed: boolean;
|
|
1119
1146
|
},
|
|
1120
1147
|
): T {
|
|
1148
|
+
if (stateProfile !== undefined) {
|
|
1149
|
+
try {
|
|
1150
|
+
return mutateTelegramRuntimeStateSection(path, stateProfile, "admission", current => {
|
|
1151
|
+
const state = normalizeAdmissionState(current, profileKey, stateProfile);
|
|
1152
|
+
const retained = state.leases.filter(lease => getProcessLiveness(lease.owner) !== "dead");
|
|
1153
|
+
const pruned = retained.length !== state.leases.length;
|
|
1154
|
+
state.leases = retained;
|
|
1155
|
+
const outcome = operation(state);
|
|
1156
|
+
return { value: pruned || outcome.changed ? normalizeAdmissionState(state, profileKey, stateProfile) : current, result: outcome.result };
|
|
1157
|
+
}, { isCurrent: isStorageCurrent, publishRename: publication.publishRename, onPublicationBoundary(boundary) {
|
|
1158
|
+
if (boundary !== "after-rename") publication.onPublicationBoundary?.(boundary, path);
|
|
1159
|
+
} });
|
|
1160
|
+
} catch (error) { return mapStateError(error); }
|
|
1161
|
+
}
|
|
1121
1162
|
return withTelegramFileTransaction(transactionPath, () => {
|
|
1122
1163
|
const state = readState(options.path, options.profileKey);
|
|
1123
1164
|
const retainedLeases = state.leases.filter(
|
|
@@ -1132,7 +1173,16 @@ export function createTelegramWorkspaceAdmissionLedger(
|
|
|
1132
1173
|
}
|
|
1133
1174
|
|
|
1134
1175
|
function read(): TelegramWorkspaceAdmissionLedgerSnapshot {
|
|
1135
|
-
|
|
1176
|
+
let state: TelegramWorkspaceAdmissionState;
|
|
1177
|
+
if (stateProfile !== undefined) {
|
|
1178
|
+
if (!isStorageCurrent()) authorityChanged("Telegram consolidated admission storage authority changed.");
|
|
1179
|
+
try {
|
|
1180
|
+
const file = readTelegramRuntimeState(path);
|
|
1181
|
+
const value = Object.hasOwn(file.profiles, stateProfile) ? file.profiles[stateProfile]?.admission : undefined;
|
|
1182
|
+
state = normalizeAdmissionState(value, profileKey, stateProfile);
|
|
1183
|
+
if (!isStorageCurrent()) authorityChanged("Telegram consolidated admission storage authority changed.");
|
|
1184
|
+
} catch (error) { return mapStateError(error); }
|
|
1185
|
+
} else state = readState(options.path, options.profileKey);
|
|
1136
1186
|
return {
|
|
1137
1187
|
profileKey: state.profileKey,
|
|
1138
1188
|
leases: state.leases.map(cloneLease),
|
|
@@ -1675,8 +1725,8 @@ export function createTelegramWorkspaceAdmissionLedger(
|
|
|
1675
1725
|
}
|
|
1676
1726
|
|
|
1677
1727
|
return {
|
|
1678
|
-
getProfileKey: () => options.profileKey,
|
|
1679
|
-
getOwner: () => ({ ...options.owner }),
|
|
1728
|
+
getProfileKey: () => stateProfile === undefined ? options.profileKey : profileKey,
|
|
1729
|
+
getOwner: () => ({ ...(stateProfile === undefined ? options.owner : boundOwner) }),
|
|
1680
1730
|
listReservedSlots: () => {
|
|
1681
1731
|
const fence = read().fence;
|
|
1682
1732
|
return fence && isTelegramWorkspaceRetirementFence(fence) ? [fence.slot] : [];
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Durable Workspace identity value construction
|
|
3
|
+
* Zones: workspace identity, session identity, key encoding
|
|
4
|
+
* Owns exact session/CWD normalization, bounded keys and local instance-slot encoding.
|
|
5
|
+
* Excludes allocation, bindings/targets, storage-reference approval, custody and effects.
|
|
6
|
+
*/
|
|
7
|
+
import { createHash } from "node:crypto";
|
|
8
|
+
import { posix, resolve } from "node:path";
|
|
9
|
+
|
|
10
|
+
export interface TelegramWorkspaceBindingIdentity {
|
|
11
|
+
cwd: string;
|
|
12
|
+
workspaceKey: string;
|
|
13
|
+
/** Exact durable Pi session identity; absent only on legacy cwd-only bindings. */
|
|
14
|
+
sessionId?: string;
|
|
15
|
+
/** Full SHA-256 index component for session-qualified bindings. */
|
|
16
|
+
sessionKey?: string;
|
|
17
|
+
/** Immutable legacy binding-key component, not the displayed global letter. */
|
|
18
|
+
instanceSlot: string;
|
|
19
|
+
bindingKey: string;
|
|
20
|
+
/** Profile-wide letter reserved by the transient claim. */
|
|
21
|
+
slot?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export const TELEGRAM_WORKSPACE_KEY_MAX_LENGTH = 180;
|
|
25
|
+
const TELEGRAM_SESSION_ID_MAX_LENGTH = 256;
|
|
26
|
+
|
|
27
|
+
export function normalizeTelegramSessionId(
|
|
28
|
+
sessionId: string,
|
|
29
|
+
): string | undefined {
|
|
30
|
+
if (typeof sessionId !== "string") return undefined;
|
|
31
|
+
const normalized = sessionId.trim();
|
|
32
|
+
return normalized && Buffer.byteLength(normalized, "utf8") <=
|
|
33
|
+
TELEGRAM_SESSION_ID_MAX_LENGTH
|
|
34
|
+
? normalized
|
|
35
|
+
: undefined;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function createTelegramSessionKey(
|
|
39
|
+
sessionId: string,
|
|
40
|
+
): string | undefined {
|
|
41
|
+
const normalized = normalizeTelegramSessionId(sessionId);
|
|
42
|
+
return normalized
|
|
43
|
+
? createHash("sha256").update(normalized).digest("hex")
|
|
44
|
+
: undefined;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function normalizeTelegramWorkspacePath(
|
|
48
|
+
cwd: string,
|
|
49
|
+
): string | undefined {
|
|
50
|
+
const trimmed = cwd.trim();
|
|
51
|
+
if (!trimmed) return undefined;
|
|
52
|
+
const normalized = (trimmed.startsWith("/")
|
|
53
|
+
? posix.normalize(trimmed)
|
|
54
|
+
: resolve(trimmed).replaceAll("\\", "/"));
|
|
55
|
+
const withoutTrailingSeparators =
|
|
56
|
+
normalized.length > 1 ? normalized.replace(/\/+$/u, "") : normalized;
|
|
57
|
+
return process.platform === "win32"
|
|
58
|
+
? withoutTrailingSeparators.replace(/^([A-Z]):/u, (_, drive: string) =>
|
|
59
|
+
`${drive.toLowerCase()}:`,
|
|
60
|
+
)
|
|
61
|
+
: withoutTrailingSeparators;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function createTelegramWorkspaceDirectoryKey(
|
|
65
|
+
cwd: string,
|
|
66
|
+
): string | undefined {
|
|
67
|
+
const normalized = normalizeTelegramWorkspacePath(cwd);
|
|
68
|
+
if (!normalized) return undefined;
|
|
69
|
+
const readable =
|
|
70
|
+
normalized
|
|
71
|
+
.replace(/[^\p{L}\p{N}._-]+/gu, "-")
|
|
72
|
+
.replace(/^-+|-+$/gu, "") || "root";
|
|
73
|
+
const candidate = `--${readable}--`;
|
|
74
|
+
if (candidate.length <= TELEGRAM_WORKSPACE_KEY_MAX_LENGTH) return candidate;
|
|
75
|
+
const digest = createHash("sha256")
|
|
76
|
+
.update(normalized)
|
|
77
|
+
.digest("hex")
|
|
78
|
+
.slice(0, 12);
|
|
79
|
+
const prefixLength =
|
|
80
|
+
TELEGRAM_WORKSPACE_KEY_MAX_LENGTH - digest.length - 5;
|
|
81
|
+
return `--${readable.slice(0, prefixLength)}-${digest}--`;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function createTelegramWorkspaceInstanceSlot(
|
|
85
|
+
ordinal: number,
|
|
86
|
+
): string | undefined {
|
|
87
|
+
if (!Number.isSafeInteger(ordinal) || ordinal < 0) return undefined;
|
|
88
|
+
let value = ordinal + 1;
|
|
89
|
+
let slot = "";
|
|
90
|
+
while (value > 0) {
|
|
91
|
+
value -= 1;
|
|
92
|
+
slot = String.fromCharCode(97 + (value % 26)) + slot;
|
|
93
|
+
value = Math.floor(value / 26);
|
|
94
|
+
}
|
|
95
|
+
return slot;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function createTelegramWorkspaceBindingIdentityWithKey(
|
|
99
|
+
cwd: string,
|
|
100
|
+
workspaceKey: string,
|
|
101
|
+
ordinal: number,
|
|
102
|
+
sessionId?: string,
|
|
103
|
+
): TelegramWorkspaceBindingIdentity | undefined {
|
|
104
|
+
const instanceSlot = createTelegramWorkspaceInstanceSlot(ordinal);
|
|
105
|
+
if (!instanceSlot) return undefined;
|
|
106
|
+
const normalizedSessionId = sessionId === undefined
|
|
107
|
+
? undefined
|
|
108
|
+
: normalizeTelegramSessionId(sessionId);
|
|
109
|
+
const sessionKey = normalizedSessionId
|
|
110
|
+
? createTelegramSessionKey(normalizedSessionId)
|
|
111
|
+
: undefined;
|
|
112
|
+
if (sessionId !== undefined && (!normalizedSessionId || !sessionKey)) {
|
|
113
|
+
return undefined;
|
|
114
|
+
}
|
|
115
|
+
const legacyBindingKey = instanceSlot === "a"
|
|
116
|
+
? workspaceKey
|
|
117
|
+
: `${workspaceKey}${instanceSlot}`;
|
|
118
|
+
return {
|
|
119
|
+
cwd,
|
|
120
|
+
workspaceKey,
|
|
121
|
+
...(normalizedSessionId && sessionKey
|
|
122
|
+
? { sessionId: normalizedSessionId, sessionKey }
|
|
123
|
+
: {}),
|
|
124
|
+
instanceSlot,
|
|
125
|
+
bindingKey: sessionKey
|
|
126
|
+
? `${legacyBindingKey}-s-${sessionKey}`
|
|
127
|
+
: legacyBindingKey,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export function createTelegramWorkspaceBindingIdentity(
|
|
132
|
+
cwd: string,
|
|
133
|
+
ordinal = 0,
|
|
134
|
+
sessionId?: string,
|
|
135
|
+
): TelegramWorkspaceBindingIdentity | undefined {
|
|
136
|
+
const normalized = normalizeTelegramWorkspacePath(cwd);
|
|
137
|
+
const workspaceKey = normalized
|
|
138
|
+
? createTelegramWorkspaceDirectoryKey(normalized)
|
|
139
|
+
: undefined;
|
|
140
|
+
if (!normalized || !workspaceKey) return undefined;
|
|
141
|
+
return createTelegramWorkspaceBindingIdentityWithKey(
|
|
142
|
+
normalized,
|
|
143
|
+
workspaceKey,
|
|
144
|
+
ordinal,
|
|
145
|
+
sessionId,
|
|
146
|
+
);
|
|
147
|
+
}
|