@deadragdoll/tellymcp 0.0.11 → 0.0.13
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/README-ru.md +48 -0
- package/README.md +47 -0
- package/TOOLS.md +232 -3
- package/VERSION.md +2 -2
- package/dist/cli.js +109 -1
- package/dist/services/features/telegram-mcp/browser.service.js +38 -1
- package/dist/services/features/telegram-mcp/gateway-socket.service.js +10 -0
- package/dist/services/features/telegram-mcp/mcp-server.service.js +12 -0
- package/dist/services/features/telegram-mcp/src/app/bootstrap/runtime.js +14 -0
- package/dist/services/features/telegram-mcp/src/app/config/env.js +13 -0
- package/dist/services/features/telegram-mcp/src/entities/request/model/schema.js +151 -2
- package/dist/services/features/telegram-mcp/src/features/browser/model/browserClickTool.js +1 -1
- package/dist/services/features/telegram-mcp/src/features/browser/model/browserDomTool.js +1 -1
- package/dist/services/features/telegram-mcp/src/features/browser/model/browserFillTool.js +1 -1
- package/dist/services/features/telegram-mcp/src/features/browser/model/browserInjectScriptTool.js +28 -0
- package/dist/services/features/telegram-mcp/src/features/browser/model/browserListAttachedInstancesTool.js +33 -0
- package/dist/services/features/telegram-mcp/src/features/browser/model/browserListTabsTool.js +33 -0
- package/dist/services/features/telegram-mcp/src/features/browser/model/browserOpenTool.js +1 -1
- package/dist/services/features/telegram-mcp/src/features/browser/model/browserPressTool.js +1 -1
- package/dist/services/features/telegram-mcp/src/features/browser/model/browserRecordingStartTool.js +28 -0
- package/dist/services/features/telegram-mcp/src/features/browser/model/browserRecordingStatusTool.js +28 -0
- package/dist/services/features/telegram-mcp/src/features/browser/model/browserRecordingStopTool.js +28 -0
- 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 +543 -9
- package/dist/services/features/telegram-mcp/src/features/browser-attach/model/browserRecordingBundle.js +502 -0
- package/dist/services/features/telegram-mcp/src/features/browser-attach/model/firefoxAttachRegistry.js +79 -0
- package/dist/services/features/telegram-mcp/src/features/browser-attach/model/firefoxAttachServer.js +559 -0
- package/dist/services/features/telegram-mcp/src/features/browser-attach/model/types.js +2 -0
- package/dist/services/features/telegram-mcp/src/features/distributed-gateway/model/gatewayHttpService.js +48 -0
- package/dist/services/features/telegram-mcp/src/shared/i18n/resources/en.js +5 -1
- package/dist/services/features/telegram-mcp/src/shared/i18n/resources/ru.js +5 -1
- package/dist/services/features/telegram-mcp/src/shared/integrations/redis/stateStore.js +56 -0
- package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transport.js +37 -0
- package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportConsoleRegistry.js +13 -7
- package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportConstructorWiring.js +9 -0
- package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportMenuShell.js +3 -0
- package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportPayloadState.js +7 -0
- package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportTerminalActions.js +231 -34
- package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportTerminalRuntime.js +61 -6
- package/dist/services/features/telegram-mcp/src/shared/lib/terminalPromptDetection.js +200 -28
- package/docs/STANDALONE-ru.md +42 -6
- package/docs/STANDALONE.md +42 -6
- package/package.json +6 -3
- package/packages/chrome-attach-extension/dist/background.js +1326 -0
- package/packages/chrome-attach-extension/dist/icon.svg +6 -0
- package/packages/chrome-attach-extension/dist/manifest.json +36 -0
- package/packages/chrome-attach-extension/dist/options.html +312 -0
- package/packages/chrome-attach-extension/dist/options.js +593 -0
- package/packages/chrome-attach-extension/dist/popup.html +93 -0
- package/packages/chrome-attach-extension/dist/popup.js +79 -0
- package/packages/chrome-attach-extension/dist/recorder-content.js +83 -0
- package/packages/chrome-attach-extension/dist/recorder-page.js +266 -0
- package/packages/firefox-attach-extension/README.md +13 -0
- package/packages/firefox-attach-extension/dist/background.js +1242 -0
- package/packages/firefox-attach-extension/dist/icon.svg +6 -0
- package/packages/firefox-attach-extension/dist/manifest.json +56 -0
- package/packages/firefox-attach-extension/dist/options.html +312 -0
- package/packages/firefox-attach-extension/dist/options.js +527 -0
- package/packages/firefox-attach-extension/dist/popup.html +93 -0
- package/packages/firefox-attach-extension/dist/popup.js +64 -0
- package/packages/firefox-attach-extension/dist/recorder-content.js +77 -0
- package/packages/firefox-attach-extension/dist/recorder-page.js +302 -0
|
@@ -25,9 +25,35 @@ class TransportTerminalRuntime {
|
|
|
25
25
|
}
|
|
26
26
|
startPromptScan() {
|
|
27
27
|
if (!this.host.config.terminal.promptScanEnabled) {
|
|
28
|
+
this.host.logger.debug("terminal prompt scan disabled", {
|
|
29
|
+
promptScanEnabled: this.host.config.terminal.promptScanEnabled,
|
|
30
|
+
});
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
if (this.host.config.distributed.mode === "gateway") {
|
|
34
|
+
this.host.logger.info("terminal prompt scan armed for live clients", {
|
|
35
|
+
intervalSeconds: this.host.config.terminal.promptScanIntervalSeconds,
|
|
36
|
+
cooldownSeconds: this.host.config.terminal.promptScanCooldownSeconds,
|
|
37
|
+
strategy: this.host.config.terminal.promptScanStrategy,
|
|
38
|
+
minScore: this.host.config.terminal.promptScanMinScore,
|
|
39
|
+
});
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
this.ensurePromptScanRunning();
|
|
43
|
+
}
|
|
44
|
+
clearTerminalPromptScanTimer() {
|
|
45
|
+
if (this.terminalPromptScanTimer) {
|
|
46
|
+
clearInterval(this.terminalPromptScanTimer);
|
|
47
|
+
this.terminalPromptScanTimer = undefined;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
ensurePromptScanRunning() {
|
|
51
|
+
if (!this.host.config.terminal.promptScanEnabled) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
if (this.terminalPromptScanTimer) {
|
|
28
55
|
return;
|
|
29
56
|
}
|
|
30
|
-
this.clearTerminalPromptScanTimer();
|
|
31
57
|
const intervalMs = this.host.config.terminal.promptScanIntervalSeconds * 1000;
|
|
32
58
|
const timer = setInterval(() => {
|
|
33
59
|
void this.runTerminalPromptScanCycle().catch((error) => {
|
|
@@ -54,11 +80,12 @@ class TransportTerminalRuntime {
|
|
|
54
80
|
});
|
|
55
81
|
});
|
|
56
82
|
}
|
|
57
|
-
|
|
58
|
-
if (this.terminalPromptScanTimer) {
|
|
59
|
-
|
|
60
|
-
this.terminalPromptScanTimer = undefined;
|
|
83
|
+
pausePromptScan() {
|
|
84
|
+
if (!this.terminalPromptScanTimer) {
|
|
85
|
+
return;
|
|
61
86
|
}
|
|
87
|
+
this.clearTerminalPromptScanTimer();
|
|
88
|
+
this.host.logger.info("terminal prompt scan paused", {});
|
|
62
89
|
}
|
|
63
90
|
scheduleTerminalNudgeForInboxMessage(sessionId, session) {
|
|
64
91
|
if (!this.host.config.terminal.nudgeEnabled) {
|
|
@@ -153,15 +180,43 @@ class TransportTerminalRuntime {
|
|
|
153
180
|
}
|
|
154
181
|
}
|
|
155
182
|
async runTerminalPromptScanCycle() {
|
|
156
|
-
if (!this.host.config.terminal.promptScanEnabled
|
|
183
|
+
if (!this.host.config.terminal.promptScanEnabled) {
|
|
184
|
+
this.host.logger.debug("terminal prompt scan cycle skipped", {
|
|
185
|
+
skipReason: "disabled",
|
|
186
|
+
});
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
if (this.terminalPromptScanInFlight) {
|
|
190
|
+
this.host.logger.debug("terminal prompt scan cycle skipped", {
|
|
191
|
+
skipReason: "in_flight",
|
|
192
|
+
});
|
|
157
193
|
return;
|
|
158
194
|
}
|
|
159
195
|
this.terminalPromptScanInFlight = true;
|
|
160
196
|
try {
|
|
197
|
+
if (this.host.ensureGatewayScopedConsolesBoundForPrincipal) {
|
|
198
|
+
const principals = await this.host.bindingStore.listBoundPrincipals();
|
|
199
|
+
this.host.logger.debug("terminal prompt scan syncing bound gateway principals", {
|
|
200
|
+
principalCount: principals.length,
|
|
201
|
+
});
|
|
202
|
+
for (const principal of principals) {
|
|
203
|
+
await this.host.ensureGatewayScopedConsolesBoundForPrincipal(principal);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
161
206
|
const sessions = await this.host.sessionStore.listSessions();
|
|
207
|
+
this.host.logger.debug("terminal prompt scan cycle started", {
|
|
208
|
+
sessionCount: sessions.length,
|
|
209
|
+
});
|
|
210
|
+
if (sessions.length === 0) {
|
|
211
|
+
this.host.logger.debug("terminal prompt scan cycle found no sessions");
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
162
214
|
for (const session of sessions) {
|
|
163
215
|
await this.actions.scanPromptForSession(session);
|
|
164
216
|
}
|
|
217
|
+
this.host.logger.debug("terminal prompt scan cycle completed", {
|
|
218
|
+
sessionCount: sessions.length,
|
|
219
|
+
});
|
|
165
220
|
}
|
|
166
221
|
finally {
|
|
167
222
|
this.terminalPromptScanInFlight = false;
|
|
@@ -16,6 +16,11 @@ const STRONG_PATTERNS = [
|
|
|
16
16
|
score: 5,
|
|
17
17
|
reason: "press_enter_prompt",
|
|
18
18
|
},
|
|
19
|
+
{
|
|
20
|
+
pattern: /press\s+enter\s+to\s+confirm\b.*\besc\s+to\s+cancel/iu,
|
|
21
|
+
score: 5,
|
|
22
|
+
reason: "confirm_cancel_prompt",
|
|
23
|
+
},
|
|
19
24
|
{
|
|
20
25
|
pattern: /waiting\s+for\s+(?:user\s+)?input|awaiting\s+(?:user\s+)?(?:input|confirmation)|requires?\s+approval|need\s+your\s+(?:input|permission|approval)|cannot\s+continue\s+without/iu,
|
|
21
26
|
score: 4,
|
|
@@ -46,6 +51,11 @@ const STRONG_PATTERNS = [
|
|
|
46
51
|
score: 4,
|
|
47
52
|
reason: "tool_continue_prompt",
|
|
48
53
|
},
|
|
54
|
+
{
|
|
55
|
+
pattern: /\bwould\s+you\s+like\s+to\s+run\s+the\s+following\s+command\b/iu,
|
|
56
|
+
score: 5,
|
|
57
|
+
reason: "command_approval_prompt",
|
|
58
|
+
},
|
|
49
59
|
];
|
|
50
60
|
const MEDIUM_PATTERNS = [
|
|
51
61
|
{
|
|
@@ -64,6 +74,17 @@ const MEDIUM_PATTERNS = [
|
|
|
64
74
|
reason: "tool_choice_keyword",
|
|
65
75
|
},
|
|
66
76
|
];
|
|
77
|
+
const NUMBERED_CHOICE_LINE_PATTERN = /^(?:[>›*•-]\s*)?\d+\.\s+.+$/u;
|
|
78
|
+
const CONFIRM_CANCEL_FOOTER_PATTERN = /\b(?:press\s+enter\s+to\s+confirm|enter\s+to\s+submit)\b.*\besc\s+to\s+cancel\b/iu;
|
|
79
|
+
const SUBMIT_CANCEL_FOOTER_PATTERN = /\benter\s+to\s+submit\b.*\besc\s+to\s+cancel\b/iu;
|
|
80
|
+
const CONTINUE_FOOTER_PATTERN = /\b(?:press\s+enter\s+to\s+continue|hit\s+enter\s+to\s+continue)\b/iu;
|
|
81
|
+
const NUMBERED_HOTKEY_FOOTER_PATTERN = /\bpress\s+(?:(?:\d+\s*,\s*)+\s*(?:or\s+)?\d+\s*,?\s*)?(?:enter\b.*\besc|esc\b.*\benter|\d+\b.*\benter|\d+\b.*\besc).*\b(?:quit|continue|cancel|confirm|submit)\b/iu;
|
|
82
|
+
const POSITIVE_CHOICE_PATTERN = /\b(?:yes|allow|approve|proceed|continue)\b/iu;
|
|
83
|
+
const NEGATIVE_CHOICE_PATTERN = /\b(?:no|cancel|deny|reject)\b/iu;
|
|
84
|
+
const STICKY_CHOICE_PATTERN = /\bdon'?t\s+ask\s+again|always\s+allow|for\s+this\s+session\b/iu;
|
|
85
|
+
const SKIP_CHOICE_PATTERN = /\bskip\b/iu;
|
|
86
|
+
const LEADING_SELECTION_MARKER_PATTERN = /^(?:[>›*•-]\s*)(?=\d+\.\s)/u;
|
|
87
|
+
const ACTION_HINT_PATTERN = /\b(?:press|input|choose|choice|select|option|enter|esc(?:ape)?|accept|decline|confirm|cancel|continue|proceed|yes|no)\b|(?:\[[Yy](?:es)?\/[Nn](?:o)?\])|(?:\([Yy](?:es)?\/[Nn](?:o)?\))/u;
|
|
67
88
|
function normalizeLine(line) {
|
|
68
89
|
return line
|
|
69
90
|
.replaceAll("\r", "")
|
|
@@ -119,25 +140,6 @@ function scoreMediumLine(line, strategy) {
|
|
|
119
140
|
function isStrongReason(reason) {
|
|
120
141
|
return STRONG_PATTERNS.some((candidate) => candidate.reason === reason);
|
|
121
142
|
}
|
|
122
|
-
function detectGroupedSignals(candidateLines, strategy) {
|
|
123
|
-
const signals = [];
|
|
124
|
-
const hasPrimaryAllowChoice = candidateLines.some((line) => /^>?[\s\d.]*allow\b/iu.test(line));
|
|
125
|
-
const hasSecondaryApprovalChoice = candidateLines.some((line) => /\b(?:allow\s+once|allow\s+for\s+this\s+session|always\s+allow|cancel)\b/iu.test(line));
|
|
126
|
-
const hasSubmitCancelHint = candidateLines.some((line) => /\benter\s+to\s+submit\b.*\besc\s+to\s+cancel\b/iu.test(line));
|
|
127
|
-
if (hasPrimaryAllowChoice && hasSecondaryApprovalChoice) {
|
|
128
|
-
signals.push({
|
|
129
|
-
score: 5,
|
|
130
|
-
reason: "approval_choice_group",
|
|
131
|
-
});
|
|
132
|
-
}
|
|
133
|
-
if (hasSubmitCancelHint) {
|
|
134
|
-
signals.push({
|
|
135
|
-
score: strategy === "balanced" ? 2 : 3,
|
|
136
|
-
reason: "submit_cancel_hint",
|
|
137
|
-
});
|
|
138
|
-
}
|
|
139
|
-
return signals;
|
|
140
|
-
}
|
|
141
143
|
function collectCandidateLines(rawText, maxLines) {
|
|
142
144
|
const normalized = rawText
|
|
143
145
|
.split("\n")
|
|
@@ -145,6 +147,168 @@ function collectCandidateLines(rawText, maxLines) {
|
|
|
145
147
|
.filter((line) => line.length > 0);
|
|
146
148
|
return normalized.slice(-Math.max(1, maxLines));
|
|
147
149
|
}
|
|
150
|
+
function normalizeFingerprintLine(line) {
|
|
151
|
+
return normalizeLine(line)
|
|
152
|
+
.replace(LEADING_SELECTION_MARKER_PATTERN, "")
|
|
153
|
+
.replace(/\s+/gu, " ")
|
|
154
|
+
.trim();
|
|
155
|
+
}
|
|
156
|
+
function buildDetectionFingerprint(strategy, matchedLines) {
|
|
157
|
+
const normalizedExcerpt = matchedLines
|
|
158
|
+
.map(normalizeFingerprintLine)
|
|
159
|
+
.join("\n");
|
|
160
|
+
return (0, node_crypto_1.createHash)("sha1")
|
|
161
|
+
.update(`${strategy}\n${normalizedExcerpt}`)
|
|
162
|
+
.digest("hex");
|
|
163
|
+
}
|
|
164
|
+
function buildExcerptWithContext(candidateLines, matchedLines) {
|
|
165
|
+
if (matchedLines.length === 0) {
|
|
166
|
+
return "";
|
|
167
|
+
}
|
|
168
|
+
const firstMatchedIndex = candidateLines.findIndex((line) => line === matchedLines[0]);
|
|
169
|
+
const lastMatchedIndex = matchedLines.length === 1
|
|
170
|
+
? firstMatchedIndex
|
|
171
|
+
: candidateLines.findIndex((line, index) => index >= Math.max(0, firstMatchedIndex) &&
|
|
172
|
+
line === matchedLines[matchedLines.length - 1]);
|
|
173
|
+
if (firstMatchedIndex < 0 || lastMatchedIndex < 0) {
|
|
174
|
+
return matchedLines.slice(-8).join("\n");
|
|
175
|
+
}
|
|
176
|
+
const excerptStart = Math.max(0, firstMatchedIndex - 2);
|
|
177
|
+
const excerptEnd = Math.min(candidateLines.length, lastMatchedIndex + 1);
|
|
178
|
+
return candidateLines.slice(excerptStart, excerptEnd).slice(-8).join("\n");
|
|
179
|
+
}
|
|
180
|
+
function findChoiceActionBlock(candidateLines) {
|
|
181
|
+
for (let startIndex = candidateLines.length - 1; startIndex >= 0; startIndex -= 1) {
|
|
182
|
+
const line = candidateLines[startIndex] ?? "";
|
|
183
|
+
if (!NUMBERED_CHOICE_LINE_PATTERN.test(line)) {
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
const choiceLines = [];
|
|
187
|
+
let firstChoiceIndex = startIndex;
|
|
188
|
+
while (firstChoiceIndex >= 0) {
|
|
189
|
+
const currentLine = candidateLines[firstChoiceIndex] ?? "";
|
|
190
|
+
if (!NUMBERED_CHOICE_LINE_PATTERN.test(currentLine)) {
|
|
191
|
+
break;
|
|
192
|
+
}
|
|
193
|
+
choiceLines.unshift(currentLine);
|
|
194
|
+
firstChoiceIndex -= 1;
|
|
195
|
+
}
|
|
196
|
+
firstChoiceIndex += 1;
|
|
197
|
+
if (choiceLines.length < 2) {
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
const lastChoiceIndex = firstChoiceIndex + choiceLines.length - 1;
|
|
201
|
+
const headerLines = candidateLines.slice(Math.max(0, firstChoiceIndex - 5), firstChoiceIndex);
|
|
202
|
+
const tailLines = candidateLines.slice(lastChoiceIndex + 1, Math.min(candidateLines.length, lastChoiceIndex + 4));
|
|
203
|
+
const footerLine = tailLines[0] ?? null;
|
|
204
|
+
const nearbyLines = [...headerLines, ...tailLines];
|
|
205
|
+
const hasActionHint = nearbyLines.some((candidate) => ACTION_HINT_PATTERN.test(candidate));
|
|
206
|
+
const hasChoiceSemantics = POSITIVE_CHOICE_PATTERN.test(choiceLines.join("\n")) ||
|
|
207
|
+
NEGATIVE_CHOICE_PATTERN.test(choiceLines.join("\n")) ||
|
|
208
|
+
STICKY_CHOICE_PATTERN.test(choiceLines.join("\n")) ||
|
|
209
|
+
SKIP_CHOICE_PATTERN.test(choiceLines.join("\n"));
|
|
210
|
+
if (!hasActionHint && !hasChoiceSemantics) {
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
return {
|
|
214
|
+
footerLine,
|
|
215
|
+
choiceLines,
|
|
216
|
+
headerLines,
|
|
217
|
+
tailLines,
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
return null;
|
|
221
|
+
}
|
|
222
|
+
function detectChoiceFooterSignals(candidateLines, strategy) {
|
|
223
|
+
const block = findChoiceActionBlock(candidateLines);
|
|
224
|
+
if (!block) {
|
|
225
|
+
return null;
|
|
226
|
+
}
|
|
227
|
+
const choiceText = block.choiceLines.join("\n");
|
|
228
|
+
const contextLines = [
|
|
229
|
+
...block.headerLines,
|
|
230
|
+
...block.choiceLines,
|
|
231
|
+
...(block.footerLine ? [block.footerLine] : []),
|
|
232
|
+
...block.tailLines,
|
|
233
|
+
];
|
|
234
|
+
const reasons = new Set(["numbered_choice_group"]);
|
|
235
|
+
const matchedLines = [
|
|
236
|
+
...block.choiceLines,
|
|
237
|
+
...(block.footerLine ? [block.footerLine] : []),
|
|
238
|
+
...block.tailLines,
|
|
239
|
+
];
|
|
240
|
+
let score = strategy === "balanced" ? 5 : 6;
|
|
241
|
+
const footerLine = block.footerLine ?? "";
|
|
242
|
+
if (CONFIRM_CANCEL_FOOTER_PATTERN.test(footerLine)) {
|
|
243
|
+
reasons.add("confirm_cancel_footer");
|
|
244
|
+
score += strategy === "balanced" ? 2 : 3;
|
|
245
|
+
}
|
|
246
|
+
if (CONTINUE_FOOTER_PATTERN.test(footerLine)) {
|
|
247
|
+
reasons.add("continue_footer");
|
|
248
|
+
score += strategy === "balanced" ? 2 : 3;
|
|
249
|
+
}
|
|
250
|
+
if (NUMBERED_HOTKEY_FOOTER_PATTERN.test(footerLine)) {
|
|
251
|
+
reasons.add("numbered_hotkey_footer");
|
|
252
|
+
score += strategy === "balanced" ? 2 : 3;
|
|
253
|
+
}
|
|
254
|
+
if (SUBMIT_CANCEL_FOOTER_PATTERN.test(footerLine)) {
|
|
255
|
+
reasons.add("submit_cancel_hint");
|
|
256
|
+
}
|
|
257
|
+
if (/press\s+enter\s+to\s+confirm\b/iu.test(footerLine)) {
|
|
258
|
+
reasons.add("confirm_cancel_hint");
|
|
259
|
+
}
|
|
260
|
+
if (contextLines.some((line) => ACTION_HINT_PATTERN.test(line))) {
|
|
261
|
+
reasons.add("action_hint_present");
|
|
262
|
+
score += 2;
|
|
263
|
+
}
|
|
264
|
+
if (POSITIVE_CHOICE_PATTERN.test(choiceText)) {
|
|
265
|
+
reasons.add("positive_choice_present");
|
|
266
|
+
score += 2;
|
|
267
|
+
}
|
|
268
|
+
if (NEGATIVE_CHOICE_PATTERN.test(choiceText)) {
|
|
269
|
+
reasons.add("negative_choice_present");
|
|
270
|
+
score += 2;
|
|
271
|
+
}
|
|
272
|
+
if (STICKY_CHOICE_PATTERN.test(choiceText)) {
|
|
273
|
+
reasons.add("sticky_choice_present");
|
|
274
|
+
score += 2;
|
|
275
|
+
}
|
|
276
|
+
if (SKIP_CHOICE_PATTERN.test(choiceText)) {
|
|
277
|
+
reasons.add("skip_choice_present");
|
|
278
|
+
score += 2;
|
|
279
|
+
}
|
|
280
|
+
if (reasons.has("positive_choice_present") && reasons.has("negative_choice_present")) {
|
|
281
|
+
reasons.add("yes_no_choice_group");
|
|
282
|
+
reasons.add("approval_choice_group");
|
|
283
|
+
score += 3;
|
|
284
|
+
}
|
|
285
|
+
if (reasons.has("positive_choice_present") && reasons.has("skip_choice_present")) {
|
|
286
|
+
reasons.add("choice_update_group");
|
|
287
|
+
score += 2;
|
|
288
|
+
}
|
|
289
|
+
if (/^(?:[>›*•-]\s*)?(?:\d+\.\s+)?yes,\s+proceed\b/imu.test(choiceText) &&
|
|
290
|
+
reasons.has("sticky_choice_present")) {
|
|
291
|
+
reasons.add("proceed_choice_group");
|
|
292
|
+
}
|
|
293
|
+
for (const line of contextLines) {
|
|
294
|
+
for (const contribution of scoreStrongLine(line)) {
|
|
295
|
+
reasons.add(contribution.reason);
|
|
296
|
+
score += contribution.score;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
return {
|
|
300
|
+
score,
|
|
301
|
+
reasons: Array.from(reasons),
|
|
302
|
+
matchedLines,
|
|
303
|
+
promptActions: {
|
|
304
|
+
numberedOptions: block.choiceLines
|
|
305
|
+
.map((line) => line.match(/^(?:[>›*•-]\s*)?(\d+)\.\s+/u)?.[1] ?? null)
|
|
306
|
+
.filter((value) => Boolean(value)),
|
|
307
|
+
hasEnter: contextLines.some((line) => /\benter\b/iu.test(line)),
|
|
308
|
+
hasEscape: contextLines.some((line) => /\besc(?:ape)?\b/iu.test(line)),
|
|
309
|
+
},
|
|
310
|
+
};
|
|
311
|
+
}
|
|
148
312
|
function detectTerminalInteractivePrompt(rawText, options = {}) {
|
|
149
313
|
const strategy = options.strategy ?? "strict";
|
|
150
314
|
const minScore = options.minScore ?? (strategy === "balanced" ? 4 : 5);
|
|
@@ -152,6 +316,20 @@ function detectTerminalInteractivePrompt(rawText, options = {}) {
|
|
|
152
316
|
if (candidateLines.length === 0) {
|
|
153
317
|
return null;
|
|
154
318
|
}
|
|
319
|
+
const choiceFooterDetection = detectChoiceFooterSignals(candidateLines, strategy);
|
|
320
|
+
if (choiceFooterDetection && choiceFooterDetection.score >= minScore) {
|
|
321
|
+
const matchedLines = choiceFooterDetection.matchedLines.slice(-6);
|
|
322
|
+
const excerpt = buildExcerptWithContext(candidateLines, matchedLines);
|
|
323
|
+
const fingerprint = buildDetectionFingerprint(strategy, matchedLines);
|
|
324
|
+
return {
|
|
325
|
+
score: choiceFooterDetection.score,
|
|
326
|
+
fingerprint,
|
|
327
|
+
excerpt,
|
|
328
|
+
matchedLines,
|
|
329
|
+
reasons: choiceFooterDetection.reasons,
|
|
330
|
+
promptActions: choiceFooterDetection.promptActions,
|
|
331
|
+
};
|
|
332
|
+
}
|
|
155
333
|
const scoredLines = [];
|
|
156
334
|
let score = 0;
|
|
157
335
|
const reasons = new Set();
|
|
@@ -174,12 +352,8 @@ function detectTerminalInteractivePrompt(rawText, options = {}) {
|
|
|
174
352
|
score += 3;
|
|
175
353
|
reasons.add("multiple_options");
|
|
176
354
|
}
|
|
177
|
-
for (const signal of detectGroupedSignals(candidateLines, strategy)) {
|
|
178
|
-
score += signal.score;
|
|
179
|
-
reasons.add(signal.reason);
|
|
180
|
-
}
|
|
181
355
|
const hasStrongReason = Array.from(reasons).some(isStrongReason);
|
|
182
|
-
const hasGroupedReason = reasons.has("
|
|
356
|
+
const hasGroupedReason = reasons.has("multiple_options");
|
|
183
357
|
const hasOnlyQuestionMarkReason = reasons.size === 1 && reasons.has("question_mark");
|
|
184
358
|
if (strategy === "strict") {
|
|
185
359
|
if (hasOnlyQuestionMarkReason) {
|
|
@@ -223,10 +397,8 @@ function detectTerminalInteractivePrompt(rawText, options = {}) {
|
|
|
223
397
|
const matchedLines = scoredLines
|
|
224
398
|
.map((entry) => entry.line)
|
|
225
399
|
.slice(-6);
|
|
226
|
-
const excerpt = matchedLines
|
|
227
|
-
const fingerprint = (
|
|
228
|
-
.update(`${strategy}\n${excerpt}`)
|
|
229
|
-
.digest("hex");
|
|
400
|
+
const excerpt = buildExcerptWithContext(candidateLines, matchedLines);
|
|
401
|
+
const fingerprint = buildDetectionFingerprint(strategy, matchedLines);
|
|
230
402
|
return {
|
|
231
403
|
score,
|
|
232
404
|
fingerprint,
|
package/docs/STANDALONE-ru.md
CHANGED
|
@@ -31,6 +31,8 @@ npm install -g @deadragdoll/tellymcp
|
|
|
31
31
|
|
|
32
32
|
```bash
|
|
33
33
|
tellymcp browser install
|
|
34
|
+
tellymcp extension firefox
|
|
35
|
+
tellymcp extension chrome
|
|
34
36
|
tellymcp codex-plugin install
|
|
35
37
|
```
|
|
36
38
|
|
|
@@ -91,7 +93,41 @@ TELLYMCP_SESSION_ID=NEW
|
|
|
91
93
|
TELLYMCP_SESSION_LABEL=NEW
|
|
92
94
|
```
|
|
93
95
|
|
|
94
|
-
## 5.
|
|
96
|
+
## 5. Attach extensions для браузера
|
|
97
|
+
|
|
98
|
+
Если нужно attach'иться к уже открытому Firefox или Chrome на агентской машине, включи локальный attach bridge в env этого агента:
|
|
99
|
+
|
|
100
|
+
```env
|
|
101
|
+
BROWSER_ATTACH_ENABLED=true
|
|
102
|
+
BROWSER_ATTACH_WS_HOST=127.0.0.1
|
|
103
|
+
BROWSER_ATTACH_WS_PORT=9999
|
|
104
|
+
BROWSER_ATTACH_WS_PATH=/browser-attach/ws
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Выгрузи unpacked extension bundle из установленного пакета:
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
tellymcp extension firefox
|
|
111
|
+
tellymcp extension chrome
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Команда создаст один из каталогов:
|
|
115
|
+
|
|
116
|
+
- `./tellymcp-firefox-attach`
|
|
117
|
+
- `./tellymcp-chrome-attach`
|
|
118
|
+
|
|
119
|
+
Дальше загрузи его в локальный браузер:
|
|
120
|
+
|
|
121
|
+
- Firefox: `about:debugging#/runtime/this-firefox` -> `Load Temporary Add-on` -> выбрать `manifest.json`
|
|
122
|
+
- Chrome: `chrome://extensions` -> включить Developer mode -> `Load unpacked` -> выбрать выгруженный каталог
|
|
123
|
+
|
|
124
|
+
После этого browser control panel умеет:
|
|
125
|
+
|
|
126
|
+
- attach'ить текущую agent-сессию к живой вкладке
|
|
127
|
+
- запускать и останавливать structured recording bundles в `.mcp-xchange/web/...`
|
|
128
|
+
- инжектить helper scripts в attached tab
|
|
129
|
+
|
|
130
|
+
## 6. Запуск
|
|
95
131
|
|
|
96
132
|
Gateway:
|
|
97
133
|
|
|
@@ -111,7 +147,7 @@ tellymcp run --env .env -s NEW
|
|
|
111
147
|
tellymcp run
|
|
112
148
|
```
|
|
113
149
|
|
|
114
|
-
##
|
|
150
|
+
## 7. Webhook
|
|
115
151
|
|
|
116
152
|
Gateway поддерживает polling и webhook.
|
|
117
153
|
|
|
@@ -131,7 +167,7 @@ TELEGRAM_WEBHOOK_SECRET=change_me_webhook_secret
|
|
|
131
167
|
- `/api/webapp`
|
|
132
168
|
- `/api/healthz`
|
|
133
169
|
|
|
134
|
-
##
|
|
170
|
+
## 8. MCP
|
|
135
171
|
|
|
136
172
|
Локальный client-mode MCP endpoint:
|
|
137
173
|
|
|
@@ -141,7 +177,7 @@ http://127.0.0.1:8787/mcp
|
|
|
141
177
|
|
|
142
178
|
Используй MCP HTTP endpoint, который поднимает `tellymcp run`.
|
|
143
179
|
|
|
144
|
-
##
|
|
180
|
+
## 9. Проверки
|
|
145
181
|
|
|
146
182
|
```bash
|
|
147
183
|
tellymcp doctor --env .env
|
|
@@ -153,7 +189,7 @@ tellymcp doctor --env .env
|
|
|
153
189
|
tellymcp system-prune --env .env --yes
|
|
154
190
|
```
|
|
155
191
|
|
|
156
|
-
##
|
|
192
|
+
## 10. Операционные заметки
|
|
157
193
|
|
|
158
194
|
- gateway-бот — это основной user-facing control plane
|
|
159
195
|
- консоли видны из gateway live registry
|
|
@@ -162,7 +198,7 @@ tellymcp system-prune --env .env --yes
|
|
|
162
198
|
- browser screenshot для человека лучше отправлять через `browser_screenshot(send_to_telegram=true)`
|
|
163
199
|
- файлы между консолями лучше отправлять через `send_partner_file`
|
|
164
200
|
|
|
165
|
-
##
|
|
201
|
+
## 11. Чего не делать в новых setup
|
|
166
202
|
|
|
167
203
|
Не строй новые setup вокруг:
|
|
168
204
|
|
package/docs/STANDALONE.md
CHANGED
|
@@ -31,6 +31,8 @@ Optional:
|
|
|
31
31
|
|
|
32
32
|
```bash
|
|
33
33
|
tellymcp browser install
|
|
34
|
+
tellymcp extension firefox
|
|
35
|
+
tellymcp extension chrome
|
|
34
36
|
tellymcp codex-plugin install
|
|
35
37
|
```
|
|
36
38
|
|
|
@@ -91,7 +93,41 @@ TELLYMCP_SESSION_ID=NEW
|
|
|
91
93
|
TELLYMCP_SESSION_LABEL=NEW
|
|
92
94
|
```
|
|
93
95
|
|
|
94
|
-
## 5.
|
|
96
|
+
## 5. Attached Browser Extensions
|
|
97
|
+
|
|
98
|
+
If you want to attach TellyMCP to an already running Firefox or Chrome tab on an agent machine, enable the local attach bridge in that agent env:
|
|
99
|
+
|
|
100
|
+
```env
|
|
101
|
+
BROWSER_ATTACH_ENABLED=true
|
|
102
|
+
BROWSER_ATTACH_WS_HOST=127.0.0.1
|
|
103
|
+
BROWSER_ATTACH_WS_PORT=9999
|
|
104
|
+
BROWSER_ATTACH_WS_PATH=/browser-attach/ws
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Export the unpacked extension bundle from the installed package:
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
tellymcp extension firefox
|
|
111
|
+
tellymcp extension chrome
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
This creates one of:
|
|
115
|
+
|
|
116
|
+
- `./tellymcp-firefox-attach`
|
|
117
|
+
- `./tellymcp-chrome-attach`
|
|
118
|
+
|
|
119
|
+
Load it into the local browser:
|
|
120
|
+
|
|
121
|
+
- Firefox: `about:debugging#/runtime/this-firefox` -> `Load Temporary Add-on` -> choose `manifest.json`
|
|
122
|
+
- Chrome: `chrome://extensions` -> enable Developer mode -> `Load unpacked` -> choose the exported directory
|
|
123
|
+
|
|
124
|
+
After that the browser control panel can:
|
|
125
|
+
|
|
126
|
+
- attach the current agent session to a live browser tab
|
|
127
|
+
- start and stop structured recording bundles in `.mcp-xchange/web/...`
|
|
128
|
+
- inject helper scripts into the attached tab
|
|
129
|
+
|
|
130
|
+
## 6. Run
|
|
95
131
|
|
|
96
132
|
Gateway:
|
|
97
133
|
|
|
@@ -111,7 +147,7 @@ After `.mcpsession.json` is created in the workspace, later runs can usually use
|
|
|
111
147
|
tellymcp run
|
|
112
148
|
```
|
|
113
149
|
|
|
114
|
-
##
|
|
150
|
+
## 7. Webhook
|
|
115
151
|
|
|
116
152
|
Gateway supports polling and webhook.
|
|
117
153
|
|
|
@@ -131,7 +167,7 @@ If nginx already proxies `location /api/ { ... }` to the standalone listener, th
|
|
|
131
167
|
- `/api/webapp`
|
|
132
168
|
- `/api/healthz`
|
|
133
169
|
|
|
134
|
-
##
|
|
170
|
+
## 8. MCP
|
|
135
171
|
|
|
136
172
|
Local client-mode MCP:
|
|
137
173
|
|
|
@@ -141,7 +177,7 @@ http://127.0.0.1:8787/mcp
|
|
|
141
177
|
|
|
142
178
|
Use the MCP HTTP endpoint exposed by `tellymcp run`.
|
|
143
179
|
|
|
144
|
-
##
|
|
180
|
+
## 9. Health Checks
|
|
145
181
|
|
|
146
182
|
```bash
|
|
147
183
|
tellymcp doctor --env .env
|
|
@@ -153,7 +189,7 @@ Destructive cleanup:
|
|
|
153
189
|
tellymcp system-prune --env .env --yes
|
|
154
190
|
```
|
|
155
191
|
|
|
156
|
-
##
|
|
192
|
+
## 10. Operational Notes
|
|
157
193
|
|
|
158
194
|
- the gateway bot is the user-facing control plane
|
|
159
195
|
- consoles are discovered from the gateway live registry
|
|
@@ -162,7 +198,7 @@ tellymcp system-prune --env .env --yes
|
|
|
162
198
|
- browser screenshot replies to humans should use `browser_screenshot(send_to_telegram=true)`
|
|
163
199
|
- file results between consoles should use `send_partner_file`
|
|
164
200
|
|
|
165
|
-
##
|
|
201
|
+
## 11. Legacy Concepts To Avoid
|
|
166
202
|
|
|
167
203
|
Do not build new setups around:
|
|
168
204
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deadragdoll/tellymcp",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.13",
|
|
4
4
|
"description": "TellyMCP - Telegram control plane for MCP-connected coding agents",
|
|
5
5
|
"main": "dist/services/features/telegram-mcp/runtime.service.js",
|
|
6
6
|
"bin": {
|
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
},
|
|
9
9
|
"files": [
|
|
10
10
|
"dist",
|
|
11
|
+
"packages/firefox-attach-extension/dist",
|
|
12
|
+
"packages/chrome-attach-extension/dist",
|
|
11
13
|
"scripts",
|
|
12
14
|
"config/templates",
|
|
13
15
|
"config/codex",
|
|
@@ -36,8 +38,9 @@
|
|
|
36
38
|
},
|
|
37
39
|
"scripts": {
|
|
38
40
|
"postinstall": "node ./scripts/postinstall.js",
|
|
39
|
-
"
|
|
40
|
-
"
|
|
41
|
+
"build:extensions": "node ./packages/firefox-attach-extension/scripts/build.mjs && node ./packages/chrome-attach-extension/scripts/build.mjs",
|
|
42
|
+
"prepack": "yarn build && yarn build:extensions",
|
|
43
|
+
"prepublishOnly": "yarn build && yarn build:extensions && yarn lint && yarn test",
|
|
41
44
|
"mcp:local": "codex mcp add telegramHuman --url http://127.0.0.1:8787/mcp",
|
|
42
45
|
"dev:gw": "TS_NODE_TRANSPILE_ONLY=1 TS_NODE_EXPERIMENTAL_RESOLVER=1 NODE_OPTIONS=--openssl-legacy-provider ts-node ./node_modules/moleculer/bin/moleculer-runner.js src/services --config src/moleculer.config.ts --envfile /opt/tellymcp-gateway/.env-gateway --hot --mask \"**/*.service.ts\"",
|
|
43
46
|
"dev:builder": "TS_NODE_TRANSPILE_ONLY=1 TS_NODE_EXPERIMENTAL_RESOLVER=1 NODE_OPTIONS=--openssl-legacy-provider ts-node ./node_modules/moleculer/bin/moleculer-runner.js src/services --config src/moleculer.config.ts --envfile ./.env-builder --hot --mask \"**/*.service.ts\"",
|