@deadragdoll/tellymcp 0.0.9 → 0.0.11

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 (157) hide show
  1. package/.env.example.client +42 -50
  2. package/.env.example.gateway +49 -61
  3. package/CHANGELOG.md +3 -3
  4. package/README-ru.md +205 -384
  5. package/README.md +195 -1194
  6. package/TOOLS.md +294 -377
  7. package/VERSION.md +36 -10
  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 +40 -20
  15. package/config/templates/env.client.template +34 -18
  16. package/config/templates/env.gateway.template +38 -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/approval.service.js +1 -1
  23. package/dist/services/features/telegram-mcp/browser.service.js +95 -2
  24. package/dist/services/features/telegram-mcp/collaboration.service.js +41 -4
  25. package/dist/services/features/telegram-mcp/ensuredb.service.js +145 -26
  26. package/dist/services/features/telegram-mcp/gateway-delivery.service.js +285 -103
  27. package/dist/services/features/telegram-mcp/gateway-rmq.service.js +3 -2
  28. package/dist/services/features/telegram-mcp/gateway-socket.service.js +905 -87
  29. package/dist/services/features/telegram-mcp/gateway.service.js +876 -81
  30. package/dist/services/features/telegram-mcp/mcp-http.service.js +9 -1
  31. package/dist/services/features/telegram-mcp/mcp-server.service.js +17 -26
  32. package/dist/services/features/telegram-mcp/notify.service.js +128 -2
  33. package/dist/services/features/telegram-mcp/runtime.service.js +27 -7
  34. package/dist/services/features/telegram-mcp/session-context.service.js +29 -2
  35. package/dist/services/features/telegram-mcp/src/app/bootstrap/runtime.js +133 -10
  36. package/dist/services/features/telegram-mcp/src/app/config/env.js +161 -51
  37. package/dist/services/features/telegram-mcp/src/app/http.js +375 -42
  38. package/dist/services/features/telegram-mcp/src/app/webapp/assets.js +386 -58
  39. package/dist/services/features/telegram-mcp/src/app/webapp/auth.js +3 -0
  40. package/dist/services/features/telegram-mcp/src/app/webapp/terminal.js +12 -0
  41. package/dist/services/features/telegram-mcp/src/entities/request/model/schema.js +116 -104
  42. package/dist/services/features/telegram-mcp/src/entities/xchange/model/types.js +2 -0
  43. package/dist/services/features/telegram-mcp/src/features/ask-user/model/askUserTelegram.js +1 -1
  44. package/dist/services/features/telegram-mcp/src/features/browser/model/browserScreenshotTool.js +1 -1
  45. package/dist/services/features/telegram-mcp/src/features/browser/model/browserService.js +249 -19
  46. package/dist/services/features/telegram-mcp/src/features/collaboration/model/gatewaySessionsService.js +71 -0
  47. package/dist/services/features/telegram-mcp/src/features/collaboration/model/listGatewaySessionsTool.js +33 -0
  48. package/dist/services/features/telegram-mcp/src/features/collaboration/model/localCollaborationBackend.js +93 -82
  49. package/dist/services/features/telegram-mcp/src/features/collaboration/model/sendPartnerFileService.js +47 -5
  50. package/dist/services/features/telegram-mcp/src/features/collaboration/model/sendPartnerFileTool.js +1 -1
  51. package/dist/services/features/telegram-mcp/src/features/collaboration/model/sendPartnerNoteTool.js +1 -1
  52. package/dist/services/features/telegram-mcp/src/features/distributed-client/model/gatewayClientAccess.js +82 -0
  53. package/dist/services/features/telegram-mcp/src/features/distributed-client/model/gatewayCollaborationBackend.js +22 -14
  54. package/dist/services/features/telegram-mcp/src/features/distributed-gateway/model/gatewayHttpService.js +649 -75
  55. package/dist/services/features/telegram-mcp/src/features/distributed-gateway/model/remoteConsoleActionClient.js +76 -0
  56. package/dist/services/features/telegram-mcp/src/features/embedded-runtime/model/embeddedRuntimeBroker.js +92 -0
  57. package/dist/services/features/telegram-mcp/src/features/foreground-terminal/model/foregroundTerminalRuntime.js +192 -0
  58. package/dist/services/features/telegram-mcp/src/features/notify/model/notifyService.js +382 -3
  59. package/dist/services/features/telegram-mcp/src/features/notify/model/notifyTelegramTool.js +1 -1
  60. package/dist/services/features/telegram-mcp/src/features/notify/model/sendFileToTelegramTool.js +33 -0
  61. package/dist/services/features/telegram-mcp/src/features/session-context/model/clearSessionContextTool.js +1 -1
  62. package/dist/services/features/telegram-mcp/src/features/session-context/model/getSessionContextTool.js +1 -1
  63. package/dist/services/features/telegram-mcp/src/features/session-context/model/renameSessionTool.js +1 -1
  64. package/dist/services/features/telegram-mcp/src/features/session-context/model/sessionContextService.js +42 -200
  65. package/dist/services/features/telegram-mcp/src/features/session-context/model/setSessionContextTool.js +1 -1
  66. package/dist/services/features/telegram-mcp/src/features/terminal-buffer/model/terminalBufferService.js +96 -0
  67. package/dist/services/features/telegram-mcp/src/features/terminal-input/model/terminalInputService.js +97 -0
  68. package/dist/services/features/telegram-mcp/src/features/tools-sync/model/refreshToolsMarkdownService.js +107 -58
  69. package/dist/services/features/telegram-mcp/src/features/tools-sync/model/refreshToolsMarkdownTool.js +1 -1
  70. package/dist/services/features/telegram-mcp/src/features/xchange/model/getXchangeRecordTool.js +28 -0
  71. package/dist/services/features/telegram-mcp/src/features/xchange/model/listXchangeRecordsTool.js +28 -0
  72. package/dist/services/features/telegram-mcp/src/features/xchange/model/markXchangeRecordReadTool.js +28 -0
  73. package/dist/services/features/telegram-mcp/src/features/xchange/model/xchangeService.js +169 -0
  74. package/dist/services/features/telegram-mcp/src/processes/human-approval/model/orchestrator.js +5 -4
  75. package/dist/services/features/telegram-mcp/src/shared/i18n/index.js +46 -0
  76. package/dist/services/features/telegram-mcp/src/shared/i18n/resources/en.js +652 -0
  77. package/dist/services/features/telegram-mcp/src/shared/i18n/resources/ru.js +652 -0
  78. package/dist/services/features/telegram-mcp/src/shared/integrations/memory/processLocalSessionStore.js +27 -0
  79. package/dist/services/features/telegram-mcp/src/shared/integrations/object-storage/minioExchangeStore.js +11 -8
  80. package/dist/services/features/telegram-mcp/src/shared/integrations/redis/stateStore.js +51 -71
  81. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/proxyFetch.js +21 -22
  82. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transport.js +411 -6524
  83. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportAttachmentStore.js +93 -0
  84. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportBroadcastActions.js +385 -0
  85. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportConsoleRegistry.js +143 -0
  86. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportConstructorWiring.js +633 -0
  87. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportContent.js +84 -0
  88. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportContext.js +78 -0
  89. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportDocumentActions.js +36 -0
  90. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportEventActions.js +292 -0
  91. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportFileHandoffActions.js +352 -0
  92. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportFormatting.js +75 -0
  93. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportGatewayActions.js +44 -0
  94. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportLifecycleActions.js +161 -0
  95. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportLive.js +56 -0
  96. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportLiveActions.js +77 -0
  97. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportMenuCallbacks.js +254 -0
  98. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportMenuFactories.js +538 -0
  99. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportMenuFingerprints.js +93 -0
  100. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportMenuFlow.js +344 -0
  101. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportMenuShell.js +62 -0
  102. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportMenuState.js +408 -0
  103. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportMenuText.js +216 -0
  104. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportMessageFlow.js +452 -0
  105. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportOutputActions.js +189 -0
  106. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportPartnerActions.js +286 -0
  107. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportPayloadState.js +101 -0
  108. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportProjectActions.js +463 -0
  109. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportProjectEntryActions.js +202 -0
  110. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportProjectEvents.js +99 -0
  111. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportProjectMenus.js +138 -0
  112. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportProjectState.js +308 -0
  113. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportProjectView.js +426 -0
  114. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportRequestFlow.js +278 -0
  115. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportSessionActions.js +143 -0
  116. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportTerminalActions.js +468 -0
  117. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportTerminalRuntime.js +171 -0
  118. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportTypes.js +2 -0
  119. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportUtils.js +330 -0
  120. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportXchangeState.js +107 -0
  121. package/dist/services/features/telegram-mcp/src/shared/integrations/terminal/client.js +255 -0
  122. package/dist/services/features/telegram-mcp/src/shared/integrations/terminal/ptyRegistry.js +543 -0
  123. package/dist/services/features/telegram-mcp/src/shared/integrations/xchange/sqliteRecordStore.js +223 -0
  124. package/dist/services/features/telegram-mcp/src/shared/lib/gatewayScope.js +24 -0
  125. package/dist/services/features/telegram-mcp/src/shared/lib/logger/logger.js +6 -0
  126. package/dist/services/features/telegram-mcp/src/shared/lib/project-identity/projectIdentity.js +147 -76
  127. package/dist/services/features/telegram-mcp/src/shared/lib/telegramXchangeRecords.js +72 -0
  128. package/dist/services/features/telegram-mcp/src/shared/lib/terminalPromptDetection.js +237 -0
  129. package/dist/services/features/telegram-mcp/src/shared/lib/version/versionHandshake.js +129 -1
  130. package/dist/services/features/telegram-mcp/src/shared/lib/xchangeRecordHints.js +98 -0
  131. package/dist/services/features/telegram-mcp/standalone-http.service.js +13 -1
  132. package/dist/services/features/telegram-mcp/terminal-buffer.service.js +42 -0
  133. package/dist/services/features/telegram-mcp/terminal-input.service.js +41 -0
  134. package/dist/services/features/telegram-mcp/tools-sync.service.js +16 -2
  135. package/dist/services/features/telegram-mcp/xchange.service.js +123 -0
  136. package/docs/STANDALONE-ru.md +172 -0
  137. package/docs/STANDALONE.md +172 -0
  138. package/package.json +11 -5
  139. package/scripts/build-package-artifact.sh +27 -0
  140. package/scripts/deploy-gateway.sh +64 -0
  141. package/scripts/deploy-onebot-branch.sh +27 -0
  142. package/scripts/postinstall.js +11 -36
  143. package/STANDALONE-ru.md +0 -274
  144. package/STANDALONE.md +0 -274
  145. package/dist/services/features/telegram-mcp/inbox.service.js +0 -33
  146. package/dist/services/features/telegram-mcp/pair.service.js +0 -33
  147. package/dist/services/features/telegram-mcp/src/app/webapp/tmux.js +0 -10
  148. package/dist/services/features/telegram-mcp/src/features/inbox/model/deleteTelegramInboxMessageTool.js +0 -33
  149. package/dist/services/features/telegram-mcp/src/features/inbox/model/getTelegramInboxCountTool.js +0 -33
  150. package/dist/services/features/telegram-mcp/src/features/inbox/model/getTelegramInboxTool.js +0 -33
  151. package/dist/services/features/telegram-mcp/src/features/inbox/model/inboxService.js +0 -77
  152. package/dist/services/features/telegram-mcp/src/features/pair-session/model/clearSessionPairingTool.js +0 -33
  153. package/dist/services/features/telegram-mcp/src/features/pair-session/model/createSessionPairCodeTool.js +0 -33
  154. package/dist/services/features/telegram-mcp/src/features/pair-session/model/generatePairCode.js +0 -202
  155. package/dist/services/features/telegram-mcp/src/features/session-context/model/getTmuxTargetTool.js +0 -33
  156. package/dist/services/features/telegram-mcp/src/features/session-context/model/setTmuxTargetTool.js +0 -33
  157. package/dist/services/features/telegram-mcp/src/shared/integrations/tmux/client.js +0 -363
@@ -0,0 +1,468 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TransportTerminalActions = void 0;
4
+ const client_1 = require("../terminal/client");
5
+ const terminalPromptDetection_1 = require("../../lib/terminalPromptDetection");
6
+ const transportUtils_1 = require("./transportUtils");
7
+ const TERMINAL_NUDGE_FAILURE_NOTICE_COOLDOWN_MS = 5 * 60 * 1000;
8
+ const TERMINAL_PROMPT_SCAN_MATCHED_LINES_LIMIT = 6;
9
+ class TransportTerminalActions {
10
+ host;
11
+ constructor(host) {
12
+ this.host = host;
13
+ }
14
+ async ensurePtyTargetForSession(sessionId, session) {
15
+ const ensuredTarget = (0, client_1.ensureTerminalTargetForSession)(this.host.config.terminal, {
16
+ sessionId,
17
+ ...(typeof session.cwd === "string" ? { cwd: session.cwd } : {}),
18
+ ...(typeof session.terminalTarget === "string"
19
+ ? { target: session.terminalTarget }
20
+ : {}),
21
+ });
22
+ if (!ensuredTarget) {
23
+ return null;
24
+ }
25
+ if (ensuredTarget === session.terminalTarget) {
26
+ return ensuredTarget;
27
+ }
28
+ await this.host.sessionStore.setSession({
29
+ ...session,
30
+ terminalTarget: ensuredTarget,
31
+ updatedAt: new Date().toISOString(),
32
+ });
33
+ this.host.logger.info("PTY terminal target normalized", {
34
+ sessionId,
35
+ previousTerminalTarget: session.terminalTarget,
36
+ normalizedTerminalTarget: ensuredTarget,
37
+ });
38
+ return ensuredTarget;
39
+ }
40
+ async nudgeForInboxMessage(sessionId) {
41
+ await this.nudgeForSession(sessionId, {
42
+ message: this.host.config.terminal.nudgeMessage,
43
+ reason: "human_message",
44
+ });
45
+ }
46
+ async nudgeForSession(sessionId, input) {
47
+ if (!this.host.config.terminal.nudgeEnabled) {
48
+ return;
49
+ }
50
+ const session = await this.host.sessionStore.getSession(sessionId);
51
+ if (!session?.terminalTarget) {
52
+ this.host.logger.debug("terminal nudge skipped", {
53
+ sessionId,
54
+ nudgeReason: input.reason,
55
+ skipReason: "no_terminal_target",
56
+ });
57
+ return;
58
+ }
59
+ let normalizedSession = session;
60
+ if (!session.terminalTarget.startsWith("pty:")) {
61
+ const normalizedTarget = await this.ensurePtyTargetForSession(sessionId, session);
62
+ if (normalizedTarget) {
63
+ normalizedSession = {
64
+ ...session,
65
+ terminalTarget: normalizedTarget,
66
+ };
67
+ }
68
+ }
69
+ const nowMs = Date.now();
70
+ if (!(0, transportUtils_1.shouldNudge)(session.lastTerminalNudgeAt, this.host.config.terminal.nudgeCooldownSeconds, nowMs)) {
71
+ this.host.logger.debug("terminal nudge skipped because of cooldown", {
72
+ sessionId,
73
+ reason: input.reason,
74
+ terminalTarget: normalizedSession.terminalTarget,
75
+ lastTerminalNudgeAt: normalizedSession.lastTerminalNudgeAt,
76
+ });
77
+ return;
78
+ }
79
+ await this.host.sendTypingForSession(sessionId);
80
+ let terminalTarget = normalizedSession.terminalTarget ?? null;
81
+ if (!terminalTarget) {
82
+ this.host.logger.debug("terminal nudge skipped", {
83
+ sessionId,
84
+ nudgeReason: input.reason,
85
+ skipReason: "normalized_target_missing",
86
+ });
87
+ return;
88
+ }
89
+ try {
90
+ await (0, client_1.sendTerminalLiteralLine)(this.host.config.terminal, terminalTarget, input.message);
91
+ }
92
+ catch (error) {
93
+ if ((0, client_1.isTerminalTargetInvalidError)(error) || (0, client_1.isTerminalUnavailableError)(error)) {
94
+ const recoveredTarget = await this.ensurePtyTargetForSession(sessionId, normalizedSession);
95
+ if (recoveredTarget) {
96
+ terminalTarget = recoveredTarget;
97
+ await (0, client_1.sendTerminalLiteralLine)(this.host.config.terminal, recoveredTarget, input.message);
98
+ }
99
+ else {
100
+ await this.notifyTargetInvalid(sessionId, normalizedSession, error);
101
+ throw error;
102
+ }
103
+ }
104
+ else {
105
+ throw error;
106
+ }
107
+ }
108
+ const lastTerminalNudgeAt = new Date(nowMs).toISOString();
109
+ await this.host.sessionStore.setSession({
110
+ ...normalizedSession,
111
+ terminalTarget: terminalTarget,
112
+ lastTerminalNudgeAt,
113
+ });
114
+ this.host.terminalNudgeFailureNoticeAt.delete(sessionId);
115
+ this.host.logger.info("terminal nudge sent", {
116
+ sessionId,
117
+ reason: input.reason,
118
+ message: input.message,
119
+ terminalTarget,
120
+ lastTerminalNudgeAt,
121
+ });
122
+ }
123
+ async tryRecoverTarget(sessionId, session) {
124
+ const recoveredTarget = await (0, client_1.resolveTerminalTargetFromHint)(this.host.config.terminal, {
125
+ terminalTarget: session.terminalTarget,
126
+ });
127
+ if (!recoveredTarget || recoveredTarget === session.terminalTarget) {
128
+ return recoveredTarget;
129
+ }
130
+ await this.host.sessionStore.setSession({
131
+ ...session,
132
+ terminalTarget: recoveredTarget,
133
+ updatedAt: new Date().toISOString(),
134
+ });
135
+ this.host.logger.warn("terminal target auto-recovered", {
136
+ sessionId,
137
+ previousTerminalTarget: session.terminalTarget,
138
+ recoveredTerminalTarget: recoveredTarget,
139
+ });
140
+ return recoveredTarget;
141
+ }
142
+ async notifyTargetInvalid(sessionId, session, error) {
143
+ const binding = await this.host.bindingStore.getBinding(sessionId);
144
+ if (!binding) {
145
+ return;
146
+ }
147
+ const nowMs = Date.now();
148
+ const lastNoticeAt = this.host.terminalNudgeFailureNoticeAt.get(sessionId);
149
+ if (lastNoticeAt &&
150
+ nowMs - lastNoticeAt < TERMINAL_NUDGE_FAILURE_NOTICE_COOLDOWN_MS) {
151
+ return;
152
+ }
153
+ this.host.terminalNudgeFailureNoticeAt.set(sessionId, nowMs);
154
+ const sessionLabel = session.label ?? sessionId;
155
+ const terminalTarget = session.terminalTarget ?? "unknown";
156
+ const errorMessage = error instanceof Error ? error.message : String(error);
157
+ const locale = await this.host.resolveLocaleForTelegramUserId(binding.telegramUserId);
158
+ try {
159
+ await this.host.sendNotification({
160
+ sessionId,
161
+ sessionLabel: "TellyMCP",
162
+ recipient: {
163
+ telegramChatId: binding.telegramChatId,
164
+ telegramUserId: binding.telegramUserId,
165
+ },
166
+ message: [
167
+ this.host.t(locale, "menu:notices.terminal.target_invalid_title", {
168
+ sessionName: sessionLabel,
169
+ }),
170
+ this.host.t(locale, "menu:notices.terminal.target_invalid_target", {
171
+ terminalTarget,
172
+ }),
173
+ this.host.t(locale, "menu:system.error_prefix", {
174
+ message: errorMessage,
175
+ }),
176
+ this.host.t(locale, "menu:system.terminal_recreated_hint"),
177
+ this.host.t(locale, "menu:notices.terminal.target_invalid_action"),
178
+ ].join("\n"),
179
+ });
180
+ }
181
+ catch (notifyError) {
182
+ this.host.logger.warn("Failed to deliver terminal target failure notification", {
183
+ sessionId,
184
+ terminalTarget,
185
+ telegramChatId: binding.telegramChatId,
186
+ telegramUserId: binding.telegramUserId,
187
+ notifyError: notifyError instanceof Error
188
+ ? (notifyError.stack ?? notifyError.message)
189
+ : String(notifyError),
190
+ });
191
+ }
192
+ }
193
+ async notifyUnavailable(sessionId, session, error) {
194
+ const binding = await this.host.bindingStore.getBinding(sessionId);
195
+ if (!binding) {
196
+ return;
197
+ }
198
+ const nowMs = Date.now();
199
+ const lastNoticeAt = this.host.terminalNudgeFailureNoticeAt.get(sessionId);
200
+ if (lastNoticeAt &&
201
+ nowMs - lastNoticeAt < TERMINAL_NUDGE_FAILURE_NOTICE_COOLDOWN_MS) {
202
+ return;
203
+ }
204
+ this.host.terminalNudgeFailureNoticeAt.set(sessionId, nowMs);
205
+ const sessionLabel = session.label ?? sessionId;
206
+ const terminalTarget = session.terminalTarget ?? "unknown";
207
+ const errorMessage = error instanceof Error ? error.message : String(error);
208
+ const locale = await this.host.resolveLocaleForTelegramUserId(binding.telegramUserId);
209
+ try {
210
+ await this.host.sendNotification({
211
+ sessionId,
212
+ sessionLabel: "TellyMCP",
213
+ recipient: {
214
+ telegramChatId: binding.telegramChatId,
215
+ telegramUserId: binding.telegramUserId,
216
+ },
217
+ message: [
218
+ this.host.t(locale, "menu:notices.terminal.unavailable_title", {
219
+ sessionName: sessionLabel,
220
+ }),
221
+ this.host.t(locale, "menu:notices.terminal.unavailable_body"),
222
+ this.host.t(locale, "menu:notices.terminal.unavailable_target", {
223
+ terminalTarget,
224
+ }),
225
+ this.host.t(locale, "menu:system.error_prefix", {
226
+ message: errorMessage,
227
+ }),
228
+ this.host.t(locale, "menu:notices.terminal.unavailable_reason"),
229
+ this.host.t(locale, "menu:notices.terminal.unavailable_action"),
230
+ ].join("\n"),
231
+ });
232
+ }
233
+ catch (notifyError) {
234
+ this.host.logger.warn("Failed to deliver terminal unavailable notification", {
235
+ sessionId,
236
+ terminalTarget,
237
+ telegramChatId: binding.telegramChatId,
238
+ telegramUserId: binding.telegramUserId,
239
+ notifyError: notifyError instanceof Error
240
+ ? (notifyError.stack ?? notifyError.message)
241
+ : String(notifyError),
242
+ });
243
+ }
244
+ }
245
+ async scanPromptForSession(session) {
246
+ if (!session.terminalTarget) {
247
+ this.host.terminalPromptNoticeState.delete(session.sessionId);
248
+ return;
249
+ }
250
+ const binding = await this.host.bindingStore.getBinding(session.sessionId);
251
+ if (!binding) {
252
+ this.host.terminalPromptNoticeState.delete(session.sessionId);
253
+ return;
254
+ }
255
+ let terminalTarget = session.terminalTarget;
256
+ let capture;
257
+ try {
258
+ capture = await this.capturePromptBuffer(session);
259
+ }
260
+ catch (error) {
261
+ if ((0, client_1.isTerminalUnavailableError)(error)) {
262
+ this.host.logger.debug("terminal prompt scan skipped because terminal is unavailable", {
263
+ sessionId: session.sessionId,
264
+ terminalTarget,
265
+ });
266
+ return;
267
+ }
268
+ if ((0, client_1.isTerminalTargetInvalidError)(error)) {
269
+ const recoveredTarget = await this.tryRecoverTarget(session.sessionId, session);
270
+ if (!recoveredTarget) {
271
+ this.host.logger.debug("terminal prompt scan skipped because target is invalid", {
272
+ sessionId: session.sessionId,
273
+ terminalTarget,
274
+ });
275
+ return;
276
+ }
277
+ terminalTarget = recoveredTarget;
278
+ capture = await this.capturePromptBuffer({
279
+ ...session,
280
+ terminalTarget: recoveredTarget,
281
+ });
282
+ }
283
+ else {
284
+ this.host.logger.warn("terminal prompt scan capture failed", {
285
+ sessionId: session.sessionId,
286
+ terminalTarget,
287
+ error: error instanceof Error
288
+ ? (error.stack ?? error.message)
289
+ : String(error),
290
+ });
291
+ return;
292
+ }
293
+ }
294
+ const detection = (0, terminalPromptDetection_1.detectTerminalInteractivePrompt)(capture, {
295
+ strategy: this.host.config.terminal.promptScanStrategy,
296
+ minScore: this.host.config.terminal.promptScanMinScore,
297
+ });
298
+ if (!detection) {
299
+ this.host.logger.debug("terminal prompt scan found no interactive prompt", {
300
+ sessionId: session.sessionId,
301
+ terminalTarget,
302
+ strategy: this.host.config.terminal.promptScanStrategy,
303
+ minScore: this.host.config.terminal.promptScanMinScore,
304
+ });
305
+ this.host.terminalPromptNoticeState.delete(session.sessionId);
306
+ return;
307
+ }
308
+ if (!this.shouldSendPromptNotice(session.sessionId, detection)) {
309
+ return;
310
+ }
311
+ await this.notifyPromptDetected(session, binding, detection, terminalTarget);
312
+ }
313
+ async capturePromptBuffer(session) {
314
+ const target = session.terminalTarget;
315
+ if (!target) {
316
+ throw new Error("terminal target is not configured");
317
+ }
318
+ if (this.host.config.terminal.captureMode === "visible") {
319
+ return (0, client_1.captureVisibleTerminal)(this.host.config.terminal, target, this.host.config.terminal.captureLines, this.host.config.webapp.visibleScreens);
320
+ }
321
+ return (0, client_1.captureTerminalPaneRange)(this.host.config.terminal, target, `-${this.host.config.terminal.captureLines}`, false);
322
+ }
323
+ shouldSendPromptNotice(sessionId, detection) {
324
+ const existing = this.host.terminalPromptNoticeState.get(sessionId);
325
+ const nowMs = Date.now();
326
+ const cooldownMs = this.host.config.terminal.promptScanCooldownSeconds * 1000;
327
+ if (existing &&
328
+ existing.fingerprint === detection.fingerprint &&
329
+ nowMs - existing.sentAtMs < cooldownMs) {
330
+ this.host.logger.debug("terminal prompt detected but notification is on cooldown", {
331
+ sessionId,
332
+ fingerprint: detection.fingerprint,
333
+ score: detection.score,
334
+ reasons: detection.reasons,
335
+ cooldownSeconds: this.host.config.terminal.promptScanCooldownSeconds,
336
+ });
337
+ return false;
338
+ }
339
+ this.host.terminalPromptNoticeState.set(sessionId, {
340
+ fingerprint: detection.fingerprint,
341
+ sentAtMs: nowMs,
342
+ });
343
+ return true;
344
+ }
345
+ async notifyPromptDetected(session, binding, detection, terminalTarget) {
346
+ if (!binding) {
347
+ return;
348
+ }
349
+ const locale = await this.host.resolveLocaleForTelegramUserId(binding.telegramUserId);
350
+ const sessionLabel = session.label ?? session.sessionId;
351
+ const excerpt = detection.matchedLines
352
+ .slice(-TERMINAL_PROMPT_SCAN_MATCHED_LINES_LIMIT)
353
+ .join("\n");
354
+ await this.host.sendNotification({
355
+ sessionId: session.sessionId,
356
+ sessionLabel: "TellyMCP",
357
+ recipient: {
358
+ telegramChatId: binding.telegramChatId,
359
+ telegramUserId: binding.telegramUserId,
360
+ },
361
+ message: [
362
+ this.host.t(locale, "menu:notices.terminal.prompt_detected_title", {
363
+ sessionName: sessionLabel,
364
+ }),
365
+ this.host.t(locale, "menu:notices.terminal.prompt_detected_score", {
366
+ score: detection.score,
367
+ }),
368
+ this.host.t(locale, "menu:notices.terminal.prompt_detected_target", {
369
+ terminalTarget: terminalTarget,
370
+ }),
371
+ this.host.t(locale, "menu:notices.terminal.prompt_detected_hint"),
372
+ this.host.t(locale, "menu:notices.terminal.prompt_detected_excerpt"),
373
+ excerpt,
374
+ ].join("\n"),
375
+ });
376
+ try {
377
+ await this.host.sendLiveViewLauncherMessage({
378
+ principal: {
379
+ telegramChatId: binding.telegramChatId,
380
+ telegramUserId: binding.telegramUserId,
381
+ },
382
+ sessionId: session.sessionId,
383
+ sessionName: sessionLabel,
384
+ locale,
385
+ });
386
+ }
387
+ catch (error) {
388
+ this.host.logger.warn("Failed to deliver terminal prompt live launcher", {
389
+ sessionId: session.sessionId,
390
+ terminalTarget,
391
+ telegramChatId: binding.telegramChatId,
392
+ telegramUserId: binding.telegramUserId,
393
+ error: error instanceof Error
394
+ ? (error.stack ?? error.message)
395
+ : String(error),
396
+ });
397
+ }
398
+ this.host.logger.info("terminal prompt detected", {
399
+ sessionId: session.sessionId,
400
+ terminalTarget,
401
+ score: detection.score,
402
+ strategy: this.host.config.terminal.promptScanStrategy,
403
+ minScore: this.host.config.terminal.promptScanMinScore,
404
+ reasons: detection.reasons,
405
+ fingerprint: detection.fingerprint,
406
+ matchedLines: detection.matchedLines,
407
+ excerpt,
408
+ });
409
+ }
410
+ async captureBuffer(session, scope) {
411
+ const target = session.terminalTarget;
412
+ if (!target) {
413
+ throw new Error("terminal target is not configured");
414
+ }
415
+ const paneStart = await this.resolveCaptureStart(target, scope);
416
+ const stdout = await (0, client_1.captureTerminalPaneRange)(this.host.config.terminal, target, paneStart, false);
417
+ const capturedAt = new Date().toISOString();
418
+ const scopeDescription = this.describeCaptureScope(scope);
419
+ const titleBase = session.label ?? session.sessionId;
420
+ const filenameBase = (0, transportUtils_1.slugifyFilenamePart)(titleBase) || "session-buffer";
421
+ const timestamp = capturedAt.replace(/[:.]/g, "-");
422
+ const filename = `${filenameBase}-${timestamp}.md`;
423
+ const content = [
424
+ "# Terminal Buffer",
425
+ "",
426
+ `- Session: ${session.label ?? session.sessionId}`,
427
+ `- Session ID: ${session.sessionId}`,
428
+ `- terminal target: ${target}`,
429
+ `- Capture scope: ${scopeDescription}`,
430
+ `- Captured at: ${capturedAt}`,
431
+ "",
432
+ "```text",
433
+ stdout.replaceAll("\u0000", ""),
434
+ "```",
435
+ "",
436
+ ].join("\n");
437
+ return {
438
+ filename,
439
+ buffer: Buffer.from(content, "utf8"),
440
+ captureMode: scope.mode,
441
+ scopeDescription,
442
+ };
443
+ }
444
+ async resolveCaptureStart(target, scope) {
445
+ if (scope.mode === "full") {
446
+ return "-";
447
+ }
448
+ if (scope.mode === "lines") {
449
+ return `-${scope.lines}`;
450
+ }
451
+ const height = await (0, client_1.getTerminalWindowHeight)(this.host.config.terminal, target);
452
+ if (typeof height !== "number" || height <= 0) {
453
+ return `-${this.host.config.terminal.captureLines}`;
454
+ }
455
+ return `-${height}`;
456
+ }
457
+ describeCaptureScope(scope) {
458
+ switch (scope.mode) {
459
+ case "visible":
460
+ return "visible pane";
461
+ case "lines":
462
+ return `last ${scope.lines} lines`;
463
+ case "full":
464
+ return "full history";
465
+ }
466
+ }
467
+ }
468
+ exports.TransportTerminalActions = TransportTerminalActions;
@@ -0,0 +1,171 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TransportTerminalRuntime = void 0;
4
+ const grammy_1 = require("grammy");
5
+ const client_1 = require("../terminal/client");
6
+ class TransportTerminalRuntime {
7
+ host;
8
+ terminalPromptScanTimer;
9
+ terminalPromptScanInFlight = false;
10
+ constructor(host) {
11
+ this.host = host;
12
+ }
13
+ get actions() {
14
+ const actions = this.host.terminalActions;
15
+ if (!actions) {
16
+ throw new Error("TransportTerminalRuntime requires terminal actions");
17
+ }
18
+ return actions;
19
+ }
20
+ clearTerminalNudgeDebounceTimers() {
21
+ for (const timer of this.host.terminalNudgeDebounceTimers.values()) {
22
+ clearTimeout(timer);
23
+ }
24
+ this.host.terminalNudgeDebounceTimers.clear();
25
+ }
26
+ startPromptScan() {
27
+ if (!this.host.config.terminal.promptScanEnabled) {
28
+ return;
29
+ }
30
+ this.clearTerminalPromptScanTimer();
31
+ const intervalMs = this.host.config.terminal.promptScanIntervalSeconds * 1000;
32
+ const timer = setInterval(() => {
33
+ void this.runTerminalPromptScanCycle().catch((error) => {
34
+ this.host.logger.warn("terminal prompt scan cycle failed", {
35
+ error: error instanceof Error
36
+ ? (error.stack ?? error.message)
37
+ : String(error),
38
+ });
39
+ });
40
+ }, intervalMs);
41
+ timer.unref();
42
+ this.terminalPromptScanTimer = timer;
43
+ this.host.logger.info("terminal prompt scan scheduled", {
44
+ intervalSeconds: this.host.config.terminal.promptScanIntervalSeconds,
45
+ cooldownSeconds: this.host.config.terminal.promptScanCooldownSeconds,
46
+ strategy: this.host.config.terminal.promptScanStrategy,
47
+ minScore: this.host.config.terminal.promptScanMinScore,
48
+ });
49
+ void this.runTerminalPromptScanCycle().catch((error) => {
50
+ this.host.logger.warn("initial terminal prompt scan failed", {
51
+ error: error instanceof Error
52
+ ? (error.stack ?? error.message)
53
+ : String(error),
54
+ });
55
+ });
56
+ }
57
+ clearTerminalPromptScanTimer() {
58
+ if (this.terminalPromptScanTimer) {
59
+ clearInterval(this.terminalPromptScanTimer);
60
+ this.terminalPromptScanTimer = undefined;
61
+ }
62
+ }
63
+ scheduleTerminalNudgeForInboxMessage(sessionId, session) {
64
+ if (!this.host.config.terminal.nudgeEnabled) {
65
+ return;
66
+ }
67
+ if (!session?.terminalTarget) {
68
+ this.host.logger.debug("terminal nudge scheduling skipped for inbox message", {
69
+ sessionId,
70
+ reason: "no_terminal_target",
71
+ });
72
+ return;
73
+ }
74
+ const existingTimer = this.host.terminalNudgeDebounceTimers.get(sessionId);
75
+ if (existingTimer) {
76
+ clearTimeout(existingTimer);
77
+ }
78
+ const timer = setTimeout(() => {
79
+ this.host.terminalNudgeDebounceTimers.delete(sessionId);
80
+ void this.actions
81
+ .nudgeForInboxMessage(sessionId)
82
+ .catch((error) => {
83
+ const payload = {
84
+ sessionId,
85
+ error: error instanceof Error
86
+ ? (error.stack ?? error.message)
87
+ : String(error),
88
+ };
89
+ if ((0, client_1.isTerminalUnavailableError)(error)) {
90
+ void this.host.sessionStore
91
+ .getSession(sessionId)
92
+ .then((currentSession) => {
93
+ if (!currentSession?.terminalTarget) {
94
+ return;
95
+ }
96
+ return this.actions.notifyUnavailable(sessionId, currentSession, error);
97
+ });
98
+ this.host.logger.warn("terminal nudge skipped because terminal is unavailable", payload);
99
+ return;
100
+ }
101
+ this.host.logger.error("terminal nudge failed", payload);
102
+ });
103
+ }, this.host.config.terminal.nudgeDebounceSeconds * 1000);
104
+ timer.unref();
105
+ this.host.terminalNudgeDebounceTimers.set(sessionId, timer);
106
+ this.host.logger.info("terminal nudge scheduled for inbox message", {
107
+ sessionId,
108
+ terminalTarget: session.terminalTarget,
109
+ debounceSeconds: this.host.config.terminal.nudgeDebounceSeconds,
110
+ });
111
+ }
112
+ async sendTypingForSession(sessionId) {
113
+ if (!this.host.isTelegramEnabled()) {
114
+ this.host.logger.debug("Telegram typing skipped because transport is disabled", {
115
+ sessionId,
116
+ });
117
+ return;
118
+ }
119
+ const binding = await this.host.bindingStore.getBinding(sessionId);
120
+ if (!binding) {
121
+ this.host.logger.debug("Telegram typing skipped because session is unbound", {
122
+ sessionId,
123
+ });
124
+ return;
125
+ }
126
+ let attempt = 0;
127
+ while (true) {
128
+ attempt += 1;
129
+ try {
130
+ await this.host.bot.api.sendChatAction(binding.telegramChatId, "typing");
131
+ this.host.logger.debug("Telegram typing action sent", {
132
+ sessionId,
133
+ telegramChatId: binding.telegramChatId,
134
+ telegramUserId: binding.telegramUserId,
135
+ });
136
+ return;
137
+ }
138
+ catch (error) {
139
+ if (!(error instanceof grammy_1.GrammyError) || error.error_code !== 429) {
140
+ throw error;
141
+ }
142
+ const retryAfterSeconds = Math.max(1, error.parameters.retry_after ?? 1);
143
+ this.host.logger.warn("Telegram rate limit hit while sending typing action, cooling down", {
144
+ sessionId,
145
+ telegramChatId: binding.telegramChatId,
146
+ telegramUserId: binding.telegramUserId,
147
+ attempt,
148
+ retryAfterSeconds,
149
+ description: error.description,
150
+ });
151
+ await new Promise((resolve) => setTimeout(resolve, retryAfterSeconds * 1000));
152
+ }
153
+ }
154
+ }
155
+ async runTerminalPromptScanCycle() {
156
+ if (!this.host.config.terminal.promptScanEnabled || this.terminalPromptScanInFlight) {
157
+ return;
158
+ }
159
+ this.terminalPromptScanInFlight = true;
160
+ try {
161
+ const sessions = await this.host.sessionStore.listSessions();
162
+ for (const session of sessions) {
163
+ await this.actions.scanPromptForSession(session);
164
+ }
165
+ }
166
+ finally {
167
+ this.terminalPromptScanInFlight = false;
168
+ }
169
+ }
170
+ }
171
+ exports.TransportTerminalRuntime = TransportTerminalRuntime;
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });