micro-models-agent 0.28.17 → 0.29.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/cli/commands.js +3 -116
- package/dist/cli/main.js +8 -35
- package/dist/cli/repl.js +611 -110
- package/dist/cli/setup.js +12 -32
- package/dist/config/config.js +30 -46
- package/dist/config/defaults.js +1 -10
- package/dist/config/security.js +8 -15
- package/dist/core/agent-moe.js +12 -24
- package/dist/core/agent.js +47 -281
- package/dist/core/bootstrap.js +36 -52
- package/dist/core/session-logger.js +2 -35
- package/dist/i18n/en.json +15 -79
- package/dist/i18n/index.js +9 -12
- package/dist/i18n/ru.json +15 -79
- package/dist/index.js +13 -13
- package/dist/llm/openai-compat.js +10 -39
- package/dist/logger/app-logger.js +16 -83
- package/dist/main.js +625 -243
- package/dist/modules/browser/session.js +60 -108
- package/dist/modules/context/history.js +15 -0
- package/dist/modules/context/manager.js +10 -119
- package/dist/modules/execution/auditor.js +39 -33
- package/dist/modules/execution/index.js +6 -8
- package/dist/modules/execution/module.js +32 -474
- package/dist/modules/execution/moe-executor.js +40 -97
- package/dist/modules/execution/planner.js +13 -63
- package/dist/modules/execution/stuck-detector.js +39 -252
- package/dist/modules/execution/tracker.js +7 -21
- package/dist/modules/execution/verifier.js +17 -46
- package/dist/modules/hallucination/confidence.js +2 -7
- package/dist/modules/hallucination/consistency.js +42 -8
- package/dist/modules/hallucination/detector.js +21 -26
- package/dist/modules/hallucination/factual.js +150 -170
- package/dist/modules/hallucination/index.js +4 -5
- package/dist/modules/index.js +5 -5
- package/dist/modules/mcp/client.js +2 -8
- package/dist/modules/memory/store.js +0 -4
- package/dist/modules/plugins/builtin/lint-on-write.js +38 -143
- package/dist/modules/processes/detect.js +34 -0
- package/dist/modules/processes/index.js +2 -1
- package/dist/modules/processes/registry.js +35 -125
- package/dist/modules/processes/runner.js +110 -9
- package/dist/modules/security/audit-log.js +10 -30
- package/dist/modules/security/command-validator.js +16 -42
- package/dist/modules/security/content-scanner.js +8 -9
- package/dist/modules/security/network-validator.js +2 -2
- package/dist/modules/security/path-validator.js +10 -64
- package/dist/modules/security/security-policies.js +67 -221
- package/dist/modules/security/session-encryption.js +25 -42
- package/dist/modules/session/manager.js +10 -15
- package/dist/modules/session/store.js +8 -62
- package/dist/modules/skills/index.js +3 -2
- package/dist/modules/skills/matcher.js +27 -0
- package/dist/modules/skills/module.js +23 -10
- package/dist/tools/bash.js +90 -287
- package/dist/tools/create-dir.js +1 -0
- package/dist/tools/delete-file.js +1 -0
- package/dist/tools/edit-file.js +8 -10
- package/dist/tools/executor.js +7 -57
- package/dist/tools/grep-tool.js +29 -51
- package/dist/tools/index.js +40 -55
- package/dist/tools/load-skill.js +18 -14
- package/dist/tools/move-file.js +2 -3
- package/dist/tools/pipeline-run.js +1 -1
- package/dist/tools/read-file.js +5 -15
- package/dist/tools/search-history.js +22 -42
- package/dist/tools/subagent.js +12 -21
- package/dist/tools/web-browse.js +25 -54
- package/dist/tools/web-fetch.js +34 -60
- package/dist/tools/web-search.js +20 -39
- package/dist/tools/write-file.js +10 -13
- package/dist/ui/diff.js +16 -9
- package/dist/ui/renderer.js +6 -69
- package/package.json +1 -1
- package/dist/cli/repl-commands.js +0 -633
- package/dist/core/workspace.js +0 -76
- package/dist/logger/file-log.js +0 -151
- package/dist/modules/certification/cli.js +0 -176
- package/dist/modules/certification/fact-checker.js +0 -84
- package/dist/modules/certification/loader.js +0 -111
- package/dist/modules/certification/manifest.js +0 -50
- package/dist/modules/certification/runner.js +0 -162
- package/dist/modules/certification/scenarios.js +0 -124
- package/dist/modules/certification/types.js +0 -1
- package/dist/modules/execution/plan-coverage.js +0 -68
- package/dist/modules/execution/plan-persister.js +0 -46
- package/dist/modules/execution/plan-store.js +0 -159
- package/dist/modules/hallucination/js-identifiers.js +0 -72
- package/dist/modules/hallucination/llm-judge.js +0 -103
- package/dist/modules/lsp/client.js +0 -235
- package/dist/modules/lsp/config.js +0 -81
- package/dist/modules/lsp/index.js +0 -3
- package/dist/modules/lsp/module.js +0 -68
- package/dist/modules/lsp/types.js +0 -1
package/dist/tools/web-browse.js
CHANGED
|
@@ -1,22 +1,22 @@
|
|
|
1
|
-
import { t } from
|
|
2
|
-
import { isUrlAllowed, sanitizeUrl
|
|
3
|
-
import { logNetworkRequest, logSecurityBlock
|
|
4
|
-
import { getSessionSecurityConfig } from
|
|
5
|
-
import { MAX_PREVIEW_LINES } from
|
|
1
|
+
import { t } from '../i18n/index';
|
|
2
|
+
import { isUrlAllowed, sanitizeUrl } from '../modules/security/network-validator';
|
|
3
|
+
import { logNetworkRequest, logSecurityBlock } from '../modules/security/audit-log';
|
|
4
|
+
import { getSessionSecurityConfig } from '../modules/security/session-isolation';
|
|
5
|
+
import { MAX_PREVIEW_LINES } from './preview';
|
|
6
6
|
const MAX_CHARS = 3000;
|
|
7
7
|
export const webBrowseTool = {
|
|
8
|
-
name:
|
|
8
|
+
name: 'web_browse',
|
|
9
9
|
description: `Fetch and read a web page. Returns the page content as plain text (max ${MAX_PREVIEW_LINES} lines / ${MAX_CHARS} chars).`,
|
|
10
|
-
tags: [
|
|
10
|
+
tags: ['research'],
|
|
11
11
|
parameters: {
|
|
12
|
-
type:
|
|
12
|
+
type: 'object',
|
|
13
13
|
properties: {
|
|
14
|
-
url: { type:
|
|
14
|
+
url: { type: 'string', description: 'URL to fetch' },
|
|
15
15
|
},
|
|
16
|
-
required: [
|
|
16
|
+
required: ['url'],
|
|
17
17
|
},
|
|
18
18
|
handler: async (ctx, args) => {
|
|
19
|
-
const url = String(args.url ||
|
|
19
|
+
const url = String(args.url || '');
|
|
20
20
|
// Get session-specific security config
|
|
21
21
|
const securityConfig = ctx.sessionContext
|
|
22
22
|
? getSessionSecurityConfig(ctx.config, ctx.sessionContext).network
|
|
@@ -30,57 +30,28 @@ export const webBrowseTool = {
|
|
|
30
30
|
};
|
|
31
31
|
}
|
|
32
32
|
try {
|
|
33
|
-
const response = await fetch(url, {
|
|
34
|
-
signal: AbortSignal.timeout(securityConfig?.requestTimeout || 15000),
|
|
35
|
-
});
|
|
33
|
+
const response = await fetch(url, { signal: AbortSignal.timeout(securityConfig?.requestTimeout || 15000) });
|
|
36
34
|
const text = await response.text();
|
|
37
35
|
const stripped = text
|
|
38
|
-
.replace(/<script[\s\S]*?<\/script>/gi,
|
|
39
|
-
.replace(/<style[\s\S]*?<\/style>/gi,
|
|
40
|
-
.replace(/<[^>]+>/g,
|
|
41
|
-
.replace(/&[^;]+;/g,
|
|
42
|
-
.replace(/\s+/g,
|
|
36
|
+
.replace(/<script[\s\S]*?<\/script>/gi, '')
|
|
37
|
+
.replace(/<style[\s\S]*?<\/style>/gi, '')
|
|
38
|
+
.replace(/<[^>]+>/g, '')
|
|
39
|
+
.replace(/&[^;]+;/g, ' ')
|
|
40
|
+
.replace(/\s+/g, ' ')
|
|
43
41
|
.trim();
|
|
42
|
+
// Log successful network request
|
|
44
43
|
logNetworkRequest(ctx.sessionId, sanitizeUrl(url), true, `Status: ${response.status}`);
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
content = stripped.slice(0, maxChars) + t("file.truncated");
|
|
44
|
+
const maxLen = securityConfig?.maxResponseSize || MAX_CHARS;
|
|
45
|
+
let content = stripped.length > maxLen ? stripped.slice(0, maxLen) + t('file.truncated') : stripped;
|
|
46
|
+
const lines = content.split('\n');
|
|
47
|
+
if (lines.length > MAX_PREVIEW_LINES) {
|
|
48
|
+
content = lines.slice(0, MAX_PREVIEW_LINES).join('\n') + `\n... (${lines.length - MAX_PREVIEW_LINES} more lines)`;
|
|
51
49
|
}
|
|
52
|
-
|
|
53
|
-
content = stripped;
|
|
54
|
-
}
|
|
55
|
-
const contentLines = content.split("\n");
|
|
56
|
-
if (contentLines.length > MAX_PREVIEW_LINES) {
|
|
57
|
-
content =
|
|
58
|
-
contentLines.slice(0, MAX_PREVIEW_LINES).join("\n") +
|
|
59
|
-
`\n... (${contentLines.length - MAX_PREVIEW_LINES} more lines)`;
|
|
60
|
-
}
|
|
61
|
-
const truncated = stripped.length > maxChars || fullLines > MAX_PREVIEW_LINES
|
|
62
|
-
? t("file.truncated").trim()
|
|
63
|
-
: "";
|
|
64
|
-
return {
|
|
65
|
-
success: true,
|
|
66
|
-
output: content || t("file.empty_page"),
|
|
67
|
-
display: t("tool.web_browse_result", {
|
|
68
|
-
url: sanitizeUrl(url),
|
|
69
|
-
chars: String(fullChars),
|
|
70
|
-
lines: String(fullLines),
|
|
71
|
-
truncated,
|
|
72
|
-
}),
|
|
73
|
-
};
|
|
50
|
+
return { success: true, output: content || t('file.empty_page') };
|
|
74
51
|
}
|
|
75
52
|
catch (err) {
|
|
76
53
|
logNetworkRequest(ctx.sessionId, sanitizeUrl(url), false, `Error: ${err.message}`);
|
|
77
|
-
return {
|
|
78
|
-
success: false,
|
|
79
|
-
output: t("error.fetch_url_failed", {
|
|
80
|
-
url: sanitizeUrl(url),
|
|
81
|
-
message: err.message,
|
|
82
|
-
}),
|
|
83
|
-
};
|
|
54
|
+
return { success: false, output: t('error.fetch_url_failed', { url: sanitizeUrl(url), message: err.message }) };
|
|
84
55
|
}
|
|
85
56
|
},
|
|
86
57
|
};
|
package/dist/tools/web-fetch.js
CHANGED
|
@@ -1,29 +1,29 @@
|
|
|
1
|
-
import { t } from
|
|
2
|
-
import { isUrlAllowed, sanitizeUrl
|
|
3
|
-
import { logNetworkRequest, logSecurityBlock
|
|
4
|
-
import { getSessionSecurityConfig } from
|
|
5
|
-
import { DEFAULT_SECURITY_CONFIG } from
|
|
6
|
-
import { MAX_PREVIEW_LINES } from
|
|
1
|
+
import { t } from '../i18n/index';
|
|
2
|
+
import { isUrlAllowed, sanitizeUrl } from '../modules/security/network-validator';
|
|
3
|
+
import { logNetworkRequest, logSecurityBlock } from '../modules/security/audit-log';
|
|
4
|
+
import { getSessionSecurityConfig } from '../modules/security/session-isolation';
|
|
5
|
+
import { DEFAULT_SECURITY_CONFIG } from '../config/security';
|
|
6
|
+
import { MAX_PREVIEW_LINES } from './preview';
|
|
7
7
|
const MAX_CHARS = 5000;
|
|
8
8
|
function stripHtml(html) {
|
|
9
9
|
return html
|
|
10
|
-
.replace(/<script[\s\S]*?<\/script>/gi,
|
|
11
|
-
.replace(/<style[\s\S]*?<\/style>/gi,
|
|
12
|
-
.replace(/<[^>]+>/g,
|
|
13
|
-
.replace(/&[^;]+;/g,
|
|
14
|
-
.replace(/\s+/g,
|
|
10
|
+
.replace(/<script[\s\S]*?<\/script>/gi, '')
|
|
11
|
+
.replace(/<style[\s\S]*?<\/style>/gi, '')
|
|
12
|
+
.replace(/<[^>]+>/g, '')
|
|
13
|
+
.replace(/&[^;]+;/g, ' ')
|
|
14
|
+
.replace(/\s+/g, ' ')
|
|
15
15
|
.trim();
|
|
16
16
|
}
|
|
17
17
|
export const webFetchTool = {
|
|
18
|
-
name:
|
|
19
|
-
description:
|
|
20
|
-
tags: [
|
|
18
|
+
name: 'web_fetch',
|
|
19
|
+
description: 'Fetch a URL and convert its content to markdown. Use for reading documentation, APIs, web pages.',
|
|
20
|
+
tags: ['research'],
|
|
21
21
|
parameters: {
|
|
22
|
-
type:
|
|
22
|
+
type: 'object',
|
|
23
23
|
properties: {
|
|
24
|
-
url: { type:
|
|
24
|
+
url: { type: 'string', description: 'URL to fetch' },
|
|
25
25
|
},
|
|
26
|
-
required: [
|
|
26
|
+
required: ['url'],
|
|
27
27
|
},
|
|
28
28
|
handler: async (ctx, args) => {
|
|
29
29
|
const url = String(args.url);
|
|
@@ -41,58 +41,32 @@ export const webFetchTool = {
|
|
|
41
41
|
};
|
|
42
42
|
}
|
|
43
43
|
try {
|
|
44
|
-
const response = await fetch(url, {
|
|
45
|
-
signal: AbortSignal.timeout(securityConfig?.requestTimeout || 15000),
|
|
46
|
-
});
|
|
44
|
+
const response = await fetch(url, { signal: AbortSignal.timeout(securityConfig?.requestTimeout || 15000) });
|
|
47
45
|
if (!response.ok) {
|
|
48
|
-
return {
|
|
49
|
-
success: false,
|
|
50
|
-
output: t("error.http", {
|
|
51
|
-
status: response.status,
|
|
52
|
-
statusText: response.statusText,
|
|
53
|
-
}),
|
|
54
|
-
};
|
|
46
|
+
return { success: false, output: t('error.http', { status: response.status, statusText: response.statusText }) };
|
|
55
47
|
}
|
|
56
|
-
const contentType = response.headers.get(
|
|
48
|
+
const contentType = response.headers.get('content-type') || '';
|
|
57
49
|
const text = await response.text();
|
|
58
|
-
const cleaned = contentType.includes(
|
|
50
|
+
const cleaned = contentType.includes('html') ? stripHtml(text) : text;
|
|
51
|
+
// Log successful network request
|
|
59
52
|
logNetworkRequest(ctx.sessionId, sanitizeUrl(url), true, `Status: ${response.status}`);
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
53
|
+
if (cleaned.length > (securityConfig?.maxResponseSize || MAX_CHARS)) {
|
|
54
|
+
let content = cleaned.slice(0, securityConfig?.maxResponseSize || MAX_CHARS) + t('file.truncated');
|
|
55
|
+
const lines = content.split('\n');
|
|
56
|
+
if (lines.length > MAX_PREVIEW_LINES) {
|
|
57
|
+
content = lines.slice(0, MAX_PREVIEW_LINES).join('\n') + `\n... (${lines.length - MAX_PREVIEW_LINES} more lines)`;
|
|
58
|
+
}
|
|
59
|
+
return { success: true, output: content };
|
|
66
60
|
}
|
|
67
|
-
|
|
68
|
-
|
|
61
|
+
const lines = cleaned.split('\n');
|
|
62
|
+
if (lines.length > MAX_PREVIEW_LINES) {
|
|
63
|
+
return { success: true, output: lines.slice(0, MAX_PREVIEW_LINES).join('\n') + `\n... (${lines.length - MAX_PREVIEW_LINES} more lines)` };
|
|
69
64
|
}
|
|
70
|
-
|
|
71
|
-
if (contentLines.length > MAX_PREVIEW_LINES) {
|
|
72
|
-
content =
|
|
73
|
-
contentLines.slice(0, MAX_PREVIEW_LINES).join("\n") +
|
|
74
|
-
`\n... (${contentLines.length - MAX_PREVIEW_LINES} more lines)`;
|
|
75
|
-
}
|
|
76
|
-
const truncated = cleaned.length > maxChars || fullLines > MAX_PREVIEW_LINES
|
|
77
|
-
? t("file.truncated").trim()
|
|
78
|
-
: "";
|
|
79
|
-
return {
|
|
80
|
-
success: true,
|
|
81
|
-
output: content || t("file.empty_page"),
|
|
82
|
-
display: t("tool.web_fetch_result", {
|
|
83
|
-
url: sanitizeUrl(url),
|
|
84
|
-
chars: String(fullChars),
|
|
85
|
-
lines: String(fullLines),
|
|
86
|
-
truncated,
|
|
87
|
-
}),
|
|
88
|
-
};
|
|
65
|
+
return { success: true, output: cleaned || t('file.empty_page') };
|
|
89
66
|
}
|
|
90
67
|
catch (e) {
|
|
91
68
|
logNetworkRequest(ctx.sessionId, sanitizeUrl(url), false, `Error: ${e.message}`);
|
|
92
|
-
return {
|
|
93
|
-
success: false,
|
|
94
|
-
output: t("error.fetch_failed", { message: e.message }),
|
|
95
|
-
};
|
|
69
|
+
return { success: false, output: t('error.fetch_failed', { message: e.message }) };
|
|
96
70
|
}
|
|
97
71
|
},
|
|
98
72
|
};
|
package/dist/tools/web-search.js
CHANGED
|
@@ -1,24 +1,21 @@
|
|
|
1
|
-
import { t } from
|
|
2
|
-
import { isUrlAllowed, sanitizeUrl
|
|
3
|
-
import { logNetworkRequest, logSecurityBlock
|
|
4
|
-
import { getSessionSecurityConfig } from
|
|
1
|
+
import { t } from '../i18n/index';
|
|
2
|
+
import { isUrlAllowed, sanitizeUrl } from '../modules/security/network-validator';
|
|
3
|
+
import { logNetworkRequest, logSecurityBlock } from '../modules/security/audit-log';
|
|
4
|
+
import { getSessionSecurityConfig } from '../modules/security/session-isolation';
|
|
5
5
|
export const webSearchTool = {
|
|
6
|
-
name:
|
|
7
|
-
description:
|
|
8
|
-
tags: [
|
|
6
|
+
name: 'web_search',
|
|
7
|
+
description: 'Search the web for information. Returns search results with titles and snippets.',
|
|
8
|
+
tags: ['research'],
|
|
9
9
|
parameters: {
|
|
10
|
-
type:
|
|
10
|
+
type: 'object',
|
|
11
11
|
properties: {
|
|
12
|
-
query: { type:
|
|
13
|
-
numResults: {
|
|
14
|
-
type: "number",
|
|
15
|
-
description: "Number of results (default 5)",
|
|
16
|
-
},
|
|
12
|
+
query: { type: 'string', description: 'Search query' },
|
|
13
|
+
numResults: { type: 'number', description: 'Number of results (default 5)' },
|
|
17
14
|
},
|
|
18
|
-
required: [
|
|
15
|
+
required: ['query'],
|
|
19
16
|
},
|
|
20
17
|
handler: async (ctx, args) => {
|
|
21
|
-
const query = String(args.query ||
|
|
18
|
+
const query = String(args.query || '');
|
|
22
19
|
const numResults = Number(args.numResults) || 5;
|
|
23
20
|
// Build search URL
|
|
24
21
|
const url = `https://html.duckduckgo.com/html/?q=${encodeURIComponent(query)}`;
|
|
@@ -35,44 +32,28 @@ export const webSearchTool = {
|
|
|
35
32
|
};
|
|
36
33
|
}
|
|
37
34
|
try {
|
|
38
|
-
const response = await fetch(url, {
|
|
39
|
-
signal: AbortSignal.timeout(securityConfig?.requestTimeout || 10000),
|
|
40
|
-
});
|
|
35
|
+
const response = await fetch(url, { signal: AbortSignal.timeout(securityConfig?.requestTimeout || 10000) });
|
|
41
36
|
const html = await response.text();
|
|
42
37
|
const results = [];
|
|
43
|
-
const snippetRegex = /<a[^>]+class="result__a"[^>]
|
|
38
|
+
const snippetRegex = /<a[^>]+class="result__a"[^>]*>([\s\S]*?)<\/a>[\s\S]*?<a[^>]+class="result__snippet"[^>]*>([\s\S]*?)<\/a>/gi;
|
|
44
39
|
let match;
|
|
45
40
|
let count = 0;
|
|
46
41
|
while ((match = snippetRegex.exec(html)) !== null && count < numResults) {
|
|
47
|
-
const
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
results.push(`${count + 1}. ${title}\n URL: ${href}\n ${snippet}`);
|
|
42
|
+
const title = match[1].replace(/<[^>]+>/g, '').trim();
|
|
43
|
+
const snippet = match[2].replace(/<[^>]+>/g, '').trim();
|
|
44
|
+
results.push(`${title}: ${snippet}`);
|
|
51
45
|
count++;
|
|
52
46
|
}
|
|
53
47
|
// Log successful network request
|
|
54
48
|
logNetworkRequest(ctx.sessionId, sanitizeUrl(url), true, `Results: ${results.length}`);
|
|
55
49
|
if (results.length === 0) {
|
|
56
|
-
return { success: true, output: t(
|
|
50
|
+
return { success: true, output: t('tool.no_results', { query }) };
|
|
57
51
|
}
|
|
58
|
-
return {
|
|
59
|
-
success: true,
|
|
60
|
-
output: t("tool.search_results", {
|
|
61
|
-
query,
|
|
62
|
-
results: results.join("\n"),
|
|
63
|
-
}),
|
|
64
|
-
display: t("tool.web_search_result", {
|
|
65
|
-
query,
|
|
66
|
-
count: String(results.length),
|
|
67
|
-
}),
|
|
68
|
-
};
|
|
52
|
+
return { success: true, output: t('tool.search_results', { query, results: results.join('\n') }) };
|
|
69
53
|
}
|
|
70
54
|
catch (err) {
|
|
71
55
|
logNetworkRequest(ctx.sessionId, sanitizeUrl(url), false, `Error: ${err.message}`);
|
|
72
|
-
return {
|
|
73
|
-
success: false,
|
|
74
|
-
output: t("error.search_failed", { message: err.message }),
|
|
75
|
-
};
|
|
56
|
+
return { success: false, output: t('error.search_failed', { message: err.message }) };
|
|
76
57
|
}
|
|
77
58
|
},
|
|
78
59
|
};
|
package/dist/tools/write-file.js
CHANGED
|
@@ -47,19 +47,15 @@ export const writeFileTool = {
|
|
|
47
47
|
output: `[SECURITY BLOCKED] Maximum file operations (${maxFileOps}) exceeded`,
|
|
48
48
|
};
|
|
49
49
|
}
|
|
50
|
-
// Check content for dangerous patterns
|
|
51
|
-
// contentScan has its own `enabled` flag, but it must NOT fire while the
|
|
52
|
-
// global security switch is off (false positives on ordinary CLI code).
|
|
50
|
+
// Check content for dangerous patterns
|
|
53
51
|
const content = String(args.content);
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
};
|
|
62
|
-
}
|
|
52
|
+
const scanResult = scanContent(content, path, ctx.config.security?.contentScan);
|
|
53
|
+
if (!scanResult.allowed) {
|
|
54
|
+
logSecurityBlock(ctx.sessionId, "file_write", scanResult.reason || "Content contains dangerous patterns", path);
|
|
55
|
+
return {
|
|
56
|
+
success: false,
|
|
57
|
+
output: `[SECURITY BLOCKED] ${scanResult.reason}`,
|
|
58
|
+
};
|
|
63
59
|
}
|
|
64
60
|
const dir = dirname(resolved);
|
|
65
61
|
if (!existsSync(dir)) {
|
|
@@ -77,7 +73,8 @@ export const writeFileTool = {
|
|
|
77
73
|
// Increment file operations counter
|
|
78
74
|
ctx.fileOperationsCount = currentCount + 1;
|
|
79
75
|
// Log successful file write
|
|
80
|
-
logFileWrite(ctx.sessionId, path, true, `File ${fileExists ?
|
|
76
|
+
logFileWrite(ctx.sessionId, path, true, `File ${fileExists ? 'updated' : 'created'}`);
|
|
77
|
+
ctx.trackCreatedPath?.(path);
|
|
81
78
|
return { success: true, output: t("file.written", { path }), diff };
|
|
82
79
|
},
|
|
83
80
|
};
|
package/dist/ui/diff.js
CHANGED
|
@@ -110,10 +110,10 @@ function formatLine(line, maxNumWidth) {
|
|
|
110
110
|
const num = line.type === "remove" ? line.oldNum : line.newNum;
|
|
111
111
|
const numStr = num !== null ? String(num).padStart(maxNumWidth) : " ".repeat(maxNumWidth);
|
|
112
112
|
if (line.type === "remove") {
|
|
113
|
-
return `${
|
|
113
|
+
return pc.red(`${pc.bgRed(`${numStr} - ${line.content}`)}`);
|
|
114
114
|
}
|
|
115
115
|
else if (line.type === "add") {
|
|
116
|
-
return `${
|
|
116
|
+
return pc.green(`${pc.bgGreen(`${numStr} + ${line.content}`)}`);
|
|
117
117
|
}
|
|
118
118
|
else if (line.content === "...") {
|
|
119
119
|
return pc.dim(` ${" ".repeat(maxNumWidth)}...`);
|
|
@@ -127,13 +127,17 @@ export function generateDiff(oldContent, newContent) {
|
|
|
127
127
|
return "";
|
|
128
128
|
const oldLines = oldContent.split("\n");
|
|
129
129
|
const newLines = newContent.split("\n");
|
|
130
|
+
if (oldLines.length === 1 &&
|
|
131
|
+
oldLines[0] === "" &&
|
|
132
|
+
newLines.length === 1 &&
|
|
133
|
+
newLines[0] === "") {
|
|
134
|
+
return "";
|
|
135
|
+
}
|
|
130
136
|
const diff = buildDiff(oldLines, newLines);
|
|
131
137
|
if (diff.length === 0)
|
|
132
138
|
return "";
|
|
133
|
-
const
|
|
134
|
-
const
|
|
135
|
-
const maxOldNum = oldNums.length > 0 ? Math.max(...oldNums) : 0;
|
|
136
|
-
const maxNewNum = newNums.length > 0 ? Math.max(...newNums) : 0;
|
|
139
|
+
const maxOldNum = Math.max(...diff.filter((l) => l.oldNum !== null).map((l) => l.oldNum));
|
|
140
|
+
const maxNewNum = Math.max(...diff.filter((l) => l.newNum !== null).map((l) => l.newNum));
|
|
137
141
|
const maxNumWidth = Math.max(String(maxOldNum).length, String(maxNewNum).length, 3);
|
|
138
142
|
let lines = diff.map((l) => formatLine(l, maxNumWidth));
|
|
139
143
|
if (lines.length > MAX_DIFF_LINES) {
|
|
@@ -149,7 +153,7 @@ export function generateNewFileDiff(content) {
|
|
|
149
153
|
const diffLines = [];
|
|
150
154
|
for (let i = 0; i < lines.length; i++) {
|
|
151
155
|
const numStr = String(i + 1).padStart(maxNumWidth);
|
|
152
|
-
diffLines.push(`${
|
|
156
|
+
diffLines.push(pc.green(`${pc.bgGreen(`${numStr} + ${lines[i]}`)}`));
|
|
153
157
|
}
|
|
154
158
|
if (diffLines.length > MAX_DIFF_LINES) {
|
|
155
159
|
const truncated = diffLines.slice(0, MAX_DIFF_LINES);
|
|
@@ -164,7 +168,7 @@ export function generateDeleteDiff(content) {
|
|
|
164
168
|
const diffLines = [];
|
|
165
169
|
for (let i = 0; i < lines.length; i++) {
|
|
166
170
|
const numStr = String(i + 1).padStart(maxNumWidth);
|
|
167
|
-
diffLines.push(`${
|
|
171
|
+
diffLines.push(pc.red(`${pc.bgRed(`${numStr} - ${lines[i]}`)}`));
|
|
168
172
|
}
|
|
169
173
|
if (diffLines.length > MAX_DIFF_LINES) {
|
|
170
174
|
const truncated = diffLines.slice(0, MAX_DIFF_LINES);
|
|
@@ -174,5 +178,8 @@ export function generateDeleteDiff(content) {
|
|
|
174
178
|
return diffLines.join("\n");
|
|
175
179
|
}
|
|
176
180
|
export function generateMoveDiff(fromPath, toPath) {
|
|
177
|
-
return [
|
|
181
|
+
return [
|
|
182
|
+
pc.red(` - ${fromPath}`),
|
|
183
|
+
pc.green(` + ${toPath}`),
|
|
184
|
+
].join("\n");
|
|
178
185
|
}
|
package/dist/ui/renderer.js
CHANGED
|
@@ -1,32 +1,9 @@
|
|
|
1
1
|
import { pc } from "./colors";
|
|
2
2
|
import { FormattingStream } from "./md-formatter";
|
|
3
3
|
import { Spinner } from "./spinner";
|
|
4
|
-
import { box
|
|
4
|
+
import { box } from "./box";
|
|
5
5
|
import { getTerminalWidth } from "./table";
|
|
6
6
|
import { t } from "../i18n/index";
|
|
7
|
-
const GUTTER = " ";
|
|
8
|
-
/** opencode-like leading marker per tool category. */
|
|
9
|
-
export function toolMarker(tool) {
|
|
10
|
-
switch (tool) {
|
|
11
|
-
case "write_file":
|
|
12
|
-
case "edit_file":
|
|
13
|
-
case "create_dir":
|
|
14
|
-
case "move_file":
|
|
15
|
-
case "delete_file":
|
|
16
|
-
return "←";
|
|
17
|
-
case "read_file":
|
|
18
|
-
case "list_dir":
|
|
19
|
-
case "file_info":
|
|
20
|
-
return "→";
|
|
21
|
-
case "glob":
|
|
22
|
-
case "grep":
|
|
23
|
-
return "✱";
|
|
24
|
-
case "bash":
|
|
25
|
-
return "$";
|
|
26
|
-
default:
|
|
27
|
-
return "⚙";
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
7
|
export function isRichTerminal() {
|
|
31
8
|
return Boolean(process.stdout.isTTY) && !process.env.CI;
|
|
32
9
|
}
|
|
@@ -61,14 +38,12 @@ export class Renderer {
|
|
|
61
38
|
out;
|
|
62
39
|
err;
|
|
63
40
|
width;
|
|
64
|
-
toolStyle;
|
|
65
41
|
card = null;
|
|
66
42
|
constructor(opts = {}) {
|
|
67
43
|
this.rich = opts.rich ?? isRichTerminal();
|
|
68
44
|
this.out = opts.out ?? process.stdout;
|
|
69
45
|
this.err = opts.err ?? process.stderr;
|
|
70
46
|
this.width = opts.width ?? getTerminalWidth();
|
|
71
|
-
this.toolStyle = opts.toolStyle ?? "inline";
|
|
72
47
|
this.spinner = new Spinner({
|
|
73
48
|
enabled: this.rich && (opts.spinner ?? true),
|
|
74
49
|
stream: this.err,
|
|
@@ -86,25 +61,12 @@ export class Renderer {
|
|
|
86
61
|
meta(chunk) {
|
|
87
62
|
this.spinner.stop();
|
|
88
63
|
if (this.card) {
|
|
89
|
-
|
|
90
|
-
this.writeInlineBody(chunk);
|
|
91
|
-
}
|
|
92
|
-
else {
|
|
93
|
-
this.card.body.push(chunk);
|
|
94
|
-
}
|
|
64
|
+
this.card.body.push(chunk);
|
|
95
65
|
}
|
|
96
66
|
else {
|
|
97
67
|
this.out.write(chunk);
|
|
98
68
|
}
|
|
99
69
|
}
|
|
100
|
-
/** Stream tool body lines with the gutter prefix, skipping blank lines. */
|
|
101
|
-
writeInlineBody(chunk) {
|
|
102
|
-
for (const line of chunk.split("\n")) {
|
|
103
|
-
if (line.trim() === "")
|
|
104
|
-
continue;
|
|
105
|
-
this.out.write(`${GUTTER}${line}\n`);
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
70
|
/** Dimmed reasoning stream (already colored by the agent). */
|
|
109
71
|
reasoning(chunk) {
|
|
110
72
|
this.spinner.stop();
|
|
@@ -127,41 +89,15 @@ export class Renderer {
|
|
|
127
89
|
return;
|
|
128
90
|
}
|
|
129
91
|
this.card = { tool, args, body: [], start: Date.now() };
|
|
130
|
-
if (this.toolStyle === "inline") {
|
|
131
|
-
const marker = toolMarker(tool);
|
|
132
|
-
this.out.write(`\n${pc.dim(marker)} ${friendlyTool(tool)}${summary ? ` ${pc.dim(summary)}` : ""}\n`);
|
|
133
|
-
return;
|
|
134
|
-
}
|
|
135
92
|
this.spinner.start(`${pc.dim("⚙")} ${friendlyTool(tool)}${summary ? ` ${pc.dim(summary)}` : ""}`);
|
|
136
93
|
}
|
|
137
|
-
toolEnd(_tool, duration, error
|
|
94
|
+
toolEnd(_tool, duration, error) {
|
|
138
95
|
this.spinner.stop();
|
|
139
|
-
if (!this.rich)
|
|
140
|
-
if (ctxDelta !== undefined && ctxDelta !== 0) {
|
|
141
|
-
const deltaStr = ctxDelta > 0
|
|
142
|
-
? pc.green(`+${ctxDelta}`)
|
|
143
|
-
: pc.yellow(`${ctxDelta} ↓`);
|
|
144
|
-
this.out.write(`${pc.dim("ctx")} ${deltaStr}\n`);
|
|
145
|
-
}
|
|
96
|
+
if (!this.rich)
|
|
146
97
|
return;
|
|
147
|
-
}
|
|
148
98
|
if (!this.card)
|
|
149
99
|
return;
|
|
150
100
|
const { tool, args, body } = this.card;
|
|
151
|
-
const marker = error ? pc.red("✗") : pc.green("✓");
|
|
152
|
-
let footer = `${marker} ${pc.dim(`${duration}ms`)}`;
|
|
153
|
-
if (ctxDelta !== undefined && ctxDelta !== 0) {
|
|
154
|
-
const deltaStr = ctxDelta > 0
|
|
155
|
-
? pc.green(`+${ctxDelta}`)
|
|
156
|
-
: pc.yellow(`${ctxDelta} ↓`);
|
|
157
|
-
footer += ` ${pc.dim("ctx")} ${deltaStr}`;
|
|
158
|
-
}
|
|
159
|
-
if (this.toolStyle === "inline") {
|
|
160
|
-
this.out.write(`${GUTTER}${footer}\n`);
|
|
161
|
-
this.out.write(`${divider(this.width)}\n`);
|
|
162
|
-
this.card = null;
|
|
163
|
-
return;
|
|
164
|
-
}
|
|
165
101
|
const lines = [];
|
|
166
102
|
const summary = summarizeArgs(args);
|
|
167
103
|
if (summary)
|
|
@@ -172,7 +108,8 @@ export class Renderer {
|
|
|
172
108
|
lines.push(line);
|
|
173
109
|
}
|
|
174
110
|
}
|
|
175
|
-
|
|
111
|
+
const marker = error ? pc.red("✗") : pc.green("✓");
|
|
112
|
+
lines.push(`${marker} ${pc.dim(`${duration}ms`)}`);
|
|
176
113
|
const title = `${marker} ${friendlyTool(tool)}`;
|
|
177
114
|
for (const line of box(lines, { title, width: this.width })) {
|
|
178
115
|
this.out.write(`${line}\n`);
|