@deadragdoll/tellymcp 0.0.12 → 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 +19 -0
- package/README.md +19 -0
- package/TOOLS.md +206 -3
- package/dist/cli.js +109 -1
- package/dist/services/features/telegram-mcp/browser.service.js +38 -1
- 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 +147 -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/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 +485 -1
- 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/shared/integrations/redis/stateStore.js +28 -0
- 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
|
@@ -4,6 +4,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.BrowserService = void 0;
|
|
7
|
+
const promises_1 = require("node:fs/promises");
|
|
7
8
|
const node_path_1 = __importDefault(require("node:path"));
|
|
8
9
|
const gatewayClientAccess_1 = require("../../distributed-client/model/gatewayClientAccess");
|
|
9
10
|
function pushBounded(list, entry, max) {
|
|
@@ -60,10 +61,11 @@ class BrowserService {
|
|
|
60
61
|
logger;
|
|
61
62
|
projectIdentityResolver;
|
|
62
63
|
remoteConsoleInvoker;
|
|
64
|
+
firefoxAttachRegistry;
|
|
63
65
|
playwrightModulePromise;
|
|
64
66
|
browserPromise;
|
|
65
67
|
sessionStates = new Map();
|
|
66
|
-
constructor(config, sessionStore, maintenanceStore, bindingStore, xchangeFileMetaStore, objectStore, telegramTransport, logger, projectIdentityResolver, remoteConsoleInvoker) {
|
|
68
|
+
constructor(config, sessionStore, maintenanceStore, bindingStore, xchangeFileMetaStore, objectStore, telegramTransport, logger, projectIdentityResolver, remoteConsoleInvoker, firefoxAttachRegistry) {
|
|
67
69
|
this.config = config;
|
|
68
70
|
this.sessionStore = sessionStore;
|
|
69
71
|
this.maintenanceStore = maintenanceStore;
|
|
@@ -74,6 +76,173 @@ class BrowserService {
|
|
|
74
76
|
this.logger = logger;
|
|
75
77
|
this.projectIdentityResolver = projectIdentityResolver;
|
|
76
78
|
this.remoteConsoleInvoker = remoteConsoleInvoker;
|
|
79
|
+
this.firefoxAttachRegistry = firefoxAttachRegistry;
|
|
80
|
+
}
|
|
81
|
+
async listAttachedInstances(input) {
|
|
82
|
+
const resolvedSessionId = await this.resolveOptionalSessionIdForRemote(input);
|
|
83
|
+
if (resolvedSessionId) {
|
|
84
|
+
const remote = await this.invokeRemote(resolvedSessionId, "telegramMcp.browser.listAttachedInstancesRemote", {
|
|
85
|
+
...input,
|
|
86
|
+
session_id: resolvedSessionId,
|
|
87
|
+
});
|
|
88
|
+
if (remote) {
|
|
89
|
+
return remote;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
const instances = this.listFirefoxAttachInstances();
|
|
93
|
+
return {
|
|
94
|
+
...(resolvedSessionId ? { session_id: resolvedSessionId } : {}),
|
|
95
|
+
total: instances.length,
|
|
96
|
+
instances: instances.map((instance) => ({
|
|
97
|
+
instance_id: instance.instanceId,
|
|
98
|
+
browser: instance.browser,
|
|
99
|
+
extension_version: instance.extensionVersion,
|
|
100
|
+
...(instance.profileName ? { profile_name: instance.profileName } : {}),
|
|
101
|
+
connected_at: instance.connectedAt,
|
|
102
|
+
last_seen_at: instance.lastSeenAt,
|
|
103
|
+
capabilities: [...instance.capabilities],
|
|
104
|
+
tab_count: instance.tabs.length,
|
|
105
|
+
...(instance.activeTab
|
|
106
|
+
? {
|
|
107
|
+
active_tab: {
|
|
108
|
+
tab_id: instance.activeTab.tab_id,
|
|
109
|
+
...(typeof instance.activeTab.window_id === "number"
|
|
110
|
+
? { window_id: instance.activeTab.window_id }
|
|
111
|
+
: {}),
|
|
112
|
+
active: instance.activeTab.active,
|
|
113
|
+
title: instance.activeTab.title,
|
|
114
|
+
url: instance.activeTab.url,
|
|
115
|
+
...(instance.activeTab.status
|
|
116
|
+
? { status: instance.activeTab.status }
|
|
117
|
+
: {}),
|
|
118
|
+
},
|
|
119
|
+
}
|
|
120
|
+
: {}),
|
|
121
|
+
})),
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
async listTabs(input) {
|
|
125
|
+
const resolvedSessionId = await this.resolveOptionalSessionIdForRemote(input);
|
|
126
|
+
if (resolvedSessionId) {
|
|
127
|
+
const remote = await this.invokeRemote(resolvedSessionId, "telegramMcp.browser.listTabsRemote", {
|
|
128
|
+
...input,
|
|
129
|
+
session_id: resolvedSessionId,
|
|
130
|
+
});
|
|
131
|
+
if (remote) {
|
|
132
|
+
return remote;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
const instances = this.listFirefoxAttachInstances();
|
|
136
|
+
const instance = input.instance_id?.trim()
|
|
137
|
+
? instances.find((item) => item.instanceId === input.instance_id?.trim())
|
|
138
|
+
: instances.length === 1
|
|
139
|
+
? instances[0]
|
|
140
|
+
: undefined;
|
|
141
|
+
if (!instance) {
|
|
142
|
+
if (input.instance_id?.trim()) {
|
|
143
|
+
throw new Error(`Attached browser instance '${input.instance_id.trim()}' was not found.`);
|
|
144
|
+
}
|
|
145
|
+
throw new Error("Attached browser instance is ambiguous. Pass instance_id or keep exactly one connected browser instance.");
|
|
146
|
+
}
|
|
147
|
+
const selectedAttachment = resolvedSessionId
|
|
148
|
+
? await this.getAttachedBrowserAttachment(resolvedSessionId)
|
|
149
|
+
: null;
|
|
150
|
+
return {
|
|
151
|
+
...(resolvedSessionId ? { session_id: resolvedSessionId } : {}),
|
|
152
|
+
instance_id: instance.instanceId,
|
|
153
|
+
total: instance.tabs.length,
|
|
154
|
+
tabs: instance.tabs.map((tab) => ({
|
|
155
|
+
tab_id: tab.tab_id,
|
|
156
|
+
...(typeof tab.window_id === "number" ? { window_id: tab.window_id } : {}),
|
|
157
|
+
active: tab.active,
|
|
158
|
+
...(selectedAttachment &&
|
|
159
|
+
selectedAttachment.instanceId === instance.instanceId &&
|
|
160
|
+
selectedAttachment.tabId === tab.tab_id
|
|
161
|
+
? { selected: true }
|
|
162
|
+
: {}),
|
|
163
|
+
title: tab.title,
|
|
164
|
+
url: tab.url,
|
|
165
|
+
...(tab.status ? { status: tab.status } : {}),
|
|
166
|
+
})),
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
async startRecording(input) {
|
|
170
|
+
const resolved = this.projectIdentityResolver.resolveSessionDefaults(input);
|
|
171
|
+
const normalizedSessionId = await this.normalizeSessionIdForAccess(resolved.sessionId);
|
|
172
|
+
const remote = await this.invokeRemote(normalizedSessionId, "telegramMcp.browser.startRecordingRemote", {
|
|
173
|
+
...input,
|
|
174
|
+
session_id: normalizedSessionId,
|
|
175
|
+
});
|
|
176
|
+
if (remote) {
|
|
177
|
+
return remote;
|
|
178
|
+
}
|
|
179
|
+
const attached = await this.getAttachedBrowserAttachment(normalizedSessionId);
|
|
180
|
+
if (!attached) {
|
|
181
|
+
throw new Error("No attached Firefox tab is selected for this session. Select a tab in the extension popup first.");
|
|
182
|
+
}
|
|
183
|
+
if (!this.firefoxAttachRegistry) {
|
|
184
|
+
throw new Error("Firefox attach backend is not available.");
|
|
185
|
+
}
|
|
186
|
+
const record = await this.firefoxAttachRegistry.startRecording({
|
|
187
|
+
sessionId: normalizedSessionId,
|
|
188
|
+
instanceId: input.instance_id?.trim() || attached.instanceId,
|
|
189
|
+
tabId: attached.tabId,
|
|
190
|
+
tabTitle: attached.title?.trim() || "attached-tab",
|
|
191
|
+
...(attached.url ? { tabUrl: attached.url } : {}),
|
|
192
|
+
});
|
|
193
|
+
return this.mapRecordingRecord(normalizedSessionId, record);
|
|
194
|
+
}
|
|
195
|
+
async stopRecording(input) {
|
|
196
|
+
const resolved = this.projectIdentityResolver.resolveSessionDefaults(input);
|
|
197
|
+
const normalizedSessionId = await this.normalizeSessionIdForAccess(resolved.sessionId);
|
|
198
|
+
const remote = await this.invokeRemote(normalizedSessionId, "telegramMcp.browser.stopRecordingRemote", {
|
|
199
|
+
...input,
|
|
200
|
+
session_id: normalizedSessionId,
|
|
201
|
+
});
|
|
202
|
+
if (remote) {
|
|
203
|
+
return remote;
|
|
204
|
+
}
|
|
205
|
+
if (!this.firefoxAttachRegistry) {
|
|
206
|
+
throw new Error("Firefox attach backend is not available.");
|
|
207
|
+
}
|
|
208
|
+
const record = await this.firefoxAttachRegistry.stopRecording({
|
|
209
|
+
sessionId: normalizedSessionId,
|
|
210
|
+
});
|
|
211
|
+
if (!record) {
|
|
212
|
+
return {
|
|
213
|
+
session_id: normalizedSessionId,
|
|
214
|
+
stopped: false,
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
return {
|
|
218
|
+
session_id: normalizedSessionId,
|
|
219
|
+
stopped: true,
|
|
220
|
+
recording_id: record.recordingId,
|
|
221
|
+
bundle_dir_name: record.bundleDirName,
|
|
222
|
+
bundle_relative_path: record.bundleRelativePath,
|
|
223
|
+
bundle_path: record.bundlePath,
|
|
224
|
+
...(record.stoppedAt ? { stopped_at: record.stoppedAt } : {}),
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
async getRecordingStatus(input) {
|
|
228
|
+
const resolved = this.projectIdentityResolver.resolveSessionDefaults(input);
|
|
229
|
+
const normalizedSessionId = await this.normalizeSessionIdForAccess(resolved.sessionId);
|
|
230
|
+
const remote = await this.invokeRemote(normalizedSessionId, "telegramMcp.browser.getRecordingStatusRemote", {
|
|
231
|
+
...input,
|
|
232
|
+
session_id: normalizedSessionId,
|
|
233
|
+
});
|
|
234
|
+
if (remote) {
|
|
235
|
+
return remote;
|
|
236
|
+
}
|
|
237
|
+
if (!this.firefoxAttachRegistry) {
|
|
238
|
+
throw new Error("Firefox attach backend is not available.");
|
|
239
|
+
}
|
|
240
|
+
const record = await this.firefoxAttachRegistry.getRecordingStatus(normalizedSessionId);
|
|
241
|
+
return {
|
|
242
|
+
session_id: normalizedSessionId,
|
|
243
|
+
active: record?.status === "recording",
|
|
244
|
+
...(record ? { recording: this.mapRecordingStatusRecord(record) } : {}),
|
|
245
|
+
};
|
|
77
246
|
}
|
|
78
247
|
async open(input) {
|
|
79
248
|
const resolved = this.projectIdentityResolver.resolveSessionDefaults(input);
|
|
@@ -173,6 +342,22 @@ class BrowserService {
|
|
|
173
342
|
return remote;
|
|
174
343
|
}
|
|
175
344
|
this.ensureEnabled();
|
|
345
|
+
const attached = await this.getAttachedBrowserAttachment(normalizedSessionId);
|
|
346
|
+
if (attached) {
|
|
347
|
+
const result = await this.runAttachedTabAction(normalizedSessionId, attached, {
|
|
348
|
+
action: "click",
|
|
349
|
+
payload: this.buildAttachedLocatorPayload(input),
|
|
350
|
+
});
|
|
351
|
+
return {
|
|
352
|
+
session_id: normalizedSessionId,
|
|
353
|
+
clicked: true,
|
|
354
|
+
...(input.ai_tag ? { ai_tag: input.ai_tag } : {}),
|
|
355
|
+
...(input.selector ? { selector: input.selector } : {}),
|
|
356
|
+
...(input.text ? { text: input.text } : {}),
|
|
357
|
+
url: String(result?.url || attached.url || ""),
|
|
358
|
+
...(result?.title ? { title: String(result.title) } : {}),
|
|
359
|
+
};
|
|
360
|
+
}
|
|
176
361
|
const { sessionId, state } = await this.requireSessionState(input);
|
|
177
362
|
const locator = this.resolveLocator(state.page, input);
|
|
178
363
|
await locator.click({
|
|
@@ -202,6 +387,26 @@ class BrowserService {
|
|
|
202
387
|
return remote;
|
|
203
388
|
}
|
|
204
389
|
this.ensureEnabled();
|
|
390
|
+
const attached = await this.getAttachedBrowserAttachment(normalizedSessionId);
|
|
391
|
+
if (attached) {
|
|
392
|
+
const result = await this.runAttachedTabAction(normalizedSessionId, attached, {
|
|
393
|
+
action: "fill",
|
|
394
|
+
payload: {
|
|
395
|
+
...this.buildAttachedLocatorPayload(input),
|
|
396
|
+
value: input.value,
|
|
397
|
+
},
|
|
398
|
+
});
|
|
399
|
+
return {
|
|
400
|
+
session_id: normalizedSessionId,
|
|
401
|
+
filled: true,
|
|
402
|
+
...(input.ai_tag ? { ai_tag: input.ai_tag } : {}),
|
|
403
|
+
...(input.selector ? { selector: input.selector } : {}),
|
|
404
|
+
...(input.text ? { text: input.text } : {}),
|
|
405
|
+
value_length: input.value.length,
|
|
406
|
+
url: String(result?.url || attached.url || ""),
|
|
407
|
+
...(result?.title ? { title: String(result.title) } : {}),
|
|
408
|
+
};
|
|
409
|
+
}
|
|
205
410
|
const { sessionId, state } = await this.requireSessionState(input);
|
|
206
411
|
const locator = this.resolveLocator(state.page, input);
|
|
207
412
|
await locator.fill(input.value, {
|
|
@@ -232,6 +437,26 @@ class BrowserService {
|
|
|
232
437
|
return remote;
|
|
233
438
|
}
|
|
234
439
|
this.ensureEnabled();
|
|
440
|
+
const attached = await this.getAttachedBrowserAttachment(normalizedSessionId);
|
|
441
|
+
if (attached) {
|
|
442
|
+
const result = await this.runAttachedTabAction(normalizedSessionId, attached, {
|
|
443
|
+
action: "press",
|
|
444
|
+
payload: {
|
|
445
|
+
...this.buildAttachedLocatorPayload(input),
|
|
446
|
+
key: input.key,
|
|
447
|
+
},
|
|
448
|
+
});
|
|
449
|
+
return {
|
|
450
|
+
session_id: normalizedSessionId,
|
|
451
|
+
pressed: true,
|
|
452
|
+
key: input.key,
|
|
453
|
+
...(input.ai_tag ? { ai_tag: input.ai_tag } : {}),
|
|
454
|
+
...(input.selector ? { selector: input.selector } : {}),
|
|
455
|
+
...(input.text ? { text: input.text } : {}),
|
|
456
|
+
url: String(result?.url || attached.url || ""),
|
|
457
|
+
...(result?.title ? { title: String(result.title) } : {}),
|
|
458
|
+
};
|
|
459
|
+
}
|
|
235
460
|
const { sessionId, state } = await this.requireSessionState(input);
|
|
236
461
|
if (input.selector || input.text) {
|
|
237
462
|
const locator = this.resolveLocator(state.page, input);
|
|
@@ -256,6 +481,64 @@ class BrowserService {
|
|
|
256
481
|
...(state.title ? { title: state.title } : {}),
|
|
257
482
|
};
|
|
258
483
|
}
|
|
484
|
+
async injectScript(input) {
|
|
485
|
+
const resolved = this.projectIdentityResolver.resolveSessionDefaults(input);
|
|
486
|
+
const normalizedSessionId = await this.normalizeSessionIdForAccess(resolved.sessionId);
|
|
487
|
+
const remote = await this.invokeRemote(normalizedSessionId, "telegramMcp.browser.injectScriptRemote", {
|
|
488
|
+
...input,
|
|
489
|
+
session_id: normalizedSessionId,
|
|
490
|
+
});
|
|
491
|
+
if (remote) {
|
|
492
|
+
return remote;
|
|
493
|
+
}
|
|
494
|
+
this.ensureEnabled();
|
|
495
|
+
const namespace = input.namespace?.trim() || "TELLY";
|
|
496
|
+
const source = input.source?.trim().length
|
|
497
|
+
? input.source
|
|
498
|
+
: input.file_path?.trim()
|
|
499
|
+
? await (0, promises_1.readFile)(node_path_1.default.resolve(input.file_path.trim()), "utf8")
|
|
500
|
+
: null;
|
|
501
|
+
if (!source) {
|
|
502
|
+
throw new Error("Provide source or file_path.");
|
|
503
|
+
}
|
|
504
|
+
const sourceType = input.source?.trim().length ? "inline" : "file";
|
|
505
|
+
const wrappedSource = this.wrapInjectedScript(source, namespace);
|
|
506
|
+
const attached = await this.getAttachedBrowserAttachment(normalizedSessionId);
|
|
507
|
+
if (attached) {
|
|
508
|
+
const result = await this.runAttachedTabAction(normalizedSessionId, attached, {
|
|
509
|
+
action: "inject_script",
|
|
510
|
+
payload: {
|
|
511
|
+
namespace,
|
|
512
|
+
source,
|
|
513
|
+
},
|
|
514
|
+
});
|
|
515
|
+
return {
|
|
516
|
+
session_id: normalizedSessionId,
|
|
517
|
+
injected: true,
|
|
518
|
+
namespace,
|
|
519
|
+
source_type: sourceType,
|
|
520
|
+
bytes: Buffer.byteLength(source, "utf8"),
|
|
521
|
+
url: String(result?.url || attached.url || ""),
|
|
522
|
+
...(result?.title ? { title: String(result.title) } : {}),
|
|
523
|
+
};
|
|
524
|
+
}
|
|
525
|
+
const { sessionId, state } = await this.requireSessionState(input);
|
|
526
|
+
await state.page.addScriptTag({
|
|
527
|
+
content: wrappedSource,
|
|
528
|
+
});
|
|
529
|
+
state.currentUrl = state.page.url();
|
|
530
|
+
state.title = await state.page.title().catch(() => state.title);
|
|
531
|
+
state.lastUsedAt = new Date().toISOString();
|
|
532
|
+
return {
|
|
533
|
+
session_id: sessionId,
|
|
534
|
+
injected: true,
|
|
535
|
+
namespace,
|
|
536
|
+
source_type: sourceType,
|
|
537
|
+
bytes: Buffer.byteLength(source, "utf8"),
|
|
538
|
+
url: state.currentUrl,
|
|
539
|
+
...(state.title ? { title: state.title } : {}),
|
|
540
|
+
};
|
|
541
|
+
}
|
|
259
542
|
async reload(input) {
|
|
260
543
|
const resolved = this.projectIdentityResolver.resolveSessionDefaults(input);
|
|
261
544
|
const normalizedSessionId = await this.normalizeSessionIdForAccess(resolved.sessionId);
|
|
@@ -453,6 +736,37 @@ class BrowserService {
|
|
|
453
736
|
return remote;
|
|
454
737
|
}
|
|
455
738
|
this.ensureEnabled();
|
|
739
|
+
const attached = await this.getAttachedBrowserAttachment(normalizedSessionId);
|
|
740
|
+
if (attached) {
|
|
741
|
+
const selector = input.selector?.trim() || "body";
|
|
742
|
+
const result = await this.runAttachedTabAction(normalizedSessionId, attached, {
|
|
743
|
+
action: "dom",
|
|
744
|
+
payload: {
|
|
745
|
+
selector,
|
|
746
|
+
include_html: input.include_html !== false,
|
|
747
|
+
include_text: input.include_text !== false,
|
|
748
|
+
},
|
|
749
|
+
});
|
|
750
|
+
return {
|
|
751
|
+
session_id: normalizedSessionId,
|
|
752
|
+
selector,
|
|
753
|
+
found: result?.found === true,
|
|
754
|
+
...(result?.url ? { url: String(result.url) } : {}),
|
|
755
|
+
...(result?.title ? { title: String(result.title) } : {}),
|
|
756
|
+
...(typeof result?.outer_html === "string"
|
|
757
|
+
? { outer_html: result.outer_html }
|
|
758
|
+
: {}),
|
|
759
|
+
...(typeof result?.text_content === "string"
|
|
760
|
+
? { text_content: result.text_content }
|
|
761
|
+
: {}),
|
|
762
|
+
...(typeof result?.visible === "boolean"
|
|
763
|
+
? { visible: result.visible }
|
|
764
|
+
: {}),
|
|
765
|
+
...(result?.attributes && typeof result.attributes === "object"
|
|
766
|
+
? { attributes: result.attributes }
|
|
767
|
+
: {}),
|
|
768
|
+
};
|
|
769
|
+
}
|
|
456
770
|
const { sessionId, state } = await this.requireSessionState(input);
|
|
457
771
|
const selector = input.selector?.trim() || "body";
|
|
458
772
|
const snapshot = await state.page
|
|
@@ -583,6 +897,75 @@ class BrowserService {
|
|
|
583
897
|
return remote;
|
|
584
898
|
}
|
|
585
899
|
this.ensureEnabled();
|
|
900
|
+
const attached = await this.getAttachedBrowserAttachment(normalizedSessionId);
|
|
901
|
+
if (attached) {
|
|
902
|
+
const session = await this.sessionStore.getSession(normalizedSessionId);
|
|
903
|
+
const fileName = sanitizeScreenshotName(input.file_name);
|
|
904
|
+
const result = await this.runAttachedTabAction(normalizedSessionId, attached, {
|
|
905
|
+
action: "screenshot",
|
|
906
|
+
});
|
|
907
|
+
const pngBase64 = typeof result?.png_base64 === "string" ? result.png_base64 : "";
|
|
908
|
+
if (!pngBase64) {
|
|
909
|
+
throw new Error("Attached browser screenshot did not return PNG data.");
|
|
910
|
+
}
|
|
911
|
+
const pngBuffer = Buffer.from(pngBase64, "base64");
|
|
912
|
+
const workspaceDir = this.objectStore.resolveWorkspaceDir(session);
|
|
913
|
+
const exchangeDir = node_path_1.default.resolve(workspaceDir, this.config.exchange.dir);
|
|
914
|
+
const storedFile = await this.objectStore.storeFile({
|
|
915
|
+
session,
|
|
916
|
+
sessionId: normalizedSessionId,
|
|
917
|
+
source: "browser-screenshot",
|
|
918
|
+
relativePath: buildDatedRelativePath(fileName),
|
|
919
|
+
content: pngBuffer,
|
|
920
|
+
mimeType: "image/png",
|
|
921
|
+
});
|
|
922
|
+
await this.xchangeFileMetaStore.setXchangeFileMeta({
|
|
923
|
+
sessionId: normalizedSessionId,
|
|
924
|
+
filePath: storedFile.filePath,
|
|
925
|
+
relativePath: storedFile.relativePath,
|
|
926
|
+
source: "browser-screenshot",
|
|
927
|
+
uploadedAt: new Date().toISOString(),
|
|
928
|
+
storageRef: storedFile.storageRef,
|
|
929
|
+
bucketName: storedFile.bucketName,
|
|
930
|
+
objectName: storedFile.objectName,
|
|
931
|
+
vfsNodeId: storedFile.vfsNodeId,
|
|
932
|
+
vfsPublicUrl: storedFile.vfsPublicUrl,
|
|
933
|
+
vfsParentId: storedFile.vfsParentId,
|
|
934
|
+
mimeType: "image/png",
|
|
935
|
+
sizeBytes: storedFile.sizeBytes,
|
|
936
|
+
...(input.caption ? { caption: input.caption } : {}),
|
|
937
|
+
});
|
|
938
|
+
let telegramMessageId;
|
|
939
|
+
if (input.send_to_telegram === true) {
|
|
940
|
+
if (this.config.distributed.mode === "client") {
|
|
941
|
+
telegramMessageId = await this.sendScreenshotToGatewayTelegramRoute({
|
|
942
|
+
sessionId: normalizedSessionId,
|
|
943
|
+
fileName,
|
|
944
|
+
pngBuffer,
|
|
945
|
+
...(input.caption ? { caption: input.caption } : {}),
|
|
946
|
+
});
|
|
947
|
+
}
|
|
948
|
+
else {
|
|
949
|
+
const binding = await this.bindingStore.getBinding(normalizedSessionId);
|
|
950
|
+
if (!binding) {
|
|
951
|
+
throw new Error("Session is not linked to Telegram, so screenshot cannot be sent there.");
|
|
952
|
+
}
|
|
953
|
+
const sent = await this.telegramTransport.sendDocumentToChat(binding.telegramChatId, storedFile.filePath, input.caption);
|
|
954
|
+
telegramMessageId = sent.messageId;
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
return {
|
|
958
|
+
session_id: normalizedSessionId,
|
|
959
|
+
file_path: storedFile.filePath,
|
|
960
|
+
workspace_dir: workspaceDir,
|
|
961
|
+
exchange_dir: exchangeDir,
|
|
962
|
+
...(typeof telegramMessageId === "number"
|
|
963
|
+
? { telegram_message_id: telegramMessageId }
|
|
964
|
+
: {}),
|
|
965
|
+
...(result?.url ? { url: String(result.url) } : {}),
|
|
966
|
+
...(result?.title ? { title: String(result.title) } : {}),
|
|
967
|
+
};
|
|
968
|
+
}
|
|
586
969
|
const { sessionId, state, session } = await this.requireSessionState(input);
|
|
587
970
|
const fileName = sanitizeScreenshotName(input.file_name);
|
|
588
971
|
const pngBuffer = input.selector?.trim()
|
|
@@ -875,6 +1258,107 @@ class BrowserService {
|
|
|
875
1258
|
const localSession = await this.sessionStore.getSession(localSessionId);
|
|
876
1259
|
return localSession ? localSessionId : trimmed;
|
|
877
1260
|
}
|
|
1261
|
+
async resolveOptionalSessionIdForRemote(input) {
|
|
1262
|
+
const trimmed = input.session_id?.trim();
|
|
1263
|
+
if (!trimmed) {
|
|
1264
|
+
return undefined;
|
|
1265
|
+
}
|
|
1266
|
+
return await this.normalizeSessionIdForAccess(trimmed);
|
|
1267
|
+
}
|
|
1268
|
+
listFirefoxAttachInstances() {
|
|
1269
|
+
if (!this.firefoxAttachRegistry) {
|
|
1270
|
+
return [];
|
|
1271
|
+
}
|
|
1272
|
+
return this.firefoxAttachRegistry.listInstances();
|
|
1273
|
+
}
|
|
1274
|
+
buildAttachedLocatorPayload(input) {
|
|
1275
|
+
return {
|
|
1276
|
+
...(input.ai_tag?.trim() ? { ai_tag: input.ai_tag.trim() } : {}),
|
|
1277
|
+
...(input.selector?.trim() ? { selector: input.selector.trim() } : {}),
|
|
1278
|
+
...(input.text?.trim() ? { text: input.text.trim() } : {}),
|
|
1279
|
+
...(typeof input.exact === "boolean" ? { exact: input.exact } : {}),
|
|
1280
|
+
};
|
|
1281
|
+
}
|
|
1282
|
+
async getAttachedBrowserAttachment(sessionId) {
|
|
1283
|
+
return await this.maintenanceStore.getBrowserAttachment(sessionId);
|
|
1284
|
+
}
|
|
1285
|
+
mapRecordingRecord(sessionId, record) {
|
|
1286
|
+
return {
|
|
1287
|
+
session_id: sessionId,
|
|
1288
|
+
started: true,
|
|
1289
|
+
backend: "firefox-attached",
|
|
1290
|
+
recording_id: record.recordingId,
|
|
1291
|
+
instance_id: record.instanceId,
|
|
1292
|
+
tab_id: record.tabId,
|
|
1293
|
+
...(record.tabTitle ? { tab_title: record.tabTitle } : {}),
|
|
1294
|
+
...(record.tabUrl ? { tab_url: record.tabUrl } : {}),
|
|
1295
|
+
bundle_dir_name: record.bundleDirName,
|
|
1296
|
+
bundle_relative_path: record.bundleRelativePath,
|
|
1297
|
+
bundle_path: record.bundlePath,
|
|
1298
|
+
started_at: record.startedAt,
|
|
1299
|
+
};
|
|
1300
|
+
}
|
|
1301
|
+
mapRecordingStatusRecord(record) {
|
|
1302
|
+
return {
|
|
1303
|
+
backend: "firefox-attached",
|
|
1304
|
+
recording_id: record.recordingId,
|
|
1305
|
+
instance_id: record.instanceId,
|
|
1306
|
+
tab_id: record.tabId,
|
|
1307
|
+
...(record.tabTitle ? { tab_title: record.tabTitle } : {}),
|
|
1308
|
+
...(record.tabUrl ? { tab_url: record.tabUrl } : {}),
|
|
1309
|
+
bundle_dir_name: record.bundleDirName,
|
|
1310
|
+
bundle_relative_path: record.bundleRelativePath,
|
|
1311
|
+
bundle_path: record.bundlePath,
|
|
1312
|
+
started_at: record.startedAt,
|
|
1313
|
+
...(record.stoppedAt ? { stopped_at: record.stoppedAt } : {}),
|
|
1314
|
+
status: record.status,
|
|
1315
|
+
event_count: record.eventCount,
|
|
1316
|
+
...(record.lastEventAt ? { last_event_at: record.lastEventAt } : {}),
|
|
1317
|
+
};
|
|
1318
|
+
}
|
|
1319
|
+
async runAttachedTabAction(sessionId, attachment, input) {
|
|
1320
|
+
if (!this.firefoxAttachRegistry) {
|
|
1321
|
+
throw new Error("Attached browser backend is not available.");
|
|
1322
|
+
}
|
|
1323
|
+
const result = await this.firefoxAttachRegistry.invokeTabAction({
|
|
1324
|
+
instanceId: attachment.instanceId,
|
|
1325
|
+
tabId: attachment.tabId,
|
|
1326
|
+
action: input.action,
|
|
1327
|
+
payload: input.payload,
|
|
1328
|
+
});
|
|
1329
|
+
await this.maintenanceStore.setBrowserAttachment({
|
|
1330
|
+
...attachment,
|
|
1331
|
+
...(result?.title ? { title: String(result.title) } : {}),
|
|
1332
|
+
...(result?.url ? { url: String(result.url) } : {}),
|
|
1333
|
+
});
|
|
1334
|
+
this.logger.info("Attached browser tab action executed", {
|
|
1335
|
+
sessionId,
|
|
1336
|
+
instanceId: attachment.instanceId,
|
|
1337
|
+
tabId: attachment.tabId,
|
|
1338
|
+
action: input.action,
|
|
1339
|
+
});
|
|
1340
|
+
return result;
|
|
1341
|
+
}
|
|
1342
|
+
wrapInjectedScript(source, namespace) {
|
|
1343
|
+
return `const __tellyNamespace = ${JSON.stringify(namespace)};
|
|
1344
|
+
window[__tellyNamespace] = window[__tellyNamespace] || {};
|
|
1345
|
+
var TELLY = window[__tellyNamespace];
|
|
1346
|
+
const __tellyBeforeKeys = new Set(Object.getOwnPropertyNames(window));
|
|
1347
|
+
${source}
|
|
1348
|
+
for (const __tellyKey of Object.getOwnPropertyNames(window)) {
|
|
1349
|
+
if (__tellyBeforeKeys.has(__tellyKey)) {
|
|
1350
|
+
continue;
|
|
1351
|
+
}
|
|
1352
|
+
if (__tellyKey === __tellyNamespace) {
|
|
1353
|
+
continue;
|
|
1354
|
+
}
|
|
1355
|
+
try {
|
|
1356
|
+
window[__tellyNamespace][__tellyKey] = window[__tellyKey];
|
|
1357
|
+
} catch {
|
|
1358
|
+
// ignore unassignable globals
|
|
1359
|
+
}
|
|
1360
|
+
}`;
|
|
1361
|
+
}
|
|
878
1362
|
async sendScreenshotToGatewayTelegramRoute(input) {
|
|
879
1363
|
if (!this.config.distributed.gatewayPublicUrl) {
|
|
880
1364
|
throw new Error("send_to_telegram on client nodes requires GATEWAY_PUBLIC_URL.");
|