micro-models-agent 0.41.2 → 0.42.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 +299 -55
  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
@@ -1,20 +1,19 @@
1
- import { readdirSync, statSync, existsSync } from "fs";
2
- import { resolve } from "path";
3
- import { t } from "../i18n/index";
4
- import { isPathInScope } from "../modules/security/path-validator";
5
- import { safeResolvePath } from "./path-utils";
6
- import { MAX_PREVIEW_LINES } from "./preview";
1
+ import { readdirSync, statSync, existsSync } from 'fs';
2
+ import { resolve } from 'path';
3
+ import { t } from '../i18n/index';
4
+ import { isPathInScope } from '../modules/security/path-validator';
5
+ import { safeResolvePath } from './path-utils';
6
+ import { MAX_PREVIEW_LINES } from './preview';
7
7
  export const listDirTool = {
8
- name: "list_dir",
8
+ name: 'list_dir',
9
9
  description: `List files and directories in a given path. Shows up to ${MAX_PREVIEW_LINES} entries by default.`,
10
- tags: ["file"],
11
- boundedOutput: true,
10
+ tags: ['file'],
12
11
  parameters: {
13
- type: "object",
12
+ type: 'object',
14
13
  properties: {
15
- path: { type: "string", description: "Directory path" },
14
+ path: { type: 'string', description: 'Directory path' },
16
15
  },
17
- required: ["path"],
16
+ required: ['path'],
18
17
  },
19
18
  handler: async (ctx, args) => {
20
19
  const path = String(args.path);
@@ -24,25 +23,25 @@ export const listDirTool = {
24
23
  if (!scopeCheck.allowed) {
25
24
  return {
26
25
  success: false,
27
- output: t("file.path_not_allowed", {
26
+ output: t('file.path_not_allowed', {
28
27
  path: `${path} — ${scopeCheck.reason}`,
29
28
  }),
30
29
  };
31
30
  }
32
31
  if (!existsSync(resolved)) {
33
- return { success: false, output: t("file.dir_notfound", { path }) };
32
+ return { success: false, output: t('file.dir_notfound', { path }) };
34
33
  }
35
34
  const entries = readdirSync(resolved);
36
- const lines = entries.map((e) => {
35
+ const lines = entries.map(e => {
37
36
  const full = resolve(resolved, e);
38
37
  return statSync(full).isDirectory() ? `${e}/` : e;
39
38
  });
40
39
  if (lines.length === 0) {
41
- return { success: true, output: t("file.empty") };
40
+ return { success: true, output: t('file.empty') };
42
41
  }
43
42
  const truncated = lines.length > MAX_PREVIEW_LINES
44
43
  ? `\n... (${lines.length - MAX_PREVIEW_LINES} more entries)`
45
- : "";
46
- return { success: true, output: lines.slice(0, MAX_PREVIEW_LINES).join("\n") + truncated };
44
+ : '';
45
+ return { success: true, output: lines.slice(0, MAX_PREVIEW_LINES).join('\n') + truncated };
47
46
  },
48
47
  };
@@ -28,7 +28,9 @@ export function createLoadSkillTool(skillsModule) {
28
28
  return { success: false, output: result.message + hint };
29
29
  }
30
30
  const budget = skillsModule.getBudget();
31
- const tokens = result.skill ? Math.ceil(result.skill.content.length / 4) : 0;
31
+ const tokens = result.skill
32
+ ? Math.ceil(result.skill.content.length / 4)
33
+ : 0;
32
34
  return {
33
35
  success: true,
34
36
  output: t("tool.skill_budget", {
@@ -1,5 +1,5 @@
1
- import { resolve, normalize, dirname, basename, sep } from "path";
2
- import { existsSync } from "fs";
1
+ import { resolve, normalize, dirname, basename, sep } from 'path';
2
+ import { existsSync } from 'fs';
3
3
  /**
4
4
  * Resolve a user-provided path against baseDir.
5
5
  *
@@ -13,7 +13,7 @@ import { existsSync } from "fs";
13
13
  */
14
14
  export function safeResolvePath(baseDir, userPath) {
15
15
  const norm = normalize(userPath);
16
- const stripped = norm.replace(/^[/\\]/, "");
16
+ const stripped = norm.replace(/^[/\\]/, '');
17
17
  const resolved = resolve(baseDir, stripped);
18
18
  // Fast path: if it exists (or parent does for new files), return it
19
19
  if (existsSync(resolved) || existsSync(dirname(resolved)))
@@ -31,7 +31,7 @@ export function safeResolvePath(baseDir, userPath) {
31
31
  while (idx >= 0) {
32
32
  const afterIdx = idx + name.length;
33
33
  const afterChar = stripped[afterIdx];
34
- if (afterChar && afterChar !== "\\" && afterChar !== "/") {
34
+ if (afterChar && afterChar !== '\\' && afterChar !== '/') {
35
35
  const fixed = stripped.slice(0, afterIdx) + sep + stripped.slice(afterIdx);
36
36
  const fixedResolved = resolve(baseDir, normalize(fixed));
37
37
  if (existsSync(fixedResolved) || existsSync(dirname(fixedResolved))) {
@@ -1,13 +1,13 @@
1
- import { t } from "../i18n/index";
2
- import { PipelineEngine } from "../modules/pipelines/engine";
3
- import { PipelineParser } from "../modules/pipelines/parser";
4
- import { TemplateEngine } from "../modules/pipelines/template";
5
- import { Agent } from "../core/agent";
6
- import { ContextManager } from "../modules/context/manager";
7
- import { PluginManager } from "../modules/plugins/manager";
8
- import { HallucinationDetector } from "../modules/hallucination/detector";
9
- import { logSecurityBlock } from "../modules/security/audit-log";
10
- import { getSessionSecurityConfig } from "../modules/security/session-isolation";
1
+ import { t } from '../i18n/index';
2
+ import { PipelineEngine } from '../modules/pipelines/engine';
3
+ import { PipelineParser } from '../modules/pipelines/parser';
4
+ import { TemplateEngine } from '../modules/pipelines/template';
5
+ import { Agent } from '../core/agent';
6
+ import { ContextManager } from '../modules/context/manager';
7
+ import { PluginManager } from '../modules/plugins/manager';
8
+ import { HallucinationDetector } from '../modules/hallucination/detector';
9
+ import { logSecurityBlock } from '../modules/security/audit-log';
10
+ import { getSessionSecurityConfig } from '../modules/security/session-isolation';
11
11
  const engine = new PipelineEngine();
12
12
  const MAX_CONCURRENT = 3;
13
13
  const MAX_ATTEMPTS = 3;
@@ -52,29 +52,29 @@ async function runStep(ctx, step, params, outputs) {
52
52
  return { ok: false, output: "", error: lastError };
53
53
  }
54
54
  export const pipelineRunTool = {
55
- name: "pipeline_run",
56
- description: "Run a named pipeline with YAML definition. Creates a DAG of sub-agents that execute in dependency order.",
57
- tags: ["shell", "code"],
55
+ name: 'pipeline_run',
56
+ description: 'Run a named pipeline with YAML definition. Creates a DAG of sub-agents that execute in dependency order.',
57
+ tags: ['shell', 'code'],
58
58
  parameters: {
59
- type: "object",
59
+ type: 'object',
60
60
  properties: {
61
- name: { type: "string", description: "Pipeline name" },
62
- yaml: { type: "string", description: "Pipeline YAML definition with steps" },
63
- params: { type: "object", description: "Template params for {key} placeholders" },
61
+ name: { type: 'string', description: 'Pipeline name' },
62
+ yaml: { type: 'string', description: 'Pipeline YAML definition with steps' },
63
+ params: { type: 'object', description: 'Template params for {key} placeholders' },
64
64
  },
65
- required: ["name", "yaml"],
65
+ required: ['name', 'yaml'],
66
66
  },
67
67
  handler: async (ctx, args) => {
68
- const name = String(args.name || "");
69
- const yaml = String(args.yaml || "");
68
+ const name = String(args.name || '');
69
+ const yaml = String(args.yaml || '');
70
70
  const params = args.params || {};
71
71
  if (!yaml) {
72
- return { success: false, output: t("pipeline.invalid") };
72
+ return { success: false, output: t('pipeline.invalid') };
73
73
  }
74
74
  if (!ctx.llmProvider || !ctx.toolExecutor) {
75
75
  return {
76
76
  success: false,
77
- output: "Pipeline cannot run: missing llmProvider or toolExecutor in context",
77
+ output: 'Pipeline cannot run: missing llmProvider or toolExecutor in context',
78
78
  };
79
79
  }
80
80
  const securityConfig = ctx.sessionContext
@@ -104,7 +104,7 @@ export const pipelineRunTool = {
104
104
  if (ready.length === 0) {
105
105
  if (failed.size > 0)
106
106
  break;
107
- throw new Error(t("pipeline.circular", { stepId: order.join(", ") }));
107
+ throw new Error(t('pipeline.circular', { stepId: order.join(', ') }));
108
108
  }
109
109
  const batch = ready.slice(0, MAX_CONCURRENT);
110
110
  const results = await Promise.all(batch.map((step) => runStep(ctx, step, params, outputs)));
@@ -114,12 +114,12 @@ export const pipelineRunTool = {
114
114
  if (result.ok) {
115
115
  completed.add(step.id);
116
116
  outputs[step.id] = { output: result.output };
117
- engine.setStepStatus(step.id, "done");
117
+ engine.setStepStatus(step.id, 'done');
118
118
  logs.push(` ✓ ${step.id}: ${result.output.split("\n")[0]}`);
119
119
  }
120
120
  else {
121
121
  failed.add(step.id);
122
- engine.setStepStatus(step.id, "failed");
122
+ engine.setStepStatus(step.id, 'failed');
123
123
  logs.push(` ✗ ${step.id}: ${result.error}`);
124
124
  }
125
125
  }
@@ -1,29 +1,29 @@
1
- import { processRegistry } from "../modules/processes";
2
- import { t } from "../i18n/index";
1
+ import { processRegistry } from '../modules/processes';
2
+ import { t } from '../i18n/index';
3
3
  export const processKillTool = {
4
- name: "process_kill",
5
- description: "Stop a background process started via bash (dev server, watcher). Kills the whole process tree (children included). Use the id returned by bash or process_list.",
6
- tags: ["shell"],
4
+ name: 'process_kill',
5
+ description: 'Stop a background process started via bash (dev server, watcher). Kills the whole process tree (children included). Use the id returned by bash or process_list.',
6
+ tags: ['shell'],
7
7
  parameters: {
8
- type: "object",
8
+ type: 'object',
9
9
  properties: {
10
- id: { type: "string", description: "Process id from bash output or process_list" },
10
+ id: { type: 'string', description: 'Process id from bash output or process_list' },
11
11
  },
12
- required: ["id"],
12
+ required: ['id'],
13
13
  },
14
14
  handler: async (ctx, args) => {
15
15
  const id = String(args.id);
16
16
  const entry = processRegistry.get(id);
17
17
  if (!entry) {
18
- return { success: false, output: t("proc.not_found", { id }) };
18
+ return { success: false, output: t('proc.not_found', { id }) };
19
19
  }
20
20
  const killed = processRegistry.kill(id);
21
21
  if (!killed) {
22
- return { success: false, output: t("proc.kill_failed", { id }) };
22
+ return { success: false, output: t('proc.kill_failed', { id }) };
23
23
  }
24
24
  return {
25
25
  success: true,
26
- output: t("proc.killed", { id, pid: entry.pid }),
26
+ output: t('proc.killed', { id, pid: entry.pid }),
27
27
  };
28
28
  },
29
29
  };
@@ -1,36 +1,38 @@
1
- import { processRegistry } from "../modules/processes";
2
- import { t } from "../i18n/index";
1
+ import { processRegistry } from '../modules/processes';
2
+ import { t } from '../i18n/index';
3
3
  export const processListTool = {
4
- name: "process_list",
5
- description: "List background processes started via the bash tool (dev servers, watchers, long-running commands). Shows id, pid, command, status, and recent output. Use with process_log and process_kill to inspect or stop them.",
6
- tags: ["shell"],
4
+ name: 'process_list',
5
+ description: 'List background processes started via the bash tool (dev servers, watchers, long-running commands). Shows id, pid, command, status, and recent output. Use with process_log and process_kill to inspect or stop them.',
6
+ tags: ['shell'],
7
7
  parameters: {
8
- type: "object",
8
+ type: 'object',
9
9
  properties: {},
10
10
  },
11
11
  handler: async (ctx) => {
12
12
  const list = processRegistry.list(ctx.sessionId);
13
13
  if (list.length === 0) {
14
- return { success: true, output: t("proc.none") };
14
+ return { success: true, output: t('proc.none') };
15
15
  }
16
16
  const statusLabel = (status) => {
17
- if (status === "running")
18
- return t("proc.status_running");
19
- if (status === "exited")
20
- return t("proc.status_exited");
21
- return t("proc.status_killed");
17
+ if (status === 'running')
18
+ return t('proc.status_running');
19
+ if (status === 'exited')
20
+ return t('proc.status_exited');
21
+ return t('proc.status_killed');
22
22
  };
23
- const lines = [`${t("proc.list_header")} (${list.length}):`];
23
+ const lines = [`${t('proc.list_header')} (${list.length}):`];
24
24
  for (const entry of list) {
25
- const tail = entry.log.length > 0 ? entry.log[entry.log.length - 1] : "";
26
- const detail = tail ? ` — ${tail.slice(0, 80)}${tail.length > 80 ? "…" : ""}` : "";
25
+ const tail = entry.log.length > 0 ? entry.log[entry.log.length - 1] : '';
26
+ const detail = tail
27
+ ? ` — ${tail.slice(0, 80)}${tail.length > 80 ? '…' : ''}`
28
+ : '';
27
29
  lines.push(` ${entry.id} PID ${entry.pid} ${statusLabel(entry.status)} ${entry.command}${detail}`);
28
30
  }
29
- lines.push(`\n${t("proc.hint", {
30
- list: "process_list",
31
- log: "process_log",
32
- kill: "process_kill",
31
+ lines.push(`\n${t('proc.hint', {
32
+ list: 'process_list',
33
+ log: 'process_log',
34
+ kill: 'process_kill',
33
35
  })}`);
34
- return { success: true, output: lines.join("\n") };
36
+ return { success: true, output: lines.join('\n') };
35
37
  },
36
38
  };
@@ -1,45 +1,41 @@
1
- import { processRegistry } from "../modules/processes";
2
- import { t } from "../i18n/index";
3
- import { MAX_PREVIEW_LINES } from "./preview";
1
+ import { processRegistry } from '../modules/processes';
2
+ import { t } from '../i18n/index';
3
+ import { MAX_PREVIEW_LINES } from './preview';
4
4
  const DEFAULT_TAIL = MAX_PREVIEW_LINES;
5
5
  export const processLogTool = {
6
- name: "process_log",
6
+ name: 'process_log',
7
7
  description: `Show the buffered output of a background process started via bash. Shows the last ${DEFAULT_TAIL} lines by default. Use after starting a dev server to verify it came up without errors, and while it runs to check its state.`,
8
- tags: ["shell"],
9
- boundedOutput: true,
8
+ tags: ['shell'],
10
9
  parameters: {
11
- type: "object",
10
+ type: 'object',
12
11
  properties: {
13
- id: { type: "string", description: "Process id from bash output or process_list" },
14
- tail: {
15
- type: "number",
16
- description: `Number of trailing lines to show (default: ${DEFAULT_TAIL}, max 300)`,
17
- },
12
+ id: { type: 'string', description: 'Process id from bash output or process_list' },
13
+ tail: { type: 'number', description: `Number of trailing lines to show (default: ${DEFAULT_TAIL}, max 300)` },
18
14
  },
19
- required: ["id"],
15
+ required: ['id'],
20
16
  },
21
17
  handler: async (ctx, args) => {
22
18
  const id = String(args.id);
23
19
  const entry = processRegistry.get(id);
24
20
  if (!entry) {
25
- return { success: false, output: t("proc.not_found", { id }) };
21
+ return { success: false, output: t('proc.not_found', { id }) };
26
22
  }
27
- const status = entry.status === "running"
28
- ? t("proc.status_running")
29
- : entry.status === "exited"
30
- ? t("proc.status_exited")
31
- : t("proc.status_killed");
32
- const tail = typeof args.tail === "number" ? args.tail : DEFAULT_TAIL;
23
+ const status = entry.status === 'running'
24
+ ? t('proc.status_running')
25
+ : entry.status === 'exited'
26
+ ? t('proc.status_exited')
27
+ : t('proc.status_killed');
28
+ const tail = typeof args.tail === 'number' ? args.tail : DEFAULT_TAIL;
33
29
  const log = processRegistry.getLog(id, tail);
34
30
  if (!log) {
35
31
  return {
36
32
  success: true,
37
- output: `${t("proc.log_header", { id, status })} ${t("proc.log_empty")}`,
33
+ output: `${t('proc.log_header', { id, status })} ${t('proc.log_empty')}`,
38
34
  };
39
35
  }
40
36
  return {
41
37
  success: true,
42
- output: `${t("proc.log_header", { id, status })}\n${log}`,
38
+ output: `${t('proc.log_header', { id, status })}\n${log}`,
43
39
  };
44
40
  },
45
41
  };
@@ -28,7 +28,9 @@ function normalizeQuestions(args) {
28
28
  const q = item;
29
29
  if (typeof q.question !== "string" || !q.question.trim())
30
30
  continue;
31
- const options = Array.isArray(q.options) ? q.options.filter(isOption) : undefined;
31
+ const options = Array.isArray(q.options)
32
+ ? q.options.filter(isOption)
33
+ : undefined;
32
34
  // An explicitly provided options array with no valid entries is a
33
35
  // malformed call — skip the question instead of blocking on stdin.
34
36
  if (Array.isArray(q.options) && options.length === 0)
@@ -5,20 +5,12 @@ import { isPathInScope } from "../modules/security/path-validator";
5
5
  import { DEFAULT_SECURITY_CONFIG } from "../config/security";
6
6
  import { logSecurityBlock } from "../modules/security/audit-log";
7
7
  import { safeResolvePath } from "./path-utils";
8
- /**
9
- * Default lines per read_file call. Deliberately much larger than
10
- * MAX_PREVIEW_LINES (15): a 15-line default forces the model to issue 10-17
11
- * calls per file, which floods the context and drives compaction every 15
12
- * iterations (observed: 96 read_file calls, App.tsx read 23x, then 11
13
- * compactions in ~20 min that deleted the user's task mid-turn). 300 lines
14
- * covers typical component files in one call while still capping output.
15
- */
16
- const DEFAULT_LIMIT = 300;
8
+ import { MAX_PREVIEW_LINES } from "./preview";
9
+ const DEFAULT_LIMIT = MAX_PREVIEW_LINES;
17
10
  export const readFileTool = {
18
11
  name: "read_file",
19
12
  description: `Read a file from the filesystem. Reads up to ${DEFAULT_LIMIT} lines at a time by default; use offset to page through large files.`,
20
13
  tags: ["file", "code"],
21
- boundedOutput: true,
22
14
  parameters: {
23
15
  type: "object",
24
16
  properties: {
@@ -1,30 +1,30 @@
1
- import { homedir } from "os";
2
- import { join } from "path";
3
- import { t } from "../i18n/index";
4
- import { MemoryStore } from "../modules/memory/store";
5
- const CATEGORIES = ["preferences", "conventions", "decisions", "errors", "facts"];
1
+ import { homedir } from 'os';
2
+ import { join } from 'path';
3
+ import { t } from '../i18n/index';
4
+ import { MemoryStore } from '../modules/memory/store';
5
+ const CATEGORIES = ['preferences', 'conventions', 'decisions', 'errors', 'facts'];
6
6
  export const recallTool = {
7
- name: "recall",
8
- description: "Recall stored information from memory. Search across preferences, facts, conventions, decisions, errors.",
9
- tags: ["memory"],
7
+ name: 'recall',
8
+ description: 'Recall stored information from memory. Search across preferences, facts, conventions, decisions, errors.',
9
+ tags: ['memory'],
10
10
  parameters: {
11
- type: "object",
11
+ type: 'object',
12
12
  properties: {
13
13
  query: {
14
- type: "string",
15
- description: "Search query (full-text search across all memory)",
14
+ type: 'string',
15
+ description: 'Search query (full-text search across all memory)',
16
16
  },
17
17
  category: {
18
- type: "string",
19
- description: "Limit to specific category: preferences, conventions, decisions, errors, facts",
18
+ type: 'string',
19
+ description: 'Limit to specific category: preferences, conventions, decisions, errors, facts',
20
20
  enum: CATEGORIES,
21
21
  },
22
22
  },
23
23
  },
24
24
  handler: async (_ctx, args) => {
25
- const query = args.query ? String(args.query) : "";
26
- const category = args.category ? String(args.category) : "";
27
- const memoryDir = join(homedir(), ".mma", "memory");
25
+ const query = args.query ? String(args.query) : '';
26
+ const category = args.category ? String(args.category) : '';
27
+ const memoryDir = join(homedir(), '.mma', 'memory');
28
28
  const store = new MemoryStore(memoryDir);
29
29
  try {
30
30
  // No query, no category → show everything
@@ -39,26 +39,20 @@ export const recallTool = {
39
39
  if (category) {
40
40
  const results = searchCategory(store, category, query);
41
41
  if (results.length === 0) {
42
- return { success: true, output: t("tool.recall.empty", { query }) };
42
+ return { success: true, output: t('tool.recall.empty', { query }) };
43
43
  }
44
- return {
45
- success: true,
46
- output: t("tool.recall.search_results", { category, results: results.join("\n") }),
47
- };
44
+ return { success: true, output: t('tool.recall.search_results', { category, results: results.join('\n') }) };
48
45
  }
49
46
  // Query across all
50
47
  const results = store.search(query);
51
48
  if (results.length === 0) {
52
- return { success: true, output: t("tool.recall.empty", { query }) };
49
+ return { success: true, output: t('tool.recall.empty', { query }) };
53
50
  }
54
- const formatted = results.map((r) => `[${r.file}] ${r.match}`).join("\n");
55
- return {
56
- success: true,
57
- output: t("tool.recall.search_results", { category: "all", results: formatted }),
58
- };
51
+ const formatted = results.map(r => `[${r.file}] ${r.match}`).join('\n');
52
+ return { success: true, output: t('tool.recall.search_results', { category: 'all', results: formatted }) };
59
53
  }
60
54
  catch (err) {
61
- return { success: true, output: t("tool.memory_error", { error: String(err) }) };
55
+ return { success: true, output: t('tool.memory_error', { error: String(err) }) };
62
56
  }
63
57
  },
64
58
  };
@@ -66,14 +60,14 @@ function formatAll(store) {
66
60
  const parts = [];
67
61
  const prefs = store.getPreferences();
68
62
  if (Object.keys(prefs).length > 0) {
69
- parts.push("Preferences:");
63
+ parts.push('Preferences:');
70
64
  for (const [k, v] of Object.entries(prefs)) {
71
65
  parts.push(` ${k} = ${v}`);
72
66
  }
73
67
  }
74
- for (const cat of ["facts", "conventions", "decisions", "errors"]) {
68
+ for (const cat of ['facts', 'conventions', 'decisions', 'errors']) {
75
69
  const content = store.read(cat);
76
- const entries = content.split("\n").filter((l) => l.startsWith("- "));
70
+ const entries = content.split('\n').filter(l => l.startsWith('- '));
77
71
  if (entries.length > 0) {
78
72
  parts.push(`\n${cat.charAt(0).toUpperCase() + cat.slice(1)} (last 5):`);
79
73
  for (const e of entries.slice(-5)) {
@@ -81,27 +75,27 @@ function formatAll(store) {
81
75
  }
82
76
  }
83
77
  }
84
- return parts.length > 0 ? parts.join("\n") : t("tool.recall.no_memory");
78
+ return parts.length > 0 ? parts.join('\n') : t('tool.recall.no_memory');
85
79
  }
86
80
  function formatCategory(store, category) {
87
- if (category === "preferences") {
81
+ if (category === 'preferences') {
88
82
  const prefs = store.getPreferences();
89
83
  if (Object.keys(prefs).length === 0)
90
- return t("tool.recall.no_memory");
91
- const lines = ["Preferences:"];
84
+ return t('tool.recall.no_memory');
85
+ const lines = ['Preferences:'];
92
86
  for (const [k, v] of Object.entries(prefs)) {
93
87
  lines.push(` ${k} = ${v}`);
94
88
  }
95
- return lines.join("\n");
89
+ return lines.join('\n');
96
90
  }
97
91
  const content = store.read(category);
98
- const entries = content.split("\n").filter((l) => l.startsWith("- "));
92
+ const entries = content.split('\n').filter(l => l.startsWith('- '));
99
93
  if (entries.length === 0)
100
- return t("tool.recall.no_memory");
101
- return `${category.charAt(0).toUpperCase() + category.slice(1)} (${entries.length} entries):\n${entries.join("\n")}`;
94
+ return t('tool.recall.no_memory');
95
+ return `${category.charAt(0).toUpperCase() + category.slice(1)} (${entries.length} entries):\n${entries.join('\n')}`;
102
96
  }
103
97
  function searchCategory(store, category, query) {
104
- if (category === "preferences") {
98
+ if (category === 'preferences') {
105
99
  const prefs = store.getPreferences();
106
100
  const lower = query.toLowerCase();
107
101
  return Object.entries(prefs)
@@ -110,8 +104,7 @@ function searchCategory(store, category, query) {
110
104
  }
111
105
  const content = store.read(category);
112
106
  const lower = query.toLowerCase();
113
- return content
114
- .split("\n")
115
- .filter((l) => l.startsWith("- ") && l.toLowerCase().includes(lower))
116
- .map((l) => ` ${l}`);
107
+ return content.split('\n')
108
+ .filter(l => l.startsWith('- ') && l.toLowerCase().includes(lower))
109
+ .map(l => ` ${l}`);
117
110
  }
@@ -19,29 +19,18 @@ export class ToolRegistry {
19
19
  return Array.from(this.tools.values());
20
20
  }
21
21
  getByTags(tags) {
22
- return this.getAll().filter((t) => {
22
+ return this.getAll().filter(t => {
23
23
  if (!t.tags || t.tags.length === 0)
24
24
  return false;
25
- return tags.some((tag) => t.tags.includes(tag));
25
+ return tags.some(tag => t.tags.includes(tag));
26
26
  });
27
27
  }
28
- /**
29
- * Tools to expose to the LLM. Always includes `alwaysOn` tools, then tools
30
- * matching the given tags. When no tags are given, returns every registered
31
- * tool (backward-compatible). Duplicates are impossible because the
32
- * alwaysOn + tagged sets are unioned by name.
33
- */
34
28
  getAllForLLM(tags) {
35
- const all = this.getAll();
36
- const tagSet = new Set(tags ?? []);
37
- const selected = tagSet.size === 0
38
- ? all
39
- : all.filter((t) => t.alwaysOn || (t.tags && t.tags.some((tag) => tagSet.has(tag))));
40
- return selected.map((t) => ({
29
+ const tools = tags ? this.getByTags(tags) : this.getAll();
30
+ return tools.map(t => ({
41
31
  name: t.name,
42
32
  description: t.description,
43
33
  parameters: t.parameters,
44
- boundedOutput: t.boundedOutput,
45
34
  }));
46
35
  }
47
36
  }