micro-models-agent 0.2.75 → 0.2.77

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.
@@ -0,0 +1,379 @@
1
+ import { logInfo, logError } from '../logger.js';
2
+ /**
3
+ * Интерактивный инструмент для работы с веб-страницами через headless-браузер.
4
+ *
5
+ * Поддерживает 9 actions: navigate, click, type, extract, screenshot,
6
+ * scroll, wait, get_links, evaluate.
7
+ *
8
+ * Сессия браузера stateful: navigate открывает страницу,
9
+ * последующие action'ы работают с ней.
10
+ *
11
+ * @module tools/web-browse
12
+ */
13
+ // ─── Browser Session ─────────────────────────────────────
14
+ /** Тип для Playwright (ленивый импорт) */
15
+ let playwrightModule = null;
16
+ async function getPlaywright() {
17
+ if (!playwrightModule) {
18
+ playwrightModule = await import('playwright');
19
+ }
20
+ return playwrightModule;
21
+ }
22
+ const state = {
23
+ browser: null,
24
+ context: null,
25
+ page: null,
26
+ lastActivity: 0,
27
+ };
28
+ /** Таймаут неактивности (5 минут) */
29
+ const IDLE_TIMEOUT_MS = 5 * 60 * 1000;
30
+ /** Таймаут навигации */
31
+ const NAV_TIMEOUT_MS = 30_000;
32
+ /** Инициализация браузера (лениво) */
33
+ async function ensureBrowser() {
34
+ // Проверяем есть ли активная сессия
35
+ if (state.page && !state.page.isClosed()) {
36
+ state.lastActivity = Date.now();
37
+ return state.page;
38
+ }
39
+ // Закрываем старые ресурсы
40
+ await closeBrowser();
41
+ const pw = await getPlaywright();
42
+ state.browser = await pw.chromium.launch({
43
+ headless: true,
44
+ args: ['--no-sandbox', '--disable-gpu'],
45
+ });
46
+ state.context = await state.browser.newContext({
47
+ userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
48
+ viewport: { width: 1280, height: 720 },
49
+ });
50
+ state.page = await state.context.newPage();
51
+ state.lastActivity = Date.now();
52
+ logInfo('BROWSER', 'Headless browser launched');
53
+ return state.page;
54
+ }
55
+ /** Закрытие браузера */
56
+ async function closeBrowser() {
57
+ try {
58
+ if (state.page && !state.page.isClosed())
59
+ await state.page.close();
60
+ if (state.context)
61
+ await state.context.close();
62
+ if (state.browser)
63
+ await state.browser.close();
64
+ }
65
+ catch (e) {
66
+ logError('BROWSER', `close error: ${e.message}`);
67
+ }
68
+ state.browser = null;
69
+ state.context = null;
70
+ state.page = null;
71
+ }
72
+ /** Проверка и закрытие при простое */
73
+ async function checkIdle() {
74
+ if (state.page && Date.now() - state.lastActivity > IDLE_TIMEOUT_MS) {
75
+ logInfo('BROWSER', 'Idle timeout, closing browser');
76
+ await closeBrowser();
77
+ }
78
+ }
79
+ // ─── Actions ─────────────────────────────────────────────
80
+ async function actionNavigate(page, args) {
81
+ const url = String(args.url ?? '').trim();
82
+ if (!url)
83
+ return 'Error: url is required for navigate';
84
+ if (!url.startsWith('http://') && !url.startsWith('https://')) {
85
+ return 'Error: URL must start with http:// or https://';
86
+ }
87
+ const response = await page.goto(url, {
88
+ waitUntil: 'networkidle',
89
+ timeout: NAV_TIMEOUT_MS,
90
+ });
91
+ const status = response?.status() ?? 0;
92
+ const title = await page.title();
93
+ const finalUrl = page.url();
94
+ return `Navigated to: ${finalUrl}\nTitle: ${title}\nStatus: ${status}`;
95
+ }
96
+ async function actionClick(page, args) {
97
+ const selector = String(args.selector ?? '').trim();
98
+ if (!selector)
99
+ return 'Error: selector is required for click';
100
+ // Пробуем CSS-селектор, затем текст
101
+ try {
102
+ await page.click(selector, { timeout: 10_000 });
103
+ }
104
+ catch {
105
+ // Если CSS-селектор не сработал, пробуем getByText
106
+ try {
107
+ await page.getByText(selector, { exact: false }).first().click({ timeout: 10_000 });
108
+ }
109
+ catch (e) {
110
+ return `Error: could not click "${selector}": ${e.message}`;
111
+ }
112
+ }
113
+ // Ждём стабилизации после клика
114
+ await page.waitForLoadState('domcontentloaded').catch(() => { });
115
+ return `Clicked: ${selector}`;
116
+ }
117
+ async function actionType(page, args) {
118
+ const selector = String(args.selector ?? '').trim();
119
+ const value = String(args.value ?? '');
120
+ if (!selector)
121
+ return 'Error: selector is required for type';
122
+ try {
123
+ await page.fill(selector, value, { timeout: 10_000 });
124
+ }
125
+ catch {
126
+ // Fallback: кликаем и печатаем
127
+ try {
128
+ await page.click(selector, { timeout: 5_000 });
129
+ await page.keyboard.type(value, { delay: 30 });
130
+ }
131
+ catch (e) {
132
+ return `Error: could not type into "${selector}": ${e.message}`;
133
+ }
134
+ }
135
+ return `Typed "${value}" into ${selector}`;
136
+ }
137
+ async function actionExtract(page, args) {
138
+ const selector = String(args.selector ?? '').trim();
139
+ const format = String(args.format ?? 'text');
140
+ const maxChars = Number(args.maxChars) || 10_000;
141
+ let content;
142
+ if (selector) {
143
+ // Извлечение из конкретного элемента
144
+ try {
145
+ if (format === 'html') {
146
+ content = await page.innerHTML(selector, { timeout: 10_000 });
147
+ }
148
+ else {
149
+ content = await page.textContent(selector, { timeout: 10_000 }) ?? '';
150
+ }
151
+ }
152
+ catch (e) {
153
+ return `Error: could not extract from "${selector}": ${e.message}`;
154
+ }
155
+ }
156
+ else {
157
+ // Извлечение всей страницы
158
+ if (format === 'html') {
159
+ content = await page.content();
160
+ }
161
+ else {
162
+ content = await page.evaluate(() => document.body?.innerText ?? '');
163
+ }
164
+ }
165
+ // Очистка
166
+ content = content
167
+ .replace(/\t/g, ' ')
168
+ .replace(/[ ]{2,}/g, ' ')
169
+ .replace(/\n{3,}/g, '\n\n')
170
+ .trim();
171
+ if (content.length > maxChars) {
172
+ content = content.slice(0, maxChars) + `\n... (truncated, ${content.length} total chars)`;
173
+ }
174
+ return content || '(empty)';
175
+ }
176
+ async function actionScreenshot(page, args) {
177
+ const selector = String(args.selector ?? '').trim();
178
+ let buffer;
179
+ if (selector) {
180
+ try {
181
+ const element = await page.$(selector);
182
+ if (!element)
183
+ return `Error: element "${selector}" not found`;
184
+ buffer = await element.screenshot({ type: 'png' });
185
+ }
186
+ catch (e) {
187
+ return `Error: screenshot of "${selector}" failed: ${e.message}`;
188
+ }
189
+ }
190
+ else {
191
+ buffer = await page.screenshot({ type: 'png', fullPage: false });
192
+ }
193
+ const base64 = buffer.toString('base64');
194
+ const url = page.url();
195
+ const title = await page.title();
196
+ return `Screenshot of: ${url}\nTitle: ${title}\nSize: ${buffer.length} bytes\nFormat: PNG\n\nbase64:${base64}`;
197
+ }
198
+ async function actionScroll(page, args) {
199
+ const direction = String(args.direction ?? 'down');
200
+ const scrolled = await page.evaluate((dir) => {
201
+ const amount = dir === 'up' ? -window.innerHeight : window.innerHeight;
202
+ window.scrollBy(0, amount);
203
+ return {
204
+ scrollTop: window.scrollY,
205
+ scrollHeight: document.documentElement.scrollHeight,
206
+ clientHeight: window.innerHeight,
207
+ };
208
+ }, direction);
209
+ const atTop = scrolled.scrollTop <= 0;
210
+ const atBottom = scrolled.scrollTop + scrolled.clientHeight >= scrolled.scrollHeight - 10;
211
+ let status = `Scrolled ${direction}. Position: ${scrolled.scrollTop}px`;
212
+ if (atTop && direction === 'up')
213
+ status += ' (at top)';
214
+ if (atBottom && direction === 'down')
215
+ status += ' (at bottom)';
216
+ return status;
217
+ }
218
+ async function actionWait(page, args) {
219
+ const selector = String(args.selector ?? '').trim();
220
+ const timeout = Number(args.timeout) || 5000;
221
+ if (selector) {
222
+ try {
223
+ await page.waitForSelector(selector, { state: 'visible', timeout });
224
+ return `Element "${selector}" appeared`;
225
+ }
226
+ catch {
227
+ return `Timeout: element "${selector}" not found within ${timeout}ms`;
228
+ }
229
+ }
230
+ else {
231
+ await page.waitForTimeout(timeout);
232
+ return `Waited ${timeout}ms`;
233
+ }
234
+ }
235
+ async function actionGetLinks(page, _args) {
236
+ const links = await page.$$eval('a[href]', (anchors) => anchors.map((a) => ({
237
+ text: (a.textContent ?? '').trim().slice(0, 100),
238
+ href: a.getAttribute('href') ?? '',
239
+ })).filter((l) => l.href && !l.href.startsWith('#') && !l.href.startsWith('javascript:')));
240
+ if (links.length === 0)
241
+ return 'No links found on page.';
242
+ const maxLinks = 50;
243
+ let output = `Found ${links.length} links:\n\n`;
244
+ for (let i = 0; i < Math.min(links.length, maxLinks); i++) {
245
+ const l = links[i];
246
+ output += `${i + 1}. ${l.text || '(no text)'}\n ${l.href}\n`;
247
+ }
248
+ if (links.length > maxLinks) {
249
+ output += `\n... and ${links.length - maxLinks} more`;
250
+ }
251
+ return output.trim();
252
+ }
253
+ async function actionEvaluate(page, args) {
254
+ const code = String(args.value ?? args.code ?? '').trim();
255
+ if (!code)
256
+ return 'Error: value (JS code) is required for evaluate';
257
+ try {
258
+ const result = await page.evaluate((script) => {
259
+ // eslint-disable-next-line no-eval
260
+ return eval(script);
261
+ }, code);
262
+ if (result === undefined)
263
+ return '(undefined)';
264
+ if (result === null)
265
+ return '(null)';
266
+ if (typeof result === 'object')
267
+ return JSON.stringify(result, null, 2);
268
+ return String(result);
269
+ }
270
+ catch (e) {
271
+ return `Error: evaluate failed: ${e.message}`;
272
+ }
273
+ }
274
+ // ─── Tool Definition ─────────────────────────────────────
275
+ export const webBrowseDefinition = {
276
+ type: 'function',
277
+ function: {
278
+ name: 'web_browse',
279
+ description: 'Interact with web pages using a headless browser. Navigate, click, type, scroll, extract text/HTML, take screenshots, and execute JavaScript on pages.',
280
+ parameters: {
281
+ type: 'object',
282
+ properties: {
283
+ action: {
284
+ type: 'string',
285
+ enum: ['navigate', 'click', 'type', 'extract', 'screenshot', 'scroll', 'wait', 'get_links', 'evaluate', 'close'],
286
+ description: 'Action to perform on the page',
287
+ },
288
+ url: { type: 'string', description: 'URL to navigate to (for navigate action)' },
289
+ selector: { type: 'string', description: 'CSS selector or text to target an element' },
290
+ value: { type: 'string', description: 'Value to type, or JS code to evaluate' },
291
+ direction: { type: 'string', enum: ['up', 'down'], description: 'Scroll direction (default: down)' },
292
+ format: { type: 'string', enum: ['text', 'html'], description: 'Extract format (default: text)' },
293
+ maxChars: { type: 'number', description: 'Max characters to extract (default: 10000)' },
294
+ timeout: { type: 'number', description: 'Timeout in ms for wait action (default: 5000)' },
295
+ },
296
+ required: ['action'],
297
+ },
298
+ },
299
+ };
300
+ // ─── Handler ─────────────────────────────────────────────
301
+ export const webBrowseHandler = {
302
+ name: 'web_browse',
303
+ definition: webBrowseDefinition,
304
+ async execute(args) {
305
+ const action = String(args.action ?? '').trim();
306
+ if (!action) {
307
+ return { stdout: '', stderr: 'action is required', exitCode: 1, truncated: false };
308
+ }
309
+ try {
310
+ // Проверка простой сессии перед каждым action'ом
311
+ await checkIdle();
312
+ // Close action
313
+ if (action === 'close') {
314
+ await closeBrowser();
315
+ return { stdout: 'Browser closed.', stderr: '', exitCode: 0, truncated: false };
316
+ }
317
+ // Navigate не требует существующей страницы
318
+ if (action === 'navigate') {
319
+ // Валидация URL до запуска браузера
320
+ const url = String(args.url ?? '').trim();
321
+ if (!url) {
322
+ return { stdout: '', stderr: 'url is required for navigate', exitCode: 1, truncated: false };
323
+ }
324
+ if (!url.startsWith('http://') && !url.startsWith('https://')) {
325
+ return { stdout: '', stderr: 'URL must start with http:// or https://', exitCode: 1, truncated: false };
326
+ }
327
+ const page = await ensureBrowser();
328
+ const result = await actionNavigate(page, args);
329
+ return { stdout: result, stderr: '', exitCode: 0, truncated: false };
330
+ }
331
+ // Все остальные action'ы требуют активную страницу
332
+ if (!state.page || state.page.isClosed()) {
333
+ return {
334
+ stdout: '',
335
+ stderr: 'No active page. Use navigate action first.',
336
+ exitCode: 1,
337
+ truncated: false,
338
+ };
339
+ }
340
+ let result;
341
+ switch (action) {
342
+ case 'click':
343
+ result = await actionClick(state.page, args);
344
+ break;
345
+ case 'type':
346
+ result = await actionType(state.page, args);
347
+ break;
348
+ case 'extract':
349
+ result = await actionExtract(state.page, args);
350
+ break;
351
+ case 'screenshot':
352
+ result = await actionScreenshot(state.page, args);
353
+ break;
354
+ case 'scroll':
355
+ result = await actionScroll(state.page, args);
356
+ break;
357
+ case 'wait':
358
+ result = await actionWait(state.page, args);
359
+ break;
360
+ case 'get_links':
361
+ result = await actionGetLinks(state.page, args);
362
+ break;
363
+ case 'evaluate':
364
+ result = await actionEvaluate(state.page, args);
365
+ break;
366
+ default:
367
+ return { stdout: '', stderr: `Unknown action: ${action}`, exitCode: 1, truncated: false };
368
+ }
369
+ state.lastActivity = Date.now();
370
+ const exitCode = result.startsWith('Error') || result.startsWith('Timeout') ? 1 : 0;
371
+ return { stdout: result, stderr: '', exitCode, truncated: false };
372
+ }
373
+ catch (e) {
374
+ logError('BROWSER', `${action} error: ${e.message}`);
375
+ return { stdout: '', stderr: `Browser error: ${e.message}`, exitCode: 1, truncated: false };
376
+ }
377
+ },
378
+ };
379
+ //# sourceMappingURL=web-browse.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"web-browse.js","sourceRoot":"","sources":["../../src/tools/web-browse.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "micro-models-agent",
3
- "version": "0.2.75",
3
+ "version": "0.2.77",
4
4
  "description": "Micro Models Agent (MMA) — LLM agent harness for small models (Qwen3.5-9B, 32K-64K context)",
5
5
  "type": "module",
6
6
  "bin": {
@@ -22,7 +22,7 @@
22
22
  "prepublishOnly": "npm run build",
23
23
  "prepack": "npm run build",
24
24
  "dev": "bun --watch src/index.ts",
25
- "test": "bun test tests/context.test.ts tests/sanitizer.test.ts tests/code-index.test.ts tests/plan-todo-memory.test.ts tests/file-loader-syntax.test.ts tests/commands.test.ts tests/subagent.test.ts tests/specialist-config.test.ts tests/tool-executor.test.ts tests/specialist-integration.test.ts tests/ink-ui.test.tsx tests/fetch-retry.test.ts",
25
+ "test": "bun test tests/context.test.ts tests/sanitizer.test.ts tests/code-index.test.ts tests/plan-todo-memory.test.ts tests/file-loader-syntax.test.ts tests/commands.test.ts tests/subagent.test.ts tests/specialist-config.test.ts tests/tool-executor.test.ts tests/specialist-integration.test.ts tests/ink-ui.test.tsx tests/fetch-retry.test.ts tests/web-browse.test.ts",
26
26
  "test:integration": "bun test tests/llm-integration.test.ts tests/agent-integration.test.ts tests/subagent-integration.test.ts",
27
27
  "test:all": "bun test",
28
28
  "test:watch": "vitest"
@@ -36,6 +36,7 @@
36
36
  "js-tiktoken": "^1.0.0",
37
37
  "jsonrepair": "^3.15.0",
38
38
  "picocolors": "^1.1.1",
39
+ "playwright": "^1.61.1",
39
40
  "react": "^19.0.0"
40
41
  },
41
42
  "devDependencies": {