micro-models-agent 0.23.0 → 0.24.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.
Files changed (171) hide show
  1. package/README.md +312 -312
  2. package/bin/mma.mjs +8 -8
  3. package/dist/main.js +8777 -864
  4. package/package.json +4 -3
  5. package/dist/cli/commands.js +0 -220
  6. package/dist/cli/completer.js +0 -168
  7. package/dist/cli/index.js +0 -2
  8. package/dist/cli/main.js +0 -113
  9. package/dist/cli/repl.js +0 -987
  10. package/dist/cli/security-commands.js +0 -166
  11. package/dist/cli/setup.js +0 -229
  12. package/dist/config/config.js +0 -186
  13. package/dist/config/defaults.js +0 -91
  14. package/dist/config/experts.js +0 -15
  15. package/dist/config/index.js +0 -3
  16. package/dist/config/security.js +0 -193
  17. package/dist/config/types.js +0 -1
  18. package/dist/core/agent-moe.js +0 -98
  19. package/dist/core/agent.js +0 -461
  20. package/dist/core/bootstrap.js +0 -321
  21. package/dist/core/index.js +0 -2
  22. package/dist/core/prompt-builder.js +0 -55
  23. package/dist/core/session-logger.js +0 -122
  24. package/dist/core/types.js +0 -1
  25. package/dist/i18n/en.json +0 -461
  26. package/dist/i18n/index.js +0 -43
  27. package/dist/i18n/ru.json +0 -461
  28. package/dist/index.js +0 -22
  29. package/dist/llm/image-utils.js +0 -144
  30. package/dist/llm/index.js +0 -4
  31. package/dist/llm/model-loader.js +0 -78
  32. package/dist/llm/openai-compat.js +0 -324
  33. package/dist/llm/orchestrator.js +0 -194
  34. package/dist/llm/provider.js +0 -10
  35. package/dist/llm/response.js +0 -39
  36. package/dist/llm/token-counter.js +0 -39
  37. package/dist/llm/types.js +0 -1
  38. package/dist/logger/app-logger.js +0 -76
  39. package/dist/logger/index.js +0 -1
  40. package/dist/migration/backup.js +0 -45
  41. package/dist/migration/detect.js +0 -50
  42. package/dist/migration/index.js +0 -2
  43. package/dist/modules/browser/actions.js +0 -46
  44. package/dist/modules/browser/cookie-store.js +0 -24
  45. package/dist/modules/browser/index.js +0 -5
  46. package/dist/modules/browser/module.js +0 -28
  47. package/dist/modules/browser/session.js +0 -287
  48. package/dist/modules/browser/snapshot.js +0 -114
  49. package/dist/modules/browser/types.js +0 -9
  50. package/dist/modules/context/history.js +0 -15
  51. package/dist/modules/context/index.js +0 -1
  52. package/dist/modules/context/manager.js +0 -240
  53. package/dist/modules/execution/auditor.js +0 -72
  54. package/dist/modules/execution/index.js +0 -6
  55. package/dist/modules/execution/module.js +0 -337
  56. package/dist/modules/execution/moe-executor.js +0 -209
  57. package/dist/modules/execution/plan-validator.js +0 -153
  58. package/dist/modules/execution/planner.js +0 -35
  59. package/dist/modules/execution/stuck-detector.js +0 -134
  60. package/dist/modules/execution/tracker.js +0 -53
  61. package/dist/modules/execution/types.js +0 -1
  62. package/dist/modules/execution/verifier.js +0 -149
  63. package/dist/modules/hallucination/confidence.js +0 -54
  64. package/dist/modules/hallucination/consistency.js +0 -60
  65. package/dist/modules/hallucination/detector.js +0 -41
  66. package/dist/modules/hallucination/factual.js +0 -170
  67. package/dist/modules/hallucination/index.js +0 -4
  68. package/dist/modules/index.js +0 -5
  69. package/dist/modules/indexer/cache.js +0 -38
  70. package/dist/modules/indexer/index.js +0 -3
  71. package/dist/modules/indexer/module.js +0 -192
  72. package/dist/modules/indexer/walker.js +0 -101
  73. package/dist/modules/mcp/client.js +0 -393
  74. package/dist/modules/mcp/index.js +0 -3
  75. package/dist/modules/mcp/module.js +0 -146
  76. package/dist/modules/mcp/registry.js +0 -15
  77. package/dist/modules/memory/index.js +0 -1
  78. package/dist/modules/memory/module.js +0 -48
  79. package/dist/modules/memory/search.js +0 -40
  80. package/dist/modules/memory/store.js +0 -65
  81. package/dist/modules/pipelines/engine.js +0 -60
  82. package/dist/modules/pipelines/index.js +0 -3
  83. package/dist/modules/pipelines/parser.js +0 -53
  84. package/dist/modules/pipelines/template.js +0 -14
  85. package/dist/modules/plugins/builtin/lint-on-write.js +0 -121
  86. package/dist/modules/plugins/builtin/notify.js +0 -8
  87. package/dist/modules/plugins/index.js +0 -1
  88. package/dist/modules/plugins/loader.js +0 -28
  89. package/dist/modules/plugins/manager.js +0 -161
  90. package/dist/modules/plugins/types.js +0 -1
  91. package/dist/modules/processes/detect.js +0 -34
  92. package/dist/modules/processes/index.js +0 -3
  93. package/dist/modules/processes/registry.js +0 -148
  94. package/dist/modules/processes/runner.js +0 -124
  95. package/dist/modules/registry.js +0 -45
  96. package/dist/modules/security/audit-log.js +0 -116
  97. package/dist/modules/security/audit-notifier.js +0 -292
  98. package/dist/modules/security/command-validator.js +0 -185
  99. package/dist/modules/security/content-scanner.js +0 -52
  100. package/dist/modules/security/data-sanitizer.js +0 -97
  101. package/dist/modules/security/encryption.js +0 -240
  102. package/dist/modules/security/index.js +0 -14
  103. package/dist/modules/security/network-validator.js +0 -79
  104. package/dist/modules/security/path-validator.js +0 -155
  105. package/dist/modules/security/rate-limiter.js +0 -119
  106. package/dist/modules/security/security-policies.js +0 -393
  107. package/dist/modules/security/session-encryption.js +0 -193
  108. package/dist/modules/security/session-isolation.js +0 -95
  109. package/dist/modules/session/index.js +0 -3
  110. package/dist/modules/session/manager.js +0 -167
  111. package/dist/modules/session/module.js +0 -24
  112. package/dist/modules/session/store.js +0 -174
  113. package/dist/modules/session/types.js +0 -1
  114. package/dist/modules/skills/index.js +0 -3
  115. package/dist/modules/skills/loader.js +0 -72
  116. package/dist/modules/skills/matcher.js +0 -27
  117. package/dist/modules/skills/module.js +0 -143
  118. package/dist/modules/types.js +0 -1
  119. package/dist/modules/updater/checker.js +0 -32
  120. package/dist/modules/updater/index.js +0 -1
  121. package/dist/modules/user-profile/compressor.js +0 -16
  122. package/dist/modules/user-profile/index.js +0 -1
  123. package/dist/modules/user-profile/profile.js +0 -68
  124. package/dist/skills/builtin/builtin/git.md +0 -36
  125. package/dist/skills/builtin/builtin/typescript.md +0 -35
  126. package/dist/tools/approve.js +0 -32
  127. package/dist/tools/attach-image.js +0 -89
  128. package/dist/tools/bash.js +0 -140
  129. package/dist/tools/browser.js +0 -97
  130. package/dist/tools/create-dir.js +0 -56
  131. package/dist/tools/delete-file.js +0 -63
  132. package/dist/tools/edit-file.js +0 -77
  133. package/dist/tools/executor.js +0 -95
  134. package/dist/tools/file-info.js +0 -45
  135. package/dist/tools/filter-tools.js +0 -10
  136. package/dist/tools/glob-tool.js +0 -26
  137. package/dist/tools/grep-tool.js +0 -64
  138. package/dist/tools/index.js +0 -52
  139. package/dist/tools/list-dir.js +0 -47
  140. package/dist/tools/load-skill.js +0 -48
  141. package/dist/tools/mcp-call.js +0 -68
  142. package/dist/tools/move-file.js +0 -84
  143. package/dist/tools/path-utils.js +0 -51
  144. package/dist/tools/pipeline-run.js +0 -144
  145. package/dist/tools/preview.js +0 -2
  146. package/dist/tools/process-kill.js +0 -29
  147. package/dist/tools/process-list.js +0 -38
  148. package/dist/tools/process-log.js +0 -41
  149. package/dist/tools/question.js +0 -142
  150. package/dist/tools/read-file.js +0 -73
  151. package/dist/tools/recall.js +0 -110
  152. package/dist/tools/registry.js +0 -36
  153. package/dist/tools/remember.js +0 -67
  154. package/dist/tools/scope-check.js +0 -30
  155. package/dist/tools/search-history.js +0 -64
  156. package/dist/tools/subagent.js +0 -142
  157. package/dist/tools/types.js +0 -1
  158. package/dist/tools/user-input.js +0 -123
  159. package/dist/tools/web-browse.js +0 -57
  160. package/dist/tools/web-fetch.js +0 -72
  161. package/dist/tools/web-search.js +0 -59
  162. package/dist/tools/write-file.js +0 -80
  163. package/dist/ui/box.js +0 -81
  164. package/dist/ui/colors.js +0 -4
  165. package/dist/ui/diff.js +0 -185
  166. package/dist/ui/index.js +0 -6
  167. package/dist/ui/md-formatter.js +0 -212
  168. package/dist/ui/output.js +0 -13
  169. package/dist/ui/renderer.js +0 -141
  170. package/dist/ui/spinner.js +0 -70
  171. 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,9 +0,0 @@
1
- export const DEFAULT_BROWSER_CONFIG = {
2
- headless: true,
3
- maxElements: 30,
4
- screenshotMaxWidth: 1280,
5
- cookieDir: '',
6
- viewportWidth: 1280,
7
- viewportHeight: 720,
8
- navigationTimeout: 15000,
9
- };
@@ -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
- }