@llblab/pi-telegram 0.51.5 → 0.52.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.
Files changed (181) hide show
  1. package/AGENTS.md +10 -8
  2. package/BACKLOG.md +1 -8
  3. package/CHANGELOG.md +23 -0
  4. package/LICENSE +21 -0
  5. package/README.md +16 -6
  6. package/dist/lib/activity-verbosity.d.ts +1 -5
  7. package/dist/lib/activity-verbosity.js +5 -7
  8. package/dist/lib/bindings.d.ts +9 -2
  9. package/dist/lib/bindings.js +22 -13
  10. package/dist/lib/bus-api.js +4 -2
  11. package/dist/lib/bus-follower.d.ts +55 -57
  12. package/dist/lib/bus-follower.js +213 -140
  13. package/dist/lib/bus-leader.d.ts +21 -2
  14. package/dist/lib/bus-leader.js +181 -25
  15. package/dist/lib/bus-transport.d.ts +4 -1
  16. package/dist/lib/bus-transport.js +28 -4
  17. package/dist/lib/bus.d.ts +52 -56
  18. package/dist/lib/bus.js +78 -246
  19. package/dist/lib/channel-posts.d.ts +1 -3
  20. package/dist/lib/channel-posts.js +23 -26
  21. package/dist/lib/command-templates.d.ts +0 -5
  22. package/dist/lib/command-templates.js +5 -5
  23. package/dist/lib/commands.d.ts +12 -26
  24. package/dist/lib/commands.js +152 -98
  25. package/dist/lib/config.d.ts +0 -17
  26. package/dist/lib/config.js +12 -14
  27. package/dist/lib/delivery.d.ts +0 -4
  28. package/dist/lib/delivery.js +2 -5
  29. package/dist/lib/extension.js +281 -61
  30. package/dist/lib/generative-apps.d.ts +0 -19
  31. package/dist/lib/inbound.d.ts +0 -2
  32. package/dist/lib/inbound.js +2 -2
  33. package/dist/lib/journal.d.ts +177 -16
  34. package/dist/lib/journal.js +1115 -249
  35. package/dist/lib/keyboard.d.ts +0 -2
  36. package/dist/lib/keyboard.js +2 -2
  37. package/dist/lib/lifecycle.d.ts +57 -2
  38. package/dist/lib/lifecycle.js +110 -5
  39. package/dist/lib/locks.d.ts +100 -12
  40. package/dist/lib/locks.js +380 -14
  41. package/dist/lib/logging.d.ts +30 -12
  42. package/dist/lib/logging.js +129 -72
  43. package/dist/lib/media.d.ts +28 -5
  44. package/dist/lib/media.js +26 -8
  45. package/dist/lib/menu-model.d.ts +0 -11
  46. package/dist/lib/menu-model.js +8 -8
  47. package/dist/lib/menu-settings.d.ts +0 -18
  48. package/dist/lib/menu-settings.js +18 -18
  49. package/dist/lib/menu-status.d.ts +4 -4
  50. package/dist/lib/menu-status.js +12 -7
  51. package/dist/lib/menu-thinking.d.ts +1 -1
  52. package/dist/lib/menu-thinking.js +1 -1
  53. package/dist/lib/menu.d.ts +1 -0
  54. package/dist/lib/menu.js +4 -3
  55. package/dist/lib/model.js +2 -4
  56. package/dist/lib/outbound-attachments.d.ts +0 -1
  57. package/dist/lib/outbound-attachments.js +1 -1
  58. package/dist/lib/outbound-buttons.js +1 -8
  59. package/dist/lib/outbound-markup.d.ts +1 -6
  60. package/dist/lib/outbound-markup.js +4 -21
  61. package/dist/lib/outbound.d.ts +0 -8
  62. package/dist/lib/outbound.js +5 -5
  63. package/dist/lib/paths.d.ts +50 -7
  64. package/dist/lib/paths.js +155 -17
  65. package/dist/lib/pi.d.ts +1 -0
  66. package/dist/lib/pi.js +2 -1
  67. package/dist/lib/polling.d.ts +0 -5
  68. package/dist/lib/polling.js +5 -5
  69. package/dist/lib/preview.d.ts +0 -3
  70. package/dist/lib/preview.js +3 -3
  71. package/dist/lib/prompt-templates.d.ts +0 -1
  72. package/dist/lib/prompt-templates.js +1 -1
  73. package/dist/lib/queue.d.ts +3 -15
  74. package/dist/lib/queue.js +69 -8
  75. package/dist/lib/recovery.d.ts +29 -9
  76. package/dist/lib/recovery.js +104 -33
  77. package/dist/lib/replies.d.ts +0 -4
  78. package/dist/lib/replies.js +1 -1
  79. package/dist/lib/routing.d.ts +73 -8
  80. package/dist/lib/routing.js +2206 -307
  81. package/dist/lib/sections.d.ts +0 -2
  82. package/dist/lib/sections.js +1 -1
  83. package/dist/lib/status.d.ts +46 -9
  84. package/dist/lib/status.js +179 -27
  85. package/dist/lib/sync.d.ts +0 -5
  86. package/dist/lib/sync.js +9 -8
  87. package/dist/lib/telegram-api.d.ts +11 -3
  88. package/dist/lib/telegram-api.js +34 -23
  89. package/dist/lib/thread-cleanup-manager.d.ts +1 -0
  90. package/dist/lib/thread-cleanup-manager.js +18 -21
  91. package/dist/lib/thread-naming.d.ts +32 -5
  92. package/dist/lib/thread-naming.js +191 -7
  93. package/dist/lib/thread-reconciler.d.ts +2 -0
  94. package/dist/lib/thread-reconciler.js +1 -1
  95. package/dist/lib/threads.d.ts +292 -59
  96. package/dist/lib/threads.js +1664 -373
  97. package/dist/lib/turns.d.ts +0 -1
  98. package/dist/lib/turns.js +1 -1
  99. package/dist/lib/updates.d.ts +184 -28
  100. package/dist/lib/updates.js +1032 -114
  101. package/dist/lib/wire.d.ts +12 -0
  102. package/dist/lib/wire.js +21 -0
  103. package/dist/lib/workspace-admission.d.ts +25 -17
  104. package/dist/lib/workspace-admission.js +95 -20
  105. package/dist/lib/workspace-identity.d.ts +20 -0
  106. package/dist/lib/workspace-identity.js +103 -0
  107. package/dist/lib/workspace-retirement.d.ts +45 -10
  108. package/dist/lib/workspace-retirement.js +196 -64
  109. package/dist/package.json +6 -6
  110. package/dist/skills/generated-control-surface/SKILL.md +3 -3
  111. package/dist/skills/telegram-bridge/references/diagnosis.md +3 -3
  112. package/docs/README.md +1 -1
  113. package/docs/activity.md +1 -1
  114. package/docs/architecture.md +158 -39
  115. package/docs/callback-namespaces.md +1 -1
  116. package/docs/delivery.md +1 -1
  117. package/docs/generative-apps.md +2 -2
  118. package/docs/inbound.md +1 -1
  119. package/docs/multi-instance-bus.md +127 -19
  120. package/docs/public-api.md +5 -3
  121. package/docs/ui-style.md +26 -8
  122. package/docs/updates.md +1 -1
  123. package/lib/activity-verbosity.ts +5 -9
  124. package/lib/bindings.ts +23 -10
  125. package/lib/bus-api.ts +5 -2
  126. package/lib/bus-follower.ts +251 -226
  127. package/lib/bus-leader.ts +178 -31
  128. package/lib/bus-transport.ts +30 -8
  129. package/lib/bus.ts +110 -334
  130. package/lib/channel-posts.ts +29 -29
  131. package/lib/command-templates.ts +5 -5
  132. package/lib/commands.ts +150 -112
  133. package/lib/config.ts +12 -17
  134. package/lib/delivery.ts +2 -8
  135. package/lib/extension.ts +282 -72
  136. package/lib/generative-apps.ts +0 -22
  137. package/lib/inbound.ts +2 -2
  138. package/lib/journal.ts +1182 -326
  139. package/lib/keyboard.ts +2 -2
  140. package/lib/lifecycle.ts +141 -5
  141. package/lib/locks.ts +402 -23
  142. package/lib/logging.ts +151 -101
  143. package/lib/media.ts +51 -9
  144. package/lib/menu-model.ts +8 -8
  145. package/lib/menu-settings.ts +18 -18
  146. package/lib/menu-status.ts +11 -0
  147. package/lib/menu-thinking.ts +2 -2
  148. package/lib/menu.ts +5 -1
  149. package/lib/model.ts +2 -4
  150. package/lib/outbound-attachments.ts +1 -1
  151. package/lib/outbound-buttons.ts +1 -10
  152. package/lib/outbound-markup.ts +5 -24
  153. package/lib/outbound.ts +5 -5
  154. package/lib/paths.ts +177 -17
  155. package/lib/pi.ts +5 -2
  156. package/lib/polling.ts +5 -5
  157. package/lib/preview.ts +3 -3
  158. package/lib/prompt-templates.ts +1 -1
  159. package/lib/queue.ts +67 -9
  160. package/lib/recovery.ts +104 -44
  161. package/lib/replies.ts +1 -1
  162. package/lib/routing.ts +1955 -375
  163. package/lib/sections.ts +1 -1
  164. package/lib/status.ts +185 -36
  165. package/lib/sync.ts +8 -8
  166. package/lib/telegram-api.ts +48 -33
  167. package/lib/thread-cleanup-manager.ts +24 -21
  168. package/lib/thread-naming.ts +267 -9
  169. package/lib/thread-reconciler.ts +3 -1
  170. package/lib/threads.ts +1718 -491
  171. package/lib/turns.ts +1 -1
  172. package/lib/updates.ts +1072 -151
  173. package/lib/wire.ts +28 -0
  174. package/lib/workspace-admission.ts +97 -47
  175. package/lib/workspace-identity.ts +147 -0
  176. package/lib/workspace-retirement.ts +234 -102
  177. package/package.json +6 -6
  178. package/scripts/audit-exports.mjs +100 -0
  179. package/scripts/check-downgrade.mjs +80 -43
  180. package/skills/generated-control-surface/SKILL.md +3 -3
  181. package/skills/telegram-bridge/references/diagnosis.md +3 -3
@@ -17,7 +17,5 @@ export type TelegramInlineKeyboardButton = {
17
17
  export interface TelegramInlineKeyboardMarkup {
18
18
  inline_keyboard: TelegramInlineKeyboardButton[][];
19
19
  }
20
- export declare const TELEGRAM_CALLBACK_DATA_MAX_BYTES = 64;
21
- export declare function getTelegramCallbackDataByteLength(value: string): number;
22
20
  export declare function assertTelegramCallbackData(callbackData: string, context?: string): string;
23
21
  export declare function assertTelegramInlineKeyboardCallbackData(replyMarkup: unknown, context?: string): void;
@@ -3,8 +3,8 @@
3
3
  * Zones: telegram ui, shared structure
4
4
  * Owns the shared Bot API reply-markup shape while feature domains own their button semantics
5
5
  */
6
- export const TELEGRAM_CALLBACK_DATA_MAX_BYTES = 64;
7
- export function getTelegramCallbackDataByteLength(value) {
6
+ const TELEGRAM_CALLBACK_DATA_MAX_BYTES = 64;
7
+ function getTelegramCallbackDataByteLength(value) {
8
8
  return new TextEncoder().encode(value).byteLength;
9
9
  }
10
10
  export function assertTelegramCallbackData(callbackData, context = "Telegram callback_data") {
@@ -1,7 +1,8 @@
1
1
  /**
2
- * Telegram lifecycle hook registration helpers
2
+ * Telegram session lifecycle coordination and hook registration
3
3
  * Zones: pi agent lifecycle, telegram session
4
- * Binds prepared Telegram lifecycle runtimes to pi extension lifecycle events
4
+ * Owns context generations, bounded connect intent across resume, and session sequencing.
5
+ * Transport authority, durable bindings, and queue custody remain with their owners.
5
6
  */
6
7
  import * as BusFollower from "./bus-follower.ts";
7
8
  import * as Queue from "./queue.ts";
@@ -50,6 +51,58 @@ export interface TelegramSessionLifecycleHooks {
50
51
  onSessionStart: (event: SessionStartEvent, ctx: ExtensionContext) => Promise<void>;
51
52
  onSessionShutdown: (event: SessionShutdownEvent, ctx: ExtensionContext) => Promise<void>;
52
53
  }
54
+ export interface TelegramConnectionIntent {
55
+ id: string;
56
+ cwd: string;
57
+ profileName?: string;
58
+ }
59
+ interface TelegramConnectionHandoff extends TelegramConnectionIntent {
60
+ pid: number;
61
+ targetSessionFile: string;
62
+ expiresAtMs: number;
63
+ }
64
+ export interface TelegramConnectionHandoffStore {
65
+ pending?: TelegramConnectionHandoff;
66
+ }
67
+ export interface TelegramConnectionLifecycle {
68
+ prepare(event: SessionStartEvent, ctx: ExtensionContext): (() => void) | undefined;
69
+ onSessionShutdown(event: SessionShutdownEvent, ctx: ExtensionContext): void;
70
+ }
71
+ export declare function createTelegramConnectionLifecycle(deps: {
72
+ intent: ReturnType<typeof createTelegramConnectionIntentRuntime>;
73
+ getGeneration(): number;
74
+ isCurrent(ctx: ExtensionContext): boolean;
75
+ getProfileName(): string | undefined;
76
+ isConnected(): boolean;
77
+ activateProfile(profileName: string | undefined, isCurrent: () => boolean): Promise<boolean>;
78
+ start(ctx: ExtensionContext): Promise<{
79
+ ok: boolean;
80
+ message?: string;
81
+ }>;
82
+ recordError(error: unknown): void;
83
+ }): TelegramConnectionLifecycle;
84
+ export declare function createTelegramConnectionIntentRuntime(options?: {
85
+ store?: TelegramConnectionHandoffStore;
86
+ now?: () => number;
87
+ pid?: number;
88
+ }): {
89
+ begin(cwd: string, profileName?: string): string;
90
+ isActive(id: string): boolean;
91
+ finish(id: string): void;
92
+ cancel(): void;
93
+ suspend(input: {
94
+ reason: string;
95
+ cwd: string;
96
+ targetSessionFile?: string;
97
+ connected: boolean;
98
+ profileName?: string;
99
+ }): void;
100
+ resume(input: {
101
+ reason: string;
102
+ cwd: string;
103
+ sessionFile?: string;
104
+ }): TelegramConnectionIntent | undefined;
105
+ };
53
106
  export interface TelegramSessionContextStore<TContext> {
54
107
  get: () => TContext | undefined;
55
108
  getGeneration: () => number;
@@ -62,6 +115,7 @@ export declare function createTelegramSessionContextStore<TContext>(options?: {
62
115
  }): TelegramSessionContextStore<TContext>;
63
116
  export declare function createTelegramSessionGenerationFence(store: TelegramSessionContextStore<ExtensionContext>, hooks: TelegramSessionLifecycleHooks): TelegramSessionLifecycleHooks;
64
117
  export interface TelegramBridgeSessionServiceRuntime {
118
+ connection?: TelegramConnectionLifecycle;
65
119
  resumeGroupedInput(ctx: ExtensionContext): void;
66
120
  suspendGroupedInput(): void;
67
121
  delivery: {
@@ -108,6 +162,7 @@ export interface TelegramBridgeSessionLifecyclePorts<TQueueItem, TModel = unknow
108
162
  };
109
163
  delivery: TelegramBridgeSessionServiceRuntime["delivery"];
110
164
  polling: TelegramBridgeSessionServiceRuntime["polling"];
165
+ connection?: TelegramBridgeSessionServiceRuntime["connection"];
111
166
  inboundWorker: TelegramBridgeSessionServiceRuntime["inboundWorker"];
112
167
  capabilityMonitor: TelegramBridgeSessionServiceRuntime["capabilityMonitor"];
113
168
  queueWatchdog: TelegramBridgeSessionServiceRuntime["queueWatchdog"];
@@ -1,8 +1,11 @@
1
1
  /**
2
- * Telegram lifecycle hook registration helpers
2
+ * Telegram session lifecycle coordination and hook registration
3
3
  * Zones: pi agent lifecycle, telegram session
4
- * Binds prepared Telegram lifecycle runtimes to pi extension lifecycle events
4
+ * Owns context generations, bounded connect intent across resume, and session sequencing.
5
+ * Transport authority, durable bindings, and queue custody remain with their owners.
5
6
  */
7
+ import { randomUUID } from "node:crypto";
8
+ import { formatTelegramConnectionFailure } from "./status.js";
6
9
  import * as BusFollower from "./bus-follower.js";
7
10
  import * as Queue from "./queue.js";
8
11
  import * as TextGroups from "./text-groups.js";
@@ -23,6 +26,98 @@ export function createAgentStartDedupHook(inner, schedulePublication) {
23
26
  return inner(event, ctx);
24
27
  };
25
28
  }
29
+ const HANDOFF_KEY = Symbol.for("pi-telegram.connection-resume.v1");
30
+ function processHandoffStore() {
31
+ const globals = globalThis;
32
+ return globals[HANDOFF_KEY] ??= {};
33
+ }
34
+ export function createTelegramConnectionLifecycle(deps) {
35
+ return {
36
+ onSessionShutdown(event, ctx) {
37
+ deps.intent.suspend({ reason: event.reason, cwd: ctx.cwd,
38
+ targetSessionFile: event.targetSessionFile, connected: deps.isConnected(),
39
+ profileName: deps.getProfileName() });
40
+ },
41
+ prepare(event, ctx) {
42
+ const intent = deps.intent.resume({ reason: event.reason, cwd: ctx.cwd,
43
+ sessionFile: event.reason === "resume" ? ctx.sessionManager.getSessionFile() : undefined });
44
+ if (!intent)
45
+ return undefined;
46
+ const generation = deps.getGeneration();
47
+ const isCurrent = () => deps.intent.isActive(intent.id) &&
48
+ generation === deps.getGeneration() && deps.isCurrent(ctx);
49
+ return () => {
50
+ // Startup remains extension-owned background work; no captured command ctx crosses resume.
51
+ void (async () => {
52
+ if (!isCurrent())
53
+ return;
54
+ if (!await deps.activateProfile(intent.profileName, isCurrent)) {
55
+ if (isCurrent())
56
+ ctx.ui.notify("Telegram profile unavailable. Run /telegram-setup.", "warning");
57
+ return;
58
+ }
59
+ if (!isCurrent())
60
+ return;
61
+ const result = await deps.start(ctx);
62
+ if (!isCurrent())
63
+ return;
64
+ if (!result.ok) {
65
+ deps.recordError(new Error(result.message ?? "Telegram resume connection failed."));
66
+ ctx.ui.notify(formatTelegramConnectionFailure(result.message), "warning");
67
+ }
68
+ })().catch((error) => {
69
+ deps.recordError(error);
70
+ if (isCurrent())
71
+ ctx.ui.notify(formatTelegramConnectionFailure(error), "warning");
72
+ }).finally(() => deps.intent.finish(intent.id));
73
+ };
74
+ },
75
+ };
76
+ }
77
+ export function createTelegramConnectionIntentRuntime(options = {}) {
78
+ const store = options.store ?? processHandoffStore();
79
+ const now = options.now ?? Date.now;
80
+ const pid = options.pid ?? process.pid;
81
+ let active;
82
+ return {
83
+ begin(cwd, profileName) {
84
+ store.pending = undefined;
85
+ active = { id: randomUUID(), cwd, profileName };
86
+ return active.id;
87
+ },
88
+ isActive(id) {
89
+ return active?.id === id;
90
+ },
91
+ finish(id) {
92
+ if (active?.id === id)
93
+ active = undefined;
94
+ },
95
+ cancel() {
96
+ active = undefined;
97
+ store.pending = undefined;
98
+ },
99
+ suspend(input) {
100
+ const intent = active ?? (input.connected
101
+ ? { id: randomUUID(), cwd: input.cwd, profileName: input.profileName }
102
+ : undefined);
103
+ active = undefined;
104
+ store.pending = input.reason === "resume" && input.targetSessionFile &&
105
+ intent?.cwd === input.cwd
106
+ ? { ...intent, pid, targetSessionFile: input.targetSessionFile, expiresAtMs: now() + 30_000 }
107
+ : undefined;
108
+ },
109
+ resume(input) {
110
+ const handoff = store.pending;
111
+ store.pending = undefined;
112
+ if (!handoff || handoff.pid !== pid || now() >= handoff.expiresAtMs ||
113
+ input.reason !== "resume" || input.cwd !== handoff.cwd ||
114
+ input.sessionFile !== handoff.targetSessionFile)
115
+ return undefined;
116
+ active = { id: randomUUID(), cwd: handoff.cwd, profileName: handoff.profileName };
117
+ return { ...active };
118
+ },
119
+ };
120
+ }
26
121
  export function createTelegramSessionContextStore(options = {}) {
27
122
  let currentContext;
28
123
  let currentIdentity;
@@ -112,6 +207,7 @@ export function createTelegramBridgeSessionLifecycleDeps(ports) {
112
207
  }),
113
208
  delivery: ports.services.delivery,
114
209
  polling: ports.services.polling,
210
+ connection: ports.services.connection,
115
211
  inboundWorker: ports.services.inboundWorker,
116
212
  capabilityMonitor: ports.services.capabilityMonitor,
117
213
  queueWatchdog: ports.services.queueWatchdog,
@@ -128,20 +224,27 @@ export function createTelegramBridgeSessionLifecycleAssembly(deps) {
128
224
  suspendPolling: deps.follower.suspendPolling,
129
225
  recordRuntimeEvent: deps.follower.recordRuntimeEvent,
130
226
  });
227
+ let preserveTarget = true;
131
228
  const queueLifecycle = Queue.createTelegramSessionLifecycleRuntime({
132
229
  ...deps.queue,
133
230
  isSessionActive,
134
- stopPolling: suspendForReplacement,
231
+ stopPolling: () => suspendForReplacement(preserveTarget),
135
232
  clearPendingMediaGroups: deps.services.suspendGroupedInput,
136
233
  });
137
234
  const servicesLifecycle = {
138
235
  async onSessionStart(event, ctx) {
236
+ const resumeConnection = deps.services.connection?.prepare(event, ctx);
139
237
  await queueLifecycle.onSessionStart(event, ctx);
140
238
  if (!isSessionActive(ctx))
141
239
  return;
142
240
  deps.services.resumeGroupedInput(ctx);
143
241
  await deps.services.delivery.onSessionStart();
144
- await deps.services.polling.onSessionStart(event, ctx);
242
+ if (!isSessionActive(ctx))
243
+ return;
244
+ if (resumeConnection)
245
+ resumeConnection();
246
+ else
247
+ await deps.services.polling.onSessionStart(event, ctx);
145
248
  deps.services.capabilityMonitor.start(ctx);
146
249
  deps.services.queueWatchdog.start(ctx);
147
250
  },
@@ -150,6 +253,8 @@ export function createTelegramBridgeSessionLifecycleAssembly(deps) {
150
253
  const isCurrent = () => deps.contextStore.isCurrent(ctx, generation);
151
254
  if (!isCurrent())
152
255
  return;
256
+ deps.services.connection?.onSessionShutdown(event, ctx);
257
+ preserveTarget = event.reason !== "resume";
153
258
  let preserveThread;
154
259
  if (event.reason === "quit") {
155
260
  try {
@@ -238,7 +343,7 @@ export function createTelegramCompactionObserverRuntime(deps) {
238
343
  typingStartedByObserver = false;
239
344
  deps.updateStatus(ctx);
240
345
  deps.recordRuntimeEvent?.("compact", new Error("Compaction observer timed out"));
241
- deps.onCompactionAbandoned?.();
346
+ // Observer expiry releases local presence, not Pi's eventual terminal result.
242
347
  requestDispatch();
243
348
  }, timeoutMs);
244
349
  unrefTelegramLifecycleTimer(fallbackTimer);
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Telegram singleton lock helpers
3
3
  * Zones: telegram ownership, filesystem, transport authority
4
- * Owns extension-local owners.json access and Telegram bridge ownership semantics
4
+ * Owns filesystem authority, atomic runtime-section publication and Telegram bridge ownership semantics
5
5
  */
6
6
  import { renameSync } from "node:fs";
7
7
  export declare const TELEGRAM_LOCK_KEY = "default";
@@ -14,10 +14,21 @@ export declare const TELEGRAM_OWNERSHIP_REFRESH_MS = 2000;
14
14
  * Named profile → the validated profile name
15
15
  */
16
16
  export declare function resolveTelegramLockKey(activeProfile?: string): string;
17
- export interface TelegramActiveProfileGetter {
17
+ interface TelegramActiveProfileGetter {
18
18
  getActiveProfileName: () => string | undefined;
19
19
  }
20
20
  export declare function createTelegramLockKeyResolver(activeProfile: TelegramActiveProfileGetter): () => string;
21
+ /** Structural session view; Locks never imports the lifecycle owner. */
22
+ interface TelegramOwnedStateSessionPort<TContext extends TelegramLockContext> {
23
+ get: () => TContext | undefined;
24
+ getGeneration: () => number;
25
+ isCurrent: (ctx: TContext, generation?: number) => boolean;
26
+ }
27
+ /**
28
+ * Captures exact context, session generation and owned leader epoch before awaits.
29
+ * Session replacement, release or re-election revokes the grant; a successor never renews it.
30
+ */
31
+ export declare function createTelegramOwnedStateAuthorityCapture<TContext extends TelegramLockContext>(lock: Pick<TelegramLockRuntime<TContext>, "owns" | "getOwnedLeaderEpoch">, session: TelegramOwnedStateSessionPort<TContext>): () => (() => boolean) | undefined;
21
32
  export interface TelegramLockEntry {
22
33
  pid: number;
23
34
  cwd?: string;
@@ -27,6 +38,8 @@ export interface TelegramLockEntry {
27
38
  runtimeGeneration?: number;
28
39
  busSocketPath?: string;
29
40
  busSecret?: string;
41
+ /** Polling journal custody; inherited by every successor and kept after release. */
42
+ journalPath?: string;
30
43
  }
31
44
  export interface TelegramLockContext {
32
45
  cwd: string;
@@ -43,12 +56,12 @@ export type TelegramLockState = {
43
56
  kind: "stale";
44
57
  lock: TelegramLockEntry;
45
58
  };
46
- export interface TelegramLockAcquireOptions {
59
+ interface TelegramLockAcquireOptions {
47
60
  force?: boolean;
48
61
  expectedOwner?: TelegramLockEntry;
49
62
  election?: boolean;
50
63
  }
51
- export type TelegramLockAcquireResult = {
64
+ type TelegramLockAcquireResult = {
52
65
  ok: true;
53
66
  lock: TelegramLockEntry;
54
67
  replacedStale: boolean;
@@ -56,6 +69,12 @@ export type TelegramLockAcquireResult = {
56
69
  ok: false;
57
70
  lock: TelegramLockEntry;
58
71
  };
72
+ export type TelegramOwnedStatePublicationResult<T> = {
73
+ committed: false;
74
+ } | {
75
+ committed: true;
76
+ result: T;
77
+ };
59
78
  export interface TelegramLockRuntime<TContext extends TelegramLockContext> {
60
79
  acquire: (ctx: TContext, options?: TelegramLockAcquireOptions) => TelegramLockAcquireResult;
61
80
  release: () => TelegramLockState;
@@ -64,17 +83,26 @@ export interface TelegramLockRuntime<TContext extends TelegramLockContext> {
64
83
  getOwnedLeaderEpoch: () => number | string | undefined;
65
84
  owns: (ctx?: TelegramLockContext) => boolean;
66
85
  commitIfOwned: (commit: () => void) => boolean;
86
+ /** Consolidated-store publication; domain reducers retain their own exact payload/CAS rules. */
87
+ publishStateSectionIfOwned?: <T>(section: "workspace" | "runtime", mutate: (current: unknown, observed: Readonly<TelegramRuntimeStateProfile>) => TelegramRuntimeStateMutation<T>, options: TelegramOwnedStatePublicationOptions) => TelegramOwnedStatePublicationResult<T>;
67
88
  refresh: (ctx?: TelegramLockContext) => boolean;
89
+ /** The polling journal named by owners.json for this key, owned or not. */
90
+ getJournalPath: () => string | undefined;
68
91
  }
69
- export interface TelegramLockOwnershipGuard<TContext extends TelegramLockContext> {
92
+ interface TelegramLockOwnershipGuard<TContext extends TelegramLockContext> {
70
93
  ownsContext: (ctx: TContext) => boolean;
71
94
  }
72
- export interface TelegramLockContextStore<TContext extends TelegramLockContext> {
95
+ interface TelegramLockContextStore<TContext extends TelegramLockContext> {
73
96
  get: () => TContext | undefined;
74
97
  }
75
- export interface TelegramLockRuntimeOptions {
98
+ interface TelegramLockRuntimeOptions {
76
99
  key?: string | (() => string | undefined);
77
100
  locksPath?: string;
101
+ /** Consolidated version-2 state envelope; exclusive with `locksPath`. */
102
+ statePath?: string;
103
+ statePublication?: Pick<TelegramRuntimeStatePublicationOptions, "onPublicationBoundary" | "publishRename">;
104
+ /** Read-only ownership file of releases that used another directory; a live fresh owner there blocks acquisition. */
105
+ legacyLocksPath?: string;
78
106
  pid?: number;
79
107
  isProcessAlive?: (pid: number) => boolean;
80
108
  instanceId?: string;
@@ -84,9 +112,25 @@ export interface TelegramLockRuntimeOptions {
84
112
  mintLeaderEpoch?: () => number | string;
85
113
  runtimeGeneration?: number;
86
114
  staleHeartbeatMs?: number;
115
+ /** First leader without an inherited pointer names the polling journal it creates. */
116
+ createJournalPath?: (ctx: TelegramLockContext) => string | undefined;
87
117
  }
118
+ /**
119
+ * Leader polling journal: the path owners.json names, else the session that would host it on acquisition.
120
+ * Without a session identity, the flat root `inbox` remains the compatibility fallback.
121
+ */
122
+ export declare function createTelegramLeaderJournalPathResolver(deps: {
123
+ getNamedJournalPath: () => string | undefined;
124
+ getSessionId: () => string | undefined;
125
+ getProfileName: () => string | undefined;
126
+ }): {
127
+ createJournalPath: () => string | undefined;
128
+ resolve(profileName?: string): string;
129
+ };
130
+ /** A released key keeps only `{ journalPath }`: no owner, but the successor's polling custody. */
131
+ export declare function readTelegramLockJournalPath(value: unknown): string | undefined;
88
132
  export declare function readLocks(path?: string): Record<string, unknown>;
89
- export interface TelegramRenameRetryOptions {
133
+ interface TelegramRenameRetryOptions {
90
134
  rename?: typeof renameSync;
91
135
  attempts?: number;
92
136
  retryDelayMs?: number;
@@ -100,17 +144,56 @@ export interface TelegramFileTransactionOptions {
100
144
  retryDelayMs?: number;
101
145
  }
102
146
  export declare function withTelegramFileTransaction<T>(transactionPath: string, operation: () => T, options?: TelegramFileTransactionOptions): T;
147
+ export type TelegramRuntimeStateSection = "transport" | "workspace" | "admission" | "runtime";
148
+ type TelegramRuntimeStateProfile = Partial<Record<TelegramRuntimeStateSection, unknown>>;
149
+ interface TelegramRuntimeStateFile {
150
+ version: 2;
151
+ profiles: Record<string, TelegramRuntimeStateProfile>;
152
+ }
153
+ export declare class TelegramRuntimeStateError extends Error {
154
+ readonly code: "invalid" | "authority-changed" | "publication-unknown";
155
+ constructor(code: TelegramRuntimeStateError["code"], message: string, options?: ErrorOptions);
156
+ }
157
+ /** Strict, observational envelope read; section owners validate their own payloads. Legacy state is never adopted here. */
158
+ export declare function readTelegramRuntimeState(path: string): TelegramRuntimeStateFile;
159
+ /**
160
+ * Operator-approved optimistic recovery before leader acquisition: when the shared envelope, any transport section or a
161
+ * caller-validated section is damaged, publish a fresh empty envelope instead of refusing. All profiles lose runtime
162
+ * continuity. Filesystem access errors are not damage and still throw. Returns whether a reset was published.
163
+ */
164
+ export declare function resetDamagedTelegramRuntimeState(path: string, validateProfile?: (profile: string, sections: Readonly<TelegramRuntimeStateProfile>) => void): boolean;
165
+ export interface TelegramRuntimeStateMutation<T> {
166
+ value: unknown;
167
+ result: T;
168
+ }
169
+ interface TelegramRuntimeStatePublicationOptions {
170
+ /** Exact domain authority, recaptured by the caller before invoking this synchronous transaction. */
171
+ isCurrent: () => boolean;
172
+ onPublicationBoundary?: (boundary: "before-write" | "after-write-before-rename" | "after-rename") => void;
173
+ publishRename?: typeof renameSync;
174
+ }
175
+ export interface TelegramOwnedStatePublicationOptions extends TelegramRuntimeStatePublicationOptions {
176
+ /** Optional caller-bound physical/logical identity; a mismatch cannot redirect an owner grant. */
177
+ expectedScope?: {
178
+ path: string;
179
+ profile: string;
180
+ };
181
+ }
182
+ /**
183
+ * One physical read/check/write transaction for one named section. The caller supplies domain policy; copies expose
184
+ * current sibling facts without granting writes to them. No await, nested transaction, repair or legacy import.
185
+ */
186
+ export declare function mutateTelegramRuntimeStateSection<T>(path: string, profile: string, section: TelegramRuntimeStateSection, mutate: (current: unknown, observed: Readonly<TelegramRuntimeStateProfile>) => TelegramRuntimeStateMutation<T>, options: TelegramRuntimeStatePublicationOptions): T;
103
187
  export declare function writeLocks(path: string, locks: Record<string, unknown>): void;
104
188
  export declare function parseTelegramLockEntry(value: unknown): TelegramLockEntry | undefined;
105
189
  export declare function isProcessAlive(pid: number): boolean;
106
- export declare function formatTelegramLockEntry(lock: TelegramLockEntry): string;
107
190
  export declare function createTelegramLockRuntime<TContext extends TelegramLockContext>(options?: TelegramLockRuntimeOptions): TelegramLockRuntime<TContext>;
108
191
  export declare function createTelegramLockOwnershipGuard<TContext extends TelegramLockContext>(lock: TelegramLockRuntime<TContext>): TelegramLockOwnershipGuard<TContext>;
109
192
  export declare function createTelegramDirectDeliveryOwnershipChecker<TContext extends TelegramLockContext>(deps: {
110
193
  lock: TelegramLockRuntime<TContext>;
111
194
  contextStore: TelegramLockContextStore<TContext>;
112
195
  }): () => boolean;
113
- export interface TelegramLockedPollingStartOptions {
196
+ interface TelegramLockedPollingStartOptions {
114
197
  force?: boolean;
115
198
  forceFreshLeaderThread?: boolean;
116
199
  requestedThreadName?: string;
@@ -119,7 +202,7 @@ export interface TelegramLockedPollingStartOptions {
119
202
  };
120
203
  onAcquired?: () => Promise<void> | void;
121
204
  }
122
- export type TelegramLockedPollingStartResult = {
205
+ type TelegramLockedPollingStartResult = {
123
206
  ok: true;
124
207
  message?: string;
125
208
  canTakeover?: false;
@@ -134,14 +217,18 @@ export interface TelegramLockedPollingRuntime<TContext extends TelegramLockConte
134
217
  stop: () => Promise<string>;
135
218
  suspend: () => Promise<void>;
136
219
  isSuspended: () => boolean;
220
+ /** Fence one owned polling generation; suspension, restart, conflict or lock loss revokes it. */
221
+ captureTransportAuthority: (ctx: TContext) => (() => boolean) | undefined;
137
222
  onPersistentConflict: (ctx: TContext, count: number) => Promise<void>;
138
223
  onSessionStart: (_event: unknown, ctx: TContext) => Promise<void>;
139
224
  registerFollowerWithOwner?: (ctx: TContext, owner: TelegramLockEntry) => boolean | undefined | Promise<boolean | undefined>;
140
225
  restoreFollowerWithOwner?: (ctx: TContext, owner: TelegramLockEntry) => boolean | undefined | Promise<boolean | undefined>;
141
226
  stopFollowerRegistration?: () => void;
142
227
  }
143
- export interface TelegramLockedPollingRuntimeDeps<TContext extends TelegramLockContext> {
228
+ interface TelegramLockedPollingRuntimeDeps<TContext extends TelegramLockContext> {
144
229
  lock: TelegramLockRuntime<TContext>;
230
+ /** Optimistically replaces a damaged shared runtime state before a non-election acquisition. */
231
+ resetDamagedState?: () => boolean;
145
232
  hasBotToken: () => boolean;
146
233
  getBotTokenDiagnostic?: () => string | undefined;
147
234
  canStartPolling?: (ctx: TContext) => boolean;
@@ -163,3 +250,4 @@ export interface TelegramLockedPollingRuntimeDeps<TContext extends TelegramLockC
163
250
  ownershipRefreshMs?: number;
164
251
  }
165
252
  export declare function createTelegramLockedPollingRuntime<TContext extends TelegramLockContext>(deps: TelegramLockedPollingRuntimeDeps<TContext>): TelegramLockedPollingRuntime<TContext>;
253
+ export {};