@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.
- package/.env.example.client +42 -52
- package/.env.example.gateway +48 -62
- package/CHANGELOG.md +3 -3
- package/README-ru.md +226 -376
- package/README.md +213 -1184
- package/TOOLS.md +320 -377
- package/VERSION.md +13 -13
- package/config/codex/plugins/telly-workflows/.codex-plugin/plugin.json +18 -0
- package/config/codex/plugins/telly-workflows/references/invariants.md +10 -0
- package/config/codex/plugins/telly-workflows/skills/telly-browser-screenshot/SKILL.md +27 -0
- package/config/codex/plugins/telly-workflows/skills/telly-collab-artifact/SKILL.md +22 -0
- package/config/codex/plugins/telly-workflows/skills/telly-human-telegram/SKILL.md +31 -0
- package/config/codex/plugins/telly-workflows/skills/telly-partner-note/SKILL.md +36 -0
- package/config/templates/env.both.template +39 -20
- package/config/templates/env.client.template +34 -19
- package/config/templates/env.gateway.template +37 -22
- package/dist/cli.js +322 -75
- package/dist/codexPluginInstaller.js +215 -0
- package/dist/lib/mixins/session.errors.js +34 -1
- package/dist/lib/pinoTargets.js +2 -2
- package/dist/moleculer.config.js +7 -5
- package/dist/services/features/telegram-mcp/browser.service.js +94 -1
- package/dist/services/features/telegram-mcp/collaboration.service.js +40 -3
- package/dist/services/features/telegram-mcp/ensuredb.service.js +145 -26
- package/dist/services/features/telegram-mcp/gateway-delivery.service.js +285 -103
- package/dist/services/features/telegram-mcp/gateway-socket.service.js +907 -85
- package/dist/services/features/telegram-mcp/gateway.service.js +876 -81
- package/dist/services/features/telegram-mcp/mcp-http.service.js +8 -0
- package/dist/services/features/telegram-mcp/mcp-server.service.js +17 -26
- package/dist/services/features/telegram-mcp/notify.service.js +127 -1
- package/dist/services/features/telegram-mcp/session-context.service.js +28 -1
- package/dist/services/features/telegram-mcp/src/app/bootstrap/runtime.js +132 -10
- package/dist/services/features/telegram-mcp/src/app/config/env.js +158 -52
- package/dist/services/features/telegram-mcp/src/app/http.js +375 -42
- package/dist/services/features/telegram-mcp/src/app/webapp/assets.js +386 -58
- package/dist/services/features/telegram-mcp/src/app/webapp/auth.js +3 -0
- package/dist/services/features/telegram-mcp/src/app/webapp/terminal.js +12 -0
- package/dist/services/features/telegram-mcp/src/entities/request/model/schema.js +120 -104
- package/dist/services/features/telegram-mcp/src/entities/xchange/model/types.js +2 -0
- package/dist/services/features/telegram-mcp/src/features/ask-user/model/askUserTelegram.js +1 -1
- package/dist/services/features/telegram-mcp/src/features/browser/model/browserOpenTool.js +1 -1
- package/dist/services/features/telegram-mcp/src/features/browser/model/browserScreenshotTool.js +1 -1
- package/dist/services/features/telegram-mcp/src/features/browser/model/browserService.js +306 -26
- package/dist/services/features/telegram-mcp/src/features/collaboration/model/gatewaySessionsService.js +71 -0
- package/dist/services/features/telegram-mcp/src/features/collaboration/model/listGatewaySessionsTool.js +33 -0
- package/dist/services/features/telegram-mcp/src/features/collaboration/model/localCollaborationBackend.js +93 -82
- package/dist/services/features/telegram-mcp/src/features/collaboration/model/sendPartnerFileService.js +47 -5
- package/dist/services/features/telegram-mcp/src/features/collaboration/model/sendPartnerFileTool.js +1 -1
- package/dist/services/features/telegram-mcp/src/features/collaboration/model/sendPartnerNoteTool.js +1 -1
- package/dist/services/features/telegram-mcp/src/features/distributed-client/model/gatewayClientAccess.js +82 -0
- package/dist/services/features/telegram-mcp/src/features/distributed-client/model/gatewayCollaborationBackend.js +22 -14
- package/dist/services/features/telegram-mcp/src/features/distributed-gateway/model/gatewayHttpService.js +697 -75
- package/dist/services/features/telegram-mcp/src/features/distributed-gateway/model/remoteConsoleActionClient.js +76 -0
- package/dist/services/features/telegram-mcp/src/features/embedded-runtime/model/embeddedRuntimeBroker.js +92 -0
- package/dist/services/features/telegram-mcp/src/features/foreground-terminal/model/foregroundTerminalRuntime.js +192 -0
- package/dist/services/features/telegram-mcp/src/features/notify/model/notifyService.js +382 -3
- package/dist/services/features/telegram-mcp/src/features/notify/model/notifyTelegramTool.js +1 -1
- package/dist/services/features/telegram-mcp/src/features/notify/model/sendFileToTelegramTool.js +33 -0
- package/dist/services/features/telegram-mcp/src/features/session-context/model/clearSessionContextTool.js +1 -1
- package/dist/services/features/telegram-mcp/src/features/session-context/model/getSessionContextTool.js +1 -1
- package/dist/services/features/telegram-mcp/src/features/session-context/model/renameSessionTool.js +1 -1
- package/dist/services/features/telegram-mcp/src/features/session-context/model/sessionContextService.js +42 -200
- package/dist/services/features/telegram-mcp/src/features/session-context/model/setSessionContextTool.js +1 -1
- package/dist/services/features/telegram-mcp/src/features/terminal-buffer/model/terminalBufferService.js +96 -0
- package/dist/services/features/telegram-mcp/src/features/terminal-input/model/terminalInputService.js +97 -0
- package/dist/services/features/telegram-mcp/src/features/tools-sync/model/refreshToolsMarkdownService.js +107 -58
- package/dist/services/features/telegram-mcp/src/features/tools-sync/model/refreshToolsMarkdownTool.js +1 -1
- package/dist/services/features/telegram-mcp/src/features/xchange/model/getXchangeRecordTool.js +28 -0
- package/dist/services/features/telegram-mcp/src/features/xchange/model/listXchangeRecordsTool.js +28 -0
- package/dist/services/features/telegram-mcp/src/features/xchange/model/markXchangeRecordReadTool.js +28 -0
- package/dist/services/features/telegram-mcp/src/features/xchange/model/xchangeService.js +169 -0
- package/dist/services/features/telegram-mcp/src/processes/human-approval/model/orchestrator.js +5 -4
- package/dist/services/features/telegram-mcp/src/shared/i18n/resources/en.js +143 -42
- package/dist/services/features/telegram-mcp/src/shared/i18n/resources/ru.js +139 -38
- package/dist/services/features/telegram-mcp/src/shared/integrations/memory/processLocalSessionStore.js +27 -0
- package/dist/services/features/telegram-mcp/src/shared/integrations/object-storage/minioExchangeStore.js +11 -8
- package/dist/services/features/telegram-mcp/src/shared/integrations/redis/stateStore.js +69 -70
- package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/proxyFetch.js +21 -22
- package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transport.js +444 -7093
- package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportAttachmentStore.js +93 -0
- package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportBroadcastActions.js +385 -0
- package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportConsoleRegistry.js +149 -0
- package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportConstructorWiring.js +642 -0
- package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportContent.js +84 -0
- package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportContext.js +78 -0
- package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportDocumentActions.js +36 -0
- package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportEventActions.js +292 -0
- package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportFileHandoffActions.js +352 -0
- package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportFormatting.js +75 -0
- package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportGatewayActions.js +44 -0
- package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportLifecycleActions.js +161 -0
- package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportLive.js +56 -0
- package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportLiveActions.js +77 -0
- package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportMenuCallbacks.js +254 -0
- package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportMenuFactories.js +538 -0
- package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportMenuFingerprints.js +93 -0
- package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportMenuFlow.js +344 -0
- package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportMenuShell.js +65 -0
- package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportMenuState.js +408 -0
- package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportMenuText.js +216 -0
- package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportMessageFlow.js +452 -0
- package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportOutputActions.js +189 -0
- package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportPartnerActions.js +286 -0
- package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportPayloadState.js +108 -0
- package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportProjectActions.js +463 -0
- package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportProjectEntryActions.js +202 -0
- package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportProjectEvents.js +99 -0
- package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportProjectMenus.js +138 -0
- package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportProjectState.js +308 -0
- package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportProjectView.js +426 -0
- package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportRequestFlow.js +278 -0
- package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportSessionActions.js +143 -0
- package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportTerminalActions.js +665 -0
- package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportTerminalRuntime.js +226 -0
- package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportTypes.js +2 -0
- package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportUtils.js +330 -0
- package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportXchangeState.js +107 -0
- package/dist/services/features/telegram-mcp/src/shared/integrations/terminal/client.js +255 -0
- package/dist/services/features/telegram-mcp/src/shared/integrations/terminal/ptyRegistry.js +543 -0
- package/dist/services/features/telegram-mcp/src/shared/integrations/xchange/sqliteRecordStore.js +223 -0
- package/dist/services/features/telegram-mcp/src/shared/lib/gatewayScope.js +24 -0
- package/dist/services/features/telegram-mcp/src/shared/lib/logger/logger.js +6 -0
- package/dist/services/features/telegram-mcp/src/shared/lib/project-identity/projectIdentity.js +147 -76
- package/dist/services/features/telegram-mcp/src/shared/lib/telegramXchangeRecords.js +72 -0
- package/dist/services/features/telegram-mcp/src/shared/lib/terminalPromptDetection.js +409 -0
- package/dist/services/features/telegram-mcp/src/shared/lib/version/versionHandshake.js +129 -1
- package/dist/services/features/telegram-mcp/src/shared/lib/xchangeRecordHints.js +98 -0
- package/dist/services/features/telegram-mcp/standalone-http.service.js +12 -0
- package/dist/services/features/telegram-mcp/terminal-buffer.service.js +42 -0
- package/dist/services/features/telegram-mcp/terminal-input.service.js +41 -0
- package/dist/services/features/telegram-mcp/tools-sync.service.js +15 -1
- package/dist/services/features/telegram-mcp/xchange.service.js +123 -0
- package/docs/STANDALONE-ru.md +172 -0
- package/docs/STANDALONE.md +172 -0
- package/package.json +10 -5
- package/scripts/build-package-artifact.sh +27 -0
- package/scripts/deploy-gateway.sh +64 -0
- package/scripts/deploy-onebot-branch.sh +27 -0
- package/scripts/postinstall.js +11 -36
- package/STANDALONE-ru.md +0 -274
- package/STANDALONE.md +0 -274
- package/dist/services/features/telegram-mcp/inbox.service.js +0 -33
- package/dist/services/features/telegram-mcp/pair.service.js +0 -33
- package/dist/services/features/telegram-mcp/src/app/webapp/tmux.js +0 -10
- package/dist/services/features/telegram-mcp/src/features/inbox/model/deleteTelegramInboxMessageTool.js +0 -33
- package/dist/services/features/telegram-mcp/src/features/inbox/model/getTelegramInboxCountTool.js +0 -33
- package/dist/services/features/telegram-mcp/src/features/inbox/model/getTelegramInboxTool.js +0 -33
- package/dist/services/features/telegram-mcp/src/features/inbox/model/inboxService.js +0 -77
- package/dist/services/features/telegram-mcp/src/features/pair-session/model/clearSessionPairingTool.js +0 -33
- package/dist/services/features/telegram-mcp/src/features/pair-session/model/createSessionPairCodeTool.js +0 -33
- package/dist/services/features/telegram-mcp/src/features/pair-session/model/generatePairCode.js +0 -202
- package/dist/services/features/telegram-mcp/src/features/session-context/model/getTmuxTargetTool.js +0 -33
- package/dist/services/features/telegram-mcp/src/features/session-context/model/setTmuxTargetTool.js +0 -33
- 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;
|