verybot 0.1.8

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 (277) hide show
  1. package/README.md +167 -0
  2. package/dist/aliases/store.d.ts +21 -0
  3. package/dist/aliases/store.js +148 -0
  4. package/dist/aliases/types.d.ts +6 -0
  5. package/dist/aliases/types.js +1 -0
  6. package/dist/brain/agent-registry.d.ts +96 -0
  7. package/dist/brain/agent-registry.js +141 -0
  8. package/dist/brain/agent.d.ts +167 -0
  9. package/dist/brain/agent.js +932 -0
  10. package/dist/brain/channel-store.d.ts +27 -0
  11. package/dist/brain/channel-store.js +78 -0
  12. package/dist/brain/compaction.d.ts +37 -0
  13. package/dist/brain/compaction.js +214 -0
  14. package/dist/brain/context.d.ts +43 -0
  15. package/dist/brain/context.js +139 -0
  16. package/dist/brain/delegation-store.d.ts +33 -0
  17. package/dist/brain/delegation-store.js +106 -0
  18. package/dist/brain/loop.d.ts +24 -0
  19. package/dist/brain/loop.js +318 -0
  20. package/dist/brain/mcp-adapter.d.ts +43 -0
  21. package/dist/brain/mcp-adapter.js +244 -0
  22. package/dist/brain/memory-extractor.d.ts +26 -0
  23. package/dist/brain/memory-extractor.js +82 -0
  24. package/dist/brain/providers.d.ts +14 -0
  25. package/dist/brain/providers.js +85 -0
  26. package/dist/brain/queue.d.ts +18 -0
  27. package/dist/brain/queue.js +111 -0
  28. package/dist/brain/run-tools.d.ts +50 -0
  29. package/dist/brain/run-tools.js +136 -0
  30. package/dist/brain/session-key.d.ts +23 -0
  31. package/dist/brain/session-key.js +41 -0
  32. package/dist/brain/session-state.d.ts +36 -0
  33. package/dist/brain/session-state.js +51 -0
  34. package/dist/brain/session-store.d.ts +50 -0
  35. package/dist/brain/session-store.js +207 -0
  36. package/dist/brain/session.d.ts +32 -0
  37. package/dist/brain/session.js +75 -0
  38. package/dist/brain/task-subscriber.d.ts +56 -0
  39. package/dist/brain/task-subscriber.js +317 -0
  40. package/dist/brain/user-content.d.ts +16 -0
  41. package/dist/brain/user-content.js +32 -0
  42. package/dist/brain/utils.d.ts +4 -0
  43. package/dist/brain/utils.js +26 -0
  44. package/dist/brain/worker-coordinator.d.ts +25 -0
  45. package/dist/brain/worker-coordinator.js +83 -0
  46. package/dist/channels/commands.d.ts +50 -0
  47. package/dist/channels/commands.js +132 -0
  48. package/dist/channels/discord/channel.d.ts +29 -0
  49. package/dist/channels/discord/channel.js +159 -0
  50. package/dist/channels/discord/markdown.d.ts +19 -0
  51. package/dist/channels/discord/markdown.js +62 -0
  52. package/dist/channels/manager.d.ts +29 -0
  53. package/dist/channels/manager.js +100 -0
  54. package/dist/channels/slack/channel.d.ts +37 -0
  55. package/dist/channels/slack/channel.js +227 -0
  56. package/dist/channels/slack/markdown.d.ts +19 -0
  57. package/dist/channels/slack/markdown.js +62 -0
  58. package/dist/channels/specs.d.ts +32 -0
  59. package/dist/channels/specs.js +99 -0
  60. package/dist/channels/telegram/channel.d.ts +29 -0
  61. package/dist/channels/telegram/channel.js +182 -0
  62. package/dist/channels/telegram/markdown.d.ts +17 -0
  63. package/dist/channels/telegram/markdown.js +66 -0
  64. package/dist/channels/types.d.ts +26 -0
  65. package/dist/channels/types.js +1 -0
  66. package/dist/channels/whatsapp/channel.d.ts +34 -0
  67. package/dist/channels/whatsapp/channel.js +276 -0
  68. package/dist/channels/whatsapp/markdown.d.ts +20 -0
  69. package/dist/channels/whatsapp/markdown.js +51 -0
  70. package/dist/cli/claude-login.d.ts +5 -0
  71. package/dist/cli/claude-login.js +47 -0
  72. package/dist/cli/config.d.ts +5 -0
  73. package/dist/cli/config.js +78 -0
  74. package/dist/cli/index.d.ts +11 -0
  75. package/dist/cli/index.js +96 -0
  76. package/dist/computer/browser/actions.d.ts +31 -0
  77. package/dist/computer/browser/actions.js +148 -0
  78. package/dist/computer/browser/context-manager.d.ts +28 -0
  79. package/dist/computer/browser/context-manager.js +78 -0
  80. package/dist/computer/browser/manager.d.ts +91 -0
  81. package/dist/computer/browser/manager.js +344 -0
  82. package/dist/computer/browser/profile-badge.d.ts +13 -0
  83. package/dist/computer/browser/profile-badge.js +67 -0
  84. package/dist/computer/browser/screenshot.d.ts +5 -0
  85. package/dist/computer/browser/screenshot.js +21 -0
  86. package/dist/computer/browser/snapshot.d.ts +30 -0
  87. package/dist/computer/browser/snapshot.js +242 -0
  88. package/dist/computer/browser/tools.d.ts +5 -0
  89. package/dist/computer/browser/tools.js +167 -0
  90. package/dist/computer/browser/types.d.ts +26 -0
  91. package/dist/computer/browser/types.js +1 -0
  92. package/dist/computer/desktop/adapter.d.ts +25 -0
  93. package/dist/computer/desktop/adapter.js +11 -0
  94. package/dist/computer/desktop/macos.d.ts +24 -0
  95. package/dist/computer/desktop/macos.js +223 -0
  96. package/dist/computer/desktop/tools.d.ts +25 -0
  97. package/dist/computer/desktop/tools.js +114 -0
  98. package/dist/config/agent-config.d.ts +55 -0
  99. package/dist/config/agent-config.js +16 -0
  100. package/dist/config/model-catalog.d.ts +22 -0
  101. package/dist/config/model-catalog.js +112 -0
  102. package/dist/config/model-spec.d.ts +8 -0
  103. package/dist/config/model-spec.js +66 -0
  104. package/dist/config/store.d.ts +25 -0
  105. package/dist/config/store.js +143 -0
  106. package/dist/config.d.ts +110 -0
  107. package/dist/config.js +259 -0
  108. package/dist/control-ui/assets/index-Cbl7G5Sc.css +1 -0
  109. package/dist/control-ui/assets/index-Cu1P4C62.js +266 -0
  110. package/dist/control-ui/assets/noto-sans-cyrillic-ext-wght-normal-DSNfmdVt.woff2 +0 -0
  111. package/dist/control-ui/assets/noto-sans-cyrillic-wght-normal-B2hlT84T.woff2 +0 -0
  112. package/dist/control-ui/assets/noto-sans-devanagari-wght-normal-Cv-Vwajv.woff2 +0 -0
  113. package/dist/control-ui/assets/noto-sans-greek-ext-wght-normal-12T8GTDR.woff2 +0 -0
  114. package/dist/control-ui/assets/noto-sans-greek-wght-normal-Ymb6dZNd.woff2 +0 -0
  115. package/dist/control-ui/assets/noto-sans-latin-ext-wght-normal-W1qJv59z.woff2 +0 -0
  116. package/dist/control-ui/assets/noto-sans-latin-wght-normal-BYSzYMf3.woff2 +0 -0
  117. package/dist/control-ui/assets/noto-sans-vietnamese-wght-normal-DLTJy58D.woff2 +0 -0
  118. package/dist/control-ui/index.html +14 -0
  119. package/dist/control-ui/vite.svg +1 -0
  120. package/dist/events.d.ts +2 -0
  121. package/dist/events.js +11 -0
  122. package/dist/gateway/broadcast.d.ts +5 -0
  123. package/dist/gateway/broadcast.js +33 -0
  124. package/dist/gateway/methods/aliases.d.ts +17 -0
  125. package/dist/gateway/methods/aliases.js +22 -0
  126. package/dist/gateway/methods/chat.d.ts +33 -0
  127. package/dist/gateway/methods/chat.js +37 -0
  128. package/dist/gateway/methods/config.d.ts +14 -0
  129. package/dist/gateway/methods/config.js +24 -0
  130. package/dist/gateway/methods/models.d.ts +10 -0
  131. package/dist/gateway/methods/models.js +14 -0
  132. package/dist/gateway/methods/playbooks.d.ts +45 -0
  133. package/dist/gateway/methods/playbooks.js +488 -0
  134. package/dist/gateway/methods/prompt-templates.d.ts +27 -0
  135. package/dist/gateway/methods/prompt-templates.js +106 -0
  136. package/dist/gateway/methods/scheduler.d.ts +62 -0
  137. package/dist/gateway/methods/scheduler.js +129 -0
  138. package/dist/gateway/methods/sessions.d.ts +44 -0
  139. package/dist/gateway/methods/sessions.js +111 -0
  140. package/dist/gateway/methods/system.d.ts +12 -0
  141. package/dist/gateway/methods/system.js +39 -0
  142. package/dist/gateway/methods/tasks.d.ts +40 -0
  143. package/dist/gateway/methods/tasks.js +151 -0
  144. package/dist/gateway/methods/teams.d.ts +69 -0
  145. package/dist/gateway/methods/teams.js +376 -0
  146. package/dist/gateway/methods/tools.d.ts +6 -0
  147. package/dist/gateway/methods/tools.js +7 -0
  148. package/dist/gateway/methods/whatsapp.d.ts +19 -0
  149. package/dist/gateway/methods/whatsapp.js +35 -0
  150. package/dist/gateway/rpc.d.ts +38 -0
  151. package/dist/gateway/rpc.js +79 -0
  152. package/dist/gateway/server.d.ts +9 -0
  153. package/dist/gateway/server.js +137 -0
  154. package/dist/index.d.ts +1 -0
  155. package/dist/index.js +254 -0
  156. package/dist/integrations/github.d.ts +7 -0
  157. package/dist/integrations/github.js +133 -0
  158. package/dist/integrations/mcp.d.ts +7 -0
  159. package/dist/integrations/mcp.js +106 -0
  160. package/dist/integrations/registry.d.ts +47 -0
  161. package/dist/integrations/registry.js +332 -0
  162. package/dist/integrations/scanner.d.ts +10 -0
  163. package/dist/integrations/scanner.js +122 -0
  164. package/dist/integrations/twitter.d.ts +10 -0
  165. package/dist/integrations/twitter.js +120 -0
  166. package/dist/integrations/types.d.ts +72 -0
  167. package/dist/integrations/types.js +1 -0
  168. package/dist/logger.d.ts +16 -0
  169. package/dist/logger.js +104 -0
  170. package/dist/markdown/chunk.d.ts +9 -0
  171. package/dist/markdown/chunk.js +52 -0
  172. package/dist/markdown/ir.d.ts +37 -0
  173. package/dist/markdown/ir.js +529 -0
  174. package/dist/markdown/render.d.ts +22 -0
  175. package/dist/markdown/render.js +148 -0
  176. package/dist/markdown/table-render.d.ts +43 -0
  177. package/dist/markdown/table-render.js +219 -0
  178. package/dist/markdown/tables.d.ts +17 -0
  179. package/dist/markdown/tables.js +27 -0
  180. package/dist/memory/embedding.d.ts +16 -0
  181. package/dist/memory/embedding.js +66 -0
  182. package/dist/memory/explicit.d.ts +16 -0
  183. package/dist/memory/explicit.js +29 -0
  184. package/dist/memory/extractor.d.ts +13 -0
  185. package/dist/memory/extractor.js +82 -0
  186. package/dist/memory/search.d.ts +15 -0
  187. package/dist/memory/search.js +57 -0
  188. package/dist/memory/session-learning.d.ts +23 -0
  189. package/dist/memory/session-learning.js +55 -0
  190. package/dist/memory/store.d.ts +36 -0
  191. package/dist/memory/store.js +334 -0
  192. package/dist/memory/types.d.ts +9 -0
  193. package/dist/memory/types.js +2 -0
  194. package/dist/paths.d.ts +28 -0
  195. package/dist/paths.js +48 -0
  196. package/dist/prompt-templates/builtins/index.d.ts +4 -0
  197. package/dist/prompt-templates/builtins/index.js +5 -0
  198. package/dist/prompt-templates/builtins/planner.d.ts +4 -0
  199. package/dist/prompt-templates/builtins/planner.js +77 -0
  200. package/dist/prompt-templates/store.d.ts +45 -0
  201. package/dist/prompt-templates/store.js +224 -0
  202. package/dist/prompt-templates/types.d.ts +10 -0
  203. package/dist/prompt-templates/types.js +1 -0
  204. package/dist/scheduler/connected-channels.d.ts +24 -0
  205. package/dist/scheduler/connected-channels.js +57 -0
  206. package/dist/scheduler/scheduler.d.ts +22 -0
  207. package/dist/scheduler/scheduler.js +132 -0
  208. package/dist/scheduler/store.d.ts +27 -0
  209. package/dist/scheduler/store.js +205 -0
  210. package/dist/scheduler/types.d.ts +29 -0
  211. package/dist/scheduler/types.js +1 -0
  212. package/dist/security/command-validator.d.ts +22 -0
  213. package/dist/security/command-validator.js +160 -0
  214. package/dist/security/docker-sandbox.d.ts +48 -0
  215. package/dist/security/docker-sandbox.js +218 -0
  216. package/dist/security/env-filter.d.ts +8 -0
  217. package/dist/security/env-filter.js +41 -0
  218. package/dist/skills/loader.d.ts +33 -0
  219. package/dist/skills/loader.js +132 -0
  220. package/dist/skills/prompt.d.ts +6 -0
  221. package/dist/skills/prompt.js +17 -0
  222. package/dist/skills/read-tool.d.ts +7 -0
  223. package/dist/skills/read-tool.js +24 -0
  224. package/dist/skills/scanner.d.ts +6 -0
  225. package/dist/skills/scanner.js +73 -0
  226. package/dist/skills/types.d.ts +15 -0
  227. package/dist/skills/types.js +1 -0
  228. package/dist/tasks/inline-attachment-content.d.ts +9 -0
  229. package/dist/tasks/inline-attachment-content.js +64 -0
  230. package/dist/tasks/store.d.ts +112 -0
  231. package/dist/tasks/store.js +519 -0
  232. package/dist/tasks/types.d.ts +129 -0
  233. package/dist/tasks/types.js +80 -0
  234. package/dist/teams/status-config.d.ts +8 -0
  235. package/dist/teams/status-config.js +40 -0
  236. package/dist/teams/store.d.ts +111 -0
  237. package/dist/teams/store.js +671 -0
  238. package/dist/teams/types.d.ts +30 -0
  239. package/dist/teams/types.js +1 -0
  240. package/dist/tools/bash.d.ts +18 -0
  241. package/dist/tools/bash.js +64 -0
  242. package/dist/tools/channel-history.d.ts +10 -0
  243. package/dist/tools/channel-history.js +43 -0
  244. package/dist/tools/delegate.d.ts +20 -0
  245. package/dist/tools/delegate.js +299 -0
  246. package/dist/tools/fs.d.ts +4 -0
  247. package/dist/tools/fs.js +335 -0
  248. package/dist/tools/integration-toggle.d.ts +14 -0
  249. package/dist/tools/integration-toggle.js +47 -0
  250. package/dist/tools/memory.d.ts +13 -0
  251. package/dist/tools/memory.js +59 -0
  252. package/dist/tools/prompt-templates.d.ts +7 -0
  253. package/dist/tools/prompt-templates.js +133 -0
  254. package/dist/tools/registry.d.ts +6 -0
  255. package/dist/tools/registry.js +9 -0
  256. package/dist/tools/schedule.d.ts +8 -0
  257. package/dist/tools/schedule.js +219 -0
  258. package/dist/tools/speak.d.ts +10 -0
  259. package/dist/tools/speak.js +56 -0
  260. package/dist/tools/tasks.d.ts +67 -0
  261. package/dist/tools/tasks.js +288 -0
  262. package/dist/tools/teams.d.ts +22 -0
  263. package/dist/tools/teams.js +470 -0
  264. package/dist/tools/web-fetch.d.ts +3 -0
  265. package/dist/tools/web-fetch.js +22 -0
  266. package/dist/tts/edge.d.ts +10 -0
  267. package/dist/tts/edge.js +60 -0
  268. package/dist/tts/speak.d.ts +12 -0
  269. package/dist/tts/speak.js +81 -0
  270. package/dist/tts/transcribe.d.ts +5 -0
  271. package/dist/tts/transcribe.js +40 -0
  272. package/dist/utils.d.ts +5 -0
  273. package/dist/utils.js +22 -0
  274. package/dist/version.d.ts +1 -0
  275. package/dist/version.js +13 -0
  276. package/package.json +102 -0
  277. package/verybot.js +2 -0
@@ -0,0 +1,344 @@
1
+ import { existsSync, mkdirSync, mkdtempSync, rmSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { tmpdir } from "node:os";
4
+ import { chromium } from "playwright-extra";
5
+ import StealthPlugin from "puppeteer-extra-plugin-stealth";
6
+ import { BROWSER_PROFILE_DIR, BROWSER_PROFILES_DIR } from "../../paths.js";
7
+ import { logger } from "../../logger.js";
8
+ import { buildProfileBadgeScript } from "./profile-badge.js";
9
+ // Guard against double-registration (e.g. test runners that re-evaluate modules).
10
+ let stealthApplied = false;
11
+ function ensureStealth() {
12
+ if (stealthApplied)
13
+ return;
14
+ chromium.use(StealthPlugin());
15
+ stealthApplied = true;
16
+ }
17
+ const DEFAULT_PROFILE = "default";
18
+ const PROFILE_NAME_MAX_LENGTH = 50;
19
+ const PROFILE_NAME_PATTERN = /^[a-zA-Z0-9-]+$/;
20
+ /** Chromium launch flags: automation-control hiding + clean launch defaults. */
21
+ const LAUNCH_ARGS = [
22
+ "--disable-blink-features=AutomationControlled",
23
+ "--no-first-run",
24
+ "--no-default-browser-check",
25
+ "--disable-component-update",
26
+ ];
27
+ /** Validate a profile name. Throws on invalid input. */
28
+ export function validateProfileName(name) {
29
+ if (!name || name.length > PROFILE_NAME_MAX_LENGTH) {
30
+ throw new Error(`Profile name must be 1-${PROFILE_NAME_MAX_LENGTH} characters. Got: "${name}"`);
31
+ }
32
+ if (!PROFILE_NAME_PATTERN.test(name)) {
33
+ throw new Error(`Profile name may only contain letters, digits, and hyphens. Got: "${name}"`);
34
+ }
35
+ }
36
+ export class BrowserManager {
37
+ static cachedCleanUAByMode = new Map();
38
+ context = null;
39
+ config;
40
+ roleRefs = {};
41
+ sessionRoleRefs = new Map();
42
+ activeProfile;
43
+ tempProfileDir = null;
44
+ // NEW: Session-aware page tracking (for per-tab mode)
45
+ sessionPages = new Map();
46
+ currentSessionKey = null;
47
+ constructor(config) {
48
+ this.config = config;
49
+ const profile = config.profile ?? DEFAULT_PROFILE;
50
+ if (profile !== DEFAULT_PROFILE)
51
+ validateProfileName(profile);
52
+ this.activeProfile = profile;
53
+ }
54
+ isPerTabMode() {
55
+ return this.config.mode === "per-tab-per-session";
56
+ }
57
+ resolveSessionKey(sessionKey) {
58
+ return sessionKey ?? this.currentSessionKey;
59
+ }
60
+ ensureSessionPages(sessionKey) {
61
+ const existing = this.sessionPages.get(sessionKey);
62
+ if (existing)
63
+ return existing;
64
+ const created = [];
65
+ this.sessionPages.set(sessionKey, created);
66
+ return created;
67
+ }
68
+ trackPageForSession(page, sessionKey) {
69
+ const pages = this.ensureSessionPages(sessionKey);
70
+ if (!pages.includes(page))
71
+ pages.push(page);
72
+ page.once("close", () => {
73
+ const ownedPages = this.sessionPages.get(sessionKey);
74
+ if (!ownedPages)
75
+ return;
76
+ const idx = ownedPages.indexOf(page);
77
+ if (idx !== -1)
78
+ ownedPages.splice(idx, 1);
79
+ if (ownedPages.length === 0)
80
+ this.sessionPages.delete(sessionKey);
81
+ });
82
+ }
83
+ pruneClosedPages(sessionKey) {
84
+ const pages = this.sessionPages.get(sessionKey);
85
+ if (!pages)
86
+ return;
87
+ const openPages = pages.filter((p) => !p.isClosed());
88
+ if (openPages.length === 0) {
89
+ this.sessionPages.delete(sessionKey);
90
+ return;
91
+ }
92
+ if (openPages.length !== pages.length)
93
+ this.sessionPages.set(sessionKey, openPages);
94
+ }
95
+ /**
96
+ * Set the current session key for page tracking (per-tab-per-session mode).
97
+ * Called by buildRunTools() to bind tools to a specific session.
98
+ */
99
+ setSessionKey(key) {
100
+ this.currentSessionKey = key;
101
+ }
102
+ /**
103
+ * Get the current session key (if set).
104
+ */
105
+ getSessionKey() {
106
+ return this.currentSessionKey;
107
+ }
108
+ /**
109
+ * MODIFIED: launch() now session-aware
110
+ * If mode is "per-tab-per-session" and sessionKey is set,
111
+ * pages are tracked per session.
112
+ */
113
+ async launch(sessionKey) {
114
+ ensureStealth();
115
+ const key = this.resolveSessionKey(sessionKey);
116
+ if (this.context) {
117
+ // Per-tab mode: check session-specific pages first
118
+ if (this.isPerTabMode() &&
119
+ key) {
120
+ this.pruneClosedPages(key);
121
+ const sessionPageList = this.sessionPages.get(key) ?? [];
122
+ if (sessionPageList.length > 0) {
123
+ return sessionPageList[sessionPageList.length - 1];
124
+ }
125
+ const newPage = await this.context.newPage();
126
+ this.trackPageForSession(newPage, key);
127
+ return newPage;
128
+ }
129
+ // Shared/per-browser mode: keep using the active page when available.
130
+ const pages = this.context.pages();
131
+ const current = pages[pages.length - 1];
132
+ if (current && !current.isClosed())
133
+ return current;
134
+ return await this.context.newPage();
135
+ }
136
+ // ... rest of launch() logic unchanged
137
+ const isTemp = this.config.profileDir === "temp";
138
+ const profileDir = isTemp
139
+ ? mkdtempSync(join(tmpdir(), "verybot-browser-"))
140
+ : this.resolveProfileDir();
141
+ this.tempProfileDir = isTemp ? profileDir : null;
142
+ const headless = this.config.headless ?? true;
143
+ const userAgent = this.config.userAgent ?? (await this.detectCleanUA(headless));
144
+ const isMac = process.platform === "darwin";
145
+ const deviceScaleFactor = isMac ? 2 : 1;
146
+ const viewport = { width: isMac ? 1512 : 1920, height: isMac ? 982 : 1080 };
147
+ // Always set a clean UA on persistent-context launch to cover the first page.
148
+ this.context = await chromium.launchPersistentContext(profileDir, {
149
+ headless,
150
+ viewport,
151
+ deviceScaleFactor,
152
+ userAgent,
153
+ args: LAUNCH_ARGS,
154
+ });
155
+ if (!isTemp) {
156
+ await this.context.addInitScript(buildProfileBadgeScript(this.activeProfile));
157
+ }
158
+ logger.info(`Browser launched (profile: ${this.activeProfile}, mode: ${this.config.mode ?? "shared"})`);
159
+ const pages = this.context.pages();
160
+ const page = pages[pages.length - 1] ?? (await this.context.newPage());
161
+ if (this.isPerTabMode() && key) {
162
+ this.trackPageForSession(page, key);
163
+ }
164
+ return page;
165
+ }
166
+ /**
167
+ * Probe-launch Chromium once per mode to derive a stable UA without "HeadlessChrome".
168
+ * This protects the initial page in persistent contexts where plugin hooks may not run yet.
169
+ */
170
+ async detectCleanUA(headless) {
171
+ const cacheKey = headless ? "headless" : "headful";
172
+ const cachedUA = BrowserManager.cachedCleanUAByMode.get(cacheKey);
173
+ if (cachedUA)
174
+ return cachedUA;
175
+ const probeProfileDir = mkdtempSync(join(tmpdir(), "verybot-browser-ua-probe-"));
176
+ const probeContext = await chromium.launchPersistentContext(probeProfileDir, {
177
+ headless,
178
+ args: ["--no-first-run"],
179
+ });
180
+ try {
181
+ const probePage = probeContext.pages()[0] ?? (await probeContext.newPage());
182
+ const rawUA = await probePage.evaluate(() => navigator.userAgent);
183
+ const cleanUA = rawUA.replace(/HeadlessChrome/g, "Chrome");
184
+ BrowserManager.cachedCleanUAByMode.set(cacheKey, cleanUA);
185
+ return cleanUA;
186
+ }
187
+ finally {
188
+ await probeContext.close();
189
+ if (existsSync(probeProfileDir)) {
190
+ rmSync(probeProfileDir, { recursive: true, force: true });
191
+ }
192
+ }
193
+ }
194
+ /** Resolve the user-data directory for the active named profile. */
195
+ resolveProfileDir() {
196
+ if (this.config.profileDir && this.config.profileDir !== "temp") {
197
+ return this.config.profileDir;
198
+ }
199
+ // Named profile → dedicated subdirectory under BROWSER_PROFILES_DIR
200
+ if (this.activeProfile !== DEFAULT_PROFILE) {
201
+ const dir = join(BROWSER_PROFILES_DIR, this.activeProfile);
202
+ mkdirSync(dir, { recursive: true });
203
+ return dir;
204
+ }
205
+ // Default profile → legacy shared dir
206
+ return BROWSER_PROFILE_DIR;
207
+ }
208
+ /** Close current browser and switch to a different named profile. */
209
+ async switchProfile(name) {
210
+ validateProfileName(name);
211
+ await this.close();
212
+ this.activeProfile = name;
213
+ logger.info(`Switched to browser profile: ${name}`);
214
+ }
215
+ /** Get the currently active profile name. */
216
+ getActiveProfile() {
217
+ return this.activeProfile;
218
+ }
219
+ /** Update config for next browser launch. Closes existing browser if headless mode changed. */
220
+ async updateConfig(config) {
221
+ const headlessChanged = this.context && (config.headless ?? true) !== (this.config.headless ?? true);
222
+ this.config = { ...this.config, ...config };
223
+ if (headlessChanged) {
224
+ logger.info("Headless mode changed — closing browser so next launch uses new setting");
225
+ await this.close();
226
+ }
227
+ }
228
+ /**
229
+ * MODIFIED: getPage() respects session boundary in per-tab mode
230
+ */
231
+ getPage(sessionKey) {
232
+ if (!this.context)
233
+ return null;
234
+ const key = this.resolveSessionKey(sessionKey);
235
+ // Per-tab mode: return session's own page
236
+ if (this.isPerTabMode() &&
237
+ key) {
238
+ this.pruneClosedPages(key);
239
+ const pages = this.sessionPages.get(key) ?? [];
240
+ return pages.length > 0 ? pages[pages.length - 1] : null;
241
+ }
242
+ // Shared mode or no session key: return last page globally
243
+ const pages = this.context.pages();
244
+ return pages[pages.length - 1] ?? null;
245
+ }
246
+ /** Check if the browser is currently launched. */
247
+ isLaunched() {
248
+ return this.context !== null;
249
+ }
250
+ /** Store role refs from the latest snapshot. */
251
+ setRoleRefs(refs, sessionKey) {
252
+ const key = this.resolveSessionKey(sessionKey);
253
+ if (this.isPerTabMode() && key) {
254
+ this.sessionRoleRefs.set(key, refs);
255
+ return;
256
+ }
257
+ this.roleRefs = refs;
258
+ }
259
+ /** Get stored role refs from the latest snapshot. */
260
+ getRoleRefs(sessionKey) {
261
+ const key = this.resolveSessionKey(sessionKey);
262
+ if (this.isPerTabMode() && key) {
263
+ return this.sessionRoleRefs.get(key) ?? {};
264
+ }
265
+ return this.roleRefs;
266
+ }
267
+ /**
268
+ * Resolve a ref string (e.g. "e5") to a Playwright Locator.
269
+ * Ported from main project's pw-session.ts:refLocator.
270
+ */
271
+ refLocator(ref, sessionKey) {
272
+ const page = this.getPage(sessionKey);
273
+ if (!page)
274
+ throw new Error("Browser not launched. Use browser_navigate first.");
275
+ const normalized = ref.startsWith("@")
276
+ ? ref.slice(1)
277
+ : ref.startsWith("ref=")
278
+ ? ref.slice(4)
279
+ : ref;
280
+ const refs = this.getRoleRefs(sessionKey);
281
+ const info = refs[normalized];
282
+ if (!info) {
283
+ throw new Error(`Unknown ref "${normalized}". Take a new snapshot and use a ref from that snapshot.`);
284
+ }
285
+ const locator = info.name
286
+ ? page.getByRole(info.role, { name: info.name, exact: true })
287
+ : page.getByRole(info.role);
288
+ return info.nth !== undefined ? locator.nth(info.nth) : locator;
289
+ }
290
+ /**
291
+ * NEW: Get all pages for a session (useful for debugging/cleanup).
292
+ */
293
+ getSessionPages(sessionKey) {
294
+ return this.sessionPages.get(sessionKey) ?? [];
295
+ }
296
+ /**
297
+ * MODIFIED: close() now clears session page tracking
298
+ */
299
+ async close() {
300
+ if (this.context) {
301
+ await this.context.close();
302
+ this.context = null;
303
+ }
304
+ this.roleRefs = {};
305
+ this.sessionRoleRefs.clear();
306
+ this.sessionPages.clear(); // NEW
307
+ this.currentSessionKey = null; // NEW
308
+ if (this.tempProfileDir) {
309
+ try {
310
+ if (existsSync(this.tempProfileDir)) {
311
+ rmSync(this.tempProfileDir, { recursive: true, force: true });
312
+ }
313
+ }
314
+ catch (err) {
315
+ logger.warn(`Failed to remove temp browser profile dir ${this.tempProfileDir}: ${err}`);
316
+ }
317
+ finally {
318
+ this.tempProfileDir = null;
319
+ }
320
+ }
321
+ logger.info("Browser closed");
322
+ }
323
+ /**
324
+ * NEW: Clear pages for a specific session (called on session cleanup)
325
+ */
326
+ async clearSessionPages(sessionKey) {
327
+ const pages = this.sessionPages.get(sessionKey);
328
+ this.sessionRoleRefs.delete(sessionKey);
329
+ if (!pages)
330
+ return;
331
+ // Close all pages for this session
332
+ for (const page of [...pages]) {
333
+ try {
334
+ await page.close();
335
+ }
336
+ catch (err) {
337
+ logger.warn(`Failed to close page during session cleanup: ${err}`);
338
+ }
339
+ }
340
+ this.sessionPages.delete(sessionKey);
341
+ if (this.currentSessionKey === sessionKey)
342
+ this.currentSessionKey = null;
343
+ }
344
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Injectable JS script that renders a floating profile badge at the top of
3
+ * every page. Injected via `context.addInitScript()` so it runs on every
4
+ * navigation within the persistent browser context.
5
+ *
6
+ * The badge color is deterministic — derived from a simple hash of the profile
7
+ * name so each profile always gets the same hue.
8
+ */
9
+ /**
10
+ * Build the init-script source string for a given profile name.
11
+ * The returned string is plain JS (no imports) safe for `addInitScript`.
12
+ */
13
+ export declare function buildProfileBadgeScript(profileName: string): string;
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Injectable JS script that renders a floating profile badge at the top of
3
+ * every page. Injected via `context.addInitScript()` so it runs on every
4
+ * navigation within the persistent browser context.
5
+ *
6
+ * The badge color is deterministic — derived from a simple hash of the profile
7
+ * name so each profile always gets the same hue.
8
+ */
9
+ const BADGE_ID = "__verybot_profile_badge__";
10
+ /**
11
+ * Build the init-script source string for a given profile name.
12
+ * The returned string is plain JS (no imports) safe for `addInitScript`.
13
+ */
14
+ export function buildProfileBadgeScript(profileName) {
15
+ // Defense-in-depth: reject anything outside [a-zA-Z0-9-] even if caller validated
16
+ if (!/^[a-zA-Z0-9-]+$/.test(profileName)) {
17
+ throw new Error(`Unsafe profile name for badge script: "${profileName}"`);
18
+ }
19
+ // Deterministic hue from profile name (djb2 hash → 0-360)
20
+ let hash = 5381;
21
+ for (let i = 0; i < profileName.length; i++) {
22
+ hash = ((hash << 5) + hash + profileName.charCodeAt(i)) >>> 0;
23
+ }
24
+ const hue = hash % 360;
25
+ // Also override document.title so the window is identifiable
26
+ const upperName = profileName.toUpperCase();
27
+ return `
28
+ (function() {
29
+ if (document.getElementById("${BADGE_ID}")) return;
30
+
31
+ /* --- title prefix --- */
32
+ var origTitle = document.title;
33
+ document.title = "[${upperName}] " + origTitle;
34
+ new MutationObserver(function() {
35
+ if (!document.title.startsWith("[${upperName}] ")) {
36
+ document.title = "[${upperName}] " + document.title;
37
+ }
38
+ }).observe(document.querySelector("title") || document.head, { childList: true, subtree: true, characterData: true });
39
+
40
+ /* --- badge bar --- */
41
+ var bar = document.createElement("div");
42
+ bar.id = "${BADGE_ID}";
43
+ bar.style.cssText =
44
+ "position:fixed;top:0;left:0;right:0;height:28px;z-index:2147483647;" +
45
+ "background:hsl(${hue},65%,45%);color:#fff;font:bold 13px/28px sans-serif;" +
46
+ "display:flex;align-items:center;padding:0 12px;box-shadow:0 1px 4px rgba(0,0,0,.25);";
47
+
48
+ var dot = document.createElement("span");
49
+ dot.textContent = "\\u25CF ";
50
+ dot.style.marginRight = "6px";
51
+ bar.appendChild(dot);
52
+
53
+ var label = document.createElement("span");
54
+ label.textContent = "${upperName}";
55
+ label.style.flex = "1";
56
+ bar.appendChild(label);
57
+
58
+ var close = document.createElement("span");
59
+ close.textContent = "\\u2715";
60
+ close.style.cssText = "cursor:pointer;padding:0 4px;font-size:15px;opacity:.8;";
61
+ close.addEventListener("click", function() { bar.remove(); });
62
+ bar.appendChild(close);
63
+
64
+ document.documentElement.appendChild(bar);
65
+ })();
66
+ `;
67
+ }
@@ -0,0 +1,5 @@
1
+ /** Compress a screenshot PNG to JPEG, reducing quality until under the size limit. */
2
+ export declare function compressScreenshot(png: Buffer): Promise<{
3
+ base64: string;
4
+ mediaType: string;
5
+ }>;
@@ -0,0 +1,21 @@
1
+ import sharp from "sharp";
2
+ import { logger } from "../../logger.js";
3
+ /** Anthropic API rejects base64 images over 5 MB. Compress PNG -> JPEG to stay under. */
4
+ const MAX_IMAGE_BYTES = 3_500_000;
5
+ const JPEG_QUALITY_START = 80;
6
+ const JPEG_QUALITY_MIN = 40;
7
+ const JPEG_QUALITY_STEP = 10;
8
+ /** Compress a screenshot PNG to JPEG, reducing quality until under the size limit. */
9
+ export async function compressScreenshot(png) {
10
+ let lastJpeg = null;
11
+ for (let quality = JPEG_QUALITY_START; quality >= JPEG_QUALITY_MIN; quality -= JPEG_QUALITY_STEP) {
12
+ lastJpeg = await sharp(png).jpeg({ quality }).toBuffer();
13
+ if (lastJpeg.length <= MAX_IMAGE_BYTES) {
14
+ logger.info(`Browser: screenshot compressed ${png.length} -> ${lastJpeg.length} bytes (JPEG q${quality})`);
15
+ return { base64: lastJpeg.toString("base64"), mediaType: "image/jpeg" };
16
+ }
17
+ }
18
+ // Last resort: use the already-computed lowest quality buffer
19
+ logger.info(`Browser: screenshot compressed ${png.length} -> ${lastJpeg.length} bytes (JPEG q${JPEG_QUALITY_MIN}, may exceed limit)`);
20
+ return { base64: lastJpeg.toString("base64"), mediaType: "image/jpeg" };
21
+ }
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Parses Playwright's ariaSnapshot() output,
3
+ * assigns refs (e1, e2…) to interactive/content elements, and returns an
4
+ * annotated snapshot string plus a RoleRefMap for resolving refs back to locators.
5
+ */
6
+ export type RoleRef = {
7
+ role: string;
8
+ name?: string;
9
+ /** Index used only when role+name duplicates exist. */
10
+ nth?: number;
11
+ };
12
+ export type RoleRefMap = Record<string, RoleRef>;
13
+ export type RoleSnapshotOptions = {
14
+ /** Only include interactive elements (buttons, links, inputs, etc.). */
15
+ interactive?: boolean;
16
+ /** Maximum depth to include (0 = root only). */
17
+ maxDepth?: number;
18
+ /** Remove unnamed structural elements and empty branches. */
19
+ compact?: boolean;
20
+ };
21
+ /** Validate and normalize a ref string like "e5", "@e5", or "ref=e5". Returns null if invalid. */
22
+ export declare function parseRoleRef(raw: string): string | null;
23
+ /**
24
+ * Parse Playwright's `ariaSnapshot()` text, assign refs to interactive/content
25
+ * elements, and return the annotated snapshot + a map from ref → role info.
26
+ */
27
+ export declare function buildRoleSnapshotFromAriaSnapshot(ariaSnapshot: string, options?: RoleSnapshotOptions): {
28
+ snapshot: string;
29
+ refs: RoleRefMap;
30
+ };