@deadragdoll/tellymcp 0.0.13 → 0.0.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/.env.example.client +5 -1
  2. package/.env.example.gateway +16 -1
  3. package/CHANGELOG.md +30 -0
  4. package/README-ru.md +14 -0
  5. package/README.md +14 -0
  6. package/TOOLS.md +182 -10
  7. package/config/templates/env.both.template +15 -2
  8. package/config/templates/env.client.template +5 -2
  9. package/config/templates/env.gateway.template +15 -2
  10. package/dist/cli.js +28 -1
  11. package/dist/services/features/telegram-mcp/browser.service.js +18 -0
  12. package/dist/services/features/telegram-mcp/file-content.service.js +94 -0
  13. package/dist/services/features/telegram-mcp/gateway-delivery.service.js +5 -1
  14. package/dist/services/features/telegram-mcp/gateway-socket.service.js +43 -11
  15. package/dist/services/features/telegram-mcp/mcp-http.service.js +1 -0
  16. package/dist/services/features/telegram-mcp/mcp-server.service.js +18 -0
  17. package/dist/services/features/telegram-mcp/src/app/bootstrap/runtime.js +9 -1
  18. package/dist/services/features/telegram-mcp/src/app/config/env.js +119 -2
  19. package/dist/services/features/telegram-mcp/src/app/http.js +139 -99
  20. package/dist/services/features/telegram-mcp/src/app/oauthFacade.js +642 -0
  21. package/dist/services/features/telegram-mcp/src/app/webapp/assets.js +151 -170
  22. package/dist/services/features/telegram-mcp/src/app/webapp/auth.js +96 -99
  23. package/dist/services/features/telegram-mcp/src/entities/request/model/schema.js +62 -19
  24. package/dist/services/features/telegram-mcp/src/features/browser/model/browserAttachActiveTabTool.js +28 -0
  25. package/dist/services/features/telegram-mcp/src/features/browser/model/browserAttachTabTool.js +28 -0
  26. package/dist/services/features/telegram-mcp/src/features/browser/model/browserDetachTabTool.js +28 -0
  27. package/dist/services/features/telegram-mcp/src/features/browser/model/browserListAttachedInstancesTool.js +1 -1
  28. package/dist/services/features/telegram-mcp/src/features/browser/model/browserListTabsTool.js +1 -1
  29. package/dist/services/features/telegram-mcp/src/features/browser/model/browserService.js +410 -27
  30. package/dist/services/features/telegram-mcp/src/features/browser-attach/model/browserRecordingBundle.js +37 -3
  31. package/dist/services/features/telegram-mcp/src/features/browser-attach/model/firefoxAttachRegistry.js +7 -6
  32. package/dist/services/features/telegram-mcp/src/features/browser-attach/model/firefoxAttachServer.js +214 -37
  33. package/dist/services/features/telegram-mcp/src/features/browser-attach/model/types.js +186 -0
  34. package/dist/services/features/telegram-mcp/src/features/collaboration/model/collaborationService.js +2 -0
  35. package/dist/services/features/telegram-mcp/src/features/collaboration/model/sendPartnerFileService.js +6 -3
  36. package/dist/services/features/telegram-mcp/src/features/distributed-client/model/gatewayClientAccess.js +4 -1
  37. package/dist/services/features/telegram-mcp/src/features/distributed-gateway/model/gatewayHttpService.js +33 -35
  38. package/dist/services/features/telegram-mcp/src/features/file-content/model/getFileListTool.js +33 -0
  39. package/dist/services/features/telegram-mcp/src/features/file-content/model/getFileService.js +327 -0
  40. package/dist/services/features/telegram-mcp/src/features/file-content/model/getFileTool.js +81 -0
  41. package/dist/services/features/telegram-mcp/src/features/file-content/model/temporaryFileLinkStore.js +307 -0
  42. package/dist/services/features/telegram-mcp/src/features/file-content/model/workspaceFilePolicy.js +115 -0
  43. package/dist/services/features/telegram-mcp/src/features/notify/model/notifyService.js +5 -1
  44. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transport.js +3 -0
  45. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportFileHandoffActions.js +6 -3
  46. package/dist/services/features/telegram-mcp/src/shared/integrations/terminal/client.js +29 -6
  47. package/dist/services/features/telegram-mcp/src/shared/integrations/terminal/ptyRegistry.js +100 -2
  48. package/dist/services/features/telegram-mcp/src/shared/lib/bodyLimits.js +63 -0
  49. package/dist/services/features/telegram-mcp/src/shared/lib/gatewayAuth.js +13 -0
  50. package/dist/services/features/telegram-mcp/src/shared/lib/time/localTimestamp.js +21 -0
  51. package/docs/CHAT_CONNECTOR.md +134 -0
  52. package/docs/STANDALONE-ru.md +13 -0
  53. package/docs/STANDALONE.md +13 -0
  54. package/package.json +5 -3
  55. package/packages/chrome-attach-extension/dist/background.js +572 -163
  56. package/packages/chrome-attach-extension/dist/manifest.json +2 -1
  57. package/packages/chrome-attach-extension/dist/options.js +15 -2
  58. package/packages/chrome-attach-extension/dist/recorder-content.js +14 -1
  59. package/packages/chrome-attach-extension/dist/recorder-page.js +34 -18
  60. package/packages/firefox-attach-extension/dist/background.js +413 -33
  61. package/packages/firefox-attach-extension/dist/manifest.json +0 -12
  62. package/packages/firefox-attach-extension/dist/options.js +14 -1
  63. package/packages/firefox-attach-extension/dist/recorder-content.js +14 -1
  64. package/packages/firefox-attach-extension/dist/recorder-page.js +34 -18
@@ -6,6 +6,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.BrowserService = void 0;
7
7
  const promises_1 = require("node:fs/promises");
8
8
  const node_path_1 = __importDefault(require("node:path"));
9
+ const bodyLimits_1 = require("../../../shared/lib/bodyLimits");
10
+ const localTimestamp_1 = require("../../../shared/lib/time/localTimestamp");
9
11
  const gatewayClientAccess_1 = require("../../distributed-client/model/gatewayClientAccess");
10
12
  function pushBounded(list, entry, max) {
11
13
  list.push(entry);
@@ -22,7 +24,7 @@ function trimList(list, limit) {
22
24
  function sanitizeScreenshotName(fileName) {
23
25
  const trimmed = fileName?.trim();
24
26
  if (!trimmed) {
25
- const timestamp = new Date().toISOString().replace(/[:.]/gu, "-");
27
+ const timestamp = (0, localTimestamp_1.formatLocalTimestampForFileName)(new Date());
26
28
  return `browser-screenshot-${timestamp}.png`;
27
29
  }
28
30
  const parsed = node_path_1.default.parse(trimmed);
@@ -31,8 +33,8 @@ function sanitizeScreenshotName(fileName) {
31
33
  return `${base}${extension}`;
32
34
  }
33
35
  function buildDatedRelativePath(fileName, date = new Date()) {
34
- const dateSegment = date.toISOString().slice(0, 10);
35
- const timeSegment = date.toTimeString().slice(0, 8).replace(/:/gu, "-");
36
+ const dateSegment = (0, localTimestamp_1.formatLocalDateSegment)(date);
37
+ const timeSegment = (0, localTimestamp_1.formatLocalTimeSegment)(date);
36
38
  return `${dateSegment}/${timeSegment}/${fileName}`;
37
39
  }
38
40
  function isAbsoluteBrowserUrl(value) {
@@ -166,6 +168,137 @@ class BrowserService {
166
168
  })),
167
169
  };
168
170
  }
171
+ async attachActiveTab(input) {
172
+ const resolved = this.projectIdentityResolver.resolveSessionDefaults(input);
173
+ const normalizedSessionId = await this.normalizeSessionIdForAccess(resolved.sessionId);
174
+ const remote = await this.invokeRemote(normalizedSessionId, "telegramMcp.browser.attachActiveTabRemote", {
175
+ ...input,
176
+ session_id: normalizedSessionId,
177
+ });
178
+ if (remote) {
179
+ return remote;
180
+ }
181
+ const instances = this.listFirefoxAttachInstances();
182
+ const instance = input.instance_id?.trim()
183
+ ? instances.find((item) => item.instanceId === input.instance_id?.trim())
184
+ : instances.length === 1
185
+ ? instances[0]
186
+ : undefined;
187
+ if (!instance) {
188
+ if (input.instance_id?.trim()) {
189
+ throw new Error(`Attached browser instance '${input.instance_id.trim()}' was not found.`);
190
+ }
191
+ throw new Error("Attached browser instance is ambiguous. Pass instance_id or keep exactly one connected browser instance.");
192
+ }
193
+ const activeTab = instance.activeTab;
194
+ if (!activeTab) {
195
+ throw new Error(`Attached browser instance '${instance.instanceId}' does not expose an active tab.`);
196
+ }
197
+ return await this.attachTab({
198
+ session_id: normalizedSessionId,
199
+ instance_id: instance.instanceId,
200
+ tab_id: activeTab.tab_id,
201
+ });
202
+ }
203
+ async attachTab(input) {
204
+ const resolved = this.projectIdentityResolver.resolveSessionDefaults(input);
205
+ const normalizedSessionId = await this.normalizeSessionIdForAccess(resolved.sessionId);
206
+ const remote = await this.invokeRemote(normalizedSessionId, "telegramMcp.browser.attachTabRemote", {
207
+ ...input,
208
+ session_id: normalizedSessionId,
209
+ });
210
+ if (remote) {
211
+ return remote;
212
+ }
213
+ if (!this.firefoxAttachRegistry) {
214
+ throw new Error("Attached browser backend is not available.");
215
+ }
216
+ const instances = this.listFirefoxAttachInstances();
217
+ const instance = input.instance_id?.trim()
218
+ ? instances.find((item) => item.instanceId === input.instance_id?.trim())
219
+ : instances.length === 1
220
+ ? instances[0]
221
+ : undefined;
222
+ if (!instance) {
223
+ if (input.instance_id?.trim()) {
224
+ throw new Error(`Attached browser instance '${input.instance_id.trim()}' was not found.`);
225
+ }
226
+ throw new Error("Attached browser instance is ambiguous. Pass instance_id or keep exactly one connected browser instance.");
227
+ }
228
+ const tab = instance.tabs.find((item) => item.tab_id === input.tab_id);
229
+ if (!tab) {
230
+ throw new Error(`Tab '${input.tab_id}' was not found in attached browser instance '${instance.instanceId}'.`);
231
+ }
232
+ const result = await this.firefoxAttachRegistry.invokeTabAction({
233
+ instanceId: instance.instanceId,
234
+ tabId: tab.tab_id,
235
+ action: "attach",
236
+ });
237
+ const attachedAt = (0, localTimestamp_1.formatLocalTimestamp)(new Date());
238
+ await this.maintenanceStore.setBrowserAttachment({
239
+ sessionId: normalizedSessionId,
240
+ backend: "firefox-attached",
241
+ instanceId: instance.instanceId,
242
+ tabId: tab.tab_id,
243
+ attachedAt,
244
+ ...(result?.title
245
+ ? { title: String(result.title) }
246
+ : tab.title
247
+ ? { title: tab.title }
248
+ : {}),
249
+ ...(result?.url
250
+ ? { url: String(result.url) }
251
+ : tab.url
252
+ ? { url: tab.url }
253
+ : {}),
254
+ });
255
+ return {
256
+ session_id: normalizedSessionId,
257
+ backend: "firefox-attached",
258
+ instance_id: instance.instanceId,
259
+ tab_id: tab.tab_id,
260
+ attached_at: attachedAt,
261
+ ...(result?.title
262
+ ? { title: String(result.title) }
263
+ : tab.title
264
+ ? { title: tab.title }
265
+ : {}),
266
+ ...(result?.url
267
+ ? { url: String(result.url) }
268
+ : tab.url
269
+ ? { url: tab.url }
270
+ : {}),
271
+ };
272
+ }
273
+ async detachTab(input) {
274
+ const resolved = this.projectIdentityResolver.resolveSessionDefaults(input);
275
+ const normalizedSessionId = await this.normalizeSessionIdForAccess(resolved.sessionId);
276
+ const remote = await this.invokeRemote(normalizedSessionId, "telegramMcp.browser.detachTabRemote", {
277
+ ...input,
278
+ session_id: normalizedSessionId,
279
+ });
280
+ if (remote) {
281
+ return remote;
282
+ }
283
+ const attachment = await this.getAttachedBrowserAttachment(normalizedSessionId);
284
+ if (attachment && this.firefoxAttachRegistry) {
285
+ try {
286
+ await this.firefoxAttachRegistry.invokeTabAction({
287
+ instanceId: attachment.instanceId,
288
+ tabId: attachment.tabId,
289
+ action: "detach",
290
+ });
291
+ }
292
+ catch {
293
+ // Clearing local state is still the source of truth.
294
+ }
295
+ }
296
+ await this.maintenanceStore.clearBrowserAttachment(normalizedSessionId);
297
+ return {
298
+ session_id: normalizedSessionId,
299
+ detached: true,
300
+ };
301
+ }
169
302
  async startRecording(input) {
170
303
  const resolved = this.projectIdentityResolver.resolveSessionDefaults(input);
171
304
  const normalizedSessionId = await this.normalizeSessionIdForAccess(resolved.sessionId);
@@ -178,7 +311,7 @@ class BrowserService {
178
311
  }
179
312
  const attached = await this.getAttachedBrowserAttachment(normalizedSessionId);
180
313
  if (!attached) {
181
- throw new Error("No attached Firefox tab is selected for this session. Select a tab in the extension popup first.");
314
+ throw new Error("No attached browser tab is selected for this session. Select a tab in the browser attach panel first.");
182
315
  }
183
316
  if (!this.firefoxAttachRegistry) {
184
317
  throw new Error("Firefox attach backend is not available.");
@@ -284,7 +417,7 @@ class BrowserService {
284
417
  });
285
418
  state.currentUrl = state.page.url();
286
419
  state.title = await state.page.title();
287
- state.lastUsedAt = new Date().toISOString();
420
+ state.lastUsedAt = (0, localTimestamp_1.formatLocalTimestamp)(new Date());
288
421
  this.logger.info("Browser page opened", {
289
422
  sessionId: resolved.sessionId,
290
423
  normalizedSessionId,
@@ -318,8 +451,40 @@ class BrowserService {
318
451
  return remote;
319
452
  }
320
453
  this.ensureEnabled();
454
+ const attached = await this.getAttachedBrowserAttachment(normalizedSessionId);
455
+ if (attached) {
456
+ const result = await this.runAttachedTabAction(normalizedSessionId, attached, {
457
+ action: "get_logs",
458
+ payload: {
459
+ limit: input.limit,
460
+ },
461
+ });
462
+ const consoleMessages = Array.isArray(result?.console_messages)
463
+ ? result.console_messages
464
+ : [];
465
+ return {
466
+ session_id: normalizedSessionId,
467
+ total: typeof result?.console_total === "number"
468
+ ? result.console_total
469
+ : consoleMessages.length,
470
+ messages: consoleMessages.map((message) => ({
471
+ type: typeof message?.type === "string"
472
+ ? message.type
473
+ : "log",
474
+ text: typeof message?.text === "string"
475
+ ? message.text
476
+ : "",
477
+ ...(typeof message?.location === "string"
478
+ ? { location: message.location }
479
+ : {}),
480
+ timestamp: typeof message?.timestamp === "string"
481
+ ? message.timestamp
482
+ : (0, localTimestamp_1.formatLocalTimestamp)(new Date()),
483
+ })),
484
+ };
485
+ }
321
486
  const { sessionId, state } = await this.requireSessionState(input);
322
- state.lastUsedAt = new Date().toISOString();
487
+ state.lastUsedAt = (0, localTimestamp_1.formatLocalTimestamp)(new Date());
323
488
  return {
324
489
  session_id: sessionId,
325
490
  total: state.consoleMessages.length,
@@ -365,7 +530,7 @@ class BrowserService {
365
530
  });
366
531
  state.currentUrl = state.page.url();
367
532
  state.title = await state.page.title().catch(() => state.title);
368
- state.lastUsedAt = new Date().toISOString();
533
+ state.lastUsedAt = (0, localTimestamp_1.formatLocalTimestamp)(new Date());
369
534
  return {
370
535
  session_id: sessionId,
371
536
  clicked: true,
@@ -414,7 +579,7 @@ class BrowserService {
414
579
  });
415
580
  state.currentUrl = state.page.url();
416
581
  state.title = await state.page.title().catch(() => state.title);
417
- state.lastUsedAt = new Date().toISOString();
582
+ state.lastUsedAt = (0, localTimestamp_1.formatLocalTimestamp)(new Date());
418
583
  return {
419
584
  session_id: sessionId,
420
585
  filled: true,
@@ -469,7 +634,7 @@ class BrowserService {
469
634
  }
470
635
  state.currentUrl = state.page.url();
471
636
  state.title = await state.page.title().catch(() => state.title);
472
- state.lastUsedAt = new Date().toISOString();
637
+ state.lastUsedAt = (0, localTimestamp_1.formatLocalTimestamp)(new Date());
473
638
  return {
474
639
  session_id: sessionId,
475
640
  pressed: true,
@@ -493,14 +658,22 @@ class BrowserService {
493
658
  }
494
659
  this.ensureEnabled();
495
660
  const namespace = input.namespace?.trim() || "TELLY";
661
+ const sourcePath = input.file_path?.trim()
662
+ ? node_path_1.default.resolve(input.file_path.trim())
663
+ : null;
664
+ if (!input.source?.trim().length && sourcePath) {
665
+ const sourceStats = await (0, promises_1.stat)(sourcePath);
666
+ (0, bodyLimits_1.assertBodySize)(sourceStats.size);
667
+ }
496
668
  const source = input.source?.trim().length
497
669
  ? input.source
498
- : input.file_path?.trim()
499
- ? await (0, promises_1.readFile)(node_path_1.default.resolve(input.file_path.trim()), "utf8")
670
+ : sourcePath
671
+ ? await (0, promises_1.readFile)(sourcePath, "utf8")
500
672
  : null;
501
673
  if (!source) {
502
674
  throw new Error("Provide source or file_path.");
503
675
  }
676
+ (0, bodyLimits_1.assertStringBodySize)(source);
504
677
  const sourceType = input.source?.trim().length ? "inline" : "file";
505
678
  const wrappedSource = this.wrapInjectedScript(source, namespace);
506
679
  const attached = await this.getAttachedBrowserAttachment(normalizedSessionId);
@@ -528,7 +701,7 @@ class BrowserService {
528
701
  });
529
702
  state.currentUrl = state.page.url();
530
703
  state.title = await state.page.title().catch(() => state.title);
531
- state.lastUsedAt = new Date().toISOString();
704
+ state.lastUsedAt = (0, localTimestamp_1.formatLocalTimestamp)(new Date());
532
705
  return {
533
706
  session_id: sessionId,
534
707
  injected: true,
@@ -550,6 +723,18 @@ class BrowserService {
550
723
  return remote;
551
724
  }
552
725
  this.ensureEnabled();
726
+ const attached = await this.getAttachedBrowserAttachment(normalizedSessionId);
727
+ if (attached) {
728
+ const result = await this.runAttachedTabAction(normalizedSessionId, attached, {
729
+ action: "reload",
730
+ });
731
+ return {
732
+ session_id: normalizedSessionId,
733
+ reloaded: true,
734
+ url: String(result?.url || attached.url || ""),
735
+ ...(result?.title ? { title: String(result.title) } : {}),
736
+ };
737
+ }
553
738
  const { sessionId, state } = await this.requireSessionState(input);
554
739
  const waitUntil = (input.wait_until ??
555
740
  this.config.browser.waitUntil);
@@ -559,7 +744,7 @@ class BrowserService {
559
744
  });
560
745
  state.currentUrl = state.page.url();
561
746
  state.title = await state.page.title().catch(() => state.title);
562
- state.lastUsedAt = new Date().toISOString();
747
+ state.lastUsedAt = (0, localTimestamp_1.formatLocalTimestamp)(new Date());
563
748
  this.logger.info("Browser page reloaded", {
564
749
  sessionId,
565
750
  url: state.currentUrl,
@@ -584,6 +769,30 @@ class BrowserService {
584
769
  return remote;
585
770
  }
586
771
  this.ensureEnabled();
772
+ const attached = await this.getAttachedBrowserAttachment(normalizedSessionId);
773
+ if (attached) {
774
+ const waitState = input.state ?? "visible";
775
+ const result = await this.runAttachedTabAction(normalizedSessionId, attached, {
776
+ action: "wait_for",
777
+ payload: {
778
+ ...this.buildAttachedLocatorPayload(input),
779
+ state: waitState,
780
+ ...(typeof input.timeout_ms === "number"
781
+ ? { timeout_ms: input.timeout_ms }
782
+ : {}),
783
+ },
784
+ });
785
+ return {
786
+ session_id: normalizedSessionId,
787
+ waited: true,
788
+ state: waitState,
789
+ ...(input.ai_tag ? { ai_tag: input.ai_tag } : {}),
790
+ ...(input.selector ? { selector: input.selector } : {}),
791
+ ...(input.text ? { text: input.text } : {}),
792
+ url: String(result?.url || attached.url || ""),
793
+ ...(result?.title ? { title: String(result.title) } : {}),
794
+ };
795
+ }
587
796
  const { sessionId, state } = await this.requireSessionState(input);
588
797
  const locator = this.resolveLocator(state.page, input);
589
798
  const waitState = input.state ?? "visible";
@@ -593,7 +802,7 @@ class BrowserService {
593
802
  });
594
803
  state.currentUrl = state.page.url();
595
804
  state.title = await state.page.title().catch(() => state.title);
596
- state.lastUsedAt = new Date().toISOString();
805
+ state.lastUsedAt = (0, localTimestamp_1.formatLocalTimestamp)(new Date());
597
806
  return {
598
807
  session_id: sessionId,
599
808
  waited: true,
@@ -616,6 +825,32 @@ class BrowserService {
616
825
  return remote;
617
826
  }
618
827
  this.ensureEnabled();
828
+ const attached = await this.getAttachedBrowserAttachment(normalizedSessionId);
829
+ if (attached) {
830
+ const result = await this.runAttachedTabAction(normalizedSessionId, attached, {
831
+ action: "wait_for_url",
832
+ payload: {
833
+ ...(input.url?.trim() ? { url: input.url.trim() } : {}),
834
+ ...(input.url_contains?.trim()
835
+ ? { url_contains: input.url_contains.trim() }
836
+ : {}),
837
+ ...(typeof input.timeout_ms === "number"
838
+ ? { timeout_ms: input.timeout_ms }
839
+ : {}),
840
+ },
841
+ });
842
+ return {
843
+ session_id: normalizedSessionId,
844
+ waited: true,
845
+ matched: input.url?.trim() ? "url" : "url_contains",
846
+ ...(input.url?.trim() ? { url: input.url.trim() } : {}),
847
+ ...(input.url_contains?.trim()
848
+ ? { url_contains: input.url_contains.trim() }
849
+ : {}),
850
+ current_url: String(result?.url || attached.url || ""),
851
+ ...(result?.title ? { title: String(result.title) } : {}),
852
+ };
853
+ }
619
854
  const { sessionId, state } = await this.requireSessionState(input);
620
855
  const timeout = this.resolveTimeoutMs(input.timeout_ms);
621
856
  if (input.url?.trim()) {
@@ -634,7 +869,7 @@ class BrowserService {
634
869
  }
635
870
  state.currentUrl = state.page.url();
636
871
  state.title = await state.page.title().catch(() => state.title);
637
- state.lastUsedAt = new Date().toISOString();
872
+ state.lastUsedAt = (0, localTimestamp_1.formatLocalTimestamp)(new Date());
638
873
  return {
639
874
  session_id: sessionId,
640
875
  waited: true,
@@ -658,8 +893,37 @@ class BrowserService {
658
893
  return remote;
659
894
  }
660
895
  this.ensureEnabled();
896
+ const attached = await this.getAttachedBrowserAttachment(normalizedSessionId);
897
+ if (attached) {
898
+ const result = await this.runAttachedTabAction(normalizedSessionId, attached, {
899
+ action: "get_logs",
900
+ payload: {
901
+ limit: input.limit,
902
+ },
903
+ });
904
+ const pageErrors = Array.isArray(result?.page_errors)
905
+ ? result.page_errors
906
+ : [];
907
+ return {
908
+ session_id: normalizedSessionId,
909
+ total: typeof result?.page_error_total === "number"
910
+ ? result.page_error_total
911
+ : pageErrors.length,
912
+ errors: pageErrors.map((error) => ({
913
+ message: typeof error?.message === "string"
914
+ ? error.message
915
+ : "",
916
+ ...(typeof error?.stack === "string"
917
+ ? { stack: error.stack }
918
+ : {}),
919
+ timestamp: typeof error?.timestamp === "string"
920
+ ? error.timestamp
921
+ : (0, localTimestamp_1.formatLocalTimestamp)(new Date()),
922
+ })),
923
+ };
924
+ }
661
925
  const { sessionId, state } = await this.requireSessionState(input);
662
- state.lastUsedAt = new Date().toISOString();
926
+ state.lastUsedAt = (0, localTimestamp_1.formatLocalTimestamp)(new Date());
663
927
  return {
664
928
  session_id: sessionId,
665
929
  total: state.pageErrors.length,
@@ -681,8 +945,46 @@ class BrowserService {
681
945
  return remote;
682
946
  }
683
947
  this.ensureEnabled();
948
+ const attached = await this.getAttachedBrowserAttachment(normalizedSessionId);
949
+ if (attached) {
950
+ const result = await this.runAttachedTabAction(normalizedSessionId, attached, {
951
+ action: "get_logs",
952
+ payload: {
953
+ limit: input.limit,
954
+ },
955
+ });
956
+ const failures = Array.isArray(result?.network_failures)
957
+ ? result.network_failures
958
+ : [];
959
+ return {
960
+ session_id: normalizedSessionId,
961
+ total: typeof result?.network_failure_total === "number"
962
+ ? result.network_failure_total
963
+ : failures.length,
964
+ failures: failures.map((failure) => ({
965
+ url: typeof failure?.url === "string"
966
+ ? failure.url
967
+ : "",
968
+ method: typeof failure?.method === "string"
969
+ ? failure.method
970
+ : "GET",
971
+ ...(typeof failure?.status === "number"
972
+ ? { status: failure.status }
973
+ : {}),
974
+ ...(typeof failure?.error_text === "string"
975
+ ? { error_text: failure.error_text }
976
+ : {}),
977
+ ...(typeof failure?.resource_type === "string"
978
+ ? { resource_type: failure.resource_type }
979
+ : {}),
980
+ timestamp: typeof failure?.timestamp === "string"
981
+ ? failure.timestamp
982
+ : (0, localTimestamp_1.formatLocalTimestamp)(new Date()),
983
+ })),
984
+ };
985
+ }
684
986
  const { sessionId, state } = await this.requireSessionState(input);
685
- state.lastUsedAt = new Date().toISOString();
987
+ state.lastUsedAt = (0, localTimestamp_1.formatLocalTimestamp)(new Date());
686
988
  return {
687
989
  session_id: sessionId,
688
990
  total: state.networkFailures.length,
@@ -709,6 +1011,25 @@ class BrowserService {
709
1011
  return remote;
710
1012
  }
711
1013
  this.ensureEnabled();
1014
+ const attached = await this.getAttachedBrowserAttachment(normalizedSessionId);
1015
+ if (attached) {
1016
+ const result = await this.runAttachedTabAction(normalizedSessionId, attached, {
1017
+ action: "clear_logs",
1018
+ });
1019
+ return {
1020
+ session_id: normalizedSessionId,
1021
+ cleared: true,
1022
+ console_messages_cleared: typeof result?.console_messages_cleared === "number"
1023
+ ? result.console_messages_cleared
1024
+ : 0,
1025
+ page_errors_cleared: typeof result?.page_errors_cleared === "number"
1026
+ ? result.page_errors_cleared
1027
+ : 0,
1028
+ network_failures_cleared: typeof result?.network_failures_cleared === "number"
1029
+ ? result.network_failures_cleared
1030
+ : 0,
1031
+ };
1032
+ }
712
1033
  const { sessionId, state } = await this.requireSessionState(input);
713
1034
  const consoleMessagesCleared = state.consoleMessages.length;
714
1035
  const pageErrorsCleared = state.pageErrors.length;
@@ -716,7 +1037,7 @@ class BrowserService {
716
1037
  state.consoleMessages = [];
717
1038
  state.pageErrors = [];
718
1039
  state.networkFailures = [];
719
- state.lastUsedAt = new Date().toISOString();
1040
+ state.lastUsedAt = (0, localTimestamp_1.formatLocalTimestamp)(new Date());
720
1041
  return {
721
1042
  session_id: sessionId,
722
1043
  cleared: true,
@@ -801,7 +1122,7 @@ class BrowserService {
801
1122
  includeText: input.include_text !== false,
802
1123
  })
803
1124
  .catch(() => ({ found: false }));
804
- state.lastUsedAt = new Date().toISOString();
1125
+ state.lastUsedAt = (0, localTimestamp_1.formatLocalTimestamp)(new Date());
805
1126
  state.currentUrl = state.page.url();
806
1127
  state.title = await state.page.title().catch(() => state.title);
807
1128
  return {
@@ -831,6 +1152,57 @@ class BrowserService {
831
1152
  return remote;
832
1153
  }
833
1154
  this.ensureEnabled();
1155
+ const attached = await this.getAttachedBrowserAttachment(normalizedSessionId);
1156
+ if (attached) {
1157
+ const properties = input.properties?.length
1158
+ ? input.properties
1159
+ : [
1160
+ "display",
1161
+ "position",
1162
+ "visibility",
1163
+ "opacity",
1164
+ "color",
1165
+ "background-color",
1166
+ "font-size",
1167
+ "z-index",
1168
+ "overflow",
1169
+ ];
1170
+ const result = await this.runAttachedTabAction(normalizedSessionId, attached, {
1171
+ action: "computed_style",
1172
+ payload: {
1173
+ selector: input.selector,
1174
+ properties,
1175
+ },
1176
+ });
1177
+ return {
1178
+ session_id: normalizedSessionId,
1179
+ selector: input.selector,
1180
+ found: result?.found === true,
1181
+ ...(result?.url ? { url: String(result.url) } : {}),
1182
+ ...(result?.title ? { title: String(result.title) } : {}),
1183
+ ...(typeof result?.visible === "boolean"
1184
+ ? { visible: result.visible }
1185
+ : {}),
1186
+ ...(result?.styles && typeof result.styles === "object"
1187
+ ? { styles: result.styles }
1188
+ : {}),
1189
+ ...(result?.box &&
1190
+ typeof result.box === "object" &&
1191
+ typeof result.box.x === "number" &&
1192
+ typeof result.box.y === "number" &&
1193
+ typeof result.box.width === "number" &&
1194
+ typeof result.box.height === "number"
1195
+ ? {
1196
+ box: {
1197
+ x: Number(result.box.x),
1198
+ y: Number(result.box.y),
1199
+ width: Number(result.box.width),
1200
+ height: Number(result.box.height),
1201
+ },
1202
+ }
1203
+ : {}),
1204
+ };
1205
+ }
834
1206
  const { sessionId, state } = await this.requireSessionState(input);
835
1207
  const properties = input.properties?.length
836
1208
  ? input.properties
@@ -870,7 +1242,7 @@ class BrowserService {
870
1242
  };
871
1243
  }, properties)
872
1244
  .catch(() => ({ found: false }));
873
- state.lastUsedAt = new Date().toISOString();
1245
+ state.lastUsedAt = (0, localTimestamp_1.formatLocalTimestamp)(new Date());
874
1246
  state.currentUrl = state.page.url();
875
1247
  state.title = await state.page.title().catch(() => state.title);
876
1248
  return {
@@ -924,7 +1296,7 @@ class BrowserService {
924
1296
  filePath: storedFile.filePath,
925
1297
  relativePath: storedFile.relativePath,
926
1298
  source: "browser-screenshot",
927
- uploadedAt: new Date().toISOString(),
1299
+ uploadedAt: (0, localTimestamp_1.formatLocalTimestamp)(new Date()),
928
1300
  storageRef: storedFile.storageRef,
929
1301
  bucketName: storedFile.bucketName,
930
1302
  objectName: storedFile.objectName,
@@ -992,7 +1364,7 @@ class BrowserService {
992
1364
  mimeType: "image/png",
993
1365
  });
994
1366
  const filePath = storedFile.filePath;
995
- state.lastUsedAt = new Date().toISOString();
1367
+ state.lastUsedAt = (0, localTimestamp_1.formatLocalTimestamp)(new Date());
996
1368
  state.currentUrl = state.page.url();
997
1369
  state.title = await state.page.title().catch(() => state.title);
998
1370
  this.logger.info("Browser screenshot captured", {
@@ -1006,7 +1378,7 @@ class BrowserService {
1006
1378
  filePath,
1007
1379
  relativePath: storedFile.relativePath,
1008
1380
  source: "browser-screenshot",
1009
- uploadedAt: new Date().toISOString(),
1381
+ uploadedAt: (0, localTimestamp_1.formatLocalTimestamp)(new Date()),
1010
1382
  storageRef: storedFile.storageRef,
1011
1383
  bucketName: storedFile.bucketName,
1012
1384
  objectName: storedFile.objectName,
@@ -1059,6 +1431,17 @@ class BrowserService {
1059
1431
  return remote;
1060
1432
  }
1061
1433
  this.ensureEnabled();
1434
+ const attached = await this.getAttachedBrowserAttachment(normalizedSessionId);
1435
+ if (attached) {
1436
+ await this.runAttachedTabAction(normalizedSessionId, attached, {
1437
+ action: "close",
1438
+ });
1439
+ await this.maintenanceStore.clearBrowserAttachment(normalizedSessionId);
1440
+ return {
1441
+ session_id: normalizedSessionId,
1442
+ closed: true,
1443
+ };
1444
+ }
1062
1445
  const state = this.sessionStates.get(normalizedSessionId);
1063
1446
  if (state) {
1064
1447
  await this.closeState(normalizedSessionId, state);
@@ -1132,7 +1515,7 @@ class BrowserService {
1132
1515
  const browser = await this.ensureBrowser();
1133
1516
  const context = await browser.newContext(this.buildContextOptions(openInput));
1134
1517
  const page = await context.newPage();
1135
- const createdAt = new Date().toISOString();
1518
+ const createdAt = (0, localTimestamp_1.formatLocalTimestamp)(new Date());
1136
1519
  const state = {
1137
1520
  context,
1138
1521
  page,
@@ -1149,14 +1532,14 @@ class BrowserService {
1149
1532
  ...(formatConsoleLocation(message)
1150
1533
  ? { location: formatConsoleLocation(message) }
1151
1534
  : {}),
1152
- timestamp: new Date().toISOString(),
1535
+ timestamp: (0, localTimestamp_1.formatLocalTimestamp)(new Date()),
1153
1536
  }, this.config.browser.maxEvents);
1154
1537
  });
1155
1538
  page.on("pageerror", (error) => {
1156
1539
  pushBounded(state.pageErrors, {
1157
1540
  message: error.message,
1158
1541
  ...(error.stack ? { stack: error.stack } : {}),
1159
- timestamp: new Date().toISOString(),
1542
+ timestamp: (0, localTimestamp_1.formatLocalTimestamp)(new Date()),
1160
1543
  }, this.config.browser.maxEvents);
1161
1544
  });
1162
1545
  page.on("requestfailed", (request) => {
@@ -1206,7 +1589,7 @@ class BrowserService {
1206
1589
  : {}),
1207
1590
  ...(failure?.errorText ? { errorText: failure.errorText } : {}),
1208
1591
  resourceType: request.resourceType(),
1209
- timestamp: new Date().toISOString(),
1592
+ timestamp: (0, localTimestamp_1.formatLocalTimestamp)(new Date()),
1210
1593
  }, this.config.browser.maxEvents);
1211
1594
  }
1212
1595
  async requireSessionState(input) {