micro-models-agent 0.28.0 → 0.28.1
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/dist/main.js +314 -208
- package/package.json +1 -1
- package/dist/cli/commands.js +0 -220
- package/dist/cli/completer.js +0 -168
- package/dist/cli/index.js +0 -2
- package/dist/cli/main.js +0 -113
- package/dist/cli/repl.js +0 -987
- package/dist/cli/security-commands.js +0 -166
- package/dist/cli/setup.js +0 -229
- package/dist/config/config.js +0 -186
- package/dist/config/defaults.js +0 -91
- package/dist/config/experts.js +0 -15
- package/dist/config/index.js +0 -3
- package/dist/config/security.js +0 -193
- package/dist/config/types.js +0 -1
- package/dist/core/agent-moe.js +0 -98
- package/dist/core/agent.js +0 -461
- package/dist/core/bootstrap.js +0 -321
- package/dist/core/index.js +0 -2
- package/dist/core/prompt-builder.js +0 -55
- package/dist/core/session-logger.js +0 -122
- package/dist/core/types.js +0 -1
- package/dist/i18n/en.json +0 -461
- package/dist/i18n/index.js +0 -43
- package/dist/i18n/ru.json +0 -461
- 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 -324
- 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 -76
- 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 -287
- package/dist/modules/browser/snapshot.js +0 -114
- package/dist/modules/browser/types.js +0 -9
- package/dist/modules/context/history.js +0 -15
- package/dist/modules/context/index.js +0 -1
- package/dist/modules/context/manager.js +0 -240
- package/dist/modules/execution/auditor.js +0 -72
- package/dist/modules/execution/index.js +0 -6
- package/dist/modules/execution/module.js +0 -337
- package/dist/modules/execution/moe-executor.js +0 -209
- package/dist/modules/execution/plan-validator.js +0 -153
- package/dist/modules/execution/planner.js +0 -35
- package/dist/modules/execution/stuck-detector.js +0 -134
- package/dist/modules/execution/tracker.js +0 -53
- package/dist/modules/execution/types.js +0 -1
- package/dist/modules/execution/verifier.js +0 -149
- package/dist/modules/hallucination/confidence.js +0 -54
- package/dist/modules/hallucination/consistency.js +0 -60
- package/dist/modules/hallucination/detector.js +0 -41
- package/dist/modules/hallucination/factual.js +0 -170
- package/dist/modules/hallucination/index.js +0 -4
- 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/mcp/client.js +0 -393
- 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 -65
- 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 -121
- 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/detect.js +0 -34
- package/dist/modules/processes/index.js +0 -3
- package/dist/modules/processes/registry.js +0 -148
- package/dist/modules/processes/runner.js +0 -124
- package/dist/modules/registry.js +0 -45
- package/dist/modules/security/audit-log.js +0 -116
- package/dist/modules/security/audit-notifier.js +0 -292
- package/dist/modules/security/command-validator.js +0 -185
- package/dist/modules/security/content-scanner.js +0 -52
- 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 -155
- package/dist/modules/security/rate-limiter.js +0 -119
- package/dist/modules/security/security-policies.js +0 -393
- package/dist/modules/security/session-encryption.js +0 -193
- package/dist/modules/security/session-isolation.js +0 -95
- package/dist/modules/session/index.js +0 -3
- package/dist/modules/session/manager.js +0 -167
- package/dist/modules/session/module.js +0 -24
- package/dist/modules/session/store.js +0 -174
- package/dist/modules/session/types.js +0 -1
- package/dist/modules/skills/index.js +0 -3
- package/dist/modules/skills/loader.js +0 -72
- package/dist/modules/skills/matcher.js +0 -27
- package/dist/modules/skills/module.js +0 -143
- 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 -140
- package/dist/tools/browser.js +0 -97
- package/dist/tools/create-dir.js +0 -56
- package/dist/tools/delete-file.js +0 -63
- package/dist/tools/edit-file.js +0 -77
- package/dist/tools/executor.js +0 -95
- 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 -64
- package/dist/tools/index.js +0 -52
- package/dist/tools/list-dir.js +0 -47
- package/dist/tools/load-skill.js +0 -48
- package/dist/tools/mcp-call.js +0 -68
- package/dist/tools/move-file.js +0 -84
- 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 -73
- 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 -64
- package/dist/tools/subagent.js +0 -142
- package/dist/tools/types.js +0 -1
- package/dist/tools/user-input.js +0 -123
- package/dist/tools/web-browse.js +0 -57
- package/dist/tools/web-fetch.js +0 -72
- package/dist/tools/web-search.js +0 -59
- package/dist/tools/write-file.js +0 -80
- package/dist/ui/box.js +0 -81
- package/dist/ui/colors.js +0 -4
- package/dist/ui/diff.js +0 -185
- 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 -141
- package/dist/ui/spinner.js +0 -70
- package/dist/ui/table.js +0 -144
|
@@ -1,287 +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', { action, threshold: this.threshold });
|
|
22
|
-
}
|
|
23
|
-
return null;
|
|
24
|
-
}
|
|
25
|
-
reset() {
|
|
26
|
-
this.history = [];
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
export class BrowserSession {
|
|
30
|
-
browser = null;
|
|
31
|
-
context = null;
|
|
32
|
-
page = null;
|
|
33
|
-
config;
|
|
34
|
-
cookieStore;
|
|
35
|
-
state = { isOpen: false, url: null, title: null, elementCount: 0 };
|
|
36
|
-
actionTracker = new BrowserActionTracker(3);
|
|
37
|
-
constructor(config) {
|
|
38
|
-
this.config = config;
|
|
39
|
-
this.cookieStore = new CookieStore(config.cookieDir);
|
|
40
|
-
}
|
|
41
|
-
getState() {
|
|
42
|
-
return { ...this.state };
|
|
43
|
-
}
|
|
44
|
-
async execute(action, args) {
|
|
45
|
-
try {
|
|
46
|
-
let result;
|
|
47
|
-
switch (action) {
|
|
48
|
-
case 'open':
|
|
49
|
-
result = await this.open(String(args.url || ''));
|
|
50
|
-
break;
|
|
51
|
-
case 'click':
|
|
52
|
-
result = await this.click(Number(args.target));
|
|
53
|
-
break;
|
|
54
|
-
case 'type':
|
|
55
|
-
result = await this.type(Number(args.target), String(args.text || ''));
|
|
56
|
-
break;
|
|
57
|
-
case 'scroll':
|
|
58
|
-
result = await this.scroll(String(args.direction || 'down'));
|
|
59
|
-
break;
|
|
60
|
-
case 'back':
|
|
61
|
-
result = await this.back();
|
|
62
|
-
break;
|
|
63
|
-
case 'forward':
|
|
64
|
-
result = await this.forward();
|
|
65
|
-
break;
|
|
66
|
-
case 'screenshot':
|
|
67
|
-
result = await this.screenshot();
|
|
68
|
-
break;
|
|
69
|
-
case 'snapshot':
|
|
70
|
-
result = await this.snapshot();
|
|
71
|
-
break;
|
|
72
|
-
case 'close':
|
|
73
|
-
result = await this.close();
|
|
74
|
-
break;
|
|
75
|
-
case 'wait':
|
|
76
|
-
result = await this.wait(Number(args.ms || 1000));
|
|
77
|
-
break;
|
|
78
|
-
default: return { success: false, output: t('browser.unknown_action', { action }) };
|
|
79
|
-
}
|
|
80
|
-
if (action !== 'open') {
|
|
81
|
-
const warning = this.actionTracker.record(action, args, this.page?.url() || '');
|
|
82
|
-
if (warning && result.success) {
|
|
83
|
-
result.output = result.output + '\n\n⚠️ ' + warning;
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
return result;
|
|
87
|
-
}
|
|
88
|
-
catch (err) {
|
|
89
|
-
return { success: false, output: t('browser.error', { message: err.message }) };
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
async ensurePage() {
|
|
93
|
-
if (!this.page) {
|
|
94
|
-
return { success: false, output: t('browser.no_page') };
|
|
95
|
-
}
|
|
96
|
-
return null;
|
|
97
|
-
}
|
|
98
|
-
async launch() {
|
|
99
|
-
if (this.browser)
|
|
100
|
-
return;
|
|
101
|
-
this.browser = await chromium.launch({ headless: this.config.headless });
|
|
102
|
-
this.context = await this.browser.newContext({
|
|
103
|
-
viewport: { width: this.config.viewportWidth, height: this.config.viewportHeight },
|
|
104
|
-
});
|
|
105
|
-
const savedCookies = await this.cookieStore.load();
|
|
106
|
-
if (savedCookies.length > 0) {
|
|
107
|
-
await this.context.addCookies(savedCookies.map(c => ({
|
|
108
|
-
...c,
|
|
109
|
-
sameSite: c.sameSite,
|
|
110
|
-
})));
|
|
111
|
-
}
|
|
112
|
-
this.page = await this.context.newPage();
|
|
113
|
-
this.page.setDefaultTimeout(this.config.navigationTimeout);
|
|
114
|
-
}
|
|
115
|
-
async saveCookies() {
|
|
116
|
-
if (!this.context)
|
|
117
|
-
return;
|
|
118
|
-
const cookies = await this.context.cookies();
|
|
119
|
-
await this.cookieStore.save(cookies);
|
|
120
|
-
}
|
|
121
|
-
async takeSnapshot() {
|
|
122
|
-
if (!this.page)
|
|
123
|
-
return 'No page open.';
|
|
124
|
-
await this.page.evaluate(buildIndexInjectionScript());
|
|
125
|
-
const html = await this.page.content();
|
|
126
|
-
const url = this.page.url();
|
|
127
|
-
const title = await this.page.title();
|
|
128
|
-
const allElements = extractInteractiveElements(html, this.config.maxElements + 5);
|
|
129
|
-
const truncated = allElements.length > this.config.maxElements;
|
|
130
|
-
const elements = allElements.slice(0, this.config.maxElements);
|
|
131
|
-
this.state = {
|
|
132
|
-
isOpen: true,
|
|
133
|
-
url,
|
|
134
|
-
title,
|
|
135
|
-
elementCount: elements.length,
|
|
136
|
-
};
|
|
137
|
-
return formatSnapshot({ url, title, elements, truncated });
|
|
138
|
-
}
|
|
139
|
-
async open(url) {
|
|
140
|
-
if (!url)
|
|
141
|
-
return { success: false, output: t('browser.url_required') };
|
|
142
|
-
if (!url.startsWith('http://') && !url.startsWith('https://')) {
|
|
143
|
-
url = 'https://' + url;
|
|
144
|
-
}
|
|
145
|
-
await this.launch();
|
|
146
|
-
if (!this.page)
|
|
147
|
-
return { success: false, output: t('browser.create_page_failed') };
|
|
148
|
-
this.actionTracker.reset();
|
|
149
|
-
try {
|
|
150
|
-
await this.page.goto(url, { waitUntil: 'domcontentloaded', timeout: this.config.navigationTimeout });
|
|
151
|
-
}
|
|
152
|
-
catch (err) {
|
|
153
|
-
return { success: false, output: t('browser.nav_failed', { message: err.message }) };
|
|
154
|
-
}
|
|
155
|
-
const snapshot = await this.takeSnapshot();
|
|
156
|
-
await this.saveCookies();
|
|
157
|
-
return { success: true, output: snapshot };
|
|
158
|
-
}
|
|
159
|
-
async click(target) {
|
|
160
|
-
const err = await this.ensurePage();
|
|
161
|
-
if (err)
|
|
162
|
-
return err;
|
|
163
|
-
if (!this.page)
|
|
164
|
-
return { success: false, output: 'No page' };
|
|
165
|
-
if (!target || target < 1) {
|
|
166
|
-
return { success: false, output: t('browser.invalid_target') };
|
|
167
|
-
}
|
|
168
|
-
try {
|
|
169
|
-
const script = buildClickScript(target);
|
|
170
|
-
await this.page.evaluate(script);
|
|
171
|
-
await this.page.waitForLoadState('domcontentloaded', { timeout: 5000 }).catch(() => { });
|
|
172
|
-
await new Promise(r => setTimeout(r, 500));
|
|
173
|
-
const snapshot = await this.takeSnapshot();
|
|
174
|
-
await this.saveCookies();
|
|
175
|
-
return { success: true, output: snapshot };
|
|
176
|
-
}
|
|
177
|
-
catch (err) {
|
|
178
|
-
return { success: false, output: t('browser.click_failed', { message: err.message }) };
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
async type(target, text) {
|
|
182
|
-
const err = await this.ensurePage();
|
|
183
|
-
if (err)
|
|
184
|
-
return err;
|
|
185
|
-
if (!this.page)
|
|
186
|
-
return { success: false, output: 'No page' };
|
|
187
|
-
if (!target || target < 1) {
|
|
188
|
-
return { success: false, output: t('browser.invalid_target_short') };
|
|
189
|
-
}
|
|
190
|
-
if (!text) {
|
|
191
|
-
return { success: false, output: t('browser.text_required') };
|
|
192
|
-
}
|
|
193
|
-
try {
|
|
194
|
-
const script = buildTypeScript(target, text);
|
|
195
|
-
await this.page.evaluate(script);
|
|
196
|
-
const snapshot = await this.takeSnapshot();
|
|
197
|
-
return { success: true, output: snapshot };
|
|
198
|
-
}
|
|
199
|
-
catch (err) {
|
|
200
|
-
return { success: false, output: t('browser.type_failed', { message: err.message }) };
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
async scroll(direction) {
|
|
204
|
-
const err = await this.ensurePage();
|
|
205
|
-
if (err)
|
|
206
|
-
return err;
|
|
207
|
-
if (!this.page)
|
|
208
|
-
return { success: false, output: 'No page' };
|
|
209
|
-
const dir = direction;
|
|
210
|
-
if (!['up', 'down', 'top', 'bottom'].includes(dir)) {
|
|
211
|
-
return { success: false, output: t('browser.direction_invalid') };
|
|
212
|
-
}
|
|
213
|
-
try {
|
|
214
|
-
await this.page.evaluate(buildScrollScript(dir));
|
|
215
|
-
await new Promise(r => setTimeout(r, 300));
|
|
216
|
-
const snapshot = await this.takeSnapshot();
|
|
217
|
-
return { success: true, output: snapshot };
|
|
218
|
-
}
|
|
219
|
-
catch (err) {
|
|
220
|
-
return { success: false, output: t('browser.scroll_failed', { message: err.message }) };
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
async back() {
|
|
224
|
-
const err = await this.ensurePage();
|
|
225
|
-
if (err)
|
|
226
|
-
return err;
|
|
227
|
-
if (!this.page)
|
|
228
|
-
return { success: false, output: 'No page' };
|
|
229
|
-
await this.page.goBack({ waitUntil: 'domcontentloaded', timeout: this.config.navigationTimeout }).catch(() => { });
|
|
230
|
-
await new Promise(r => setTimeout(r, 300));
|
|
231
|
-
const snapshot = await this.takeSnapshot();
|
|
232
|
-
return { success: true, output: snapshot };
|
|
233
|
-
}
|
|
234
|
-
async forward() {
|
|
235
|
-
const err = await this.ensurePage();
|
|
236
|
-
if (err)
|
|
237
|
-
return err;
|
|
238
|
-
if (!this.page)
|
|
239
|
-
return { success: false, output: 'No page' };
|
|
240
|
-
await this.page.goForward({ waitUntil: 'domcontentloaded', timeout: this.config.navigationTimeout }).catch(() => { });
|
|
241
|
-
await new Promise(r => setTimeout(r, 300));
|
|
242
|
-
const snapshot = await this.takeSnapshot();
|
|
243
|
-
return { success: true, output: snapshot };
|
|
244
|
-
}
|
|
245
|
-
async screenshot() {
|
|
246
|
-
const err = await this.ensurePage();
|
|
247
|
-
if (err)
|
|
248
|
-
return err;
|
|
249
|
-
if (!this.page)
|
|
250
|
-
return { success: false, output: 'No page' };
|
|
251
|
-
const buffer = await this.page.screenshot({ type: 'png', fullPage: false });
|
|
252
|
-
const snapshot = await this.takeSnapshot();
|
|
253
|
-
return { success: true, output: snapshot, screenshot: buffer };
|
|
254
|
-
}
|
|
255
|
-
async snapshot() {
|
|
256
|
-
const err = await this.ensurePage();
|
|
257
|
-
if (err)
|
|
258
|
-
return err;
|
|
259
|
-
const snap = await this.takeSnapshot();
|
|
260
|
-
return { success: true, output: snap };
|
|
261
|
-
}
|
|
262
|
-
async wait(ms) {
|
|
263
|
-
const err = await this.ensurePage();
|
|
264
|
-
if (err)
|
|
265
|
-
return err;
|
|
266
|
-
await new Promise(r => setTimeout(r, Math.min(ms, 10000)));
|
|
267
|
-
const snapshot = await this.takeSnapshot();
|
|
268
|
-
return { success: true, output: snapshot };
|
|
269
|
-
}
|
|
270
|
-
async close() {
|
|
271
|
-
await this.saveCookies();
|
|
272
|
-
if (this.page) {
|
|
273
|
-
await this.page.close().catch(() => { });
|
|
274
|
-
this.page = null;
|
|
275
|
-
}
|
|
276
|
-
if (this.context) {
|
|
277
|
-
await this.context.close().catch(() => { });
|
|
278
|
-
this.context = null;
|
|
279
|
-
}
|
|
280
|
-
if (this.browser) {
|
|
281
|
-
await this.browser.close().catch(() => { });
|
|
282
|
-
this.browser = null;
|
|
283
|
-
}
|
|
284
|
-
this.state = { isOpen: false, url: null, title: null, elementCount: 0 };
|
|
285
|
-
return { success: true, output: t('browser.closed') };
|
|
286
|
-
}
|
|
287
|
-
}
|
|
@@ -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
|
-
}
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { appendFileSync, mkdirSync, existsSync } from 'fs';
|
|
2
|
-
import { join } from 'path';
|
|
3
|
-
export class SessionHistoryWriter {
|
|
4
|
-
historyPath;
|
|
5
|
-
constructor(sessionsDir, sessionId) {
|
|
6
|
-
const sessionDir = join(sessionsDir, sessionId);
|
|
7
|
-
if (!existsSync(sessionDir)) {
|
|
8
|
-
mkdirSync(sessionDir, { recursive: true });
|
|
9
|
-
}
|
|
10
|
-
this.historyPath = join(sessionDir, 'history.jsonl');
|
|
11
|
-
}
|
|
12
|
-
append(msg) {
|
|
13
|
-
appendFileSync(this.historyPath, JSON.stringify(msg) + '\n', 'utf-8');
|
|
14
|
-
}
|
|
15
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { ContextManager } from './manager';
|
|
@@ -1,240 +0,0 @@
|
|
|
1
|
-
import { getMessageText } from "../../llm/provider";
|
|
2
|
-
const COMPACTION_INTERVAL = 15;
|
|
3
|
-
const KEEP_LAST_N = 6;
|
|
4
|
-
export class ContextManager {
|
|
5
|
-
contextWindow;
|
|
6
|
-
messages = [];
|
|
7
|
-
compactedBlock = null;
|
|
8
|
-
iterationsSinceCompaction = 0;
|
|
9
|
-
budget;
|
|
10
|
-
compactionThreshold;
|
|
11
|
-
fileFacts = [];
|
|
12
|
-
decisionFacts = [];
|
|
13
|
-
errorFacts = [];
|
|
14
|
-
tokenCounter;
|
|
15
|
-
pendingImageParts = [];
|
|
16
|
-
onCompact = null;
|
|
17
|
-
constructor(contextWindow, contextBudget, tokenCounter) {
|
|
18
|
-
this.contextWindow = contextWindow;
|
|
19
|
-
this.compactionThreshold = contextBudget?.compactionThreshold ?? 0.75;
|
|
20
|
-
this.budget = this.calculateBudget(contextWindow, contextBudget);
|
|
21
|
-
this.tokenCounter = tokenCounter ?? null;
|
|
22
|
-
}
|
|
23
|
-
calculateBudget(window, contextBudget) {
|
|
24
|
-
if (contextBudget) {
|
|
25
|
-
const systemPrompt = Math.floor(window * contextBudget.systemPrompt);
|
|
26
|
-
const responseReserve = Math.floor(window * contextBudget.responseReserve);
|
|
27
|
-
const history = Math.max(0, window - systemPrompt - responseReserve);
|
|
28
|
-
return { systemPrompt, responseReserve, history };
|
|
29
|
-
}
|
|
30
|
-
// Fallback for backwards compatibility (tests, subagents without explicit budget)
|
|
31
|
-
return {
|
|
32
|
-
systemPrompt: Math.floor(window * 0.25),
|
|
33
|
-
responseReserve: Math.floor(window * 0.12),
|
|
34
|
-
history: Math.floor(window * 0.63),
|
|
35
|
-
};
|
|
36
|
-
}
|
|
37
|
-
getBudget() {
|
|
38
|
-
return { ...this.budget };
|
|
39
|
-
}
|
|
40
|
-
addMessage(msg) {
|
|
41
|
-
// Auto-attach pending images to the next user message
|
|
42
|
-
if (msg.role === "user" && this.pendingImageParts.length > 0) {
|
|
43
|
-
const textPart = {
|
|
44
|
-
type: "text",
|
|
45
|
-
text: typeof msg.content === "string" ? msg.content : getMessageText(msg.content),
|
|
46
|
-
};
|
|
47
|
-
msg = {
|
|
48
|
-
...msg,
|
|
49
|
-
content: [textPart, ...this.pendingImageParts],
|
|
50
|
-
};
|
|
51
|
-
this.pendingImageParts = [];
|
|
52
|
-
}
|
|
53
|
-
this.messages.push(msg);
|
|
54
|
-
this.iterationsSinceCompaction++;
|
|
55
|
-
}
|
|
56
|
-
/**
|
|
57
|
-
* Queue an image part to be attached to the next user message.
|
|
58
|
-
*/
|
|
59
|
-
addPendingImage(part) {
|
|
60
|
-
this.pendingImageParts.push(part);
|
|
61
|
-
}
|
|
62
|
-
/**
|
|
63
|
-
* Check if there are pending images waiting to be attached.
|
|
64
|
-
*/
|
|
65
|
-
hasPendingImages() {
|
|
66
|
-
return this.pendingImageParts.length > 0;
|
|
67
|
-
}
|
|
68
|
-
/**
|
|
69
|
-
* Get pending image parts without clearing them.
|
|
70
|
-
*/
|
|
71
|
-
getPendingImages() {
|
|
72
|
-
return [...this.pendingImageParts];
|
|
73
|
-
}
|
|
74
|
-
/**
|
|
75
|
-
* Clear pending images (e.g., if user sends a text-only message).
|
|
76
|
-
*/
|
|
77
|
-
clearPendingImages() {
|
|
78
|
-
this.pendingImageParts = [];
|
|
79
|
-
}
|
|
80
|
-
getMessageCount() {
|
|
81
|
-
return this.messages.length;
|
|
82
|
-
}
|
|
83
|
-
estimateMessageTokens(m) {
|
|
84
|
-
const text = getMessageText(m.content);
|
|
85
|
-
if (this.tokenCounter) {
|
|
86
|
-
let t = this.tokenCounter.count(text);
|
|
87
|
-
// Image tokens: base64 ~130 tokens per 512x512 tile; rough estimate
|
|
88
|
-
if (Array.isArray(m.content)) {
|
|
89
|
-
for (const part of m.content) {
|
|
90
|
-
if (part.type === "image_url" && part.image_url?.url) {
|
|
91
|
-
const b64Len = part.image_url.url.includes(",")
|
|
92
|
-
? part.image_url.url.split(",")[1]?.length ?? 0
|
|
93
|
-
: part.image_url.url.length;
|
|
94
|
-
// ~130 tokens per 512 bytes of base64
|
|
95
|
-
t += Math.ceil(b64Len / 512) * 130;
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
if (m.tool_calls) {
|
|
100
|
-
for (const tc of m.tool_calls) {
|
|
101
|
-
t += this.tokenCounter.count(tc.id);
|
|
102
|
-
t += this.tokenCounter.count(tc.function.name);
|
|
103
|
-
t += this.tokenCounter.count(tc.function.arguments);
|
|
104
|
-
t += 4;
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
return t;
|
|
108
|
-
}
|
|
109
|
-
let t = Math.ceil(text.length / 2);
|
|
110
|
-
if (Array.isArray(m.content)) {
|
|
111
|
-
for (const part of m.content) {
|
|
112
|
-
if (part.type === "image_url" && part.image_url?.url) {
|
|
113
|
-
const b64Len = part.image_url.url.includes(",")
|
|
114
|
-
? part.image_url.url.split(",")[1]?.length ?? 0
|
|
115
|
-
: part.image_url.url.length;
|
|
116
|
-
t += Math.ceil(b64Len / 512) * 130;
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
if (m.tool_calls) {
|
|
121
|
-
for (const tc of m.tool_calls) {
|
|
122
|
-
t += Math.ceil(tc.id.length / 2);
|
|
123
|
-
t += Math.ceil(tc.function.name.length / 2);
|
|
124
|
-
t += Math.ceil(tc.function.arguments.length / 2);
|
|
125
|
-
t += 4;
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
return t;
|
|
129
|
-
}
|
|
130
|
-
needsCompaction() {
|
|
131
|
-
if (this.iterationsSinceCompaction >= COMPACTION_INTERVAL)
|
|
132
|
-
return true;
|
|
133
|
-
const totalTokens = this.messages.reduce((sum, m) => sum + this.estimateMessageTokens(m), 0);
|
|
134
|
-
return totalTokens > this.budget.history * this.compactionThreshold;
|
|
135
|
-
}
|
|
136
|
-
compact() {
|
|
137
|
-
if (this.messages.length <= KEEP_LAST_N * 2)
|
|
138
|
-
return;
|
|
139
|
-
const cutoff = this.messages.length - KEEP_LAST_N * 2;
|
|
140
|
-
const oldTurns = this.messages.slice(0, cutoff);
|
|
141
|
-
const recentTurns = this.messages.slice(cutoff);
|
|
142
|
-
this.extractFacts(oldTurns);
|
|
143
|
-
const parts = [];
|
|
144
|
-
parts.push(`[Compressed: ${oldTurns.length} old turns removed]`);
|
|
145
|
-
if (this.fileFacts.length > 0) {
|
|
146
|
-
parts.push(`[Files: ${this.fileFacts.slice(-15).join("; ")}]`);
|
|
147
|
-
}
|
|
148
|
-
if (this.decisionFacts.length > 0) {
|
|
149
|
-
parts.push(`[Decisions: ${this.decisionFacts.slice(-5).join("; ")}]`);
|
|
150
|
-
}
|
|
151
|
-
if (this.errorFacts.length > 0) {
|
|
152
|
-
parts.push(`[Errors: ${this.errorFacts.slice(-3).join("; ")}]`);
|
|
153
|
-
}
|
|
154
|
-
this.compactedBlock = parts.join(" ");
|
|
155
|
-
const summary = {
|
|
156
|
-
role: "user",
|
|
157
|
-
content: `<system-summary>${this.compactedBlock}</system-summary>`,
|
|
158
|
-
};
|
|
159
|
-
const firstSystem = this.messages.find((m) => m.role === "system");
|
|
160
|
-
this.messages = [
|
|
161
|
-
...(firstSystem ? [firstSystem] : []),
|
|
162
|
-
summary,
|
|
163
|
-
...recentTurns,
|
|
164
|
-
];
|
|
165
|
-
this.iterationsSinceCompaction = 0;
|
|
166
|
-
if (this.onCompact) {
|
|
167
|
-
this.onCompact(summary);
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
extractFacts(turns) {
|
|
171
|
-
const filePatterns = [
|
|
172
|
-
/(?:Created|Updated|Written|Deleted|Moved) ([\w./\\-]+\.[a-z]+)/gi,
|
|
173
|
-
/Файл (?:создан|обновлён|записан|удалён|перемещён):? ([\w./\\-]+\.[a-z]+)/gi,
|
|
174
|
-
/file (?:created|updated|written|deleted|moved):? ([\w./\\-]+\.[a-z]+)/gi,
|
|
175
|
-
];
|
|
176
|
-
for (const msg of turns) {
|
|
177
|
-
const content = getMessageText(msg.content);
|
|
178
|
-
if (msg.role === "tool") {
|
|
179
|
-
for (const pattern of filePatterns) {
|
|
180
|
-
for (const match of content.matchAll(pattern)) {
|
|
181
|
-
this.fileFacts.push(match[1]);
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
if (content.includes("Plan:") && content.includes("[")) {
|
|
185
|
-
const planLine = content
|
|
186
|
-
.split("\n")
|
|
187
|
-
.find((line) => line.includes("Plan:"));
|
|
188
|
-
if (planLine)
|
|
189
|
-
this.decisionFacts.push(planLine.trim());
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
if (msg.role === "assistant") {
|
|
193
|
-
if (content.includes("decided:") || content.includes("decision:")) {
|
|
194
|
-
const line = content
|
|
195
|
-
.split("\n")
|
|
196
|
-
.find((l) => l.includes("decided:") || l.includes("decision:"));
|
|
197
|
-
if (line)
|
|
198
|
-
this.decisionFacts.push(line.trim().slice(0, 200));
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
if (content.includes("Error:") ||
|
|
202
|
-
content.includes("failed") ||
|
|
203
|
-
content.includes("Ошибка:") ||
|
|
204
|
-
content.includes("не удалось")) {
|
|
205
|
-
const line = content
|
|
206
|
-
.split("\n")
|
|
207
|
-
.find((l) => l.includes("Error:") ||
|
|
208
|
-
l.includes("failed") ||
|
|
209
|
-
l.includes("Ошибка:") ||
|
|
210
|
-
l.includes("не удалось"));
|
|
211
|
-
if (line)
|
|
212
|
-
this.errorFacts.push(line.trim().slice(0, 250));
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
/**
|
|
217
|
-
* Replace the system prompt in place (keeps it first) or prepend a new one.
|
|
218
|
-
* Used to refresh dynamic prompt blocks (e.g. the plan checklist) mid-run.
|
|
219
|
-
*/
|
|
220
|
-
updateSystemPrompt(content) {
|
|
221
|
-
const idx = this.messages.findIndex((m) => m.role === "system");
|
|
222
|
-
if (idx >= 0) {
|
|
223
|
-
this.messages[idx] = { ...this.messages[idx], content };
|
|
224
|
-
}
|
|
225
|
-
else {
|
|
226
|
-
this.messages.unshift({ role: "system", content });
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
getActiveHistory() {
|
|
230
|
-
return [...this.messages];
|
|
231
|
-
}
|
|
232
|
-
clear() {
|
|
233
|
-
this.messages = [];
|
|
234
|
-
this.compactedBlock = null;
|
|
235
|
-
this.iterationsSinceCompaction = 0;
|
|
236
|
-
}
|
|
237
|
-
getEstimatedTokens() {
|
|
238
|
-
return this.messages.reduce((sum, m) => sum + this.estimateMessageTokens(m), 0);
|
|
239
|
-
}
|
|
240
|
-
}
|