micro-models-agent 0.41.2 → 0.43.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.
Files changed (171) hide show
  1. package/bin/mma.mjs +41 -41
  2. package/dist/cli/commands.js +19 -9
  3. package/dist/cli/completer.js +37 -36
  4. package/dist/cli/index.js +2 -2
  5. package/dist/cli/main.js +23 -48
  6. package/dist/cli/repl-commands.js +12 -40
  7. package/dist/cli/repl.js +87 -217
  8. package/dist/cli/security-commands.js +7 -5
  9. package/dist/cli/setup.js +26 -8
  10. package/dist/config/config.js +5 -52
  11. package/dist/config/defaults.js +5 -29
  12. package/dist/config/experts.js +1 -1
  13. package/dist/config/index.js +3 -3
  14. package/dist/config/security.js +10 -3
  15. package/dist/core/agent-moe.js +10 -2
  16. package/dist/core/agent.js +82 -273
  17. package/dist/core/bootstrap.js +13 -80
  18. package/dist/core/index.js +2 -2
  19. package/dist/core/prompt-builder.js +2 -23
  20. package/dist/core/session-logger.js +4 -46
  21. package/dist/i18n/en.json +2 -75
  22. package/dist/i18n/ru.json +1 -74
  23. package/dist/index.js +1 -1
  24. package/dist/llm/image-utils.js +5 -4
  25. package/dist/llm/index.js +4 -4
  26. package/dist/llm/model-loader.js +6 -6
  27. package/dist/llm/openai-compat.js +34 -40
  28. package/dist/llm/orchestrator.js +29 -33
  29. package/dist/llm/response.js +9 -9
  30. package/dist/logger/app-logger.js +1 -1
  31. package/dist/logger/index.js +1 -1
  32. package/dist/main.js +648 -183
  33. package/dist/migration/backup.js +13 -13
  34. package/dist/migration/detect.js +11 -11
  35. package/dist/migration/index.js +2 -2
  36. package/dist/modules/browser/actions.js +4 -34
  37. package/dist/modules/browser/bridge-server.mjs +202 -202
  38. package/dist/modules/browser/cookie-store.js +6 -6
  39. package/dist/modules/browser/index.js +5 -7
  40. package/dist/modules/browser/module.js +7 -8
  41. package/dist/modules/browser/session.js +84 -87
  42. package/dist/modules/browser/snapshot.js +58 -92
  43. package/dist/modules/browser/types.js +1 -4
  44. package/dist/modules/certification/cli.js +4 -2
  45. package/dist/modules/certification/fact-checker.js +3 -1
  46. package/dist/modules/certification/loader.js +9 -3
  47. package/dist/modules/certification/runner.js +4 -1
  48. package/dist/modules/context/index.js +1 -1
  49. package/dist/modules/context/manager.js +86 -160
  50. package/dist/modules/execution/auditor.js +25 -177
  51. package/dist/modules/execution/module.js +544 -201
  52. package/dist/modules/execution/moe-executor.js +0 -25
  53. package/dist/modules/execution/plan-store.js +3 -1
  54. package/dist/modules/execution/plan-validator.js +10 -10
  55. package/dist/modules/execution/planner.js +1 -6
  56. package/dist/modules/execution/stuck-detector.js +10 -173
  57. package/dist/modules/execution/verifier.js +42 -86
  58. package/dist/modules/hallucination/confidence.js +1 -8
  59. package/dist/modules/hallucination/detector.js +5 -2
  60. package/dist/modules/hallucination/factual.js +64 -3
  61. package/dist/modules/hallucination/index.js +1 -1
  62. package/dist/modules/hallucination/js-identifiers.js +0 -190
  63. package/dist/modules/hallucination/llm-judge.js +3 -1
  64. package/dist/modules/indexer/cache.js +7 -9
  65. package/dist/modules/indexer/index.js +3 -3
  66. package/dist/modules/indexer/module.js +42 -95
  67. package/dist/modules/indexer/walker.js +17 -17
  68. package/dist/modules/lsp/client.js +31 -74
  69. package/dist/modules/lsp/config.js +33 -87
  70. package/dist/modules/lsp/index.js +3 -3
  71. package/dist/modules/lsp/module.js +21 -185
  72. package/dist/modules/mcp/module.js +6 -2
  73. package/dist/modules/memory/index.js +1 -1
  74. package/dist/modules/memory/module.js +23 -71
  75. package/dist/modules/memory/search.js +9 -11
  76. package/dist/modules/memory/store.js +13 -13
  77. package/dist/modules/pipelines/engine.js +10 -10
  78. package/dist/modules/pipelines/index.js +3 -3
  79. package/dist/modules/pipelines/parser.js +14 -17
  80. package/dist/modules/pipelines/template.js +1 -1
  81. package/dist/modules/plugins/builtin/lint-on-write.js +16 -21
  82. package/dist/modules/plugins/builtin/notify.js +2 -3
  83. package/dist/modules/plugins/index.js +1 -1
  84. package/dist/modules/plugins/loader.js +17 -59
  85. package/dist/modules/plugins/manager.js +17 -73
  86. package/dist/modules/processes/index.js +1 -1
  87. package/dist/modules/processes/registry.js +46 -135
  88. package/dist/modules/registry.js +2 -4
  89. package/dist/modules/security/audit-notifier.js +39 -39
  90. package/dist/modules/security/command-validator.js +8 -2
  91. package/dist/modules/security/data-sanitizer.js +9 -1
  92. package/dist/modules/security/encryption.js +56 -58
  93. package/dist/modules/security/network-validator.js +9 -1
  94. package/dist/modules/security/path-validator.js +3 -1
  95. package/dist/modules/security/security-policies.js +19 -3
  96. package/dist/modules/security/session-encryption.js +1 -1
  97. package/dist/modules/security/session-isolation.js +8 -8
  98. package/dist/modules/session/index.js +3 -3
  99. package/dist/modules/session/module.js +5 -5
  100. package/dist/modules/session/store.js +9 -3
  101. package/dist/modules/skills/module.js +2 -1
  102. package/dist/modules/updater/checker.js +6 -70
  103. package/dist/modules/updater/index.js +1 -2
  104. package/dist/modules/user-profile/compressor.js +2 -2
  105. package/dist/modules/user-profile/index.js +1 -1
  106. package/dist/modules/user-profile/profile.js +9 -9
  107. package/dist/tools/attach-image.js +1 -1
  108. package/dist/tools/bash.js +19 -178
  109. package/dist/tools/browser.js +29 -46
  110. package/dist/tools/executor.js +5 -4
  111. package/dist/tools/file-info.js +12 -13
  112. package/dist/tools/filter-tools.js +2 -9
  113. package/dist/tools/glob-tool.js +11 -11
  114. package/dist/tools/grep-tool.js +3 -1
  115. package/dist/tools/index.js +2 -13
  116. package/dist/tools/list-dir.js +17 -18
  117. package/dist/tools/load-skill.js +3 -1
  118. package/dist/tools/path-utils.js +4 -4
  119. package/dist/tools/pipeline-run.js +25 -25
  120. package/dist/tools/process-kill.js +11 -11
  121. package/dist/tools/process-list.js +22 -20
  122. package/dist/tools/process-log.js +18 -22
  123. package/dist/tools/question.js +3 -1
  124. package/dist/tools/read-file.js +2 -10
  125. package/dist/tools/recall.js +37 -44
  126. package/dist/tools/registry.js +4 -15
  127. package/dist/tools/remember.js +29 -29
  128. package/dist/tools/scope-check.js +9 -9
  129. package/dist/tools/subagent.js +9 -54
  130. package/dist/tools/user-input.js +1 -1
  131. package/dist/tools/web-browse.js +3 -3
  132. package/dist/tools/web-fetch.js +3 -3
  133. package/dist/tools/web-search.js +3 -3
  134. package/dist/tools/write-file.js +3 -1
  135. package/dist/ui/box.js +5 -1
  136. package/dist/ui/index.js +6 -6
  137. package/dist/ui/md-formatter.js +33 -33
  138. package/dist/ui/output.js +5 -5
  139. package/dist/ui/renderer.js +10 -15
  140. package/dist/ui/table.js +1 -1
  141. package/package.json +48 -48
  142. package/dist/cli/plugin-commands.js +0 -36
  143. package/dist/cli/run-result.js +0 -22
  144. package/dist/core/version.js +0 -24
  145. package/dist/modules/artifacts/store.js +0 -61
  146. package/dist/modules/browser/bridge-client.js +0 -199
  147. package/dist/modules/browser/bridge-path.js +0 -10
  148. package/dist/modules/browser/driver.js +0 -136
  149. package/dist/modules/context/chunk-query.js +0 -100
  150. package/dist/modules/context/fact-extractor.js +0 -162
  151. package/dist/modules/context/history.js +0 -15
  152. package/dist/modules/execution/audit-runners.js +0 -152
  153. package/dist/modules/execution/execution-plugin.js +0 -272
  154. package/dist/modules/execution/plan-tool.js +0 -508
  155. package/dist/modules/execution/windows-commands.js +0 -41
  156. package/dist/modules/indexer/project-profile.js +0 -183
  157. package/dist/modules/lsp/check-tool.js +0 -58
  158. package/dist/modules/lsp/command.js +0 -60
  159. package/dist/modules/lsp/probe.js +0 -76
  160. package/dist/modules/lsp/project-root.js +0 -32
  161. package/dist/modules/lsp/startup-check.js +0 -141
  162. package/dist/modules/processes/detect.js +0 -34
  163. package/dist/modules/skills/matcher.js +0 -27
  164. package/dist/modules/updater/module.js +0 -116
  165. package/dist/tools/chunk-query.js +0 -99
  166. package/dist/tools/download-file.js +0 -116
  167. package/dist/tools/enable-tools.js +0 -58
  168. package/dist/tools/hidden-tools-block.js +0 -37
  169. package/dist/ui/line-editor.js +0 -703
  170. package/dist/ui/line-math.js +0 -69
  171. package/dist/ui/plan-view.js +0 -103
@@ -7,7 +7,9 @@ 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) ? schema.required : [];
10
+ const required = Array.isArray(schema.required)
11
+ ? schema.required
12
+ : [];
11
13
  return {
12
14
  type: "object",
13
15
  properties: schema.properties,
@@ -116,7 +118,9 @@ export class MCPModule {
116
118
  const result = await client.callTool(d.toolName, args);
117
119
  return {
118
120
  success: true,
119
- output: typeof result === "string" ? result : JSON.stringify(result, null, 2),
121
+ output: typeof result === "string"
122
+ ? result
123
+ : JSON.stringify(result, null, 2),
120
124
  };
121
125
  }
122
126
  catch (e) {
@@ -1 +1 @@
1
- export { MemoryStore } from "./store";
1
+ export { MemoryStore } from './store';
@@ -1,92 +1,44 @@
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"];
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';
7
6
  export class MemoryModule {
8
- name = "memory";
7
+ name = 'memory';
9
8
  store;
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
- }
9
+ constructor(memoryDir) {
10
+ const dir = memoryDir || join(homedir(), '.mma', 'memory');
11
+ this.store = new MemoryStore(dir);
18
12
  }
19
13
  getSystemPromptBlock() {
20
- const parts = [];
21
14
  const prefs = this.store.getPreferences();
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)
15
+ if (Object.keys(prefs).length === 0)
32
16
  return null;
33
- const content = parts.join("\n\n");
17
+ const prefStr = Object.entries(prefs)
18
+ .map(([k, v]) => `${k}=${v}`)
19
+ .join(', ');
34
20
  return {
35
- content,
36
- priority: "normal",
21
+ content: `User preferences: ${prefStr}`,
22
+ priority: 'normal',
37
23
  essential: false,
38
- estimatedTokens: Math.ceil(content.length / 4) + 10,
24
+ estimatedTokens: Math.ceil(prefStr.length / 4) + 10,
39
25
  };
40
26
  }
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
- }
70
27
  getToolDefinitions() {
71
28
  return [rememberTool, recallTool];
72
29
  }
73
30
  getPlugin() {
74
31
  return {
75
- name: "memory",
32
+ name: 'memory',
76
33
  isBuiltin: true,
77
34
  onBuildPrompt: () => {
78
- const parts = [];
79
35
  const prefs = this.store.getPreferences();
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;
36
+ if (Object.keys(prefs).length === 0)
37
+ return null;
38
+ const prefStr = Object.entries(prefs)
39
+ .map(([k, v]) => `${k}=${v}`)
40
+ .join(', ');
41
+ return `User preferences: ${prefStr}`;
90
42
  },
91
43
  };
92
44
  }
@@ -1,6 +1,6 @@
1
- import { readFileSync, existsSync } from "fs";
2
- import { join } from "path";
3
- const MEMORY_FILES = ["conventions", "decisions", "errors", "facts"];
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, "utf-8");
17
- const lines = content.split("\n");
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,20 +22,18 @@ export class MemorySearch {
22
22
  }
23
23
  }
24
24
  // Search preferences.json
25
- const prefsPath = join(this.memoryDir, "preferences.json");
25
+ const prefsPath = join(this.memoryDir, 'preferences.json');
26
26
  if (existsSync(prefsPath)) {
27
27
  try {
28
- const prefs = JSON.parse(readFileSync(prefsPath, "utf-8"));
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: "preferences", match: `${key} = ${value}` });
32
+ results.push({ file: 'preferences', match: `${key} = ${value}` });
33
33
  }
34
34
  }
35
35
  }
36
- catch {
37
- /* skip */
38
- }
36
+ catch { /* skip */ }
39
37
  }
40
38
  return results;
41
39
  }
@@ -1,7 +1,7 @@
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"];
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`, "utf-8");
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, "utf-8");
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("T", " ").slice(0, 19);
30
+ const timestamp = new Date().toISOString().replace('T', ' ').slice(0, 19);
31
31
  const formatted = `- **${timestamp}** — ${entry}\n`;
32
- appendFileSync(path, formatted, "utf-8");
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, "preferences.json");
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, "utf-8"));
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), "utf-8");
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), "utf-8");
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 "../../i18n/index";
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("pipeline.circular", { stepId }));
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) === "running")
36
+ if (this.stepStatus.get(s.id) === 'running')
37
37
  return false;
38
- if (this.stepStatus.get(s.id) === "failed")
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) || "pending";
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 === "done" || s === "skipped";
54
+ return s === 'done' || s === 'skipped';
55
55
  });
56
56
  }
57
57
  reset() {
@@ -1,3 +1,3 @@
1
- export { PipelineParser } from "./parser";
2
- export { PipelineEngine } from "./engine";
3
- export { TemplateEngine } from "./template";
1
+ export { PipelineParser } from './parser';
2
+ export { PipelineEngine } from './engine';
3
+ export { TemplateEngine } from './template';
@@ -1,23 +1,23 @@
1
- import { t } from "../../i18n/index";
1
+ import { t } from '../../i18n/index';
2
2
  export class PipelineParser {
3
3
  static parse(yaml) {
4
- const lines = yaml.split("\n").filter((l) => l.trim());
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(" ") && !line.startsWith(" -")) {
10
- const [key, ...valParts] = line.split(":");
11
- const val = valParts.join(":").trim();
12
- if (key === "steps") {
13
- currentSection = "steps";
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 === "steps") {
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,15 +28,12 @@ export class PipelineParser {
28
28
  if (kvMatch) {
29
29
  const key = kvMatch[1];
30
30
  let val = kvMatch[2].trim();
31
- if (val === "true")
31
+ if (val === 'true')
32
32
  val = true;
33
- else if (val === "false")
33
+ else if (val === 'false')
34
34
  val = false;
35
- else if (val.startsWith("[") && val.endsWith("]")) {
36
- val = val
37
- .slice(1, -1)
38
- .split(",")
39
- .map((s) => s.trim().replace(/^['"]|['"]$/g, ""));
35
+ else if (val.startsWith('[') && val.endsWith(']')) {
36
+ val = val.slice(1, -1).split(',').map((s) => s.trim().replace(/^['"]|['"]$/g, ''));
40
37
  }
41
38
  lastStep[key] = val;
42
39
  }
@@ -44,11 +41,11 @@ export class PipelineParser {
44
41
  }
45
42
  }
46
43
  if (!result.name || currentSteps.length === 0) {
47
- throw new Error(t("pipeline.invalid"));
44
+ throw new Error(t('pipeline.invalid'));
48
45
  }
49
46
  for (const step of currentSteps) {
50
47
  if (!step.id || !step.agent || !step.prompt) {
51
- throw new Error(t("pipeline.step_missing_fields", { step: JSON.stringify(step) }));
48
+ throw new Error(t('pipeline.step_missing_fields', { step: JSON.stringify(step) }));
52
49
  }
53
50
  }
54
51
  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}\\}`, "g"), val);
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,7 +2,6 @@ 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";
6
5
  const TYPE_CHECK_DEBOUNCE_MS = 2000;
7
6
  // Per-file syntax checks are cached by (path, content hash): re-writing a file
8
7
  // with identical content skips the (expensive) check. Content changes → cache
@@ -41,7 +40,6 @@ function getWinDecoder() {
41
40
  export class LintOnWritePlugin {
42
41
  name = "lint-on-write";
43
42
  priority = 10;
44
- isBuiltin = true;
45
43
  _checkPromise = null;
46
44
  _checkTimestamp = 0;
47
45
  async onAfterTool(ctx, call, result) {
@@ -64,10 +62,10 @@ export class LintOnWritePlugin {
64
62
  return;
65
63
  }
66
64
  await this.runProjectLint(ctx, result);
67
- await this.runProjectTypeCheck(fullPath, ctx.baseDir, result);
65
+ await this.runProjectTypeCheck(ctx, result);
68
66
  }
69
67
  async checkSyntax(filePath, ext, baseDir) {
70
- if (ext === ".ts" || ext === ".tsx" || ext === ".cts" || ext === ".mts") {
68
+ if (ext === ".ts" || ext === ".tsx") {
71
69
  // Syntax-only check via bun's parser (~200ms) instead of `npx tsc`
72
70
  // (~3-5s per write). Full type errors are still surfaced by the
73
71
  // debounced project typecheck below when a tsconfig exists.
@@ -95,19 +93,21 @@ export class LintOnWritePlugin {
95
93
  return null;
96
94
  }
97
95
  const stderr = err.stderr?.toString() || err.stdout?.toString() || "";
98
- const firstError = stderr.split("\n").find((line) => line.trim()) || "TypeScript syntax error";
96
+ const firstError = stderr.split("\n").find((line) => line.trim()) ||
97
+ "TypeScript syntax error";
99
98
  syntaxCache.set(filePath, { hash, error: firstError.trim() });
100
99
  return firstError.trim();
101
100
  }
102
101
  }
103
- if (ext === ".js" || ext === ".jsx" || ext === ".cjs" || ext === ".mjs") {
102
+ if (ext === ".js" || ext === ".jsx") {
104
103
  try {
105
104
  await runAsync(`node --check "${filePath}"`, baseDir, 5000);
106
105
  return null;
107
106
  }
108
107
  catch (err) {
109
108
  const stderr = err.stderr?.toString() || "";
110
- const firstError = stderr.split("\n").find((line) => line.trim()) || "JavaScript syntax error";
109
+ const firstError = stderr.split("\n").find((line) => line.trim()) ||
110
+ "JavaScript syntax error";
111
111
  return firstError.trim();
112
112
  }
113
113
  }
@@ -129,25 +129,18 @@ 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");
135
132
  ctx.logger.warn(`Lint failed: ${err.message}`);
136
- result.output += `\n\n[Project lint failed]: ${detail}`;
133
+ result.output += `\n\n[Project lint failed]: ${err.message}`;
137
134
  }
138
135
  }
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");
136
+ async runProjectTypeCheck(ctx, result) {
137
+ const tsconfigPath = join(ctx.baseDir, "tsconfig.json");
146
138
  if (!existsSync(tsconfigPath)) {
147
139
  return;
148
140
  }
149
141
  const now = Date.now();
150
- if (this._checkPromise && now - this._checkTimestamp < TYPE_CHECK_DEBOUNCE_MS) {
142
+ if (this._checkPromise &&
143
+ now - this._checkTimestamp < TYPE_CHECK_DEBOUNCE_MS) {
151
144
  const error = await this._checkPromise;
152
145
  if (error) {
153
146
  result.output += `\n\n[Project typecheck failed]: ${error}`;
@@ -155,7 +148,7 @@ export class LintOnWritePlugin {
155
148
  return;
156
149
  }
157
150
  this._checkTimestamp = now;
158
- this._checkPromise = this.runTscCheck(projectRoot);
151
+ this._checkPromise = this.runTscCheck(ctx.baseDir);
159
152
  const error = await this._checkPromise;
160
153
  if (error) {
161
154
  result.output += `\n\n[Project typecheck failed]: ${error}`;
@@ -174,7 +167,9 @@ export class LintOnWritePlugin {
174
167
  }
175
168
  const stderr = err.stderr?.toString() || err.stdout?.toString() || "";
176
169
  if (stderr.includes("error TS")) {
177
- const firstError = stderr.split("\n").find((line) => line.includes("error TS")) ||
170
+ const firstError = stderr
171
+ .split("\n")
172
+ .find((line) => line.includes("error TS")) ||
178
173
  "TypeScript type error";
179
174
  return firstError.trim();
180
175
  }
@@ -1,9 +1,8 @@
1
1
  export class NotifyPlugin {
2
- name = "notify";
2
+ name = 'notify';
3
3
  priority = 5;
4
- isBuiltin = true;
5
4
  onSessionEnd(ctx) {
6
- ctx.logger.info("Session ended");
5
+ ctx.logger.info('Session ended');
7
6
  }
8
7
  }
9
8
  export const plugin = new NotifyPlugin();
@@ -1 +1 @@
1
- export { PluginManager } from "./manager";
1
+ export { PluginManager } from './manager';
@@ -1,70 +1,28 @@
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"];
1
+ import { readdirSync, existsSync, statSync } from 'fs';
2
+ import { join } from 'path';
3
+ import { t } from '../../i18n/index';
10
4
  export class PluginLoader {
11
- loadFromDir(dirPath, pluginManager, logger, options) {
5
+ loadFromDir(dirPath, pluginManager, logger) {
12
6
  if (!existsSync(dirPath))
13
7
  return;
14
- const entries = readdirSync(dirPath).sort();
8
+ const entries = readdirSync(dirPath);
15
9
  for (const entry of entries) {
16
10
  const fullPath = join(dirPath, entry);
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);
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 }));
27
21
  }
28
22
  }
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
- }
23
+ catch (e) {
24
+ logger?.warn(t('plugin.skipped', { entry, message: e.message }));
64
25
  }
65
26
  }
66
- catch (e) {
67
- logger?.warn(t("plugin.skipped", { entry: label, message: e.message }));
68
- }
69
27
  }
70
28
  }