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
|
@@ -7,9 +7,7 @@ function sanitizeToolName(server, tool) {
|
|
|
7
7
|
}
|
|
8
8
|
function convertInputSchema(schema) {
|
|
9
9
|
if (schema && typeof schema === "object" && "properties" in schema) {
|
|
10
|
-
const required = Array.isArray(schema.required)
|
|
11
|
-
? schema.required
|
|
12
|
-
: [];
|
|
10
|
+
const required = Array.isArray(schema.required) ? schema.required : [];
|
|
13
11
|
return {
|
|
14
12
|
type: "object",
|
|
15
13
|
properties: schema.properties,
|
|
@@ -118,9 +116,7 @@ export class MCPModule {
|
|
|
118
116
|
const result = await client.callTool(d.toolName, args);
|
|
119
117
|
return {
|
|
120
118
|
success: true,
|
|
121
|
-
output: typeof result === "string"
|
|
122
|
-
? result
|
|
123
|
-
: JSON.stringify(result, null, 2),
|
|
119
|
+
output: typeof result === "string" ? result : JSON.stringify(result, null, 2),
|
|
124
120
|
};
|
|
125
121
|
}
|
|
126
122
|
catch (e) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export { MemoryStore } from
|
|
1
|
+
export { MemoryStore } from "./store";
|
|
@@ -1,44 +1,92 @@
|
|
|
1
|
-
import { homedir } from
|
|
2
|
-
import { join } from
|
|
3
|
-
import { MemoryStore } from
|
|
4
|
-
import { rememberTool } from
|
|
5
|
-
import { recallTool } from
|
|
1
|
+
import { homedir } from "os";
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
import { MemoryStore } from "./store";
|
|
4
|
+
import { rememberTool } from "../../tools/remember";
|
|
5
|
+
import { recallTool } from "../../tools/recall";
|
|
6
|
+
const MEMORY_MD_FILES = ["errors", "conventions", "decisions", "facts"];
|
|
6
7
|
export class MemoryModule {
|
|
7
|
-
name =
|
|
8
|
+
name = "memory";
|
|
8
9
|
store;
|
|
9
|
-
constructor(
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
constructor(storeOrDir) {
|
|
11
|
+
if (storeOrDir instanceof MemoryStore) {
|
|
12
|
+
this.store = storeOrDir;
|
|
13
|
+
}
|
|
14
|
+
else {
|
|
15
|
+
const dir = storeOrDir || join(homedir(), ".mma", "memory");
|
|
16
|
+
this.store = new MemoryStore(dir);
|
|
17
|
+
}
|
|
12
18
|
}
|
|
13
19
|
getSystemPromptBlock() {
|
|
20
|
+
const parts = [];
|
|
14
21
|
const prefs = this.store.getPreferences();
|
|
15
|
-
if (Object.keys(prefs).length
|
|
22
|
+
if (Object.keys(prefs).length > 0) {
|
|
23
|
+
const prefStr = Object.entries(prefs)
|
|
24
|
+
.map(([k, v]) => `${k}=${v}`)
|
|
25
|
+
.join(", ");
|
|
26
|
+
parts.push(`User preferences: ${prefStr}`);
|
|
27
|
+
}
|
|
28
|
+
const memoryTail = this.getMemoryTail();
|
|
29
|
+
if (memoryTail)
|
|
30
|
+
parts.push(memoryTail);
|
|
31
|
+
if (parts.length === 0)
|
|
16
32
|
return null;
|
|
17
|
-
const
|
|
18
|
-
.map(([k, v]) => `${k}=${v}`)
|
|
19
|
-
.join(', ');
|
|
33
|
+
const content = parts.join("\n\n");
|
|
20
34
|
return {
|
|
21
|
-
content
|
|
22
|
-
priority:
|
|
35
|
+
content,
|
|
36
|
+
priority: "normal",
|
|
23
37
|
essential: false,
|
|
24
|
-
estimatedTokens: Math.ceil(
|
|
38
|
+
estimatedTokens: Math.ceil(content.length / 4) + 10,
|
|
25
39
|
};
|
|
26
40
|
}
|
|
41
|
+
/**
|
|
42
|
+
* Bounded tail of the append-only .md memory files (errors, conventions,
|
|
43
|
+
* decisions, facts). This is what makes past-session lessons visible in the
|
|
44
|
+
* system prompt — without it the loop's error rules are write-only.
|
|
45
|
+
*/
|
|
46
|
+
getMemoryTail() {
|
|
47
|
+
const sections = [];
|
|
48
|
+
const tailPerFile = 3;
|
|
49
|
+
for (const name of MEMORY_MD_FILES) {
|
|
50
|
+
const content = this.store.read(name).trim();
|
|
51
|
+
if (!content)
|
|
52
|
+
continue;
|
|
53
|
+
const entries = content
|
|
54
|
+
.split(/\r?\n/)
|
|
55
|
+
.map((l) => l.trim())
|
|
56
|
+
.filter((l) => l.startsWith("- "))
|
|
57
|
+
.slice(-tailPerFile);
|
|
58
|
+
if (entries.length === 0)
|
|
59
|
+
continue;
|
|
60
|
+
sections.push(`[Memory ${name}]:\n${entries.join("\n")}`);
|
|
61
|
+
}
|
|
62
|
+
if (sections.length === 0)
|
|
63
|
+
return null;
|
|
64
|
+
let joined = sections.join("\n\n");
|
|
65
|
+
// Hard cap so a huge errors.md can never blow the system prompt budget.
|
|
66
|
+
if (joined.length > 1200)
|
|
67
|
+
joined = joined.slice(0, 1200) + "\n…";
|
|
68
|
+
return joined;
|
|
69
|
+
}
|
|
27
70
|
getToolDefinitions() {
|
|
28
71
|
return [rememberTool, recallTool];
|
|
29
72
|
}
|
|
30
73
|
getPlugin() {
|
|
31
74
|
return {
|
|
32
|
-
name:
|
|
75
|
+
name: "memory",
|
|
33
76
|
isBuiltin: true,
|
|
34
77
|
onBuildPrompt: () => {
|
|
78
|
+
const parts = [];
|
|
35
79
|
const prefs = this.store.getPreferences();
|
|
36
|
-
if (Object.keys(prefs).length
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
.
|
|
41
|
-
|
|
80
|
+
if (Object.keys(prefs).length > 0) {
|
|
81
|
+
const prefStr = Object.entries(prefs)
|
|
82
|
+
.map(([k, v]) => `${k}=${v}`)
|
|
83
|
+
.join(", ");
|
|
84
|
+
parts.push(`User preferences: ${prefStr}`);
|
|
85
|
+
}
|
|
86
|
+
const memoryTail = this.getMemoryTail();
|
|
87
|
+
if (memoryTail)
|
|
88
|
+
parts.push(memoryTail);
|
|
89
|
+
return parts.length > 0 ? parts.join("\n\n") : null;
|
|
42
90
|
},
|
|
43
91
|
};
|
|
44
92
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { readFileSync, existsSync } from
|
|
2
|
-
import { join } from
|
|
3
|
-
const MEMORY_FILES = [
|
|
1
|
+
import { readFileSync, existsSync } from "fs";
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
const MEMORY_FILES = ["conventions", "decisions", "errors", "facts"];
|
|
4
4
|
export class MemorySearch {
|
|
5
5
|
memoryDir;
|
|
6
6
|
constructor(memoryDir) {
|
|
@@ -13,8 +13,8 @@ export class MemorySearch {
|
|
|
13
13
|
const path = join(this.memoryDir, `${name}.md`);
|
|
14
14
|
if (!existsSync(path))
|
|
15
15
|
continue;
|
|
16
|
-
const content = readFileSync(path,
|
|
17
|
-
const lines = content.split(
|
|
16
|
+
const content = readFileSync(path, "utf-8");
|
|
17
|
+
const lines = content.split("\n");
|
|
18
18
|
for (const line of lines) {
|
|
19
19
|
if (line.toLowerCase().includes(lowerQuery)) {
|
|
20
20
|
results.push({ file: name, match: line.trim() });
|
|
@@ -22,18 +22,20 @@ export class MemorySearch {
|
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
24
|
// Search preferences.json
|
|
25
|
-
const prefsPath = join(this.memoryDir,
|
|
25
|
+
const prefsPath = join(this.memoryDir, "preferences.json");
|
|
26
26
|
if (existsSync(prefsPath)) {
|
|
27
27
|
try {
|
|
28
|
-
const prefs = JSON.parse(readFileSync(prefsPath,
|
|
28
|
+
const prefs = JSON.parse(readFileSync(prefsPath, "utf-8"));
|
|
29
29
|
for (const [key, value] of Object.entries(prefs)) {
|
|
30
30
|
const searchStr = `${key}=${value}`;
|
|
31
31
|
if (searchStr.toLowerCase().includes(lowerQuery)) {
|
|
32
|
-
results.push({ file:
|
|
32
|
+
results.push({ file: "preferences", match: `${key} = ${value}` });
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
35
|
}
|
|
36
|
-
catch {
|
|
36
|
+
catch {
|
|
37
|
+
/* skip */
|
|
38
|
+
}
|
|
37
39
|
}
|
|
38
40
|
return results;
|
|
39
41
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { readFileSync, writeFileSync, appendFileSync, existsSync, mkdirSync } from
|
|
2
|
-
import { join } from
|
|
3
|
-
import { MemorySearch } from
|
|
4
|
-
const MEMORY_FILES = [
|
|
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", "facts"];
|
|
5
5
|
export class MemoryStore {
|
|
6
6
|
memoryDir;
|
|
7
7
|
constructor(memoryDir) {
|
|
@@ -10,7 +10,7 @@ export class MemoryStore {
|
|
|
10
10
|
for (const name of MEMORY_FILES) {
|
|
11
11
|
const path = join(this.memoryDir, `${name}.md`);
|
|
12
12
|
if (!existsSync(path)) {
|
|
13
|
-
writeFileSync(path, `# ${name.charAt(0).toUpperCase() + name.slice(1)}\n\n`,
|
|
13
|
+
writeFileSync(path, `# ${name.charAt(0).toUpperCase() + name.slice(1)}\n\n`, "utf-8");
|
|
14
14
|
}
|
|
15
15
|
}
|
|
16
16
|
}
|
|
@@ -22,28 +22,28 @@ export class MemoryStore {
|
|
|
22
22
|
read(name) {
|
|
23
23
|
const path = join(this.memoryDir, `${name}.md`);
|
|
24
24
|
if (!existsSync(path))
|
|
25
|
-
return
|
|
26
|
-
return readFileSync(path,
|
|
25
|
+
return "";
|
|
26
|
+
return readFileSync(path, "utf-8");
|
|
27
27
|
}
|
|
28
28
|
append(name, entry) {
|
|
29
29
|
const path = join(this.memoryDir, `${name}.md`);
|
|
30
|
-
const timestamp = new Date().toISOString().replace(
|
|
30
|
+
const timestamp = new Date().toISOString().replace("T", " ").slice(0, 19);
|
|
31
31
|
const formatted = `- **${timestamp}** — ${entry}\n`;
|
|
32
|
-
appendFileSync(path, formatted,
|
|
32
|
+
appendFileSync(path, formatted, "utf-8");
|
|
33
33
|
}
|
|
34
34
|
search(query) {
|
|
35
35
|
const searchModule = new MemorySearch(this.memoryDir);
|
|
36
36
|
return searchModule.query(query);
|
|
37
37
|
}
|
|
38
38
|
prefsPath() {
|
|
39
|
-
return join(this.memoryDir,
|
|
39
|
+
return join(this.memoryDir, "preferences.json");
|
|
40
40
|
}
|
|
41
41
|
getPreferences() {
|
|
42
42
|
const path = this.prefsPath();
|
|
43
43
|
if (!existsSync(path))
|
|
44
44
|
return {};
|
|
45
45
|
try {
|
|
46
|
-
return JSON.parse(readFileSync(path,
|
|
46
|
+
return JSON.parse(readFileSync(path, "utf-8"));
|
|
47
47
|
}
|
|
48
48
|
catch {
|
|
49
49
|
return {};
|
|
@@ -52,14 +52,14 @@ export class MemoryStore {
|
|
|
52
52
|
setPreference(key, value) {
|
|
53
53
|
const prefs = this.getPreferences();
|
|
54
54
|
prefs[key] = value;
|
|
55
|
-
writeFileSync(this.prefsPath(), JSON.stringify(prefs, null, 2),
|
|
55
|
+
writeFileSync(this.prefsPath(), JSON.stringify(prefs, null, 2), "utf-8");
|
|
56
56
|
}
|
|
57
57
|
deletePreference(key) {
|
|
58
58
|
const prefs = this.getPreferences();
|
|
59
59
|
if (!(key in prefs))
|
|
60
60
|
return false;
|
|
61
61
|
delete prefs[key];
|
|
62
|
-
writeFileSync(this.prefsPath(), JSON.stringify(prefs, null, 2),
|
|
62
|
+
writeFileSync(this.prefsPath(), JSON.stringify(prefs, null, 2), "utf-8");
|
|
63
63
|
return true;
|
|
64
64
|
}
|
|
65
65
|
appendRule(category, pattern, cause, solution) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t } from
|
|
1
|
+
import { t } from "../../i18n/index";
|
|
2
2
|
export class PipelineEngine {
|
|
3
3
|
stepStatus = new Map();
|
|
4
4
|
resolveDependencies(steps) {
|
|
@@ -7,7 +7,7 @@ export class PipelineEngine {
|
|
|
7
7
|
const order = [];
|
|
8
8
|
function visit(stepId, stepMap) {
|
|
9
9
|
if (inStack.has(stepId))
|
|
10
|
-
throw new Error(t(
|
|
10
|
+
throw new Error(t("pipeline.circular", { stepId }));
|
|
11
11
|
if (visited.has(stepId))
|
|
12
12
|
return;
|
|
13
13
|
inStack.add(stepId);
|
|
@@ -21,7 +21,7 @@ export class PipelineEngine {
|
|
|
21
21
|
visited.add(stepId);
|
|
22
22
|
order.push(stepId);
|
|
23
23
|
}
|
|
24
|
-
const stepMap = new Map(steps.map(s => [s.id, s]));
|
|
24
|
+
const stepMap = new Map(steps.map((s) => [s.id, s]));
|
|
25
25
|
for (const step of steps) {
|
|
26
26
|
if (!visited.has(step.id)) {
|
|
27
27
|
visit(step.id, stepMap);
|
|
@@ -30,28 +30,28 @@ export class PipelineEngine {
|
|
|
30
30
|
return order;
|
|
31
31
|
}
|
|
32
32
|
getReadySteps(steps, completed) {
|
|
33
|
-
return steps.filter(s => {
|
|
33
|
+
return steps.filter((s) => {
|
|
34
34
|
if (completed.has(s.id))
|
|
35
35
|
return false;
|
|
36
|
-
if (this.stepStatus.get(s.id) ===
|
|
36
|
+
if (this.stepStatus.get(s.id) === "running")
|
|
37
37
|
return false;
|
|
38
|
-
if (this.stepStatus.get(s.id) ===
|
|
38
|
+
if (this.stepStatus.get(s.id) === "failed")
|
|
39
39
|
return false;
|
|
40
40
|
if (!s.depends_on || s.depends_on.length === 0)
|
|
41
41
|
return true;
|
|
42
|
-
return s.depends_on.every(d => completed.has(d));
|
|
42
|
+
return s.depends_on.every((d) => completed.has(d));
|
|
43
43
|
});
|
|
44
44
|
}
|
|
45
45
|
setStepStatus(stepId, status) {
|
|
46
46
|
this.stepStatus.set(stepId, status);
|
|
47
47
|
}
|
|
48
48
|
getStepStatus(stepId) {
|
|
49
|
-
return this.stepStatus.get(stepId) ||
|
|
49
|
+
return this.stepStatus.get(stepId) || "pending";
|
|
50
50
|
}
|
|
51
51
|
isComplete(stepIds) {
|
|
52
|
-
return stepIds.every(id => {
|
|
52
|
+
return stepIds.every((id) => {
|
|
53
53
|
const s = this.stepStatus.get(id);
|
|
54
|
-
return s ===
|
|
54
|
+
return s === "done" || s === "skipped";
|
|
55
55
|
});
|
|
56
56
|
}
|
|
57
57
|
reset() {
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export { PipelineParser } from
|
|
2
|
-
export { PipelineEngine } from
|
|
3
|
-
export { TemplateEngine } from
|
|
1
|
+
export { PipelineParser } from "./parser";
|
|
2
|
+
export { PipelineEngine } from "./engine";
|
|
3
|
+
export { TemplateEngine } from "./template";
|
|
@@ -1,23 +1,23 @@
|
|
|
1
|
-
import { t } from
|
|
1
|
+
import { t } from "../../i18n/index";
|
|
2
2
|
export class PipelineParser {
|
|
3
3
|
static parse(yaml) {
|
|
4
|
-
const lines = yaml.split(
|
|
4
|
+
const lines = yaml.split("\n").filter((l) => l.trim());
|
|
5
5
|
const result = {};
|
|
6
6
|
let currentSection = null;
|
|
7
7
|
let currentSteps = [];
|
|
8
8
|
for (const line of lines) {
|
|
9
|
-
if (line.match(/^\w+:/) && !line.startsWith(
|
|
10
|
-
const [key, ...valParts] = line.split(
|
|
11
|
-
const val = valParts.join(
|
|
12
|
-
if (key ===
|
|
13
|
-
currentSection =
|
|
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
14
|
}
|
|
15
15
|
else {
|
|
16
16
|
result[key] = val;
|
|
17
17
|
currentSection = null;
|
|
18
18
|
}
|
|
19
19
|
}
|
|
20
|
-
else if (currentSection ===
|
|
20
|
+
else if (currentSection === "steps") {
|
|
21
21
|
const stepMatch = line.match(/^\s+- id:\s*(\S+)/);
|
|
22
22
|
if (stepMatch) {
|
|
23
23
|
currentSteps.push({ id: stepMatch[1] });
|
|
@@ -28,12 +28,15 @@ export class PipelineParser {
|
|
|
28
28
|
if (kvMatch) {
|
|
29
29
|
const key = kvMatch[1];
|
|
30
30
|
let val = kvMatch[2].trim();
|
|
31
|
-
if (val ===
|
|
31
|
+
if (val === "true")
|
|
32
32
|
val = true;
|
|
33
|
-
else if (val ===
|
|
33
|
+
else if (val === "false")
|
|
34
34
|
val = false;
|
|
35
|
-
else if (val.startsWith(
|
|
36
|
-
val = val
|
|
35
|
+
else if (val.startsWith("[") && val.endsWith("]")) {
|
|
36
|
+
val = val
|
|
37
|
+
.slice(1, -1)
|
|
38
|
+
.split(",")
|
|
39
|
+
.map((s) => s.trim().replace(/^['"]|['"]$/g, ""));
|
|
37
40
|
}
|
|
38
41
|
lastStep[key] = val;
|
|
39
42
|
}
|
|
@@ -41,11 +44,11 @@ export class PipelineParser {
|
|
|
41
44
|
}
|
|
42
45
|
}
|
|
43
46
|
if (!result.name || currentSteps.length === 0) {
|
|
44
|
-
throw new Error(t(
|
|
47
|
+
throw new Error(t("pipeline.invalid"));
|
|
45
48
|
}
|
|
46
49
|
for (const step of currentSteps) {
|
|
47
50
|
if (!step.id || !step.agent || !step.prompt) {
|
|
48
|
-
throw new Error(t(
|
|
51
|
+
throw new Error(t("pipeline.step_missing_fields", { step: JSON.stringify(step) }));
|
|
49
52
|
}
|
|
50
53
|
}
|
|
51
54
|
return { name: result.name, steps: currentSteps };
|
|
@@ -2,7 +2,7 @@ export class TemplateEngine {
|
|
|
2
2
|
static render(template, params, stepOutputs) {
|
|
3
3
|
let result = template;
|
|
4
4
|
for (const [key, val] of Object.entries(params)) {
|
|
5
|
-
result = result.replace(new RegExp(`\\{${key}\\}`,
|
|
5
|
+
result = result.replace(new RegExp(`\\{${key}\\}`, "g"), val);
|
|
6
6
|
}
|
|
7
7
|
if (stepOutputs) {
|
|
8
8
|
result = result.replace(/\{\{steps\.(\w+)\.output\}\}/g, (_, stepId) => {
|
|
@@ -2,6 +2,7 @@ import { spawn, execSync } from "child_process";
|
|
|
2
2
|
import { existsSync, readFileSync } from "fs";
|
|
3
3
|
import { resolve, extname, join } from "path";
|
|
4
4
|
import { platform } from "os";
|
|
5
|
+
import { findProjectRoot } from "../../lsp/project-root";
|
|
5
6
|
const TYPE_CHECK_DEBOUNCE_MS = 2000;
|
|
6
7
|
// Per-file syntax checks are cached by (path, content hash): re-writing a file
|
|
7
8
|
// with identical content skips the (expensive) check. Content changes → cache
|
|
@@ -40,6 +41,7 @@ function getWinDecoder() {
|
|
|
40
41
|
export class LintOnWritePlugin {
|
|
41
42
|
name = "lint-on-write";
|
|
42
43
|
priority = 10;
|
|
44
|
+
isBuiltin = true;
|
|
43
45
|
_checkPromise = null;
|
|
44
46
|
_checkTimestamp = 0;
|
|
45
47
|
async onAfterTool(ctx, call, result) {
|
|
@@ -62,10 +64,10 @@ export class LintOnWritePlugin {
|
|
|
62
64
|
return;
|
|
63
65
|
}
|
|
64
66
|
await this.runProjectLint(ctx, result);
|
|
65
|
-
await this.runProjectTypeCheck(ctx, result);
|
|
67
|
+
await this.runProjectTypeCheck(fullPath, ctx.baseDir, result);
|
|
66
68
|
}
|
|
67
69
|
async checkSyntax(filePath, ext, baseDir) {
|
|
68
|
-
if (ext === ".ts" || ext === ".tsx") {
|
|
70
|
+
if (ext === ".ts" || ext === ".tsx" || ext === ".cts" || ext === ".mts") {
|
|
69
71
|
// Syntax-only check via bun's parser (~200ms) instead of `npx tsc`
|
|
70
72
|
// (~3-5s per write). Full type errors are still surfaced by the
|
|
71
73
|
// debounced project typecheck below when a tsconfig exists.
|
|
@@ -93,21 +95,19 @@ export class LintOnWritePlugin {
|
|
|
93
95
|
return null;
|
|
94
96
|
}
|
|
95
97
|
const stderr = err.stderr?.toString() || err.stdout?.toString() || "";
|
|
96
|
-
const firstError = stderr.split("\n").find((line) => line.trim()) ||
|
|
97
|
-
"TypeScript syntax error";
|
|
98
|
+
const firstError = stderr.split("\n").find((line) => line.trim()) || "TypeScript syntax error";
|
|
98
99
|
syntaxCache.set(filePath, { hash, error: firstError.trim() });
|
|
99
100
|
return firstError.trim();
|
|
100
101
|
}
|
|
101
102
|
}
|
|
102
|
-
if (ext === ".js" || ext === ".jsx") {
|
|
103
|
+
if (ext === ".js" || ext === ".jsx" || ext === ".cjs" || ext === ".mjs") {
|
|
103
104
|
try {
|
|
104
105
|
await runAsync(`node --check "${filePath}"`, baseDir, 5000);
|
|
105
106
|
return null;
|
|
106
107
|
}
|
|
107
108
|
catch (err) {
|
|
108
109
|
const stderr = err.stderr?.toString() || "";
|
|
109
|
-
const firstError = stderr.split("\n").find((line) => line.trim()) ||
|
|
110
|
-
"JavaScript syntax error";
|
|
110
|
+
const firstError = stderr.split("\n").find((line) => line.trim()) || "JavaScript syntax error";
|
|
111
111
|
return firstError.trim();
|
|
112
112
|
}
|
|
113
113
|
}
|
|
@@ -129,18 +129,25 @@ export class LintOnWritePlugin {
|
|
|
129
129
|
ctx.logger.debug("Lint passed");
|
|
130
130
|
}
|
|
131
131
|
catch (err) {
|
|
132
|
+
const stderr = (err.stderr?.toString?.() || "").trim();
|
|
133
|
+
const stdout = (err.stdout?.toString?.() || "").trim();
|
|
134
|
+
const detail = (stderr || stdout || err.message).split("\n").filter(Boolean).slice(-5).join("\n");
|
|
132
135
|
ctx.logger.warn(`Lint failed: ${err.message}`);
|
|
133
|
-
result.output += `\n\n[Project lint failed]: ${
|
|
136
|
+
result.output += `\n\n[Project lint failed]: ${detail}`;
|
|
134
137
|
}
|
|
135
138
|
}
|
|
136
|
-
async runProjectTypeCheck(
|
|
137
|
-
|
|
139
|
+
async runProjectTypeCheck(filePath, baseDir, result) {
|
|
140
|
+
// Resolve the project root from the EDITED FILE, not the agent baseDir.
|
|
141
|
+
// In a nested layout (`baseDir/proj/tsconfig.json`) the old baseDir-based
|
|
142
|
+
// lookup silently skipped the typecheck on every write — the whole
|
|
143
|
+
// session then ran with zero type feedback (observed in ses_mst2r0r5).
|
|
144
|
+
const projectRoot = findProjectRoot(filePath, baseDir, ["tsconfig.json", "package.json"]);
|
|
145
|
+
const tsconfigPath = join(projectRoot, "tsconfig.json");
|
|
138
146
|
if (!existsSync(tsconfigPath)) {
|
|
139
147
|
return;
|
|
140
148
|
}
|
|
141
149
|
const now = Date.now();
|
|
142
|
-
if (this._checkPromise &&
|
|
143
|
-
now - this._checkTimestamp < TYPE_CHECK_DEBOUNCE_MS) {
|
|
150
|
+
if (this._checkPromise && now - this._checkTimestamp < TYPE_CHECK_DEBOUNCE_MS) {
|
|
144
151
|
const error = await this._checkPromise;
|
|
145
152
|
if (error) {
|
|
146
153
|
result.output += `\n\n[Project typecheck failed]: ${error}`;
|
|
@@ -148,7 +155,7 @@ export class LintOnWritePlugin {
|
|
|
148
155
|
return;
|
|
149
156
|
}
|
|
150
157
|
this._checkTimestamp = now;
|
|
151
|
-
this._checkPromise = this.runTscCheck(
|
|
158
|
+
this._checkPromise = this.runTscCheck(projectRoot);
|
|
152
159
|
const error = await this._checkPromise;
|
|
153
160
|
if (error) {
|
|
154
161
|
result.output += `\n\n[Project typecheck failed]: ${error}`;
|
|
@@ -167,9 +174,7 @@ export class LintOnWritePlugin {
|
|
|
167
174
|
}
|
|
168
175
|
const stderr = err.stderr?.toString() || err.stdout?.toString() || "";
|
|
169
176
|
if (stderr.includes("error TS")) {
|
|
170
|
-
const firstError = stderr
|
|
171
|
-
.split("\n")
|
|
172
|
-
.find((line) => line.includes("error TS")) ||
|
|
177
|
+
const firstError = stderr.split("\n").find((line) => line.includes("error TS")) ||
|
|
173
178
|
"TypeScript type error";
|
|
174
179
|
return firstError.trim();
|
|
175
180
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export { PluginManager } from
|
|
1
|
+
export { PluginManager } from "./manager";
|
|
@@ -1,28 +1,70 @@
|
|
|
1
|
-
import { readdirSync, existsSync, statSync } from
|
|
2
|
-
import { join } from
|
|
3
|
-
import { t } from
|
|
1
|
+
import { readdirSync, existsSync, statSync } from "fs";
|
|
2
|
+
import { join, basename } from "path";
|
|
3
|
+
import { t } from "../../i18n/index";
|
|
4
|
+
import { semverGt } from "../updater/checker";
|
|
5
|
+
/**
|
|
6
|
+
* Entry file candidates for folder plugins. Looked up in order:
|
|
7
|
+
* `plugin.ts` > `plugin.js` > `index.ts` > `index.js`.
|
|
8
|
+
*/
|
|
9
|
+
const FOLDER_ENTRY_NAMES = ["plugin.ts", "plugin.js", "index.ts", "index.js"];
|
|
4
10
|
export class PluginLoader {
|
|
5
|
-
loadFromDir(dirPath, pluginManager, logger) {
|
|
11
|
+
loadFromDir(dirPath, pluginManager, logger, options) {
|
|
6
12
|
if (!existsSync(dirPath))
|
|
7
13
|
return;
|
|
8
|
-
const entries = readdirSync(dirPath);
|
|
14
|
+
const entries = readdirSync(dirPath).sort();
|
|
9
15
|
for (const entry of entries) {
|
|
10
16
|
const fullPath = join(dirPath, entry);
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
17
|
+
const stat = statSync(fullPath);
|
|
18
|
+
if (stat.isFile()) {
|
|
19
|
+
if (!entry.endsWith(".ts") && !entry.endsWith(".js"))
|
|
20
|
+
continue;
|
|
21
|
+
this.tryLoad(fullPath, entry, pluginManager, logger, options);
|
|
22
|
+
}
|
|
23
|
+
else if (stat.isDirectory()) {
|
|
24
|
+
const entryFile = this.findEntryFile(fullPath);
|
|
25
|
+
if (entryFile) {
|
|
26
|
+
this.tryLoad(entryFile, `${entry}/${basename(entryFile)}`, pluginManager, logger, options);
|
|
21
27
|
}
|
|
22
28
|
}
|
|
23
|
-
|
|
24
|
-
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
findEntryFile(dir) {
|
|
32
|
+
for (const name of FOLDER_ENTRY_NAMES) {
|
|
33
|
+
const candidate = join(dir, name);
|
|
34
|
+
if (existsSync(candidate))
|
|
35
|
+
return candidate;
|
|
36
|
+
}
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
tryLoad(fullPath, label, pluginManager, logger, options) {
|
|
40
|
+
try {
|
|
41
|
+
const mod = require(fullPath);
|
|
42
|
+
const plugin = mod.default || mod.plugin;
|
|
43
|
+
if (plugin && plugin.name) {
|
|
44
|
+
if (plugin.minMmaVersion &&
|
|
45
|
+
options?.mmaVersion &&
|
|
46
|
+
semverGt(plugin.minMmaVersion, options.mmaVersion)) {
|
|
47
|
+
logger?.warn(t("plugin.incompatible", {
|
|
48
|
+
name: plugin.name,
|
|
49
|
+
min: plugin.minMmaVersion,
|
|
50
|
+
current: options.mmaVersion,
|
|
51
|
+
}));
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
const result = pluginManager.register(plugin, options?.source);
|
|
55
|
+
if (result.status === "registered") {
|
|
56
|
+
logger?.warn(t("plugin.loaded", { name: plugin.name }));
|
|
57
|
+
}
|
|
58
|
+
else if (result.status === "skipped_older") {
|
|
59
|
+
logger?.warn(t("plugin.dedup_older", {
|
|
60
|
+
name: plugin.name,
|
|
61
|
+
version: plugin.version || "0.0.0",
|
|
62
|
+
}));
|
|
63
|
+
}
|
|
25
64
|
}
|
|
26
65
|
}
|
|
66
|
+
catch (e) {
|
|
67
|
+
logger?.warn(t("plugin.skipped", { entry: label, message: e.message }));
|
|
68
|
+
}
|
|
27
69
|
}
|
|
28
70
|
}
|