@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
@@ -3,7 +3,6 @@
3
3
  * Zones: filesystem diagnostics, unclean-shutdown recovery
4
4
  * Owns fail-safe classification of temporary ownership and routing artifacts
5
5
  */
6
- import { renameSync } from "node:fs";
7
6
  import { type TelegramFileTransactionOptions } from "./locks.ts";
8
7
  export type TelegramRuntimeArtifactKind = "owners" | "state" | "transaction";
9
8
  export interface TelegramRuntimeCorruptArtifact {
@@ -35,19 +34,19 @@ export type TelegramRuntimeRecoveryResult = {
35
34
  } | {
36
35
  kind: "blocked-live-owner";
37
36
  livePids: number[];
38
- quarantineDir?: string;
37
+ deletedPaths?: string[];
39
38
  } | {
40
39
  kind: "recovered";
41
40
  artifacts: TelegramRuntimeCorruptArtifact[];
42
- quarantineDir: string;
41
+ /** Damaged disposable artifacts deleted per the approved corruption policy; nothing is quarantined. */
42
+ deletedPaths: string[];
43
43
  };
44
44
  export interface TelegramRuntimeRecoveryOptions extends TelegramRuntimeRecoveryClassificationOptions {
45
45
  recoveryTransactionPath?: string;
46
- quarantineRoot?: string;
47
46
  pid?: number;
48
47
  getNowMs?: () => number;
49
- quarantineRename?: typeof renameSync;
50
- quarantineRenameRetryDelayMs?: number;
48
+ /** Fault-injection seam; production uses bounded-retry recursive removal. */
49
+ removePath?: (path: string) => void;
51
50
  transactionOptions?: TelegramFileTransactionOptions;
52
51
  }
53
52
  export type TelegramPollingStartRecoveryDecision = {
@@ -74,13 +73,34 @@ export interface TelegramPollingStartRecoveryHandlerDeps {
74
73
  */
75
74
  export declare function classifyTelegramRuntimeRecovery(options: TelegramRuntimeRecoveryClassificationOptions): TelegramRuntimeRecoveryClassification;
76
75
  /**
77
- * Quarantine classifier-approved disposable corruption under two guards.
76
+ * Delete classifier-approved disposable corruption under two guards.
78
77
  *
79
78
  * A dedicated recovery transaction serializes recoverers. The ownership
80
79
  * transaction then prevents a new Telegram owner from appearing between the
81
- * final classification and mutation. Every artifact is renamed within its
82
- * filesystem; durable config and diagnostics never enter the candidate set.
80
+ * final classification and mutation. Damaged ownership debris and canonical
81
+ * state are deleted (operator policy: unfinished Restores in unreadable state
82
+ * are acceptable loss). Durable config and diagnostics never enter the set.
83
83
  */
84
84
  export declare function recoverTelegramRuntimeState(options: TelegramRuntimeRecoveryOptions): TelegramRuntimeRecoveryResult;
85
+ /**
86
+ * Remove recovery folders written by earlier releases (runtime root and session folders).
87
+ * Current releases delete damaged files instead of quarantining them; nothing reads these copies.
88
+ */
89
+ export declare function removeTelegramLegacyRecoveryStorage(runtimeDir: string): string[];
90
+ export interface TelegramSessionFolderSweeperDeps {
91
+ getSessionsDir: () => string;
92
+ getProfileName: () => string | undefined;
93
+ /** Sessions holding a Workspace slot binding, live registrations and this process's own session. */
94
+ getKeptSessionIds: () => Iterable<string | undefined>;
95
+ getNowMs?: () => number;
96
+ intervalMs?: number;
97
+ }
98
+ /**
99
+ * Leader housekeeping (operator policy): a session without a Workspace slot loses its
100
+ * current-profile journal family; the folder disappears once no profile uses it.
101
+ */
102
+ export declare function createTelegramSessionFolderSweeper(deps: TelegramSessionFolderSweeperDeps): {
103
+ sweep: () => string[];
104
+ };
85
105
  /** Build the `/telegram-connect` recovery boundary around runtime artifacts. */
86
106
  export declare function createTelegramPollingStartRecoveryHandler(deps: TelegramPollingStartRecoveryHandlerDeps): () => Promise<TelegramPollingStartRecoveryDecision>;
@@ -3,10 +3,10 @@
3
3
  * Zones: filesystem diagnostics, unclean-shutdown recovery
4
4
  * Owns fail-safe classification of temporary ownership and routing artifacts
5
5
  */
6
- import { randomUUID } from "node:crypto";
7
- import { existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, } from "node:fs";
6
+ import { existsSync, lstatSync, readFileSync, readdirSync, rmdirSync, rmSync, } from "node:fs";
8
7
  import { basename, dirname, join } from "node:path";
9
- import { isProcessAlive as defaultIsProcessAlive, parseTelegramLockEntry, renameTelegramPathWithRetry, TELEGRAM_BUS_LEADER_STALE_HEARTBEAT_MS, withTelegramFileTransaction, } from "./locks.js";
8
+ import { decodeTelegramSessionDirectoryName, getTelegramProfilePathSuffix } from "./paths.js";
9
+ import { isProcessAlive as defaultIsProcessAlive, parseTelegramLockEntry, TELEGRAM_BUS_LEADER_STALE_HEARTBEAT_MS, withTelegramFileTransaction, } from "./locks.js";
10
10
  const OWNER_FILE_PATTERN = /^owner\.([A-Za-z0-9-]+)\.json$/u;
11
11
  const RECLAIM_FILE_PATTERN = /^owner\.reclaim\.(\d+)\.([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\.json$/u;
12
12
  function corruption(kind, path, reason) {
@@ -40,6 +40,9 @@ function inspectState(path) {
40
40
  if (!existsSync(path))
41
41
  return { source: "state", ownerPids: [] };
42
42
  try {
43
+ const stat = lstatSync(path);
44
+ if (!stat.isFile() || stat.isSymbolicLink() || stat.nlink !== 1 || stat.size > 8 * 1024 * 1024)
45
+ return corruption("state", path, `${basename(path)} cannot be safely inspected`);
43
46
  JSON.parse(readFileSync(path, "utf8"));
44
47
  return { source: "state", ownerPids: [] };
45
48
  }
@@ -136,19 +139,19 @@ export function classifyTelegramRuntimeRecovery(options) {
136
139
  : { kind: "recoverable-corruption", artifacts };
137
140
  }
138
141
  /**
139
- * Quarantine classifier-approved disposable corruption under two guards.
142
+ * Delete classifier-approved disposable corruption under two guards.
140
143
  *
141
144
  * A dedicated recovery transaction serializes recoverers. The ownership
142
145
  * transaction then prevents a new Telegram owner from appearing between the
143
- * final classification and mutation. Every artifact is renamed within its
144
- * filesystem; durable config and diagnostics never enter the candidate set.
146
+ * final classification and mutation. Damaged ownership debris and canonical
147
+ * state are deleted (operator policy: unfinished Restores in unreadable state
148
+ * are acceptable loss). Durable config and diagnostics never enter the set.
145
149
  */
146
150
  export function recoverTelegramRuntimeState(options) {
147
- const pid = options.pid ?? process.pid;
148
151
  const transactionPath = options.transactionPath ?? `${options.ownersPath}.transaction`;
149
152
  const recoveryTransactionPath = options.recoveryTransactionPath ??
150
153
  join(dirname(options.ownersPath), "runtime-recovery.transaction");
151
- const quarantineRoot = options.quarantineRoot ?? join(dirname(options.ownersPath), "recovery");
154
+ const removePath = options.removePath ?? ((path) => rmSync(path, { recursive: true, force: true, maxRetries: 3, retryDelay: 50 }));
152
155
  const classificationOptions = {
153
156
  ownersPath: options.ownersPath,
154
157
  statePaths: options.statePaths,
@@ -167,29 +170,18 @@ export function recoverTelegramRuntimeState(options) {
167
170
  livePids: initial.livePids,
168
171
  };
169
172
  }
170
- let quarantineDir;
173
+ const deletedPaths = [];
171
174
  const recoveredArtifacts = [];
172
- const ensureQuarantineDir = () => {
173
- if (quarantineDir)
174
- return quarantineDir;
175
- quarantineDir = join(quarantineRoot, `${options.getNowMs?.() ?? Date.now()}-${pid}-${randomUUID()}`);
176
- mkdirSync(quarantineDir, { recursive: true, mode: 0o700 });
177
- return quarantineDir;
178
- };
179
- const quarantineArtifact = (artifact) => {
175
+ const deleteArtifact = (artifact) => {
180
176
  if (!existsSync(artifact.path))
181
177
  return;
182
- const destination = join(ensureQuarantineDir(), basename(artifact.path));
183
- if (renameTelegramPathWithRetry(artifact.path, destination, {
184
- rename: options.quarantineRename,
185
- retryDelayMs: options.quarantineRenameRetryDelayMs,
186
- })) {
187
- recoveredArtifacts.push(artifact);
188
- }
178
+ removePath(artifact.path);
179
+ deletedPaths.push(artifact.path);
180
+ recoveredArtifacts.push(artifact);
189
181
  };
190
182
  for (const artifact of initial.artifacts) {
191
183
  if (artifact.kind === "transaction")
192
- quarantineArtifact(artifact);
184
+ deleteArtifact(artifact);
193
185
  }
194
186
  return withTelegramFileTransaction(transactionPath, () => {
195
187
  const current = classifyTelegramRuntimeRecovery({
@@ -200,25 +192,107 @@ export function recoverTelegramRuntimeState(options) {
200
192
  return {
201
193
  kind: "blocked-live-owner",
202
194
  livePids: current.livePids,
203
- quarantineDir,
195
+ ...(deletedPaths.length > 0 ? { deletedPaths } : {}),
204
196
  };
205
197
  }
206
198
  if (current.kind === "recoverable-corruption") {
207
199
  for (const artifact of current.artifacts) {
208
200
  if (artifact.kind !== "transaction")
209
- quarantineArtifact(artifact);
201
+ deleteArtifact(artifact);
210
202
  }
211
203
  }
212
- return recoveredArtifacts.length > 0 && quarantineDir
204
+ return recoveredArtifacts.length > 0
213
205
  ? {
214
206
  kind: "recovered",
215
207
  artifacts: recoveredArtifacts,
216
- quarantineDir,
208
+ deletedPaths,
217
209
  }
218
210
  : { kind: "not-needed" };
219
211
  }, options.transactionOptions);
220
212
  }, options.transactionOptions);
221
213
  }
214
+ /**
215
+ * Remove recovery folders written by earlier releases (runtime root and session folders).
216
+ * Current releases delete damaged files instead of quarantining them; nothing reads these copies.
217
+ */
218
+ export function removeTelegramLegacyRecoveryStorage(runtimeDir) {
219
+ const removed = [];
220
+ const remove = (path) => {
221
+ try {
222
+ if (!lstatSync(path).isDirectory())
223
+ return;
224
+ rmSync(path, { recursive: true, force: true, maxRetries: 3, retryDelay: 50 });
225
+ removed.push(path);
226
+ }
227
+ catch {
228
+ // Best-effort housekeeping; a later startup retries.
229
+ }
230
+ };
231
+ remove(join(runtimeDir, "recovery"));
232
+ let sessions = [];
233
+ try {
234
+ sessions = readdirSync(join(runtimeDir, "sessions"));
235
+ }
236
+ catch { /* no sessions yet */ }
237
+ for (const session of sessions)
238
+ remove(join(runtimeDir, "sessions", session, "recovery"));
239
+ return removed;
240
+ }
241
+ const TELEGRAM_SESSION_SWEEP_INTERVAL_MS = 10 * 60 * 1000;
242
+ /**
243
+ * Leader housekeeping (operator policy): a session without a Workspace slot loses its
244
+ * current-profile journal family; the folder disappears once no profile uses it.
245
+ */
246
+ export function createTelegramSessionFolderSweeper(deps) {
247
+ const getNowMs = deps.getNowMs ?? Date.now;
248
+ const intervalMs = deps.intervalMs ?? TELEGRAM_SESSION_SWEEP_INTERVAL_MS;
249
+ let lastSweepAtMs;
250
+ return {
251
+ sweep() {
252
+ const now = getNowMs();
253
+ if (lastSweepAtMs !== undefined && now - lastSweepAtMs < intervalMs)
254
+ return [];
255
+ lastSweepAtMs = now;
256
+ const sessionsDir = deps.getSessionsDir();
257
+ const kept = new Set(deps.getKeptSessionIds());
258
+ const suffix = getTelegramProfilePathSuffix(deps.getProfileName()).replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
259
+ const family = new RegExp(`^journal\\.[a-f0-9]{16}${suffix}\\.json(?:\\.segments|\\.retained)?$`, "u");
260
+ const removed = [];
261
+ let names;
262
+ try {
263
+ names = readdirSync(sessionsDir);
264
+ }
265
+ catch {
266
+ return removed;
267
+ }
268
+ for (const name of names) {
269
+ const sessionId = decodeTelegramSessionDirectoryName(name);
270
+ if (sessionId === undefined || kept.has(sessionId))
271
+ continue;
272
+ const folder = join(sessionsDir, name);
273
+ try {
274
+ if (!lstatSync(folder).isDirectory())
275
+ continue;
276
+ for (const entry of readdirSync(folder)) {
277
+ if (!family.test(entry))
278
+ continue;
279
+ const path = join(folder, entry);
280
+ rmSync(path, { recursive: true, force: true, maxRetries: 3, retryDelay: 50 });
281
+ removed.push(path);
282
+ }
283
+ if (readdirSync(folder).length === 0) {
284
+ rmdirSync(folder);
285
+ removed.push(folder);
286
+ }
287
+ }
288
+ catch {
289
+ // Best-effort housekeeping; the next sweep retries.
290
+ }
291
+ }
292
+ return removed;
293
+ },
294
+ };
295
+ }
222
296
  /** Build the `/telegram-connect` recovery boundary around runtime artifacts. */
223
297
  export function createTelegramPollingStartRecoveryHandler(deps) {
224
298
  return async () => {
@@ -257,10 +331,7 @@ export function createTelegramPollingStartRecoveryHandler(deps) {
257
331
  });
258
332
  }
259
333
  try {
260
- const recovery = recoverTelegramRuntimeState({
261
- ownersPath,
262
- statePaths,
263
- });
334
+ const recovery = recoverTelegramRuntimeState({ ownersPath, statePaths });
264
335
  if (recovery.kind === "blocked-live-owner") {
265
336
  return {
266
337
  kind: "blocked",
@@ -117,10 +117,6 @@ export declare function sendTelegramNativeMarkdownReply<TReplyMarkup = unknown>(
117
117
  }, options?: TelegramReplyTargetOptions & {
118
118
  replyMarkup?: TReplyMarkup;
119
119
  }): Promise<number | undefined>;
120
- export declare function sendTelegramNativeRichMessage(chatId: number, richMessage: TelegramInputRichMessage, deps: {
121
- recordOwnership?: TelegramReplyOwnershipRecorder["record"];
122
- sendRichMessage: (body: TelegramSendRichMessageBody) => Promise<TelegramSentMessage>;
123
- }, options?: TelegramReplyTargetOptions): Promise<number>;
124
120
  export type TelegramAssistantRenderingMode = "rich" | "html";
125
121
  export interface TelegramRenderedMessageRuntimeDeps<TReplyMarkup> {
126
122
  renderTelegramMessage: (text: string, options?: {
@@ -498,7 +498,7 @@ export async function sendTelegramNativeMarkdownReply(chatId, replyToMessageId,
498
498
  }
499
499
  return lastMessageId;
500
500
  }
501
- export async function sendTelegramNativeRichMessage(chatId, richMessage, deps, options) {
501
+ async function sendTelegramNativeRichMessage(chatId, richMessage, deps, options) {
502
502
  const sent = await deps.sendRichMessage({
503
503
  chat_id: chatId,
504
504
  rich_message: richMessage,
@@ -7,6 +7,7 @@ import * as Bus from "./bus.ts";
7
7
  import * as Commands from "./commands.ts";
8
8
  import type { TelegramConfigStore } from "./config.ts";
9
9
  import type { TelegramInboundHandlerRuntime } from "./inbound.ts";
10
+ import type { TelegramUpdateJournalQueuedCompletion, TelegramUpdateJournalQueuedReceiptEvidence } from "./journal.ts";
10
11
  import * as Media from "./media.ts";
11
12
  import * as Menu from "./menu.ts";
12
13
  import * as Model from "./model.ts";
@@ -15,11 +16,12 @@ import * as PromptTemplates from "./prompt-templates.ts";
15
16
  import * as Queue from "./queue.ts";
16
17
  import type { TelegramBridgeRuntime } from "./runtime.ts";
17
18
  import type { TelegramSectionRegistry } from "./sections.ts";
18
- import type { TelegramInputRichMessage } from "./telegram-api.ts";
19
+ import type { TelegramApiCallOptions, TelegramInputRichMessage } from "./telegram-api.ts";
19
20
  import * as TextGroups from "./text-groups.ts";
20
21
  import * as ThreadReconciler from "./thread-reconciler.ts";
21
22
  import type { TelegramInstanceThreadIdentityCandidate, TelegramTopicTargetRecord } from "./threads.ts";
22
23
  import * as Turns from "./turns.ts";
24
+ import type { createTelegramWorkspaceExternalProtectionCapture } from "./workspace-retirement.ts";
23
25
  interface TelegramPromptPeerView {
24
26
  id?: unknown;
25
27
  is_bot?: unknown;
@@ -37,9 +39,37 @@ export declare function resolveTelegramGuestPromptPeer(input: {
37
39
  guestBotCallerChat?: TelegramPromptPeerView;
38
40
  ownerUserId?: number;
39
41
  }): string | undefined;
42
+ /** Stable file scope of the remote Guest Mode peer: username, else numeric id; never the bot's own scope. */
43
+ export declare function resolveTelegramGuestFileScope(input: Parameters<typeof resolveTelegramGuestPromptPeer>[0]): string;
40
44
  import * as Threads from "./threads.ts";
41
45
  import * as Updates from "./updates.ts";
46
+ /** One admitted Restore attempt through recipient readiness, never source dispatch or cleanup. */
47
+ export declare function advanceTelegramWorkspaceRestore(input: {
48
+ request: Threads.TelegramWorkspaceRestoreRequest;
49
+ authority: Threads.TelegramWorkspaceRestoreAuthority;
50
+ restoreStore: Threads.TelegramWorkspaceRestore;
51
+ getRecipient: () => Threads.TelegramWorkspaceRestoreRecipient | undefined;
52
+ /** Adapter proves current canonical ownership and authenticates observations; apply needs a fresh issuance. */
53
+ runRecipient: (input: {
54
+ intent: Threads.TelegramWorkspaceRestoreIntent;
55
+ mode: "apply" | "inspect";
56
+ isCurrent: () => boolean;
57
+ }) => Promise<{
58
+ operationId: string;
59
+ recipient: Threads.TelegramWorkspaceRestoreRecipient;
60
+ target: Threads.TelegramWorkspaceRestoreRequest["target"];
61
+ slot: string;
62
+ ready: boolean;
63
+ } | undefined>;
64
+ /**
65
+ * Recovery for a same-session successor already on the relocated target: it may receive the first grant, but only
66
+ * read-only inspection runs. It never commits a new relocation or applies a target.
67
+ */
68
+ inspectOnly?: true;
69
+ }): Promise<Threads.TelegramWorkspaceRestoreIntent | undefined>;
42
70
  export declare const TELEGRAM_ALL_TAB_COMMAND_MAX_AGE_MS: number;
71
+ /** One user-facing answer for expired or previous-process routing controls. */
72
+ export declare const TELEGRAM_ROUTING_CHOICE_EXPIRED = "\u231B Routing choice expired.";
43
73
  export declare function isTelegramAllTabCommandExpired(message: {
44
74
  date?: number;
45
75
  message_thread_id?: number;
@@ -66,7 +96,7 @@ export interface TelegramInboundRouteRuntimeDeps<TMessage extends TelegramRouted
66
96
  configStore: Pick<TelegramConfigStore, "get" | "getAllowedUserId" | "persistAllowedUserId" | "persist"> & {
67
97
  set?: TelegramConfigStore["set"];
68
98
  };
69
- callApi?: <TResponse>(method: string, body: Record<string, unknown>) => Promise<TResponse>;
99
+ callApi?: <TResponse>(method: string, body: Record<string, unknown>, options?: TelegramApiCallOptions) => Promise<TResponse>;
70
100
  getCurrentInstanceId?: () => string | undefined;
71
101
  getAdmissionScope?: () => string | undefined;
72
102
  getAdmissionJournalBinding?: () => string | undefined;
@@ -87,11 +117,32 @@ export interface TelegramInboundRouteRuntimeDeps<TMessage extends TelegramRouted
87
117
  handleTelegramTopicLifecycleUpdate?: (lifecycle: Updates.TelegramTopicLifecycleUpdate<TMessage>, ctx: TContext) => Promise<void> | void;
88
118
  handleTelegramThreadTargetObserved?: (target: Threads.TelegramTopicTargetRecord["target"], ctx: TContext) => Promise<void> | void;
89
119
  foreignOwnedUpdateForwarder?: Updates.TelegramForeignOwnedUpdateForwarder<TContext, Updates.TelegramMessageReactionUpdated, TCallbackQuery, TMessage>;
90
- replaceFollowerThreadTarget?: (input: {
91
- record: Threads.TelegramTopicTargetRecord;
92
- target: Threads.TelegramTopicTargetRecord["target"];
93
- oldTarget: Threads.TelegramTopicTargetRecord["target"];
94
- }) => Promise<boolean>;
120
+ getWorkspaceRestoreStore?: () => Threads.TelegramWorkspaceRestore | undefined;
121
+ captureWorkspaceExternalProtection?: ReturnType<typeof createTelegramWorkspaceExternalProtectionCapture>;
122
+ /** Strict committed abandonment plus retained original; shared by Restore and temporary-input cancellation. */
123
+ inspectRestoreSourceAbandonment?: (updateId: number, journalBindingKey: string) => Threads.TelegramTemporaryThreadCancellationEvidence | undefined;
124
+ /** Strict active-journal observation only; a hint or missing source never substitutes for this ACK. */
125
+ inspectRestoreSourceCompletion?: (expected: Updates.TelegramDeferredSourceEvidence & {
126
+ completionSha256: string;
127
+ }) => (Updates.TelegramDeferredSourceEvidence & {
128
+ completionSha256: string;
129
+ }) | undefined;
130
+ inspectRestoreQueuedReceipt?: (expected: TelegramUpdateJournalQueuedCompletion & {
131
+ journalBindingKey: string;
132
+ }) => TelegramUpdateJournalQueuedReceiptEvidence | undefined;
133
+ hasWorkspaceRestoreAuthority?: () => boolean;
134
+ /** Strict complete namespace plus exact current/historical references; only an empty result clears journal protection. */
135
+ inspectTemporaryThreadSources?: (target: Queue.TelegramQueueTarget, requiredJournalBindingKeys: readonly string[]) => readonly number[] | undefined;
136
+ /** Quiet period after the last cancelled input before one cleanup attempt; defaults to 1000 ms. */
137
+ temporaryThreadCleanupDelayMs?: number;
138
+ getSessionGeneration?: () => number;
139
+ workspaceRestoreRecipient?: {
140
+ getSessionId: (ctx: TContext) => string | undefined;
141
+ getCwd: (ctx: TContext) => string | undefined;
142
+ getLeaderIdentity: Threads.TelegramLeaderThreadStateRuntime["getIdentity"];
143
+ followerRegistry: Pick<Bus.TelegramBusFollowerRegistry, "get" | "register">;
144
+ runFollower: ReturnType<typeof Bus.createTelegramBusWorkspaceRestoreController>;
145
+ };
95
146
  bridgeRuntime: TelegramBridgeRuntime;
96
147
  activeTurnRuntime: Queue.TelegramActiveTurnStore;
97
148
  mediaGroupRuntime: Media.TelegramMediaGroupController<TMessage, TContext>;
@@ -183,7 +234,21 @@ export declare function createTelegramInboundRouteRuntime<TUpdate extends Update
183
234
  message?: TMessage;
184
235
  edited_message?: TMessage;
185
236
  callback_query?: TCallbackQuery;
186
- }, TMessage extends TelegramRoutedMessage, TCallbackQuery extends TelegramRoutedCallbackQuery, TContext, TModel extends Model.MenuModel>(deps: TelegramInboundRouteRuntimeDeps<TMessage, TCallbackQuery, TContext, TModel>): Updates.TelegramUpdateRuntimeController<TContext, TUpdate>;
237
+ }, TMessage extends TelegramRoutedMessage, TCallbackQuery extends TelegramRoutedCallbackQuery, TContext, TModel extends Model.MenuModel>(deps: TelegramInboundRouteRuntimeDeps<TMessage, TCallbackQuery, TContext, TModel>): Updates.TelegramUpdateRuntimeController<TContext, TUpdate> & {
238
+ expireRoutingInput: NonNullable<Updates.TelegramUpdateWorkerRuntimeDeps<TContext>["expireRoutingInput"]>;
239
+ shouldReviewHistoricalInput: NonNullable<Updates.TelegramUpdateWorkerRuntimeDeps<TContext>["shouldReviewHistoricalInput"]>;
240
+ shouldHoldPendingInput: NonNullable<Updates.TelegramUpdateWorkerRuntimeDeps<TContext>["shouldHoldPendingInput"]>;
241
+ forgetPreviousWorld(input: Updates.TelegramHeldSourcePreparation<TContext>, captureTransport?: (ctx: TContext) => (() => boolean) | undefined): Promise<{
242
+ forgotten: number;
243
+ deleted: number;
244
+ }>;
245
+ beforeQueueReceiptPublished: NonNullable<Updates.TelegramUpdateWorkerRuntimeDeps<TContext>["beforeQueueReceiptPublished"]>;
246
+ onQueueReceiptCommitted: NonNullable<Updates.TelegramUpdateWorkerRuntimeDeps<TContext>["onQueueReceiptCommitted"]>;
247
+ onQueueReceiptCompleted: NonNullable<Updates.TelegramUpdateWorkerRuntimeDeps<TContext>["onQueueReceiptCompleted"]>;
248
+ onUpdateCompleted: NonNullable<Updates.TelegramUpdateWorkerRuntimeDeps<TContext>["onUpdateCompleted"]>;
249
+ onWorkspaceRestoreRecipientObserved(follower: Bus.TelegramBusFollowerView, isCurrent: () => boolean, ctx: TContext | undefined): Promise<void> | undefined;
250
+ waitForRestoreSettlement(): Promise<void>;
251
+ };
187
252
  export interface TelegramAssistantOutputAuthority<TTransportStamp> {
188
253
  transportStamp: TTransportStamp;
189
254
  route: "direct" | "follower" | "none";