micro-models-agent 0.41.2 → 0.42.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 +19 -9
- package/dist/cli/completer.js +37 -36
- package/dist/cli/index.js +2 -2
- package/dist/cli/main.js +23 -48
- package/dist/cli/repl-commands.js +12 -40
- package/dist/cli/repl.js +87 -217
- package/dist/cli/security-commands.js +7 -5
- package/dist/cli/setup.js +26 -8
- package/dist/config/config.js +5 -52
- package/dist/config/defaults.js +5 -29
- package/dist/config/experts.js +1 -1
- package/dist/config/index.js +3 -3
- package/dist/config/security.js +10 -3
- package/dist/core/agent-moe.js +10 -2
- package/dist/core/agent.js +82 -273
- package/dist/core/bootstrap.js +13 -80
- package/dist/core/index.js +2 -2
- package/dist/core/prompt-builder.js +2 -23
- package/dist/core/session-logger.js +4 -46
- package/dist/i18n/en.json +2 -75
- package/dist/i18n/ru.json +1 -74
- package/dist/index.js +1 -1
- package/dist/llm/image-utils.js +5 -4
- package/dist/llm/index.js +4 -4
- package/dist/llm/model-loader.js +6 -6
- package/dist/llm/openai-compat.js +34 -40
- package/dist/llm/orchestrator.js +29 -33
- package/dist/llm/response.js +9 -9
- package/dist/logger/app-logger.js +1 -1
- package/dist/logger/index.js +1 -1
- package/dist/main.js +299 -55
- package/dist/migration/backup.js +13 -13
- package/dist/migration/detect.js +11 -11
- package/dist/migration/index.js +2 -2
- package/dist/modules/browser/actions.js +4 -34
- package/dist/modules/browser/bridge-server.mjs +202 -202
- package/dist/modules/browser/cookie-store.js +6 -6
- package/dist/modules/browser/index.js +5 -7
- package/dist/modules/browser/module.js +7 -8
- package/dist/modules/browser/session.js +84 -87
- package/dist/modules/browser/snapshot.js +58 -92
- package/dist/modules/browser/types.js +1 -4
- package/dist/modules/certification/cli.js +4 -2
- package/dist/modules/certification/fact-checker.js +3 -1
- package/dist/modules/certification/loader.js +9 -3
- package/dist/modules/certification/runner.js +4 -1
- package/dist/modules/context/index.js +1 -1
- package/dist/modules/context/manager.js +86 -160
- package/dist/modules/execution/auditor.js +25 -177
- package/dist/modules/execution/module.js +544 -201
- package/dist/modules/execution/moe-executor.js +0 -25
- package/dist/modules/execution/plan-store.js +3 -1
- package/dist/modules/execution/plan-validator.js +10 -10
- package/dist/modules/execution/planner.js +1 -6
- package/dist/modules/execution/stuck-detector.js +10 -173
- package/dist/modules/execution/verifier.js +42 -86
- package/dist/modules/hallucination/confidence.js +1 -8
- package/dist/modules/hallucination/detector.js +5 -2
- package/dist/modules/hallucination/factual.js +64 -3
- package/dist/modules/hallucination/index.js +1 -1
- package/dist/modules/hallucination/js-identifiers.js +0 -190
- package/dist/modules/hallucination/llm-judge.js +3 -1
- package/dist/modules/indexer/cache.js +7 -9
- package/dist/modules/indexer/index.js +3 -3
- package/dist/modules/indexer/module.js +42 -95
- package/dist/modules/indexer/walker.js +17 -17
- package/dist/modules/lsp/client.js +31 -74
- package/dist/modules/lsp/config.js +33 -87
- package/dist/modules/lsp/index.js +3 -3
- package/dist/modules/lsp/module.js +21 -185
- package/dist/modules/mcp/module.js +6 -2
- package/dist/modules/memory/index.js +1 -1
- package/dist/modules/memory/module.js +23 -71
- package/dist/modules/memory/search.js +9 -11
- package/dist/modules/memory/store.js +13 -13
- package/dist/modules/pipelines/engine.js +10 -10
- package/dist/modules/pipelines/index.js +3 -3
- package/dist/modules/pipelines/parser.js +14 -17
- package/dist/modules/pipelines/template.js +1 -1
- package/dist/modules/plugins/builtin/lint-on-write.js +16 -21
- package/dist/modules/plugins/builtin/notify.js +2 -3
- package/dist/modules/plugins/index.js +1 -1
- package/dist/modules/plugins/loader.js +17 -59
- package/dist/modules/plugins/manager.js +17 -73
- package/dist/modules/processes/index.js +1 -1
- package/dist/modules/processes/registry.js +46 -135
- package/dist/modules/registry.js +2 -4
- package/dist/modules/security/audit-notifier.js +39 -39
- package/dist/modules/security/command-validator.js +8 -2
- package/dist/modules/security/data-sanitizer.js +9 -1
- package/dist/modules/security/encryption.js +56 -58
- package/dist/modules/security/network-validator.js +9 -1
- package/dist/modules/security/path-validator.js +3 -1
- package/dist/modules/security/security-policies.js +19 -3
- package/dist/modules/security/session-encryption.js +1 -1
- package/dist/modules/security/session-isolation.js +8 -8
- package/dist/modules/session/index.js +3 -3
- package/dist/modules/session/module.js +5 -5
- package/dist/modules/session/store.js +9 -3
- package/dist/modules/skills/module.js +2 -1
- package/dist/modules/updater/checker.js +6 -70
- package/dist/modules/updater/index.js +1 -2
- package/dist/modules/user-profile/compressor.js +2 -2
- package/dist/modules/user-profile/index.js +1 -1
- package/dist/modules/user-profile/profile.js +9 -9
- package/dist/tools/attach-image.js +1 -1
- package/dist/tools/bash.js +19 -178
- package/dist/tools/browser.js +29 -46
- package/dist/tools/executor.js +5 -4
- package/dist/tools/file-info.js +12 -13
- package/dist/tools/filter-tools.js +2 -9
- package/dist/tools/glob-tool.js +11 -11
- package/dist/tools/grep-tool.js +3 -1
- package/dist/tools/index.js +2 -13
- package/dist/tools/list-dir.js +17 -18
- package/dist/tools/load-skill.js +3 -1
- package/dist/tools/path-utils.js +4 -4
- package/dist/tools/pipeline-run.js +25 -25
- package/dist/tools/process-kill.js +11 -11
- package/dist/tools/process-list.js +22 -20
- package/dist/tools/process-log.js +18 -22
- package/dist/tools/question.js +3 -1
- package/dist/tools/read-file.js +2 -10
- package/dist/tools/recall.js +37 -44
- package/dist/tools/registry.js +4 -15
- package/dist/tools/remember.js +29 -29
- package/dist/tools/scope-check.js +9 -9
- package/dist/tools/subagent.js +9 -54
- package/dist/tools/user-input.js +1 -1
- package/dist/tools/web-browse.js +3 -3
- package/dist/tools/web-fetch.js +3 -3
- package/dist/tools/web-search.js +3 -3
- package/dist/tools/write-file.js +3 -1
- package/dist/ui/box.js +5 -1
- package/dist/ui/index.js +6 -6
- package/dist/ui/md-formatter.js +33 -33
- package/dist/ui/output.js +5 -5
- package/dist/ui/renderer.js +10 -15
- package/dist/ui/table.js +1 -1
- package/package.json +48 -48
- package/dist/cli/plugin-commands.js +0 -36
- package/dist/cli/run-result.js +0 -22
- package/dist/core/version.js +0 -24
- package/dist/modules/artifacts/store.js +0 -61
- package/dist/modules/browser/bridge-client.js +0 -199
- package/dist/modules/browser/bridge-path.js +0 -10
- package/dist/modules/browser/driver.js +0 -136
- package/dist/modules/context/chunk-query.js +0 -100
- package/dist/modules/context/fact-extractor.js +0 -162
- package/dist/modules/context/history.js +0 -15
- package/dist/modules/execution/audit-runners.js +0 -152
- package/dist/modules/execution/execution-plugin.js +0 -272
- package/dist/modules/execution/plan-tool.js +0 -508
- package/dist/modules/execution/windows-commands.js +0 -41
- package/dist/modules/indexer/project-profile.js +0 -183
- package/dist/modules/lsp/check-tool.js +0 -58
- package/dist/modules/lsp/command.js +0 -60
- package/dist/modules/lsp/probe.js +0 -76
- package/dist/modules/lsp/project-root.js +0 -32
- package/dist/modules/lsp/startup-check.js +0 -141
- package/dist/modules/processes/detect.js +0 -34
- package/dist/modules/skills/matcher.js +0 -27
- package/dist/modules/updater/module.js +0 -116
- package/dist/tools/chunk-query.js +0 -99
- package/dist/tools/download-file.js +0 -116
- package/dist/tools/enable-tools.js +0 -58
- package/dist/tools/hidden-tools-block.js +0 -37
- package/dist/ui/line-editor.js +0 -703
- package/dist/ui/line-math.js +0 -69
- package/dist/ui/plan-view.js +0 -103
|
@@ -1,35 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { chromium, } from "playwright";
|
|
2
2
|
import { extractInteractiveElements, formatSnapshot } from "./snapshot";
|
|
3
|
-
import { buildClickScript, buildTypeScript, buildScrollScript, buildIndexInjectionScript,
|
|
3
|
+
import { buildClickScript, buildTypeScript, buildScrollScript, buildIndexInjectionScript, } from "./actions";
|
|
4
4
|
import { CookieStore } from "./cookie-store";
|
|
5
5
|
import { t } from "../../i18n/index";
|
|
6
|
-
const CONSOLE_MAX_LINE = 400;
|
|
7
|
-
export class ConsoleBuffer {
|
|
8
|
-
entries = [];
|
|
9
|
-
maxEntries;
|
|
10
|
-
constructor(maxEntries = 20) {
|
|
11
|
-
this.maxEntries = maxEntries;
|
|
12
|
-
}
|
|
13
|
-
add(type, text) {
|
|
14
|
-
const clean = String(text).replace(/\s+/g, " ").trim();
|
|
15
|
-
if (!clean)
|
|
16
|
-
return;
|
|
17
|
-
const line = clean.length > CONSOLE_MAX_LINE ? clean.slice(0, CONSOLE_MAX_LINE) + "…" : clean;
|
|
18
|
-
this.entries.push({ type, text: line });
|
|
19
|
-
if (this.entries.length > this.maxEntries) {
|
|
20
|
-
this.entries.splice(0, this.entries.length - this.maxEntries);
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
clear() {
|
|
24
|
-
this.entries = [];
|
|
25
|
-
}
|
|
26
|
-
getAll() {
|
|
27
|
-
return [...this.entries];
|
|
28
|
-
}
|
|
29
|
-
get size() {
|
|
30
|
-
return this.entries.length;
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
6
|
export class BrowserActionTracker {
|
|
34
7
|
history = [];
|
|
35
8
|
threshold;
|
|
@@ -57,7 +30,9 @@ export class BrowserActionTracker {
|
|
|
57
30
|
}
|
|
58
31
|
}
|
|
59
32
|
export class BrowserSession {
|
|
60
|
-
|
|
33
|
+
browser = null;
|
|
34
|
+
context = null;
|
|
35
|
+
page = null;
|
|
61
36
|
config;
|
|
62
37
|
cookieStore;
|
|
63
38
|
state = {
|
|
@@ -115,7 +90,7 @@ export class BrowserSession {
|
|
|
115
90
|
};
|
|
116
91
|
}
|
|
117
92
|
if (action !== "open") {
|
|
118
|
-
const warning = this.actionTracker.record(action, args, this.
|
|
93
|
+
const warning = this.actionTracker.record(action, args, this.page?.url() || "");
|
|
119
94
|
if (warning && result.success) {
|
|
120
95
|
result.output = result.output + "\n\n⚠️ " + warning;
|
|
121
96
|
}
|
|
@@ -129,41 +104,45 @@ export class BrowserSession {
|
|
|
129
104
|
};
|
|
130
105
|
}
|
|
131
106
|
}
|
|
132
|
-
async
|
|
133
|
-
if (!this.
|
|
107
|
+
async ensurePage() {
|
|
108
|
+
if (!this.page) {
|
|
134
109
|
return { success: false, output: t("browser.no_page") };
|
|
135
110
|
}
|
|
136
111
|
return null;
|
|
137
112
|
}
|
|
138
113
|
async launch() {
|
|
139
|
-
if (this.
|
|
114
|
+
if (this.browser)
|
|
140
115
|
return;
|
|
141
|
-
this.
|
|
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
|
+
});
|
|
142
123
|
const savedCookies = await this.cookieStore.load();
|
|
143
124
|
if (savedCookies.length > 0) {
|
|
144
|
-
await this.
|
|
125
|
+
await this.context.addCookies(savedCookies.map((c) => ({
|
|
126
|
+
...c,
|
|
127
|
+
sameSite: c.sameSite,
|
|
128
|
+
})));
|
|
145
129
|
}
|
|
130
|
+
this.page = await this.context.newPage();
|
|
131
|
+
this.page.setDefaultTimeout(this.config.navigationTimeout);
|
|
146
132
|
}
|
|
147
133
|
async saveCookies() {
|
|
148
|
-
if (!this.
|
|
134
|
+
if (!this.context)
|
|
149
135
|
return;
|
|
150
|
-
const cookies = await this.
|
|
136
|
+
const cookies = await this.context.cookies();
|
|
151
137
|
await this.cookieStore.save(cookies);
|
|
152
138
|
}
|
|
153
139
|
async takeSnapshot() {
|
|
154
|
-
if (!this.
|
|
140
|
+
if (!this.page)
|
|
155
141
|
return "No page open.";
|
|
156
|
-
await this.
|
|
157
|
-
const html = await this.
|
|
158
|
-
const url = this.
|
|
159
|
-
const title = await this.
|
|
160
|
-
let content = "";
|
|
161
|
-
try {
|
|
162
|
-
content = String(await this.driver.evaluate(buildTextExtractionScript()));
|
|
163
|
-
}
|
|
164
|
-
catch {
|
|
165
|
-
content = "";
|
|
166
|
-
}
|
|
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();
|
|
167
146
|
const allElements = extractInteractiveElements(html, this.config.maxElements + 5);
|
|
168
147
|
const truncated = allElements.length > this.config.maxElements;
|
|
169
148
|
const elements = allElements.slice(0, this.config.maxElements);
|
|
@@ -173,30 +152,26 @@ export class BrowserSession {
|
|
|
173
152
|
title,
|
|
174
153
|
elementCount: elements.length,
|
|
175
154
|
};
|
|
176
|
-
return formatSnapshot({
|
|
177
|
-
url,
|
|
178
|
-
title,
|
|
179
|
-
elements,
|
|
180
|
-
content,
|
|
181
|
-
console: this.driver.getConsole(),
|
|
182
|
-
networkErrors: this.driver.getNetworkErrors(),
|
|
183
|
-
truncated,
|
|
184
|
-
});
|
|
155
|
+
return formatSnapshot({ url, title, elements, truncated });
|
|
185
156
|
}
|
|
186
157
|
async open(url) {
|
|
187
158
|
if (!url)
|
|
188
159
|
return { success: false, output: t("browser.url_required") };
|
|
189
|
-
if (!url.startsWith("http://") &&
|
|
160
|
+
if (!url.startsWith("http://") &&
|
|
161
|
+
!url.startsWith("https://") &&
|
|
162
|
+
!url.startsWith("file://")) {
|
|
190
163
|
const isLocalhost = /^(localhost|127\.0\.0\.1|0\.0\.0\.0)(:\d+)?$/i.test(url);
|
|
191
164
|
url = isLocalhost ? "http://" + url : "https://" + url;
|
|
192
165
|
}
|
|
193
166
|
await this.launch();
|
|
194
|
-
if (!this.
|
|
167
|
+
if (!this.page)
|
|
195
168
|
return { success: false, output: t("browser.create_page_failed") };
|
|
196
169
|
this.actionTracker.reset();
|
|
197
|
-
this.driver.resetEventLog();
|
|
198
170
|
try {
|
|
199
|
-
await this.
|
|
171
|
+
await this.page.goto(url, {
|
|
172
|
+
waitUntil: "domcontentloaded",
|
|
173
|
+
timeout: this.config.navigationTimeout,
|
|
174
|
+
});
|
|
200
175
|
}
|
|
201
176
|
catch (err) {
|
|
202
177
|
return {
|
|
@@ -209,17 +184,20 @@ export class BrowserSession {
|
|
|
209
184
|
return { success: true, output: snapshot };
|
|
210
185
|
}
|
|
211
186
|
async click(target) {
|
|
212
|
-
const err = await this.
|
|
187
|
+
const err = await this.ensurePage();
|
|
213
188
|
if (err)
|
|
214
189
|
return err;
|
|
215
|
-
if (!this.
|
|
190
|
+
if (!this.page)
|
|
216
191
|
return { success: false, output: "No page" };
|
|
217
192
|
if (!target || target < 1) {
|
|
218
193
|
return { success: false, output: t("browser.invalid_target") };
|
|
219
194
|
}
|
|
220
195
|
try {
|
|
221
|
-
|
|
222
|
-
await this.
|
|
196
|
+
const script = buildClickScript(target);
|
|
197
|
+
await this.page.evaluate(script);
|
|
198
|
+
await this.page
|
|
199
|
+
.waitForLoadState("domcontentloaded", { timeout: 5000 })
|
|
200
|
+
.catch(() => { });
|
|
223
201
|
await new Promise((r) => setTimeout(r, 500));
|
|
224
202
|
const snapshot = await this.takeSnapshot();
|
|
225
203
|
await this.saveCookies();
|
|
@@ -233,10 +211,10 @@ export class BrowserSession {
|
|
|
233
211
|
}
|
|
234
212
|
}
|
|
235
213
|
async type(target, text) {
|
|
236
|
-
const err = await this.
|
|
214
|
+
const err = await this.ensurePage();
|
|
237
215
|
if (err)
|
|
238
216
|
return err;
|
|
239
|
-
if (!this.
|
|
217
|
+
if (!this.page)
|
|
240
218
|
return { success: false, output: "No page" };
|
|
241
219
|
if (!target || target < 1) {
|
|
242
220
|
return { success: false, output: t("browser.invalid_target_short") };
|
|
@@ -245,7 +223,8 @@ export class BrowserSession {
|
|
|
245
223
|
return { success: false, output: t("browser.text_required") };
|
|
246
224
|
}
|
|
247
225
|
try {
|
|
248
|
-
|
|
226
|
+
const script = buildTypeScript(target, text);
|
|
227
|
+
await this.page.evaluate(script);
|
|
249
228
|
const snapshot = await this.takeSnapshot();
|
|
250
229
|
return { success: true, output: snapshot };
|
|
251
230
|
}
|
|
@@ -257,17 +236,17 @@ export class BrowserSession {
|
|
|
257
236
|
}
|
|
258
237
|
}
|
|
259
238
|
async scroll(direction) {
|
|
260
|
-
const err = await this.
|
|
239
|
+
const err = await this.ensurePage();
|
|
261
240
|
if (err)
|
|
262
241
|
return err;
|
|
263
|
-
if (!this.
|
|
242
|
+
if (!this.page)
|
|
264
243
|
return { success: false, output: "No page" };
|
|
265
244
|
const dir = direction;
|
|
266
245
|
if (!["up", "down", "top", "bottom"].includes(dir)) {
|
|
267
246
|
return { success: false, output: t("browser.direction_invalid") };
|
|
268
247
|
}
|
|
269
248
|
try {
|
|
270
|
-
await this.
|
|
249
|
+
await this.page.evaluate(buildScrollScript(dir));
|
|
271
250
|
await new Promise((r) => setTimeout(r, 300));
|
|
272
251
|
const snapshot = await this.takeSnapshot();
|
|
273
252
|
return { success: true, output: snapshot };
|
|
@@ -280,46 +259,56 @@ export class BrowserSession {
|
|
|
280
259
|
}
|
|
281
260
|
}
|
|
282
261
|
async back() {
|
|
283
|
-
const err = await this.
|
|
262
|
+
const err = await this.ensurePage();
|
|
284
263
|
if (err)
|
|
285
264
|
return err;
|
|
286
|
-
if (!this.
|
|
265
|
+
if (!this.page)
|
|
287
266
|
return { success: false, output: "No page" };
|
|
288
|
-
await this.
|
|
267
|
+
await this.page
|
|
268
|
+
.goBack({
|
|
269
|
+
waitUntil: "domcontentloaded",
|
|
270
|
+
timeout: this.config.navigationTimeout,
|
|
271
|
+
})
|
|
272
|
+
.catch(() => { });
|
|
289
273
|
await new Promise((r) => setTimeout(r, 300));
|
|
290
274
|
const snapshot = await this.takeSnapshot();
|
|
291
275
|
return { success: true, output: snapshot };
|
|
292
276
|
}
|
|
293
277
|
async forward() {
|
|
294
|
-
const err = await this.
|
|
278
|
+
const err = await this.ensurePage();
|
|
295
279
|
if (err)
|
|
296
280
|
return err;
|
|
297
|
-
if (!this.
|
|
281
|
+
if (!this.page)
|
|
298
282
|
return { success: false, output: "No page" };
|
|
299
|
-
await this.
|
|
283
|
+
await this.page
|
|
284
|
+
.goForward({
|
|
285
|
+
waitUntil: "domcontentloaded",
|
|
286
|
+
timeout: this.config.navigationTimeout,
|
|
287
|
+
})
|
|
288
|
+
.catch(() => { });
|
|
300
289
|
await new Promise((r) => setTimeout(r, 300));
|
|
301
290
|
const snapshot = await this.takeSnapshot();
|
|
302
291
|
return { success: true, output: snapshot };
|
|
303
292
|
}
|
|
304
293
|
async screenshot() {
|
|
305
|
-
const err = await this.
|
|
294
|
+
const err = await this.ensurePage();
|
|
306
295
|
if (err)
|
|
307
296
|
return err;
|
|
308
|
-
if (!this.
|
|
297
|
+
if (!this.page)
|
|
309
298
|
return { success: false, output: "No page" };
|
|
310
|
-
const buffer = await this.
|
|
299
|
+
const buffer = await this.page.screenshot({ type: "png", fullPage: false });
|
|
311
300
|
const snapshot = await this.takeSnapshot();
|
|
312
301
|
return { success: true, output: snapshot, screenshot: buffer };
|
|
313
302
|
}
|
|
314
303
|
async snapshot() {
|
|
315
|
-
const err = await this.
|
|
304
|
+
const err = await this.ensurePage();
|
|
316
305
|
if (err)
|
|
317
306
|
return err;
|
|
318
307
|
const snap = await this.takeSnapshot();
|
|
319
308
|
return { success: true, output: snap };
|
|
320
309
|
}
|
|
321
310
|
async wait(ms) {
|
|
322
|
-
const err = await this.
|
|
311
|
+
const err = await this.ensurePage();
|
|
323
312
|
if (err)
|
|
324
313
|
return err;
|
|
325
314
|
await new Promise((r) => setTimeout(r, Math.min(ms, 10000)));
|
|
@@ -328,9 +317,17 @@ export class BrowserSession {
|
|
|
328
317
|
}
|
|
329
318
|
async close() {
|
|
330
319
|
await this.saveCookies();
|
|
331
|
-
if (this.
|
|
332
|
-
await this.
|
|
333
|
-
this.
|
|
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;
|
|
334
331
|
}
|
|
335
332
|
this.state = { isOpen: false, url: null, title: null, elementCount: 0 };
|
|
336
333
|
return { success: true, output: t("browser.closed") };
|
|
@@ -1,50 +1,46 @@
|
|
|
1
|
-
import { t } from
|
|
2
|
-
import { DEFAULT_BROWSER_CONFIG } from "./types";
|
|
1
|
+
import { t } from '../../i18n/index';
|
|
3
2
|
function inferRole(tag, type) {
|
|
4
3
|
const t = tag.toLowerCase();
|
|
5
|
-
if (t ===
|
|
6
|
-
return
|
|
7
|
-
if (t ===
|
|
8
|
-
return
|
|
9
|
-
if (t ===
|
|
4
|
+
if (t === 'a')
|
|
5
|
+
return 'link';
|
|
6
|
+
if (t === 'button')
|
|
7
|
+
return 'button';
|
|
8
|
+
if (t === 'input') {
|
|
10
9
|
const inputType = type?.toLowerCase();
|
|
11
|
-
if (inputType ===
|
|
12
|
-
return
|
|
13
|
-
if (inputType ===
|
|
14
|
-
return
|
|
15
|
-
if (inputType ===
|
|
16
|
-
return
|
|
17
|
-
return
|
|
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';
|
|
18
17
|
}
|
|
19
|
-
if (t ===
|
|
20
|
-
return
|
|
21
|
-
if (t ===
|
|
22
|
-
return
|
|
23
|
-
return
|
|
18
|
+
if (t === 'textarea')
|
|
19
|
+
return 'textbox';
|
|
20
|
+
if (t === 'select')
|
|
21
|
+
return 'combobox';
|
|
22
|
+
return 'widget';
|
|
24
23
|
}
|
|
25
24
|
function getName(tag, attrs, html, matchIndex) {
|
|
26
|
-
const fromAttr = attrs[
|
|
27
|
-
attrs[
|
|
28
|
-
attrs[
|
|
29
|
-
attrs[
|
|
30
|
-
attrs[
|
|
31
|
-
attrs[
|
|
32
|
-
|
|
25
|
+
const fromAttr = (attrs['aria-label'] ||
|
|
26
|
+
attrs['placeholder'] ||
|
|
27
|
+
attrs['title'] ||
|
|
28
|
+
attrs['alt'] ||
|
|
29
|
+
attrs['value'] ||
|
|
30
|
+
attrs['name'] ||
|
|
31
|
+
'');
|
|
33
32
|
if (fromAttr)
|
|
34
33
|
return fromAttr;
|
|
35
34
|
const afterTag = html.slice(matchIndex);
|
|
36
|
-
const closeIdx = afterTag.indexOf(
|
|
35
|
+
const closeIdx = afterTag.indexOf('>');
|
|
37
36
|
if (closeIdx === -1)
|
|
38
|
-
return
|
|
37
|
+
return '';
|
|
39
38
|
const afterOpen = afterTag.slice(closeIdx + 1);
|
|
40
39
|
const endTag = `</${tag}>`;
|
|
41
40
|
const endIdx = afterOpen.indexOf(endTag);
|
|
42
41
|
if (endIdx === -1)
|
|
43
|
-
return
|
|
44
|
-
const text = afterOpen
|
|
45
|
-
.slice(0, endIdx)
|
|
46
|
-
.replace(/<[^>]+>/g, "")
|
|
47
|
-
.trim();
|
|
42
|
+
return '';
|
|
43
|
+
const text = afterOpen.slice(0, endIdx).replace(/<[^>]+>/g, '').trim();
|
|
48
44
|
return text.slice(0, 50);
|
|
49
45
|
}
|
|
50
46
|
export function extractInteractiveElements(html, maxElements = 30) {
|
|
@@ -60,21 +56,23 @@ export function extractInteractiveElements(html, maxElements = 30) {
|
|
|
60
56
|
const attrRegex = /(\w[\w-]*)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|(\S+)))?/g;
|
|
61
57
|
let attrMatch;
|
|
62
58
|
while ((attrMatch = attrRegex.exec(attrStr)) !== null) {
|
|
63
|
-
attrs[attrMatch[1].toLowerCase()] = attrMatch[2] ?? attrMatch[3] ?? attrMatch[4] ??
|
|
59
|
+
attrs[attrMatch[1].toLowerCase()] = attrMatch[2] ?? attrMatch[3] ?? attrMatch[4] ?? '';
|
|
64
60
|
}
|
|
65
|
-
if (attrs[
|
|
61
|
+
if (attrs['style']?.includes('display:none') || attrs['style']?.includes('display: none'))
|
|
66
62
|
continue;
|
|
67
|
-
if (attrs[
|
|
63
|
+
if (attrs['hidden'] !== undefined)
|
|
68
64
|
continue;
|
|
69
|
-
if (attrs[
|
|
65
|
+
if (attrs['aria-hidden'] === 'true')
|
|
70
66
|
continue;
|
|
71
|
-
if (tag ===
|
|
67
|
+
if (tag === 'input' && attrs['type'] === 'hidden')
|
|
72
68
|
continue;
|
|
73
|
-
const role = attrs[
|
|
74
|
-
const isInteractive = [
|
|
69
|
+
const role = attrs['role'] || inferRole(tag, attrs['type']);
|
|
70
|
+
const isInteractive = [
|
|
71
|
+
'a', 'button', 'input', 'textarea', 'select',
|
|
72
|
+
].includes(tag) || attrs['role'] !== undefined;
|
|
75
73
|
if (!isInteractive)
|
|
76
74
|
continue;
|
|
77
|
-
if (tag ===
|
|
75
|
+
if (tag === 'a' && !attrs['href'])
|
|
78
76
|
continue;
|
|
79
77
|
const name = getName(tag, attrs, html, match.index);
|
|
80
78
|
elements.push({
|
|
@@ -82,67 +80,35 @@ export function extractInteractiveElements(html, maxElements = 30) {
|
|
|
82
80
|
tag,
|
|
83
81
|
role,
|
|
84
82
|
name,
|
|
85
|
-
value: attrs[
|
|
86
|
-
href: attrs[
|
|
83
|
+
value: attrs['value'],
|
|
84
|
+
href: attrs['href'],
|
|
87
85
|
visible: true,
|
|
88
86
|
});
|
|
89
87
|
}
|
|
90
88
|
return elements;
|
|
91
89
|
}
|
|
92
|
-
export function
|
|
93
|
-
if (maxChars <= 0)
|
|
94
|
-
return "";
|
|
95
|
-
if (text.length <= maxChars)
|
|
96
|
-
return text;
|
|
97
|
-
const cut = text.slice(0, maxChars);
|
|
98
|
-
const lastNewline = cut.lastIndexOf("\n");
|
|
99
|
-
const safe = lastNewline > maxChars * 0.6 ? cut.slice(0, lastNewline) : cut;
|
|
100
|
-
return `${safe}\n${t("browser.truncated")}`;
|
|
101
|
-
}
|
|
102
|
-
export function formatSnapshot(snapshot, maxContentChars = DEFAULT_BROWSER_CONFIG.maxContentChars) {
|
|
90
|
+
export function formatSnapshot(snapshot) {
|
|
103
91
|
const lines = [];
|
|
104
92
|
lines.push(`Page: "${snapshot.title}"`);
|
|
105
93
|
lines.push(`URL: ${snapshot.url}`);
|
|
106
|
-
lines.push(
|
|
94
|
+
lines.push('');
|
|
107
95
|
if (snapshot.elements.length === 0 && !snapshot.truncated) {
|
|
108
|
-
lines.push(t(
|
|
109
|
-
|
|
110
|
-
else {
|
|
111
|
-
for (const el of snapshot.elements) {
|
|
112
|
-
let line = `[${el.index}] ${el.role}`;
|
|
113
|
-
if (el.name)
|
|
114
|
-
line += ` "${el.name}"`;
|
|
115
|
-
if (el.href)
|
|
116
|
-
line += ` → ${el.href}`;
|
|
117
|
-
if (el.value)
|
|
118
|
-
line += ` = "${el.value}"`;
|
|
119
|
-
lines.push(line);
|
|
120
|
-
}
|
|
121
|
-
if (snapshot.truncated) {
|
|
122
|
-
lines.push("");
|
|
123
|
-
lines.push(t("browser.more_elements"));
|
|
124
|
-
}
|
|
96
|
+
lines.push(t('browser.no_elements'));
|
|
97
|
+
return lines.join('\n');
|
|
125
98
|
}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
lines.push(
|
|
135
|
-
lines.push(t("browser.console_header"));
|
|
136
|
-
for (const entry of snapshot.console) {
|
|
137
|
-
lines.push(`[${entry.type}] ${entry.text}`);
|
|
138
|
-
}
|
|
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);
|
|
139
108
|
}
|
|
140
|
-
if (snapshot.
|
|
141
|
-
lines.push(
|
|
142
|
-
lines.push(t(
|
|
143
|
-
for (const err of snapshot.networkErrors) {
|
|
144
|
-
lines.push(`${err.method} ${err.url} → ${err.error}`);
|
|
145
|
-
}
|
|
109
|
+
if (snapshot.truncated) {
|
|
110
|
+
lines.push('');
|
|
111
|
+
lines.push(t('browser.more_elements'));
|
|
146
112
|
}
|
|
147
|
-
return lines.join(
|
|
113
|
+
return lines.join('\n');
|
|
148
114
|
}
|
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
export const DEFAULT_BROWSER_CONFIG = {
|
|
2
2
|
headless: true,
|
|
3
3
|
maxElements: 30,
|
|
4
|
-
maxContentChars: 2500,
|
|
5
|
-
maxConsoleEntries: 40,
|
|
6
|
-
maxConsoleLineChars: 400,
|
|
7
4
|
screenshotMaxWidth: 1280,
|
|
8
|
-
cookieDir:
|
|
5
|
+
cookieDir: '',
|
|
9
6
|
viewportWidth: 1280,
|
|
10
7
|
viewportHeight: 720,
|
|
11
8
|
navigationTimeout: 15000,
|
|
@@ -7,7 +7,7 @@ import { t } from "../../i18n/index";
|
|
|
7
7
|
import { pc } from "../../ui/colors";
|
|
8
8
|
import { loadScenarios, filterByTags } from "./loader";
|
|
9
9
|
import { runScenario, findMmaRoot } from "./runner";
|
|
10
|
-
import { readManifest, upsertCertification, removeCertification } from "./manifest";
|
|
10
|
+
import { readManifest, upsertCertification, removeCertification, } from "./manifest";
|
|
11
11
|
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
12
12
|
const MMA_ROOT = findMmaRoot(HERE);
|
|
13
13
|
const USER_SCENARIO_DIR = join(homedir(), ".mma", "certification", "scenarios");
|
|
@@ -85,7 +85,9 @@ export async function certify(opts) {
|
|
|
85
85
|
defaultReps: opts.reps,
|
|
86
86
|
defaultThreshold: 2,
|
|
87
87
|
onRep: (id, rep, reps, passed, failures) => {
|
|
88
|
-
const word = passed
|
|
88
|
+
const word = passed
|
|
89
|
+
? pc.green(t("cli.cert_rep_pass"))
|
|
90
|
+
: pc.red(t("cli.cert_rep_fail"));
|
|
89
91
|
console.log(`[${idx}/${total}] ${id} (${rep}/${reps})... ${word}`);
|
|
90
92
|
if (!passed)
|
|
91
93
|
console.log(` ${pc.dim(failures.join("; "))}`);
|
|
@@ -71,7 +71,9 @@ function describe(check) {
|
|
|
71
71
|
case "dirExists":
|
|
72
72
|
return `directory missing: ${check.path}`;
|
|
73
73
|
case "fileContent": {
|
|
74
|
-
const what = check.contains !== undefined
|
|
74
|
+
const what = check.contains !== undefined
|
|
75
|
+
? `contains "${check.contains}"`
|
|
76
|
+
: `equals "${check.equals}"`;
|
|
75
77
|
return `${check.path} does not ${what}`;
|
|
76
78
|
}
|
|
77
79
|
case "fileRegex":
|
|
@@ -35,7 +35,9 @@ export function validateScenario(s) {
|
|
|
35
35
|
if (!CHECK_TYPES.includes(c.type)) {
|
|
36
36
|
errors.push(`[${s.id}] unknown check type: ${c.type}`);
|
|
37
37
|
}
|
|
38
|
-
if (c.type === "fileContent" &&
|
|
38
|
+
if (c.type === "fileContent" &&
|
|
39
|
+
c.contains === undefined &&
|
|
40
|
+
c.equals === undefined) {
|
|
39
41
|
errors.push(`[${s.id}] fileContent needs contains or equals`);
|
|
40
42
|
}
|
|
41
43
|
}
|
|
@@ -46,7 +48,9 @@ export function validateScenario(s) {
|
|
|
46
48
|
(!Number.isInteger(s.passThreshold) || s.passThreshold < 1)) {
|
|
47
49
|
errors.push(`[${s.id}] passThreshold must be a positive integer`);
|
|
48
50
|
}
|
|
49
|
-
if (s.passThreshold !== undefined &&
|
|
51
|
+
if (s.passThreshold !== undefined &&
|
|
52
|
+
s.reps !== undefined &&
|
|
53
|
+
s.passThreshold > s.reps) {
|
|
50
54
|
errors.push(`[${s.id}] passThreshold > reps (must be <= reps)`);
|
|
51
55
|
}
|
|
52
56
|
return errors;
|
|
@@ -92,7 +96,9 @@ function normalizeScenario(data, file) {
|
|
|
92
96
|
prompt: String(d.prompt ?? ""),
|
|
93
97
|
reps: typeof d.reps === "number" ? d.reps : undefined,
|
|
94
98
|
passThreshold: typeof d.passThreshold === "number" ? d.passThreshold : undefined,
|
|
95
|
-
fixtures: Array.isArray(d.fixtures)
|
|
99
|
+
fixtures: Array.isArray(d.fixtures)
|
|
100
|
+
? d.fixtures
|
|
101
|
+
: undefined,
|
|
96
102
|
checks: Array.isArray(d.checks) ? d.checks : [],
|
|
97
103
|
skipReason: typeof d.skipReason === "string" ? d.skipReason : undefined,
|
|
98
104
|
};
|
|
@@ -98,7 +98,10 @@ export function resolveMmaEntry(mmaRoot) {
|
|
|
98
98
|
return join(mmaRoot, "dist", "main.js");
|
|
99
99
|
}
|
|
100
100
|
export function findMmaRoot(fromDir) {
|
|
101
|
-
const candidates = [
|
|
101
|
+
const candidates = [
|
|
102
|
+
resolve(fromDir, "..", "..", ".."),
|
|
103
|
+
resolve(fromDir, ".."),
|
|
104
|
+
];
|
|
102
105
|
for (const c of candidates) {
|
|
103
106
|
if (existsSync(join(c, "package.json")))
|
|
104
107
|
return c;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export { ContextManager } from
|
|
1
|
+
export { ContextManager } from './manager';
|