micro-models-agent 0.7.10 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/commands.js +173 -0
- package/dist/cli/completer.js +168 -0
- package/dist/cli/index.js +2 -0
- package/dist/cli/main.js +95 -0
- package/dist/cli/repl.js +762 -0
- package/dist/cli/security-commands.js +166 -0
- package/dist/cli/setup.js +214 -0
- package/dist/config/config.js +123 -0
- package/dist/config/defaults.js +91 -0
- package/dist/config/experts.js +15 -0
- package/dist/config/index.js +3 -0
- package/dist/config/security.js +187 -0
- package/dist/config/types.js +1 -0
- package/dist/core/agent.js +626 -0
- package/dist/core/bootstrap.js +307 -0
- package/dist/core/index.js +2 -0
- package/dist/core/prompt-builder.js +55 -0
- package/dist/core/types.js +1 -0
- package/dist/i18n/en.json +405 -0
- package/dist/i18n/index.js +43 -0
- package/dist/i18n/ru.json +405 -0
- package/dist/index.js +22 -0
- package/dist/llm/index.js +4 -0
- package/dist/llm/model-loader.js +78 -0
- package/dist/llm/openai-compat.js +277 -0
- package/dist/llm/orchestrator.js +194 -0
- package/dist/llm/provider.js +2 -0
- package/dist/llm/response.js +39 -0
- package/dist/llm/token-counter.js +37 -0
- package/dist/llm/types.js +1 -0
- package/dist/logger/app-logger.js +76 -0
- package/dist/logger/index.js +1 -0
- package/dist/migration/backup.js +45 -0
- package/dist/migration/detect.js +50 -0
- package/dist/migration/index.js +2 -0
- package/dist/modules/browser/actions.js +46 -0
- package/dist/modules/browser/cookie-store.js +24 -0
- package/dist/modules/browser/index.js +5 -0
- package/dist/modules/browser/module.js +28 -0
- package/dist/modules/browser/session.js +287 -0
- package/dist/modules/browser/snapshot.js +114 -0
- package/dist/modules/browser/types.js +9 -0
- package/dist/modules/context/history.js +15 -0
- package/dist/modules/context/index.js +1 -0
- package/dist/modules/context/manager.js +179 -0
- package/dist/modules/execution/auditor.js +72 -0
- package/dist/modules/execution/index.js +6 -0
- package/dist/modules/execution/module.js +334 -0
- package/dist/modules/execution/moe-executor.js +196 -0
- package/dist/modules/execution/plan-validator.js +153 -0
- package/dist/modules/execution/planner.js +35 -0
- package/dist/modules/execution/stuck-detector.js +113 -0
- package/dist/modules/execution/tracker.js +53 -0
- package/dist/modules/execution/types.js +1 -0
- package/dist/modules/execution/verifier.js +149 -0
- package/dist/modules/hallucination/confidence.js +47 -0
- package/dist/modules/hallucination/consistency.js +32 -0
- package/dist/modules/hallucination/detector.js +41 -0
- package/dist/modules/hallucination/factual.js +128 -0
- package/dist/modules/hallucination/index.js +4 -0
- package/dist/modules/index.js +5 -0
- package/dist/modules/indexer/cache.js +38 -0
- package/dist/modules/indexer/index.js +3 -0
- package/dist/modules/indexer/module.js +192 -0
- package/dist/modules/indexer/walker.js +101 -0
- package/dist/modules/mcp/client.js +393 -0
- package/dist/modules/mcp/index.js +3 -0
- package/dist/modules/mcp/module.js +146 -0
- package/dist/modules/mcp/registry.js +15 -0
- package/dist/modules/memory/index.js +1 -0
- package/dist/modules/memory/search.js +26 -0
- package/dist/modules/memory/store.js +38 -0
- package/dist/modules/pipelines/engine.js +60 -0
- package/dist/modules/pipelines/index.js +3 -0
- package/dist/modules/pipelines/parser.js +53 -0
- package/dist/modules/pipelines/template.js +14 -0
- package/dist/modules/plugins/builtin/lint-on-write.js +121 -0
- package/dist/modules/plugins/builtin/notify.js +8 -0
- package/dist/modules/plugins/index.js +1 -0
- package/dist/modules/plugins/loader.js +28 -0
- package/dist/modules/plugins/manager.js +161 -0
- package/dist/modules/plugins/types.js +1 -0
- package/dist/modules/registry.js +45 -0
- package/dist/modules/security/audit-log.js +108 -0
- package/dist/modules/security/audit-notifier.js +292 -0
- package/dist/modules/security/command-validator.js +91 -0
- package/dist/modules/security/content-scanner.js +52 -0
- package/dist/modules/security/data-sanitizer.js +97 -0
- package/dist/modules/security/encryption.js +218 -0
- package/dist/modules/security/index.js +14 -0
- package/dist/modules/security/network-validator.js +79 -0
- package/dist/modules/security/path-validator.js +155 -0
- package/dist/modules/security/rate-limiter.js +119 -0
- package/dist/modules/security/security-policies.js +393 -0
- package/dist/modules/security/session-encryption.js +193 -0
- package/dist/modules/security/session-isolation.js +95 -0
- package/dist/modules/session/index.js +3 -0
- package/dist/modules/session/manager.js +167 -0
- package/dist/modules/session/module.js +28 -0
- package/dist/modules/session/store.js +174 -0
- package/dist/modules/session/types.js +1 -0
- package/dist/modules/skills/index.js +3 -0
- package/dist/modules/skills/loader.js +72 -0
- package/dist/modules/skills/matcher.js +27 -0
- package/dist/modules/skills/module.js +180 -0
- package/dist/modules/types.js +1 -0
- package/dist/modules/updater/checker.js +32 -0
- package/dist/modules/updater/index.js +1 -0
- package/dist/modules/user-profile/compressor.js +16 -0
- package/dist/modules/user-profile/index.js +1 -0
- package/dist/modules/user-profile/profile.js +68 -0
- package/dist/tools/approve.js +32 -0
- package/dist/tools/bash.js +77 -0
- package/dist/tools/browser.js +97 -0
- package/dist/tools/create-dir.js +57 -0
- package/dist/tools/delete-file.js +64 -0
- package/dist/tools/edit-file.js +78 -0
- package/dist/tools/executor.js +83 -0
- package/dist/tools/file-info.js +46 -0
- package/dist/tools/filter-tools.js +10 -0
- package/dist/tools/glob-tool.js +19 -0
- package/dist/tools/grep-tool.js +51 -0
- package/dist/tools/index.js +44 -0
- package/dist/tools/list-dir.js +40 -0
- package/dist/tools/load-skill.js +48 -0
- package/dist/tools/mcp-call.js +68 -0
- package/dist/tools/move-file.js +84 -0
- package/dist/tools/pipeline-run.js +39 -0
- package/dist/tools/question.js +142 -0
- package/dist/tools/read-file.js +65 -0
- package/dist/tools/registry.js +36 -0
- package/dist/tools/scope-check.js +30 -0
- package/dist/tools/search-history.js +64 -0
- package/dist/tools/subagent.js +130 -0
- package/dist/tools/types.js +1 -0
- package/dist/tools/user-input.js +123 -0
- package/dist/tools/web-browse.js +51 -0
- package/dist/tools/web-fetch.js +62 -0
- package/dist/tools/web-search.js +59 -0
- package/dist/tools/write-file.js +80 -0
- package/dist/ui/box.js +81 -0
- package/dist/ui/colors.js +4 -0
- package/dist/ui/diff.js +185 -0
- package/dist/ui/index.js +6 -0
- package/dist/ui/md-formatter.js +212 -0
- package/dist/ui/output.js +13 -0
- package/dist/ui/renderer.js +141 -0
- package/dist/ui/spinner.js +70 -0
- package/dist/ui/table.js +144 -0
- package/package.json +1 -1
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync, appendFileSync, existsSync, mkdirSync } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { MemorySearch } from './search';
|
|
4
|
+
const MEMORY_FILES = ['conventions', 'decisions', 'errors'];
|
|
5
|
+
export class MemoryStore {
|
|
6
|
+
memoryDir;
|
|
7
|
+
constructor(memoryDir) {
|
|
8
|
+
this.memoryDir = memoryDir;
|
|
9
|
+
this.ensureDir();
|
|
10
|
+
for (const name of MEMORY_FILES) {
|
|
11
|
+
const path = join(this.memoryDir, `${name}.md`);
|
|
12
|
+
if (!existsSync(path)) {
|
|
13
|
+
writeFileSync(path, `# ${name.charAt(0).toUpperCase() + name.slice(1)}\n\n`, 'utf-8');
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
ensureDir() {
|
|
18
|
+
if (!existsSync(this.memoryDir)) {
|
|
19
|
+
mkdirSync(this.memoryDir, { recursive: true });
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
read(name) {
|
|
23
|
+
const path = join(this.memoryDir, `${name}.md`);
|
|
24
|
+
if (!existsSync(path))
|
|
25
|
+
return '';
|
|
26
|
+
return readFileSync(path, 'utf-8');
|
|
27
|
+
}
|
|
28
|
+
append(name, entry) {
|
|
29
|
+
const path = join(this.memoryDir, `${name}.md`);
|
|
30
|
+
const timestamp = new Date().toISOString().replace('T', ' ').slice(0, 19);
|
|
31
|
+
const formatted = `- **${timestamp}** — ${entry}\n`;
|
|
32
|
+
appendFileSync(path, formatted, 'utf-8');
|
|
33
|
+
}
|
|
34
|
+
search(query) {
|
|
35
|
+
const searchModule = new MemorySearch(this.memoryDir);
|
|
36
|
+
return searchModule.query(query);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { t } from '../../i18n/index';
|
|
2
|
+
export class PipelineEngine {
|
|
3
|
+
stepStatus = new Map();
|
|
4
|
+
resolveDependencies(steps) {
|
|
5
|
+
const visited = new Set();
|
|
6
|
+
const inStack = new Set();
|
|
7
|
+
const order = [];
|
|
8
|
+
function visit(stepId, stepMap) {
|
|
9
|
+
if (inStack.has(stepId))
|
|
10
|
+
throw new Error(t('pipeline.circular', { stepId }));
|
|
11
|
+
if (visited.has(stepId))
|
|
12
|
+
return;
|
|
13
|
+
inStack.add(stepId);
|
|
14
|
+
const step = stepMap.get(stepId);
|
|
15
|
+
if (step?.depends_on) {
|
|
16
|
+
for (const dep of step.depends_on) {
|
|
17
|
+
visit(dep, stepMap);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
inStack.delete(stepId);
|
|
21
|
+
visited.add(stepId);
|
|
22
|
+
order.push(stepId);
|
|
23
|
+
}
|
|
24
|
+
const stepMap = new Map(steps.map(s => [s.id, s]));
|
|
25
|
+
for (const step of steps) {
|
|
26
|
+
if (!visited.has(step.id)) {
|
|
27
|
+
visit(step.id, stepMap);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return order;
|
|
31
|
+
}
|
|
32
|
+
getReadySteps(steps, completed) {
|
|
33
|
+
return steps.filter(s => {
|
|
34
|
+
if (completed.has(s.id))
|
|
35
|
+
return false;
|
|
36
|
+
if (this.stepStatus.get(s.id) === 'running')
|
|
37
|
+
return false;
|
|
38
|
+
if (this.stepStatus.get(s.id) === 'failed')
|
|
39
|
+
return false;
|
|
40
|
+
if (!s.depends_on || s.depends_on.length === 0)
|
|
41
|
+
return true;
|
|
42
|
+
return s.depends_on.every(d => completed.has(d));
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
setStepStatus(stepId, status) {
|
|
46
|
+
this.stepStatus.set(stepId, status);
|
|
47
|
+
}
|
|
48
|
+
getStepStatus(stepId) {
|
|
49
|
+
return this.stepStatus.get(stepId) || 'pending';
|
|
50
|
+
}
|
|
51
|
+
isComplete(stepIds) {
|
|
52
|
+
return stepIds.every(id => {
|
|
53
|
+
const s = this.stepStatus.get(id);
|
|
54
|
+
return s === 'done' || s === 'skipped';
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
reset() {
|
|
58
|
+
this.stepStatus.clear();
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { t } from '../../i18n/index';
|
|
2
|
+
export class PipelineParser {
|
|
3
|
+
static parse(yaml) {
|
|
4
|
+
const lines = yaml.split('\n').filter(l => l.trim());
|
|
5
|
+
const result = {};
|
|
6
|
+
let currentSection = null;
|
|
7
|
+
let currentSteps = [];
|
|
8
|
+
for (const line of lines) {
|
|
9
|
+
if (line.match(/^\w+:/) && !line.startsWith(' ') && !line.startsWith(' -')) {
|
|
10
|
+
const [key, ...valParts] = line.split(':');
|
|
11
|
+
const val = valParts.join(':').trim();
|
|
12
|
+
if (key === 'steps') {
|
|
13
|
+
currentSection = 'steps';
|
|
14
|
+
}
|
|
15
|
+
else {
|
|
16
|
+
result[key] = val;
|
|
17
|
+
currentSection = null;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
else if (currentSection === 'steps') {
|
|
21
|
+
const stepMatch = line.match(/^\s+- id:\s*(\S+)/);
|
|
22
|
+
if (stepMatch) {
|
|
23
|
+
currentSteps.push({ id: stepMatch[1] });
|
|
24
|
+
}
|
|
25
|
+
else if (currentSteps.length > 0) {
|
|
26
|
+
const lastStep = currentSteps[currentSteps.length - 1];
|
|
27
|
+
const kvMatch = line.match(/^\s+(\w+):\s*(.+)$/);
|
|
28
|
+
if (kvMatch) {
|
|
29
|
+
const key = kvMatch[1];
|
|
30
|
+
let val = kvMatch[2].trim();
|
|
31
|
+
if (val === 'true')
|
|
32
|
+
val = true;
|
|
33
|
+
else if (val === 'false')
|
|
34
|
+
val = false;
|
|
35
|
+
else if (val.startsWith('[') && val.endsWith(']')) {
|
|
36
|
+
val = val.slice(1, -1).split(',').map((s) => s.trim().replace(/^['"]|['"]$/g, ''));
|
|
37
|
+
}
|
|
38
|
+
lastStep[key] = val;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
if (!result.name || currentSteps.length === 0) {
|
|
44
|
+
throw new Error(t('pipeline.invalid'));
|
|
45
|
+
}
|
|
46
|
+
for (const step of currentSteps) {
|
|
47
|
+
if (!step.id || !step.agent || !step.prompt) {
|
|
48
|
+
throw new Error(t('pipeline.step_missing_fields', { step: JSON.stringify(step) }));
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return { name: result.name, steps: currentSteps };
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export class TemplateEngine {
|
|
2
|
+
static render(template, params, stepOutputs) {
|
|
3
|
+
let result = template;
|
|
4
|
+
for (const [key, val] of Object.entries(params)) {
|
|
5
|
+
result = result.replace(new RegExp(`\\{${key}\\}`, 'g'), val);
|
|
6
|
+
}
|
|
7
|
+
if (stepOutputs) {
|
|
8
|
+
result = result.replace(/\{\{steps\.(\w+)\.output\}\}/g, (_, stepId) => {
|
|
9
|
+
return stepOutputs[stepId]?.output || `{{steps.${stepId}.output}}`;
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
return result;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { execSync } from 'child_process';
|
|
2
|
+
import { existsSync, readFileSync } from 'fs';
|
|
3
|
+
import { resolve, extname, join } from 'path';
|
|
4
|
+
let projectTypeCheckPromise = null;
|
|
5
|
+
let projectTypeCheckTimestamp = 0;
|
|
6
|
+
const TYPE_CHECK_DEBOUNCE_MS = 2000;
|
|
7
|
+
export class LintOnWritePlugin {
|
|
8
|
+
name = 'lint-on-write';
|
|
9
|
+
priority = 10;
|
|
10
|
+
async onAfterTool(ctx, call, result) {
|
|
11
|
+
if (call.name !== 'write_file' && call.name !== 'edit_file') {
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
if (!result.success) {
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
const path = String(call.arguments.path || '');
|
|
18
|
+
if (!path)
|
|
19
|
+
return;
|
|
20
|
+
const fullPath = resolve(ctx.baseDir, path);
|
|
21
|
+
if (!existsSync(fullPath))
|
|
22
|
+
return;
|
|
23
|
+
const ext = extname(fullPath);
|
|
24
|
+
const syntaxError = this.checkSyntax(fullPath, ext, ctx.baseDir);
|
|
25
|
+
if (syntaxError) {
|
|
26
|
+
result.output += `\n\n[Syntax check failed]: ${syntaxError}`;
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
await this.runProjectLint(ctx, result);
|
|
30
|
+
await this.runProjectTypeCheck(ctx, result);
|
|
31
|
+
}
|
|
32
|
+
checkSyntax(filePath, ext, baseDir) {
|
|
33
|
+
if (ext === '.ts' || ext === '.tsx') {
|
|
34
|
+
try {
|
|
35
|
+
execSync(`npx tsc --noEmit --skipLibCheck "${filePath}"`, { cwd: baseDir, stdio: 'pipe', timeout: 10000 });
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
catch (err) {
|
|
39
|
+
if (err.status === 127 || err.message.includes('not found') || err.message.includes('ENOENT')) {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
const stderr = err.stderr?.toString() || err.stdout?.toString() || '';
|
|
43
|
+
if (stderr.includes('error TS')) {
|
|
44
|
+
const firstError = stderr.split('\n').find((line) => line.includes('error TS')) || 'TypeScript syntax error';
|
|
45
|
+
return firstError.trim();
|
|
46
|
+
}
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
if (ext === '.js' || ext === '.jsx') {
|
|
51
|
+
try {
|
|
52
|
+
execSync(`node --check "${filePath}"`, { cwd: baseDir, stdio: 'pipe', timeout: 5000 });
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
catch (err) {
|
|
56
|
+
const stderr = err.stderr?.toString() || '';
|
|
57
|
+
const firstError = stderr.split('\n').find((line) => line.trim()) || 'JavaScript syntax error';
|
|
58
|
+
return firstError.trim();
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
async runProjectLint(ctx, result) {
|
|
64
|
+
try {
|
|
65
|
+
const packageJsonPath = join(ctx.baseDir, 'package.json');
|
|
66
|
+
if (!existsSync(packageJsonPath)) {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
|
|
70
|
+
const lintScript = packageJson.scripts?.lint;
|
|
71
|
+
if (!lintScript) {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
ctx.logger.debug(`Running lint: ${lintScript}`);
|
|
75
|
+
execSync(lintScript, { cwd: ctx.baseDir, stdio: 'pipe' });
|
|
76
|
+
ctx.logger.debug('Lint passed');
|
|
77
|
+
}
|
|
78
|
+
catch (err) {
|
|
79
|
+
ctx.logger.warn(`Lint failed: ${err.message}`);
|
|
80
|
+
result.output += `\n\n[Project lint failed]: ${err.message}`;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
async runProjectTypeCheck(ctx, result) {
|
|
84
|
+
const tsconfigPath = join(ctx.baseDir, 'tsconfig.json');
|
|
85
|
+
if (!existsSync(tsconfigPath)) {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
const now = Date.now();
|
|
89
|
+
if (projectTypeCheckPromise && now - projectTypeCheckTimestamp < TYPE_CHECK_DEBOUNCE_MS) {
|
|
90
|
+
const error = await projectTypeCheckPromise;
|
|
91
|
+
if (error) {
|
|
92
|
+
result.output += `\n\n[Project typecheck failed]: ${error}`;
|
|
93
|
+
}
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
projectTypeCheckTimestamp = now;
|
|
97
|
+
projectTypeCheckPromise = this.runTscCheck(ctx.baseDir);
|
|
98
|
+
const error = await projectTypeCheckPromise;
|
|
99
|
+
if (error) {
|
|
100
|
+
result.output += `\n\n[Project typecheck failed]: ${error}`;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
async runTscCheck(baseDir) {
|
|
104
|
+
try {
|
|
105
|
+
execSync(`npx tsc --noEmit --skipLibCheck`, { cwd: baseDir, stdio: 'pipe', timeout: 30000 });
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
catch (err) {
|
|
109
|
+
if (err.status === 127 || err.message.includes('not found') || err.message.includes('ENOENT')) {
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
const stderr = err.stderr?.toString() || err.stdout?.toString() || '';
|
|
113
|
+
if (stderr.includes('error TS')) {
|
|
114
|
+
const firstError = stderr.split('\n').find((line) => line.includes('error TS')) || 'TypeScript type error';
|
|
115
|
+
return firstError.trim();
|
|
116
|
+
}
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
export const plugin = new LintOnWritePlugin();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { PluginManager } from './manager';
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { readdirSync, existsSync, statSync } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { t } from '../../i18n/index';
|
|
4
|
+
export class PluginLoader {
|
|
5
|
+
loadFromDir(dirPath, pluginManager, logger) {
|
|
6
|
+
if (!existsSync(dirPath))
|
|
7
|
+
return;
|
|
8
|
+
const entries = readdirSync(dirPath);
|
|
9
|
+
for (const entry of entries) {
|
|
10
|
+
const fullPath = join(dirPath, entry);
|
|
11
|
+
if (!statSync(fullPath).isFile())
|
|
12
|
+
continue;
|
|
13
|
+
if (!entry.endsWith('.ts') && !entry.endsWith('.js'))
|
|
14
|
+
continue;
|
|
15
|
+
try {
|
|
16
|
+
const mod = require(fullPath);
|
|
17
|
+
const plugin = mod.default || mod.plugin;
|
|
18
|
+
if (plugin && plugin.name) {
|
|
19
|
+
pluginManager.register(plugin);
|
|
20
|
+
logger?.warn(t('plugin.loaded', { name: plugin.name }));
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
catch (e) {
|
|
24
|
+
logger?.warn(t('plugin.skipped', { entry, message: e.message }));
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
export class PluginManager {
|
|
2
|
+
plugins = [];
|
|
3
|
+
register(plugin) {
|
|
4
|
+
this.plugins.push(plugin);
|
|
5
|
+
this.plugins.sort((a, b) => (b.priority || 0) - (a.priority || 0));
|
|
6
|
+
}
|
|
7
|
+
getAllPlugins() {
|
|
8
|
+
return [...this.plugins];
|
|
9
|
+
}
|
|
10
|
+
runOnBuildPrompt() {
|
|
11
|
+
return this.plugins.map(p => {
|
|
12
|
+
try {
|
|
13
|
+
return p.onBuildPrompt?.() ?? null;
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
async runOnBeforeTool(ctx, call) {
|
|
21
|
+
for (const plugin of this.plugins) {
|
|
22
|
+
if (plugin.onBeforeTool) {
|
|
23
|
+
try {
|
|
24
|
+
const result = await plugin.onBeforeTool(ctx, call);
|
|
25
|
+
if (result === false)
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
catch { /* error isolation */ }
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
async runOnAfterTool(ctx, call, result) {
|
|
34
|
+
for (const plugin of this.plugins) {
|
|
35
|
+
if (plugin.onAfterTool) {
|
|
36
|
+
try {
|
|
37
|
+
await plugin.onAfterTool(ctx, call, result);
|
|
38
|
+
}
|
|
39
|
+
catch { /* error isolation */ }
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
async runOnError(ctx, error) {
|
|
44
|
+
for (const plugin of this.plugins) {
|
|
45
|
+
if (plugin.onError) {
|
|
46
|
+
try {
|
|
47
|
+
await plugin.onError(ctx, error);
|
|
48
|
+
}
|
|
49
|
+
catch { /* error isolation */ }
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
runOnSessionStart(ctx) {
|
|
54
|
+
for (const plugin of this.plugins) {
|
|
55
|
+
if (plugin.onSessionStart) {
|
|
56
|
+
try {
|
|
57
|
+
plugin.onSessionStart(ctx);
|
|
58
|
+
}
|
|
59
|
+
catch { /* error isolation */ }
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
runOnBeforeThink(ctx) {
|
|
64
|
+
for (const plugin of this.plugins) {
|
|
65
|
+
if (plugin.onBeforeThink) {
|
|
66
|
+
try {
|
|
67
|
+
plugin.onBeforeThink(ctx);
|
|
68
|
+
}
|
|
69
|
+
catch { /* error isolation */ }
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
runOnAfterThink(ctx, response) {
|
|
74
|
+
for (const plugin of this.plugins) {
|
|
75
|
+
if (plugin.onAfterThink) {
|
|
76
|
+
try {
|
|
77
|
+
plugin.onAfterThink(ctx, response);
|
|
78
|
+
}
|
|
79
|
+
catch { /* error isolation */ }
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
runOnSessionEnd(ctx) {
|
|
84
|
+
for (const plugin of this.plugins) {
|
|
85
|
+
if (plugin.onSessionEnd) {
|
|
86
|
+
try {
|
|
87
|
+
plugin.onSessionEnd(ctx);
|
|
88
|
+
}
|
|
89
|
+
catch { /* error isolation */ }
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
runOnToolCall(ctx) {
|
|
94
|
+
for (const plugin of this.plugins) {
|
|
95
|
+
if (plugin.onToolCall) {
|
|
96
|
+
try {
|
|
97
|
+
plugin.onToolCall(ctx);
|
|
98
|
+
}
|
|
99
|
+
catch { /* error isolation */ }
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
runOnPhase(ctx, phase) {
|
|
104
|
+
for (const plugin of this.plugins) {
|
|
105
|
+
if (plugin.onPhase) {
|
|
106
|
+
try {
|
|
107
|
+
plugin.onPhase(ctx, phase);
|
|
108
|
+
}
|
|
109
|
+
catch { /* error isolation */ }
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
runOnToolStart(ctx, call) {
|
|
114
|
+
for (const plugin of this.plugins) {
|
|
115
|
+
if (plugin.onToolStart) {
|
|
116
|
+
try {
|
|
117
|
+
plugin.onToolStart(ctx, call);
|
|
118
|
+
}
|
|
119
|
+
catch { /* error isolation */ }
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
runOnToolEnd(ctx, call, result, durationMs) {
|
|
124
|
+
for (const plugin of this.plugins) {
|
|
125
|
+
if (plugin.onToolEnd) {
|
|
126
|
+
try {
|
|
127
|
+
plugin.onToolEnd(ctx, call, result, durationMs);
|
|
128
|
+
}
|
|
129
|
+
catch { /* error isolation */ }
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
/** Pipe a text chunk through plugin transforms; returns the (possibly replaced) chunk. */
|
|
134
|
+
runOnText(ctx, chunk) {
|
|
135
|
+
for (const plugin of this.plugins) {
|
|
136
|
+
if (plugin.onText) {
|
|
137
|
+
try {
|
|
138
|
+
const out = plugin.onText(ctx, chunk);
|
|
139
|
+
if (typeof out === 'string')
|
|
140
|
+
chunk = out;
|
|
141
|
+
}
|
|
142
|
+
catch { /* error isolation */ }
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return chunk;
|
|
146
|
+
}
|
|
147
|
+
/** Pipe a meta chunk through plugin transforms; returns the (possibly replaced) chunk. */
|
|
148
|
+
runOnMeta(ctx, chunk) {
|
|
149
|
+
for (const plugin of this.plugins) {
|
|
150
|
+
if (plugin.onMeta) {
|
|
151
|
+
try {
|
|
152
|
+
const out = plugin.onMeta(ctx, chunk);
|
|
153
|
+
if (typeof out === 'string')
|
|
154
|
+
chunk = out;
|
|
155
|
+
}
|
|
156
|
+
catch { /* error isolation */ }
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return chunk;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
export class ModuleRegistry {
|
|
2
|
+
modules = new Map();
|
|
3
|
+
register(mod) {
|
|
4
|
+
this.modules.set(mod.name, mod);
|
|
5
|
+
}
|
|
6
|
+
get(name) {
|
|
7
|
+
return this.modules.get(name);
|
|
8
|
+
}
|
|
9
|
+
listModules() {
|
|
10
|
+
return Array.from(this.modules.keys()).sort();
|
|
11
|
+
}
|
|
12
|
+
collectPromptBlocks() {
|
|
13
|
+
const blocks = [];
|
|
14
|
+
for (const mod of this.modules.values()) {
|
|
15
|
+
if (mod.getSystemPromptBlock) {
|
|
16
|
+
const block = mod.getSystemPromptBlock();
|
|
17
|
+
if (block)
|
|
18
|
+
blocks.push(block);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return blocks;
|
|
22
|
+
}
|
|
23
|
+
collectToolDefinitions() {
|
|
24
|
+
const tools = [];
|
|
25
|
+
for (const mod of this.modules.values()) {
|
|
26
|
+
if (mod.getToolDefinitions) {
|
|
27
|
+
const defs = mod.getToolDefinitions();
|
|
28
|
+
if (defs)
|
|
29
|
+
tools.push(...defs);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return tools;
|
|
33
|
+
}
|
|
34
|
+
collectPlugins() {
|
|
35
|
+
const plugins = [];
|
|
36
|
+
for (const mod of this.modules.values()) {
|
|
37
|
+
if (mod.getPlugin) {
|
|
38
|
+
const plugin = mod.getPlugin();
|
|
39
|
+
if (plugin)
|
|
40
|
+
plugins.push(plugin);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return plugins;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, appendFileSync } from "fs";
|
|
2
|
+
import { resolve } from "path";
|
|
3
|
+
import { homedir } from "os";
|
|
4
|
+
/**
|
|
5
|
+
* Directory and file path for audit logs
|
|
6
|
+
*/
|
|
7
|
+
const AUDIT_LOG_DIR = resolve(homedir(), ".mma", "logs");
|
|
8
|
+
const AUDIT_LOG_PATH = resolve(AUDIT_LOG_DIR, "audit.jsonl");
|
|
9
|
+
/**
|
|
10
|
+
* Ensure audit log directory exists
|
|
11
|
+
*/
|
|
12
|
+
function ensureAuditLogDir() {
|
|
13
|
+
if (!existsSync(AUDIT_LOG_DIR)) {
|
|
14
|
+
mkdirSync(AUDIT_LOG_DIR, { recursive: true, mode: 0o700 });
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Write an audit log entry
|
|
19
|
+
*/
|
|
20
|
+
export function logAudit(entry) {
|
|
21
|
+
ensureAuditLogDir();
|
|
22
|
+
try {
|
|
23
|
+
const logEntry = JSON.stringify(entry);
|
|
24
|
+
appendFileSync(AUDIT_LOG_PATH, logEntry + "\n", "utf8");
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
// Silently fail if we can't write to audit log
|
|
28
|
+
// This shouldn't break the agent
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Log a tool call
|
|
33
|
+
*/
|
|
34
|
+
export function logToolCall(sessionId, tool, success, details) {
|
|
35
|
+
logAudit({
|
|
36
|
+
timestamp: new Date().toISOString(),
|
|
37
|
+
sessionId,
|
|
38
|
+
action: "tool_call",
|
|
39
|
+
tool,
|
|
40
|
+
success,
|
|
41
|
+
details,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Log a file write operation
|
|
46
|
+
*/
|
|
47
|
+
export function logFileWrite(sessionId, path, success, details) {
|
|
48
|
+
logAudit({
|
|
49
|
+
timestamp: new Date().toISOString(),
|
|
50
|
+
sessionId,
|
|
51
|
+
action: "file_write",
|
|
52
|
+
path,
|
|
53
|
+
success,
|
|
54
|
+
details,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Log a file delete operation
|
|
59
|
+
*/
|
|
60
|
+
export function logFileDelete(sessionId, path, success, details) {
|
|
61
|
+
logAudit({
|
|
62
|
+
timestamp: new Date().toISOString(),
|
|
63
|
+
sessionId,
|
|
64
|
+
action: "file_delete",
|
|
65
|
+
path,
|
|
66
|
+
success,
|
|
67
|
+
details,
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Log a bash command execution
|
|
72
|
+
*/
|
|
73
|
+
export function logBashCommand(sessionId, command, success, details) {
|
|
74
|
+
logAudit({
|
|
75
|
+
timestamp: new Date().toISOString(),
|
|
76
|
+
sessionId,
|
|
77
|
+
action: "bash_command",
|
|
78
|
+
command,
|
|
79
|
+
success,
|
|
80
|
+
details,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Log a network request
|
|
85
|
+
*/
|
|
86
|
+
export function logNetworkRequest(sessionId, url, success, details) {
|
|
87
|
+
logAudit({
|
|
88
|
+
timestamp: new Date().toISOString(),
|
|
89
|
+
sessionId,
|
|
90
|
+
action: "network_request",
|
|
91
|
+
url,
|
|
92
|
+
success,
|
|
93
|
+
details,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Log a security block event
|
|
98
|
+
*/
|
|
99
|
+
export function logSecurityBlock(sessionId, action, reason, details) {
|
|
100
|
+
logAudit({
|
|
101
|
+
timestamp: new Date().toISOString(),
|
|
102
|
+
sessionId,
|
|
103
|
+
action: "security_block",
|
|
104
|
+
success: false,
|
|
105
|
+
reason,
|
|
106
|
+
details,
|
|
107
|
+
});
|
|
108
|
+
}
|