micro-models-agent 0.41.0 → 0.41.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/mma.mjs +41 -41
- package/dist/cli/commands.js +9 -19
- package/dist/cli/completer.js +36 -37
- package/dist/cli/index.js +2 -2
- package/dist/cli/main.js +48 -23
- package/dist/cli/plugin-commands.js +36 -0
- package/dist/cli/repl-commands.js +40 -12
- package/dist/cli/repl.js +217 -87
- package/dist/cli/run-result.js +22 -0
- package/dist/cli/security-commands.js +5 -7
- package/dist/cli/setup.js +8 -26
- package/dist/config/config.js +52 -5
- package/dist/config/defaults.js +29 -5
- package/dist/config/experts.js +1 -1
- package/dist/config/index.js +3 -3
- package/dist/config/security.js +3 -10
- package/dist/core/agent-moe.js +2 -10
- package/dist/core/agent.js +273 -82
- package/dist/core/bootstrap.js +80 -13
- package/dist/core/index.js +2 -2
- package/dist/core/prompt-builder.js +23 -2
- package/dist/core/session-logger.js +46 -4
- package/dist/core/version.js +24 -0
- package/dist/i18n/en.json +75 -2
- package/dist/i18n/ru.json +74 -1
- package/dist/index.js +1 -1
- package/dist/llm/image-utils.js +4 -5
- package/dist/llm/index.js +4 -4
- package/dist/llm/model-loader.js +6 -6
- package/dist/llm/openai-compat.js +40 -34
- package/dist/llm/orchestrator.js +33 -29
- package/dist/llm/response.js +9 -9
- package/dist/logger/app-logger.js +1 -1
- package/dist/logger/index.js +1 -1
- package/dist/main.js +1111 -954
- package/dist/migration/backup.js +13 -13
- package/dist/migration/detect.js +11 -11
- package/dist/migration/index.js +2 -2
- package/dist/modules/artifacts/store.js +61 -0
- package/dist/modules/browser/actions.js +34 -4
- package/dist/modules/browser/bridge-client.js +199 -0
- package/dist/modules/browser/bridge-path.js +10 -0
- package/dist/modules/browser/bridge-server.mjs +202 -202
- package/dist/modules/browser/cookie-store.js +6 -6
- package/dist/modules/browser/driver.js +136 -0
- package/dist/modules/browser/index.js +7 -5
- package/dist/modules/browser/module.js +8 -7
- package/dist/modules/browser/session.js +87 -84
- package/dist/modules/browser/snapshot.js +92 -58
- package/dist/modules/browser/types.js +4 -1
- package/dist/modules/certification/cli.js +2 -4
- package/dist/modules/certification/fact-checker.js +1 -3
- package/dist/modules/certification/loader.js +3 -9
- package/dist/modules/certification/runner.js +1 -4
- package/dist/modules/context/chunk-query.js +100 -0
- package/dist/modules/context/fact-extractor.js +162 -0
- package/dist/modules/context/history.js +15 -0
- package/dist/modules/context/index.js +1 -1
- package/dist/modules/context/manager.js +160 -86
- package/dist/modules/execution/audit-runners.js +152 -0
- package/dist/modules/execution/auditor.js +177 -25
- package/dist/modules/execution/execution-plugin.js +272 -0
- package/dist/modules/execution/module.js +201 -544
- package/dist/modules/execution/moe-executor.js +25 -0
- package/dist/modules/execution/plan-store.js +1 -3
- package/dist/modules/execution/plan-tool.js +508 -0
- package/dist/modules/execution/plan-validator.js +10 -10
- package/dist/modules/execution/planner.js +6 -1
- package/dist/modules/execution/stuck-detector.js +173 -10
- package/dist/modules/execution/verifier.js +86 -42
- package/dist/modules/execution/windows-commands.js +41 -0
- package/dist/modules/hallucination/confidence.js +8 -1
- package/dist/modules/hallucination/detector.js +2 -5
- package/dist/modules/hallucination/factual.js +3 -64
- package/dist/modules/hallucination/index.js +1 -1
- package/dist/modules/hallucination/js-identifiers.js +190 -0
- package/dist/modules/hallucination/llm-judge.js +1 -3
- package/dist/modules/indexer/cache.js +9 -7
- package/dist/modules/indexer/index.js +3 -3
- package/dist/modules/indexer/module.js +95 -42
- package/dist/modules/indexer/project-profile.js +183 -0
- package/dist/modules/indexer/walker.js +17 -17
- package/dist/modules/lsp/check-tool.js +58 -0
- package/dist/modules/lsp/client.js +74 -31
- package/dist/modules/lsp/command.js +60 -0
- package/dist/modules/lsp/config.js +87 -33
- package/dist/modules/lsp/index.js +3 -3
- package/dist/modules/lsp/module.js +185 -21
- package/dist/modules/lsp/probe.js +76 -0
- package/dist/modules/lsp/project-root.js +32 -0
- package/dist/modules/lsp/startup-check.js +141 -0
- package/dist/modules/mcp/module.js +2 -6
- package/dist/modules/memory/index.js +1 -1
- package/dist/modules/memory/module.js +71 -23
- package/dist/modules/memory/search.js +11 -9
- package/dist/modules/memory/store.js +13 -13
- package/dist/modules/pipelines/engine.js +10 -10
- package/dist/modules/pipelines/index.js +3 -3
- package/dist/modules/pipelines/parser.js +17 -14
- package/dist/modules/pipelines/template.js +1 -1
- package/dist/modules/plugins/builtin/lint-on-write.js +21 -16
- package/dist/modules/plugins/builtin/notify.js +3 -2
- package/dist/modules/plugins/index.js +1 -1
- package/dist/modules/plugins/loader.js +59 -17
- package/dist/modules/plugins/manager.js +73 -17
- package/dist/modules/processes/detect.js +34 -0
- package/dist/modules/processes/index.js +1 -1
- package/dist/modules/processes/registry.js +135 -46
- package/dist/modules/registry.js +4 -2
- package/dist/modules/security/audit-notifier.js +39 -39
- package/dist/modules/security/command-validator.js +2 -8
- package/dist/modules/security/data-sanitizer.js +1 -9
- package/dist/modules/security/encryption.js +58 -56
- package/dist/modules/security/network-validator.js +1 -9
- package/dist/modules/security/path-validator.js +1 -3
- package/dist/modules/security/security-policies.js +3 -19
- package/dist/modules/security/session-encryption.js +1 -1
- package/dist/modules/security/session-isolation.js +8 -8
- package/dist/modules/session/index.js +3 -3
- package/dist/modules/session/module.js +5 -5
- package/dist/modules/session/store.js +3 -9
- package/dist/modules/skills/matcher.js +27 -0
- package/dist/modules/skills/module.js +1 -2
- package/dist/modules/updater/checker.js +70 -6
- package/dist/modules/updater/index.js +2 -1
- package/dist/modules/updater/module.js +116 -0
- package/dist/modules/user-profile/compressor.js +2 -2
- package/dist/modules/user-profile/index.js +1 -1
- package/dist/modules/user-profile/profile.js +9 -9
- package/dist/tools/attach-image.js +1 -1
- package/dist/tools/bash.js +178 -19
- package/dist/tools/browser.js +46 -29
- package/dist/tools/chunk-query.js +99 -0
- package/dist/tools/download-file.js +116 -0
- package/dist/tools/enable-tools.js +58 -0
- package/dist/tools/executor.js +4 -5
- package/dist/tools/file-info.js +13 -12
- package/dist/tools/filter-tools.js +9 -2
- package/dist/tools/glob-tool.js +11 -11
- package/dist/tools/grep-tool.js +1 -3
- package/dist/tools/hidden-tools-block.js +37 -0
- package/dist/tools/index.js +13 -2
- package/dist/tools/list-dir.js +18 -17
- package/dist/tools/load-skill.js +1 -3
- package/dist/tools/path-utils.js +4 -4
- package/dist/tools/pipeline-run.js +25 -25
- package/dist/tools/process-kill.js +11 -11
- package/dist/tools/process-list.js +20 -22
- package/dist/tools/process-log.js +22 -18
- package/dist/tools/question.js +1 -3
- package/dist/tools/read-file.js +10 -2
- package/dist/tools/recall.js +44 -37
- package/dist/tools/registry.js +15 -4
- package/dist/tools/remember.js +29 -29
- package/dist/tools/scope-check.js +9 -9
- package/dist/tools/subagent.js +54 -9
- package/dist/tools/user-input.js +1 -1
- package/dist/tools/web-browse.js +3 -3
- package/dist/tools/web-fetch.js +3 -3
- package/dist/tools/web-search.js +3 -3
- package/dist/tools/write-file.js +1 -3
- package/dist/ui/box.js +1 -5
- package/dist/ui/index.js +6 -6
- package/dist/ui/line-editor.js +703 -0
- package/dist/ui/line-math.js +69 -0
- package/dist/ui/md-formatter.js +33 -33
- package/dist/ui/output.js +5 -5
- package/dist/ui/plan-view.js +103 -0
- package/dist/ui/renderer.js +15 -10
- package/dist/ui/table.js +1 -1
- package/package.json +48 -48
|
@@ -1,14 +1,44 @@
|
|
|
1
|
+
import { semverGt } from "../updater/checker";
|
|
1
2
|
export class PluginManager {
|
|
2
3
|
plugins = [];
|
|
3
|
-
|
|
4
|
+
/**
|
|
5
|
+
* Register a plugin. Plugins with the same `name` are deduplicated — the
|
|
6
|
+
* higher `version` wins (unversioned plugins count as "0.0.0"). The loser is
|
|
7
|
+
* discarded; `skipped_older` lets the caller warn about the dropped copy.
|
|
8
|
+
*/
|
|
9
|
+
register(plugin, source) {
|
|
10
|
+
if (source)
|
|
11
|
+
plugin.source = source;
|
|
12
|
+
const existing = this.plugins.find((p) => p.name === plugin.name);
|
|
13
|
+
if (existing) {
|
|
14
|
+
if (semverGt(plugin.version || "0.0.0", existing.version || "0.0.0")) {
|
|
15
|
+
this.plugins = this.plugins.filter((p) => p.name !== plugin.name);
|
|
16
|
+
this.plugins.push(plugin);
|
|
17
|
+
this.sort();
|
|
18
|
+
return { status: "registered" };
|
|
19
|
+
}
|
|
20
|
+
return { status: "skipped_older", existing };
|
|
21
|
+
}
|
|
4
22
|
this.plugins.push(plugin);
|
|
23
|
+
this.sort();
|
|
24
|
+
return { status: "registered" };
|
|
25
|
+
}
|
|
26
|
+
sort() {
|
|
5
27
|
this.plugins.sort((a, b) => (b.priority || 0) - (a.priority || 0));
|
|
6
28
|
}
|
|
7
29
|
getAllPlugins() {
|
|
8
30
|
return [...this.plugins];
|
|
9
31
|
}
|
|
32
|
+
/** Detailed plugin list (name, version, source) for REPL / CLI display. */
|
|
33
|
+
getPluginInfos() {
|
|
34
|
+
return [...this.plugins].map((plugin) => ({ plugin, source: plugin.source }));
|
|
35
|
+
}
|
|
36
|
+
/** Find a registered plugin by name, or null. */
|
|
37
|
+
getPlugin(name) {
|
|
38
|
+
return this.plugins.find((p) => p.name === name) ?? null;
|
|
39
|
+
}
|
|
10
40
|
runOnBuildPrompt() {
|
|
11
|
-
return this.plugins.map(p => {
|
|
41
|
+
return this.plugins.map((p) => {
|
|
12
42
|
try {
|
|
13
43
|
return p.onBuildPrompt?.() ?? null;
|
|
14
44
|
}
|
|
@@ -25,7 +55,9 @@ export class PluginManager {
|
|
|
25
55
|
if (result === false)
|
|
26
56
|
return false;
|
|
27
57
|
}
|
|
28
|
-
catch {
|
|
58
|
+
catch {
|
|
59
|
+
/* error isolation */
|
|
60
|
+
}
|
|
29
61
|
}
|
|
30
62
|
}
|
|
31
63
|
return true;
|
|
@@ -36,7 +68,9 @@ export class PluginManager {
|
|
|
36
68
|
try {
|
|
37
69
|
await plugin.onAfterTool(ctx, call, result);
|
|
38
70
|
}
|
|
39
|
-
catch {
|
|
71
|
+
catch {
|
|
72
|
+
/* error isolation */
|
|
73
|
+
}
|
|
40
74
|
}
|
|
41
75
|
}
|
|
42
76
|
}
|
|
@@ -46,7 +80,9 @@ export class PluginManager {
|
|
|
46
80
|
try {
|
|
47
81
|
await plugin.onError(ctx, error);
|
|
48
82
|
}
|
|
49
|
-
catch {
|
|
83
|
+
catch {
|
|
84
|
+
/* error isolation */
|
|
85
|
+
}
|
|
50
86
|
}
|
|
51
87
|
}
|
|
52
88
|
}
|
|
@@ -56,7 +92,9 @@ export class PluginManager {
|
|
|
56
92
|
try {
|
|
57
93
|
plugin.onSessionStart(ctx);
|
|
58
94
|
}
|
|
59
|
-
catch {
|
|
95
|
+
catch {
|
|
96
|
+
/* error isolation */
|
|
97
|
+
}
|
|
60
98
|
}
|
|
61
99
|
}
|
|
62
100
|
}
|
|
@@ -66,7 +104,9 @@ export class PluginManager {
|
|
|
66
104
|
try {
|
|
67
105
|
plugin.onBeforeThink(ctx);
|
|
68
106
|
}
|
|
69
|
-
catch {
|
|
107
|
+
catch {
|
|
108
|
+
/* error isolation */
|
|
109
|
+
}
|
|
70
110
|
}
|
|
71
111
|
}
|
|
72
112
|
}
|
|
@@ -76,7 +116,9 @@ export class PluginManager {
|
|
|
76
116
|
try {
|
|
77
117
|
plugin.onAfterThink(ctx, response);
|
|
78
118
|
}
|
|
79
|
-
catch {
|
|
119
|
+
catch {
|
|
120
|
+
/* error isolation */
|
|
121
|
+
}
|
|
80
122
|
}
|
|
81
123
|
}
|
|
82
124
|
}
|
|
@@ -86,7 +128,9 @@ export class PluginManager {
|
|
|
86
128
|
try {
|
|
87
129
|
plugin.onSessionEnd(ctx);
|
|
88
130
|
}
|
|
89
|
-
catch {
|
|
131
|
+
catch {
|
|
132
|
+
/* error isolation */
|
|
133
|
+
}
|
|
90
134
|
}
|
|
91
135
|
}
|
|
92
136
|
}
|
|
@@ -96,7 +140,9 @@ export class PluginManager {
|
|
|
96
140
|
try {
|
|
97
141
|
plugin.onToolCall(ctx);
|
|
98
142
|
}
|
|
99
|
-
catch {
|
|
143
|
+
catch {
|
|
144
|
+
/* error isolation */
|
|
145
|
+
}
|
|
100
146
|
}
|
|
101
147
|
}
|
|
102
148
|
}
|
|
@@ -106,7 +152,9 @@ export class PluginManager {
|
|
|
106
152
|
try {
|
|
107
153
|
plugin.onPhase(ctx, phase);
|
|
108
154
|
}
|
|
109
|
-
catch {
|
|
155
|
+
catch {
|
|
156
|
+
/* error isolation */
|
|
157
|
+
}
|
|
110
158
|
}
|
|
111
159
|
}
|
|
112
160
|
}
|
|
@@ -116,7 +164,9 @@ export class PluginManager {
|
|
|
116
164
|
try {
|
|
117
165
|
plugin.onToolStart(ctx, call);
|
|
118
166
|
}
|
|
119
|
-
catch {
|
|
167
|
+
catch {
|
|
168
|
+
/* error isolation */
|
|
169
|
+
}
|
|
120
170
|
}
|
|
121
171
|
}
|
|
122
172
|
}
|
|
@@ -126,7 +176,9 @@ export class PluginManager {
|
|
|
126
176
|
try {
|
|
127
177
|
plugin.onToolEnd(ctx, call, result, durationMs);
|
|
128
178
|
}
|
|
129
|
-
catch {
|
|
179
|
+
catch {
|
|
180
|
+
/* error isolation */
|
|
181
|
+
}
|
|
130
182
|
}
|
|
131
183
|
}
|
|
132
184
|
}
|
|
@@ -136,10 +188,12 @@ export class PluginManager {
|
|
|
136
188
|
if (plugin.onText) {
|
|
137
189
|
try {
|
|
138
190
|
const out = plugin.onText(ctx, chunk);
|
|
139
|
-
if (typeof out ===
|
|
191
|
+
if (typeof out === "string")
|
|
140
192
|
chunk = out;
|
|
141
193
|
}
|
|
142
|
-
catch {
|
|
194
|
+
catch {
|
|
195
|
+
/* error isolation */
|
|
196
|
+
}
|
|
143
197
|
}
|
|
144
198
|
}
|
|
145
199
|
return chunk;
|
|
@@ -150,10 +204,12 @@ export class PluginManager {
|
|
|
150
204
|
if (plugin.onMeta) {
|
|
151
205
|
try {
|
|
152
206
|
const out = plugin.onMeta(ctx, chunk);
|
|
153
|
-
if (typeof out ===
|
|
207
|
+
if (typeof out === "string")
|
|
154
208
|
chunk = out;
|
|
155
209
|
}
|
|
156
|
-
catch {
|
|
210
|
+
catch {
|
|
211
|
+
/* error isolation */
|
|
212
|
+
}
|
|
157
213
|
}
|
|
158
214
|
}
|
|
159
215
|
return chunk;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Heuristic detection of long-running (server/watch) commands.
|
|
3
|
+
*
|
|
4
|
+
* The explicit `background: true` bash parameter always wins; this list is a
|
|
5
|
+
* convenience so dev servers don't block the agent. It only inspects the raw
|
|
6
|
+
* shell command (a technical process-type decision, not task classification).
|
|
7
|
+
*/
|
|
8
|
+
const LONG_RUNNING_PATTERNS = [
|
|
9
|
+
/\b(npm|pnpm|yarn|bun|npx)\s+(run\s+)?(dev|start|serve|preview|watch|server)\b/i,
|
|
10
|
+
/\b(deno|node)\s+.*\b(run\s+)?(dev|serve|watch|server)\b/i,
|
|
11
|
+
/(^|\s)--watch\b/i,
|
|
12
|
+
/(^|\s)-w\b/i,
|
|
13
|
+
/\bnodemon\b/i,
|
|
14
|
+
/\btsx watch\b/i,
|
|
15
|
+
/\b(ts-node|tsc)\s+.*--watch\b/i,
|
|
16
|
+
/\bvite(?!\s+(build|create))\b/i,
|
|
17
|
+
/\bwebpack(-dev-server|\s+serve)\b/i,
|
|
18
|
+
/\bastro dev\b/i,
|
|
19
|
+
/\bnext (dev|start)\b/i,
|
|
20
|
+
/\bnuxt (dev|start)\b/i,
|
|
21
|
+
/\bsvelte-kit (dev|preview)\b/i,
|
|
22
|
+
/\bgatsby develop\b/i,
|
|
23
|
+
/\bdocker(-compose)?\s+.*\bup\b/i,
|
|
24
|
+
/\buvicorn\b|\bgunicorn\b/i,
|
|
25
|
+
/\bpython\s+-m\s+http\.server\b/i,
|
|
26
|
+
/\bflask run\b/i,
|
|
27
|
+
/\bphp artisan serve\b/i,
|
|
28
|
+
/\brails (server|s)\b/i,
|
|
29
|
+
/\bvitest watch\b/i,
|
|
30
|
+
/\bjest --watch\b/i,
|
|
31
|
+
];
|
|
32
|
+
export function isLongRunningCommand(command) {
|
|
33
|
+
return LONG_RUNNING_PATTERNS.some((pattern) => pattern.test(command));
|
|
34
|
+
}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { registerKillable, unregisterKillable, killByCallId
|
|
1
|
+
export { registerKillable, unregisterKillable, killByCallId } from "./runner";
|
|
2
2
|
export { processRegistry } from "./registry";
|
|
@@ -3,21 +3,107 @@ import { platform } from "os";
|
|
|
3
3
|
const MAX_LOG_LINES = 2000;
|
|
4
4
|
const MAX_KEPT_PROCESSES = 20;
|
|
5
5
|
let seq = 0;
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
6
|
+
/** OEM decoder (Russian CP866 / active code page) — cached after first use. */
|
|
7
|
+
let oemDecoder;
|
|
8
|
+
function getOemDecoder() {
|
|
9
|
+
if (oemDecoder !== undefined)
|
|
10
|
+
return oemDecoder;
|
|
11
|
+
oemDecoder = null;
|
|
12
|
+
try {
|
|
13
|
+
const cpOut = spawnSync("chcp.com", {
|
|
14
|
+
encoding: "buffer",
|
|
15
|
+
timeout: 2000,
|
|
16
|
+
windowsHide: true,
|
|
17
|
+
}).stdout;
|
|
18
|
+
const m = cpOut ? cpOut.toString("latin1").match(/(\d+)/) : null;
|
|
19
|
+
if (m) {
|
|
20
|
+
try {
|
|
21
|
+
oemDecoder = new TextDecoder("ibm" + m[1]);
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
oemDecoder = null;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
/* ignore */
|
|
30
|
+
}
|
|
31
|
+
if (!oemDecoder) {
|
|
11
32
|
try {
|
|
12
|
-
|
|
13
|
-
windowsHide: true,
|
|
14
|
-
stdio: "ignore",
|
|
15
|
-
timeout: 3000,
|
|
16
|
-
});
|
|
33
|
+
oemDecoder = new TextDecoder("ibm866");
|
|
17
34
|
}
|
|
18
35
|
catch {
|
|
19
|
-
|
|
36
|
+
oemDecoder = null;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return oemDecoder;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Decode one complete line of output to UTF-8.
|
|
43
|
+
*
|
|
44
|
+
* Modern tools (Bun, Node, npm, git) write UTF-8 into the pipe regardless of
|
|
45
|
+
* the console code page, while built-in cmd.exe commands (echo, type, dir)
|
|
46
|
+
* emit the OEM code page (CP866 on Russian Windows). Try strict UTF-8 first;
|
|
47
|
+
* fall back to the OEM decoder when the bytes are not valid UTF-8.
|
|
48
|
+
*/
|
|
49
|
+
function decodeLineToUtf8(buf) {
|
|
50
|
+
try {
|
|
51
|
+
return new TextDecoder("utf-8", { fatal: true }).decode(buf);
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
/* not valid UTF-8 → OEM code page output */
|
|
55
|
+
}
|
|
56
|
+
const oem = getOemDecoder();
|
|
57
|
+
if (oem) {
|
|
58
|
+
try {
|
|
59
|
+
return oem.decode(buf);
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
/* unknown encoding — latin1 fallback */
|
|
20
63
|
}
|
|
64
|
+
}
|
|
65
|
+
return buf.toString("latin1");
|
|
66
|
+
}
|
|
67
|
+
/** Synchronous pause without blocking the event loop (via Atomics.wait). */
|
|
68
|
+
function sleepSync(ms) {
|
|
69
|
+
try {
|
|
70
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
/* ignore */
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
function taskkillPid(pid) {
|
|
77
|
+
try {
|
|
78
|
+
spawnSync("taskkill", ["/pid", String(pid), "/T", "/F"], {
|
|
79
|
+
windowsHide: true,
|
|
80
|
+
stdio: "ignore",
|
|
81
|
+
timeout: 5000,
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
/* already dead */
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Kill a spawned tree on Windows.
|
|
90
|
+
*
|
|
91
|
+
* `taskkill /T` has a race: when cmd.exe is killed, a child (node) that is
|
|
92
|
+
* still starting up can survive as an orphan because it was never attached to
|
|
93
|
+
* the tree at kill time. A single pass misses it. Running taskkill twice with
|
|
94
|
+
* a settle gap catches those late starters on the second pass — the second
|
|
95
|
+
* tree walk sees the child that was still being created during the first one.
|
|
96
|
+
* This is much cheaper than enumerating processes (wmic/PowerShell), which
|
|
97
|
+
* slows every kill down to seconds under parallel load.
|
|
98
|
+
*/
|
|
99
|
+
export function killTree(child) {
|
|
100
|
+
const pid = child.pid;
|
|
101
|
+
if (!pid)
|
|
102
|
+
return;
|
|
103
|
+
if (platform() === "win32") {
|
|
104
|
+
taskkillPid(pid);
|
|
105
|
+
sleepSync(150);
|
|
106
|
+
taskkillPid(pid);
|
|
21
107
|
return;
|
|
22
108
|
}
|
|
23
109
|
try {
|
|
@@ -73,31 +159,10 @@ class ProcessRegistry {
|
|
|
73
159
|
}
|
|
74
160
|
this.children.set(id, child);
|
|
75
161
|
entry.pid = child.pid ?? 0;
|
|
76
|
-
// On Windows, children spawned via cmd.exe output in the OEM code page,
|
|
77
|
-
// NOT UTF-8. Detect the active code page via chcp.com; fall back to
|
|
78
|
-
// ibm866 (Russian CP866) when detection fails — still correct for the
|
|
79
|
-
// majority of users, and always better than the UTF-8 default.
|
|
80
|
-
const decoder = platform() === "win32"
|
|
81
|
-
? (() => {
|
|
82
|
-
try {
|
|
83
|
-
const cpOut = require("child_process")
|
|
84
|
-
.execSync("chcp.com", {
|
|
85
|
-
encoding: "buffer",
|
|
86
|
-
timeout: 2000,
|
|
87
|
-
windowsHide: true,
|
|
88
|
-
})
|
|
89
|
-
.toString("latin1");
|
|
90
|
-
const m = cpOut.match(/(\d+)/);
|
|
91
|
-
if (m)
|
|
92
|
-
return new TextDecoder("ibm" + m[1]);
|
|
93
|
-
}
|
|
94
|
-
catch {
|
|
95
|
-
/* ignore */
|
|
96
|
-
}
|
|
97
|
-
return new TextDecoder("ibm866");
|
|
98
|
-
})()
|
|
99
|
-
: new TextDecoder("utf-8");
|
|
100
162
|
let buf = "";
|
|
163
|
+
let byteBuf = Buffer.alloc(0);
|
|
164
|
+
const isWin = platform() === "win32";
|
|
165
|
+
const unixDecoder = new TextDecoder("utf-8");
|
|
101
166
|
const pushLine = (line) => {
|
|
102
167
|
if (entry.log.length >= MAX_LOG_LINES) {
|
|
103
168
|
entry.log.shift();
|
|
@@ -105,19 +170,40 @@ class ProcessRegistry {
|
|
|
105
170
|
entry.log.push(line);
|
|
106
171
|
};
|
|
107
172
|
const append = (chunk) => {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
173
|
+
if (!isWin) {
|
|
174
|
+
const decoded = unixDecoder.decode(chunk, { stream: true });
|
|
175
|
+
buf += decoded;
|
|
176
|
+
const lines = buf.split(/\r?\n/);
|
|
177
|
+
buf = lines.pop() ?? "";
|
|
178
|
+
for (const line of lines) {
|
|
179
|
+
pushLine(line);
|
|
180
|
+
}
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
// Windows: buffer raw bytes and decode per line. Modern tools (Bun, Node,
|
|
184
|
+
// npm, git) write UTF-8 into the pipe regardless of the console code
|
|
185
|
+
// page; cmd.exe builtins (echo, type, dir) emit the OEM code page. Pick
|
|
186
|
+
// the right decoding per line so Russian text survives either way.
|
|
187
|
+
byteBuf = byteBuf.length ? Buffer.concat([byteBuf, chunk]) : Buffer.from(chunk);
|
|
188
|
+
let nl;
|
|
189
|
+
while ((nl = byteBuf.indexOf(0x0a)) !== -1) {
|
|
190
|
+
const raw = byteBuf.subarray(0, nl);
|
|
191
|
+
byteBuf = byteBuf.subarray(nl + 1);
|
|
192
|
+
pushLine(decodeLineToUtf8(raw).replace(/\r$/, ""));
|
|
114
193
|
}
|
|
115
194
|
};
|
|
116
195
|
const flush = () => {
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
196
|
+
if (!isWin) {
|
|
197
|
+
buf += unixDecoder.decode();
|
|
198
|
+
if (buf) {
|
|
199
|
+
pushLine(buf);
|
|
200
|
+
buf = "";
|
|
201
|
+
}
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
if (byteBuf.length) {
|
|
205
|
+
pushLine(decodeLineToUtf8(byteBuf));
|
|
206
|
+
byteBuf = Buffer.alloc(0);
|
|
121
207
|
}
|
|
122
208
|
};
|
|
123
209
|
const resolveExit = () => {
|
|
@@ -130,7 +216,10 @@ class ProcessRegistry {
|
|
|
130
216
|
entry.status = "killed";
|
|
131
217
|
}
|
|
132
218
|
entry.spawnError = err.message;
|
|
133
|
-
append
|
|
219
|
+
// Write directly to the log, not through `append`: a failed spawn often
|
|
220
|
+
// never emits `close`, so the buffered line would never be flushed and
|
|
221
|
+
// the log would be empty (observed: "records a spawn error" test).
|
|
222
|
+
pushLine(`[process error] ${err.message}`);
|
|
134
223
|
resolveExit();
|
|
135
224
|
});
|
|
136
225
|
child.on("close", (code) => {
|
package/dist/modules/registry.js
CHANGED
|
@@ -9,9 +9,11 @@ export class ModuleRegistry {
|
|
|
9
9
|
listModules() {
|
|
10
10
|
return Array.from(this.modules.keys()).sort();
|
|
11
11
|
}
|
|
12
|
-
collectPromptBlocks() {
|
|
12
|
+
collectPromptBlocks(exclude = []) {
|
|
13
13
|
const blocks = [];
|
|
14
|
-
for (const mod of this.modules
|
|
14
|
+
for (const [name, mod] of this.modules) {
|
|
15
|
+
if (exclude.includes(name))
|
|
16
|
+
continue;
|
|
15
17
|
if (mod.getSystemPromptBlock) {
|
|
16
18
|
const block = mod.getSystemPromptBlock();
|
|
17
19
|
if (block)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { writeFileSync, appendFileSync, existsSync, mkdirSync } from
|
|
2
|
-
import { join, dirname } from
|
|
3
|
-
import { homedir } from
|
|
1
|
+
import { writeFileSync, appendFileSync, existsSync, mkdirSync } from "fs";
|
|
2
|
+
import { join, dirname } from "path";
|
|
3
|
+
import { homedir } from "os";
|
|
4
4
|
/**
|
|
5
5
|
* Severity weights for comparison
|
|
6
6
|
*/
|
|
@@ -15,16 +15,16 @@ const SEVERITY_WEIGHTS = {
|
|
|
15
15
|
*/
|
|
16
16
|
export const DEFAULT_AUDIT_NOTIFIER_CONFIG = {
|
|
17
17
|
enabled: false,
|
|
18
|
-
filePath: join(homedir(),
|
|
18
|
+
filePath: join(homedir(), ".mma", "logs", "audit-notifications.jsonl"),
|
|
19
19
|
webhookTimeout: 5000,
|
|
20
|
-
minSeverity:
|
|
20
|
+
minSeverity: "medium",
|
|
21
21
|
eventTypes: [
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
22
|
+
"security_block",
|
|
23
|
+
"bash_command",
|
|
24
|
+
"file_operation",
|
|
25
|
+
"network_request",
|
|
26
|
+
"authentication",
|
|
27
|
+
"configuration_change",
|
|
28
28
|
],
|
|
29
29
|
maxRetries: 3,
|
|
30
30
|
};
|
|
@@ -85,7 +85,7 @@ export class AuditNotifier {
|
|
|
85
85
|
* Check if a severity level should be notified
|
|
86
86
|
*/
|
|
87
87
|
shouldNotifySeverity(severity) {
|
|
88
|
-
const minSeverity = this.config.minSeverity ??
|
|
88
|
+
const minSeverity = this.config.minSeverity ?? "medium";
|
|
89
89
|
const minWeight = SEVERITY_WEIGHTS[minSeverity];
|
|
90
90
|
const eventWeight = SEVERITY_WEIGHTS[severity];
|
|
91
91
|
return eventWeight >= minWeight;
|
|
@@ -123,12 +123,12 @@ export class AuditNotifier {
|
|
|
123
123
|
*/
|
|
124
124
|
mapEventType(action) {
|
|
125
125
|
const typeMap = {
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
126
|
+
tool_call: "tool_call",
|
|
127
|
+
bash_command: "bash_command",
|
|
128
|
+
file_write: "file_operation",
|
|
129
|
+
file_delete: "file_operation",
|
|
130
|
+
network_request: "network_request",
|
|
131
|
+
security_block: "security_block",
|
|
132
132
|
};
|
|
133
133
|
return typeMap[action] || null;
|
|
134
134
|
}
|
|
@@ -138,16 +138,16 @@ export class AuditNotifier {
|
|
|
138
138
|
determineSeverity(entry) {
|
|
139
139
|
// Default severity based on action type
|
|
140
140
|
const typeSeverity = {
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
141
|
+
security_block: "high",
|
|
142
|
+
bash_command: "high",
|
|
143
|
+
file_delete: "medium",
|
|
144
|
+
file_write: "medium",
|
|
145
|
+
network_request: "medium",
|
|
146
|
+
tool_call: "low",
|
|
147
|
+
session_start: "low",
|
|
148
|
+
session_end: "low",
|
|
149
149
|
};
|
|
150
|
-
return typeSeverity[entry.action] ||
|
|
150
|
+
return typeSeverity[entry.action] || "low";
|
|
151
151
|
}
|
|
152
152
|
/**
|
|
153
153
|
* Write notification to file
|
|
@@ -157,10 +157,10 @@ export class AuditNotifier {
|
|
|
157
157
|
return;
|
|
158
158
|
try {
|
|
159
159
|
const line = JSON.stringify(notification);
|
|
160
|
-
appendFileSync(this.config.filePath, line +
|
|
160
|
+
appendFileSync(this.config.filePath, line + "\n", "utf8");
|
|
161
161
|
}
|
|
162
162
|
catch (error) {
|
|
163
|
-
console.error(
|
|
163
|
+
console.error("[AuditNotifier] Failed to write to file:", error);
|
|
164
164
|
}
|
|
165
165
|
}
|
|
166
166
|
/**
|
|
@@ -173,8 +173,8 @@ export class AuditNotifier {
|
|
|
173
173
|
const controller = new AbortController();
|
|
174
174
|
const timeoutId = setTimeout(() => controller.abort(), this.config.webhookTimeout);
|
|
175
175
|
const response = await fetch(this.config.webhookUrl, {
|
|
176
|
-
method:
|
|
177
|
-
headers: {
|
|
176
|
+
method: "POST",
|
|
177
|
+
headers: { "Content-Type": "application/json" },
|
|
178
178
|
body: JSON.stringify(notification),
|
|
179
179
|
signal: controller.signal,
|
|
180
180
|
});
|
|
@@ -184,7 +184,7 @@ export class AuditNotifier {
|
|
|
184
184
|
}
|
|
185
185
|
}
|
|
186
186
|
catch (error) {
|
|
187
|
-
console.error(
|
|
187
|
+
console.error("[AuditNotifier] Webhook failed, adding to retry queue:", error);
|
|
188
188
|
this.addToRetryQueue(notification);
|
|
189
189
|
}
|
|
190
190
|
}
|
|
@@ -207,7 +207,7 @@ export class AuditNotifier {
|
|
|
207
207
|
while (this.retryQueue.length > 0) {
|
|
208
208
|
const item = this.retryQueue[0];
|
|
209
209
|
if (item.retries >= (this.config.maxRetries ?? 3)) {
|
|
210
|
-
console.error(
|
|
210
|
+
console.error("[AuditNotifier] Max retries exceeded for notification:", item.entry);
|
|
211
211
|
this.retryQueue.shift();
|
|
212
212
|
continue;
|
|
213
213
|
}
|
|
@@ -219,7 +219,7 @@ export class AuditNotifier {
|
|
|
219
219
|
item.retries++;
|
|
220
220
|
// Exponential backoff
|
|
221
221
|
const delay = Math.pow(2, item.retries) * 1000;
|
|
222
|
-
await new Promise(resolve => setTimeout(resolve, delay));
|
|
222
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
223
223
|
}
|
|
224
224
|
}
|
|
225
225
|
this.isProcessing = false;
|
|
@@ -232,9 +232,9 @@ export class AuditNotifier {
|
|
|
232
232
|
return [];
|
|
233
233
|
}
|
|
234
234
|
try {
|
|
235
|
-
const content = readFileSync(this.config.filePath,
|
|
236
|
-
const lines = content.split(
|
|
237
|
-
return lines.slice(-limit).map(line => JSON.parse(line));
|
|
235
|
+
const content = readFileSync(this.config.filePath, "utf8");
|
|
236
|
+
const lines = content.split("\n").filter(Boolean);
|
|
237
|
+
return lines.slice(-limit).map((line) => JSON.parse(line));
|
|
238
238
|
}
|
|
239
239
|
catch {
|
|
240
240
|
return [];
|
|
@@ -245,7 +245,7 @@ export class AuditNotifier {
|
|
|
245
245
|
*/
|
|
246
246
|
clearNotifications() {
|
|
247
247
|
if (this.config.filePath) {
|
|
248
|
-
writeFileSync(this.config.filePath,
|
|
248
|
+
writeFileSync(this.config.filePath, "", "utf8");
|
|
249
249
|
}
|
|
250
250
|
this.retryQueue = [];
|
|
251
251
|
}
|
|
@@ -289,4 +289,4 @@ export function createAuditNotifier(config) {
|
|
|
289
289
|
return new AuditNotifier(config);
|
|
290
290
|
}
|
|
291
291
|
// Import readFileSync for readNotifications
|
|
292
|
-
import { readFileSync } from
|
|
292
|
+
import { readFileSync } from "fs";
|
|
@@ -46,11 +46,7 @@ function extractBaseCommand(trimmed) {
|
|
|
46
46
|
i++;
|
|
47
47
|
continue;
|
|
48
48
|
}
|
|
49
|
-
if (tok === "sudo" ||
|
|
50
|
-
tok === "env" ||
|
|
51
|
-
tok === "command" ||
|
|
52
|
-
tok === "exec" ||
|
|
53
|
-
tok === "nohup") {
|
|
49
|
+
if (tok === "sudo" || tok === "env" || tok === "command" || tok === "exec" || tok === "nohup") {
|
|
54
50
|
i++;
|
|
55
51
|
continue;
|
|
56
52
|
}
|
|
@@ -150,9 +146,7 @@ export function isCommandAllowed(command, securityConfig) {
|
|
|
150
146
|
};
|
|
151
147
|
}
|
|
152
148
|
// Also check the raw first token in case it's a simple name
|
|
153
|
-
const rawFirst = trimmedCommand
|
|
154
|
-
.split(/\s+/)[0]
|
|
155
|
-
.replace(/\.(exe|cmd|bat)$/i, "");
|
|
149
|
+
const rawFirst = trimmedCommand.split(/\s+/)[0].replace(/\.(exe|cmd|bat)$/i, "");
|
|
156
150
|
if (rawFirst !== baseCommand && config.blacklist.includes(rawFirst)) {
|
|
157
151
|
return {
|
|
158
152
|
allowed: false,
|
|
@@ -73,15 +73,7 @@ export function sanitizeCommand(command) {
|
|
|
73
73
|
export function sanitizeUrl(url) {
|
|
74
74
|
try {
|
|
75
75
|
const urlObj = new URL(url);
|
|
76
|
-
const sensitiveKeys = [
|
|
77
|
-
"api_key",
|
|
78
|
-
"token",
|
|
79
|
-
"password",
|
|
80
|
-
"secret",
|
|
81
|
-
"access_token",
|
|
82
|
-
"auth",
|
|
83
|
-
"key",
|
|
84
|
-
];
|
|
76
|
+
const sensitiveKeys = ["api_key", "token", "password", "secret", "access_token", "auth", "key"];
|
|
85
77
|
const sanitizedParams = new URLSearchParams(urlObj.search);
|
|
86
78
|
for (const key of sensitiveKeys) {
|
|
87
79
|
if (sanitizedParams.has(key)) {
|