micro-models-agent 0.39.0 → 0.40.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/bin/mma.mjs +41 -41
- package/dist/cli/commands.js +116 -3
- package/dist/cli/main.js +35 -8
- package/dist/cli/repl-commands.js +633 -0
- package/dist/cli/repl.js +110 -611
- package/dist/cli/setup.js +32 -12
- package/dist/config/config.js +46 -30
- package/dist/config/defaults.js +10 -1
- package/dist/config/security.js +15 -8
- package/dist/core/agent-moe.js +24 -12
- package/dist/core/agent.js +281 -47
- package/dist/core/bootstrap.js +52 -36
- package/dist/core/session-logger.js +35 -2
- package/dist/core/workspace.js +76 -0
- package/dist/i18n/en.json +79 -15
- package/dist/i18n/index.js +12 -9
- package/dist/i18n/ru.json +79 -15
- package/dist/index.js +13 -13
- package/dist/llm/openai-compat.js +39 -10
- package/dist/logger/app-logger.js +83 -16
- package/dist/logger/file-log.js +151 -0
- package/dist/main.js +537 -284
- package/dist/modules/browser/bridge-server.mjs +113 -105
- package/dist/modules/browser/session.js +108 -60
- package/dist/modules/certification/cli.js +176 -0
- package/dist/modules/certification/fact-checker.js +84 -0
- package/dist/modules/certification/loader.js +111 -0
- package/dist/modules/certification/manifest.js +50 -0
- package/dist/modules/certification/runner.js +162 -0
- package/dist/modules/certification/scenarios.js +124 -0
- package/dist/modules/certification/types.js +1 -0
- package/dist/modules/context/manager.js +119 -10
- package/dist/modules/execution/auditor.js +33 -39
- package/dist/modules/execution/index.js +8 -6
- package/dist/modules/execution/module.js +474 -32
- package/dist/modules/execution/moe-executor.js +97 -40
- package/dist/modules/execution/plan-coverage.js +68 -0
- package/dist/modules/execution/plan-persister.js +46 -0
- package/dist/modules/execution/plan-store.js +159 -0
- package/dist/modules/execution/planner.js +63 -13
- package/dist/modules/execution/stuck-detector.js +252 -39
- package/dist/modules/execution/tracker.js +21 -7
- package/dist/modules/execution/verifier.js +46 -17
- package/dist/modules/hallucination/confidence.js +7 -2
- package/dist/modules/hallucination/consistency.js +8 -42
- package/dist/modules/hallucination/detector.js +26 -21
- package/dist/modules/hallucination/factual.js +170 -150
- package/dist/modules/hallucination/index.js +5 -4
- package/dist/modules/hallucination/js-identifiers.js +72 -0
- package/dist/modules/hallucination/llm-judge.js +103 -0
- package/dist/modules/index.js +5 -5
- package/dist/modules/lsp/client.js +235 -0
- package/dist/modules/lsp/config.js +81 -0
- package/dist/modules/lsp/index.js +3 -0
- package/dist/modules/lsp/module.js +68 -0
- package/dist/modules/lsp/types.js +1 -0
- package/dist/modules/mcp/client.js +8 -2
- package/dist/modules/memory/store.js +4 -0
- package/dist/modules/plugins/builtin/lint-on-write.js +143 -38
- package/dist/modules/processes/index.js +1 -2
- package/dist/modules/processes/registry.js +125 -35
- package/dist/modules/processes/runner.js +9 -110
- package/dist/modules/security/audit-log.js +30 -10
- package/dist/modules/security/command-validator.js +42 -16
- package/dist/modules/security/content-scanner.js +9 -8
- package/dist/modules/security/network-validator.js +2 -2
- package/dist/modules/security/path-validator.js +64 -10
- package/dist/modules/security/security-policies.js +221 -67
- package/dist/modules/security/session-encryption.js +42 -25
- package/dist/modules/session/manager.js +15 -10
- package/dist/modules/session/store.js +62 -8
- package/dist/modules/skills/index.js +2 -3
- package/dist/modules/skills/module.js +10 -23
- package/dist/tools/bash.js +287 -90
- package/dist/tools/create-dir.js +0 -1
- package/dist/tools/delete-file.js +0 -1
- package/dist/tools/edit-file.js +10 -8
- package/dist/tools/executor.js +57 -7
- package/dist/tools/grep-tool.js +51 -29
- package/dist/tools/index.js +55 -40
- package/dist/tools/load-skill.js +14 -18
- package/dist/tools/move-file.js +3 -2
- package/dist/tools/pipeline-run.js +1 -1
- package/dist/tools/read-file.js +15 -5
- package/dist/tools/search-history.js +42 -22
- package/dist/tools/subagent.js +21 -12
- package/dist/tools/web-browse.js +54 -25
- package/dist/tools/web-fetch.js +60 -34
- package/dist/tools/web-search.js +39 -20
- package/dist/tools/write-file.js +13 -10
- package/dist/ui/diff.js +9 -16
- package/dist/ui/renderer.js +69 -6
- package/package.json +48 -45
- package/dist/modules/context/history.js +0 -15
- package/dist/modules/processes/detect.js +0 -34
- package/dist/modules/skills/matcher.js +0 -27
|
@@ -1,110 +1,110 @@
|
|
|
1
|
-
import { createRequire } from
|
|
2
|
-
import readline from
|
|
1
|
+
import { createRequire } from "module";
|
|
2
|
+
import readline from "readline";
|
|
3
3
|
|
|
4
|
-
const require = createRequire(import.meta.url)
|
|
5
|
-
const { chromium } = require(
|
|
4
|
+
const require = createRequire(import.meta.url);
|
|
5
|
+
const { chromium } = require("playwright");
|
|
6
6
|
|
|
7
|
-
const CONSOLE_CAP = 500
|
|
8
|
-
const NETWORK_CAP = 100
|
|
9
|
-
let maxLineChars = 400
|
|
7
|
+
const CONSOLE_CAP = 500;
|
|
8
|
+
const NETWORK_CAP = 100;
|
|
9
|
+
let maxLineChars = 400;
|
|
10
10
|
|
|
11
|
-
let browser = null
|
|
12
|
-
let context = null
|
|
13
|
-
let page = null
|
|
14
|
-
let consoleBuf = []
|
|
15
|
-
let networkBuf = []
|
|
16
|
-
let lastUrl =
|
|
11
|
+
let browser = null;
|
|
12
|
+
let context = null;
|
|
13
|
+
let page = null;
|
|
14
|
+
let consoleBuf = [];
|
|
15
|
+
let networkBuf = [];
|
|
16
|
+
let lastUrl = "";
|
|
17
17
|
|
|
18
18
|
function pushConsole(type, text) {
|
|
19
|
-
if (consoleBuf.length >= CONSOLE_CAP) return
|
|
20
|
-
const clean = String(text).replace(/\s+/g,
|
|
21
|
-
if (!clean) return
|
|
22
|
-
const line = clean.length > maxLineChars ? clean.slice(0, maxLineChars) +
|
|
23
|
-
consoleBuf.push({ type, text: line })
|
|
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
24
|
}
|
|
25
25
|
|
|
26
26
|
function pushNetwork(err) {
|
|
27
|
-
if (networkBuf.length >= NETWORK_CAP) return
|
|
28
|
-
networkBuf.push(err)
|
|
27
|
+
if (networkBuf.length >= NETWORK_CAP) return;
|
|
28
|
+
networkBuf.push(err);
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
function drain() {
|
|
32
|
-
const consoleEvents = consoleBuf
|
|
33
|
-
const networkEvents = networkBuf
|
|
34
|
-
consoleBuf = []
|
|
35
|
-
networkBuf = []
|
|
36
|
-
return { console: consoleEvents, network: networkEvents }
|
|
32
|
+
const consoleEvents = consoleBuf;
|
|
33
|
+
const networkEvents = networkBuf;
|
|
34
|
+
consoleBuf = [];
|
|
35
|
+
networkBuf = [];
|
|
36
|
+
return { console: consoleEvents, network: networkEvents };
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
function readInt(v, fallback) {
|
|
40
|
-
const n = Number(v)
|
|
41
|
-
return Number.isFinite(n) && n > 0 ? n : fallback
|
|
40
|
+
const n = Number(v);
|
|
41
|
+
return Number.isFinite(n) && n > 0 ? n : fallback;
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
async function handleLaunch(params) {
|
|
45
|
-
if (browser) return { ok: true }
|
|
46
|
-
maxLineChars = readInt(params?.maxConsoleLineChars, 400)
|
|
45
|
+
if (browser) return { ok: true };
|
|
46
|
+
maxLineChars = readInt(params?.maxConsoleLineChars, 400);
|
|
47
47
|
browser = await chromium.launch({
|
|
48
48
|
headless: params?.headless !== false,
|
|
49
49
|
timeout: readInt(params?.timeoutMs, 30000),
|
|
50
|
-
})
|
|
50
|
+
});
|
|
51
51
|
context = await browser.newContext({
|
|
52
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(
|
|
58
|
-
page.on(
|
|
59
|
-
page.on(
|
|
60
|
-
const failure = req.failure()
|
|
61
|
-
pushNetwork({ method: req.method(), url: req.url(), error: failure?.errorText ||
|
|
62
|
-
})
|
|
63
|
-
|
|
64
|
-
return { ok: true }
|
|
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
65
|
}
|
|
66
66
|
|
|
67
67
|
async function handleGoto(params) {
|
|
68
|
-
if (!page) return { ok: false, error:
|
|
69
|
-
const url = String(params?.url ||
|
|
70
|
-
const timeoutMs = readInt(params?.timeoutMs, 15000)
|
|
71
|
-
consoleBuf = []
|
|
72
|
-
networkBuf = []
|
|
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
73
|
try {
|
|
74
|
-
await page.goto(url, { waitUntil:
|
|
75
|
-
lastUrl = page.url()
|
|
76
|
-
return { ok: true, data: { url: lastUrl } }
|
|
74
|
+
await page.goto(url, { waitUntil: "domcontentloaded", timeout: timeoutMs });
|
|
75
|
+
lastUrl = page.url();
|
|
76
|
+
return { ok: true, data: { url: lastUrl } };
|
|
77
77
|
} catch (err) {
|
|
78
|
-
lastUrl = page.url()
|
|
79
|
-
return { ok: false, error: err.message, data: { url: lastUrl } }
|
|
78
|
+
lastUrl = page.url();
|
|
79
|
+
return { ok: false, error: err.message, data: { url: lastUrl } };
|
|
80
80
|
}
|
|
81
81
|
}
|
|
82
82
|
|
|
83
83
|
async function handleEvaluate(params) {
|
|
84
|
-
if (!page) return { ok: false, error:
|
|
85
|
-
const js = String(params?.js ||
|
|
84
|
+
if (!page) return { ok: false, error: "No page open" };
|
|
85
|
+
const js = String(params?.js || "");
|
|
86
86
|
try {
|
|
87
|
-
const result = await page.evaluate(js)
|
|
88
|
-
return { ok: true, data: { result } }
|
|
87
|
+
const result = await page.evaluate(js);
|
|
88
|
+
return { ok: true, data: { result } };
|
|
89
89
|
} catch (err) {
|
|
90
|
-
return { ok: false, error: err.message }
|
|
90
|
+
return { ok: false, error: err.message };
|
|
91
91
|
}
|
|
92
92
|
}
|
|
93
93
|
|
|
94
94
|
async function handleClose() {
|
|
95
95
|
try {
|
|
96
|
-
if (page) await page.close()
|
|
96
|
+
if (page) await page.close();
|
|
97
97
|
} catch {}
|
|
98
98
|
try {
|
|
99
|
-
if (context) await context.close()
|
|
99
|
+
if (context) await context.close();
|
|
100
100
|
} catch {}
|
|
101
101
|
try {
|
|
102
|
-
if (browser) await browser.close()
|
|
102
|
+
if (browser) await browser.close();
|
|
103
103
|
} catch {}
|
|
104
|
-
page = null
|
|
105
|
-
context = null
|
|
106
|
-
browser = null
|
|
107
|
-
return { ok: true }
|
|
104
|
+
page = null;
|
|
105
|
+
context = null;
|
|
106
|
+
browser = null;
|
|
107
|
+
return { ok: true };
|
|
108
108
|
}
|
|
109
109
|
|
|
110
110
|
const handlers = {
|
|
@@ -112,83 +112,91 @@ const handlers = {
|
|
|
112
112
|
launch: handleLaunch,
|
|
113
113
|
goto: handleGoto,
|
|
114
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() :
|
|
115
|
+
content: async () => ({ ok: true, data: { html: page ? await page.content() : "" } }),
|
|
116
|
+
title: async () => ({ ok: true, data: { title: page ? await page.title() : "" } }),
|
|
117
117
|
url: async () => ({ ok: true, data: { url: page ? page.url() : lastUrl } }),
|
|
118
118
|
waitLoad: async (params) => {
|
|
119
|
-
if (!page) return { ok: false, error:
|
|
119
|
+
if (!page) return { ok: false, error: "No page open" };
|
|
120
120
|
try {
|
|
121
|
-
await page.waitForLoadState(
|
|
121
|
+
await page.waitForLoadState("domcontentloaded", {
|
|
122
|
+
timeout: readInt(params?.timeoutMs, 15000),
|
|
123
|
+
});
|
|
122
124
|
} catch {}
|
|
123
|
-
return { ok: true }
|
|
125
|
+
return { ok: true };
|
|
124
126
|
},
|
|
125
127
|
screenshot: async () => {
|
|
126
|
-
if (!page) return { ok: false, error:
|
|
128
|
+
if (!page) return { ok: false, error: "No page open" };
|
|
127
129
|
try {
|
|
128
|
-
const buf = await page.screenshot({ type:
|
|
129
|
-
return { ok: true, data: { base64: buf.toString(
|
|
130
|
+
const buf = await page.screenshot({ type: "png", fullPage: false });
|
|
131
|
+
return { ok: true, data: { base64: buf.toString("base64") } };
|
|
130
132
|
} catch (err) {
|
|
131
|
-
return { ok: false, error: err.message }
|
|
133
|
+
return { ok: false, error: err.message };
|
|
132
134
|
}
|
|
133
135
|
},
|
|
134
136
|
cookies: async () => {
|
|
135
|
-
if (!context) return { ok: true, data: { cookies: [] } }
|
|
136
|
-
const cookies = await context.cookies()
|
|
137
|
-
return { ok: true, data: { cookies } }
|
|
137
|
+
if (!context) return { ok: true, data: { cookies: [] } };
|
|
138
|
+
const cookies = await context.cookies();
|
|
139
|
+
return { ok: true, data: { cookies } };
|
|
138
140
|
},
|
|
139
141
|
setCookies: async (params) => {
|
|
140
|
-
if (!context) return { ok: false, error:
|
|
141
|
-
const cookies = Array.isArray(params?.cookies) ? params.cookies : []
|
|
142
|
+
if (!context) return { ok: false, error: "No context" };
|
|
143
|
+
const cookies = Array.isArray(params?.cookies) ? params.cookies : [];
|
|
142
144
|
if (cookies.length > 0) {
|
|
143
145
|
try {
|
|
144
|
-
await context.addCookies(cookies)
|
|
146
|
+
await context.addCookies(cookies);
|
|
145
147
|
} catch (err) {
|
|
146
|
-
return { ok: false, error: err.message }
|
|
148
|
+
return { ok: false, error: err.message };
|
|
147
149
|
}
|
|
148
150
|
}
|
|
149
|
-
return { ok: true }
|
|
151
|
+
return { ok: true };
|
|
150
152
|
},
|
|
151
153
|
back: async (params) => {
|
|
152
|
-
if (!page) return { ok: false, error:
|
|
154
|
+
if (!page) return { ok: false, error: "No page open" };
|
|
153
155
|
try {
|
|
154
|
-
await page.goBack({
|
|
156
|
+
await page.goBack({
|
|
157
|
+
waitUntil: "domcontentloaded",
|
|
158
|
+
timeout: readInt(params?.timeoutMs, 15000),
|
|
159
|
+
});
|
|
155
160
|
} catch {}
|
|
156
|
-
lastUrl = page.url()
|
|
157
|
-
return { ok: true, data: { url: lastUrl } }
|
|
161
|
+
lastUrl = page.url();
|
|
162
|
+
return { ok: true, data: { url: lastUrl } };
|
|
158
163
|
},
|
|
159
164
|
forward: async (params) => {
|
|
160
|
-
if (!page) return { ok: false, error:
|
|
165
|
+
if (!page) return { ok: false, error: "No page open" };
|
|
161
166
|
try {
|
|
162
|
-
await page.goForward({
|
|
167
|
+
await page.goForward({
|
|
168
|
+
waitUntil: "domcontentloaded",
|
|
169
|
+
timeout: readInt(params?.timeoutMs, 15000),
|
|
170
|
+
});
|
|
163
171
|
} catch {}
|
|
164
|
-
lastUrl = page.url()
|
|
165
|
-
return { ok: true, data: { url: lastUrl } }
|
|
172
|
+
lastUrl = page.url();
|
|
173
|
+
return { ok: true, data: { url: lastUrl } };
|
|
166
174
|
},
|
|
167
175
|
close: handleClose,
|
|
168
|
-
}
|
|
176
|
+
};
|
|
169
177
|
|
|
170
|
-
const rl = readline.createInterface({ input: process.stdin })
|
|
171
|
-
rl.on(
|
|
172
|
-
let req
|
|
178
|
+
const rl = readline.createInterface({ input: process.stdin });
|
|
179
|
+
rl.on("line", async (line) => {
|
|
180
|
+
let req;
|
|
173
181
|
try {
|
|
174
|
-
req = JSON.parse(line)
|
|
182
|
+
req = JSON.parse(line);
|
|
175
183
|
} catch {
|
|
176
|
-
return
|
|
184
|
+
return;
|
|
177
185
|
}
|
|
178
|
-
const handler = handlers[req.cmd]
|
|
179
|
-
let resp
|
|
186
|
+
const handler = handlers[req.cmd];
|
|
187
|
+
let resp;
|
|
180
188
|
if (!handler) {
|
|
181
|
-
resp = { id: req.id, ok: false, error: `Unknown command: ${req.cmd}`, ...drain() }
|
|
189
|
+
resp = { id: req.id, ok: false, error: `Unknown command: ${req.cmd}`, ...drain() };
|
|
182
190
|
} else {
|
|
183
191
|
try {
|
|
184
|
-
const result = await handler(req.params || {})
|
|
185
|
-
resp = { id: req.id, ...result, ...drain() }
|
|
192
|
+
const result = await handler(req.params || {});
|
|
193
|
+
resp = { id: req.id, ...result, ...drain() };
|
|
186
194
|
} catch (err) {
|
|
187
|
-
resp = { id: req.id, ok: false, error: err.message, ...drain() }
|
|
195
|
+
resp = { id: req.id, ok: false, error: err.message, ...drain() };
|
|
188
196
|
}
|
|
189
197
|
}
|
|
190
|
-
process.stdout.write(JSON.stringify(resp) +
|
|
191
|
-
})
|
|
198
|
+
process.stdout.write(JSON.stringify(resp) + "\n");
|
|
199
|
+
});
|
|
192
200
|
|
|
193
|
-
process.on(
|
|
194
|
-
process.on(
|
|
201
|
+
process.on("SIGTERM", () => process.exit(0));
|
|
202
|
+
process.on("SIGINT", () => process.exit(0));
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { chromium } from
|
|
2
|
-
import { extractInteractiveElements, formatSnapshot } from
|
|
3
|
-
import { buildClickScript, buildTypeScript, buildScrollScript, buildIndexInjectionScript } from
|
|
4
|
-
import { CookieStore } from
|
|
5
|
-
import { t } from
|
|
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
6
|
export class BrowserActionTracker {
|
|
7
7
|
history = [];
|
|
8
8
|
threshold;
|
|
@@ -14,11 +14,14 @@ export class BrowserActionTracker {
|
|
|
14
14
|
if (this.history.length < this.threshold)
|
|
15
15
|
return null;
|
|
16
16
|
const recent = this.history.slice(-this.threshold);
|
|
17
|
-
const allSame = recent.every(h => h.action === recent[0].action &&
|
|
17
|
+
const allSame = recent.every((h) => h.action === recent[0].action &&
|
|
18
18
|
h.url === recent[0].url &&
|
|
19
19
|
JSON.stringify(h.args) === JSON.stringify(recent[0].args));
|
|
20
20
|
if (allSame) {
|
|
21
|
-
return t(
|
|
21
|
+
return t("browser.repeated_action", {
|
|
22
|
+
action,
|
|
23
|
+
threshold: this.threshold,
|
|
24
|
+
});
|
|
22
25
|
}
|
|
23
26
|
return null;
|
|
24
27
|
}
|
|
@@ -32,7 +35,12 @@ export class BrowserSession {
|
|
|
32
35
|
page = null;
|
|
33
36
|
config;
|
|
34
37
|
cookieStore;
|
|
35
|
-
state = {
|
|
38
|
+
state = {
|
|
39
|
+
isOpen: false,
|
|
40
|
+
url: null,
|
|
41
|
+
title: null,
|
|
42
|
+
elementCount: 0,
|
|
43
|
+
};
|
|
36
44
|
actionTracker = new BrowserActionTracker(3);
|
|
37
45
|
constructor(config) {
|
|
38
46
|
this.config = config;
|
|
@@ -45,53 +53,60 @@ export class BrowserSession {
|
|
|
45
53
|
try {
|
|
46
54
|
let result;
|
|
47
55
|
switch (action) {
|
|
48
|
-
case
|
|
49
|
-
result = await this.open(String(args.url ||
|
|
56
|
+
case "open":
|
|
57
|
+
result = await this.open(String(args.url || ""));
|
|
50
58
|
break;
|
|
51
|
-
case
|
|
59
|
+
case "click":
|
|
52
60
|
result = await this.click(Number(args.target));
|
|
53
61
|
break;
|
|
54
|
-
case
|
|
55
|
-
result = await this.type(Number(args.target), String(args.text ||
|
|
62
|
+
case "type":
|
|
63
|
+
result = await this.type(Number(args.target), String(args.text || ""));
|
|
56
64
|
break;
|
|
57
|
-
case
|
|
58
|
-
result = await this.scroll(String(args.direction ||
|
|
65
|
+
case "scroll":
|
|
66
|
+
result = await this.scroll(String(args.direction || "down"));
|
|
59
67
|
break;
|
|
60
|
-
case
|
|
68
|
+
case "back":
|
|
61
69
|
result = await this.back();
|
|
62
70
|
break;
|
|
63
|
-
case
|
|
71
|
+
case "forward":
|
|
64
72
|
result = await this.forward();
|
|
65
73
|
break;
|
|
66
|
-
case
|
|
74
|
+
case "screenshot":
|
|
67
75
|
result = await this.screenshot();
|
|
68
76
|
break;
|
|
69
|
-
case
|
|
77
|
+
case "snapshot":
|
|
70
78
|
result = await this.snapshot();
|
|
71
79
|
break;
|
|
72
|
-
case
|
|
80
|
+
case "close":
|
|
73
81
|
result = await this.close();
|
|
74
82
|
break;
|
|
75
|
-
case
|
|
83
|
+
case "wait":
|
|
76
84
|
result = await this.wait(Number(args.ms || 1000));
|
|
77
85
|
break;
|
|
78
|
-
default:
|
|
86
|
+
default:
|
|
87
|
+
return {
|
|
88
|
+
success: false,
|
|
89
|
+
output: t("browser.unknown_action", { action }),
|
|
90
|
+
};
|
|
79
91
|
}
|
|
80
|
-
if (action !==
|
|
81
|
-
const warning = this.actionTracker.record(action, args, this.page?.url() ||
|
|
92
|
+
if (action !== "open") {
|
|
93
|
+
const warning = this.actionTracker.record(action, args, this.page?.url() || "");
|
|
82
94
|
if (warning && result.success) {
|
|
83
|
-
result.output = result.output +
|
|
95
|
+
result.output = result.output + "\n\n⚠️ " + warning;
|
|
84
96
|
}
|
|
85
97
|
}
|
|
86
98
|
return result;
|
|
87
99
|
}
|
|
88
100
|
catch (err) {
|
|
89
|
-
return {
|
|
101
|
+
return {
|
|
102
|
+
success: false,
|
|
103
|
+
output: t("browser.error", { message: err.message }),
|
|
104
|
+
};
|
|
90
105
|
}
|
|
91
106
|
}
|
|
92
107
|
async ensurePage() {
|
|
93
108
|
if (!this.page) {
|
|
94
|
-
return { success: false, output: t(
|
|
109
|
+
return { success: false, output: t("browser.no_page") };
|
|
95
110
|
}
|
|
96
111
|
return null;
|
|
97
112
|
}
|
|
@@ -100,11 +115,14 @@ export class BrowserSession {
|
|
|
100
115
|
return;
|
|
101
116
|
this.browser = await chromium.launch({ headless: this.config.headless });
|
|
102
117
|
this.context = await this.browser.newContext({
|
|
103
|
-
viewport: {
|
|
118
|
+
viewport: {
|
|
119
|
+
width: this.config.viewportWidth,
|
|
120
|
+
height: this.config.viewportHeight,
|
|
121
|
+
},
|
|
104
122
|
});
|
|
105
123
|
const savedCookies = await this.cookieStore.load();
|
|
106
124
|
if (savedCookies.length > 0) {
|
|
107
|
-
await this.context.addCookies(savedCookies.map(c => ({
|
|
125
|
+
await this.context.addCookies(savedCookies.map((c) => ({
|
|
108
126
|
...c,
|
|
109
127
|
sameSite: c.sameSite,
|
|
110
128
|
})));
|
|
@@ -120,7 +138,7 @@ export class BrowserSession {
|
|
|
120
138
|
}
|
|
121
139
|
async takeSnapshot() {
|
|
122
140
|
if (!this.page)
|
|
123
|
-
return
|
|
141
|
+
return "No page open.";
|
|
124
142
|
await this.page.evaluate(buildIndexInjectionScript());
|
|
125
143
|
const html = await this.page.content();
|
|
126
144
|
const url = this.page.url();
|
|
@@ -138,19 +156,28 @@ export class BrowserSession {
|
|
|
138
156
|
}
|
|
139
157
|
async open(url) {
|
|
140
158
|
if (!url)
|
|
141
|
-
return { success: false, output: t(
|
|
142
|
-
if (!url.startsWith(
|
|
143
|
-
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;
|
|
144
165
|
}
|
|
145
166
|
await this.launch();
|
|
146
167
|
if (!this.page)
|
|
147
|
-
return { success: false, output: t(
|
|
168
|
+
return { success: false, output: t("browser.create_page_failed") };
|
|
148
169
|
this.actionTracker.reset();
|
|
149
170
|
try {
|
|
150
|
-
await this.page.goto(url, {
|
|
171
|
+
await this.page.goto(url, {
|
|
172
|
+
waitUntil: "domcontentloaded",
|
|
173
|
+
timeout: this.config.navigationTimeout,
|
|
174
|
+
});
|
|
151
175
|
}
|
|
152
176
|
catch (err) {
|
|
153
|
-
return {
|
|
177
|
+
return {
|
|
178
|
+
success: false,
|
|
179
|
+
output: t("browser.nav_failed", { message: err.message }),
|
|
180
|
+
};
|
|
154
181
|
}
|
|
155
182
|
const snapshot = await this.takeSnapshot();
|
|
156
183
|
await this.saveCookies();
|
|
@@ -161,21 +188,26 @@ export class BrowserSession {
|
|
|
161
188
|
if (err)
|
|
162
189
|
return err;
|
|
163
190
|
if (!this.page)
|
|
164
|
-
return { success: false, output:
|
|
191
|
+
return { success: false, output: "No page" };
|
|
165
192
|
if (!target || target < 1) {
|
|
166
|
-
return { success: false, output: t(
|
|
193
|
+
return { success: false, output: t("browser.invalid_target") };
|
|
167
194
|
}
|
|
168
195
|
try {
|
|
169
196
|
const script = buildClickScript(target);
|
|
170
197
|
await this.page.evaluate(script);
|
|
171
|
-
await this.page
|
|
172
|
-
|
|
198
|
+
await this.page
|
|
199
|
+
.waitForLoadState("domcontentloaded", { timeout: 5000 })
|
|
200
|
+
.catch(() => { });
|
|
201
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
173
202
|
const snapshot = await this.takeSnapshot();
|
|
174
203
|
await this.saveCookies();
|
|
175
204
|
return { success: true, output: snapshot };
|
|
176
205
|
}
|
|
177
206
|
catch (err) {
|
|
178
|
-
return {
|
|
207
|
+
return {
|
|
208
|
+
success: false,
|
|
209
|
+
output: t("browser.click_failed", { message: err.message }),
|
|
210
|
+
};
|
|
179
211
|
}
|
|
180
212
|
}
|
|
181
213
|
async type(target, text) {
|
|
@@ -183,12 +215,12 @@ export class BrowserSession {
|
|
|
183
215
|
if (err)
|
|
184
216
|
return err;
|
|
185
217
|
if (!this.page)
|
|
186
|
-
return { success: false, output:
|
|
218
|
+
return { success: false, output: "No page" };
|
|
187
219
|
if (!target || target < 1) {
|
|
188
|
-
return { success: false, output: t(
|
|
220
|
+
return { success: false, output: t("browser.invalid_target_short") };
|
|
189
221
|
}
|
|
190
222
|
if (!text) {
|
|
191
|
-
return { success: false, output: t(
|
|
223
|
+
return { success: false, output: t("browser.text_required") };
|
|
192
224
|
}
|
|
193
225
|
try {
|
|
194
226
|
const script = buildTypeScript(target, text);
|
|
@@ -197,7 +229,10 @@ export class BrowserSession {
|
|
|
197
229
|
return { success: true, output: snapshot };
|
|
198
230
|
}
|
|
199
231
|
catch (err) {
|
|
200
|
-
return {
|
|
232
|
+
return {
|
|
233
|
+
success: false,
|
|
234
|
+
output: t("browser.type_failed", { message: err.message }),
|
|
235
|
+
};
|
|
201
236
|
}
|
|
202
237
|
}
|
|
203
238
|
async scroll(direction) {
|
|
@@ -205,19 +240,22 @@ export class BrowserSession {
|
|
|
205
240
|
if (err)
|
|
206
241
|
return err;
|
|
207
242
|
if (!this.page)
|
|
208
|
-
return { success: false, output:
|
|
243
|
+
return { success: false, output: "No page" };
|
|
209
244
|
const dir = direction;
|
|
210
|
-
if (![
|
|
211
|
-
return { success: false, output: t(
|
|
245
|
+
if (!["up", "down", "top", "bottom"].includes(dir)) {
|
|
246
|
+
return { success: false, output: t("browser.direction_invalid") };
|
|
212
247
|
}
|
|
213
248
|
try {
|
|
214
249
|
await this.page.evaluate(buildScrollScript(dir));
|
|
215
|
-
await new Promise(r => setTimeout(r, 300));
|
|
250
|
+
await new Promise((r) => setTimeout(r, 300));
|
|
216
251
|
const snapshot = await this.takeSnapshot();
|
|
217
252
|
return { success: true, output: snapshot };
|
|
218
253
|
}
|
|
219
254
|
catch (err) {
|
|
220
|
-
return {
|
|
255
|
+
return {
|
|
256
|
+
success: false,
|
|
257
|
+
output: t("browser.scroll_failed", { message: err.message }),
|
|
258
|
+
};
|
|
221
259
|
}
|
|
222
260
|
}
|
|
223
261
|
async back() {
|
|
@@ -225,9 +263,14 @@ export class BrowserSession {
|
|
|
225
263
|
if (err)
|
|
226
264
|
return err;
|
|
227
265
|
if (!this.page)
|
|
228
|
-
return { success: false, output:
|
|
229
|
-
await this.page
|
|
230
|
-
|
|
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));
|
|
231
274
|
const snapshot = await this.takeSnapshot();
|
|
232
275
|
return { success: true, output: snapshot };
|
|
233
276
|
}
|
|
@@ -236,9 +279,14 @@ export class BrowserSession {
|
|
|
236
279
|
if (err)
|
|
237
280
|
return err;
|
|
238
281
|
if (!this.page)
|
|
239
|
-
return { success: false, output:
|
|
240
|
-
await this.page
|
|
241
|
-
|
|
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));
|
|
242
290
|
const snapshot = await this.takeSnapshot();
|
|
243
291
|
return { success: true, output: snapshot };
|
|
244
292
|
}
|
|
@@ -247,8 +295,8 @@ export class BrowserSession {
|
|
|
247
295
|
if (err)
|
|
248
296
|
return err;
|
|
249
297
|
if (!this.page)
|
|
250
|
-
return { success: false, output:
|
|
251
|
-
const buffer = await this.page.screenshot({ type:
|
|
298
|
+
return { success: false, output: "No page" };
|
|
299
|
+
const buffer = await this.page.screenshot({ type: "png", fullPage: false });
|
|
252
300
|
const snapshot = await this.takeSnapshot();
|
|
253
301
|
return { success: true, output: snapshot, screenshot: buffer };
|
|
254
302
|
}
|
|
@@ -263,7 +311,7 @@ export class BrowserSession {
|
|
|
263
311
|
const err = await this.ensurePage();
|
|
264
312
|
if (err)
|
|
265
313
|
return err;
|
|
266
|
-
await new Promise(r => setTimeout(r, Math.min(ms, 10000)));
|
|
314
|
+
await new Promise((r) => setTimeout(r, Math.min(ms, 10000)));
|
|
267
315
|
const snapshot = await this.takeSnapshot();
|
|
268
316
|
return { success: true, output: snapshot };
|
|
269
317
|
}
|
|
@@ -282,6 +330,6 @@ export class BrowserSession {
|
|
|
282
330
|
this.browser = null;
|
|
283
331
|
}
|
|
284
332
|
this.state = { isOpen: false, url: null, title: null, elementCount: 0 };
|
|
285
|
-
return { success: true, output: t(
|
|
333
|
+
return { success: true, output: t("browser.closed") };
|
|
286
334
|
}
|
|
287
335
|
}
|