micro-models-agent 0.57.0 → 0.57.2

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 (233) hide show
  1. package/CHANGELOG.md +518 -476
  2. package/README.md +358 -358
  3. package/dist/certification/certifications.json +493 -493
  4. package/dist/cli/commands.js +447 -0
  5. package/dist/cli/completer.js +167 -0
  6. package/dist/cli/index.js +2 -0
  7. package/dist/cli/main.js +153 -0
  8. package/dist/cli/plugin-commands.js +36 -0
  9. package/dist/cli/repl-commands.js +761 -0
  10. package/dist/cli/repl.js +702 -0
  11. package/dist/cli/run-result.js +33 -0
  12. package/dist/cli/security-commands.js +164 -0
  13. package/dist/cli/setup.js +237 -0
  14. package/dist/config/config.js +276 -0
  15. package/dist/config/defaults.js +141 -0
  16. package/dist/config/domains.js +179 -0
  17. package/dist/config/experts.js +15 -0
  18. package/dist/config/index.js +4 -0
  19. package/dist/config/security.js +213 -0
  20. package/dist/config/types.js +1 -0
  21. package/dist/core/agent-moe.js +102 -0
  22. package/dist/core/agent.js +1018 -0
  23. package/dist/core/bootstrap.js +481 -0
  24. package/dist/core/crash-handler.js +51 -0
  25. package/dist/core/environment.js +199 -0
  26. package/dist/core/index.js +2 -0
  27. package/dist/core/prompt-builder.js +76 -0
  28. package/dist/core/session-logger.js +251 -0
  29. package/dist/core/types.js +1 -0
  30. package/dist/core/version.js +26 -0
  31. package/dist/core/workspace.js +76 -0
  32. package/dist/i18n/en.json +679 -0
  33. package/dist/i18n/index.js +46 -0
  34. package/dist/i18n/ru.json +679 -0
  35. package/dist/index.js +22 -0
  36. package/dist/llm/image-utils.js +143 -0
  37. package/dist/llm/index.js +4 -0
  38. package/dist/llm/model-loader.js +78 -0
  39. package/dist/llm/openai-compat.js +497 -0
  40. package/dist/llm/orchestrator.js +200 -0
  41. package/dist/llm/provider.js +10 -0
  42. package/dist/llm/response.js +39 -0
  43. package/dist/llm/token-counter.js +39 -0
  44. package/dist/llm/types.js +1 -0
  45. package/dist/logger/app-logger.js +189 -0
  46. package/dist/logger/file-log.js +151 -0
  47. package/dist/logger/index.js +1 -0
  48. package/dist/main.js +2351 -1640
  49. package/dist/migration/backup.js +45 -0
  50. package/dist/migration/detect.js +50 -0
  51. package/dist/migration/index.js +2 -0
  52. package/dist/modules/artifacts/store.js +61 -0
  53. package/dist/modules/browser/actions.js +76 -0
  54. package/dist/modules/browser/bridge-client.js +199 -0
  55. package/dist/modules/browser/bridge-path.js +10 -0
  56. package/dist/modules/browser/bridge-server.mjs +219 -219
  57. package/dist/modules/browser/cookie-store.js +24 -0
  58. package/dist/modules/browser/driver.js +136 -0
  59. package/dist/modules/browser/index.js +7 -0
  60. package/dist/modules/browser/module.js +29 -0
  61. package/dist/modules/browser/session.js +342 -0
  62. package/dist/modules/browser/snapshot.js +148 -0
  63. package/dist/modules/browser/types.js +12 -0
  64. package/dist/modules/certification/cli.js +213 -0
  65. package/dist/modules/certification/fact-checker.js +82 -0
  66. package/dist/modules/certification/loader.js +106 -0
  67. package/dist/modules/certification/manifest.js +58 -0
  68. package/dist/modules/certification/runner.js +245 -0
  69. package/dist/modules/certification/scenarios.js +407 -0
  70. package/dist/modules/certification/types.js +1 -0
  71. package/dist/modules/context/chunk-query.js +100 -0
  72. package/dist/modules/context/fact-extractor.js +168 -0
  73. package/dist/modules/context/history.js +15 -0
  74. package/dist/modules/context/index.js +1 -0
  75. package/dist/modules/context/manager.js +440 -0
  76. package/dist/modules/execution/audit-runners.js +206 -0
  77. package/dist/modules/execution/auditor.js +218 -0
  78. package/dist/modules/execution/execution-plugin.js +431 -0
  79. package/dist/modules/execution/index.js +8 -0
  80. package/dist/modules/execution/module.js +625 -0
  81. package/dist/modules/execution/moe-executor.js +304 -0
  82. package/dist/modules/execution/plan-coverage.js +68 -0
  83. package/dist/modules/execution/plan-persister.js +46 -0
  84. package/dist/modules/execution/plan-store.js +196 -0
  85. package/dist/modules/execution/plan-tool.js +677 -0
  86. package/dist/modules/execution/plan-validator.js +153 -0
  87. package/dist/modules/execution/planner.js +94 -0
  88. package/dist/modules/execution/stuck-detector.js +746 -0
  89. package/dist/modules/execution/tracker.js +69 -0
  90. package/dist/modules/execution/types.js +1 -0
  91. package/dist/modules/execution/verifier.js +235 -0
  92. package/dist/modules/execution/windows-commands.js +41 -0
  93. package/dist/modules/hallucination/confidence.js +66 -0
  94. package/dist/modules/hallucination/consistency.js +26 -0
  95. package/dist/modules/hallucination/detector.js +47 -0
  96. package/dist/modules/hallucination/factual.js +169 -0
  97. package/dist/modules/hallucination/index.js +5 -0
  98. package/dist/modules/hallucination/js-identifiers.js +262 -0
  99. package/dist/modules/hallucination/llm-judge.js +101 -0
  100. package/dist/modules/index.js +5 -0
  101. package/dist/modules/indexer/cache.js +40 -0
  102. package/dist/modules/indexer/index.js +3 -0
  103. package/dist/modules/indexer/module.js +246 -0
  104. package/dist/modules/indexer/project-profile.js +183 -0
  105. package/dist/modules/indexer/walker.js +101 -0
  106. package/dist/modules/lsp/check-tool.js +58 -0
  107. package/dist/modules/lsp/client.js +389 -0
  108. package/dist/modules/lsp/command.js +60 -0
  109. package/dist/modules/lsp/config.js +135 -0
  110. package/dist/modules/lsp/index.js +3 -0
  111. package/dist/modules/lsp/module.js +260 -0
  112. package/dist/modules/lsp/probe.js +86 -0
  113. package/dist/modules/lsp/project-root.js +32 -0
  114. package/dist/modules/lsp/startup-check.js +144 -0
  115. package/dist/modules/lsp/types.js +1 -0
  116. package/dist/modules/mcp/client.js +399 -0
  117. package/dist/modules/mcp/index.js +3 -0
  118. package/dist/modules/mcp/module.js +142 -0
  119. package/dist/modules/mcp/registry.js +15 -0
  120. package/dist/modules/memory/index.js +1 -0
  121. package/dist/modules/memory/module.js +96 -0
  122. package/dist/modules/memory/search.js +42 -0
  123. package/dist/modules/memory/store.js +69 -0
  124. package/dist/modules/pipelines/engine.js +60 -0
  125. package/dist/modules/pipelines/index.js +3 -0
  126. package/dist/modules/pipelines/parser.js +56 -0
  127. package/dist/modules/pipelines/template.js +14 -0
  128. package/dist/modules/plugins/builtin/lint-on-write.js +334 -0
  129. package/dist/modules/plugins/builtin/notify.js +9 -0
  130. package/dist/modules/plugins/index.js +1 -0
  131. package/dist/modules/plugins/loader.js +70 -0
  132. package/dist/modules/plugins/manager.js +261 -0
  133. package/dist/modules/plugins/types.js +1 -0
  134. package/dist/modules/pricing/index.js +61 -0
  135. package/dist/modules/pricing/prices.js +129 -0
  136. package/dist/modules/processes/detect.js +34 -0
  137. package/dist/modules/processes/index.js +2 -0
  138. package/dist/modules/processes/registry.js +327 -0
  139. package/dist/modules/processes/runner.js +23 -0
  140. package/dist/modules/providers/create.js +22 -0
  141. package/dist/modules/providers/fallback.js +79 -0
  142. package/dist/modules/providers/health.js +46 -0
  143. package/dist/modules/providers/index.js +5 -0
  144. package/dist/modules/providers/manager.js +161 -0
  145. package/dist/modules/providers/presets.js +128 -0
  146. package/dist/modules/providers/registry.js +22 -0
  147. package/dist/modules/providers/types.js +1 -0
  148. package/dist/modules/registry.js +48 -0
  149. package/dist/modules/security/audit-log.js +136 -0
  150. package/dist/modules/security/audit-notifier.js +292 -0
  151. package/dist/modules/security/command-validator.js +219 -0
  152. package/dist/modules/security/content-scanner.js +53 -0
  153. package/dist/modules/security/data-sanitizer.js +89 -0
  154. package/dist/modules/security/encryption.js +242 -0
  155. package/dist/modules/security/index.js +14 -0
  156. package/dist/modules/security/network-validator.js +88 -0
  157. package/dist/modules/security/path-validator.js +203 -0
  158. package/dist/modules/security/rate-limiter.js +119 -0
  159. package/dist/modules/security/security-policies.js +531 -0
  160. package/dist/modules/security/session-encryption.js +210 -0
  161. package/dist/modules/security/session-isolation.js +95 -0
  162. package/dist/modules/session/index.js +3 -0
  163. package/dist/modules/session/manager.js +172 -0
  164. package/dist/modules/session/module.js +24 -0
  165. package/dist/modules/session/store.js +222 -0
  166. package/dist/modules/session/types.js +1 -0
  167. package/dist/modules/skills/index.js +2 -0
  168. package/dist/modules/skills/loader.js +72 -0
  169. package/dist/modules/skills/matcher.js +27 -0
  170. package/dist/modules/skills/module.js +129 -0
  171. package/dist/modules/types.js +1 -0
  172. package/dist/modules/updater/checker.js +96 -0
  173. package/dist/modules/updater/index.js +2 -0
  174. package/dist/modules/updater/module.js +116 -0
  175. package/dist/modules/user-profile/compressor.js +16 -0
  176. package/dist/modules/user-profile/index.js +1 -0
  177. package/dist/modules/user-profile/profile.js +68 -0
  178. package/dist/skills/builtin/git.md +36 -36
  179. package/dist/skills/builtin/typescript.md +35 -35
  180. package/dist/tools/approve.js +33 -0
  181. package/dist/tools/attach-image.js +101 -0
  182. package/dist/tools/bash.js +519 -0
  183. package/dist/tools/browser.js +115 -0
  184. package/dist/tools/chunk-query.js +100 -0
  185. package/dist/tools/create-dir.js +56 -0
  186. package/dist/tools/delete-file.js +63 -0
  187. package/dist/tools/download-file.js +117 -0
  188. package/dist/tools/edit-file.js +80 -0
  189. package/dist/tools/enable-tools.js +59 -0
  190. package/dist/tools/executor.js +154 -0
  191. package/dist/tools/file-info.js +47 -0
  192. package/dist/tools/filter-tools.js +17 -0
  193. package/dist/tools/glob-tool.js +27 -0
  194. package/dist/tools/grep-tool.js +125 -0
  195. package/dist/tools/hidden-tools-block.js +37 -0
  196. package/dist/tools/index.js +78 -0
  197. package/dist/tools/list-dir.js +49 -0
  198. package/dist/tools/load-skill.js +43 -0
  199. package/dist/tools/mcp-call.js +69 -0
  200. package/dist/tools/move-file.js +86 -0
  201. package/dist/tools/path-utils.js +101 -0
  202. package/dist/tools/pipeline-run.js +145 -0
  203. package/dist/tools/preview.js +2 -0
  204. package/dist/tools/process-kill.js +40 -0
  205. package/dist/tools/process-list.js +37 -0
  206. package/dist/tools/process-log.js +54 -0
  207. package/dist/tools/question.js +141 -0
  208. package/dist/tools/read-file.js +179 -0
  209. package/dist/tools/recall.js +118 -0
  210. package/dist/tools/registry.js +47 -0
  211. package/dist/tools/remember.js +68 -0
  212. package/dist/tools/scope-check.js +32 -0
  213. package/dist/tools/search-history.js +85 -0
  214. package/dist/tools/subagent.js +196 -0
  215. package/dist/tools/types.js +1 -0
  216. package/dist/tools/user-input.js +123 -0
  217. package/dist/tools/web-browse.js +87 -0
  218. package/dist/tools/web-fetch.js +119 -0
  219. package/dist/tools/web-search.js +105 -0
  220. package/dist/tools/write-file.js +82 -0
  221. package/dist/ui/box.js +77 -0
  222. package/dist/ui/colors.js +4 -0
  223. package/dist/ui/diff.js +178 -0
  224. package/dist/ui/index.js +6 -0
  225. package/dist/ui/line-editor.js +822 -0
  226. package/dist/ui/line-math.js +73 -0
  227. package/dist/ui/md-formatter.js +212 -0
  228. package/dist/ui/output.js +13 -0
  229. package/dist/ui/plan-view.js +103 -0
  230. package/dist/ui/renderer.js +259 -0
  231. package/dist/ui/spinner.js +70 -0
  232. package/dist/ui/table.js +144 -0
  233. package/package.json +51 -51
@@ -1,219 +1,219 @@
1
- import { createRequire } from "module";
2
- import readline from "readline";
3
-
4
- const require = createRequire(import.meta.url);
5
- const { chromium } = require("playwright");
6
-
7
- const CONSOLE_CAP = 500;
8
- const NETWORK_CAP = 100;
9
- let maxLineChars = 400;
10
-
11
- let browser = null;
12
- let context = null;
13
- let page = null;
14
- let consoleBuf = [];
15
- let networkBuf = [];
16
- let lastUrl = "";
17
-
18
- function pushConsole(type, text) {
19
- if (consoleBuf.length >= CONSOLE_CAP) return;
20
- const clean = String(text).replace(/\s+/g, " ").trim();
21
- if (!clean) return;
22
- const line = clean.length > maxLineChars ? clean.slice(0, maxLineChars) + "…" : clean;
23
- consoleBuf.push({ type, text: line });
24
- }
25
-
26
- function pushNetwork(err) {
27
- if (networkBuf.length >= NETWORK_CAP) return;
28
- networkBuf.push(err);
29
- }
30
-
31
- function drain() {
32
- const consoleEvents = consoleBuf;
33
- const networkEvents = networkBuf;
34
- consoleBuf = [];
35
- networkBuf = [];
36
- return { console: consoleEvents, network: networkEvents };
37
- }
38
-
39
- function readInt(v, fallback) {
40
- const n = Number(v);
41
- return Number.isFinite(n) && n > 0 ? n : fallback;
42
- }
43
-
44
- async function handleLaunch(params) {
45
- if (browser) return { ok: true };
46
- maxLineChars = readInt(params?.maxConsoleLineChars, 400);
47
- browser = await chromium.launch({
48
- headless: params?.headless !== false,
49
- timeout: readInt(params?.timeoutMs, 30000),
50
- });
51
- context = await browser.newContext({
52
- viewport: params?.viewport || { width: 1280, height: 720 },
53
- });
54
- page = await context.newPage();
55
- page.setDefaultTimeout(readInt(params?.timeoutMs, 30000));
56
-
57
- page.on("console", (msg) => pushConsole(msg.type(), msg.text()));
58
- page.on("pageerror", (err) => pushConsole("pageerror", `Page error: ${err.message}`));
59
- page.on("requestfailed", (req) => {
60
- const failure = req.failure();
61
- pushNetwork({ method: req.method(), url: req.url(), error: failure?.errorText || "unknown" });
62
- });
63
-
64
- return { ok: true };
65
- }
66
-
67
- async function handleGoto(params) {
68
- if (!page) return { ok: false, error: "No page open" };
69
- const url = String(params?.url || "");
70
- const timeoutMs = readInt(params?.timeoutMs, 15000);
71
- consoleBuf = [];
72
- networkBuf = [];
73
- try {
74
- await page.goto(url, { waitUntil: "domcontentloaded", timeout: timeoutMs });
75
- lastUrl = page.url();
76
- return { ok: true, data: { url: lastUrl } };
77
- } catch (err) {
78
- lastUrl = page.url();
79
- return { ok: false, error: err.message, data: { url: lastUrl } };
80
- }
81
- }
82
-
83
- async function handleEvaluate(params) {
84
- if (!page) return { ok: false, error: "No page open" };
85
- const js = String(params?.js || "");
86
- try {
87
- const result = await page.evaluate(js);
88
- return { ok: true, data: { result } };
89
- } catch (err) {
90
- return { ok: false, error: err.message };
91
- }
92
- }
93
-
94
- async function handleClose() {
95
- try {
96
- if (page) await page.close();
97
- } catch {}
98
- try {
99
- if (context) await context.close();
100
- } catch {}
101
- try {
102
- if (browser) await browser.close();
103
- } catch {}
104
- page = null;
105
- context = null;
106
- browser = null;
107
- return { ok: true };
108
- }
109
-
110
- const handlers = {
111
- ping: async () => ({ ok: true, data: { pong: true, runtime: process.version } }),
112
- launch: handleLaunch,
113
- goto: handleGoto,
114
- evaluate: handleEvaluate,
115
- content: async () => ({ ok: true, data: { html: page ? await page.content() : "" } }),
116
- title: async () => ({ ok: true, data: { title: page ? await page.title() : "" } }),
117
- url: async () => ({ ok: true, data: { url: page ? page.url() : lastUrl } }),
118
- waitLoad: async (params) => {
119
- if (!page) return { ok: false, error: "No page open" };
120
- try {
121
- await page.waitForLoadState("domcontentloaded", {
122
- timeout: readInt(params?.timeoutMs, 15000),
123
- });
124
- } catch {}
125
- return { ok: true };
126
- },
127
- screenshot: async () => {
128
- if (!page) return { ok: false, error: "No page open" };
129
- try {
130
- const buf = await page.screenshot({ type: "png", fullPage: false });
131
- return { ok: true, data: { base64: buf.toString("base64") } };
132
- } catch (err) {
133
- return { ok: false, error: err.message };
134
- }
135
- },
136
- cookies: async () => {
137
- if (!context) return { ok: true, data: { cookies: [] } };
138
- const cookies = await context.cookies();
139
- return { ok: true, data: { cookies } };
140
- },
141
- setCookies: async (params) => {
142
- if (!context) return { ok: false, error: "No context" };
143
- const cookies = Array.isArray(params?.cookies) ? params.cookies : [];
144
- if (cookies.length > 0) {
145
- try {
146
- await context.addCookies(cookies);
147
- } catch (err) {
148
- return { ok: false, error: err.message };
149
- }
150
- }
151
- return { ok: true };
152
- },
153
- back: async (params) => {
154
- if (!page) return { ok: false, error: "No page open" };
155
- try {
156
- await page.goBack({
157
- waitUntil: "domcontentloaded",
158
- timeout: readInt(params?.timeoutMs, 15000),
159
- });
160
- } catch {}
161
- lastUrl = page.url();
162
- return { ok: true, data: { url: lastUrl } };
163
- },
164
- forward: async (params) => {
165
- if (!page) return { ok: false, error: "No page open" };
166
- try {
167
- await page.goForward({
168
- waitUntil: "domcontentloaded",
169
- timeout: readInt(params?.timeoutMs, 15000),
170
- });
171
- } catch {}
172
- lastUrl = page.url();
173
- return { ok: true, data: { url: lastUrl } };
174
- },
175
- close: handleClose,
176
- };
177
-
178
- const rl = readline.createInterface({ input: process.stdin });
179
- rl.on("line", async (line) => {
180
- let req;
181
- try {
182
- req = JSON.parse(line);
183
- } catch {
184
- return;
185
- }
186
- const handler = handlers[req.cmd];
187
- let resp;
188
- if (!handler) {
189
- resp = { id: req.id, ok: false, error: `Unknown command: ${req.cmd}`, ...drain() };
190
- } else {
191
- try {
192
- const result = await handler(req.params || {});
193
- resp = { id: req.id, ...result, ...drain() };
194
- } catch (err) {
195
- resp = { id: req.id, ok: false, error: err.message, ...drain() };
196
- }
197
- }
198
- process.stdout.write(JSON.stringify(resp) + "\n");
199
- });
200
-
201
- let shuttingDown = false;
202
- async function shutdown() {
203
- // Idempotent: signals, stdin close and the explicit close command may race.
204
- if (shuttingDown) return;
205
- shuttingDown = true;
206
- try {
207
- await handleClose();
208
- } catch (err) {
209
- // Best-effort cleanup: never block process exit on a broken browser.
210
- console.error("[bridge-server] cleanup during shutdown failed:", err);
211
- }
212
- process.exit(0);
213
- }
214
-
215
- process.on("SIGTERM", () => void shutdown());
216
- process.on("SIGINT", () => void shutdown());
217
- // Parent died without sending "close" (crash, kill -9): stdin EOF is the
218
- // last signal we get — without this the bridge and its Chromium leak.
219
- rl.on("close", () => void shutdown());
1
+ import { createRequire } from "module";
2
+ import readline from "readline";
3
+
4
+ const require = createRequire(import.meta.url);
5
+ const { chromium } = require("playwright");
6
+
7
+ const CONSOLE_CAP = 500;
8
+ const NETWORK_CAP = 100;
9
+ let maxLineChars = 400;
10
+
11
+ let browser = null;
12
+ let context = null;
13
+ let page = null;
14
+ let consoleBuf = [];
15
+ let networkBuf = [];
16
+ let lastUrl = "";
17
+
18
+ function pushConsole(type, text) {
19
+ if (consoleBuf.length >= CONSOLE_CAP) return;
20
+ const clean = String(text).replace(/\s+/g, " ").trim();
21
+ if (!clean) return;
22
+ const line = clean.length > maxLineChars ? clean.slice(0, maxLineChars) + "…" : clean;
23
+ consoleBuf.push({ type, text: line });
24
+ }
25
+
26
+ function pushNetwork(err) {
27
+ if (networkBuf.length >= NETWORK_CAP) return;
28
+ networkBuf.push(err);
29
+ }
30
+
31
+ function drain() {
32
+ const consoleEvents = consoleBuf;
33
+ const networkEvents = networkBuf;
34
+ consoleBuf = [];
35
+ networkBuf = [];
36
+ return { console: consoleEvents, network: networkEvents };
37
+ }
38
+
39
+ function readInt(v, fallback) {
40
+ const n = Number(v);
41
+ return Number.isFinite(n) && n > 0 ? n : fallback;
42
+ }
43
+
44
+ async function handleLaunch(params) {
45
+ if (browser) return { ok: true };
46
+ maxLineChars = readInt(params?.maxConsoleLineChars, 400);
47
+ browser = await chromium.launch({
48
+ headless: params?.headless !== false,
49
+ timeout: readInt(params?.timeoutMs, 30000),
50
+ });
51
+ context = await browser.newContext({
52
+ viewport: params?.viewport || { width: 1280, height: 720 },
53
+ });
54
+ page = await context.newPage();
55
+ page.setDefaultTimeout(readInt(params?.timeoutMs, 30000));
56
+
57
+ page.on("console", (msg) => pushConsole(msg.type(), msg.text()));
58
+ page.on("pageerror", (err) => pushConsole("pageerror", `Page error: ${err.message}`));
59
+ page.on("requestfailed", (req) => {
60
+ const failure = req.failure();
61
+ pushNetwork({ method: req.method(), url: req.url(), error: failure?.errorText || "unknown" });
62
+ });
63
+
64
+ return { ok: true };
65
+ }
66
+
67
+ async function handleGoto(params) {
68
+ if (!page) return { ok: false, error: "No page open" };
69
+ const url = String(params?.url || "");
70
+ const timeoutMs = readInt(params?.timeoutMs, 15000);
71
+ consoleBuf = [];
72
+ networkBuf = [];
73
+ try {
74
+ await page.goto(url, { waitUntil: "domcontentloaded", timeout: timeoutMs });
75
+ lastUrl = page.url();
76
+ return { ok: true, data: { url: lastUrl } };
77
+ } catch (err) {
78
+ lastUrl = page.url();
79
+ return { ok: false, error: err.message, data: { url: lastUrl } };
80
+ }
81
+ }
82
+
83
+ async function handleEvaluate(params) {
84
+ if (!page) return { ok: false, error: "No page open" };
85
+ const js = String(params?.js || "");
86
+ try {
87
+ const result = await page.evaluate(js);
88
+ return { ok: true, data: { result } };
89
+ } catch (err) {
90
+ return { ok: false, error: err.message };
91
+ }
92
+ }
93
+
94
+ async function handleClose() {
95
+ try {
96
+ if (page) await page.close();
97
+ } catch {}
98
+ try {
99
+ if (context) await context.close();
100
+ } catch {}
101
+ try {
102
+ if (browser) await browser.close();
103
+ } catch {}
104
+ page = null;
105
+ context = null;
106
+ browser = null;
107
+ return { ok: true };
108
+ }
109
+
110
+ const handlers = {
111
+ ping: async () => ({ ok: true, data: { pong: true, runtime: process.version } }),
112
+ launch: handleLaunch,
113
+ goto: handleGoto,
114
+ evaluate: handleEvaluate,
115
+ content: async () => ({ ok: true, data: { html: page ? await page.content() : "" } }),
116
+ title: async () => ({ ok: true, data: { title: page ? await page.title() : "" } }),
117
+ url: async () => ({ ok: true, data: { url: page ? page.url() : lastUrl } }),
118
+ waitLoad: async (params) => {
119
+ if (!page) return { ok: false, error: "No page open" };
120
+ try {
121
+ await page.waitForLoadState("domcontentloaded", {
122
+ timeout: readInt(params?.timeoutMs, 15000),
123
+ });
124
+ } catch {}
125
+ return { ok: true };
126
+ },
127
+ screenshot: async () => {
128
+ if (!page) return { ok: false, error: "No page open" };
129
+ try {
130
+ const buf = await page.screenshot({ type: "png", fullPage: false });
131
+ return { ok: true, data: { base64: buf.toString("base64") } };
132
+ } catch (err) {
133
+ return { ok: false, error: err.message };
134
+ }
135
+ },
136
+ cookies: async () => {
137
+ if (!context) return { ok: true, data: { cookies: [] } };
138
+ const cookies = await context.cookies();
139
+ return { ok: true, data: { cookies } };
140
+ },
141
+ setCookies: async (params) => {
142
+ if (!context) return { ok: false, error: "No context" };
143
+ const cookies = Array.isArray(params?.cookies) ? params.cookies : [];
144
+ if (cookies.length > 0) {
145
+ try {
146
+ await context.addCookies(cookies);
147
+ } catch (err) {
148
+ return { ok: false, error: err.message };
149
+ }
150
+ }
151
+ return { ok: true };
152
+ },
153
+ back: async (params) => {
154
+ if (!page) return { ok: false, error: "No page open" };
155
+ try {
156
+ await page.goBack({
157
+ waitUntil: "domcontentloaded",
158
+ timeout: readInt(params?.timeoutMs, 15000),
159
+ });
160
+ } catch {}
161
+ lastUrl = page.url();
162
+ return { ok: true, data: { url: lastUrl } };
163
+ },
164
+ forward: async (params) => {
165
+ if (!page) return { ok: false, error: "No page open" };
166
+ try {
167
+ await page.goForward({
168
+ waitUntil: "domcontentloaded",
169
+ timeout: readInt(params?.timeoutMs, 15000),
170
+ });
171
+ } catch {}
172
+ lastUrl = page.url();
173
+ return { ok: true, data: { url: lastUrl } };
174
+ },
175
+ close: handleClose,
176
+ };
177
+
178
+ const rl = readline.createInterface({ input: process.stdin });
179
+ rl.on("line", async (line) => {
180
+ let req;
181
+ try {
182
+ req = JSON.parse(line);
183
+ } catch {
184
+ return;
185
+ }
186
+ const handler = handlers[req.cmd];
187
+ let resp;
188
+ if (!handler) {
189
+ resp = { id: req.id, ok: false, error: `Unknown command: ${req.cmd}`, ...drain() };
190
+ } else {
191
+ try {
192
+ const result = await handler(req.params || {});
193
+ resp = { id: req.id, ...result, ...drain() };
194
+ } catch (err) {
195
+ resp = { id: req.id, ok: false, error: err.message, ...drain() };
196
+ }
197
+ }
198
+ process.stdout.write(JSON.stringify(resp) + "\n");
199
+ });
200
+
201
+ let shuttingDown = false;
202
+ async function shutdown() {
203
+ // Idempotent: signals, stdin close and the explicit close command may race.
204
+ if (shuttingDown) return;
205
+ shuttingDown = true;
206
+ try {
207
+ await handleClose();
208
+ } catch (err) {
209
+ // Best-effort cleanup: never block process exit on a broken browser.
210
+ console.error("[bridge-server] cleanup during shutdown failed:", err);
211
+ }
212
+ process.exit(0);
213
+ }
214
+
215
+ process.on("SIGTERM", () => void shutdown());
216
+ process.on("SIGINT", () => void shutdown());
217
+ // Parent died without sending "close" (crash, kill -9): stdin EOF is the
218
+ // last signal we get — without this the bridge and its Chromium leak.
219
+ rl.on("close", () => void shutdown());
@@ -0,0 +1,24 @@
1
+ import { readFile, writeFile, mkdir } from "fs/promises";
2
+ import { join } from "path";
3
+ export class CookieStore {
4
+ filePath;
5
+ constructor(cookieDir) {
6
+ this.filePath = join(cookieDir, "cookies.json");
7
+ }
8
+ async save(cookies) {
9
+ await mkdir(join(this.filePath, ".."), { recursive: true });
10
+ await writeFile(this.filePath, JSON.stringify(cookies, null, 2), "utf-8");
11
+ }
12
+ async load() {
13
+ try {
14
+ const data = await readFile(this.filePath, "utf-8");
15
+ return JSON.parse(data);
16
+ }
17
+ catch {
18
+ return [];
19
+ }
20
+ }
21
+ async clear() {
22
+ await this.save([]);
23
+ }
24
+ }
@@ -0,0 +1,136 @@
1
+ import { chromium } from "playwright";
2
+ import { ConsoleBuffer } from "./session";
3
+ export const DIRECT_LAUNCH_TIMEOUT_MS = 8000;
4
+ export class PlaywrightDriver {
5
+ browser = null;
6
+ context = null;
7
+ page = null;
8
+ console = new ConsoleBuffer();
9
+ network = [];
10
+ async launch(cfg) {
11
+ if (this.browser)
12
+ return;
13
+ this.browser = await chromium.launch({
14
+ headless: cfg.headless,
15
+ timeout: cfg.timeoutMs,
16
+ });
17
+ this.context = await this.browser.newContext({ viewport: cfg.viewport });
18
+ this.page = await this.context.newPage();
19
+ this.page.setDefaultTimeout(cfg.timeoutMs);
20
+ this.page.on("console", (msg) => this.console.add(msg.type(), msg.text()));
21
+ this.page.on("pageerror", (err) => this.console.add("pageerror", `Page error: ${err.message}`));
22
+ this.page.on("requestfailed", (req) => {
23
+ const failure = req.failure();
24
+ this.network.push({
25
+ method: req.method(),
26
+ url: req.url(),
27
+ error: failure?.errorText || "unknown",
28
+ });
29
+ });
30
+ }
31
+ async goto(url, timeoutMs) {
32
+ this.requirePage();
33
+ this.console.clear();
34
+ this.network = [];
35
+ await this.page.goto(url, { waitUntil: "domcontentloaded", timeout: timeoutMs });
36
+ }
37
+ async evaluate(js) {
38
+ return this.requirePage().evaluate(js);
39
+ }
40
+ async content() {
41
+ return this.requirePage().content();
42
+ }
43
+ async title() {
44
+ return this.requirePage().title();
45
+ }
46
+ url() {
47
+ return this.page?.url() || "";
48
+ }
49
+ async waitForLoad(timeoutMs) {
50
+ await this.requirePage()
51
+ .waitForLoadState("domcontentloaded", { timeout: timeoutMs })
52
+ .catch(() => { });
53
+ }
54
+ async screenshot() {
55
+ return this.requirePage().screenshot({ type: "png", fullPage: false });
56
+ }
57
+ async cookies() {
58
+ if (!this.context)
59
+ return [];
60
+ return this.context.cookies();
61
+ }
62
+ async addCookies(cookies) {
63
+ if (!this.context || cookies.length === 0)
64
+ return;
65
+ await this.context.addCookies(cookies);
66
+ }
67
+ async goBack(timeoutMs) {
68
+ await this.requirePage()
69
+ .goBack({ waitUntil: "domcontentloaded", timeout: timeoutMs })
70
+ .catch(() => { });
71
+ }
72
+ async goForward(timeoutMs) {
73
+ await this.requirePage()
74
+ .goForward({ waitUntil: "domcontentloaded", timeout: timeoutMs })
75
+ .catch(() => { });
76
+ }
77
+ resetEventLog() {
78
+ this.console.clear();
79
+ this.network = [];
80
+ }
81
+ getConsole() {
82
+ return this.console.getAll();
83
+ }
84
+ getNetworkErrors() {
85
+ return this.network.slice(-15);
86
+ }
87
+ async close() {
88
+ if (this.page)
89
+ await this.page.close().catch(() => { });
90
+ if (this.context)
91
+ await this.context.close().catch(() => { });
92
+ if (this.browser)
93
+ await this.browser.close().catch(() => { });
94
+ this.page = null;
95
+ this.context = null;
96
+ this.browser = null;
97
+ }
98
+ dispose() {
99
+ this.close().catch(() => { });
100
+ }
101
+ requirePage() {
102
+ if (!this.page)
103
+ throw new Error("No page open");
104
+ return this.page;
105
+ }
106
+ }
107
+ export function createBrowserDriver(config) {
108
+ const common = {
109
+ headless: config.headless,
110
+ viewport: { width: config.viewportWidth, height: config.viewportHeight },
111
+ timeoutMs: config.navigationTimeout,
112
+ };
113
+ return (async () => {
114
+ // Playwright's pipe/websocket transport is broken under Bun on some
115
+ // platforms (see oven-sh/bun#31105, #9911). Under Bun we always use the
116
+ // Node bridge. Under Node we try the direct path first.
117
+ if (process.versions.bun) {
118
+ return launchBridge(config, common);
119
+ }
120
+ const direct = new PlaywrightDriver();
121
+ try {
122
+ await direct.launch({ ...common, timeoutMs: DIRECT_LAUNCH_TIMEOUT_MS });
123
+ return direct;
124
+ }
125
+ catch {
126
+ direct.dispose();
127
+ }
128
+ return launchBridge(config, common);
129
+ })();
130
+ }
131
+ async function launchBridge(config, common) {
132
+ const { BridgeDriver } = await import("./bridge-client");
133
+ const bridge = new BridgeDriver({ maxConsoleLineChars: config.maxConsoleLineChars });
134
+ await bridge.launch(common);
135
+ return bridge;
136
+ }
@@ -0,0 +1,7 @@
1
+ export { BrowserModule } from "./module";
2
+ export { BrowserSession, BrowserActionTracker, ConsoleBuffer } from "./session";
3
+ export { CookieStore } from "./cookie-store";
4
+ export { createBrowserDriver, PlaywrightDriver } from "./driver";
5
+ export { BridgeDriver } from "./bridge-client";
6
+ export { extractInteractiveElements, formatSnapshot, truncateText } from "./snapshot";
7
+ export { DEFAULT_BROWSER_CONFIG } from "./types";
@@ -0,0 +1,29 @@
1
+ import { createBrowserTool, closeBrowserSession } from "../../tools/browser";
2
+ export class BrowserModule {
3
+ name = "browser";
4
+ getSystemPromptBlock() {
5
+ return {
6
+ content: [
7
+ "You have a headless browser tool. Use it to navigate websites, click links, fill forms, and read page content.",
8
+ "Workflow: open(url) → read snapshot → click/type on element numbers → read updated snapshot.",
9
+ 'Each snapshot includes a "Content" section with the visible page text, a "Console" section with browser messages, and network errors. Use these to understand the page, detect JS errors, and verify the result of your actions.',
10
+ 'Always start with "open" action. Use element numbers from snapshot for click/type targets.',
11
+ 'Use "screenshot" action only if the model supports vision. Otherwise rely on the text snapshot.',
12
+ ].join("\n"),
13
+ priority: "normal",
14
+ essential: false,
15
+ estimatedTokens: 80,
16
+ };
17
+ }
18
+ getToolDefinitions() {
19
+ return [createBrowserTool()];
20
+ }
21
+ getPlugin() {
22
+ return {
23
+ name: "browser",
24
+ onSessionEnd: () => {
25
+ closeBrowserSession();
26
+ },
27
+ };
28
+ }
29
+ }