@deadragdoll/tellymcp 0.0.3 → 0.0.5
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 +1 -1
- package/README.md +1 -1
- package/VERSION.md +115 -0
- package/dist/cli.js +4 -1
- package/dist/services/features/telegram-mcp/gateway-socket.service.js +13 -2
- package/dist/services/features/telegram-mcp/runtime.service.js +9 -2
- package/dist/services/features/telegram-mcp/src/app/bootstrap/runtime.js +2 -0
- package/dist/services/features/telegram-mcp/src/app/http.js +18 -2
- package/dist/services/features/telegram-mcp/src/app/webapp/assets.js +99 -7
- package/dist/services/features/telegram-mcp/src/app/webapp/tmux.js +2 -1
- package/dist/services/features/telegram-mcp/src/features/distributed-gateway/model/gatewayHttpService.js +1 -0
- package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transport.js +95 -0
- package/dist/services/features/telegram-mcp/src/shared/integrations/tmux/client.js +8 -2
- package/package.json +2 -1
package/README-ru.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# TellyMCP
|
|
2
2
|
|
|
3
|
-
[English](README.md) | [Русский](README-ru.md) | [Standalone Guide](STANDALONE.md) | [Standalone RU](STANDALONE-ru.md)
|
|
3
|
+
[English](README.md) | [Русский](README-ru.md) | [Standalone Guide](STANDALONE.md) | [Standalone RU](STANDALONE-ru.md) | [Release Notes](VERSION.md)
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/@deadragdoll/tellymcp)
|
|
6
6
|
[](https://www.npmjs.com/package/@deadragdoll/tellymcp)
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# TellyMCP
|
|
2
2
|
|
|
3
|
-
[English](README.md) | [Русский](README-ru.md) | [Standalone Guide](STANDALONE.md) | [Standalone RU](STANDALONE-ru.md)
|
|
3
|
+
[English](README.md) | [Русский](README-ru.md) | [Standalone Guide](STANDALONE.md) | [Standalone RU](STANDALONE-ru.md) | [Release Notes](VERSION.md)
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/@deadragdoll/tellymcp)
|
|
6
6
|
[](https://www.npmjs.com/package/@deadragdoll/tellymcp)
|
package/VERSION.md
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# TellyMCP Release Notes
|
|
2
|
+
|
|
3
|
+
Public, user-facing release notes for published versions of `@deadragdoll/tellymcp`.
|
|
4
|
+
|
|
5
|
+
For detailed engineering history, refactors, and internal development notes, see [CHANGELOG.md](CHANGELOG.md).
|
|
6
|
+
|
|
7
|
+
## 0.0.3
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
- Standalone CLI workflow:
|
|
11
|
+
- `tellymcp init <client|gateway|both>`
|
|
12
|
+
- `tellymcp run`
|
|
13
|
+
- `tellymcp doctor`
|
|
14
|
+
- `tellymcp mcp --help`
|
|
15
|
+
- Standalone and public installation guides:
|
|
16
|
+
- [STANDALONE.md](STANDALONE.md)
|
|
17
|
+
- [STANDALONE-ru.md](STANDALONE-ru.md)
|
|
18
|
+
- Browser runtime helper:
|
|
19
|
+
- `tellymcp browser install`
|
|
20
|
+
- Public README set for GitHub and npm:
|
|
21
|
+
- [README.md](README.md)
|
|
22
|
+
- [README-ru.md](README-ru.md)
|
|
23
|
+
- Human-readable release notes in this file.
|
|
24
|
+
- Telegram startup notice:
|
|
25
|
+
- version
|
|
26
|
+
- protocol
|
|
27
|
+
- mode
|
|
28
|
+
- paired sessions
|
|
29
|
+
- MCP/WebApp/Gateway endpoints
|
|
30
|
+
- Live text input button:
|
|
31
|
+
- `[txt]`
|
|
32
|
+
- sends literal text to tmux without pressing `Enter`
|
|
33
|
+
|
|
34
|
+
### Changed
|
|
35
|
+
- Default installation path is now npm-first:
|
|
36
|
+
- `npm install -g @deadragdoll/tellymcp`
|
|
37
|
+
- Standalone client mode is documented first, before gateway/both deployment.
|
|
38
|
+
- `tmux` is now documented as a strongly recommended prerequisite for the full experience:
|
|
39
|
+
- Live View
|
|
40
|
+
- nudges
|
|
41
|
+
- direct terminal control from Telegram
|
|
42
|
+
- Environment examples were split into dedicated client and gateway variants.
|
|
43
|
+
- Package build/publish flow now validates itself before packing/publishing.
|
|
44
|
+
- CLI now shows package version directly in banners and startup output.
|
|
45
|
+
|
|
46
|
+
### Collaboration
|
|
47
|
+
- Project collaboration works across local and remote sessions.
|
|
48
|
+
- `Collab` now includes:
|
|
49
|
+
- `Broadcast`
|
|
50
|
+
- `History`
|
|
51
|
+
- `Delete`
|
|
52
|
+
- `Ask` and `Share` semantics were clarified:
|
|
53
|
+
- `Ask` tells the selected session to do the work and reply back
|
|
54
|
+
- `Share` tells the current session to send something to the selected session
|
|
55
|
+
|
|
56
|
+
### Live View
|
|
57
|
+
- Telegram Mini App Live View supports:
|
|
58
|
+
- fullscreen/expand launch policy
|
|
59
|
+
- bottom toolbar layout
|
|
60
|
+
- `Esc`
|
|
61
|
+
- `Tab`
|
|
62
|
+
- `Ctrl+C`
|
|
63
|
+
- `Backspace`
|
|
64
|
+
- `Up`
|
|
65
|
+
- `Down`
|
|
66
|
+
- `Enter`
|
|
67
|
+
- Live approval flow was added for remote project sessions.
|
|
68
|
+
- Live toolbar now includes:
|
|
69
|
+
- `/`
|
|
70
|
+
- `↑`
|
|
71
|
+
- `↓`
|
|
72
|
+
- `Enter`
|
|
73
|
+
- `⌫`
|
|
74
|
+
- `[txt]`
|
|
75
|
+
- `Tab`
|
|
76
|
+
- `Esc`
|
|
77
|
+
- `Ctrl+C`
|
|
78
|
+
- `Ctrl+C` now asks for confirmation before sending an interrupt to the agent.
|
|
79
|
+
- Mobile toolbar layout now wraps cleanly into two rows instead of collapsing into a centered stack.
|
|
80
|
+
|
|
81
|
+
### Browser
|
|
82
|
+
- Browser tools use Playwright Chromium.
|
|
83
|
+
- Headless mode is the recommended default for remote and SSH-based environments.
|
|
84
|
+
- `doctor` now helps detect missing browser runtime and connectivity issues.
|
|
85
|
+
|
|
86
|
+
### Compatibility
|
|
87
|
+
- Gateway and clients now compare:
|
|
88
|
+
- package version
|
|
89
|
+
- protocol version
|
|
90
|
+
- capabilities
|
|
91
|
+
- `TOOLS.md` sync now detects outdated or missing local instructions and asks the session to refresh them.
|
|
92
|
+
|
|
93
|
+
### Removed
|
|
94
|
+
- Legacy Go/HTTP tmux proxy path was removed.
|
|
95
|
+
- Direct product path is now local `tmux` only.
|
|
96
|
+
|
|
97
|
+
## Next entry template
|
|
98
|
+
|
|
99
|
+
Copy this block for the next published version:
|
|
100
|
+
|
|
101
|
+
```md
|
|
102
|
+
## x.y.z
|
|
103
|
+
|
|
104
|
+
### Added
|
|
105
|
+
- ...
|
|
106
|
+
|
|
107
|
+
### Changed
|
|
108
|
+
- ...
|
|
109
|
+
|
|
110
|
+
### Fixed
|
|
111
|
+
- ...
|
|
112
|
+
|
|
113
|
+
### Removed
|
|
114
|
+
- ...
|
|
115
|
+
```
|
package/dist/cli.js
CHANGED
|
@@ -11,8 +11,10 @@ const node_net_1 = __importDefault(require("node:net"));
|
|
|
11
11
|
const dotenv_1 = require("dotenv");
|
|
12
12
|
const picocolors_1 = __importDefault(require("picocolors"));
|
|
13
13
|
const ws_1 = __importDefault(require("ws"));
|
|
14
|
+
const versionHandshake_1 = require("./services/features/telegram-mcp/src/shared/lib/version/versionHandshake");
|
|
14
15
|
const distDir = __dirname;
|
|
15
16
|
const packageRoot = node_path_1.default.resolve(distDir, "..");
|
|
17
|
+
const cliPackageVersion = (0, versionHandshake_1.getTellyMcpPackageVersion)(__dirname);
|
|
16
18
|
function getTmuxStatus() {
|
|
17
19
|
const result = (0, node_child_process_1.spawnSync)("tmux", ["-V"], {
|
|
18
20
|
encoding: "utf8",
|
|
@@ -27,7 +29,7 @@ function getTmuxStatus() {
|
|
|
27
29
|
return { found: false };
|
|
28
30
|
}
|
|
29
31
|
function printBanner(title, subtitle) {
|
|
30
|
-
process.stdout.write(`${picocolors_1.default.bold(picocolors_1.default.cyan("TellyMCP"))} ${picocolors_1.default.dim(title)}\n`);
|
|
32
|
+
process.stdout.write(`${picocolors_1.default.bold(picocolors_1.default.cyan("TellyMCP"))} ${picocolors_1.default.bold(picocolors_1.default.white(`v${cliPackageVersion}`))} ${picocolors_1.default.dim(title)}\n`);
|
|
31
33
|
if (subtitle) {
|
|
32
34
|
process.stdout.write(`${picocolors_1.default.dim(subtitle)}\n`);
|
|
33
35
|
}
|
|
@@ -666,6 +668,7 @@ function runRuntime(args) {
|
|
|
666
668
|
if (!(0, node_fs_1.existsSync)(servicesPath)) {
|
|
667
669
|
fail(`Missing compiled services: ${servicesPath}`);
|
|
668
670
|
}
|
|
671
|
+
printBanner("run", "Starting packaged runtime");
|
|
669
672
|
const tmux = getTmuxStatus();
|
|
670
673
|
if (tmux.found) {
|
|
671
674
|
process.stdout.write(`${picocolors_1.default.green("tmux detected:")} ${tmux.version}\n`);
|
|
@@ -554,15 +554,26 @@ const TelegramMcpGatewaySocketService = {
|
|
|
554
554
|
const action = typeof request.payload?.action === "string"
|
|
555
555
|
? request.payload.action
|
|
556
556
|
: "";
|
|
557
|
-
|
|
557
|
+
const text = typeof request.payload?.text === "string"
|
|
558
|
+
? request.payload.text
|
|
559
|
+
: "";
|
|
560
|
+
if (!["up", "down", "enter", "slash", "delete", "tab", "escape", "interrupt", "text"].includes(action)) {
|
|
558
561
|
throw new Error("Unsupported action");
|
|
559
562
|
}
|
|
563
|
+
if (action === "text" && (!text || text.length > 4000)) {
|
|
564
|
+
throw new Error("Text payload is required and must be <= 4000 characters");
|
|
565
|
+
}
|
|
560
566
|
const sessionId = request.local_session_id.trim();
|
|
561
567
|
const session = await runtime.sessionStore.getSession(sessionId);
|
|
562
568
|
if (!session?.tmuxTarget) {
|
|
563
569
|
throw new Error("tmux target is not configured for this session");
|
|
564
570
|
}
|
|
565
|
-
|
|
571
|
+
if (action === "text") {
|
|
572
|
+
await (0, tmux_1.sendTmuxLiteralText)(runtime.config.tmux, session.tmuxTarget, text);
|
|
573
|
+
}
|
|
574
|
+
else {
|
|
575
|
+
await (0, tmux_1.sendAllowedTmuxAction)(runtime.config.tmux, session.tmuxTarget, action);
|
|
576
|
+
}
|
|
566
577
|
return {
|
|
567
578
|
type: "live_response",
|
|
568
579
|
request_id: request.request_id,
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.TELEGRAM_MCP_RUNTIME_SERVICE_NAME = void 0;
|
|
4
4
|
const runtime_1 = require("./src/app/bootstrap/runtime");
|
|
5
|
+
const versionHandshake_1 = require("./src/shared/lib/version/versionHandshake");
|
|
5
6
|
exports.TELEGRAM_MCP_RUNTIME_SERVICE_NAME = "telegramMcp.runtime";
|
|
6
7
|
const TelegramMcpRuntimeService = {
|
|
7
8
|
name: exports.TELEGRAM_MCP_RUNTIME_SERVICE_NAME,
|
|
@@ -17,11 +18,17 @@ const TelegramMcpRuntimeService = {
|
|
|
17
18
|
},
|
|
18
19
|
},
|
|
19
20
|
async started() {
|
|
20
|
-
this.logger.info("Starting telegram_mcp runtime service"
|
|
21
|
+
this.logger.info("Starting telegram_mcp runtime service", {
|
|
22
|
+
packageVersion: (0, versionHandshake_1.getTellyMcpPackageVersion)(__dirname),
|
|
23
|
+
protocolVersion: versionHandshake_1.TELLYMCP_PROTOCOL_VERSION,
|
|
24
|
+
});
|
|
21
25
|
this.runtime = await (0, runtime_1.createAppRuntime)({
|
|
22
26
|
callBroker: (actionName, params, options) => this.broker.call(actionName, params, options),
|
|
23
27
|
});
|
|
24
|
-
this.logger.info("telegram_mcp runtime service is ready"
|
|
28
|
+
this.logger.info("telegram_mcp runtime service is ready", {
|
|
29
|
+
packageVersion: (0, versionHandshake_1.getTellyMcpPackageVersion)(__dirname),
|
|
30
|
+
protocolVersion: versionHandshake_1.TELLYMCP_PROTOCOL_VERSION,
|
|
31
|
+
});
|
|
25
32
|
},
|
|
26
33
|
async stopped() {
|
|
27
34
|
if (!this.runtime) {
|
|
@@ -77,6 +77,8 @@ async function createAppRuntime(input) {
|
|
|
77
77
|
logger.info("Telegram transport ready");
|
|
78
78
|
await telegramTransport.recoverPendingInboxNudges();
|
|
79
79
|
logger.info("Startup inbox nudge recovery completed");
|
|
80
|
+
await telegramTransport.sendStartupNotifications();
|
|
81
|
+
logger.info("Startup Telegram notifications completed");
|
|
80
82
|
const gatewayHttpService = new gatewayHttpService_1.GatewayHttpService(config, input.callBroker);
|
|
81
83
|
return {
|
|
82
84
|
config,
|
|
@@ -428,10 +428,19 @@ function createMcpHttpHandler(runtime, input) {
|
|
|
428
428
|
typeof Reflect.get(body, "action") === "string"
|
|
429
429
|
? String(Reflect.get(body, "action"))
|
|
430
430
|
: "";
|
|
431
|
-
|
|
431
|
+
const text = body &&
|
|
432
|
+
typeof body === "object" &&
|
|
433
|
+
typeof Reflect.get(body, "text") === "string"
|
|
434
|
+
? String(Reflect.get(body, "text"))
|
|
435
|
+
: "";
|
|
436
|
+
if (!["up", "down", "enter", "slash", "delete", "tab", "escape", "interrupt", "text"].includes(action)) {
|
|
432
437
|
writeText(res, 400, "Unsupported action");
|
|
433
438
|
return;
|
|
434
439
|
}
|
|
440
|
+
if (action === "text" && (!text || text.length > 4000)) {
|
|
441
|
+
writeText(res, 400, "Text payload is required and must be <= 4000 characters");
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
435
444
|
const nowMs = Date.now();
|
|
436
445
|
if (nowMs - webAppSession.lastActionAtMs <
|
|
437
446
|
runtime.config.webapp.actionCooldownMs) {
|
|
@@ -445,6 +454,7 @@ function createMcpHttpHandler(runtime, input) {
|
|
|
445
454
|
clientUuid: relayTarget.clientUuid,
|
|
446
455
|
localSessionId: relayTarget.localSessionId,
|
|
447
456
|
action: action,
|
|
457
|
+
...(action === "text" ? { text } : {}),
|
|
448
458
|
});
|
|
449
459
|
webAppSessions.touchAction(webAppSession.token, nowMs);
|
|
450
460
|
writeJson(res, 200, {
|
|
@@ -477,12 +487,18 @@ function createMcpHttpHandler(runtime, input) {
|
|
|
477
487
|
return;
|
|
478
488
|
}
|
|
479
489
|
try {
|
|
480
|
-
|
|
490
|
+
if (action === "text") {
|
|
491
|
+
await (0, tmux_1.sendTmuxLiteralText)(runtime.config.tmux, session.tmuxTarget, text);
|
|
492
|
+
}
|
|
493
|
+
else {
|
|
494
|
+
await (0, tmux_1.sendAllowedTmuxAction)(runtime.config.tmux, session.tmuxTarget, action);
|
|
495
|
+
}
|
|
481
496
|
webAppSessions.touchAction(webAppSession.token, nowMs);
|
|
482
497
|
runtime.logger.info("Telegram WebApp action sent to tmux", {
|
|
483
498
|
sessionId: webAppSession.sessionId,
|
|
484
499
|
telegramUserId: webAppSession.telegramUserId,
|
|
485
500
|
action,
|
|
501
|
+
...(action === "text" ? { textLength: text.length } : {}),
|
|
486
502
|
});
|
|
487
503
|
writeJson(res, 200, {
|
|
488
504
|
ok: true,
|
|
@@ -42,7 +42,7 @@ body {
|
|
|
42
42
|
bottom: calc(42px + env(safe-area-inset-bottom, 0px));
|
|
43
43
|
z-index: 30;
|
|
44
44
|
display: flex;
|
|
45
|
-
justify-content:
|
|
45
|
+
justify-content: flex-start;
|
|
46
46
|
flex-wrap: wrap;
|
|
47
47
|
gap: 8px;
|
|
48
48
|
padding: 10px 14px;
|
|
@@ -52,6 +52,11 @@ body {
|
|
|
52
52
|
backdrop-filter: blur(14px);
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
+
.toolbar-spacer {
|
|
56
|
+
flex: 1 1 auto;
|
|
57
|
+
min-width: 12px;
|
|
58
|
+
}
|
|
59
|
+
|
|
55
60
|
.btn {
|
|
56
61
|
appearance: none;
|
|
57
62
|
border: 1px solid var(--border);
|
|
@@ -73,6 +78,28 @@ body {
|
|
|
73
78
|
.btn:disabled { cursor: not-allowed; opacity: 0.55; }
|
|
74
79
|
.btn.danger:hover { border-color: var(--danger); }
|
|
75
80
|
|
|
81
|
+
.btn.danger {
|
|
82
|
+
border-color: rgba(255, 116, 116, 0.55);
|
|
83
|
+
background: linear-gradient(180deg, rgba(78, 18, 24, 0.96) 0%, rgba(50, 14, 18, 0.98) 100%);
|
|
84
|
+
color: #ffd7d7;
|
|
85
|
+
box-shadow: inset 0 0 0 1px rgba(255, 116, 116, 0.12);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.btn.danger:hover {
|
|
89
|
+
border-color: rgba(255, 116, 116, 0.9);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
.btn.primary {
|
|
93
|
+
border-color: rgba(87, 193, 255, 0.45);
|
|
94
|
+
background: linear-gradient(180deg, rgba(17, 45, 66, 0.96) 0%, rgba(12, 33, 48, 0.98) 100%);
|
|
95
|
+
color: #d9f3ff;
|
|
96
|
+
box-shadow: inset 0 0 0 1px rgba(87, 193, 255, 0.1);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.btn.primary:hover {
|
|
100
|
+
border-color: rgba(87, 193, 255, 0.8);
|
|
101
|
+
}
|
|
102
|
+
|
|
76
103
|
.statusbar {
|
|
77
104
|
position: fixed;
|
|
78
105
|
left: 0;
|
|
@@ -138,6 +165,11 @@ body {
|
|
|
138
165
|
padding: 8px 10px;
|
|
139
166
|
}
|
|
140
167
|
|
|
168
|
+
.toolbar-spacer {
|
|
169
|
+
flex: 1 1 auto;
|
|
170
|
+
min-width: 12px;
|
|
171
|
+
}
|
|
172
|
+
|
|
141
173
|
.btn.compact {
|
|
142
174
|
min-width: 42px;
|
|
143
175
|
padding: 8px 10px;
|
|
@@ -169,6 +201,7 @@ const elements = {
|
|
|
169
201
|
status: document.querySelector("[data-role=status]"),
|
|
170
202
|
updated: document.querySelector("[data-role=updated]"),
|
|
171
203
|
interrupt: document.querySelector("[data-role=interrupt]"),
|
|
204
|
+
type: document.querySelector("[data-role=type]"),
|
|
172
205
|
esc: document.querySelector("[data-role=escape]"),
|
|
173
206
|
tab: document.querySelector("[data-role=tab]"),
|
|
174
207
|
slash: document.querySelector("[data-role=slash]"),
|
|
@@ -571,6 +604,47 @@ async function sendAction(action) {
|
|
|
571
604
|
}
|
|
572
605
|
}
|
|
573
606
|
|
|
607
|
+
async function sendTextInput(text) {
|
|
608
|
+
if (state.actionBusy || !state.token) {
|
|
609
|
+
return;
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
state.actionBusy = true;
|
|
613
|
+
try {
|
|
614
|
+
const response = await fetch(config.basePath + "/api/action", {
|
|
615
|
+
method: "POST",
|
|
616
|
+
headers: {
|
|
617
|
+
"content-type": "application/json",
|
|
618
|
+
authorization: "Bearer " + state.token,
|
|
619
|
+
},
|
|
620
|
+
body: JSON.stringify({ action: "text", text }),
|
|
621
|
+
});
|
|
622
|
+
|
|
623
|
+
if (!response.ok) {
|
|
624
|
+
const text = await response.text();
|
|
625
|
+
throw new Error(text || "Failed to send text.");
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
setStatus("Text sent");
|
|
629
|
+
await refreshVisibleBuffer();
|
|
630
|
+
} finally {
|
|
631
|
+
state.actionBusy = false;
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
function confirmInterrupt() {
|
|
636
|
+
return new Promise((resolve) => {
|
|
637
|
+
if (tg && typeof tg.showConfirm === "function") {
|
|
638
|
+
tg.showConfirm("Send Ctrl+C to the tmux session? This can stop the running agent.", (ok) => {
|
|
639
|
+
resolve(Boolean(ok));
|
|
640
|
+
});
|
|
641
|
+
return;
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
resolve(window.confirm("Send Ctrl+C to the tmux session? This can stop the running agent."));
|
|
645
|
+
});
|
|
646
|
+
}
|
|
647
|
+
|
|
574
648
|
async function refreshVisibleBuffer() {
|
|
575
649
|
const payload = await fetchVisibleBuffer();
|
|
576
650
|
elements.terminal.innerHTML = renderAnsiToHtml(payload.content || "");
|
|
@@ -599,7 +673,22 @@ function startPolling() {
|
|
|
599
673
|
|
|
600
674
|
function bindUi() {
|
|
601
675
|
elements.interrupt.addEventListener("click", () => {
|
|
602
|
-
|
|
676
|
+
confirmInterrupt()
|
|
677
|
+
.then((ok) => {
|
|
678
|
+
if (!ok) {
|
|
679
|
+
return;
|
|
680
|
+
}
|
|
681
|
+
return sendAction("interrupt");
|
|
682
|
+
})
|
|
683
|
+
.catch((error) => setStatus(error.message || String(error), true));
|
|
684
|
+
});
|
|
685
|
+
|
|
686
|
+
elements.type.addEventListener("click", () => {
|
|
687
|
+
const value = window.prompt("Send text to tmux without Enter:", "");
|
|
688
|
+
if (value === null || value.length === 0) {
|
|
689
|
+
return;
|
|
690
|
+
}
|
|
691
|
+
sendTextInput(value).catch((error) => setStatus(error.message || String(error), true));
|
|
603
692
|
});
|
|
604
693
|
|
|
605
694
|
elements.esc.addEventListener("click", () => {
|
|
@@ -670,6 +759,7 @@ async function main() {
|
|
|
670
759
|
|
|
671
760
|
if (!bootstrapPayload.tmux_target) {
|
|
672
761
|
elements.interrupt.disabled = true;
|
|
762
|
+
elements.type.disabled = true;
|
|
673
763
|
elements.esc.disabled = true;
|
|
674
764
|
elements.tab.disabled = true;
|
|
675
765
|
elements.slash.disabled = true;
|
|
@@ -710,14 +800,16 @@ function renderWebAppHtml(input) {
|
|
|
710
800
|
<body>
|
|
711
801
|
<div class="app">
|
|
712
802
|
<div class="toolbar">
|
|
713
|
-
<button class="btn compact danger" data-role="interrupt" type="button">Ctrl+C</button>
|
|
714
|
-
<button class="btn compact" data-role="escape" type="button">Esc</button>
|
|
715
|
-
<button class="btn compact" data-role="tab" type="button">Tab</button>
|
|
716
803
|
<button class="btn compact" data-role="slash" type="button">/</button>
|
|
717
|
-
<button class="btn compact" data-role="delete" type="button">⌫</button>
|
|
718
804
|
<button class="btn compact" data-role="up" type="button">↑</button>
|
|
719
805
|
<button class="btn compact" data-role="down" type="button">↓</button>
|
|
720
|
-
<button class="btn compact" data-role="enter" type="button"
|
|
806
|
+
<button class="btn compact primary" data-role="enter" type="button">Enter</button>
|
|
807
|
+
<button class="btn compact" data-role="delete" type="button">⌫</button>
|
|
808
|
+
<button class="btn compact" data-role="type" type="button" title="Type text">🔤</button>
|
|
809
|
+
<button class="btn compact" data-role="tab" type="button">Tab</button>
|
|
810
|
+
<button class="btn compact" data-role="escape" type="button">Esc</button>
|
|
811
|
+
<span class="toolbar-spacer" aria-hidden="true"></span>
|
|
812
|
+
<button class="btn compact danger" data-role="interrupt" type="button">Ctrl+C</button>
|
|
721
813
|
</div>
|
|
722
814
|
<pre class="terminal" data-role="terminal">Waiting for tmux buffer…</pre>
|
|
723
815
|
<div class="statusbar">
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.sendAllowedTmuxAction = exports.isTmuxUnavailableError = exports.getTmuxWindowHeight = exports.captureVisibleTmuxPane = exports.captureTmuxPaneRange = void 0;
|
|
3
|
+
exports.sendTmuxLiteralText = exports.sendAllowedTmuxAction = exports.isTmuxUnavailableError = exports.getTmuxWindowHeight = exports.captureVisibleTmuxPane = exports.captureTmuxPaneRange = void 0;
|
|
4
4
|
var client_1 = require("../../shared/integrations/tmux/client");
|
|
5
5
|
Object.defineProperty(exports, "captureTmuxPaneRange", { enumerable: true, get: function () { return client_1.captureTmuxPaneRange; } });
|
|
6
6
|
Object.defineProperty(exports, "captureVisibleTmuxPane", { enumerable: true, get: function () { return client_1.captureVisibleTmuxPane; } });
|
|
7
7
|
Object.defineProperty(exports, "getTmuxWindowHeight", { enumerable: true, get: function () { return client_1.getTmuxWindowHeight; } });
|
|
8
8
|
Object.defineProperty(exports, "isTmuxUnavailableError", { enumerable: true, get: function () { return client_1.isTmuxUnavailableError; } });
|
|
9
9
|
Object.defineProperty(exports, "sendAllowedTmuxAction", { enumerable: true, get: function () { return client_1.sendAllowedTmuxAction; } });
|
|
10
|
+
Object.defineProperty(exports, "sendTmuxLiteralText", { enumerable: true, get: function () { return client_1.sendTmuxLiteralText; } });
|
|
@@ -173,6 +173,7 @@ class GatewayHttpService {
|
|
|
173
173
|
requestType: "action",
|
|
174
174
|
payload: {
|
|
175
175
|
action: input.action,
|
|
176
|
+
...(input.action === "text" ? { text: input.text ?? "" } : {}),
|
|
176
177
|
},
|
|
177
178
|
}, { meta: { internal_call: true } });
|
|
178
179
|
const response = unwrapLiveRelayResult(rawResponse);
|
|
@@ -17,6 +17,7 @@ const collabSemantics_1 = require("./collabSemantics");
|
|
|
17
17
|
const collabUi_1 = require("./collabUi");
|
|
18
18
|
const proxyFetch_1 = require("./proxyFetch");
|
|
19
19
|
const client_1 = require("../tmux/client");
|
|
20
|
+
const versionHandshake_1 = require("../../lib/version/versionHandshake");
|
|
20
21
|
const LOCAL_INDEX_FILE_NAME = "LOCAL_INDEX.md";
|
|
21
22
|
function trimTrailingSlashes(value) {
|
|
22
23
|
return value.replace(/\/+$/u, "");
|
|
@@ -25,6 +26,14 @@ function normalizeBasePath(value) {
|
|
|
25
26
|
const trimmed = trimTrailingSlashes(value.trim());
|
|
26
27
|
return trimmed.startsWith("/") ? trimmed || "/" : `/${trimmed || ""}`;
|
|
27
28
|
}
|
|
29
|
+
function joinHttpPath(prefix, suffix) {
|
|
30
|
+
const normalizedPrefix = prefix ? normalizeBasePath(prefix) : "";
|
|
31
|
+
const normalizedSuffix = normalizeBasePath(suffix);
|
|
32
|
+
if (!normalizedPrefix || normalizedPrefix === "/") {
|
|
33
|
+
return normalizedSuffix;
|
|
34
|
+
}
|
|
35
|
+
return `${normalizedPrefix}${normalizedSuffix}`.replace(/\/{2,}/gu, "/");
|
|
36
|
+
}
|
|
28
37
|
function resolveWebAppPublicBaseUrl(config) {
|
|
29
38
|
if (!config.webapp.publicUrl) {
|
|
30
39
|
return null;
|
|
@@ -755,6 +764,92 @@ class TelegramTransport {
|
|
|
755
764
|
recoveredSessions: recoveredCount,
|
|
756
765
|
});
|
|
757
766
|
}
|
|
767
|
+
async sendStartupNotifications() {
|
|
768
|
+
const packageVersion = (0, versionHandshake_1.getTellyMcpPackageVersion)(__dirname);
|
|
769
|
+
const sessions = await this.sessionStore.listSessions();
|
|
770
|
+
const groupedRecipients = new Map();
|
|
771
|
+
for (const session of sessions) {
|
|
772
|
+
const binding = await this.bindingStore.getBinding(session.sessionId);
|
|
773
|
+
if (!binding) {
|
|
774
|
+
continue;
|
|
775
|
+
}
|
|
776
|
+
const key = `${binding.telegramChatId}:${binding.telegramUserId}`;
|
|
777
|
+
const current = groupedRecipients.get(key);
|
|
778
|
+
if (current) {
|
|
779
|
+
current.sessionIds.push(session.sessionId);
|
|
780
|
+
current.sessionLabels.push(session.label ?? session.sessionId);
|
|
781
|
+
continue;
|
|
782
|
+
}
|
|
783
|
+
groupedRecipients.set(key, {
|
|
784
|
+
binding: {
|
|
785
|
+
telegramChatId: binding.telegramChatId,
|
|
786
|
+
telegramUserId: binding.telegramUserId,
|
|
787
|
+
},
|
|
788
|
+
sessionIds: [session.sessionId],
|
|
789
|
+
sessionLabels: [session.label ?? session.sessionId],
|
|
790
|
+
});
|
|
791
|
+
}
|
|
792
|
+
if (groupedRecipients.size === 0) {
|
|
793
|
+
this.logger.info("Skipping startup notifications because no Telegram sessions are paired");
|
|
794
|
+
return;
|
|
795
|
+
}
|
|
796
|
+
const runtimePort = this.config.distributed.mode === "gateway" || this.config.distributed.mode === "both"
|
|
797
|
+
? Number(process.env.PORT || this.config.mcp.httpPort)
|
|
798
|
+
: this.config.mcp.httpPort;
|
|
799
|
+
const rootPrefix = this.config.distributed.mode === "gateway" || this.config.distributed.mode === "both"
|
|
800
|
+
? normalizeBasePath(process.env.ROOT_PREFIX || "/api")
|
|
801
|
+
: "";
|
|
802
|
+
const localMcpPath = this.config.distributed.mode === "gateway" || this.config.distributed.mode === "both"
|
|
803
|
+
? joinHttpPath(rootPrefix, this.config.mcp.httpPath)
|
|
804
|
+
: this.config.mcp.httpPath;
|
|
805
|
+
const localWebappPath = this.config.distributed.mode === "gateway" || this.config.distributed.mode === "both"
|
|
806
|
+
? joinHttpPath(rootPrefix, this.config.webapp.basePath)
|
|
807
|
+
: this.config.webapp.basePath;
|
|
808
|
+
const localMcpUrl = `http://${this.config.mcp.httpHost}:${runtimePort}${localMcpPath}`;
|
|
809
|
+
const localWebappUrl = `http://${this.config.mcp.httpHost}:${runtimePort}${localWebappPath}`;
|
|
810
|
+
for (const recipientGroup of groupedRecipients.values()) {
|
|
811
|
+
const primarySessionId = recipientGroup.sessionIds[0];
|
|
812
|
+
if (!primarySessionId) {
|
|
813
|
+
continue;
|
|
814
|
+
}
|
|
815
|
+
const uniqueSessionLabels = Array.from(new Set(recipientGroup.sessionLabels)).sort();
|
|
816
|
+
const startupMessage = [
|
|
817
|
+
"✅ TellyMCP запущен.",
|
|
818
|
+
`Версия: ${packageVersion}`,
|
|
819
|
+
`Протокол: ${versionHandshake_1.TELLYMCP_PROTOCOL_VERSION}`,
|
|
820
|
+
`Режим: ${this.config.distributed.mode}`,
|
|
821
|
+
...(this.config.telegram.botUsername
|
|
822
|
+
? [`Бот: @${this.config.telegram.botUsername.replace(/^@/u, "")}`]
|
|
823
|
+
: []),
|
|
824
|
+
`Сессии: ${uniqueSessionLabels.join(", ")}`,
|
|
825
|
+
`MCP: ${localMcpUrl}`,
|
|
826
|
+
...(this.config.webapp.enabled ? [`WebApp: ${localWebappUrl}`] : []),
|
|
827
|
+
...(this.config.distributed.gatewayPublicUrl
|
|
828
|
+
? [`Gateway: ${this.config.distributed.gatewayPublicUrl}`]
|
|
829
|
+
: []),
|
|
830
|
+
...(this.config.distributed.gatewayWsUrl
|
|
831
|
+
? [`Gateway WS: ${this.config.distributed.gatewayWsUrl}`]
|
|
832
|
+
: []),
|
|
833
|
+
`Browser: ${this.config.browser.enabled ? (this.config.browser.headless ? "enabled, headless" : "enabled, headed") : "disabled"}`,
|
|
834
|
+
"Напиши /menu, чтобы открыть меню сессий.",
|
|
835
|
+
].join("\n");
|
|
836
|
+
try {
|
|
837
|
+
await this.sendNotification({
|
|
838
|
+
sessionId: primarySessionId,
|
|
839
|
+
sessionLabel: "TellyMCP",
|
|
840
|
+
recipient: recipientGroup.binding,
|
|
841
|
+
message: startupMessage,
|
|
842
|
+
});
|
|
843
|
+
}
|
|
844
|
+
catch (error) {
|
|
845
|
+
this.logger.warn("Failed to deliver Telegram startup notification", {
|
|
846
|
+
telegramChatId: recipientGroup.binding.telegramChatId,
|
|
847
|
+
telegramUserId: recipientGroup.binding.telegramUserId,
|
|
848
|
+
error: error instanceof Error ? error.message : String(error),
|
|
849
|
+
});
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
}
|
|
758
853
|
async sendRequest(input) {
|
|
759
854
|
const text = (0, messageFormat_1.formatTelegramMessage)(input, {
|
|
760
855
|
maxQuestionChars: this.config.telegram.maxQuestionChars,
|
|
@@ -14,6 +14,7 @@ exports.getTmuxWindowHeight = getTmuxWindowHeight;
|
|
|
14
14
|
exports.captureTmuxPaneRange = captureTmuxPaneRange;
|
|
15
15
|
exports.captureVisibleTmuxPane = captureVisibleTmuxPane;
|
|
16
16
|
exports.sendAllowedTmuxAction = sendAllowedTmuxAction;
|
|
17
|
+
exports.sendTmuxLiteralText = sendTmuxLiteralText;
|
|
17
18
|
exports.sendTmuxLiteralLine = sendTmuxLiteralLine;
|
|
18
19
|
const node_child_process_1 = require("node:child_process");
|
|
19
20
|
const promises_1 = require("node:fs/promises");
|
|
@@ -258,8 +259,8 @@ async function sendAllowedTmuxAction(config, target, action) {
|
|
|
258
259
|
: "Enter";
|
|
259
260
|
await execFileAsync("tmux", buildTmuxArgs(config, ["send-keys", "-t", target, key]));
|
|
260
261
|
}
|
|
261
|
-
async function
|
|
262
|
-
const normalized = text.replace(/\r?\n/g, " ")
|
|
262
|
+
async function sendTmuxLiteralText(config, target, text) {
|
|
263
|
+
const normalized = text.replace(/\r?\n/g, " ");
|
|
263
264
|
const bufferName = `telegram-mcp-${Date.now().toString(36)}`;
|
|
264
265
|
if (normalized.length > 0) {
|
|
265
266
|
try {
|
|
@@ -276,6 +277,11 @@ async function sendTmuxLiteralLine(config, target, text) {
|
|
|
276
277
|
finally {
|
|
277
278
|
await execFileAsync("tmux", buildTmuxArgs(config, ["delete-buffer", "-b", bufferName])).catch(() => undefined);
|
|
278
279
|
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
async function sendTmuxLiteralLine(config, target, text) {
|
|
283
|
+
await sendTmuxLiteralText(config, target, text);
|
|
284
|
+
if (text.length > 0) {
|
|
279
285
|
await delay(ENTER_AFTER_PASTE_DELAY_MS);
|
|
280
286
|
}
|
|
281
287
|
await execFileAsync("tmux", buildTmuxArgs(config, ["send-keys", "-t", target, SUBMIT_LINE_KEY]));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deadragdoll/tellymcp",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.5",
|
|
4
4
|
"description": "TellyMCP - Telegram Human-in-the-Loop MCP Server",
|
|
5
5
|
"main": "dist/services/features/telegram-mcp/runtime.service.js",
|
|
6
6
|
"bin": {
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
"README-ru.md",
|
|
15
15
|
"STANDALONE.md",
|
|
16
16
|
"STANDALONE-ru.md",
|
|
17
|
+
"VERSION.md",
|
|
17
18
|
"CHANGELOG.md",
|
|
18
19
|
"TOOLS.md",
|
|
19
20
|
".env.example.client",
|