@deadragdoll/tellymcp 0.0.10 → 0.0.12

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 (154) hide show
  1. package/.env.example.client +42 -52
  2. package/.env.example.gateway +48 -62
  3. package/CHANGELOG.md +3 -3
  4. package/README-ru.md +226 -376
  5. package/README.md +213 -1184
  6. package/TOOLS.md +320 -377
  7. package/VERSION.md +13 -13
  8. package/config/codex/plugins/telly-workflows/.codex-plugin/plugin.json +18 -0
  9. package/config/codex/plugins/telly-workflows/references/invariants.md +10 -0
  10. package/config/codex/plugins/telly-workflows/skills/telly-browser-screenshot/SKILL.md +27 -0
  11. package/config/codex/plugins/telly-workflows/skills/telly-collab-artifact/SKILL.md +22 -0
  12. package/config/codex/plugins/telly-workflows/skills/telly-human-telegram/SKILL.md +31 -0
  13. package/config/codex/plugins/telly-workflows/skills/telly-partner-note/SKILL.md +36 -0
  14. package/config/templates/env.both.template +39 -20
  15. package/config/templates/env.client.template +34 -19
  16. package/config/templates/env.gateway.template +37 -22
  17. package/dist/cli.js +322 -75
  18. package/dist/codexPluginInstaller.js +215 -0
  19. package/dist/lib/mixins/session.errors.js +34 -1
  20. package/dist/lib/pinoTargets.js +2 -2
  21. package/dist/moleculer.config.js +7 -5
  22. package/dist/services/features/telegram-mcp/browser.service.js +94 -1
  23. package/dist/services/features/telegram-mcp/collaboration.service.js +40 -3
  24. package/dist/services/features/telegram-mcp/ensuredb.service.js +145 -26
  25. package/dist/services/features/telegram-mcp/gateway-delivery.service.js +285 -103
  26. package/dist/services/features/telegram-mcp/gateway-socket.service.js +907 -85
  27. package/dist/services/features/telegram-mcp/gateway.service.js +876 -81
  28. package/dist/services/features/telegram-mcp/mcp-http.service.js +8 -0
  29. package/dist/services/features/telegram-mcp/mcp-server.service.js +17 -26
  30. package/dist/services/features/telegram-mcp/notify.service.js +127 -1
  31. package/dist/services/features/telegram-mcp/session-context.service.js +28 -1
  32. package/dist/services/features/telegram-mcp/src/app/bootstrap/runtime.js +132 -10
  33. package/dist/services/features/telegram-mcp/src/app/config/env.js +158 -52
  34. package/dist/services/features/telegram-mcp/src/app/http.js +375 -42
  35. package/dist/services/features/telegram-mcp/src/app/webapp/assets.js +386 -58
  36. package/dist/services/features/telegram-mcp/src/app/webapp/auth.js +3 -0
  37. package/dist/services/features/telegram-mcp/src/app/webapp/terminal.js +12 -0
  38. package/dist/services/features/telegram-mcp/src/entities/request/model/schema.js +120 -104
  39. package/dist/services/features/telegram-mcp/src/entities/xchange/model/types.js +2 -0
  40. package/dist/services/features/telegram-mcp/src/features/ask-user/model/askUserTelegram.js +1 -1
  41. package/dist/services/features/telegram-mcp/src/features/browser/model/browserOpenTool.js +1 -1
  42. package/dist/services/features/telegram-mcp/src/features/browser/model/browserScreenshotTool.js +1 -1
  43. package/dist/services/features/telegram-mcp/src/features/browser/model/browserService.js +306 -26
  44. package/dist/services/features/telegram-mcp/src/features/collaboration/model/gatewaySessionsService.js +71 -0
  45. package/dist/services/features/telegram-mcp/src/features/collaboration/model/listGatewaySessionsTool.js +33 -0
  46. package/dist/services/features/telegram-mcp/src/features/collaboration/model/localCollaborationBackend.js +93 -82
  47. package/dist/services/features/telegram-mcp/src/features/collaboration/model/sendPartnerFileService.js +47 -5
  48. package/dist/services/features/telegram-mcp/src/features/collaboration/model/sendPartnerFileTool.js +1 -1
  49. package/dist/services/features/telegram-mcp/src/features/collaboration/model/sendPartnerNoteTool.js +1 -1
  50. package/dist/services/features/telegram-mcp/src/features/distributed-client/model/gatewayClientAccess.js +82 -0
  51. package/dist/services/features/telegram-mcp/src/features/distributed-client/model/gatewayCollaborationBackend.js +22 -14
  52. package/dist/services/features/telegram-mcp/src/features/distributed-gateway/model/gatewayHttpService.js +697 -75
  53. package/dist/services/features/telegram-mcp/src/features/distributed-gateway/model/remoteConsoleActionClient.js +76 -0
  54. package/dist/services/features/telegram-mcp/src/features/embedded-runtime/model/embeddedRuntimeBroker.js +92 -0
  55. package/dist/services/features/telegram-mcp/src/features/foreground-terminal/model/foregroundTerminalRuntime.js +192 -0
  56. package/dist/services/features/telegram-mcp/src/features/notify/model/notifyService.js +382 -3
  57. package/dist/services/features/telegram-mcp/src/features/notify/model/notifyTelegramTool.js +1 -1
  58. package/dist/services/features/telegram-mcp/src/features/notify/model/sendFileToTelegramTool.js +33 -0
  59. package/dist/services/features/telegram-mcp/src/features/session-context/model/clearSessionContextTool.js +1 -1
  60. package/dist/services/features/telegram-mcp/src/features/session-context/model/getSessionContextTool.js +1 -1
  61. package/dist/services/features/telegram-mcp/src/features/session-context/model/renameSessionTool.js +1 -1
  62. package/dist/services/features/telegram-mcp/src/features/session-context/model/sessionContextService.js +42 -200
  63. package/dist/services/features/telegram-mcp/src/features/session-context/model/setSessionContextTool.js +1 -1
  64. package/dist/services/features/telegram-mcp/src/features/terminal-buffer/model/terminalBufferService.js +96 -0
  65. package/dist/services/features/telegram-mcp/src/features/terminal-input/model/terminalInputService.js +97 -0
  66. package/dist/services/features/telegram-mcp/src/features/tools-sync/model/refreshToolsMarkdownService.js +107 -58
  67. package/dist/services/features/telegram-mcp/src/features/tools-sync/model/refreshToolsMarkdownTool.js +1 -1
  68. package/dist/services/features/telegram-mcp/src/features/xchange/model/getXchangeRecordTool.js +28 -0
  69. package/dist/services/features/telegram-mcp/src/features/xchange/model/listXchangeRecordsTool.js +28 -0
  70. package/dist/services/features/telegram-mcp/src/features/xchange/model/markXchangeRecordReadTool.js +28 -0
  71. package/dist/services/features/telegram-mcp/src/features/xchange/model/xchangeService.js +169 -0
  72. package/dist/services/features/telegram-mcp/src/processes/human-approval/model/orchestrator.js +5 -4
  73. package/dist/services/features/telegram-mcp/src/shared/i18n/resources/en.js +143 -42
  74. package/dist/services/features/telegram-mcp/src/shared/i18n/resources/ru.js +139 -38
  75. package/dist/services/features/telegram-mcp/src/shared/integrations/memory/processLocalSessionStore.js +27 -0
  76. package/dist/services/features/telegram-mcp/src/shared/integrations/object-storage/minioExchangeStore.js +11 -8
  77. package/dist/services/features/telegram-mcp/src/shared/integrations/redis/stateStore.js +69 -70
  78. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/proxyFetch.js +21 -22
  79. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transport.js +444 -7093
  80. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportAttachmentStore.js +93 -0
  81. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportBroadcastActions.js +385 -0
  82. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportConsoleRegistry.js +149 -0
  83. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportConstructorWiring.js +642 -0
  84. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportContent.js +84 -0
  85. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportContext.js +78 -0
  86. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportDocumentActions.js +36 -0
  87. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportEventActions.js +292 -0
  88. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportFileHandoffActions.js +352 -0
  89. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportFormatting.js +75 -0
  90. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportGatewayActions.js +44 -0
  91. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportLifecycleActions.js +161 -0
  92. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportLive.js +56 -0
  93. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportLiveActions.js +77 -0
  94. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportMenuCallbacks.js +254 -0
  95. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportMenuFactories.js +538 -0
  96. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportMenuFingerprints.js +93 -0
  97. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportMenuFlow.js +344 -0
  98. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportMenuShell.js +65 -0
  99. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportMenuState.js +408 -0
  100. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportMenuText.js +216 -0
  101. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportMessageFlow.js +452 -0
  102. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportOutputActions.js +189 -0
  103. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportPartnerActions.js +286 -0
  104. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportPayloadState.js +108 -0
  105. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportProjectActions.js +463 -0
  106. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportProjectEntryActions.js +202 -0
  107. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportProjectEvents.js +99 -0
  108. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportProjectMenus.js +138 -0
  109. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportProjectState.js +308 -0
  110. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportProjectView.js +426 -0
  111. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportRequestFlow.js +278 -0
  112. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportSessionActions.js +143 -0
  113. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportTerminalActions.js +665 -0
  114. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportTerminalRuntime.js +226 -0
  115. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportTypes.js +2 -0
  116. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportUtils.js +330 -0
  117. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportXchangeState.js +107 -0
  118. package/dist/services/features/telegram-mcp/src/shared/integrations/terminal/client.js +255 -0
  119. package/dist/services/features/telegram-mcp/src/shared/integrations/terminal/ptyRegistry.js +543 -0
  120. package/dist/services/features/telegram-mcp/src/shared/integrations/xchange/sqliteRecordStore.js +223 -0
  121. package/dist/services/features/telegram-mcp/src/shared/lib/gatewayScope.js +24 -0
  122. package/dist/services/features/telegram-mcp/src/shared/lib/logger/logger.js +6 -0
  123. package/dist/services/features/telegram-mcp/src/shared/lib/project-identity/projectIdentity.js +147 -76
  124. package/dist/services/features/telegram-mcp/src/shared/lib/telegramXchangeRecords.js +72 -0
  125. package/dist/services/features/telegram-mcp/src/shared/lib/terminalPromptDetection.js +409 -0
  126. package/dist/services/features/telegram-mcp/src/shared/lib/version/versionHandshake.js +129 -1
  127. package/dist/services/features/telegram-mcp/src/shared/lib/xchangeRecordHints.js +98 -0
  128. package/dist/services/features/telegram-mcp/standalone-http.service.js +12 -0
  129. package/dist/services/features/telegram-mcp/terminal-buffer.service.js +42 -0
  130. package/dist/services/features/telegram-mcp/terminal-input.service.js +41 -0
  131. package/dist/services/features/telegram-mcp/tools-sync.service.js +15 -1
  132. package/dist/services/features/telegram-mcp/xchange.service.js +123 -0
  133. package/docs/STANDALONE-ru.md +172 -0
  134. package/docs/STANDALONE.md +172 -0
  135. package/package.json +10 -5
  136. package/scripts/build-package-artifact.sh +27 -0
  137. package/scripts/deploy-gateway.sh +64 -0
  138. package/scripts/deploy-onebot-branch.sh +27 -0
  139. package/scripts/postinstall.js +11 -36
  140. package/STANDALONE-ru.md +0 -274
  141. package/STANDALONE.md +0 -274
  142. package/dist/services/features/telegram-mcp/inbox.service.js +0 -33
  143. package/dist/services/features/telegram-mcp/pair.service.js +0 -33
  144. package/dist/services/features/telegram-mcp/src/app/webapp/tmux.js +0 -10
  145. package/dist/services/features/telegram-mcp/src/features/inbox/model/deleteTelegramInboxMessageTool.js +0 -33
  146. package/dist/services/features/telegram-mcp/src/features/inbox/model/getTelegramInboxCountTool.js +0 -33
  147. package/dist/services/features/telegram-mcp/src/features/inbox/model/getTelegramInboxTool.js +0 -33
  148. package/dist/services/features/telegram-mcp/src/features/inbox/model/inboxService.js +0 -77
  149. package/dist/services/features/telegram-mcp/src/features/pair-session/model/clearSessionPairingTool.js +0 -33
  150. package/dist/services/features/telegram-mcp/src/features/pair-session/model/createSessionPairCodeTool.js +0 -33
  151. package/dist/services/features/telegram-mcp/src/features/pair-session/model/generatePairCode.js +0 -202
  152. package/dist/services/features/telegram-mcp/src/features/session-context/model/getTmuxTargetTool.js +0 -33
  153. package/dist/services/features/telegram-mcp/src/features/session-context/model/setTmuxTargetTool.js +0 -33
  154. package/dist/services/features/telegram-mcp/src/shared/integrations/tmux/client.js +0 -363
@@ -0,0 +1,665 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TransportTerminalActions = void 0;
4
+ const grammy_1 = require("grammy");
5
+ const client_1 = require("../terminal/client");
6
+ const relay_1 = require("../../../app/webapp/relay");
7
+ const terminalPromptDetection_1 = require("../../lib/terminalPromptDetection");
8
+ const transportUtils_1 = require("./transportUtils");
9
+ const TERMINAL_NUDGE_FAILURE_NOTICE_COOLDOWN_MS = 5 * 60 * 1000;
10
+ const TERMINAL_PROMPT_SCAN_MATCHED_LINES_LIMIT = 6;
11
+ class TransportTerminalActions {
12
+ host;
13
+ constructor(host) {
14
+ this.host = host;
15
+ }
16
+ buildCapturePreview(capture) {
17
+ return capture
18
+ .split("\n")
19
+ .map((line) => line.trimEnd())
20
+ .filter((line) => line.trim().length > 0)
21
+ .slice(-6);
22
+ }
23
+ buildPromptActionTokens(detection) {
24
+ const actions = [];
25
+ const promptActions = detection.promptActions;
26
+ if (promptActions) {
27
+ actions.push(...promptActions.numberedOptions);
28
+ if (promptActions.hasEnter) {
29
+ actions.push("enter");
30
+ }
31
+ if (promptActions.hasEscape) {
32
+ actions.push("escape");
33
+ }
34
+ }
35
+ return Array.from(new Set(actions));
36
+ }
37
+ buildPromptActionKeyboard(payloadKey, actions) {
38
+ if (actions.length === 0) {
39
+ return null;
40
+ }
41
+ const keyboard = new grammy_1.InlineKeyboard();
42
+ for (const action of actions) {
43
+ const label = action === "enter"
44
+ ? "Enter"
45
+ : action === "escape"
46
+ ? "Esc"
47
+ : action;
48
+ keyboard.text(label, `terminal-prompt-action:${action}:${payloadKey}`);
49
+ }
50
+ return keyboard;
51
+ }
52
+ async sendPromptAction(sessionId, action) {
53
+ if (action === "enter" || action === "escape") {
54
+ await this.host.callGatewayJson("/live/action", {
55
+ session_id: sessionId,
56
+ action,
57
+ });
58
+ return;
59
+ }
60
+ await this.host.callGatewayJson("/live/action", {
61
+ session_id: sessionId,
62
+ action: "text",
63
+ text: action,
64
+ });
65
+ }
66
+ isExpectedRelayCaptureMiss(error) {
67
+ const message = error instanceof Error ? error.message : String(error);
68
+ return (/\bInvalid live relay view response\b/u.test(message) ||
69
+ /\bis not connected\b/u.test(message) ||
70
+ /\brequest is rejected\b/u.test(message) ||
71
+ /\brelay live capture did not return terminal content\b/u.test(message));
72
+ }
73
+ buildRelayTerminalTarget(sessionId) {
74
+ const relay = (0, relay_1.parseLiveRelaySessionId)(sessionId);
75
+ if (!relay) {
76
+ return null;
77
+ }
78
+ return `relay:${relay.clientUuid}/${relay.localSessionId}`;
79
+ }
80
+ extractTerminalTextFromGatewayMarkdown(markdownContent) {
81
+ const match = markdownContent.match(/```text\n([\s\S]*?)\n```/u);
82
+ return match?.[1] ?? markdownContent;
83
+ }
84
+ async ensurePtyTargetForSession(sessionId, session) {
85
+ const ensuredTarget = (0, client_1.ensureTerminalTargetForSession)(this.host.config.terminal, {
86
+ sessionId,
87
+ ...(typeof session.cwd === "string" ? { cwd: session.cwd } : {}),
88
+ ...(typeof session.terminalTarget === "string"
89
+ ? { target: session.terminalTarget }
90
+ : {}),
91
+ });
92
+ if (!ensuredTarget) {
93
+ return null;
94
+ }
95
+ if (ensuredTarget === session.terminalTarget) {
96
+ return ensuredTarget;
97
+ }
98
+ await this.host.sessionStore.setSession({
99
+ ...session,
100
+ terminalTarget: ensuredTarget,
101
+ updatedAt: new Date().toISOString(),
102
+ });
103
+ this.host.logger.info("PTY terminal target normalized", {
104
+ sessionId,
105
+ previousTerminalTarget: session.terminalTarget,
106
+ normalizedTerminalTarget: ensuredTarget,
107
+ });
108
+ return ensuredTarget;
109
+ }
110
+ async nudgeForInboxMessage(sessionId) {
111
+ await this.nudgeForSession(sessionId, {
112
+ message: this.host.config.terminal.nudgeMessage,
113
+ reason: "human_message",
114
+ });
115
+ }
116
+ async nudgeForSession(sessionId, input) {
117
+ if (!this.host.config.terminal.nudgeEnabled) {
118
+ return;
119
+ }
120
+ const session = await this.host.sessionStore.getSession(sessionId);
121
+ if (!session?.terminalTarget) {
122
+ this.host.logger.debug("terminal nudge skipped", {
123
+ sessionId,
124
+ nudgeReason: input.reason,
125
+ skipReason: "no_terminal_target",
126
+ });
127
+ return;
128
+ }
129
+ let normalizedSession = session;
130
+ if (!session.terminalTarget.startsWith("pty:")) {
131
+ const normalizedTarget = await this.ensurePtyTargetForSession(sessionId, session);
132
+ if (normalizedTarget) {
133
+ normalizedSession = {
134
+ ...session,
135
+ terminalTarget: normalizedTarget,
136
+ };
137
+ }
138
+ }
139
+ const nowMs = Date.now();
140
+ if (!(0, transportUtils_1.shouldNudge)(session.lastTerminalNudgeAt, this.host.config.terminal.nudgeCooldownSeconds, nowMs)) {
141
+ this.host.logger.debug("terminal nudge skipped because of cooldown", {
142
+ sessionId,
143
+ reason: input.reason,
144
+ terminalTarget: normalizedSession.terminalTarget,
145
+ lastTerminalNudgeAt: normalizedSession.lastTerminalNudgeAt,
146
+ });
147
+ return;
148
+ }
149
+ await this.host.sendTypingForSession(sessionId);
150
+ let terminalTarget = normalizedSession.terminalTarget ?? null;
151
+ if (!terminalTarget) {
152
+ this.host.logger.debug("terminal nudge skipped", {
153
+ sessionId,
154
+ nudgeReason: input.reason,
155
+ skipReason: "normalized_target_missing",
156
+ });
157
+ return;
158
+ }
159
+ try {
160
+ await (0, client_1.sendTerminalLiteralLine)(this.host.config.terminal, terminalTarget, input.message);
161
+ }
162
+ catch (error) {
163
+ if ((0, client_1.isTerminalTargetInvalidError)(error) || (0, client_1.isTerminalUnavailableError)(error)) {
164
+ const recoveredTarget = await this.ensurePtyTargetForSession(sessionId, normalizedSession);
165
+ if (recoveredTarget) {
166
+ terminalTarget = recoveredTarget;
167
+ await (0, client_1.sendTerminalLiteralLine)(this.host.config.terminal, recoveredTarget, input.message);
168
+ }
169
+ else {
170
+ await this.notifyTargetInvalid(sessionId, normalizedSession, error);
171
+ throw error;
172
+ }
173
+ }
174
+ else {
175
+ throw error;
176
+ }
177
+ }
178
+ const lastTerminalNudgeAt = new Date(nowMs).toISOString();
179
+ await this.host.sessionStore.setSession({
180
+ ...normalizedSession,
181
+ terminalTarget: terminalTarget,
182
+ lastTerminalNudgeAt,
183
+ });
184
+ this.host.terminalNudgeFailureNoticeAt.delete(sessionId);
185
+ this.host.logger.info("terminal nudge sent", {
186
+ sessionId,
187
+ reason: input.reason,
188
+ message: input.message,
189
+ terminalTarget,
190
+ lastTerminalNudgeAt,
191
+ });
192
+ }
193
+ async tryRecoverTarget(sessionId, session) {
194
+ const recoveredTarget = await (0, client_1.resolveTerminalTargetFromHint)(this.host.config.terminal, {
195
+ terminalTarget: session.terminalTarget,
196
+ });
197
+ if (!recoveredTarget || recoveredTarget === session.terminalTarget) {
198
+ return recoveredTarget;
199
+ }
200
+ await this.host.sessionStore.setSession({
201
+ ...session,
202
+ terminalTarget: recoveredTarget,
203
+ updatedAt: new Date().toISOString(),
204
+ });
205
+ this.host.logger.warn("terminal target auto-recovered", {
206
+ sessionId,
207
+ previousTerminalTarget: session.terminalTarget,
208
+ recoveredTerminalTarget: recoveredTarget,
209
+ });
210
+ return recoveredTarget;
211
+ }
212
+ async notifyTargetInvalid(sessionId, session, error) {
213
+ const binding = await this.host.bindingStore.getBinding(sessionId);
214
+ if (!binding) {
215
+ return;
216
+ }
217
+ const nowMs = Date.now();
218
+ const lastNoticeAt = this.host.terminalNudgeFailureNoticeAt.get(sessionId);
219
+ if (lastNoticeAt &&
220
+ nowMs - lastNoticeAt < TERMINAL_NUDGE_FAILURE_NOTICE_COOLDOWN_MS) {
221
+ return;
222
+ }
223
+ this.host.terminalNudgeFailureNoticeAt.set(sessionId, nowMs);
224
+ const sessionLabel = session.label ?? sessionId;
225
+ const terminalTarget = session.terminalTarget ?? "unknown";
226
+ const errorMessage = error instanceof Error ? error.message : String(error);
227
+ const locale = await this.host.resolveLocaleForTelegramUserId(binding.telegramUserId);
228
+ try {
229
+ await this.host.sendNotification({
230
+ sessionId,
231
+ sessionLabel: "TellyMCP",
232
+ recipient: {
233
+ telegramChatId: binding.telegramChatId,
234
+ telegramUserId: binding.telegramUserId,
235
+ },
236
+ message: [
237
+ this.host.t(locale, "menu:notices.terminal.target_invalid_title", {
238
+ sessionName: sessionLabel,
239
+ }),
240
+ this.host.t(locale, "menu:notices.terminal.target_invalid_target", {
241
+ terminalTarget,
242
+ }),
243
+ this.host.t(locale, "menu:system.error_prefix", {
244
+ message: errorMessage,
245
+ }),
246
+ this.host.t(locale, "menu:system.terminal_recreated_hint"),
247
+ this.host.t(locale, "menu:notices.terminal.target_invalid_action"),
248
+ ].join("\n"),
249
+ });
250
+ }
251
+ catch (notifyError) {
252
+ this.host.logger.warn("Failed to deliver terminal target failure notification", {
253
+ sessionId,
254
+ terminalTarget,
255
+ telegramChatId: binding.telegramChatId,
256
+ telegramUserId: binding.telegramUserId,
257
+ notifyError: notifyError instanceof Error
258
+ ? (notifyError.stack ?? notifyError.message)
259
+ : String(notifyError),
260
+ });
261
+ }
262
+ }
263
+ async notifyUnavailable(sessionId, session, error) {
264
+ const binding = await this.host.bindingStore.getBinding(sessionId);
265
+ if (!binding) {
266
+ return;
267
+ }
268
+ const nowMs = Date.now();
269
+ const lastNoticeAt = this.host.terminalNudgeFailureNoticeAt.get(sessionId);
270
+ if (lastNoticeAt &&
271
+ nowMs - lastNoticeAt < TERMINAL_NUDGE_FAILURE_NOTICE_COOLDOWN_MS) {
272
+ return;
273
+ }
274
+ this.host.terminalNudgeFailureNoticeAt.set(sessionId, nowMs);
275
+ const sessionLabel = session.label ?? sessionId;
276
+ const terminalTarget = session.terminalTarget ?? "unknown";
277
+ const errorMessage = error instanceof Error ? error.message : String(error);
278
+ const locale = await this.host.resolveLocaleForTelegramUserId(binding.telegramUserId);
279
+ try {
280
+ await this.host.sendNotification({
281
+ sessionId,
282
+ sessionLabel: "TellyMCP",
283
+ recipient: {
284
+ telegramChatId: binding.telegramChatId,
285
+ telegramUserId: binding.telegramUserId,
286
+ },
287
+ message: [
288
+ this.host.t(locale, "menu:notices.terminal.unavailable_title", {
289
+ sessionName: sessionLabel,
290
+ }),
291
+ this.host.t(locale, "menu:notices.terminal.unavailable_body"),
292
+ this.host.t(locale, "menu:notices.terminal.unavailable_target", {
293
+ terminalTarget,
294
+ }),
295
+ this.host.t(locale, "menu:system.error_prefix", {
296
+ message: errorMessage,
297
+ }),
298
+ this.host.t(locale, "menu:notices.terminal.unavailable_reason"),
299
+ this.host.t(locale, "menu:notices.terminal.unavailable_action"),
300
+ ].join("\n"),
301
+ });
302
+ }
303
+ catch (notifyError) {
304
+ this.host.logger.warn("Failed to deliver terminal unavailable notification", {
305
+ sessionId,
306
+ terminalTarget,
307
+ telegramChatId: binding.telegramChatId,
308
+ telegramUserId: binding.telegramUserId,
309
+ notifyError: notifyError instanceof Error
310
+ ? (notifyError.stack ?? notifyError.message)
311
+ : String(notifyError),
312
+ });
313
+ }
314
+ }
315
+ async scanPromptForSession(session) {
316
+ const relayTerminalTarget = this.buildRelayTerminalTarget(session.sessionId);
317
+ if (!session.terminalTarget && !relayTerminalTarget) {
318
+ this.host.logger.debug("terminal prompt scan skipped", {
319
+ sessionId: session.sessionId,
320
+ sessionLabel: session.label,
321
+ isRelaySession: relayTerminalTarget !== null,
322
+ skipReason: "no_terminal_target",
323
+ });
324
+ this.host.terminalPromptNoticeState.delete(session.sessionId);
325
+ return;
326
+ }
327
+ const binding = await this.host.bindingStore.getBinding(session.sessionId);
328
+ if (!binding) {
329
+ this.host.logger.debug("terminal prompt scan skipped", {
330
+ sessionId: session.sessionId,
331
+ sessionLabel: session.label,
332
+ isRelaySession: relayTerminalTarget !== null,
333
+ terminalTarget: session.terminalTarget ?? relayTerminalTarget,
334
+ skipReason: "no_binding",
335
+ });
336
+ this.host.terminalPromptNoticeState.delete(session.sessionId);
337
+ return;
338
+ }
339
+ let terminalTarget = session.terminalTarget ?? relayTerminalTarget;
340
+ let capture;
341
+ this.host.logger.debug("terminal prompt scan started", {
342
+ sessionId: session.sessionId,
343
+ sessionLabel: session.label,
344
+ isRelaySession: relayTerminalTarget !== null,
345
+ terminalTarget,
346
+ strategy: this.host.config.terminal.promptScanStrategy,
347
+ minScore: this.host.config.terminal.promptScanMinScore,
348
+ });
349
+ try {
350
+ capture = await this.capturePromptBuffer(session);
351
+ }
352
+ catch (error) {
353
+ if ((0, client_1.isTerminalUnavailableError)(error)) {
354
+ this.host.logger.debug("terminal prompt scan skipped because terminal is unavailable", {
355
+ sessionId: session.sessionId,
356
+ sessionLabel: session.label,
357
+ terminalTarget,
358
+ });
359
+ return;
360
+ }
361
+ if ((0, client_1.isTerminalTargetInvalidError)(error)) {
362
+ const recoveredTarget = await this.tryRecoverTarget(session.sessionId, session);
363
+ if (!recoveredTarget) {
364
+ this.host.logger.debug("terminal prompt scan skipped because target is invalid", {
365
+ sessionId: session.sessionId,
366
+ sessionLabel: session.label,
367
+ terminalTarget,
368
+ });
369
+ return;
370
+ }
371
+ terminalTarget = recoveredTarget;
372
+ capture = await this.capturePromptBuffer({
373
+ ...session,
374
+ terminalTarget: recoveredTarget,
375
+ });
376
+ }
377
+ else {
378
+ if (relayTerminalTarget &&
379
+ this.isExpectedRelayCaptureMiss(error)) {
380
+ this.host.logger.debug("terminal prompt scan skipped because relay capture is unavailable", {
381
+ sessionId: session.sessionId,
382
+ sessionLabel: session.label,
383
+ terminalTarget,
384
+ error: error instanceof Error
385
+ ? (error.stack ?? error.message)
386
+ : String(error),
387
+ });
388
+ this.host.terminalPromptNoticeState.delete(session.sessionId);
389
+ return;
390
+ }
391
+ this.host.logger.warn("terminal prompt scan capture failed", {
392
+ sessionId: session.sessionId,
393
+ terminalTarget,
394
+ error: error instanceof Error
395
+ ? (error.stack ?? error.message)
396
+ : String(error),
397
+ });
398
+ return;
399
+ }
400
+ }
401
+ const capturePreview = this.buildCapturePreview(capture);
402
+ this.host.logger.debug("terminal prompt buffer captured", {
403
+ sessionId: session.sessionId,
404
+ sessionLabel: session.label,
405
+ terminalTarget,
406
+ captureChars: capture.length,
407
+ captureLines: capture.split("\n").length,
408
+ previewTail: capturePreview,
409
+ });
410
+ const detection = (0, terminalPromptDetection_1.detectTerminalInteractivePrompt)(capture, {
411
+ strategy: this.host.config.terminal.promptScanStrategy,
412
+ minScore: this.host.config.terminal.promptScanMinScore,
413
+ });
414
+ if (!detection) {
415
+ this.host.logger.debug("terminal prompt scan found no interactive prompt", {
416
+ sessionId: session.sessionId,
417
+ sessionLabel: session.label,
418
+ terminalTarget,
419
+ strategy: this.host.config.terminal.promptScanStrategy,
420
+ minScore: this.host.config.terminal.promptScanMinScore,
421
+ previewTail: capturePreview,
422
+ });
423
+ this.host.terminalPromptNoticeState.delete(session.sessionId);
424
+ return;
425
+ }
426
+ if (!this.shouldSendPromptNotice(session.sessionId, detection)) {
427
+ return;
428
+ }
429
+ await this.notifyPromptDetected(session, binding, detection, terminalTarget ?? relayTerminalTarget ?? "unknown");
430
+ }
431
+ async capturePromptBuffer(session) {
432
+ const relay = (0, relay_1.parseLiveRelaySessionId)(session.sessionId);
433
+ if (relay) {
434
+ this.host.logger.debug("terminal prompt relay capture requested", {
435
+ sessionId: session.sessionId,
436
+ clientUuid: relay.clientUuid,
437
+ localSessionId: relay.localSessionId,
438
+ });
439
+ const output = await this.host.callGatewayJson("/live/capture-buffer", {
440
+ session_id: session.sessionId,
441
+ scope: {
442
+ mode: "visible",
443
+ },
444
+ });
445
+ const markdownContent = typeof output.markdown_content === "string"
446
+ ? output.markdown_content
447
+ : "";
448
+ if (!markdownContent.trim()) {
449
+ this.host.logger.warn("terminal prompt relay capture returned empty content", {
450
+ sessionId: session.sessionId,
451
+ clientUuid: relay.clientUuid,
452
+ localSessionId: relay.localSessionId,
453
+ });
454
+ throw new Error("relay live capture did not return terminal content");
455
+ }
456
+ return this.extractTerminalTextFromGatewayMarkdown(markdownContent);
457
+ }
458
+ const target = session.terminalTarget;
459
+ if (!target) {
460
+ throw new Error("terminal target is not configured");
461
+ }
462
+ if (this.host.config.terminal.captureMode === "visible") {
463
+ return (0, client_1.captureVisibleTerminal)(this.host.config.terminal, target, this.host.config.terminal.captureLines, this.host.config.webapp.visibleScreens);
464
+ }
465
+ return (0, client_1.captureTerminalPaneRange)(this.host.config.terminal, target, `-${this.host.config.terminal.captureLines}`, false);
466
+ }
467
+ shouldSendPromptNotice(sessionId, detection) {
468
+ const existing = this.host.terminalPromptNoticeState.get(sessionId);
469
+ if (existing && existing.fingerprint === detection.fingerprint) {
470
+ this.host.logger.debug("terminal prompt detected but fingerprint is unchanged", {
471
+ sessionId,
472
+ fingerprint: detection.fingerprint,
473
+ score: detection.score,
474
+ reasons: detection.reasons,
475
+ });
476
+ return false;
477
+ }
478
+ this.host.terminalPromptNoticeState.set(sessionId, {
479
+ fingerprint: detection.fingerprint,
480
+ sentAtMs: Date.now(),
481
+ });
482
+ return true;
483
+ }
484
+ async notifyPromptDetected(session, binding, detection, terminalTarget) {
485
+ if (!binding) {
486
+ return;
487
+ }
488
+ const locale = await this.host.resolveLocaleForTelegramUserId(binding.telegramUserId);
489
+ const sessionLabel = session.label ?? session.sessionId;
490
+ const excerpt = detection.excerpt
491
+ .split("\n")
492
+ .slice(-Math.max(TERMINAL_PROMPT_SCAN_MATCHED_LINES_LIMIT + 2, 8))
493
+ .join("\n") || detection.matchedLines.slice(-TERMINAL_PROMPT_SCAN_MATCHED_LINES_LIMIT).join("\n");
494
+ const promptActionTokens = this.buildPromptActionTokens(detection);
495
+ const payloadKey = promptActionTokens.length > 0
496
+ ? await this.host.createTerminalPromptActionPayload(session.sessionId, promptActionTokens)
497
+ : null;
498
+ const replyMarkup = payloadKey
499
+ ? this.buildPromptActionKeyboard(payloadKey, promptActionTokens)
500
+ : null;
501
+ await this.host.sendChatMessage(binding.telegramChatId, [
502
+ this.host.t(locale, "menu:notices.terminal.prompt_detected_title", {
503
+ sessionName: sessionLabel,
504
+ }),
505
+ excerpt,
506
+ ].join("\n"), replyMarkup ? { reply_markup: replyMarkup } : {}, {
507
+ kind: "notification",
508
+ sessionId: session.sessionId,
509
+ });
510
+ try {
511
+ await this.host.sendLiveViewLauncherMessage({
512
+ principal: {
513
+ telegramChatId: binding.telegramChatId,
514
+ telegramUserId: binding.telegramUserId,
515
+ },
516
+ sessionId: session.sessionId,
517
+ sessionName: sessionLabel,
518
+ locale,
519
+ });
520
+ }
521
+ catch (error) {
522
+ this.host.logger.warn("Failed to deliver terminal prompt live launcher", {
523
+ sessionId: session.sessionId,
524
+ terminalTarget,
525
+ telegramChatId: binding.telegramChatId,
526
+ telegramUserId: binding.telegramUserId,
527
+ error: error instanceof Error
528
+ ? (error.stack ?? error.message)
529
+ : String(error),
530
+ });
531
+ }
532
+ this.host.logger.info("terminal prompt detected", {
533
+ sessionId: session.sessionId,
534
+ terminalTarget,
535
+ score: detection.score,
536
+ strategy: this.host.config.terminal.promptScanStrategy,
537
+ minScore: this.host.config.terminal.promptScanMinScore,
538
+ reasons: detection.reasons,
539
+ fingerprint: detection.fingerprint,
540
+ matchedLines: detection.matchedLines,
541
+ excerpt,
542
+ });
543
+ }
544
+ async handlePromptActionCallback(ctx) {
545
+ const locale = await this.host.resolveLocaleForContext(ctx);
546
+ const data = ctx.callbackQuery?.data ?? "";
547
+ const match = data.match(/^terminal-prompt-action:(\d+|enter|escape):(.+)$/u);
548
+ if (!match) {
549
+ await ctx.answerCallbackQuery({
550
+ text: this.host.t(locale, "menu:notices.terminal.prompt_action_invalid"),
551
+ show_alert: true,
552
+ });
553
+ return;
554
+ }
555
+ const [, actionRaw, payloadKeyRaw] = match;
556
+ const payloadKey = payloadKeyRaw?.trim();
557
+ const action = actionRaw?.trim().toLowerCase();
558
+ if (!payloadKey || !action) {
559
+ await ctx.answerCallbackQuery({
560
+ text: this.host.t(locale, "menu:notices.terminal.prompt_action_invalid"),
561
+ show_alert: true,
562
+ });
563
+ return;
564
+ }
565
+ const payload = await this.host.getMenuPayloadByKey(payloadKey);
566
+ const storedActions = Array.isArray(payload?.promptActions)
567
+ ? payload.promptActions
568
+ .map((value) => (typeof value === "string" ? value.trim().toLowerCase() : ""))
569
+ .filter((value) => value.length > 0)
570
+ : [];
571
+ if (!payload ||
572
+ payload.kind !== "terminal-prompt-action" ||
573
+ !payload.sessionId ||
574
+ !storedActions.includes(action)) {
575
+ await ctx.answerCallbackQuery({
576
+ text: this.host.t(locale, "menu:notices.terminal.prompt_action_stale"),
577
+ show_alert: true,
578
+ });
579
+ return;
580
+ }
581
+ try {
582
+ await this.sendPromptAction(String(payload.sessionId), action);
583
+ await ctx.answerCallbackQuery({
584
+ text: this.host.t(locale, "menu:notices.terminal.prompt_action_sent", {
585
+ action: action === "enter"
586
+ ? "Enter"
587
+ : action === "escape"
588
+ ? "Esc"
589
+ : action,
590
+ }),
591
+ });
592
+ }
593
+ catch (error) {
594
+ this.host.logger.warn("terminal prompt action callback failed", {
595
+ sessionId: String(payload.sessionId),
596
+ action,
597
+ error: error instanceof Error
598
+ ? (error.stack ?? error.message)
599
+ : String(error),
600
+ });
601
+ await ctx.answerCallbackQuery({
602
+ text: this.host.t(locale, "menu:notices.terminal.prompt_action_failed"),
603
+ show_alert: true,
604
+ });
605
+ }
606
+ }
607
+ async captureBuffer(session, scope) {
608
+ const target = session.terminalTarget;
609
+ if (!target) {
610
+ throw new Error("terminal target is not configured");
611
+ }
612
+ const paneStart = await this.resolveCaptureStart(target, scope);
613
+ const stdout = await (0, client_1.captureTerminalPaneRange)(this.host.config.terminal, target, paneStart, false);
614
+ const capturedAt = new Date().toISOString();
615
+ const scopeDescription = this.describeCaptureScope(scope);
616
+ const titleBase = session.label ?? session.sessionId;
617
+ const filenameBase = (0, transportUtils_1.slugifyFilenamePart)(titleBase) || "session-buffer";
618
+ const timestamp = capturedAt.replace(/[:.]/g, "-");
619
+ const filename = `${filenameBase}-${timestamp}.md`;
620
+ const content = [
621
+ "# Terminal Buffer",
622
+ "",
623
+ `- Session: ${session.label ?? session.sessionId}`,
624
+ `- Session ID: ${session.sessionId}`,
625
+ `- terminal target: ${target}`,
626
+ `- Capture scope: ${scopeDescription}`,
627
+ `- Captured at: ${capturedAt}`,
628
+ "",
629
+ "```text",
630
+ stdout.replaceAll("\u0000", ""),
631
+ "```",
632
+ "",
633
+ ].join("\n");
634
+ return {
635
+ filename,
636
+ buffer: Buffer.from(content, "utf8"),
637
+ captureMode: scope.mode,
638
+ scopeDescription,
639
+ };
640
+ }
641
+ async resolveCaptureStart(target, scope) {
642
+ if (scope.mode === "full") {
643
+ return "-";
644
+ }
645
+ if (scope.mode === "lines") {
646
+ return `-${scope.lines}`;
647
+ }
648
+ const height = await (0, client_1.getTerminalWindowHeight)(this.host.config.terminal, target);
649
+ if (typeof height !== "number" || height <= 0) {
650
+ return `-${this.host.config.terminal.captureLines}`;
651
+ }
652
+ return `-${height}`;
653
+ }
654
+ describeCaptureScope(scope) {
655
+ switch (scope.mode) {
656
+ case "visible":
657
+ return "visible pane";
658
+ case "lines":
659
+ return `last ${scope.lines} lines`;
660
+ case "full":
661
+ return "full history";
662
+ }
663
+ }
664
+ }
665
+ exports.TransportTerminalActions = TransportTerminalActions;