@llblab/pi-kit 0.27.5 → 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.
Files changed (194) hide show
  1. package/BACKLOG.md +3 -1
  2. package/CHANGELOG.md +9 -0
  3. package/README.md +4 -4
  4. package/node_modules/@llblab/pi-state-flow/AGENTS.md +1 -1
  5. package/node_modules/@llblab/pi-state-flow/BACKLOG.md +6 -2
  6. package/node_modules/@llblab/pi-state-flow/CHANGELOG.md +4 -1
  7. package/node_modules/@llblab/pi-state-flow/dist/lib/compaction.js +4 -3
  8. package/node_modules/@llblab/pi-state-flow/dist/lib/extension.js +26 -13
  9. package/node_modules/@llblab/pi-state-flow/dist/package.json +1 -1
  10. package/node_modules/@llblab/pi-state-flow/docs/architecture.md +3 -1
  11. package/node_modules/@llblab/pi-state-flow/docs/compatibility.md +2 -2
  12. package/node_modules/@llblab/pi-state-flow/docs/usage.md +2 -1
  13. package/node_modules/@llblab/pi-state-flow/lib/compaction.ts +4 -3
  14. package/node_modules/@llblab/pi-state-flow/lib/extension.ts +25 -12
  15. package/node_modules/@llblab/pi-state-flow/package.json +1 -1
  16. package/node_modules/@llblab/pi-telegram/AGENTS.md +10 -8
  17. package/node_modules/@llblab/pi-telegram/BACKLOG.md +1 -9
  18. package/node_modules/@llblab/pi-telegram/CHANGELOG.md +13 -0
  19. package/node_modules/@llblab/pi-telegram/LICENSE +21 -0
  20. package/node_modules/@llblab/pi-telegram/README.md +15 -5
  21. package/node_modules/@llblab/pi-telegram/dist/lib/activity-verbosity.d.ts +1 -5
  22. package/node_modules/@llblab/pi-telegram/dist/lib/activity-verbosity.js +5 -7
  23. package/node_modules/@llblab/pi-telegram/dist/lib/bindings.d.ts +5 -1
  24. package/node_modules/@llblab/pi-telegram/dist/lib/bindings.js +6 -1
  25. package/node_modules/@llblab/pi-telegram/dist/lib/bus-api.js +4 -2
  26. package/node_modules/@llblab/pi-telegram/dist/lib/bus-follower.d.ts +54 -56
  27. package/node_modules/@llblab/pi-telegram/dist/lib/bus-follower.js +206 -139
  28. package/node_modules/@llblab/pi-telegram/dist/lib/bus-leader.d.ts +21 -2
  29. package/node_modules/@llblab/pi-telegram/dist/lib/bus-leader.js +181 -25
  30. package/node_modules/@llblab/pi-telegram/dist/lib/bus-transport.d.ts +4 -1
  31. package/node_modules/@llblab/pi-telegram/dist/lib/bus-transport.js +28 -4
  32. package/node_modules/@llblab/pi-telegram/dist/lib/bus.d.ts +52 -56
  33. package/node_modules/@llblab/pi-telegram/dist/lib/bus.js +78 -246
  34. package/node_modules/@llblab/pi-telegram/dist/lib/channel-posts.d.ts +1 -3
  35. package/node_modules/@llblab/pi-telegram/dist/lib/channel-posts.js +23 -26
  36. package/node_modules/@llblab/pi-telegram/dist/lib/command-templates.d.ts +0 -5
  37. package/node_modules/@llblab/pi-telegram/dist/lib/command-templates.js +5 -5
  38. package/node_modules/@llblab/pi-telegram/dist/lib/commands.d.ts +0 -23
  39. package/node_modules/@llblab/pi-telegram/dist/lib/commands.js +15 -15
  40. package/node_modules/@llblab/pi-telegram/dist/lib/config.d.ts +0 -17
  41. package/node_modules/@llblab/pi-telegram/dist/lib/config.js +12 -14
  42. package/node_modules/@llblab/pi-telegram/dist/lib/delivery.d.ts +0 -4
  43. package/node_modules/@llblab/pi-telegram/dist/lib/delivery.js +2 -5
  44. package/node_modules/@llblab/pi-telegram/dist/lib/extension.js +255 -61
  45. package/node_modules/@llblab/pi-telegram/dist/lib/generative-apps.d.ts +0 -19
  46. package/node_modules/@llblab/pi-telegram/dist/lib/inbound.d.ts +0 -2
  47. package/node_modules/@llblab/pi-telegram/dist/lib/inbound.js +2 -2
  48. package/node_modules/@llblab/pi-telegram/dist/lib/journal.d.ts +177 -16
  49. package/node_modules/@llblab/pi-telegram/dist/lib/journal.js +1115 -249
  50. package/node_modules/@llblab/pi-telegram/dist/lib/keyboard.d.ts +0 -2
  51. package/node_modules/@llblab/pi-telegram/dist/lib/keyboard.js +2 -2
  52. package/node_modules/@llblab/pi-telegram/dist/lib/lifecycle.js +1 -1
  53. package/node_modules/@llblab/pi-telegram/dist/lib/locks.d.ts +100 -12
  54. package/node_modules/@llblab/pi-telegram/dist/lib/locks.js +380 -14
  55. package/node_modules/@llblab/pi-telegram/dist/lib/logging.d.ts +30 -12
  56. package/node_modules/@llblab/pi-telegram/dist/lib/logging.js +129 -72
  57. package/node_modules/@llblab/pi-telegram/dist/lib/media.d.ts +28 -5
  58. package/node_modules/@llblab/pi-telegram/dist/lib/media.js +26 -8
  59. package/node_modules/@llblab/pi-telegram/dist/lib/menu-model.d.ts +0 -11
  60. package/node_modules/@llblab/pi-telegram/dist/lib/menu-model.js +8 -8
  61. package/node_modules/@llblab/pi-telegram/dist/lib/menu-settings.d.ts +0 -18
  62. package/node_modules/@llblab/pi-telegram/dist/lib/menu-settings.js +18 -18
  63. package/node_modules/@llblab/pi-telegram/dist/lib/menu-status.d.ts +4 -4
  64. package/node_modules/@llblab/pi-telegram/dist/lib/menu-status.js +12 -7
  65. package/node_modules/@llblab/pi-telegram/dist/lib/menu-thinking.d.ts +1 -1
  66. package/node_modules/@llblab/pi-telegram/dist/lib/menu-thinking.js +1 -1
  67. package/node_modules/@llblab/pi-telegram/dist/lib/menu.d.ts +1 -0
  68. package/node_modules/@llblab/pi-telegram/dist/lib/menu.js +4 -3
  69. package/node_modules/@llblab/pi-telegram/dist/lib/outbound-attachments.d.ts +0 -1
  70. package/node_modules/@llblab/pi-telegram/dist/lib/outbound-attachments.js +1 -1
  71. package/node_modules/@llblab/pi-telegram/dist/lib/outbound-buttons.js +1 -8
  72. package/node_modules/@llblab/pi-telegram/dist/lib/outbound-markup.d.ts +1 -6
  73. package/node_modules/@llblab/pi-telegram/dist/lib/outbound-markup.js +4 -21
  74. package/node_modules/@llblab/pi-telegram/dist/lib/outbound.d.ts +0 -8
  75. package/node_modules/@llblab/pi-telegram/dist/lib/outbound.js +5 -5
  76. package/node_modules/@llblab/pi-telegram/dist/lib/paths.d.ts +50 -7
  77. package/node_modules/@llblab/pi-telegram/dist/lib/paths.js +155 -17
  78. package/node_modules/@llblab/pi-telegram/dist/lib/pi.d.ts +1 -0
  79. package/node_modules/@llblab/pi-telegram/dist/lib/pi.js +2 -1
  80. package/node_modules/@llblab/pi-telegram/dist/lib/polling.d.ts +0 -5
  81. package/node_modules/@llblab/pi-telegram/dist/lib/polling.js +5 -5
  82. package/node_modules/@llblab/pi-telegram/dist/lib/preview.d.ts +0 -3
  83. package/node_modules/@llblab/pi-telegram/dist/lib/preview.js +3 -3
  84. package/node_modules/@llblab/pi-telegram/dist/lib/prompt-templates.d.ts +0 -1
  85. package/node_modules/@llblab/pi-telegram/dist/lib/prompt-templates.js +1 -1
  86. package/node_modules/@llblab/pi-telegram/dist/lib/queue.d.ts +3 -15
  87. package/node_modules/@llblab/pi-telegram/dist/lib/queue.js +69 -8
  88. package/node_modules/@llblab/pi-telegram/dist/lib/recovery.d.ts +29 -9
  89. package/node_modules/@llblab/pi-telegram/dist/lib/recovery.js +104 -33
  90. package/node_modules/@llblab/pi-telegram/dist/lib/replies.d.ts +0 -4
  91. package/node_modules/@llblab/pi-telegram/dist/lib/replies.js +1 -1
  92. package/node_modules/@llblab/pi-telegram/dist/lib/routing.d.ts +73 -8
  93. package/node_modules/@llblab/pi-telegram/dist/lib/routing.js +2206 -307
  94. package/node_modules/@llblab/pi-telegram/dist/lib/sections.d.ts +0 -2
  95. package/node_modules/@llblab/pi-telegram/dist/lib/sections.js +1 -1
  96. package/node_modules/@llblab/pi-telegram/dist/lib/status.d.ts +44 -9
  97. package/node_modules/@llblab/pi-telegram/dist/lib/status.js +149 -27
  98. package/node_modules/@llblab/pi-telegram/dist/lib/sync.d.ts +0 -5
  99. package/node_modules/@llblab/pi-telegram/dist/lib/sync.js +5 -4
  100. package/node_modules/@llblab/pi-telegram/dist/lib/telegram-api.d.ts +11 -3
  101. package/node_modules/@llblab/pi-telegram/dist/lib/telegram-api.js +34 -23
  102. package/node_modules/@llblab/pi-telegram/dist/lib/thread-cleanup-manager.d.ts +1 -0
  103. package/node_modules/@llblab/pi-telegram/dist/lib/thread-cleanup-manager.js +18 -21
  104. package/node_modules/@llblab/pi-telegram/dist/lib/thread-naming.d.ts +32 -5
  105. package/node_modules/@llblab/pi-telegram/dist/lib/thread-naming.js +191 -7
  106. package/node_modules/@llblab/pi-telegram/dist/lib/thread-reconciler.d.ts +2 -0
  107. package/node_modules/@llblab/pi-telegram/dist/lib/thread-reconciler.js +1 -1
  108. package/node_modules/@llblab/pi-telegram/dist/lib/threads.d.ts +291 -59
  109. package/node_modules/@llblab/pi-telegram/dist/lib/threads.js +1647 -369
  110. package/node_modules/@llblab/pi-telegram/dist/lib/turns.d.ts +0 -1
  111. package/node_modules/@llblab/pi-telegram/dist/lib/turns.js +1 -1
  112. package/node_modules/@llblab/pi-telegram/dist/lib/updates.d.ts +184 -28
  113. package/node_modules/@llblab/pi-telegram/dist/lib/updates.js +1032 -114
  114. package/node_modules/@llblab/pi-telegram/dist/lib/wire.d.ts +12 -0
  115. package/node_modules/@llblab/pi-telegram/dist/lib/wire.js +21 -0
  116. package/node_modules/@llblab/pi-telegram/dist/lib/workspace-admission.d.ts +25 -17
  117. package/node_modules/@llblab/pi-telegram/dist/lib/workspace-admission.js +95 -20
  118. package/node_modules/@llblab/pi-telegram/dist/lib/workspace-identity.d.ts +20 -0
  119. package/node_modules/@llblab/pi-telegram/dist/lib/workspace-identity.js +103 -0
  120. package/node_modules/@llblab/pi-telegram/dist/lib/workspace-retirement.d.ts +43 -8
  121. package/node_modules/@llblab/pi-telegram/dist/lib/workspace-retirement.js +148 -52
  122. package/node_modules/@llblab/pi-telegram/dist/package.json +6 -6
  123. package/node_modules/@llblab/pi-telegram/dist/skills/generated-control-surface/SKILL.md +3 -3
  124. package/node_modules/@llblab/pi-telegram/dist/skills/telegram-bridge/references/diagnosis.md +3 -3
  125. package/node_modules/@llblab/pi-telegram/docs/README.md +1 -1
  126. package/node_modules/@llblab/pi-telegram/docs/activity.md +1 -1
  127. package/node_modules/@llblab/pi-telegram/docs/architecture.md +152 -34
  128. package/node_modules/@llblab/pi-telegram/docs/callback-namespaces.md +1 -1
  129. package/node_modules/@llblab/pi-telegram/docs/delivery.md +1 -1
  130. package/node_modules/@llblab/pi-telegram/docs/generative-apps.md +2 -2
  131. package/node_modules/@llblab/pi-telegram/docs/inbound.md +1 -1
  132. package/node_modules/@llblab/pi-telegram/docs/multi-instance-bus.md +125 -19
  133. package/node_modules/@llblab/pi-telegram/docs/public-api.md +3 -1
  134. package/node_modules/@llblab/pi-telegram/docs/ui-style.md +22 -8
  135. package/node_modules/@llblab/pi-telegram/docs/updates.md +1 -1
  136. package/node_modules/@llblab/pi-telegram/lib/activity-verbosity.ts +5 -9
  137. package/node_modules/@llblab/pi-telegram/lib/bindings.ts +6 -0
  138. package/node_modules/@llblab/pi-telegram/lib/bus-api.ts +5 -2
  139. package/node_modules/@llblab/pi-telegram/lib/bus-follower.ts +243 -224
  140. package/node_modules/@llblab/pi-telegram/lib/bus-leader.ts +178 -31
  141. package/node_modules/@llblab/pi-telegram/lib/bus-transport.ts +30 -8
  142. package/node_modules/@llblab/pi-telegram/lib/bus.ts +110 -334
  143. package/node_modules/@llblab/pi-telegram/lib/channel-posts.ts +29 -29
  144. package/node_modules/@llblab/pi-telegram/lib/command-templates.ts +5 -5
  145. package/node_modules/@llblab/pi-telegram/lib/commands.ts +15 -15
  146. package/node_modules/@llblab/pi-telegram/lib/config.ts +12 -17
  147. package/node_modules/@llblab/pi-telegram/lib/delivery.ts +2 -8
  148. package/node_modules/@llblab/pi-telegram/lib/extension.ts +257 -72
  149. package/node_modules/@llblab/pi-telegram/lib/generative-apps.ts +0 -22
  150. package/node_modules/@llblab/pi-telegram/lib/inbound.ts +2 -2
  151. package/node_modules/@llblab/pi-telegram/lib/journal.ts +1182 -326
  152. package/node_modules/@llblab/pi-telegram/lib/keyboard.ts +2 -2
  153. package/node_modules/@llblab/pi-telegram/lib/lifecycle.ts +1 -1
  154. package/node_modules/@llblab/pi-telegram/lib/locks.ts +402 -23
  155. package/node_modules/@llblab/pi-telegram/lib/logging.ts +151 -101
  156. package/node_modules/@llblab/pi-telegram/lib/media.ts +51 -9
  157. package/node_modules/@llblab/pi-telegram/lib/menu-model.ts +8 -8
  158. package/node_modules/@llblab/pi-telegram/lib/menu-settings.ts +18 -18
  159. package/node_modules/@llblab/pi-telegram/lib/menu-status.ts +11 -0
  160. package/node_modules/@llblab/pi-telegram/lib/menu-thinking.ts +2 -2
  161. package/node_modules/@llblab/pi-telegram/lib/menu.ts +5 -1
  162. package/node_modules/@llblab/pi-telegram/lib/outbound-attachments.ts +1 -1
  163. package/node_modules/@llblab/pi-telegram/lib/outbound-buttons.ts +1 -10
  164. package/node_modules/@llblab/pi-telegram/lib/outbound-markup.ts +5 -24
  165. package/node_modules/@llblab/pi-telegram/lib/outbound.ts +5 -5
  166. package/node_modules/@llblab/pi-telegram/lib/paths.ts +177 -17
  167. package/node_modules/@llblab/pi-telegram/lib/pi.ts +5 -2
  168. package/node_modules/@llblab/pi-telegram/lib/polling.ts +5 -5
  169. package/node_modules/@llblab/pi-telegram/lib/preview.ts +3 -3
  170. package/node_modules/@llblab/pi-telegram/lib/prompt-templates.ts +1 -1
  171. package/node_modules/@llblab/pi-telegram/lib/queue.ts +67 -9
  172. package/node_modules/@llblab/pi-telegram/lib/recovery.ts +104 -44
  173. package/node_modules/@llblab/pi-telegram/lib/replies.ts +1 -1
  174. package/node_modules/@llblab/pi-telegram/lib/routing.ts +1955 -375
  175. package/node_modules/@llblab/pi-telegram/lib/sections.ts +1 -1
  176. package/node_modules/@llblab/pi-telegram/lib/status.ts +156 -36
  177. package/node_modules/@llblab/pi-telegram/lib/sync.ts +4 -4
  178. package/node_modules/@llblab/pi-telegram/lib/telegram-api.ts +48 -33
  179. package/node_modules/@llblab/pi-telegram/lib/thread-cleanup-manager.ts +24 -21
  180. package/node_modules/@llblab/pi-telegram/lib/thread-naming.ts +267 -9
  181. package/node_modules/@llblab/pi-telegram/lib/thread-reconciler.ts +3 -1
  182. package/node_modules/@llblab/pi-telegram/lib/threads.ts +1697 -488
  183. package/node_modules/@llblab/pi-telegram/lib/turns.ts +1 -1
  184. package/node_modules/@llblab/pi-telegram/lib/updates.ts +1072 -151
  185. package/node_modules/@llblab/pi-telegram/lib/wire.ts +28 -0
  186. package/node_modules/@llblab/pi-telegram/lib/workspace-admission.ts +97 -47
  187. package/node_modules/@llblab/pi-telegram/lib/workspace-identity.ts +147 -0
  188. package/node_modules/@llblab/pi-telegram/lib/workspace-retirement.ts +188 -89
  189. package/node_modules/@llblab/pi-telegram/package.json +6 -6
  190. package/node_modules/@llblab/pi-telegram/scripts/audit-exports.mjs +100 -0
  191. package/node_modules/@llblab/pi-telegram/scripts/check-downgrade.mjs +80 -43
  192. package/node_modules/@llblab/pi-telegram/skills/generated-control-surface/SKILL.md +3 -3
  193. package/node_modules/@llblab/pi-telegram/skills/telegram-bridge/references/diagnosis.md +3 -3
  194. package/package.json +3 -3
@@ -1,12 +1,14 @@
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
- import { chmodSync, existsSync, lstatSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, renameSync, rmSync, unlinkSync, writeFileSync, } from "node:fs";
6
+ import { chmodSync, closeSync, existsSync, fstatSync, lstatSync, mkdirSync, mkdtempSync, openSync, readFileSync, readdirSync, renameSync, rmSync, unlinkSync, writeFileSync, } from "node:fs";
7
7
  import { randomUUID } from "node:crypto";
8
- import { basename, dirname, join } from "node:path";
9
- import { resolveTelegramOwnersPath } from "./paths.js";
8
+ import { isDeepStrictEqual } from "node:util";
9
+ import { basename, dirname, isAbsolute, join, resolve } from "node:path";
10
+ import { isWireRecord as runtimeStateRecord } from "./wire.js";
11
+ import { isTelegramSessionPollingJournalPath, resolveTelegramOwnersPath, resolveTelegramSessionPollingJournalPath, resolveTelegramUpdateJournalPathForProfile, } from "./paths.js";
10
12
  export const TELEGRAM_LOCK_KEY = "default";
11
13
  export const TELEGRAM_BUS_LEADER_STALE_HEARTBEAT_MS = 8_000;
12
14
  export const TELEGRAM_OWNERSHIP_CHECK_MS = 1_000;
@@ -42,6 +44,53 @@ export function createTelegramLockKeyResolver(activeProfile) {
42
44
  return resolveTelegramLockKey(activeProfile.getActiveProfileName());
43
45
  };
44
46
  }
47
+ /**
48
+ * Captures exact context, session generation and owned leader epoch before awaits.
49
+ * Session replacement, release or re-election revokes the grant; a successor never renews it.
50
+ */
51
+ export function createTelegramOwnedStateAuthorityCapture(lock, session) {
52
+ return () => {
53
+ const ctx = session.get(), generation = session.getGeneration();
54
+ if (ctx === undefined || !session.isCurrent(ctx, generation))
55
+ return undefined;
56
+ const epoch = lock.getOwnedLeaderEpoch();
57
+ if (epoch === undefined)
58
+ return undefined;
59
+ const current = () => session.isCurrent(ctx, generation) && lock.owns(ctx) && lock.getOwnedLeaderEpoch() === epoch;
60
+ return current() ? current : undefined;
61
+ };
62
+ }
63
+ /**
64
+ * Leader polling journal: the path owners.json names, else the session that would host it on acquisition.
65
+ * Without a session identity, the flat root `inbox` remains the compatibility fallback.
66
+ */
67
+ export function createTelegramLeaderJournalPathResolver(deps) {
68
+ // An existing flat root journal keeps its custody; new installations never create one.
69
+ const createOwn = (profileName = deps.getProfileName()) => {
70
+ const root = resolveTelegramUpdateJournalPathForProfile(profileName);
71
+ if (existsSync(root))
72
+ return root;
73
+ const sessionId = deps.getSessionId();
74
+ return sessionId === undefined ? undefined : resolveTelegramSessionPollingJournalPath(sessionId, undefined, profileName);
75
+ };
76
+ return {
77
+ createJournalPath: () => createOwn(),
78
+ resolve(profileName) {
79
+ const named = deps.getNamedJournalPath();
80
+ if (named && (isTelegramSessionPollingJournalPath(named) ||
81
+ named === resolveTelegramUpdateJournalPathForProfile(profileName)))
82
+ return named;
83
+ return createOwn(profileName) ?? resolveTelegramUpdateJournalPathForProfile(profileName);
84
+ },
85
+ };
86
+ }
87
+ /** A released key keeps only `{ journalPath }`: no owner, but the successor's polling custody. */
88
+ export function readTelegramLockJournalPath(value) {
89
+ if (!value || typeof value !== "object" || Array.isArray(value))
90
+ return undefined;
91
+ const path = value.journalPath;
92
+ return typeof path === "string" && path ? path : undefined;
93
+ }
45
94
  export function readLocks(path = getOwnersPath()) {
46
95
  if (!existsSync(path))
47
96
  return {};
@@ -479,6 +528,201 @@ export function withTelegramFileTransaction(transactionPath, operation, options
479
528
  releaseLockTransactionGuard(transactionPath, owner);
480
529
  }
481
530
  }
531
+ export class TelegramRuntimeStateError extends Error {
532
+ code;
533
+ constructor(code, message, options) {
534
+ super(message, options);
535
+ this.name = "TelegramRuntimeStateError";
536
+ this.code = code;
537
+ }
538
+ }
539
+ const TELEGRAM_RUNTIME_STATE_SECTIONS = ["transport", "workspace", "admission", "runtime"];
540
+ const TELEGRAM_ACTIVE_STATE_TRANSACTIONS = Symbol.for("@llblab/pi-telegram/active-state-transactions");
541
+ function requireRuntimeStatePath(path) {
542
+ if (typeof path !== "string" || !isAbsolute(path) || resolve(path) !== path)
543
+ throw new TelegramRuntimeStateError("invalid", "Telegram runtime state path must be canonical and absolute.");
544
+ }
545
+ function requireRuntimeStateProfile(profile) {
546
+ if (typeof profile !== "string" || !profile.length || profile.length > 512 || /[\x00-\x1f]/u.test(profile))
547
+ throw new TelegramRuntimeStateError("invalid", "Telegram runtime state profile is invalid.");
548
+ }
549
+ /** Private single-link regular file read with identity continuity; initial absence only is positive. */
550
+ function readTelegramRuntimeSource(path) {
551
+ requireRuntimeStatePath(path);
552
+ let source;
553
+ let observed = false;
554
+ try {
555
+ const stat = lstatSync(path);
556
+ observed = true;
557
+ if (!stat.isFile() || stat.nlink !== 1 ||
558
+ (process.platform !== "win32" && (stat.mode & 0o077) !== 0))
559
+ throw new TelegramRuntimeStateError("invalid", "Telegram runtime state must be a private regular file.");
560
+ const fd = openSync(path, "r");
561
+ try {
562
+ const opened = fstatSync(fd);
563
+ if (opened.dev !== stat.dev || opened.ino !== stat.ino || opened.nlink !== 1 || !opened.isFile() ||
564
+ (process.platform !== "win32" && (opened.mode & 0o077) !== 0))
565
+ throw new TelegramRuntimeStateError("invalid", "Telegram runtime state identity changed before reading.");
566
+ source = readFileSync(fd).toString("utf8");
567
+ }
568
+ finally {
569
+ closeSync(fd);
570
+ }
571
+ const after = lstatSync(path);
572
+ if (after.dev !== stat.dev || after.ino !== stat.ino || after.size !== stat.size || after.mtimeMs !== stat.mtimeMs || after.ctimeMs !== stat.ctimeMs)
573
+ throw new TelegramRuntimeStateError("invalid", "Telegram runtime state changed during observation.");
574
+ }
575
+ catch (error) {
576
+ if (!observed && error.code === "ENOENT")
577
+ return undefined;
578
+ throw error;
579
+ }
580
+ return source;
581
+ }
582
+ /** Strict, observational envelope read; section owners validate their own payloads. Legacy state is never adopted here. */
583
+ export function readTelegramRuntimeState(path) {
584
+ const observed = readTelegramRuntimeSource(path);
585
+ if (observed === undefined)
586
+ return { version: 2, profiles: {} };
587
+ let value;
588
+ try {
589
+ value = JSON.parse(observed);
590
+ }
591
+ catch (error) {
592
+ throw new TelegramRuntimeStateError("invalid", "Telegram runtime state is unreadable.", { cause: error });
593
+ }
594
+ if (!runtimeStateRecord(value) || value.version !== 2 || !runtimeStateRecord(value.profiles) ||
595
+ Object.keys(value).some(key => key !== "version" && key !== "profiles"))
596
+ throw new TelegramRuntimeStateError("invalid", "Telegram runtime state envelope is unsupported or malformed.");
597
+ for (const [profile, sections] of Object.entries(value.profiles)) {
598
+ requireRuntimeStateProfile(profile);
599
+ if (!runtimeStateRecord(sections) || Object.keys(sections).some(key => !TELEGRAM_RUNTIME_STATE_SECTIONS.includes(key)))
600
+ throw new TelegramRuntimeStateError("invalid", "Telegram runtime state sections are malformed.");
601
+ }
602
+ return value;
603
+ }
604
+ /**
605
+ * Operator-approved optimistic recovery before leader acquisition: when the shared envelope, any transport section or a
606
+ * caller-validated section is damaged, publish a fresh empty envelope instead of refusing. All profiles lose runtime
607
+ * continuity. Filesystem access errors are not damage and still throw. Returns whether a reset was published.
608
+ */
609
+ export function resetDamagedTelegramRuntimeState(path, validateProfile) {
610
+ requireRuntimeStatePath(path);
611
+ const runtimeDir = join(dirname(path), "runtime");
612
+ mkdirSync(runtimeDir, { recursive: true, mode: 0o700 });
613
+ return withTelegramFileTransaction(join(runtimeDir, `${basename(path)}.transaction`), () => {
614
+ let damaged = false;
615
+ try {
616
+ for (const [profile, sections] of Object.entries(readTelegramRuntimeState(path).profiles)) {
617
+ assertTelegramStateTransport(sections.transport);
618
+ try {
619
+ validateProfile?.(profile, sections);
620
+ }
621
+ catch {
622
+ damaged = true;
623
+ }
624
+ }
625
+ }
626
+ catch (error) {
627
+ if (!(error instanceof TelegramRuntimeStateError))
628
+ throw error;
629
+ damaged = true;
630
+ }
631
+ if (!damaged)
632
+ return false;
633
+ const tempPath = join(runtimeDir, `${basename(path)}.${process.pid}.${randomUUID()}.tmp`);
634
+ try {
635
+ writeFileSync(tempPath, `${JSON.stringify({ version: 2, profiles: {} }, null, 2)}\n`, { encoding: "utf8", flag: "wx", mode: 0o600 });
636
+ if (!renameTelegramPathWithRetry(tempPath, path))
637
+ throw new Error("Telegram runtime state reset publication disappeared.");
638
+ }
639
+ finally {
640
+ try {
641
+ unlinkSync(tempPath);
642
+ }
643
+ catch (error) {
644
+ if (error.code !== "ENOENT")
645
+ throw error;
646
+ }
647
+ }
648
+ return true;
649
+ });
650
+ }
651
+ /**
652
+ * One physical read/check/write transaction for one named section. The caller supplies domain policy; copies expose
653
+ * current sibling facts without granting writes to them. No await, nested transaction, repair or legacy import.
654
+ */
655
+ export function mutateTelegramRuntimeStateSection(path, profile, section, mutate, options) {
656
+ requireRuntimeStatePath(path);
657
+ requireRuntimeStateProfile(profile);
658
+ if (!TELEGRAM_RUNTIME_STATE_SECTIONS.includes(section))
659
+ throw new TelegramRuntimeStateError("invalid", "Telegram runtime state section is invalid.");
660
+ const active = (globalThis[TELEGRAM_ACTIVE_STATE_TRANSACTIONS] ??= new Set());
661
+ if (active.has(path))
662
+ throw new TelegramRuntimeStateError("invalid", "Nested Telegram runtime state transaction is not allowed.");
663
+ const assertCurrent = () => {
664
+ if (!options.isCurrent())
665
+ throw new TelegramRuntimeStateError("authority-changed", "Telegram runtime state publication authority changed.");
666
+ };
667
+ assertCurrent();
668
+ active.add(path);
669
+ try {
670
+ const runtimeDir = join(dirname(path), "runtime");
671
+ mkdirSync(runtimeDir, { recursive: true, mode: 0o700 });
672
+ return withTelegramFileTransaction(join(runtimeDir, `${basename(path)}.transaction`), () => {
673
+ assertCurrent();
674
+ const file = readTelegramRuntimeState(path);
675
+ const previous = Object.hasOwn(file.profiles, profile) ? file.profiles[profile] : {};
676
+ const outcome = mutate(structuredClone(previous[section]), structuredClone(previous));
677
+ if (!runtimeStateRecord(outcome) || !Object.hasOwn(outcome, "value") || !Object.hasOwn(outcome, "result") || "then" in outcome)
678
+ throw new TelegramRuntimeStateError("invalid", "Telegram runtime state mutations must return a synchronous value and result.");
679
+ // Normalize intentional optional properties to their wire representation before comparison/publication.
680
+ const next = outcome.value === undefined ? undefined : JSON.parse(JSON.stringify(outcome.value));
681
+ assertCurrent();
682
+ if (isDeepStrictEqual(previous[section], next))
683
+ return outcome.result;
684
+ const updated = { ...previous };
685
+ if (next === undefined)
686
+ delete updated[section];
687
+ else
688
+ updated[section] = next;
689
+ if (Object.keys(updated).length)
690
+ Object.defineProperty(file.profiles, profile, { value: updated, enumerable: true, configurable: true, writable: true });
691
+ else
692
+ delete file.profiles[profile];
693
+ const tempPath = join(runtimeDir, `${basename(path)}.${process.pid}.${randomUUID()}.tmp`);
694
+ try {
695
+ options.onPublicationBoundary?.("before-write");
696
+ assertCurrent();
697
+ writeFileSync(tempPath, `${JSON.stringify(file, null, 2)}\n`, { encoding: "utf8", flag: "wx", mode: 0o600 });
698
+ options.onPublicationBoundary?.("after-write-before-rename");
699
+ assertCurrent();
700
+ try {
701
+ if (!renameTelegramPathWithRetry(tempPath, path, { rename: options.publishRename }))
702
+ throw new Error("Telegram runtime state temporary publication disappeared.");
703
+ options.onPublicationBoundary?.("after-rename");
704
+ assertCurrent();
705
+ }
706
+ catch (error) {
707
+ throw new TelegramRuntimeStateError("publication-unknown", "Telegram runtime state publication outcome is unknown.", { cause: error });
708
+ }
709
+ return outcome.result;
710
+ }
711
+ finally {
712
+ try {
713
+ unlinkSync(tempPath);
714
+ }
715
+ catch (error) {
716
+ if (error.code !== "ENOENT")
717
+ throw error;
718
+ }
719
+ }
720
+ });
721
+ }
722
+ finally {
723
+ active.delete(path);
724
+ }
725
+ }
482
726
  function withLockTransaction(locksPath, mutate) {
483
727
  return withTelegramFileTransaction(`${locksPath}.transaction`, () => {
484
728
  const locks = readLocksForTransaction(locksPath);
@@ -541,6 +785,7 @@ export function parseTelegramLockEntry(value) {
541
785
  ? record.busSocketPath
542
786
  : undefined,
543
787
  busSecret: typeof record.busSecret === "string" ? record.busSecret : undefined,
788
+ ...(readTelegramLockJournalPath(record) ? { journalPath: readTelegramLockJournalPath(record) } : {}),
544
789
  };
545
790
  }
546
791
  export function isProcessAlive(pid) {
@@ -555,7 +800,7 @@ export function isProcessAlive(pid) {
555
800
  return error.code !== "ESRCH";
556
801
  }
557
802
  }
558
- export function formatTelegramLockEntry(lock) {
803
+ function formatTelegramLockEntry(lock) {
559
804
  return lock.cwd ? `pid ${lock.pid}, cwd ${lock.cwd}` : `pid ${lock.pid}`;
560
805
  }
561
806
  function formatTelegramFollowerRegistrationFailure(message) {
@@ -606,6 +851,8 @@ function canSupersedeSameProcessOwner(current, pid, ctx, instanceId, runtimeGene
606
851
  }
607
852
  function createLockEntry(pid, ctx, options) {
608
853
  const lock = { pid, cwd: ctx.cwd };
854
+ if (options.journalPath)
855
+ lock.journalPath = options.journalPath;
609
856
  if (options.instanceId) {
610
857
  const nowMs = options.getNowMs?.();
611
858
  lock.instanceId = options.instanceId;
@@ -631,8 +878,26 @@ function formatLockState(state) {
631
878
  return `stale (${formatTelegramLockEntry(state.lock)})`;
632
879
  }
633
880
  }
881
+ function assertTelegramStateTransport(value) {
882
+ if (value === undefined)
883
+ return;
884
+ if (!runtimeStateRecord(value))
885
+ throw new TelegramRuntimeStateError("invalid", "Telegram state transport is malformed.");
886
+ const keys = ["pid", "cwd", "instanceId", "heartbeatMs", "leaderEpoch", "runtimeGeneration", "busSocketPath", "busSecret", "journalPath"];
887
+ if (Object.keys(value).some(key => !keys.includes(key)) ||
888
+ (value.pid === undefined ? Object.keys(value).length !== 1 || !readTelegramLockJournalPath(value) :
889
+ !Number.isSafeInteger(value.pid) || value.pid <= 0) ||
890
+ ["cwd", "instanceId", "busSocketPath", "busSecret", "journalPath"].some(key => value[key] !== undefined && typeof value[key] !== "string") ||
891
+ ["heartbeatMs", "runtimeGeneration"].some(key => value[key] !== undefined && (!Number.isSafeInteger(value[key]) || value[key] < 0)) ||
892
+ (value.journalPath !== undefined && !readTelegramLockJournalPath(value)) ||
893
+ (value.leaderEpoch !== undefined && (typeof value.leaderEpoch === "string" ? value.leaderEpoch.length === 0 : !Number.isSafeInteger(value.leaderEpoch))))
894
+ throw new TelegramRuntimeStateError("invalid", "Telegram state transport is malformed.");
895
+ }
634
896
  export function createTelegramLockRuntime(options = {}) {
635
897
  const key = options.key ?? TELEGRAM_LOCK_KEY;
898
+ if (options.statePath && options.locksPath)
899
+ throw new TelegramRuntimeStateError("invalid", "Telegram transport must select one storage identity.");
900
+ const statePath = options.statePath;
636
901
  const locksPath = options.locksPath ?? getOwnersPath();
637
902
  const pid = options.pid ?? process.pid;
638
903
  const isAlive = options.isProcessAlive ?? isProcessAlive;
@@ -650,9 +915,59 @@ export function createTelegramLockRuntime(options = {}) {
650
915
  return key() || TELEGRAM_LOCK_KEY;
651
916
  return key;
652
917
  };
918
+ const readOwners = () => {
919
+ if (!statePath)
920
+ return readLocks(locksPath);
921
+ // Read-only ownership queries fail closed (no owner); acquisition/publication still refuse malformed state.
922
+ try {
923
+ const transports = Object.fromEntries(Object.entries(readTelegramRuntimeState(statePath).profiles)
924
+ .filter(([, value]) => Object.hasOwn(value, "transport")).map(([profile, value]) => [profile, value.transport]));
925
+ assertTelegramStateTransport(transports[resolveEffectiveKey()]);
926
+ return transports;
927
+ }
928
+ catch {
929
+ return {};
930
+ }
931
+ };
932
+ const transactOwners = (mutate) => {
933
+ if (!statePath)
934
+ return withLockTransaction(locksPath, mutate);
935
+ const profile = resolveEffectiveKey();
936
+ return mutateTelegramRuntimeStateSection(statePath, profile, "transport", current => {
937
+ assertTelegramStateTransport(current);
938
+ const locks = { [profile]: current };
939
+ const outcome = mutate(locks);
940
+ const value = outcome.changed ? locks[profile] : current;
941
+ assertTelegramStateTransport(value);
942
+ return { value, result: outcome.result };
943
+ }, { ...options.statePublication, isCurrent: () => resolveEffectiveKey() === profile });
944
+ };
653
945
  const readLock = () => {
654
946
  const effectiveKey = resolveEffectiveKey();
655
- return parseTelegramLockEntry(readLocks(locksPath)[effectiveKey]);
947
+ return parseTelegramLockEntry(readOwners()[effectiveKey]);
948
+ };
949
+ // A live older-release leader polls the same bot from its own directory. Only identity is exposed: its bus
950
+ // endpoint, secret and epoch belong to another protocol and must never become a follower target.
951
+ const readLegacyOwner = (effectiveKey) => {
952
+ if (!options.legacyLocksPath || options.legacyLocksPath === (statePath ?? locksPath))
953
+ return undefined;
954
+ try {
955
+ const records = statePath ? readLocksForTransaction(options.legacyLocksPath) : readLocks(options.legacyLocksPath);
956
+ if (statePath)
957
+ for (const value of Object.values(records))
958
+ assertTelegramStateTransport(value);
959
+ const raw = records[effectiveKey];
960
+ const legacy = parseTelegramLockEntry(raw);
961
+ if (!legacy || getLockState(legacy, -1, isAlive, stateOptions()).kind !== "active-elsewhere")
962
+ return undefined;
963
+ return { pid: legacy.pid, ...(legacy.cwd ? { cwd: legacy.cwd } : {}), ...(legacy.instanceId ? { instanceId: legacy.instanceId } : {}),
964
+ ...(legacy.heartbeatMs !== undefined ? { heartbeatMs: legacy.heartbeatMs } : {}) };
965
+ }
966
+ catch (error) {
967
+ if (statePath)
968
+ throw error;
969
+ return undefined;
970
+ }
656
971
  };
657
972
  const adoptCompatibleOwnedLock = (effectiveKey, lock, ctx) => {
658
973
  if (ownedLock) {
@@ -671,8 +986,11 @@ export function createTelegramLockRuntime(options = {}) {
671
986
  return ownedLock;
672
987
  };
673
988
  return {
674
- acquire: (ctx, acquireOptions = {}) => withLockTransaction(locksPath, (locks) => {
989
+ acquire: (ctx, acquireOptions = {}) => transactOwners((locks) => {
675
990
  const effectiveKey = resolveEffectiveKey();
991
+ const legacy = readLegacyOwner(effectiveKey);
992
+ if (legacy)
993
+ return { result: { ok: false, lock: legacy }, changed: false };
676
994
  const current = parseTelegramLockEntry(locks[effectiveKey]);
677
995
  const state = getLockState(current, pid, isAlive, stateOptions());
678
996
  const expectedOwned = adoptCompatibleOwnedLock(effectiveKey, current, ctx);
@@ -714,7 +1032,9 @@ export function createTelegramLockRuntime(options = {}) {
714
1032
  changed: false,
715
1033
  };
716
1034
  }
1035
+ const journalPath = readTelegramLockJournalPath(locks[effectiveKey]) ?? options.createJournalPath?.(ctx);
717
1036
  const lock = createLockEntry(pid, ctx, {
1037
+ journalPath,
718
1038
  instanceId: options.instanceId,
719
1039
  busSocketPath: options.busSocketPath,
720
1040
  busSecret: options.busSecret,
@@ -738,13 +1058,17 @@ export function createTelegramLockRuntime(options = {}) {
738
1058
  release: () => {
739
1059
  // Withdraw local send authority even if the durable release fails.
740
1060
  deliveryRevoked = true;
741
- return withLockTransaction(locksPath, (locks) => {
1061
+ return transactOwners((locks) => {
742
1062
  const effectiveKey = resolveEffectiveKey();
743
1063
  const state = getLockState(parseTelegramLockEntry(locks[effectiveKey]), pid, isAlive, stateOptions());
744
1064
  const changed = ownedLockKey === effectiveKey &&
745
1065
  hasSameLockOwner(parseTelegramLockEntry(locks[effectiveKey]), ownedLock);
746
1066
  if (changed) {
747
- delete locks[effectiveKey];
1067
+ const journalPath = readTelegramLockJournalPath(locks[effectiveKey]);
1068
+ if (journalPath)
1069
+ locks[effectiveKey] = { journalPath };
1070
+ else
1071
+ delete locks[effectiveKey];
748
1072
  ownedLockKey = undefined;
749
1073
  ownedLock = undefined;
750
1074
  }
@@ -752,12 +1076,13 @@ export function createTelegramLockRuntime(options = {}) {
752
1076
  });
753
1077
  },
754
1078
  getState: () => getLockState(readLock(), pid, isAlive, stateOptions()),
1079
+ getJournalPath: () => readTelegramLockJournalPath(readOwners()[resolveEffectiveKey()]),
755
1080
  getStatusLabel: () => formatLockState(getLockState(readLock(), pid, isAlive, stateOptions())),
756
1081
  getOwnedLeaderEpoch: () => {
757
1082
  if (deliveryRevoked)
758
1083
  return undefined;
759
1084
  const effectiveKey = resolveEffectiveKey();
760
- const lock = parseTelegramLockEntry(readLocks(locksPath)[effectiveKey]);
1085
+ const lock = parseTelegramLockEntry(readOwners()[effectiveKey]);
761
1086
  const exactOwner = adoptCompatibleOwnedLock(effectiveKey, lock);
762
1087
  return hasSameLockOwner(lock, exactOwner) ? lock?.leaderEpoch : undefined;
763
1088
  },
@@ -765,10 +1090,10 @@ export function createTelegramLockRuntime(options = {}) {
765
1090
  if (deliveryRevoked)
766
1091
  return false;
767
1092
  const effectiveKey = resolveEffectiveKey();
768
- const lock = parseTelegramLockEntry(readLocks(locksPath)[effectiveKey]);
1093
+ const lock = parseTelegramLockEntry(readOwners()[effectiveKey]);
769
1094
  return hasSameLockOwner(lock, adoptCompatibleOwnedLock(effectiveKey, lock, ctx));
770
1095
  },
771
- commitIfOwned: (commit) => !deliveryRevoked && withLockTransaction(locksPath, (locks) => {
1096
+ commitIfOwned: (commit) => !deliveryRevoked && transactOwners((locks) => {
772
1097
  const effectiveKey = resolveEffectiveKey();
773
1098
  const lock = parseTelegramLockEntry(locks[effectiveKey]);
774
1099
  const exactOwner = ownedLockKey === effectiveKey && hasSameLockOwner(lock, ownedLock);
@@ -780,9 +1105,34 @@ export function createTelegramLockRuntime(options = {}) {
780
1105
  return { result: false, changed: false };
781
1106
  }
782
1107
  commit();
783
- return { result: true, changed: false };
1108
+ // External-file commits may retain an independent effect; never acknowledge under a changed unified owner.
1109
+ const current = !statePath || (!deliveryRevoked && hasSameLockOwner(parseTelegramLockEntry(readOwners()[effectiveKey]), ownedLock));
1110
+ return { result: current, changed: false };
784
1111
  }),
785
- refresh: (ctx) => !deliveryRevoked && withLockTransaction(locksPath, (locks) => {
1112
+ publishStateSectionIfOwned(section, mutate, publication) {
1113
+ if (section !== "workspace" && section !== "runtime")
1114
+ throw new TelegramRuntimeStateError("invalid", "Telegram owner section publication is restricted to Workspace and runtime observations.");
1115
+ if (!statePath)
1116
+ throw new TelegramRuntimeStateError("invalid", "Consolidated transport storage is not selected.");
1117
+ const profile = resolveEffectiveKey(), expected = ownedLock ? { ...ownedLock } : undefined;
1118
+ if (publication.expectedScope && (publication.expectedScope.path !== statePath || publication.expectedScope.profile !== profile))
1119
+ return { committed: false };
1120
+ if (deliveryRevoked || ownedLockKey !== profile || !expected || !publication.isCurrent())
1121
+ return { committed: false };
1122
+ const isCurrent = () => !deliveryRevoked && resolveEffectiveKey() === profile && ownedLockKey === profile &&
1123
+ hasSameLockOwner(ownedLock, expected) && publication.isCurrent() &&
1124
+ hasSameLockOwner(parseTelegramLockEntry(readOwners()[profile]), expected);
1125
+ if (!isCurrent())
1126
+ return { committed: false };
1127
+ return mutateTelegramRuntimeStateSection(statePath, profile, section, (current, observed) => {
1128
+ assertTelegramStateTransport(observed.transport);
1129
+ if (!hasSameLockOwner(parseTelegramLockEntry(observed.transport), expected))
1130
+ return { value: current, result: { committed: false } };
1131
+ const outcome = mutate(current, observed);
1132
+ return { value: outcome.value, result: { committed: true, result: outcome.result } };
1133
+ }, { ...publication, isCurrent });
1134
+ },
1135
+ refresh: (ctx) => !deliveryRevoked && transactOwners((locks) => {
786
1136
  const effectiveKey = resolveEffectiveKey();
787
1137
  const lock = parseTelegramLockEntry(locks[effectiveKey]);
788
1138
  const expectedOwner = adoptCompatibleOwnedLock(effectiveKey, lock, ctx);
@@ -806,6 +1156,7 @@ export function createTelegramLockRuntime(options = {}) {
806
1156
  ? { busSocketPath: options.busSocketPath }
807
1157
  : {}),
808
1158
  busSecret: options.busSecret ?? lock.busSecret,
1159
+ ...(lock.journalPath ? { journalPath: lock.journalPath } : {}),
809
1160
  };
810
1161
  locks[effectiveKey] = refreshedLock;
811
1162
  ownedLockKey = effectiveKey;
@@ -999,6 +1350,15 @@ export function createTelegramLockedPollingRuntime(deps) {
999
1350
  await ownershipStop;
1000
1351
  if (!isCurrent())
1001
1352
  return cancelled;
1353
+ if (!options.election && deps.resetDamagedState) {
1354
+ try {
1355
+ if (deps.resetDamagedState())
1356
+ deps.recordRuntimeEvent?.("lock", new Error("Damaged Telegram runtime state was reset; previous runtime continuity was discarded."), { phase: "state-reset" });
1357
+ }
1358
+ catch (error) {
1359
+ deps.recordRuntimeEvent?.("lock", error, { phase: "state-reset" });
1360
+ }
1361
+ }
1002
1362
  let acquired = deps.lock.acquire(ctx, {
1003
1363
  force: options.force,
1004
1364
  expectedOwner: options.election?.expectedOwner ??
@@ -1093,6 +1453,12 @@ export function createTelegramLockedPollingRuntime(deps) {
1093
1453
  return "Telegram bridge disconnected.";
1094
1454
  },
1095
1455
  suspend: suspendPolling,
1456
+ captureTransportAuthority(ctx) {
1457
+ const generation = pollingGeneration;
1458
+ const current = () => generation === pollingGeneration && activeContext === ctx && !ownershipStop &&
1459
+ (deps.isContextCurrent?.(ctx) ?? true) && deps.lock.owns(snapshotLockContext(ctx));
1460
+ return current() ? current : undefined;
1461
+ },
1096
1462
  isSuspended: () => suspendedGeneration === pollingGeneration &&
1097
1463
  suspensionsInFlight === 0 && startupsInFlight === 0 && !sessionAutoStartRun && !ownershipStop,
1098
1464
  onPersistentConflict: async (ctx, count) => {
@@ -4,22 +4,27 @@
4
4
  * Owns bounded JSONL runtime evidence files, previous-log preservation, and profile-aware log paths without becoming routing state
5
5
  */
6
6
  import * as Status from "./status.ts";
7
- export type TelegramLogPathInput = string | (() => string);
8
- export interface TelegramRuntimeJsonlEvent {
7
+ type TelegramLogPathInput = string | (() => string);
8
+ interface TelegramRuntimeJsonlEvent {
9
9
  at: number;
10
10
  category: string;
11
11
  message: string;
12
12
  details?: Record<string, unknown>;
13
13
  }
14
- export interface TelegramRuntimeJsonlLogOptions {
14
+ interface TelegramRuntimeJsonlLogOptions {
15
15
  path?: TelegramLogPathInput;
16
16
  previousPath?: TelegramLogPathInput;
17
17
  maxBytes?: number;
18
18
  getNowMs?: () => number;
19
19
  canReset?: () => boolean;
20
20
  commitReset?: (commit: () => void) => boolean;
21
+ /** Explicit shared-file protocol; profile-labelled scope reset is an append, never truncation. */
22
+ sharedProfiles?: {
23
+ getProfileName: () => string | undefined;
24
+ captureAuthority: () => (() => boolean) | undefined;
25
+ };
21
26
  }
22
- export interface TelegramRuntimeJsonlLog {
27
+ interface TelegramRuntimeJsonlLog {
23
28
  getPath: () => string;
24
29
  reset: (reason: string, scope?: Record<string, unknown>) => void;
25
30
  resetIfScopeChanged: (scopeKey: string, reason: string, scope?: Record<string, unknown>) => void;
@@ -28,7 +33,18 @@ export interface TelegramRuntimeJsonlLog {
28
33
  export declare function getTelegramRuntimeLogPath(agentDir?: string, profileName?: string): string;
29
34
  export declare function getTelegramPreviousRuntimeLogPath(agentDir?: string, profileName?: string): string;
30
35
  export declare function createTelegramRuntimeJsonlLog(options?: TelegramRuntimeJsonlLogOptions): TelegramRuntimeJsonlLog;
31
- export interface TelegramRuntimeDiagnosticsRuntime<TContext> {
36
+ interface TelegramRuntimeDiagnosticsStatusPorts<TContext> {
37
+ instanceId: string;
38
+ updateStatus(ctx: TContext, error?: string): void;
39
+ getStatusState(): Status.TelegramBridgeStatusLineState;
40
+ persistSnapshot(snapshot: ReturnType<typeof Status.createTelegramStatusSnapshot>): Promise<void>;
41
+ session?: {
42
+ get(): TContext | undefined;
43
+ getGeneration(): number;
44
+ isCurrent(ctx: TContext, generation?: number): boolean;
45
+ };
46
+ }
47
+ interface TelegramRuntimeDiagnosticsRuntime<TContext> {
32
48
  events: Status.TelegramRuntimeEventRecorder;
33
49
  recordRuntimeEvent(category: string, error: unknown, details?: Record<string, unknown>): void;
34
50
  bindStorage(ports: {
@@ -36,15 +52,17 @@ export interface TelegramRuntimeDiagnosticsRuntime<TContext> {
36
52
  getProfileName(): string | undefined;
37
53
  canReset(): boolean;
38
54
  commitReset(commit: () => void): boolean;
55
+ captureAuthority?: () => (() => boolean) | undefined;
39
56
  }): void;
40
- bindStatus(ports: {
41
- instanceId: string;
42
- updateStatus(ctx: TContext, error?: string): void;
43
- getStatusState(): Status.TelegramBridgeStatusLineState;
44
- persistSnapshot(snapshot: ReturnType<typeof Status.createTelegramStatusSnapshot>): Promise<void>;
45
- }): void;
57
+ bindStatus(ports: TelegramRuntimeDiagnosticsStatusPorts<TContext>): void;
58
+ onSessionStart(): void;
59
+ onSessionShutdown(): Promise<void>;
46
60
  updateStatus(ctx: TContext, error?: string): void;
47
61
  getStatusLines(options?: Status.TelegramBridgeStatusLineOptions): string[];
48
62
  scheduleSnapshotPersist(): void;
49
63
  }
50
- export declare function createTelegramRuntimeDiagnosticsRuntime<TContext>(): TelegramRuntimeDiagnosticsRuntime<TContext>;
64
+ export declare function createTelegramRuntimeDiagnosticsRuntime<TContext>(options?: {
65
+ sharedFile?: boolean;
66
+ snapshotTimer?: Pick<Parameters<typeof Status.createTelegramRuntimeDiagnosticsSnapshotScheduler>[0], "setTimer" | "clearTimer">;
67
+ }): TelegramRuntimeDiagnosticsRuntime<TContext>;
68
+ export {};