micro-models-agent 0.43.3 → 0.45.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +312 -312
- package/bin/mma.mjs +41 -41
- package/dist/main.js +333 -66
- package/dist/skills/builtin/git.md +36 -36
- package/dist/skills/builtin/typescript.md +35 -35
- package/package.json +1 -1
- package/dist/cli/commands.js +0 -333
- package/dist/cli/completer.js +0 -168
- package/dist/cli/index.js +0 -2
- package/dist/cli/main.js +0 -140
- package/dist/cli/repl-commands.js +0 -633
- package/dist/cli/repl.js +0 -486
- package/dist/cli/security-commands.js +0 -166
- package/dist/cli/setup.js +0 -249
- package/dist/config/config.js +0 -202
- package/dist/config/defaults.js +0 -100
- package/dist/config/experts.js +0 -15
- package/dist/config/index.js +0 -3
- package/dist/config/security.js +0 -200
- package/dist/config/types.js +0 -1
- package/dist/core/agent-moe.js +0 -110
- package/dist/core/agent.js +0 -695
- package/dist/core/bootstrap.js +0 -337
- package/dist/core/index.js +0 -2
- package/dist/core/prompt-builder.js +0 -55
- package/dist/core/session-logger.js +0 -155
- package/dist/core/types.js +0 -1
- package/dist/core/workspace.js +0 -76
- package/dist/i18n/en.json +0 -525
- package/dist/i18n/index.js +0 -46
- package/dist/i18n/ru.json +0 -525
- package/dist/index.js +0 -22
- package/dist/llm/image-utils.js +0 -144
- package/dist/llm/index.js +0 -4
- package/dist/llm/model-loader.js +0 -78
- package/dist/llm/openai-compat.js +0 -353
- package/dist/llm/orchestrator.js +0 -194
- package/dist/llm/provider.js +0 -10
- package/dist/llm/response.js +0 -39
- package/dist/llm/token-counter.js +0 -39
- package/dist/llm/types.js +0 -1
- package/dist/logger/app-logger.js +0 -143
- package/dist/logger/file-log.js +0 -151
- package/dist/logger/index.js +0 -1
- package/dist/migration/backup.js +0 -45
- package/dist/migration/detect.js +0 -50
- package/dist/migration/index.js +0 -2
- package/dist/modules/browser/actions.js +0 -46
- package/dist/modules/browser/cookie-store.js +0 -24
- package/dist/modules/browser/index.js +0 -5
- package/dist/modules/browser/module.js +0 -28
- package/dist/modules/browser/session.js +0 -335
- package/dist/modules/browser/snapshot.js +0 -114
- package/dist/modules/browser/types.js +0 -9
- package/dist/modules/certification/cli.js +0 -176
- package/dist/modules/certification/fact-checker.js +0 -84
- package/dist/modules/certification/loader.js +0 -111
- package/dist/modules/certification/manifest.js +0 -50
- package/dist/modules/certification/runner.js +0 -162
- package/dist/modules/certification/scenarios.js +0 -124
- package/dist/modules/certification/types.js +0 -1
- package/dist/modules/context/index.js +0 -1
- package/dist/modules/context/manager.js +0 -349
- package/dist/modules/execution/auditor.js +0 -66
- package/dist/modules/execution/index.js +0 -8
- package/dist/modules/execution/module.js +0 -779
- package/dist/modules/execution/moe-executor.js +0 -266
- package/dist/modules/execution/plan-coverage.js +0 -68
- package/dist/modules/execution/plan-persister.js +0 -46
- package/dist/modules/execution/plan-store.js +0 -159
- package/dist/modules/execution/plan-validator.js +0 -153
- package/dist/modules/execution/planner.js +0 -85
- package/dist/modules/execution/stuck-detector.js +0 -347
- package/dist/modules/execution/tracker.js +0 -67
- package/dist/modules/execution/types.js +0 -1
- package/dist/modules/execution/verifier.js +0 -178
- package/dist/modules/hallucination/confidence.js +0 -59
- package/dist/modules/hallucination/consistency.js +0 -26
- package/dist/modules/hallucination/detector.js +0 -46
- package/dist/modules/hallucination/factual.js +0 -190
- package/dist/modules/hallucination/index.js +0 -5
- package/dist/modules/hallucination/js-identifiers.js +0 -72
- package/dist/modules/hallucination/llm-judge.js +0 -103
- package/dist/modules/index.js +0 -5
- package/dist/modules/indexer/cache.js +0 -38
- package/dist/modules/indexer/index.js +0 -3
- package/dist/modules/indexer/module.js +0 -192
- package/dist/modules/indexer/walker.js +0 -101
- package/dist/modules/lsp/client.js +0 -235
- package/dist/modules/lsp/config.js +0 -81
- package/dist/modules/lsp/index.js +0 -3
- package/dist/modules/lsp/module.js +0 -68
- package/dist/modules/lsp/types.js +0 -1
- package/dist/modules/mcp/client.js +0 -399
- package/dist/modules/mcp/index.js +0 -3
- package/dist/modules/mcp/module.js +0 -146
- package/dist/modules/mcp/registry.js +0 -15
- package/dist/modules/memory/index.js +0 -1
- package/dist/modules/memory/module.js +0 -48
- package/dist/modules/memory/search.js +0 -40
- package/dist/modules/memory/store.js +0 -69
- package/dist/modules/pipelines/engine.js +0 -60
- package/dist/modules/pipelines/index.js +0 -3
- package/dist/modules/pipelines/parser.js +0 -53
- package/dist/modules/pipelines/template.js +0 -14
- package/dist/modules/plugins/builtin/lint-on-write.js +0 -226
- package/dist/modules/plugins/builtin/notify.js +0 -8
- package/dist/modules/plugins/index.js +0 -1
- package/dist/modules/plugins/loader.js +0 -28
- package/dist/modules/plugins/manager.js +0 -161
- package/dist/modules/plugins/types.js +0 -1
- package/dist/modules/processes/index.js +0 -2
- package/dist/modules/processes/registry.js +0 -238
- package/dist/modules/processes/runner.js +0 -23
- package/dist/modules/registry.js +0 -45
- package/dist/modules/security/audit-log.js +0 -136
- package/dist/modules/security/audit-notifier.js +0 -292
- package/dist/modules/security/command-validator.js +0 -211
- package/dist/modules/security/content-scanner.js +0 -53
- package/dist/modules/security/data-sanitizer.js +0 -97
- package/dist/modules/security/encryption.js +0 -240
- package/dist/modules/security/index.js +0 -14
- package/dist/modules/security/network-validator.js +0 -79
- package/dist/modules/security/path-validator.js +0 -209
- package/dist/modules/security/rate-limiter.js +0 -119
- package/dist/modules/security/security-policies.js +0 -547
- package/dist/modules/security/session-encryption.js +0 -210
- package/dist/modules/security/session-isolation.js +0 -95
- package/dist/modules/session/index.js +0 -3
- package/dist/modules/session/manager.js +0 -172
- package/dist/modules/session/module.js +0 -24
- package/dist/modules/session/store.js +0 -228
- package/dist/modules/session/types.js +0 -1
- package/dist/modules/skills/index.js +0 -2
- package/dist/modules/skills/loader.js +0 -72
- package/dist/modules/skills/module.js +0 -130
- package/dist/modules/types.js +0 -1
- package/dist/modules/updater/checker.js +0 -32
- package/dist/modules/updater/index.js +0 -1
- package/dist/modules/user-profile/compressor.js +0 -16
- package/dist/modules/user-profile/index.js +0 -1
- package/dist/modules/user-profile/profile.js +0 -68
- package/dist/tools/approve.js +0 -32
- package/dist/tools/attach-image.js +0 -89
- package/dist/tools/bash.js +0 -337
- package/dist/tools/browser.js +0 -97
- package/dist/tools/create-dir.js +0 -55
- package/dist/tools/delete-file.js +0 -62
- package/dist/tools/edit-file.js +0 -79
- package/dist/tools/executor.js +0 -145
- package/dist/tools/file-info.js +0 -45
- package/dist/tools/filter-tools.js +0 -10
- package/dist/tools/glob-tool.js +0 -26
- package/dist/tools/grep-tool.js +0 -86
- package/dist/tools/index.js +0 -67
- package/dist/tools/list-dir.js +0 -47
- package/dist/tools/load-skill.js +0 -44
- package/dist/tools/mcp-call.js +0 -68
- package/dist/tools/move-file.js +0 -85
- package/dist/tools/path-utils.js +0 -51
- package/dist/tools/pipeline-run.js +0 -144
- package/dist/tools/preview.js +0 -2
- package/dist/tools/process-kill.js +0 -29
- package/dist/tools/process-list.js +0 -38
- package/dist/tools/process-log.js +0 -41
- package/dist/tools/question.js +0 -142
- package/dist/tools/read-file.js +0 -83
- package/dist/tools/recall.js +0 -110
- package/dist/tools/registry.js +0 -36
- package/dist/tools/remember.js +0 -67
- package/dist/tools/scope-check.js +0 -30
- package/dist/tools/search-history.js +0 -84
- package/dist/tools/subagent.js +0 -151
- package/dist/tools/types.js +0 -1
- package/dist/tools/user-input.js +0 -123
- package/dist/tools/web-browse.js +0 -86
- package/dist/tools/web-fetch.js +0 -98
- package/dist/tools/web-search.js +0 -78
- package/dist/tools/write-file.js +0 -83
- package/dist/ui/box.js +0 -81
- package/dist/ui/colors.js +0 -4
- package/dist/ui/diff.js +0 -178
- package/dist/ui/index.js +0 -6
- package/dist/ui/md-formatter.js +0 -212
- package/dist/ui/output.js +0 -13
- package/dist/ui/renderer.js +0 -204
- package/dist/ui/spinner.js +0 -70
- package/dist/ui/table.js +0 -144
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
export function buildClickScript(elementIndex) {
|
|
2
|
-
return `
|
|
3
|
-
const el = document.querySelector('[data-browser-index="${elementIndex}"]');
|
|
4
|
-
if (!el) throw new Error('Element not found: ${elementIndex}');
|
|
5
|
-
el.click();
|
|
6
|
-
`;
|
|
7
|
-
}
|
|
8
|
-
export function buildTypeScript(elementIndex, text) {
|
|
9
|
-
const escaped = JSON.stringify(text);
|
|
10
|
-
return `
|
|
11
|
-
const el = document.querySelector('[data-browser-index="${elementIndex}"]');
|
|
12
|
-
if (!el) throw new Error('Element not found: ${elementIndex}');
|
|
13
|
-
const text = JSON.parse(${JSON.stringify(escaped)});
|
|
14
|
-
el.focus();
|
|
15
|
-
el.value = text;
|
|
16
|
-
el.dispatchEvent(new Event('input', { bubbles: true }));
|
|
17
|
-
el.dispatchEvent(new Event('change', { bubbles: true }));
|
|
18
|
-
`;
|
|
19
|
-
}
|
|
20
|
-
export function buildScrollScript(direction) {
|
|
21
|
-
switch (direction) {
|
|
22
|
-
case 'down':
|
|
23
|
-
return `window.scrollBy({ top: 600, behavior: 'smooth' })`;
|
|
24
|
-
case 'up':
|
|
25
|
-
return `window.scrollBy({ top: -600, behavior: 'smooth' })`;
|
|
26
|
-
case 'top':
|
|
27
|
-
return `window.scrollTo({ top: 0, behavior: 'smooth' })`;
|
|
28
|
-
case 'bottom':
|
|
29
|
-
return `window.scrollTo({ top: document.body.scrollHeight, behavior: 'smooth' })`;
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
export function buildWaitScript(timeoutMs) {
|
|
33
|
-
return `await new Promise(r => setTimeout(r, ${timeoutMs}))`;
|
|
34
|
-
}
|
|
35
|
-
export function buildIndexInjectionScript() {
|
|
36
|
-
return `
|
|
37
|
-
const selectors = 'a[href], button, input, textarea, select, [role="button"], [role="link"], [role="textbox"], [role="checkbox"], [role="radio"], [contenteditable="true"]';
|
|
38
|
-
const elements = document.querySelectorAll(selectors);
|
|
39
|
-
let idx = 1;
|
|
40
|
-
elements.forEach(el => {
|
|
41
|
-
const style = window.getComputedStyle(el);
|
|
42
|
-
if (style.display === 'none' || style.visibility === 'hidden') return;
|
|
43
|
-
el.setAttribute('data-browser-index', String(idx++));
|
|
44
|
-
});
|
|
45
|
-
`;
|
|
46
|
-
}
|
|
@@ -1,24 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
export { BrowserModule } from './module';
|
|
2
|
-
export { BrowserSession, BrowserActionTracker } from './session';
|
|
3
|
-
export { CookieStore } from './cookie-store';
|
|
4
|
-
export { extractInteractiveElements, formatSnapshot } from './snapshot';
|
|
5
|
-
export { DEFAULT_BROWSER_CONFIG } from './types';
|
|
@@ -1,28 +0,0 @@
|
|
|
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
|
-
'Always start with "open" action. Use element numbers from snapshot for click/type targets.',
|
|
10
|
-
'Use "screenshot" action only if the model supports vision. Otherwise rely on the text snapshot.',
|
|
11
|
-
].join('\n'),
|
|
12
|
-
priority: 'normal',
|
|
13
|
-
essential: false,
|
|
14
|
-
estimatedTokens: 80,
|
|
15
|
-
};
|
|
16
|
-
}
|
|
17
|
-
getToolDefinitions() {
|
|
18
|
-
return [createBrowserTool()];
|
|
19
|
-
}
|
|
20
|
-
getPlugin() {
|
|
21
|
-
return {
|
|
22
|
-
name: 'browser',
|
|
23
|
-
onSessionEnd: () => {
|
|
24
|
-
closeBrowserSession();
|
|
25
|
-
},
|
|
26
|
-
};
|
|
27
|
-
}
|
|
28
|
-
}
|
|
@@ -1,335 +0,0 @@
|
|
|
1
|
-
import { chromium, } from "playwright";
|
|
2
|
-
import { extractInteractiveElements, formatSnapshot } from "./snapshot";
|
|
3
|
-
import { buildClickScript, buildTypeScript, buildScrollScript, buildIndexInjectionScript, } from "./actions";
|
|
4
|
-
import { CookieStore } from "./cookie-store";
|
|
5
|
-
import { t } from "../../i18n/index";
|
|
6
|
-
export class BrowserActionTracker {
|
|
7
|
-
history = [];
|
|
8
|
-
threshold;
|
|
9
|
-
constructor(threshold = 3) {
|
|
10
|
-
this.threshold = threshold;
|
|
11
|
-
}
|
|
12
|
-
record(action, args, url) {
|
|
13
|
-
this.history.push({ action, args, url });
|
|
14
|
-
if (this.history.length < this.threshold)
|
|
15
|
-
return null;
|
|
16
|
-
const recent = this.history.slice(-this.threshold);
|
|
17
|
-
const allSame = recent.every((h) => h.action === recent[0].action &&
|
|
18
|
-
h.url === recent[0].url &&
|
|
19
|
-
JSON.stringify(h.args) === JSON.stringify(recent[0].args));
|
|
20
|
-
if (allSame) {
|
|
21
|
-
return t("browser.repeated_action", {
|
|
22
|
-
action,
|
|
23
|
-
threshold: this.threshold,
|
|
24
|
-
});
|
|
25
|
-
}
|
|
26
|
-
return null;
|
|
27
|
-
}
|
|
28
|
-
reset() {
|
|
29
|
-
this.history = [];
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
export class BrowserSession {
|
|
33
|
-
browser = null;
|
|
34
|
-
context = null;
|
|
35
|
-
page = null;
|
|
36
|
-
config;
|
|
37
|
-
cookieStore;
|
|
38
|
-
state = {
|
|
39
|
-
isOpen: false,
|
|
40
|
-
url: null,
|
|
41
|
-
title: null,
|
|
42
|
-
elementCount: 0,
|
|
43
|
-
};
|
|
44
|
-
actionTracker = new BrowserActionTracker(3);
|
|
45
|
-
constructor(config) {
|
|
46
|
-
this.config = config;
|
|
47
|
-
this.cookieStore = new CookieStore(config.cookieDir);
|
|
48
|
-
}
|
|
49
|
-
getState() {
|
|
50
|
-
return { ...this.state };
|
|
51
|
-
}
|
|
52
|
-
async execute(action, args) {
|
|
53
|
-
try {
|
|
54
|
-
let result;
|
|
55
|
-
switch (action) {
|
|
56
|
-
case "open":
|
|
57
|
-
result = await this.open(String(args.url || ""));
|
|
58
|
-
break;
|
|
59
|
-
case "click":
|
|
60
|
-
result = await this.click(Number(args.target));
|
|
61
|
-
break;
|
|
62
|
-
case "type":
|
|
63
|
-
result = await this.type(Number(args.target), String(args.text || ""));
|
|
64
|
-
break;
|
|
65
|
-
case "scroll":
|
|
66
|
-
result = await this.scroll(String(args.direction || "down"));
|
|
67
|
-
break;
|
|
68
|
-
case "back":
|
|
69
|
-
result = await this.back();
|
|
70
|
-
break;
|
|
71
|
-
case "forward":
|
|
72
|
-
result = await this.forward();
|
|
73
|
-
break;
|
|
74
|
-
case "screenshot":
|
|
75
|
-
result = await this.screenshot();
|
|
76
|
-
break;
|
|
77
|
-
case "snapshot":
|
|
78
|
-
result = await this.snapshot();
|
|
79
|
-
break;
|
|
80
|
-
case "close":
|
|
81
|
-
result = await this.close();
|
|
82
|
-
break;
|
|
83
|
-
case "wait":
|
|
84
|
-
result = await this.wait(Number(args.ms || 1000));
|
|
85
|
-
break;
|
|
86
|
-
default:
|
|
87
|
-
return {
|
|
88
|
-
success: false,
|
|
89
|
-
output: t("browser.unknown_action", { action }),
|
|
90
|
-
};
|
|
91
|
-
}
|
|
92
|
-
if (action !== "open") {
|
|
93
|
-
const warning = this.actionTracker.record(action, args, this.page?.url() || "");
|
|
94
|
-
if (warning && result.success) {
|
|
95
|
-
result.output = result.output + "\n\n⚠️ " + warning;
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
return result;
|
|
99
|
-
}
|
|
100
|
-
catch (err) {
|
|
101
|
-
return {
|
|
102
|
-
success: false,
|
|
103
|
-
output: t("browser.error", { message: err.message }),
|
|
104
|
-
};
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
async ensurePage() {
|
|
108
|
-
if (!this.page) {
|
|
109
|
-
return { success: false, output: t("browser.no_page") };
|
|
110
|
-
}
|
|
111
|
-
return null;
|
|
112
|
-
}
|
|
113
|
-
async launch() {
|
|
114
|
-
if (this.browser)
|
|
115
|
-
return;
|
|
116
|
-
this.browser = await chromium.launch({ headless: this.config.headless });
|
|
117
|
-
this.context = await this.browser.newContext({
|
|
118
|
-
viewport: {
|
|
119
|
-
width: this.config.viewportWidth,
|
|
120
|
-
height: this.config.viewportHeight,
|
|
121
|
-
},
|
|
122
|
-
});
|
|
123
|
-
const savedCookies = await this.cookieStore.load();
|
|
124
|
-
if (savedCookies.length > 0) {
|
|
125
|
-
await this.context.addCookies(savedCookies.map((c) => ({
|
|
126
|
-
...c,
|
|
127
|
-
sameSite: c.sameSite,
|
|
128
|
-
})));
|
|
129
|
-
}
|
|
130
|
-
this.page = await this.context.newPage();
|
|
131
|
-
this.page.setDefaultTimeout(this.config.navigationTimeout);
|
|
132
|
-
}
|
|
133
|
-
async saveCookies() {
|
|
134
|
-
if (!this.context)
|
|
135
|
-
return;
|
|
136
|
-
const cookies = await this.context.cookies();
|
|
137
|
-
await this.cookieStore.save(cookies);
|
|
138
|
-
}
|
|
139
|
-
async takeSnapshot() {
|
|
140
|
-
if (!this.page)
|
|
141
|
-
return "No page open.";
|
|
142
|
-
await this.page.evaluate(buildIndexInjectionScript());
|
|
143
|
-
const html = await this.page.content();
|
|
144
|
-
const url = this.page.url();
|
|
145
|
-
const title = await this.page.title();
|
|
146
|
-
const allElements = extractInteractiveElements(html, this.config.maxElements + 5);
|
|
147
|
-
const truncated = allElements.length > this.config.maxElements;
|
|
148
|
-
const elements = allElements.slice(0, this.config.maxElements);
|
|
149
|
-
this.state = {
|
|
150
|
-
isOpen: true,
|
|
151
|
-
url,
|
|
152
|
-
title,
|
|
153
|
-
elementCount: elements.length,
|
|
154
|
-
};
|
|
155
|
-
return formatSnapshot({ url, title, elements, truncated });
|
|
156
|
-
}
|
|
157
|
-
async open(url) {
|
|
158
|
-
if (!url)
|
|
159
|
-
return { success: false, output: t("browser.url_required") };
|
|
160
|
-
if (!url.startsWith("http://") &&
|
|
161
|
-
!url.startsWith("https://") &&
|
|
162
|
-
!url.startsWith("file://")) {
|
|
163
|
-
const isLocalhost = /^(localhost|127\.0\.0\.1|0\.0\.0\.0)(:\d+)?$/i.test(url);
|
|
164
|
-
url = isLocalhost ? "http://" + url : "https://" + url;
|
|
165
|
-
}
|
|
166
|
-
await this.launch();
|
|
167
|
-
if (!this.page)
|
|
168
|
-
return { success: false, output: t("browser.create_page_failed") };
|
|
169
|
-
this.actionTracker.reset();
|
|
170
|
-
try {
|
|
171
|
-
await this.page.goto(url, {
|
|
172
|
-
waitUntil: "domcontentloaded",
|
|
173
|
-
timeout: this.config.navigationTimeout,
|
|
174
|
-
});
|
|
175
|
-
}
|
|
176
|
-
catch (err) {
|
|
177
|
-
return {
|
|
178
|
-
success: false,
|
|
179
|
-
output: t("browser.nav_failed", { message: err.message }),
|
|
180
|
-
};
|
|
181
|
-
}
|
|
182
|
-
const snapshot = await this.takeSnapshot();
|
|
183
|
-
await this.saveCookies();
|
|
184
|
-
return { success: true, output: snapshot };
|
|
185
|
-
}
|
|
186
|
-
async click(target) {
|
|
187
|
-
const err = await this.ensurePage();
|
|
188
|
-
if (err)
|
|
189
|
-
return err;
|
|
190
|
-
if (!this.page)
|
|
191
|
-
return { success: false, output: "No page" };
|
|
192
|
-
if (!target || target < 1) {
|
|
193
|
-
return { success: false, output: t("browser.invalid_target") };
|
|
194
|
-
}
|
|
195
|
-
try {
|
|
196
|
-
const script = buildClickScript(target);
|
|
197
|
-
await this.page.evaluate(script);
|
|
198
|
-
await this.page
|
|
199
|
-
.waitForLoadState("domcontentloaded", { timeout: 5000 })
|
|
200
|
-
.catch(() => { });
|
|
201
|
-
await new Promise((r) => setTimeout(r, 500));
|
|
202
|
-
const snapshot = await this.takeSnapshot();
|
|
203
|
-
await this.saveCookies();
|
|
204
|
-
return { success: true, output: snapshot };
|
|
205
|
-
}
|
|
206
|
-
catch (err) {
|
|
207
|
-
return {
|
|
208
|
-
success: false,
|
|
209
|
-
output: t("browser.click_failed", { message: err.message }),
|
|
210
|
-
};
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
async type(target, text) {
|
|
214
|
-
const err = await this.ensurePage();
|
|
215
|
-
if (err)
|
|
216
|
-
return err;
|
|
217
|
-
if (!this.page)
|
|
218
|
-
return { success: false, output: "No page" };
|
|
219
|
-
if (!target || target < 1) {
|
|
220
|
-
return { success: false, output: t("browser.invalid_target_short") };
|
|
221
|
-
}
|
|
222
|
-
if (!text) {
|
|
223
|
-
return { success: false, output: t("browser.text_required") };
|
|
224
|
-
}
|
|
225
|
-
try {
|
|
226
|
-
const script = buildTypeScript(target, text);
|
|
227
|
-
await this.page.evaluate(script);
|
|
228
|
-
const snapshot = await this.takeSnapshot();
|
|
229
|
-
return { success: true, output: snapshot };
|
|
230
|
-
}
|
|
231
|
-
catch (err) {
|
|
232
|
-
return {
|
|
233
|
-
success: false,
|
|
234
|
-
output: t("browser.type_failed", { message: err.message }),
|
|
235
|
-
};
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
async scroll(direction) {
|
|
239
|
-
const err = await this.ensurePage();
|
|
240
|
-
if (err)
|
|
241
|
-
return err;
|
|
242
|
-
if (!this.page)
|
|
243
|
-
return { success: false, output: "No page" };
|
|
244
|
-
const dir = direction;
|
|
245
|
-
if (!["up", "down", "top", "bottom"].includes(dir)) {
|
|
246
|
-
return { success: false, output: t("browser.direction_invalid") };
|
|
247
|
-
}
|
|
248
|
-
try {
|
|
249
|
-
await this.page.evaluate(buildScrollScript(dir));
|
|
250
|
-
await new Promise((r) => setTimeout(r, 300));
|
|
251
|
-
const snapshot = await this.takeSnapshot();
|
|
252
|
-
return { success: true, output: snapshot };
|
|
253
|
-
}
|
|
254
|
-
catch (err) {
|
|
255
|
-
return {
|
|
256
|
-
success: false,
|
|
257
|
-
output: t("browser.scroll_failed", { message: err.message }),
|
|
258
|
-
};
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
async back() {
|
|
262
|
-
const err = await this.ensurePage();
|
|
263
|
-
if (err)
|
|
264
|
-
return err;
|
|
265
|
-
if (!this.page)
|
|
266
|
-
return { success: false, output: "No page" };
|
|
267
|
-
await this.page
|
|
268
|
-
.goBack({
|
|
269
|
-
waitUntil: "domcontentloaded",
|
|
270
|
-
timeout: this.config.navigationTimeout,
|
|
271
|
-
})
|
|
272
|
-
.catch(() => { });
|
|
273
|
-
await new Promise((r) => setTimeout(r, 300));
|
|
274
|
-
const snapshot = await this.takeSnapshot();
|
|
275
|
-
return { success: true, output: snapshot };
|
|
276
|
-
}
|
|
277
|
-
async forward() {
|
|
278
|
-
const err = await this.ensurePage();
|
|
279
|
-
if (err)
|
|
280
|
-
return err;
|
|
281
|
-
if (!this.page)
|
|
282
|
-
return { success: false, output: "No page" };
|
|
283
|
-
await this.page
|
|
284
|
-
.goForward({
|
|
285
|
-
waitUntil: "domcontentloaded",
|
|
286
|
-
timeout: this.config.navigationTimeout,
|
|
287
|
-
})
|
|
288
|
-
.catch(() => { });
|
|
289
|
-
await new Promise((r) => setTimeout(r, 300));
|
|
290
|
-
const snapshot = await this.takeSnapshot();
|
|
291
|
-
return { success: true, output: snapshot };
|
|
292
|
-
}
|
|
293
|
-
async screenshot() {
|
|
294
|
-
const err = await this.ensurePage();
|
|
295
|
-
if (err)
|
|
296
|
-
return err;
|
|
297
|
-
if (!this.page)
|
|
298
|
-
return { success: false, output: "No page" };
|
|
299
|
-
const buffer = await this.page.screenshot({ type: "png", fullPage: false });
|
|
300
|
-
const snapshot = await this.takeSnapshot();
|
|
301
|
-
return { success: true, output: snapshot, screenshot: buffer };
|
|
302
|
-
}
|
|
303
|
-
async snapshot() {
|
|
304
|
-
const err = await this.ensurePage();
|
|
305
|
-
if (err)
|
|
306
|
-
return err;
|
|
307
|
-
const snap = await this.takeSnapshot();
|
|
308
|
-
return { success: true, output: snap };
|
|
309
|
-
}
|
|
310
|
-
async wait(ms) {
|
|
311
|
-
const err = await this.ensurePage();
|
|
312
|
-
if (err)
|
|
313
|
-
return err;
|
|
314
|
-
await new Promise((r) => setTimeout(r, Math.min(ms, 10000)));
|
|
315
|
-
const snapshot = await this.takeSnapshot();
|
|
316
|
-
return { success: true, output: snapshot };
|
|
317
|
-
}
|
|
318
|
-
async close() {
|
|
319
|
-
await this.saveCookies();
|
|
320
|
-
if (this.page) {
|
|
321
|
-
await this.page.close().catch(() => { });
|
|
322
|
-
this.page = null;
|
|
323
|
-
}
|
|
324
|
-
if (this.context) {
|
|
325
|
-
await this.context.close().catch(() => { });
|
|
326
|
-
this.context = null;
|
|
327
|
-
}
|
|
328
|
-
if (this.browser) {
|
|
329
|
-
await this.browser.close().catch(() => { });
|
|
330
|
-
this.browser = null;
|
|
331
|
-
}
|
|
332
|
-
this.state = { isOpen: false, url: null, title: null, elementCount: 0 };
|
|
333
|
-
return { success: true, output: t("browser.closed") };
|
|
334
|
-
}
|
|
335
|
-
}
|
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
import { t } from '../../i18n/index';
|
|
2
|
-
function inferRole(tag, type) {
|
|
3
|
-
const t = tag.toLowerCase();
|
|
4
|
-
if (t === 'a')
|
|
5
|
-
return 'link';
|
|
6
|
-
if (t === 'button')
|
|
7
|
-
return 'button';
|
|
8
|
-
if (t === 'input') {
|
|
9
|
-
const inputType = type?.toLowerCase();
|
|
10
|
-
if (inputType === 'submit' || inputType === 'button')
|
|
11
|
-
return 'button';
|
|
12
|
-
if (inputType === 'checkbox')
|
|
13
|
-
return 'checkbox';
|
|
14
|
-
if (inputType === 'radio')
|
|
15
|
-
return 'radio';
|
|
16
|
-
return 'textbox';
|
|
17
|
-
}
|
|
18
|
-
if (t === 'textarea')
|
|
19
|
-
return 'textbox';
|
|
20
|
-
if (t === 'select')
|
|
21
|
-
return 'combobox';
|
|
22
|
-
return 'widget';
|
|
23
|
-
}
|
|
24
|
-
function getName(tag, attrs, html, matchIndex) {
|
|
25
|
-
const fromAttr = (attrs['aria-label'] ||
|
|
26
|
-
attrs['placeholder'] ||
|
|
27
|
-
attrs['title'] ||
|
|
28
|
-
attrs['alt'] ||
|
|
29
|
-
attrs['value'] ||
|
|
30
|
-
attrs['name'] ||
|
|
31
|
-
'');
|
|
32
|
-
if (fromAttr)
|
|
33
|
-
return fromAttr;
|
|
34
|
-
const afterTag = html.slice(matchIndex);
|
|
35
|
-
const closeIdx = afterTag.indexOf('>');
|
|
36
|
-
if (closeIdx === -1)
|
|
37
|
-
return '';
|
|
38
|
-
const afterOpen = afterTag.slice(closeIdx + 1);
|
|
39
|
-
const endTag = `</${tag}>`;
|
|
40
|
-
const endIdx = afterOpen.indexOf(endTag);
|
|
41
|
-
if (endIdx === -1)
|
|
42
|
-
return '';
|
|
43
|
-
const text = afterOpen.slice(0, endIdx).replace(/<[^>]+>/g, '').trim();
|
|
44
|
-
return text.slice(0, 50);
|
|
45
|
-
}
|
|
46
|
-
export function extractInteractiveElements(html, maxElements = 30) {
|
|
47
|
-
const elements = [];
|
|
48
|
-
const tagRegex = /<(a|button|input|textarea|select|div|span)([^>]*)>/gi;
|
|
49
|
-
let match;
|
|
50
|
-
while ((match = tagRegex.exec(html)) !== null) {
|
|
51
|
-
if (elements.length >= maxElements)
|
|
52
|
-
break;
|
|
53
|
-
const tag = match[1].toLowerCase();
|
|
54
|
-
const attrStr = match[2];
|
|
55
|
-
const attrs = {};
|
|
56
|
-
const attrRegex = /(\w[\w-]*)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|(\S+)))?/g;
|
|
57
|
-
let attrMatch;
|
|
58
|
-
while ((attrMatch = attrRegex.exec(attrStr)) !== null) {
|
|
59
|
-
attrs[attrMatch[1].toLowerCase()] = attrMatch[2] ?? attrMatch[3] ?? attrMatch[4] ?? '';
|
|
60
|
-
}
|
|
61
|
-
if (attrs['style']?.includes('display:none') || attrs['style']?.includes('display: none'))
|
|
62
|
-
continue;
|
|
63
|
-
if (attrs['hidden'] !== undefined)
|
|
64
|
-
continue;
|
|
65
|
-
if (attrs['aria-hidden'] === 'true')
|
|
66
|
-
continue;
|
|
67
|
-
if (tag === 'input' && attrs['type'] === 'hidden')
|
|
68
|
-
continue;
|
|
69
|
-
const role = attrs['role'] || inferRole(tag, attrs['type']);
|
|
70
|
-
const isInteractive = [
|
|
71
|
-
'a', 'button', 'input', 'textarea', 'select',
|
|
72
|
-
].includes(tag) || attrs['role'] !== undefined;
|
|
73
|
-
if (!isInteractive)
|
|
74
|
-
continue;
|
|
75
|
-
if (tag === 'a' && !attrs['href'])
|
|
76
|
-
continue;
|
|
77
|
-
const name = getName(tag, attrs, html, match.index);
|
|
78
|
-
elements.push({
|
|
79
|
-
index: elements.length + 1,
|
|
80
|
-
tag,
|
|
81
|
-
role,
|
|
82
|
-
name,
|
|
83
|
-
value: attrs['value'],
|
|
84
|
-
href: attrs['href'],
|
|
85
|
-
visible: true,
|
|
86
|
-
});
|
|
87
|
-
}
|
|
88
|
-
return elements;
|
|
89
|
-
}
|
|
90
|
-
export function formatSnapshot(snapshot) {
|
|
91
|
-
const lines = [];
|
|
92
|
-
lines.push(`Page: "${snapshot.title}"`);
|
|
93
|
-
lines.push(`URL: ${snapshot.url}`);
|
|
94
|
-
lines.push('');
|
|
95
|
-
if (snapshot.elements.length === 0 && !snapshot.truncated) {
|
|
96
|
-
lines.push(t('browser.no_elements'));
|
|
97
|
-
return lines.join('\n');
|
|
98
|
-
}
|
|
99
|
-
for (const el of snapshot.elements) {
|
|
100
|
-
let line = `[${el.index}] ${el.role}`;
|
|
101
|
-
if (el.name)
|
|
102
|
-
line += ` "${el.name}"`;
|
|
103
|
-
if (el.href)
|
|
104
|
-
line += ` → ${el.href}`;
|
|
105
|
-
if (el.value)
|
|
106
|
-
line += ` = "${el.value}"`;
|
|
107
|
-
lines.push(line);
|
|
108
|
-
}
|
|
109
|
-
if (snapshot.truncated) {
|
|
110
|
-
lines.push('');
|
|
111
|
-
lines.push(t('browser.more_elements'));
|
|
112
|
-
}
|
|
113
|
-
return lines.join('\n');
|
|
114
|
-
}
|