@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,13 +3,15 @@
3
3
  * Zones: telemetry paths, filesystem, runtime identity
4
4
  * Owns agent-dir detection and extension-local path derivation
5
5
  *
6
- * This domain is pure/path-only: it resolves directories and file paths
7
- * from environment and runtime identity. It does not read config, manage
8
- * state, or import broader Telegram domains.
6
+ * This domain is path-only: it resolves directories and file paths from
7
+ * environment and runtime identity. Its only filesystem read canonicalizes the
8
+ * existing agent-dir prefix; it does not read config, manage state, or import
9
+ * broader Telegram domains.
9
10
  */
10
11
  import { createHash } from "node:crypto";
12
+ import { realpathSync } from "node:fs";
11
13
  import { homedir } from "node:os";
12
- import { isAbsolute, join, resolve } from "node:path";
14
+ import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
13
15
  export const TELEGRAM_DEFAULT_PROFILE_NAME = "default";
14
16
  /**
15
17
  * Resolve the agent data directory for the current Pi-compatible runtime.
@@ -50,11 +52,53 @@ export function requireTelegramStoragePathReference(path, expectedPath) {
50
52
  export function resolveTelegramConfigPath() {
51
53
  return join(resolveAgentDir(), "telegram.json");
52
54
  }
53
- /** Telegram bridge temporary directory (<agentDir>/tmp/telegram). */
55
+ /**
56
+ * Resolve symlinks in the longest existing prefix of `path` and keep the missing suffix. Strict journal reads require
57
+ * canonical anchors, so a symlinked agent directory (for example macOS `/var` → `/private/var`) must not leak in.
58
+ * Relative input is returned unchanged so callers' exact-absolute-path guards still reject it. Windows keeps its
59
+ * spelling: strict journal reads are POSIX-only there, and resolving a drive-less root would add a drive letter.
60
+ */
61
+ function canonicalizeExistingPrefix(path) {
62
+ if (!isAbsolute(path) || process.platform === "win32")
63
+ return path;
64
+ const absolute = resolve(path);
65
+ const missing = [];
66
+ for (let current = absolute;; current = dirname(current)) {
67
+ try {
68
+ return join(realpathSync(current), ...missing.reverse());
69
+ }
70
+ catch (error) {
71
+ if (error.code !== "ENOENT" || dirname(current) === current)
72
+ return absolute;
73
+ missing.push(basename(current));
74
+ }
75
+ }
76
+ }
77
+ /** Telegram bridge temporary directory (<agentDir>/tmp/pi-telegram); releases before 0.52.0 used `tmp/telegram`. */
54
78
  export function resolveTelegramTempDir(agentDir = resolveAgentDir()) {
55
- return join(agentDir, "tmp", "telegram");
79
+ return join(canonicalizeExistingPrefix(agentDir), "tmp", "pi-telegram");
80
+ }
81
+ /** Consolidated-root service artifacts (transaction guards, staging and IPC), never session custody. */
82
+ export function resolveTelegramRuntimeDir(agentDir = resolveAgentDir()) {
83
+ return join(resolveTelegramTempDir(agentDir), "runtime");
84
+ }
85
+ /** Rotated shared runtime event segments, outside the persistent root-file census. */
86
+ export function resolveTelegramRuntimeLogsDir(agentDir = resolveAgentDir()) {
87
+ return join(resolveTelegramTempDir(agentDir), "logs");
88
+ }
89
+ /** Consolidated runtime state for every logical profile (`tmp/pi-telegram/state.json`). */
90
+ export function resolveTelegramStatePath(agentDir = resolveAgentDir()) {
91
+ return join(resolveTelegramTempDir(agentDir), "state.json");
92
+ }
93
+ /** Flat attachment scratch directory (<agentDir>/tmp/pi-telegram/attachments). */
94
+ export function resolveTelegramAttachmentsDir(agentDir = resolveAgentDir()) {
95
+ return join(resolveTelegramTempDir(agentDir), "attachments");
56
96
  }
57
- /** Telegram transport ownership store (<agentDir>/tmp/telegram/owners.json). */
97
+ /** Read-only pre-0.52.0 ownership file, consulted only to refuse a second poller beside a live older leader. */
98
+ export function resolveLegacyTelegramOwnersPath(agentDir = resolveAgentDir()) {
99
+ return join(agentDir, "tmp", "telegram", "owners.json");
100
+ }
101
+ /** Telegram transport ownership store (<agentDir>/tmp/pi-telegram/owners.json). */
58
102
  export function resolveTelegramOwnersPath() {
59
103
  return join(resolveTelegramTempDir(), "owners.json");
60
104
  }
@@ -66,15 +110,13 @@ export function getTelegramProfilePathSuffix(profileName) {
66
110
  export function resolveTelegramProfileTempFilePath(baseName, extension, agentDir = resolveAgentDir(), profileName) {
67
111
  return join(resolveTelegramTempDir(agentDir), `${baseName}${getTelegramProfilePathSuffix(profileName)}.${extension}`);
68
112
  }
69
- export function getTelegramDiagnosticsDisplayPaths(profileName) {
70
- const suffix = getTelegramProfilePathSuffix(profileName);
71
- const profileSlug = suffix.slice(1);
113
+ export function getTelegramDiagnosticsDisplayPaths(_profileName) {
72
114
  return {
73
- state: `~/.pi/agent/tmp/telegram/state${suffix}.json`,
74
- logs: `~/.pi/agent/tmp/telegram/logs${profileSlug ? `.${profileSlug}` : ""}.jsonl`,
115
+ state: "~/.pi/agent/tmp/pi-telegram/state.json",
116
+ logs: "~/.pi/agent/tmp/pi-telegram/logs.jsonl",
75
117
  };
76
118
  }
77
- /** Durable Workspace admission ledger (<agentDir>/tmp/telegram/workspace-admission[.<profile>].json). */
119
+ /** Durable Workspace admission ledger (<agentDir>/tmp/pi-telegram/workspace-admission[.<profile>].json). */
78
120
  export function resolveTelegramWorkspaceAdmissionPath(agentDir = resolveAgentDir(), profileName) {
79
121
  return resolveTelegramProfileTempFilePath("workspace-admission", "json", agentDir, profileName);
80
122
  }
@@ -82,6 +124,24 @@ export function resolveTelegramWorkspaceAdmissionPath(agentDir = resolveAgentDir
82
124
  export function resolveTelegramWorkspaceAdmissionPathForProfile(profileName) {
83
125
  return resolveTelegramWorkspaceAdmissionPath(resolveAgentDir(), profileName);
84
126
  }
127
+ /** Consolidated non-session service journal (`journals/<kind>.<profile sha256>.json`) and its runtime namespace. */
128
+ export function resolveTelegramServiceJournalStorage(kind, agentDir = resolveAgentDir(), profileName = TELEGRAM_DEFAULT_PROFILE_NAME) {
129
+ const profileHash = createHash("sha256").update(profileName).digest("hex");
130
+ return {
131
+ path: join(resolveTelegramTempDir(agentDir), "journals", `${kind}.${profileHash}.json`),
132
+ runtimeDir: resolveTelegramRuntimeDir(agentDir),
133
+ };
134
+ }
135
+ /** Bind one non-session journal to its exact colocated-root service namespace, never a format fallback. */
136
+ export function getTelegramJournalPublicationPaths(path, runtimeDir) {
137
+ if (runtimeDir === undefined)
138
+ return { transactionPath: `${path}.transaction`, temporaryBasePath: path };
139
+ requireTelegramStoragePathReference(path, resolve(path));
140
+ requireTelegramStoragePathReference(runtimeDir, join(dirname(dirname(path)), "runtime"));
141
+ if (basename(dirname(path)) !== "journals")
142
+ throw new Error("Telegram service journal must use its journals namespace.");
143
+ return { transactionPath: join(runtimeDir, `${basename(path)}.transaction`), temporaryBasePath: join(runtimeDir, basename(path)) };
144
+ }
85
145
  /** Durable inactive Thread cleanup work-set journal. */
86
146
  export function resolveTelegramThreadCleanupWorkPath(agentDir = resolveAgentDir(), profileName) {
87
147
  return resolveTelegramProfileTempFilePath("thread-cleanup", "json", agentDir, profileName);
@@ -90,7 +150,7 @@ export function resolveTelegramThreadCleanupWorkPath(agentDir = resolveAgentDir(
90
150
  export function resolveTelegramChannelPostJournalPath(agentDir = resolveAgentDir(), profileName) {
91
151
  return resolveTelegramProfileTempFilePath("channel-posts", "json", agentDir, profileName);
92
152
  }
93
- /** Durable inbound update journal (<agentDir>/tmp/telegram/inbox[.<profile>].json). */
153
+ /** Durable inbound update journal (<agentDir>/tmp/pi-telegram/inbox[.<profile>].json). */
94
154
  export function resolveTelegramUpdateJournalPath(agentDir = resolveAgentDir(), profileName) {
95
155
  return resolveTelegramProfileTempFilePath("inbox", "json", agentDir, profileName);
96
156
  }
@@ -109,7 +169,85 @@ export function resolveTelegramFollowerJournalPath(recipientBindingKey, agentDir
109
169
  .slice(0, 16);
110
170
  return resolveTelegramProfileTempFilePath(`follower-inbox-${bindingHash}`, "json", agentDir, profileName);
111
171
  }
112
- /** Runtime event log (<agentDir>/tmp/telegram/logs.jsonl). */
113
- export function resolveTelegramRuntimeLogPath() {
114
- return resolveTelegramProfileTempFilePath("logs", "jsonl");
172
+ const TELEGRAM_SESSION_DIRECTORY_MAX_LENGTH = 200;
173
+ const WINDOWS_RESERVED_NAME = /^(?:con|prn|aux|nul|com[0-9]|lpt[0-9])(?:\..*)?$/u;
174
+ /**
175
+ * Directory name for a session id. Lowercase letters, digits, `_`, `-` and inner `.` stay verbatim, so every real
176
+ * (UUID-shaped) id is unchanged; any other UTF-8 byte is percent-encoded. Uppercase is encoded so case-insensitive
177
+ * filesystems never merge two sessions; a leading or trailing dot and Windows reserved names are encoded so the name
178
+ * can never be `.`/`..`, hidden, or a device. The encoding is reversible; an empty or over-long result is refused.
179
+ */
180
+ export function encodeTelegramSessionDirectoryName(sessionId) {
181
+ if (typeof sessionId !== "string" || sessionId.length === 0)
182
+ return undefined;
183
+ const bytes = Buffer.from(sessionId, "utf8");
184
+ if (bytes.toString("utf8") !== sessionId)
185
+ return undefined;
186
+ let name = "";
187
+ bytes.forEach((byte, index) => {
188
+ const char = String.fromCharCode(byte);
189
+ const safe = /[a-z0-9_-]/u.test(char) || (char === "." && index > 0 && index < bytes.length - 1);
190
+ name += safe ? char : `%${byte.toString(16).toUpperCase().padStart(2, "0")}`;
191
+ });
192
+ if (WINDOWS_RESERVED_NAME.test(name))
193
+ name = `%${name.charCodeAt(0).toString(16).toUpperCase().padStart(2, "0")}${name.slice(1)}`;
194
+ return name.length <= TELEGRAM_SESSION_DIRECTORY_MAX_LENGTH ? name : undefined;
195
+ }
196
+ /** Inverse of `encodeTelegramSessionDirectoryName`; undefined unless the name is exactly canonical. */
197
+ export function decodeTelegramSessionDirectoryName(name) {
198
+ if (!/^[a-z0-9_.%A-F-]+$/u.test(name))
199
+ return undefined;
200
+ const bytes = [];
201
+ for (let index = 0; index < name.length; index++) {
202
+ if (name[index] === "%") {
203
+ const hex = name.slice(index + 1, index + 3);
204
+ if (!/^[0-9A-F]{2}$/u.test(hex))
205
+ return undefined;
206
+ bytes.push(Number.parseInt(hex, 16));
207
+ index += 2;
208
+ }
209
+ else
210
+ bytes.push(name.charCodeAt(index));
211
+ }
212
+ const decoded = Buffer.from(bytes).toString("utf8");
213
+ return encodeTelegramSessionDirectoryName(decoded) === name ? decoded : undefined;
214
+ }
215
+ /** Root of every session folder (<agentDir>/tmp/pi-telegram/sessions). */
216
+ export function resolveTelegramSessionsDir(agentDir = resolveAgentDir()) {
217
+ return join(resolveTelegramTempDir(agentDir), "sessions");
218
+ }
219
+ /** One session's folder (<agentDir>/tmp/pi-telegram/sessions/<session id>). */
220
+ export function resolveTelegramSessionDir(sessionId, agentDir = resolveAgentDir()) {
221
+ const name = encodeTelegramSessionDirectoryName(sessionId);
222
+ if (!name)
223
+ throw new Error("Telegram session directory requires a usable session id.");
224
+ return join(resolveTelegramSessionsDir(agentDir), name);
225
+ }
226
+ /** Hash naming one recipient incarnation inside its session folder; unchanged from the former follower inbox. */
227
+ export function getTelegramRecipientJournalHash(recipientBindingKey) {
228
+ if (!recipientBindingKey)
229
+ throw new Error("Telegram session journal binding key is required.");
230
+ return createHash("sha256").update(recipientBindingKey).digest("hex").slice(0, 16);
231
+ }
232
+ /** Durable per-session journal (`sessions/<session id>/journal.<hash>[.<profile>].json`); the name never reveals a role. */
233
+ export function resolveTelegramSessionJournalPath(sessionId, recipientBindingKey, agentDir = resolveAgentDir(), profileName) {
234
+ return join(resolveTelegramSessionDir(sessionId, agentDir), `journal.${getTelegramRecipientJournalHash(recipientBindingKey)}${getTelegramProfilePathSuffix(profileName)}.json`);
235
+ }
236
+ /** Leader polling journal hosted by the session that first led (`sessions/<id>/inbox[.<profile>].json`); owners.json names it. */
237
+ export function resolveTelegramSessionPollingJournalPath(sessionId, agentDir = resolveAgentDir(), profileName) {
238
+ return join(resolveTelegramSessionDir(sessionId, agentDir), `inbox${getTelegramProfilePathSuffix(profileName)}.json`);
239
+ }
240
+ /** Accept only a canonical `sessions/<id>/inbox[.<profile>].json` beneath this agent's runtime. */
241
+ export function isTelegramSessionPollingJournalPath(path, agentDir = resolveAgentDir()) {
242
+ const parts = relative(resolveTelegramSessionsDir(agentDir), path).split(sep);
243
+ return isAbsolute(path) && parts.length === 2 && decodeTelegramSessionDirectoryName(parts[0]) !== undefined &&
244
+ /^inbox(?:\.[a-zA-Z0-9._-]+)?\.json$/u.test(parts[1]);
245
+ }
246
+ /** Runtime event log (<agentDir>/tmp/pi-telegram/logs.jsonl). */
247
+ export function resolveTelegramRuntimeLogPath(agentDir = resolveAgentDir()) {
248
+ return resolveTelegramProfileTempFilePath("logs", "jsonl", agentDir);
249
+ }
250
+ /** Bounded previous shared log segment below `logs/`. */
251
+ export function resolveTelegramPreviousSharedRuntimeLogPath(agentDir = resolveAgentDir()) {
252
+ return join(resolveTelegramRuntimeLogsDir(agentDir), "logs._prev.jsonl");
115
253
  }
@@ -68,6 +68,7 @@ export declare function createScopedModelPatternPersister(deps: {
68
68
  export declare function getExtensionContextModel(ctx: ExtensionContext): ExtensionContext["model"];
69
69
  export declare function getExtensionContextCwd(ctx: ExtensionContext): string;
70
70
  export declare function getExtensionContextSessionId(ctx: ExtensionContext): string;
71
+ export declare function getExtensionContextSessionId(ctx: ExtensionContext | undefined): string | undefined;
71
72
  export declare function isExtensionContextIdle(ctx: ExtensionContext): boolean;
72
73
  export declare function hasExtensionContextPendingMessages(ctx: ExtensionContext): boolean;
73
74
  export declare function compactExtensionContext(ctx: ExtensionContext, callbacks: Parameters<ExtensionContext["compact"]>[0]): ReturnType<ExtensionContext["compact"]>;
@@ -109,7 +109,8 @@ export function getExtensionContextCwd(ctx) {
109
109
  return ctx.cwd;
110
110
  }
111
111
  export function getExtensionContextSessionId(ctx) {
112
- return ctx.sessionManager.getSessionId();
112
+ // Minimal hosts and stale contexts may omit a session manager; absence is not an invented ID.
113
+ return ctx?.sessionManager?.getSessionId?.();
113
114
  }
114
115
  export function isExtensionContextIdle(ctx) {
115
116
  return ctx.isIdle();
@@ -11,7 +11,6 @@ export interface TelegramUpdate {
11
11
  update_id: number;
12
12
  }
13
13
  export declare const TELEGRAM_GET_UPDATES_CONFLICT_STOP_LIMIT = 10;
14
- export declare const TELEGRAM_GET_UPDATES_GRACE_MS = 10000;
15
14
  export declare const TELEGRAM_ALLOWED_UPDATES: readonly ["message", "edited_message", "callback_query", "message_reaction", "guest_message"];
16
15
  export declare function buildTelegramInitialSyncRequest(): {
17
16
  offset: number;
@@ -57,7 +56,6 @@ export interface TelegramPollingControllerState extends TelegramPollingStateSnap
57
56
  pollingController?: AbortController;
58
57
  }
59
58
  export declare function createTelegramPollingControllerState(): TelegramPollingControllerState;
60
- export declare function getTelegramPollingStateSnapshot(state: TelegramPollingControllerState): TelegramPollingStateSnapshot;
61
59
  export declare function createTelegramPollingStateReader(state: TelegramPollingControllerState): () => TelegramPollingStateSnapshot;
62
60
  export declare function isTelegramPollingControllerActive(state: TelegramPollingControllerState): boolean;
63
61
  export declare function createTelegramPollingActivityReader(state: TelegramPollingControllerState): () => boolean;
@@ -271,11 +269,8 @@ export interface TelegramThreadCapabilityOrchestration<TContext, TOwner> {
271
269
  }
272
270
  export declare function createTelegramThreadCapabilityStateRuntime(): TelegramThreadCapabilityStateRuntime;
273
271
  export declare function createTelegramThreadCapabilityOrchestration<TContext, TOwner>(deps: TelegramThreadCapabilityOrchestrationDeps<TContext, TOwner>): TelegramThreadCapabilityOrchestration<TContext, TOwner>;
274
- export declare function readTelegramThreadCapability(deps: TelegramThreadCapabilityReaderDeps): Promise<boolean | undefined>;
275
- export declare function probeTelegramStartupThreadCapability(deps: TelegramStartupThreadCapabilityProbeDeps, isCurrent?: () => boolean): Promise<boolean | undefined>;
276
272
  export declare function applyTelegramThreadCapability<TContext>(ctx: TContext, threadModeEnabled: boolean, phase: string, deps: TelegramThreadCapabilityRuntimeDeps<TContext>, isCurrent?: () => boolean): Promise<void>;
277
273
  export declare function createTelegramThreadAwarePollingPorts<TContext, TOwner>(deps: TelegramThreadAwarePollingDeps<TContext, TOwner>): TelegramThreadAwarePollingPorts<TContext, TOwner>;
278
- export declare function createTelegramThreadTargetObservationHandler<TContext>(deps: TelegramThreadCapabilityRuntimeDeps<TContext>): TelegramThreadTargetObservationHandler<TContext>;
279
274
  export declare function canProbeTelegramThreadCapability<TContext>(ctx: TContext, deps: Pick<TelegramThreadCapabilityRuntimeDeps<TContext>, "ownsLock" | "isFollowerRegistered">): boolean;
280
275
  export declare function createTelegramThreadCapabilityMonitor<TContext>(deps: TelegramThreadCapabilityRuntimeDeps<TContext>): TelegramThreadCapabilityMonitor<TContext>;
281
276
  export declare class TelegramPollingBatchValidationError extends Error {
@@ -15,7 +15,7 @@ const TELEGRAM_GET_UPDATES_CONFLICT_FAST_RETRY_LIMIT = 3;
15
15
  const TELEGRAM_GET_UPDATES_CONFLICT_FAST_RETRY_MS = 1_000;
16
16
  const TELEGRAM_GET_UPDATES_CONFLICT_SLOW_RETRY_MS = 3_000;
17
17
  const TELEGRAM_POLLING_RETRY_MS = 3_000;
18
- export const TELEGRAM_GET_UPDATES_GRACE_MS = 10_000;
18
+ const TELEGRAM_GET_UPDATES_GRACE_MS = 10_000;
19
19
  // Standard Telegram DM polling does not expose ordinary message-deletion events.
20
20
  // Business deletions belong to a separate namespace and default routing ignores them.
21
21
  export const TELEGRAM_ALLOWED_UPDATES = [
@@ -80,7 +80,7 @@ export function createTelegramPollingControllerState() {
80
80
  stopReason: "not-started",
81
81
  };
82
82
  }
83
- export function getTelegramPollingStateSnapshot(state) {
83
+ function getTelegramPollingStateSnapshot(state) {
84
84
  return {
85
85
  phase: state.phase,
86
86
  phaseStartedAtMs: state.phaseStartedAtMs,
@@ -481,7 +481,7 @@ export function createTelegramThreadCapabilityOrchestration(deps) {
481
481
  }),
482
482
  };
483
483
  }
484
- export async function readTelegramThreadCapability(deps) {
484
+ async function readTelegramThreadCapability(deps) {
485
485
  const bot = await deps.callApi("getMe", {});
486
486
  if (bot.has_topics_enabled === true)
487
487
  return true;
@@ -489,7 +489,7 @@ export async function readTelegramThreadCapability(deps) {
489
489
  return false;
490
490
  return undefined;
491
491
  }
492
- export async function probeTelegramStartupThreadCapability(deps, isCurrent = () => true) {
492
+ async function probeTelegramStartupThreadCapability(deps, isCurrent = () => true) {
493
493
  if (!isCurrent())
494
494
  return;
495
495
  const threadModeEnabled = await readTelegramThreadCapability(deps);
@@ -770,7 +770,7 @@ export function createTelegramThreadAwarePollingPorts(deps) {
770
770
  stopFollowerRegistration: deps.stopFollowerRegistration,
771
771
  };
772
772
  }
773
- export function createTelegramThreadTargetObservationHandler(deps) {
773
+ function createTelegramThreadTargetObservationHandler(deps) {
774
774
  let transitionPending = false;
775
775
  return async (ctx) => {
776
776
  if (transitionPending)
@@ -156,7 +156,6 @@ export declare function createTelegramNativeMarkdownPreviewFinalizer<TReplyMarku
156
156
  }) => Promise<boolean>;
157
157
  export declare function createTelegramAssistantPreviewRuntime<TMessage, TReplyMarkup = TelegramPreviewReplyMarkup>(deps: TelegramAssistantPreviewRuntimeDeps<TMessage, TReplyMarkup>): TelegramAssistantPreviewRuntime<TMessage, TReplyMarkup>;
158
158
  export declare function createTelegramPreviewController(deps: TelegramPreviewControllerDeps): TelegramPreviewController;
159
- export declare function createTelegramAssistantMessagePreviewHooks<TMessage>(deps: TelegramAssistantMessagePreviewHookDeps<TMessage>): TelegramAssistantMessagePreviewHooks<TMessage>;
160
159
  /**
161
160
  * Returns true when the active turn is a Telegram Guest Mode query. A guest
162
161
  * query allows exactly one answer within a limited Telegram response window,
@@ -165,8 +164,6 @@ export declare function createTelegramAssistantMessagePreviewHooks<TMessage>(dep
165
164
  export declare function shouldSuppressPreviewForGuestTurn(turn: {
166
165
  guestQueryId?: string;
167
166
  } | null | undefined): boolean;
168
- export declare function handleTelegramAssistantMessagePreviewStart<TMessage>(message: TMessage, deps: TelegramAssistantMessagePreviewStartDeps<TMessage>): Promise<void>;
169
- export declare function handleTelegramAssistantMessagePreviewUpdate<TMessage>(message: TMessage, deps: TelegramAssistantMessagePreviewUpdateDeps<TMessage>): Promise<void>;
170
167
  export declare function buildTelegramPreviewFinalText(state: TelegramPreviewState): string | undefined;
171
168
  export declare function createTelegramPreviewRuntimeState(): TelegramPreviewRuntimeState;
172
169
  export declare function allocateTelegramDraftId(currentDraftId: number, maxDraftId: number): number;
@@ -168,7 +168,7 @@ export function createTelegramPreviewController(deps) {
168
168
  finalize: (chatId, _replyToMessageId, options) => finalizeTelegramPreview(chatId, getRuntimeDeps(), options),
169
169
  };
170
170
  }
171
- export function createTelegramAssistantMessagePreviewHooks(deps) {
171
+ function createTelegramAssistantMessagePreviewHooks(deps) {
172
172
  return {
173
173
  onMessageStart: async (event) => {
174
174
  await handleTelegramAssistantMessagePreviewStart(event.message, deps);
@@ -186,7 +186,7 @@ export function createTelegramAssistantMessagePreviewHooks(deps) {
186
186
  export function shouldSuppressPreviewForGuestTurn(turn) {
187
187
  return !!turn?.guestQueryId;
188
188
  }
189
- export async function handleTelegramAssistantMessagePreviewStart(message, deps) {
189
+ async function handleTelegramAssistantMessagePreviewStart(message, deps) {
190
190
  const turn = deps.getActiveTurn();
191
191
  if (!turn || !deps.isAssistantMessage(message))
192
192
  return;
@@ -211,7 +211,7 @@ export async function handleTelegramAssistantMessagePreviewStart(message, deps)
211
211
  next.precedingFlush = state?.publicationPromise ?? state?.flushPromise ?? state?.precedingFlush;
212
212
  deps.setState(next);
213
213
  }
214
- export async function handleTelegramAssistantMessagePreviewUpdate(message, deps) {
214
+ async function handleTelegramAssistantMessagePreviewUpdate(message, deps) {
215
215
  const turn = deps.getActiveTurn();
216
216
  if (!turn || !deps.isAssistantMessage(message))
217
217
  return;
@@ -12,7 +12,6 @@ export interface TelegramPromptTemplateCommand {
12
12
  export type TelegramPromptTemplateReader = (path: string) => string;
13
13
  export declare function parsePromptTemplateArgs(argsString: string): string[];
14
14
  export declare function substitutePromptTemplateArgs(content: string, args: readonly string[]): string;
15
- export declare function isTelegramPromptTemplateCommandName(name: string): boolean;
16
15
  export declare function mapPiPromptTemplateNameToTelegramCommandName(name: string): string | undefined;
17
16
  export interface TelegramPromptTemplateCommandGetterDeps {
18
17
  getCommands: () => readonly PiSlashCommandInfo[];
@@ -63,7 +63,7 @@ export function substitutePromptTemplateArgs(content, args) {
63
63
  const allArgs = args.join(" ");
64
64
  return result.replace(/\$ARGUMENTS/g, allArgs).replace(/\$@/g, allArgs);
65
65
  }
66
- export function isTelegramPromptTemplateCommandName(name) {
66
+ function isTelegramPromptTemplateCommandName(name) {
67
67
  return TELEGRAM_BOT_COMMAND_NAME_PATTERN.test(name);
68
68
  }
69
69
  export function mapPiPromptTemplateNameToTelegramCommandName(name) {
@@ -98,7 +98,6 @@ export interface PendingTelegramControlItem<TContext = unknown> extends Telegram
98
98
  }
99
99
  export type TelegramQueueItem<TContext = unknown> = PendingTelegramTurn | PendingTelegramControlItem<TContext>;
100
100
  export declare const TELEGRAM_QUEUE_HANDOFF_PAYLOAD_MAX_BYTES: number;
101
- export declare const TELEGRAM_QUEUE_HANDOFF_MAX_RECEIPTS = 256;
102
101
  export interface TelegramQueueHandoffBase {
103
102
  chatId: number;
104
103
  target?: TelegramQueueTarget;
@@ -132,6 +131,8 @@ export interface TelegramQueueHandoff {
132
131
  handoffToken: string;
133
132
  payload: TelegramQueueHandoffPayload;
134
133
  }
134
+ /** Data decoder shared by authenticated IPC and cold storage; decoding grants no admission. */
135
+ export declare function parseTelegramQueueHandoffPayload(value: unknown): TelegramQueueHandoffPayload | undefined;
135
136
  export interface TelegramQueueHandoffStageReceipt {
136
137
  status: "staged";
137
138
  receiptId: string;
@@ -192,14 +193,12 @@ export declare function getTelegramQueueLaneContract(lane: TelegramQueueLane): T
192
193
  export declare function getTelegramQueueItemAdmissionMode(item: Pick<TelegramQueueItem, "queueLane">): TelegramQueueAdmissionMode;
193
194
  export declare function isTelegramQueueItemAdmissionValid(item: Pick<TelegramQueueItem, "kind" | "queueLane">): boolean;
194
195
  export declare function assertTelegramQueueItemAdmissionValid(item: Pick<TelegramQueueItem, "kind" | "queueLane" | "admissionReceipts">): void;
195
- export declare function isPendingTelegramTurn<TContext = unknown>(item: TelegramQueueItem<TContext>): item is PendingTelegramTurn;
196
196
  export declare function createTelegramQueueStore<TContext = unknown>(initialItems?: TelegramQueueItem<TContext>[]): TelegramQueueStateStore<TContext>;
197
197
  export declare function createTelegramTransportStampRuntime(deps: {
198
198
  getProfileName(): string | undefined;
199
199
  getBotToken(): string | undefined;
200
200
  }): TelegramTransportStampRuntime;
201
201
  export declare function createTelegramTransportStampedQueueStore<TContext>(store: TelegramQueueStateStore<TContext>, getTransportStamp: () => TelegramTransportStamp): TelegramQueueStateStore<TContext>;
202
- export declare function isTelegramQueueItemSkipped<TContext = unknown>(item: TelegramQueueItem<TContext>): boolean;
203
202
  export declare function countExecutableTelegramQueueItems<TContext = unknown>(items: readonly TelegramQueueItem<TContext>[]): number;
204
203
  export declare function createTelegramQueueItemCountGetter<TContext = unknown>(store: Pick<TelegramQueueStore<TContext>, "getQueuedItems">): () => number;
205
204
  export declare function createTelegramActiveTurnStore<TTurn extends PendingTelegramTurn = PendingTelegramTurn>(): TelegramActiveTurnStore<TTurn>;
@@ -246,11 +245,6 @@ export declare function removeTelegramQueueItemsByMessageIds<TContext = unknown>
246
245
  removedItems: TelegramQueueItem<TContext>[];
247
246
  removedCount: number;
248
247
  };
249
- export declare function removeTelegramQueuedGuestPromptByOrder<TContext = unknown>(items: TelegramQueueItem<TContext>[], queueOrder: number): {
250
- items: TelegramQueueItem<TContext>[];
251
- removedItems: PendingTelegramTurn[];
252
- removedCount: number;
253
- };
254
248
  export declare function applyTelegramQueuePromptReactionDisposition<TContext = unknown>(items: TelegramQueueItem<TContext>[], messageId: number, disposition: TelegramQueueReactionDisposition, destinationLaneOrder?: number, scope?: TelegramQueueMessageScope): {
255
249
  items: TelegramQueueItem<TContext>[];
256
250
  changed: boolean;
@@ -680,10 +674,8 @@ export declare function createTelegramSessionLifecycleHooks<TContext, TQueueItem
680
674
  onSessionShutdown: (_event?: TelegramSessionLifecycleHookEvent, ctx?: TContext) => Promise<void>;
681
675
  };
682
676
  export declare function createTelegramQueueMutationController<TContext>(deps: TelegramQueueMutationControllerDeps<TContext>): TelegramQueueMutationController<TContext>;
683
- export declare function reorderTelegramQueueItemsRuntime<TContext>(deps: TelegramQueueMutationRuntimeDeps<TContext>): void;
684
677
  export declare function clearTelegramQueueItemsRuntime<TContext>(deps: TelegramQueueMutationRuntimeDeps<TContext>): number;
685
678
  export declare function removeTelegramQueueItemsByMessageIdsRuntime<TContext>(messageIds: number[], deps: TelegramQueueMutationRuntimeDeps<TContext>, scope?: TelegramQueueMessageScope): number;
686
- export declare function removeTelegramQueuedGuestPromptByOrderRuntime<TContext>(queueOrder: number, deps: TelegramQueueMutationRuntimeDeps<TContext>): boolean;
687
679
  export declare function applyTelegramQueuePromptReactionDispositionRuntime<TContext>(messageId: number, disposition: TelegramQueueReactionDisposition, deps: TelegramQueueMutationRuntimeDeps<TContext>, scope?: TelegramQueueMessageScope): boolean;
688
680
  export declare function enqueueTelegramPromptTurnRuntime<TMessage, TContext = unknown>(messages: TMessage[], deps: TelegramPromptEnqueueRuntimeDeps<TMessage, TContext>): Promise<PendingTelegramTurn>;
689
681
  export declare function createTelegramPromptEnqueueController<TMessage, TContext = unknown>(deps: TelegramPromptEnqueueControllerDeps<TMessage, TContext>): TelegramPromptEnqueueController<TMessage, TContext>;
@@ -695,6 +687,7 @@ export interface TelegramControlRuntimeDeps<TContext> extends TelegramRuntimeEve
695
687
  ctx: TContext;
696
688
  sendTextReply: (chatId: number, replyToMessageId: number, text: string, options?: {
697
689
  target?: TelegramQueueTarget;
690
+ parseMode?: "HTML";
698
691
  }) => Promise<number | undefined>;
699
692
  onSettled: (item: PendingTelegramControlItem<TContext>) => void;
700
693
  }
@@ -712,11 +705,6 @@ export interface TelegramDeferredQueueDispatchRuntime<TContext = unknown> {
712
705
  isGenerationActive: (generation: number) => boolean;
713
706
  request: (dispatchNextQueuedTelegramTurn: (ctx: TContext) => void) => void;
714
707
  }
715
- /**
716
- * Production debounce for deferred queue dispatch; the factory defaults to this
717
- * so the entrypoint wires ports instead of policy constants.
718
- */
719
- export declare const TELEGRAM_DEFERRED_DISPATCH_DELAY_MS = 50;
720
708
  export declare function createTelegramDeferredQueueDispatchRuntime<TContext = unknown>(deps?: TelegramDeferredQueueDispatchRuntimeDeps): TelegramDeferredQueueDispatchRuntime<TContext>;
721
709
  export interface TelegramQueueDispatchWatchdogRuntime<TContext = unknown> {
722
710
  start: (ctx: TContext) => void;
@@ -5,6 +5,7 @@
5
5
  */
6
6
  import { createHash } from "node:crypto";
7
7
  import { isVoiceTurn } from "./voice.js";
8
+ import { isWireRecord as isQueuePayloadRecord } from "./wire.js";
8
9
  import { isTelegramApiCommitUnknownError } from "./telegram-api.js";
9
10
  export const TELEGRAM_QUEUE_LANE_CONTRACTS = [
10
11
  // Control lane intentionally accepts both direct controls and resume prompts.
@@ -30,7 +31,67 @@ export const TELEGRAM_QUEUE_LANE_CONTRACTS = [
30
31
  },
31
32
  ];
32
33
  export const TELEGRAM_QUEUE_HANDOFF_PAYLOAD_MAX_BYTES = 8 * 1024 * 1024;
33
- export const TELEGRAM_QUEUE_HANDOFF_MAX_RECEIPTS = 256;
34
+ const TELEGRAM_QUEUE_HANDOFF_MAX_RECEIPTS = 256;
35
+ function parseQueuePayloadReceipt(value, kind) {
36
+ if (!isQueuePayloadRecord(value) || value.queueKind !== kind ||
37
+ typeof value.receiptId !== "string" || !value.receiptId ||
38
+ (value.journalBindingKey !== undefined && (typeof value.journalBindingKey !== "string" || !value.journalBindingKey.trim())) ||
39
+ !Array.isArray(value.sourceUpdateIds) || value.sourceUpdateIds.length === 0 ||
40
+ value.sourceUpdateIds.some((id, index) => !Number.isSafeInteger(id) || id < 0 ||
41
+ (index > 0 && id <= value.sourceUpdateIds[index - 1])))
42
+ return undefined;
43
+ return { queueKind: kind, receiptId: value.receiptId, sourceUpdateIds: value.sourceUpdateIds,
44
+ ...(typeof value.journalBindingKey === "string" ? { journalBindingKey: value.journalBindingKey } : {}) };
45
+ }
46
+ /** Data decoder shared by authenticated IPC and cold storage; decoding grants no admission. */
47
+ export function parseTelegramQueueHandoffPayload(value) {
48
+ if (!isQueuePayloadRecord(value) || (value.kind !== "prompt" && value.kind !== "control"))
49
+ return undefined;
50
+ const target = isQueuePayloadRecord(value.target) && typeof value.target.chatId === "number"
51
+ ? { chatId: value.target.chatId, ...(typeof value.target.threadId === "number" ? { threadId: value.target.threadId } : {}) } : undefined;
52
+ const transportStamp = isQueuePayloadRecord(value.transportStamp) &&
53
+ typeof value.transportStamp.profile === "string" && typeof value.transportStamp.generation === "string"
54
+ ? { profile: value.transportStamp.profile, generation: value.transportStamp.generation } : undefined;
55
+ if (!Number.isSafeInteger(value.chatId) || (value.target !== undefined && !target) ||
56
+ (value.transportStamp !== undefined && !transportStamp) || !Number.isSafeInteger(value.replyToMessageId) ||
57
+ (value.guestQueryId !== undefined && typeof value.guestQueryId !== "string") ||
58
+ (value.guestInlineMessageId !== undefined && typeof value.guestInlineMessageId !== "string") ||
59
+ !Number.isSafeInteger(value.queueOrder) || (value.queueLane !== "control" && value.queueLane !== "priority" && value.queueLane !== "default") ||
60
+ !Number.isSafeInteger(value.laneOrder) || typeof value.statusSummary !== "string" || !Array.isArray(value.admissionReceipts))
61
+ return undefined;
62
+ const admissionReceipts = value.admissionReceipts.map(receipt => parseQueuePayloadReceipt(receipt, value.kind));
63
+ if (admissionReceipts.length === 0 || admissionReceipts.length > TELEGRAM_QUEUE_HANDOFF_MAX_RECEIPTS ||
64
+ admissionReceipts.some(receipt => !receipt))
65
+ return undefined;
66
+ const base = { chatId: value.chatId, ...(target ? { target } : {}), ...(transportStamp ? { transportStamp } : {}),
67
+ replyToMessageId: value.replyToMessageId,
68
+ ...(typeof value.guestQueryId === "string" ? { guestQueryId: value.guestQueryId } : {}),
69
+ ...(typeof value.guestInlineMessageId === "string" ? { guestInlineMessageId: value.guestInlineMessageId } : {}),
70
+ queueOrder: value.queueOrder, queueLane: value.queueLane,
71
+ laneOrder: value.laneOrder, statusSummary: value.statusSummary,
72
+ admissionReceipts: admissionReceipts };
73
+ if (value.kind === "control") {
74
+ return value.queueLane === "control" && (value.controlType === "status" || value.controlType === "model")
75
+ ? { kind: "control", controlType: value.controlType, ...base } : undefined;
76
+ }
77
+ if (value.queueLane === "control" || !Array.isArray(value.sourceMessageIds) || value.sourceMessageIds.some(id => !Number.isSafeInteger(id)) ||
78
+ !Array.isArray(value.queuedAttachments) || value.queuedAttachments.some(attachment => !isQueuePayloadRecord(attachment) ||
79
+ typeof attachment.path !== "string" || typeof attachment.fileName !== "string") ||
80
+ !Array.isArray(value.content) || value.content.some(content => !isQueuePayloadRecord(content) ||
81
+ (content.type === "text" ? typeof content.text !== "string" : content.type === "image"
82
+ ? typeof content.data !== "string" || typeof content.mimeType !== "string" : true)) ||
83
+ typeof value.historyText !== "string" || (value.priorityEmoji !== undefined && typeof value.priorityEmoji !== "string") ||
84
+ (value.reactionSuppressionEmoji !== undefined && typeof value.reactionSuppressionEmoji !== "string") ||
85
+ (value.voiceReplyPreferred !== undefined && typeof value.voiceReplyPreferred !== "boolean") ||
86
+ (value.voiceReplyRequired !== undefined && typeof value.voiceReplyRequired !== "boolean"))
87
+ return undefined;
88
+ return { kind: "prompt", ...base, sourceMessageIds: value.sourceMessageIds,
89
+ queuedAttachments: value.queuedAttachments, content: value.content, historyText: value.historyText,
90
+ ...(typeof value.priorityEmoji === "string" ? { priorityEmoji: value.priorityEmoji } : {}),
91
+ ...(typeof value.reactionSuppressionEmoji === "string" ? { reactionSuppressionEmoji: value.reactionSuppressionEmoji } : {}),
92
+ ...(typeof value.voiceReplyPreferred === "boolean" ? { voiceReplyPreferred: value.voiceReplyPreferred } : {}),
93
+ ...(typeof value.voiceReplyRequired === "boolean" ? { voiceReplyRequired: value.voiceReplyRequired } : {}) };
94
+ }
34
95
  export function createTelegramQueueAdmissionReceipt(options) {
35
96
  if (options.sourceUpdateIds.length === 0)
36
97
  return undefined;
@@ -100,7 +161,7 @@ export function assertTelegramQueueItemAdmissionValid(item) {
100
161
  function getTelegramQueueLaneRank(lane) {
101
162
  return getTelegramQueueLaneContract(lane).dispatchRank;
102
163
  }
103
- export function isPendingTelegramTurn(item) {
164
+ function isPendingTelegramTurn(item) {
104
165
  return item.kind === "prompt";
105
166
  }
106
167
  export function createTelegramQueueStore(initialItems = []) {
@@ -148,7 +209,7 @@ export function createTelegramTransportStampedQueueStore(store, getTransportStam
148
209
  },
149
210
  };
150
211
  }
151
- export function isTelegramQueueItemSkipped(item) {
212
+ function isTelegramQueueItemSkipped(item) {
152
213
  return item.kind === "prompt" && Boolean(item.reactionSuppressionEmoji);
153
214
  }
154
215
  export function countExecutableTelegramQueueItems(items) {
@@ -503,7 +564,7 @@ export function removeTelegramQueueItemsByMessageIds(items, messageIds, scope) {
503
564
  removedCount: removedItems.length,
504
565
  };
505
566
  }
506
- export function removeTelegramQueuedGuestPromptByOrder(items, queueOrder) {
567
+ function removeTelegramQueuedGuestPromptByOrder(items, queueOrder) {
507
568
  const index = items.findIndex((item) => {
508
569
  return isPendingTelegramTurn(item) &&
509
570
  item.guestQueryId !== undefined &&
@@ -1499,7 +1560,7 @@ function appendTelegramQueueItemRuntime(item, deps) {
1499
1560
  return;
1500
1561
  commitReorderedTelegramQueueItemsRuntime(nextItems, deps);
1501
1562
  }
1502
- export function reorderTelegramQueueItemsRuntime(deps) {
1563
+ function reorderTelegramQueueItemsRuntime(deps) {
1503
1564
  commitReorderedTelegramQueueItemsRuntime(deps.getQueuedItems(), deps);
1504
1565
  }
1505
1566
  export function clearTelegramQueueItemsRuntime(deps) {
@@ -1528,7 +1589,7 @@ export function removeTelegramQueueItemsByMessageIdsRuntime(messageIds, deps, sc
1528
1589
  updateTelegramQueueStatusRuntime(deps);
1529
1590
  return removedCount;
1530
1591
  }
1531
- export function removeTelegramQueuedGuestPromptByOrderRuntime(queueOrder, deps) {
1592
+ function removeTelegramQueuedGuestPromptByOrderRuntime(queueOrder, deps) {
1532
1593
  const { items, removedItems, removedCount } = removeTelegramQueuedGuestPromptByOrder(deps.getQueuedItems(), queueOrder);
1533
1594
  if (removedCount === 0)
1534
1595
  return false;
@@ -1639,7 +1700,7 @@ export async function executeTelegramControlItemRuntime(item, deps) {
1639
1700
  * Production debounce for deferred queue dispatch; the factory defaults to this
1640
1701
  * so the entrypoint wires ports instead of policy constants.
1641
1702
  */
1642
- export const TELEGRAM_DEFERRED_DISPATCH_DELAY_MS = 50;
1703
+ const TELEGRAM_DEFERRED_DISPATCH_DELAY_MS = 50;
1643
1704
  export function createTelegramDeferredQueueDispatchRuntime(deps = {}) {
1644
1705
  let boundContext;
1645
1706
  let generation = 0;
@@ -2004,7 +2065,7 @@ export function createTelegramQueueDispatchController(deps) {
2004
2065
  const announcementGeneration = nextDispatchAnnouncementGeneration;
2005
2066
  const dispatchGeneration = deps.getDispatchGeneration?.();
2006
2067
  deps.updateStatus(ctx);
2007
- void deps.sendTextReply(dispatchPlan.item.chatId, dispatchPlan.item.replyToMessageId, "<b>⏩ Dispatching next queued turn.</b>", { target: dispatchPlan.item.target }).catch((error) => {
2068
+ void deps.sendTextReply(dispatchPlan.item.chatId, dispatchPlan.item.replyToMessageId, "<b>⏩ Dispatching next queued turn.</b>", { target: dispatchPlan.item.target, parseMode: "HTML" }).catch((error) => {
2008
2069
  deps.recordRuntimeEvent?.("dispatch", error, {
2009
2070
  phase: "next-announcement",
2010
2071
  });