@llblab/pi-telegram 0.45.11 → 0.47.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 (204) hide show
  1. package/BACKLOG.md +5 -1
  2. package/CHANGELOG.md +12 -0
  3. package/README.md +6 -6
  4. package/dist/api/activity.d.ts +6 -0
  5. package/dist/api/activity.js +6 -0
  6. package/dist/api/commands.d.ts +6 -0
  7. package/dist/api/commands.js +6 -0
  8. package/dist/api/delivery.d.ts +6 -0
  9. package/dist/api/delivery.js +6 -0
  10. package/dist/api/inbound.d.ts +6 -0
  11. package/dist/api/inbound.js +6 -0
  12. package/dist/api/keyboard.d.ts +6 -0
  13. package/dist/api/keyboard.js +6 -0
  14. package/dist/api/outbound.d.ts +6 -0
  15. package/dist/api/outbound.js +6 -0
  16. package/dist/api/sections.d.ts +7 -0
  17. package/dist/api/sections.js +6 -0
  18. package/dist/api/status.d.ts +6 -0
  19. package/dist/api/status.js +6 -0
  20. package/dist/api/updates.d.ts +6 -0
  21. package/dist/api/updates.js +6 -0
  22. package/dist/api/voice.d.ts +6 -0
  23. package/dist/api/voice.js +6 -0
  24. package/dist/index.d.ts +6 -0
  25. package/dist/index.js +6 -0
  26. package/dist/lib/activity-verbosity.d.ts +52 -0
  27. package/dist/lib/activity-verbosity.js +596 -0
  28. package/dist/lib/activity.d.ts +220 -0
  29. package/dist/lib/activity.js +574 -0
  30. package/dist/lib/agent-messages.d.ts +28 -0
  31. package/dist/lib/agent-messages.js +86 -0
  32. package/dist/lib/bindings.d.ts +250 -0
  33. package/dist/lib/bindings.js +936 -0
  34. package/dist/lib/bus-api.d.ts +18 -0
  35. package/dist/lib/bus-api.js +254 -0
  36. package/dist/lib/bus-follower.d.ts +464 -0
  37. package/dist/lib/bus-follower.js +1686 -0
  38. package/dist/lib/bus-leader.d.ts +292 -0
  39. package/dist/lib/bus-leader.js +2242 -0
  40. package/dist/lib/bus-transport.d.ts +64 -0
  41. package/dist/lib/bus-transport.js +140 -0
  42. package/dist/lib/bus.d.ts +518 -0
  43. package/dist/lib/bus.js +2055 -0
  44. package/dist/lib/channel-posts.d.ts +170 -0
  45. package/dist/lib/channel-posts.js +611 -0
  46. package/dist/lib/command-templates.d.ts +70 -0
  47. package/dist/lib/command-templates.js +744 -0
  48. package/dist/lib/commands.d.ts +541 -0
  49. package/dist/lib/commands.js +1155 -0
  50. package/dist/lib/config.d.ts +252 -0
  51. package/dist/lib/config.js +889 -0
  52. package/dist/lib/delivery.d.ts +163 -0
  53. package/dist/lib/delivery.js +542 -0
  54. package/dist/lib/extension.d.ts +7 -0
  55. package/dist/lib/extension.js +1608 -0
  56. package/dist/lib/generative-app-worker.mjs +104 -0
  57. package/dist/lib/generative-apps.d.ts +212 -0
  58. package/dist/lib/generative-apps.js +1006 -0
  59. package/dist/lib/inbound.d.ts +91 -0
  60. package/dist/lib/inbound.js +502 -0
  61. package/dist/lib/journal.d.ts +665 -0
  62. package/dist/lib/journal.js +3701 -0
  63. package/dist/lib/keyboard.d.ts +23 -0
  64. package/dist/lib/keyboard.js +37 -0
  65. package/dist/lib/lifecycle.d.ts +157 -0
  66. package/dist/lib/lifecycle.js +395 -0
  67. package/dist/lib/locks.d.ts +164 -0
  68. package/dist/lib/locks.js +1208 -0
  69. package/dist/lib/logging.d.ts +50 -0
  70. package/dist/lib/logging.js +274 -0
  71. package/dist/lib/media.d.ts +181 -0
  72. package/dist/lib/media.js +602 -0
  73. package/dist/lib/menu-model.d.ts +217 -0
  74. package/dist/lib/menu-model.js +663 -0
  75. package/dist/lib/menu-queue.d.ts +37 -0
  76. package/dist/lib/menu-queue.js +408 -0
  77. package/dist/lib/menu-settings.d.ts +116 -0
  78. package/dist/lib/menu-settings.js +595 -0
  79. package/dist/lib/menu-status.d.ts +32 -0
  80. package/dist/lib/menu-status.js +112 -0
  81. package/dist/lib/menu-thinking.d.ts +29 -0
  82. package/dist/lib/menu-thinking.js +82 -0
  83. package/dist/lib/menu.d.ts +171 -0
  84. package/dist/lib/menu.js +323 -0
  85. package/dist/lib/model.d.ts +127 -0
  86. package/dist/lib/model.js +409 -0
  87. package/dist/lib/outbound-attachments.d.ts +241 -0
  88. package/dist/lib/outbound-attachments.js +639 -0
  89. package/dist/lib/outbound-buttons.d.ts +69 -0
  90. package/dist/lib/outbound-buttons.js +248 -0
  91. package/dist/lib/outbound-markup.d.ts +42 -0
  92. package/dist/lib/outbound-markup.js +678 -0
  93. package/dist/lib/outbound-voice.d.ts +55 -0
  94. package/dist/lib/outbound-voice.js +152 -0
  95. package/dist/lib/outbound.d.ts +185 -0
  96. package/dist/lib/outbound.js +516 -0
  97. package/dist/lib/ownership.d.ts +77 -0
  98. package/dist/lib/ownership.js +174 -0
  99. package/dist/lib/paths.d.ts +40 -0
  100. package/dist/lib/paths.js +94 -0
  101. package/dist/lib/pi.d.ts +72 -0
  102. package/dist/lib/pi.js +121 -0
  103. package/dist/lib/polling.d.ts +351 -0
  104. package/dist/lib/polling.js +1196 -0
  105. package/dist/lib/preview.d.ts +192 -0
  106. package/dist/lib/preview.js +614 -0
  107. package/dist/lib/prompt-templates.d.ts +24 -0
  108. package/dist/lib/prompt-templates.js +118 -0
  109. package/dist/lib/prompts.d.ts +57 -0
  110. package/dist/lib/prompts.js +167 -0
  111. package/dist/lib/queue.d.ts +766 -0
  112. package/dist/lib/queue.js +1965 -0
  113. package/dist/lib/recovery.d.ts +86 -0
  114. package/dist/lib/recovery.js +285 -0
  115. package/dist/lib/rendering.d.ts +20 -0
  116. package/dist/lib/rendering.js +983 -0
  117. package/dist/lib/replies.d.ts +214 -0
  118. package/dist/lib/replies.js +737 -0
  119. package/dist/lib/routing.d.ts +207 -0
  120. package/dist/lib/routing.js +2282 -0
  121. package/dist/lib/runtime.d.ts +172 -0
  122. package/dist/lib/runtime.js +402 -0
  123. package/dist/lib/sections.d.ts +165 -0
  124. package/dist/lib/sections.js +327 -0
  125. package/dist/lib/setup.d.ts +82 -0
  126. package/dist/lib/setup.js +150 -0
  127. package/dist/lib/skills.d.ts +8 -0
  128. package/dist/lib/skills.js +12 -0
  129. package/dist/lib/status.d.ts +442 -0
  130. package/dist/lib/status.js +1006 -0
  131. package/dist/lib/sync.d.ts +179 -0
  132. package/dist/lib/sync.js +782 -0
  133. package/dist/lib/target.d.ts +20 -0
  134. package/dist/lib/target.js +27 -0
  135. package/dist/lib/telegram-api.d.ts +541 -0
  136. package/dist/lib/telegram-api.js +1159 -0
  137. package/dist/lib/text-groups.d.ts +89 -0
  138. package/dist/lib/text-groups.js +317 -0
  139. package/dist/lib/thread-cleanup-manager.d.ts +288 -0
  140. package/dist/lib/thread-cleanup-manager.js +560 -0
  141. package/dist/lib/thread-display.d.ts +46 -0
  142. package/dist/lib/thread-display.js +257 -0
  143. package/dist/lib/thread-naming.d.ts +46 -0
  144. package/dist/lib/thread-naming.js +78 -0
  145. package/dist/lib/thread-reconciler.d.ts +239 -0
  146. package/dist/lib/thread-reconciler.js +644 -0
  147. package/dist/lib/threads.d.ts +621 -0
  148. package/dist/lib/threads.js +3679 -0
  149. package/dist/lib/time-injection.d.ts +15 -0
  150. package/dist/lib/time-injection.js +56 -0
  151. package/dist/lib/turns.d.ts +109 -0
  152. package/dist/lib/turns.js +500 -0
  153. package/dist/lib/updates.d.ts +1290 -0
  154. package/dist/lib/updates.js +3634 -0
  155. package/dist/lib/voice.d.ts +117 -0
  156. package/dist/lib/voice.js +174 -0
  157. package/dist/lib/workspace-admission.d.ts +264 -0
  158. package/dist/lib/workspace-admission.js +1136 -0
  159. package/dist/lib/workspace-retirement.d.ts +219 -0
  160. package/dist/lib/workspace-retirement.js +587 -0
  161. package/dist/lib/workspace-slots.d.ts +30 -0
  162. package/dist/lib/workspace-slots.js +54 -0
  163. package/dist/package.json +126 -0
  164. package/dist/pi-telegram/index.js +1 -0
  165. package/dist/skills/generated-control-surface/SKILL.md +107 -0
  166. package/dist/skills/generated-control-surface/references/capability-adapters.md +27 -0
  167. package/dist/skills/generated-control-surface/references/layout-and-state.md +37 -0
  168. package/dist/skills/generative-apps/SKILL.md +115 -0
  169. package/dist/skills/show-me/SKILL.md +166 -0
  170. package/dist/skills/show-me/references/telegram-surfaces.md +43 -0
  171. package/dist/skills/telegram-bridge/SKILL.md +129 -0
  172. package/dist/skills/telegram-bridge/references/configuration.md +15 -0
  173. package/dist/skills/telegram-bridge/references/delivery-and-threads.md +27 -0
  174. package/dist/skills/telegram-bridge/references/diagnosis.md +18 -0
  175. package/docs/architecture.md +1 -1
  176. package/docs/compact-matrix-literal.md +10 -3
  177. package/docs/generative-apps.md +15 -13
  178. package/docs/multi-instance-bus.md +17 -3
  179. package/docs/outbound.md +1 -1
  180. package/docs/public-api.md +2 -2
  181. package/lib/bindings.ts +89 -6
  182. package/lib/bus-follower.ts +11 -3
  183. package/lib/bus-leader.ts +57 -20
  184. package/lib/bus.ts +12 -2
  185. package/lib/commands.ts +2 -2
  186. package/lib/config.ts +14 -7
  187. package/lib/delivery.ts +38 -6
  188. package/lib/extension.ts +26 -1
  189. package/lib/generative-apps.ts +379 -13
  190. package/lib/menu-settings.ts +21 -5
  191. package/lib/outbound-markup.ts +16 -7
  192. package/lib/pi.ts +4 -0
  193. package/lib/sync.ts +3 -0
  194. package/lib/telegram-api.ts +12 -1
  195. package/lib/thread-cleanup-manager.ts +36 -6
  196. package/lib/thread-display.ts +96 -18
  197. package/lib/threads.ts +150 -30
  198. package/package.json +56 -13
  199. package/scripts/build-dist.mjs +44 -0
  200. package/scripts/measure-bus.mjs +8 -1
  201. package/scripts/measure-workspace.mjs +8 -1
  202. package/skills/generative-apps/SKILL.md +1 -1
  203. package/skills/show-me/SKILL.md +1 -1
  204. package/skills/telegram-bridge/SKILL.md +1 -1
@@ -0,0 +1,117 @@
1
+ /**
2
+ * Voice Domain
3
+ * Zones: telegram, voice
4
+ *
5
+ * This module is the single owner of all Voice-specific coordination logic:
6
+ * - Voice reply policy (mirror / voice / manual) via getTelegramVoiceReplyMode()
7
+ * - Voice turn tagging (voiceReplyPreferred / voiceReplyRequired)
8
+ * - Voice-specific prompt contributions for the LLM
9
+ * - Voice synthesis provider registry (registration + policy/prompt hooks)
10
+ * - Voice markup parsing (planTelegramVoiceReply + helpers)
11
+ * - Voice suppression helpers (isVoiceTurn, shouldSuppressPreviewForVoice)
12
+ *
13
+ * Separation of concerns:
14
+ * - All decision logic and domain rules live here.
15
+ * - Actual delivery (sending the audio via Telegram) stays in outbound.ts.
16
+ *
17
+ * Keeps voice policy, turn tagging, prompt contributions, and markup helpers
18
+ * out of the queue, preview, turn-building, and delivery domains.
19
+ */
20
+ export type TelegramVoiceReplyMode = "manual" | "mirror" | "always";
21
+ export type TelegramVoiceSynthesisProviderResult = string | undefined;
22
+ export interface TelegramVoiceTurnView {
23
+ voiceReplyPreferred?: boolean;
24
+ voiceReplyRequired?: boolean;
25
+ hasVoiceInput?: boolean;
26
+ userText?: string;
27
+ }
28
+ export interface TelegramVoiceSynthesisProvider {
29
+ (text: string, options?: {
30
+ lang?: string;
31
+ rate?: string;
32
+ }): Promise<TelegramVoiceSynthesisProviderResult>;
33
+ getVoicePolicy?: () => {
34
+ replyMode?: TelegramVoiceReplyMode;
35
+ };
36
+ getVoicePromptContribution?: (view: TelegramVoiceTurnView) => string | undefined;
37
+ }
38
+ export type TelegramVoiceTranscriptionProviderResult = string | {
39
+ text: string;
40
+ language?: string;
41
+ } | undefined;
42
+ export interface TelegramVoiceTranscriptionFile {
43
+ path: string;
44
+ fileName?: string;
45
+ mimeType?: string;
46
+ kind?: string;
47
+ }
48
+ export interface TelegramVoiceTranscriptionProvider {
49
+ (file: TelegramVoiceTranscriptionFile, options?: {
50
+ language?: string;
51
+ }): Promise<TelegramVoiceTranscriptionProviderResult>;
52
+ }
53
+ /**
54
+ * Register a high-level Telegram voice synthesis provider.
55
+ *
56
+ * Stable public API callers must pass a stable `options.id` so diagnostics,
57
+ * replacement, and cleanup can identify the provider. Omitted ids remain a
58
+ * compatibility path for pre-matrix callers and receive generated session-local
59
+ * ids.
60
+ */
61
+ export declare function registerTelegramVoiceSynthesisProvider(provider: TelegramVoiceSynthesisProvider | ((text: string, options?: {
62
+ lang?: string;
63
+ rate?: string;
64
+ }) => Promise<TelegramVoiceSynthesisProviderResult>), options?: {
65
+ id?: string;
66
+ }): () => void;
67
+ export declare function getTelegramVoiceSynthesisProviders(): TelegramVoiceSynthesisProvider[];
68
+ export declare function hasTelegramVoiceSynthesisProvider(): boolean;
69
+ export declare function clearTelegramVoiceSynthesisProviders(): void;
70
+ /**
71
+ * Register a high-level Telegram voice transcription provider.
72
+ *
73
+ * Stable public API callers must pass a stable `options.id`. Omitted ids remain
74
+ * a compatibility path for pre-matrix callers and receive generated
75
+ * session-local ids.
76
+ */
77
+ export declare function registerTelegramVoiceTranscriptionProvider(provider: TelegramVoiceTranscriptionProvider, options?: {
78
+ id?: string;
79
+ }): () => void;
80
+ export declare function getTelegramVoiceTranscriptionProviders(): TelegramVoiceTranscriptionProvider[];
81
+ export declare function hasTelegramVoiceTranscriptionProvider(): boolean;
82
+ export declare function clearTelegramVoiceTranscriptionProviders(): void;
83
+ export declare const TELEGRAM_VOICE_REPLY_MODES: readonly ["manual", "mirror", "always"];
84
+ /**
85
+ * Returns the active voice reply mode for the current session.
86
+ *
87
+ * Pi-telegram owns reply-mode policy through telegram.json. If
88
+ * config.voice.replyMode is missing, invalid, or legacy `hidden`, the effective
89
+ * mode is manual.
90
+ */
91
+ export declare function getTelegramVoiceReplyMode(config?: {
92
+ voice?: {
93
+ replyMode?: string;
94
+ };
95
+ }): TelegramVoiceReplyMode;
96
+ /** Small helper to compute the two voice flags from mode + hasVoiceFile */
97
+ export declare function computeVoiceTurnFlags(voiceReplyMode: TelegramVoiceReplyMode | undefined, hasVoiceFile: boolean): {
98
+ voiceReplyPreferred: boolean;
99
+ voiceReplyRequired: boolean;
100
+ };
101
+ /** Returns true if the given turn is tagged as a voice turn */
102
+ export declare function isVoiceTurn(turn: {
103
+ voiceReplyPreferred?: boolean;
104
+ voiceReplyRequired?: boolean;
105
+ } | null | undefined): boolean;
106
+ export declare function computeVoicePromptContribution(voiceReplyMode: TelegramVoiceReplyMode | undefined, files: Array<{
107
+ kind?: string;
108
+ }>, rawText: string): string | undefined;
109
+ /**
110
+ * Returns true if the current turn should not show a text preview
111
+ * (e.g. because it's a voice reply).
112
+ */
113
+ export declare function shouldSuppressPreviewForVoice(turn: {
114
+ voiceReplyPreferred?: boolean;
115
+ voiceReplyRequired?: boolean;
116
+ } | null | undefined): boolean;
117
+ export { normalizeMarkdownAfterVoiceExtraction, planTelegramVoiceReply, stripTelegramCommentMarkupForDelivery, stripTelegramCommentMarkupForPreview, stripTelegramVoiceMarkupForPreview, } from "./outbound-markup.ts";
@@ -0,0 +1,174 @@
1
+ /**
2
+ * Voice Domain
3
+ * Zones: telegram, voice
4
+ *
5
+ * This module is the single owner of all Voice-specific coordination logic:
6
+ * - Voice reply policy (mirror / voice / manual) via getTelegramVoiceReplyMode()
7
+ * - Voice turn tagging (voiceReplyPreferred / voiceReplyRequired)
8
+ * - Voice-specific prompt contributions for the LLM
9
+ * - Voice synthesis provider registry (registration + policy/prompt hooks)
10
+ * - Voice markup parsing (planTelegramVoiceReply + helpers)
11
+ * - Voice suppression helpers (isVoiceTurn, shouldSuppressPreviewForVoice)
12
+ *
13
+ * Separation of concerns:
14
+ * - All decision logic and domain rules live here.
15
+ * - Actual delivery (sending the audio via Telegram) stays in outbound.ts.
16
+ *
17
+ * Keeps voice policy, turn tagging, prompt contributions, and markup helpers
18
+ * out of the queue, preview, turn-building, and delivery domains.
19
+ */
20
+ const VOICE_SYNTHESIS_PROVIDER_REGISTRY_KEY = "__piTelegramVoiceSynthesisProviders__";
21
+ const VOICE_TRANSCRIPTION_PROVIDER_REGISTRY_KEY = "__piTelegramVoiceTranscriptionProviders__";
22
+ let nextGeneratedVoiceSynthesisProviderId = 0;
23
+ let nextGeneratedVoiceTranscriptionProviderId = 0;
24
+ function getNextAvailableProviderId(registry, prefix, nextId) {
25
+ let id;
26
+ do {
27
+ id = `${prefix}-${nextId()}`;
28
+ } while (registry.has(id));
29
+ return id;
30
+ }
31
+ // --- Voice Synthesis Provider Registry ---
32
+ function getOrCreateVoiceSynthesisProviderRegistry() {
33
+ const existing = globalThis[VOICE_SYNTHESIS_PROVIDER_REGISTRY_KEY];
34
+ if (existing instanceof Map)
35
+ return existing;
36
+ const registry = new Map();
37
+ globalThis[VOICE_SYNTHESIS_PROVIDER_REGISTRY_KEY] = registry;
38
+ return registry;
39
+ }
40
+ /**
41
+ * Register a high-level Telegram voice synthesis provider.
42
+ *
43
+ * Stable public API callers must pass a stable `options.id` so diagnostics,
44
+ * replacement, and cleanup can identify the provider. Omitted ids remain a
45
+ * compatibility path for pre-matrix callers and receive generated session-local
46
+ * ids.
47
+ */
48
+ export function registerTelegramVoiceSynthesisProvider(provider, options) {
49
+ const registry = getOrCreateVoiceSynthesisProviderRegistry();
50
+ const id = options?.id ??
51
+ getNextAvailableProviderId(registry, "voice-synthesis-provider", () => nextGeneratedVoiceSynthesisProviderId++);
52
+ const normalized = typeof provider === "function"
53
+ ? Object.assign((text, options) => provider(text, options), {
54
+ getVoicePolicy: provider
55
+ .getVoicePolicy,
56
+ getVoicePromptContribution: provider.getVoicePromptContribution,
57
+ })
58
+ : provider;
59
+ registry.set(id, normalized);
60
+ return () => {
61
+ if (registry.get(id) === normalized)
62
+ registry.delete(id);
63
+ };
64
+ }
65
+ export function getTelegramVoiceSynthesisProviders() {
66
+ return Array.from(getOrCreateVoiceSynthesisProviderRegistry().values());
67
+ }
68
+ export function hasTelegramVoiceSynthesisProvider() {
69
+ return getOrCreateVoiceSynthesisProviderRegistry().size > 0;
70
+ }
71
+ export function clearTelegramVoiceSynthesisProviders() {
72
+ getOrCreateVoiceSynthesisProviderRegistry().clear();
73
+ }
74
+ function getOrCreateVoiceTranscriptionProviderRegistry() {
75
+ const existing = globalThis[VOICE_TRANSCRIPTION_PROVIDER_REGISTRY_KEY];
76
+ if (existing instanceof Map) {
77
+ return existing;
78
+ }
79
+ const registry = new Map();
80
+ globalThis[VOICE_TRANSCRIPTION_PROVIDER_REGISTRY_KEY] = registry;
81
+ return registry;
82
+ }
83
+ /**
84
+ * Register a high-level Telegram voice transcription provider.
85
+ *
86
+ * Stable public API callers must pass a stable `options.id`. Omitted ids remain
87
+ * a compatibility path for pre-matrix callers and receive generated
88
+ * session-local ids.
89
+ */
90
+ export function registerTelegramVoiceTranscriptionProvider(provider, options) {
91
+ const registry = getOrCreateVoiceTranscriptionProviderRegistry();
92
+ const id = options?.id ??
93
+ getNextAvailableProviderId(registry, "voice-transcription-provider", () => nextGeneratedVoiceTranscriptionProviderId++);
94
+ registry.set(id, provider);
95
+ return () => {
96
+ if (registry.get(id) === provider)
97
+ registry.delete(id);
98
+ };
99
+ }
100
+ export function getTelegramVoiceTranscriptionProviders() {
101
+ return Array.from(getOrCreateVoiceTranscriptionProviderRegistry().values());
102
+ }
103
+ export function hasTelegramVoiceTranscriptionProvider() {
104
+ return getOrCreateVoiceTranscriptionProviderRegistry().size > 0;
105
+ }
106
+ export function clearTelegramVoiceTranscriptionProviders() {
107
+ getOrCreateVoiceTranscriptionProviderRegistry().clear();
108
+ }
109
+ // --- Voice Reply Modes ---
110
+ export const TELEGRAM_VOICE_REPLY_MODES = [
111
+ "manual",
112
+ "mirror",
113
+ "always",
114
+ ];
115
+ /**
116
+ * Returns the active voice reply mode for the current session.
117
+ *
118
+ * Pi-telegram owns reply-mode policy through telegram.json. If
119
+ * config.voice.replyMode is missing, invalid, or legacy `hidden`, the effective
120
+ * mode is manual.
121
+ */
122
+ export function getTelegramVoiceReplyMode(config) {
123
+ const configMode = config?.voice?.replyMode;
124
+ if (configMode === "mirror" || configMode === "always")
125
+ return configMode;
126
+ return "manual";
127
+ }
128
+ // --- Voice Turn Helpers ---
129
+ /** Small helper to compute the two voice flags from mode + hasVoiceFile */
130
+ export function computeVoiceTurnFlags(voiceReplyMode, hasVoiceFile) {
131
+ return {
132
+ voiceReplyPreferred: hasVoiceFile && voiceReplyMode === "mirror",
133
+ voiceReplyRequired: voiceReplyMode === "always",
134
+ };
135
+ }
136
+ /** Returns true if the given turn is tagged as a voice turn */
137
+ export function isVoiceTurn(turn) {
138
+ return !!(turn?.voiceReplyPreferred || turn?.voiceReplyRequired);
139
+ }
140
+ // --- Voice Prompt Contribution ---
141
+ export function computeVoicePromptContribution(voiceReplyMode, files, rawText) {
142
+ const hasVoiceFile = files.some((f) => f.kind === "voice" || f.kind === "audio");
143
+ const isVoiceTagged = voiceReplyMode === "always" ||
144
+ (voiceReplyMode === "mirror" && hasVoiceFile);
145
+ if (!isVoiceTagged)
146
+ return undefined;
147
+ const view = {
148
+ ...computeVoiceTurnFlags(voiceReplyMode, hasVoiceFile),
149
+ hasVoiceInput: hasVoiceFile,
150
+ userText: rawText,
151
+ };
152
+ // Let the voice synthesis provider supply additional instructions for the LLM when in voice mode.
153
+ // When multiple providers are registered, the first one (in registration order)
154
+ // that returns a non-empty string wins.
155
+ for (const provider of getTelegramVoiceSynthesisProviders()) {
156
+ if (typeof provider.getVoicePromptContribution === "function") {
157
+ const contribution = provider.getVoicePromptContribution(view);
158
+ if (contribution?.trim()) {
159
+ return contribution.trim();
160
+ }
161
+ }
162
+ }
163
+ return undefined;
164
+ }
165
+ // --- Preview Suppression ---
166
+ /**
167
+ * Returns true if the current turn should not show a text preview
168
+ * (e.g. because it's a voice reply).
169
+ */
170
+ export function shouldSuppressPreviewForVoice(turn) {
171
+ return !!(turn?.voiceReplyPreferred || turn?.voiceReplyRequired);
172
+ }
173
+ // --- Outbound Markup Re-Exports ---
174
+ export { normalizeMarkdownAfterVoiceExtraction, planTelegramVoiceReply, stripTelegramCommentMarkupForDelivery, stripTelegramCommentMarkupForPreview, stripTelegramVoiceMarkupForPreview, } from "./outbound-markup.js";
@@ -0,0 +1,264 @@
1
+ /**
2
+ * Durable Telegram Workspace admission and retirement fencing
3
+ * Zones: telegram workspace identity, filesystem authority, process recovery
4
+ * Owns profile-scoped admission leases, destructive fences, and deletion permits
5
+ * Excludes journal/API operations, retirement policy, and binding persistence
6
+ */
7
+ import { type PathLike } from "node:fs";
8
+ import { type TelegramProcessLiveness } from "./bus.ts";
9
+ import type { TelegramTarget } from "./target.ts";
10
+ export interface TelegramWorkspaceAdmissionOwner {
11
+ processId: number;
12
+ processBirthId: string;
13
+ }
14
+ export type TelegramWorkspaceAdmissionScope = {
15
+ kind: "target";
16
+ target: TelegramTarget & {
17
+ threadId: number;
18
+ };
19
+ } | {
20
+ kind: "chat";
21
+ chatId: number;
22
+ } | {
23
+ kind: "profile";
24
+ };
25
+ export interface TelegramWorkspaceAdmissionLease {
26
+ operationId: string;
27
+ operationKind: string;
28
+ profileKey: string;
29
+ scope: TelegramWorkspaceAdmissionScope;
30
+ owner: TelegramWorkspaceAdmissionOwner;
31
+ acquiredAtMs: number;
32
+ }
33
+ export type TelegramWorkspaceDestructiveFenceKind = "pressure-retirement" | "manual-thread-cleanup" | "journal-writer-closure";
34
+ export type TelegramWorkspaceDeletionFenceKind = Exclude<TelegramWorkspaceDestructiveFenceKind, "journal-writer-closure">;
35
+ export interface TelegramWorkspaceJournalWriterClosureFence {
36
+ destructiveKind: "journal-writer-closure";
37
+ phase: "fenced";
38
+ operationId: string;
39
+ profileKey: string;
40
+ recoveryKey: string;
41
+ owner: TelegramWorkspaceAdmissionOwner;
42
+ requestedAtMs: number;
43
+ acquiredAtMs: number;
44
+ }
45
+ export declare function normalizeTelegramWorkspaceJournalWriterClosureFence(value: unknown, profileKey: string): TelegramWorkspaceJournalWriterClosureFence | undefined;
46
+ export interface TelegramWorkspaceJournalWriterProtocolMode {
47
+ version: 1;
48
+ protocol: "custody-v3";
49
+ profileKey: string;
50
+ recoveryKey: string;
51
+ startupAuthorityId: string;
52
+ closureOperationId: string;
53
+ writerInventorySha256: string;
54
+ installedBy: TelegramWorkspaceAdmissionOwner;
55
+ installedAtMs: number;
56
+ }
57
+ export declare function normalizeTelegramWorkspaceJournalWriterProtocolMode(value: unknown, profileKey: string): TelegramWorkspaceJournalWriterProtocolMode | undefined;
58
+ interface TelegramWorkspaceRetirementFenceBase {
59
+ destructiveKind?: TelegramWorkspaceDeletionFenceKind;
60
+ operationId: string;
61
+ retirementIntentId: string;
62
+ profileKey: string;
63
+ bindingKey: string;
64
+ slot: string;
65
+ target: TelegramTarget & {
66
+ threadId: number;
67
+ };
68
+ leaderEpoch: number | string;
69
+ retirementRequestedAtMs: number;
70
+ owner: TelegramWorkspaceAdmissionOwner;
71
+ acquiredAtMs: number;
72
+ }
73
+ export type TelegramWorkspaceRetirementFence = (TelegramWorkspaceRetirementFenceBase & {
74
+ phase: "fenced";
75
+ }) | (TelegramWorkspaceRetirementFenceBase & {
76
+ phase: "deletion-issued";
77
+ deletionIssuedAtMs: number;
78
+ }) | (TelegramWorkspaceRetirementFenceBase & {
79
+ phase: "commit-ready";
80
+ absenceConfirmedAtMs: number;
81
+ deletionIssuedAtMs?: number;
82
+ });
83
+ export type TelegramWorkspaceDestructiveFence = TelegramWorkspaceRetirementFence | TelegramWorkspaceJournalWriterClosureFence;
84
+ export declare function isTelegramWorkspaceRetirementFence(fence: TelegramWorkspaceDestructiveFence): fence is TelegramWorkspaceRetirementFence;
85
+ export interface TelegramWorkspaceDeletionPermit {
86
+ readonly destructiveKind?: TelegramWorkspaceDeletionFenceKind;
87
+ readonly operationId: string;
88
+ readonly retirementIntentId: string;
89
+ readonly profileKey: string;
90
+ readonly bindingKey: string;
91
+ readonly slot: string;
92
+ readonly target: TelegramTarget & {
93
+ threadId: number;
94
+ };
95
+ readonly leaderEpoch: number | string;
96
+ readonly issuedAtMs: number;
97
+ }
98
+ export type TelegramWorkspaceAdmissionBlockReason = "retirement-fenced" | "admission-active" | "retirement-active";
99
+ export type TelegramWorkspaceAdmissionAcquireResult = {
100
+ kind: "acquired";
101
+ lease: TelegramWorkspaceAdmissionLease;
102
+ resumed: boolean;
103
+ } | {
104
+ kind: "blocked";
105
+ reason: "retirement-fenced";
106
+ };
107
+ export type TelegramWorkspaceRetirementFenceAcquireResult = {
108
+ kind: "acquired";
109
+ fence: TelegramWorkspaceRetirementFence;
110
+ resumed: boolean;
111
+ } | {
112
+ kind: "blocked";
113
+ reason: "admission-active" | "retirement-active";
114
+ };
115
+ export type TelegramWorkspaceJournalWriterProtocolInstallResult = {
116
+ mode: TelegramWorkspaceJournalWriterProtocolMode;
117
+ resumed: boolean;
118
+ };
119
+ export type TelegramWorkspaceJournalWriterClosureAcquireResult = {
120
+ kind: "acquired";
121
+ fence: TelegramWorkspaceJournalWriterClosureFence;
122
+ resumed: boolean;
123
+ } | {
124
+ kind: "blocked";
125
+ reason: "admission-active" | "retirement-active";
126
+ };
127
+ export type TelegramWorkspaceDeletionPermitResult = {
128
+ kind: "issued";
129
+ fence: TelegramWorkspaceRetirementFence;
130
+ permit: TelegramWorkspaceDeletionPermit;
131
+ } | {
132
+ kind: "already-issued";
133
+ fence: TelegramWorkspaceRetirementFence;
134
+ };
135
+ export interface TelegramWorkspaceAdmissionLedgerSnapshot {
136
+ profileKey: string;
137
+ leases: TelegramWorkspaceAdmissionLease[];
138
+ fence?: TelegramWorkspaceDestructiveFence;
139
+ writerProtocolMode?: TelegramWorkspaceJournalWriterProtocolMode;
140
+ }
141
+ export type TelegramWorkspaceAdmissionPublicationBoundary = "before-write" | "after-write-before-rename";
142
+ export interface TelegramWorkspaceAdmissionLedgerOptions {
143
+ path: string;
144
+ profileKey: string;
145
+ owner: TelegramWorkspaceAdmissionOwner;
146
+ getNowMs?: () => number;
147
+ getProcessLiveness?: (owner: TelegramWorkspaceAdmissionOwner) => TelegramProcessLiveness;
148
+ publishRename?: (sourcePath: PathLike, destinationPath: PathLike) => void;
149
+ onPublicationBoundary?: (boundary: TelegramWorkspaceAdmissionPublicationBoundary, path: string) => void;
150
+ authorizeJournalWriterProtocolClosure?: (input: {
151
+ mode: TelegramWorkspaceJournalWriterProtocolMode;
152
+ closure: TelegramWorkspaceJournalWriterClosureFence;
153
+ }) => boolean;
154
+ }
155
+ export declare class TelegramWorkspaceAdmissionError extends Error {
156
+ readonly code: "invalid-input" | "invalid-state" | "state-unavailable" | "authority-changed" | "admission-blocked" | "publication-unknown";
157
+ constructor(code: TelegramWorkspaceAdmissionError["code"], message: string, options?: ErrorOptions);
158
+ }
159
+ export interface TelegramWorkspaceAdmissionLedger {
160
+ getProfileKey: () => string;
161
+ getOwner: () => TelegramWorkspaceAdmissionOwner;
162
+ listReservedSlots: () => string[];
163
+ read: () => TelegramWorkspaceAdmissionLedgerSnapshot;
164
+ acquireAdmission: (input: {
165
+ operationId: string;
166
+ operationKind: string;
167
+ scope: TelegramWorkspaceAdmissionScope;
168
+ }) => TelegramWorkspaceAdmissionAcquireResult;
169
+ releaseAdmission: (expected: TelegramWorkspaceAdmissionLease) => boolean;
170
+ acquireJournalWriterClosure: (input: {
171
+ operationId: string;
172
+ recoveryKey: string;
173
+ requestedAtMs: number;
174
+ }) => TelegramWorkspaceJournalWriterClosureAcquireResult;
175
+ releaseJournalWriterClosure: (expected: TelegramWorkspaceJournalWriterClosureFence) => boolean;
176
+ installJournalWriterProtocolMode: (expected: TelegramWorkspaceJournalWriterClosureFence, input: {
177
+ startupAuthorityId: string;
178
+ writerInventorySha256: string;
179
+ }) => TelegramWorkspaceJournalWriterProtocolInstallResult;
180
+ acquireJournalWriterAdmission: (input: {
181
+ operationId: string;
182
+ recoveryKey: string;
183
+ startupAuthorityId: string;
184
+ closureOperationId: string;
185
+ writerInventorySha256: string;
186
+ }) => TelegramWorkspaceAdmissionAcquireResult;
187
+ acquireRetirementFence: (input: {
188
+ operationId: string;
189
+ retirementIntentId: string;
190
+ bindingKey: string;
191
+ slot: string;
192
+ target: TelegramTarget & {
193
+ threadId: number;
194
+ };
195
+ leaderEpoch: number | string;
196
+ retirementRequestedAtMs: number;
197
+ }) => TelegramWorkspaceRetirementFenceAcquireResult;
198
+ acquireThreadCleanupFence: (input: {
199
+ operationId: string;
200
+ cleanupWorkSetId: string;
201
+ bindingKey: string;
202
+ slot: string;
203
+ target: TelegramTarget & {
204
+ threadId: number;
205
+ };
206
+ leaderEpoch: number | string;
207
+ cleanupRequestedAtMs: number;
208
+ }) => TelegramWorkspaceRetirementFenceAcquireResult;
209
+ adoptRetirementFence: (expected: TelegramWorkspaceRetirementFence, replacement: {
210
+ owner: TelegramWorkspaceAdmissionOwner;
211
+ leaderEpoch: number | string;
212
+ }) => TelegramWorkspaceRetirementFence;
213
+ adoptThreadCleanupFence: TelegramWorkspaceAdmissionLedger["adoptRetirementFence"];
214
+ issueDeletionPermit: (expected: TelegramWorkspaceRetirementFence) => TelegramWorkspaceDeletionPermitResult;
215
+ issueThreadCleanupDeletionPermit: TelegramWorkspaceAdmissionLedger["issueDeletionPermit"];
216
+ confirmRetirementAbsence: (expected: TelegramWorkspaceRetirementFence) => TelegramWorkspaceRetirementFence;
217
+ confirmThreadCleanupAbsence: TelegramWorkspaceAdmissionLedger["confirmRetirementAbsence"];
218
+ releaseUnissuedRetirementFence: (expected: TelegramWorkspaceRetirementFence) => boolean;
219
+ releaseUnissuedThreadCleanupFence: TelegramWorkspaceAdmissionLedger["releaseUnissuedRetirementFence"];
220
+ completeRetirementFence: (expected: TelegramWorkspaceRetirementFence) => boolean;
221
+ completeThreadCleanupFence: TelegramWorkspaceAdmissionLedger["completeRetirementFence"];
222
+ }
223
+ export declare function resolveTelegramWorkspaceDestructiveFenceKind(fence: {
224
+ destructiveKind?: TelegramWorkspaceDestructiveFenceKind;
225
+ }): TelegramWorkspaceDestructiveFenceKind;
226
+ export declare function createTelegramWorkspaceAdmissionOperationId(): string;
227
+ export declare function createTelegramWorkspaceAdmissionProfileKey(input: {
228
+ profileName?: string;
229
+ botToken: string;
230
+ }): string;
231
+ export interface TelegramWorkspaceAdmissionRuntimeBinding {
232
+ resolve: () => TelegramWorkspaceAdmissionLedger | undefined;
233
+ }
234
+ export declare function createTelegramWorkspaceAdmissionRuntimeBinding(input: {
235
+ getProfileName: () => string | undefined;
236
+ getBotToken: () => string | undefined;
237
+ getPath: (profileName?: string) => string;
238
+ owner: TelegramWorkspaceAdmissionOwner;
239
+ getNowMs?: () => number;
240
+ getProcessLiveness?: (owner: TelegramWorkspaceAdmissionOwner) => TelegramProcessLiveness;
241
+ }): TelegramWorkspaceAdmissionRuntimeBinding;
242
+ export declare function runWithTelegramWorkspaceAdmissions<T>(input: {
243
+ ledger: Pick<TelegramWorkspaceAdmissionLedger, "acquireAdmission" | "releaseAdmission">;
244
+ operationId: string;
245
+ operationKind: string;
246
+ scopes: readonly TelegramWorkspaceAdmissionScope[];
247
+ operation: () => T;
248
+ }): T;
249
+ export declare function createTelegramWorkspaceJournalWriterAdmission(input: {
250
+ ledger: Pick<TelegramWorkspaceAdmissionLedger, "acquireAdmission" | "acquireJournalWriterAdmission" | "releaseAdmission">;
251
+ protocolAuthority?: Omit<Parameters<TelegramWorkspaceAdmissionLedger["acquireJournalWriterAdmission"]>[0], "operationId">;
252
+ createOperationId?: () => string;
253
+ onReleaseError?: (error: unknown) => void;
254
+ }): <T>(operation: () => T) => T;
255
+ export declare function runWithTelegramWorkspaceAdmissionsAsync<T>(input: {
256
+ ledger: Pick<TelegramWorkspaceAdmissionLedger, "acquireAdmission" | "releaseAdmission">;
257
+ operationId: string;
258
+ operationKind: string;
259
+ scopes: readonly TelegramWorkspaceAdmissionScope[];
260
+ operation: () => Promise<T>;
261
+ onReleaseError?: (error: unknown) => void;
262
+ }): Promise<T>;
263
+ export declare function createTelegramWorkspaceAdmissionLedger(options: TelegramWorkspaceAdmissionLedgerOptions): TelegramWorkspaceAdmissionLedger;
264
+ export {};