micro-models-agent 0.40.1 → 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.
Files changed (171) hide show
  1. package/bin/mma.mjs +41 -41
  2. package/dist/cli/commands.js +9 -19
  3. package/dist/cli/completer.js +36 -37
  4. package/dist/cli/index.js +2 -2
  5. package/dist/cli/main.js +48 -23
  6. package/dist/cli/plugin-commands.js +36 -0
  7. package/dist/cli/repl-commands.js +40 -12
  8. package/dist/cli/repl.js +217 -87
  9. package/dist/cli/run-result.js +22 -0
  10. package/dist/cli/security-commands.js +5 -7
  11. package/dist/cli/setup.js +8 -26
  12. package/dist/config/config.js +52 -5
  13. package/dist/config/defaults.js +29 -5
  14. package/dist/config/experts.js +1 -1
  15. package/dist/config/index.js +3 -3
  16. package/dist/config/security.js +3 -10
  17. package/dist/core/agent-moe.js +2 -10
  18. package/dist/core/agent.js +273 -82
  19. package/dist/core/bootstrap.js +80 -13
  20. package/dist/core/index.js +2 -2
  21. package/dist/core/prompt-builder.js +23 -2
  22. package/dist/core/session-logger.js +46 -4
  23. package/dist/core/version.js +24 -0
  24. package/dist/i18n/en.json +75 -2
  25. package/dist/i18n/ru.json +74 -1
  26. package/dist/index.js +1 -1
  27. package/dist/llm/image-utils.js +4 -5
  28. package/dist/llm/index.js +4 -4
  29. package/dist/llm/model-loader.js +6 -6
  30. package/dist/llm/openai-compat.js +40 -34
  31. package/dist/llm/orchestrator.js +33 -29
  32. package/dist/llm/response.js +9 -9
  33. package/dist/logger/app-logger.js +1 -1
  34. package/dist/logger/index.js +1 -1
  35. package/dist/main.js +2489 -2186
  36. package/dist/migration/backup.js +13 -13
  37. package/dist/migration/detect.js +11 -11
  38. package/dist/migration/index.js +2 -2
  39. package/dist/modules/artifacts/store.js +61 -0
  40. package/dist/modules/browser/actions.js +34 -4
  41. package/dist/modules/browser/bridge-client.js +199 -0
  42. package/dist/modules/browser/bridge-path.js +10 -0
  43. package/dist/modules/browser/bridge-server.mjs +202 -202
  44. package/dist/modules/browser/cookie-store.js +6 -6
  45. package/dist/modules/browser/driver.js +136 -0
  46. package/dist/modules/browser/index.js +7 -5
  47. package/dist/modules/browser/module.js +8 -7
  48. package/dist/modules/browser/session.js +87 -84
  49. package/dist/modules/browser/snapshot.js +92 -58
  50. package/dist/modules/browser/types.js +4 -1
  51. package/dist/modules/certification/cli.js +2 -4
  52. package/dist/modules/certification/fact-checker.js +1 -3
  53. package/dist/modules/certification/loader.js +3 -9
  54. package/dist/modules/certification/runner.js +1 -4
  55. package/dist/modules/context/chunk-query.js +100 -0
  56. package/dist/modules/context/fact-extractor.js +162 -0
  57. package/dist/modules/context/history.js +15 -0
  58. package/dist/modules/context/index.js +1 -1
  59. package/dist/modules/context/manager.js +160 -86
  60. package/dist/modules/execution/audit-runners.js +152 -0
  61. package/dist/modules/execution/auditor.js +177 -25
  62. package/dist/modules/execution/execution-plugin.js +272 -0
  63. package/dist/modules/execution/module.js +201 -544
  64. package/dist/modules/execution/moe-executor.js +25 -0
  65. package/dist/modules/execution/plan-store.js +1 -3
  66. package/dist/modules/execution/plan-tool.js +508 -0
  67. package/dist/modules/execution/plan-validator.js +10 -10
  68. package/dist/modules/execution/planner.js +6 -1
  69. package/dist/modules/execution/stuck-detector.js +173 -10
  70. package/dist/modules/execution/verifier.js +86 -42
  71. package/dist/modules/execution/windows-commands.js +41 -0
  72. package/dist/modules/hallucination/confidence.js +8 -1
  73. package/dist/modules/hallucination/detector.js +2 -5
  74. package/dist/modules/hallucination/factual.js +3 -64
  75. package/dist/modules/hallucination/index.js +1 -1
  76. package/dist/modules/hallucination/js-identifiers.js +190 -0
  77. package/dist/modules/hallucination/llm-judge.js +1 -3
  78. package/dist/modules/indexer/cache.js +9 -7
  79. package/dist/modules/indexer/index.js +3 -3
  80. package/dist/modules/indexer/module.js +95 -42
  81. package/dist/modules/indexer/project-profile.js +183 -0
  82. package/dist/modules/indexer/walker.js +17 -17
  83. package/dist/modules/lsp/check-tool.js +58 -0
  84. package/dist/modules/lsp/client.js +74 -31
  85. package/dist/modules/lsp/command.js +60 -0
  86. package/dist/modules/lsp/config.js +87 -33
  87. package/dist/modules/lsp/index.js +3 -3
  88. package/dist/modules/lsp/module.js +185 -21
  89. package/dist/modules/lsp/probe.js +76 -0
  90. package/dist/modules/lsp/project-root.js +32 -0
  91. package/dist/modules/lsp/startup-check.js +141 -0
  92. package/dist/modules/mcp/module.js +2 -6
  93. package/dist/modules/memory/index.js +1 -1
  94. package/dist/modules/memory/module.js +71 -23
  95. package/dist/modules/memory/search.js +11 -9
  96. package/dist/modules/memory/store.js +13 -13
  97. package/dist/modules/pipelines/engine.js +10 -10
  98. package/dist/modules/pipelines/index.js +3 -3
  99. package/dist/modules/pipelines/parser.js +17 -14
  100. package/dist/modules/pipelines/template.js +1 -1
  101. package/dist/modules/plugins/builtin/lint-on-write.js +21 -16
  102. package/dist/modules/plugins/builtin/notify.js +3 -2
  103. package/dist/modules/plugins/index.js +1 -1
  104. package/dist/modules/plugins/loader.js +59 -17
  105. package/dist/modules/plugins/manager.js +73 -17
  106. package/dist/modules/processes/detect.js +34 -0
  107. package/dist/modules/processes/index.js +1 -1
  108. package/dist/modules/processes/registry.js +135 -46
  109. package/dist/modules/registry.js +4 -2
  110. package/dist/modules/security/audit-notifier.js +39 -39
  111. package/dist/modules/security/command-validator.js +2 -8
  112. package/dist/modules/security/data-sanitizer.js +1 -9
  113. package/dist/modules/security/encryption.js +58 -56
  114. package/dist/modules/security/network-validator.js +1 -9
  115. package/dist/modules/security/path-validator.js +1 -3
  116. package/dist/modules/security/security-policies.js +3 -19
  117. package/dist/modules/security/session-encryption.js +1 -1
  118. package/dist/modules/security/session-isolation.js +8 -8
  119. package/dist/modules/session/index.js +3 -3
  120. package/dist/modules/session/module.js +5 -5
  121. package/dist/modules/session/store.js +3 -9
  122. package/dist/modules/skills/matcher.js +27 -0
  123. package/dist/modules/skills/module.js +1 -2
  124. package/dist/modules/updater/checker.js +70 -6
  125. package/dist/modules/updater/index.js +2 -1
  126. package/dist/modules/updater/module.js +116 -0
  127. package/dist/modules/user-profile/compressor.js +2 -2
  128. package/dist/modules/user-profile/index.js +1 -1
  129. package/dist/modules/user-profile/profile.js +9 -9
  130. package/dist/tools/attach-image.js +1 -1
  131. package/dist/tools/bash.js +178 -19
  132. package/dist/tools/browser.js +46 -29
  133. package/dist/tools/chunk-query.js +99 -0
  134. package/dist/tools/download-file.js +116 -0
  135. package/dist/tools/enable-tools.js +58 -0
  136. package/dist/tools/executor.js +4 -5
  137. package/dist/tools/file-info.js +13 -12
  138. package/dist/tools/filter-tools.js +9 -2
  139. package/dist/tools/glob-tool.js +11 -11
  140. package/dist/tools/grep-tool.js +1 -3
  141. package/dist/tools/hidden-tools-block.js +37 -0
  142. package/dist/tools/index.js +13 -2
  143. package/dist/tools/list-dir.js +18 -17
  144. package/dist/tools/load-skill.js +1 -3
  145. package/dist/tools/path-utils.js +4 -4
  146. package/dist/tools/pipeline-run.js +25 -25
  147. package/dist/tools/process-kill.js +11 -11
  148. package/dist/tools/process-list.js +20 -22
  149. package/dist/tools/process-log.js +22 -18
  150. package/dist/tools/question.js +1 -3
  151. package/dist/tools/read-file.js +10 -2
  152. package/dist/tools/recall.js +44 -37
  153. package/dist/tools/registry.js +15 -4
  154. package/dist/tools/remember.js +29 -29
  155. package/dist/tools/scope-check.js +9 -9
  156. package/dist/tools/subagent.js +54 -9
  157. package/dist/tools/user-input.js +1 -1
  158. package/dist/tools/web-browse.js +3 -3
  159. package/dist/tools/web-fetch.js +3 -3
  160. package/dist/tools/web-search.js +3 -3
  161. package/dist/tools/write-file.js +1 -3
  162. package/dist/ui/box.js +1 -5
  163. package/dist/ui/index.js +6 -6
  164. package/dist/ui/line-editor.js +703 -0
  165. package/dist/ui/line-math.js +69 -0
  166. package/dist/ui/md-formatter.js +33 -33
  167. package/dist/ui/output.js +5 -5
  168. package/dist/ui/plan-view.js +103 -0
  169. package/dist/ui/renderer.js +15 -10
  170. package/dist/ui/table.js +1 -1
  171. package/package.json +48 -48
@@ -1,10 +1,10 @@
1
- import { createCipheriv, createDecipheriv, randomBytes, scryptSync } from 'node:crypto';
2
- import { homedir } from 'os';
3
- import { join } from 'path';
4
- import { readFileSync, writeFileSync, existsSync, copyFileSync } from 'fs';
1
+ import { createCipheriv, createDecipheriv, randomBytes, scryptSync } from "node:crypto";
2
+ import { homedir } from "os";
3
+ import { join } from "path";
4
+ import { readFileSync, writeFileSync, existsSync, copyFileSync } from "fs";
5
5
  const DEFAULT_CONFIG = {
6
- keyPath: join(homedir(), '.mma', '.encryption-key'),
7
- algorithm: 'aes-256-gcm',
6
+ keyPath: join(homedir(), ".mma", ".encryption-key"),
7
+ algorithm: "aes-256-gcm",
8
8
  };
9
9
  const IV_LENGTH = 16;
10
10
  const SALT_LENGTH = 16;
@@ -22,7 +22,7 @@ export function generateEncryptionKey() {
22
22
  export function getOrCreateEncryptionKey(config) {
23
23
  const { keyPath } = { ...DEFAULT_CONFIG, ...config };
24
24
  // Ensure directory exists
25
- const dir = join(keyPath, '..');
25
+ const dir = join(keyPath, "..");
26
26
  if (!existsSync(dir)) {
27
27
  // Note: We can't use mkdirSync here because we're in a pure function
28
28
  // This will be handled by the caller
@@ -30,7 +30,7 @@ export function getOrCreateEncryptionKey(config) {
30
30
  if (!existsSync(keyPath)) {
31
31
  const key = generateEncryptionKey();
32
32
  try {
33
- writeFileSync(keyPath, key.toString('base64'), { mode: 0o600 });
33
+ writeFileSync(keyPath, key.toString("base64"), { mode: 0o600 });
34
34
  return key;
35
35
  }
36
36
  catch {
@@ -40,8 +40,8 @@ export function getOrCreateEncryptionKey(config) {
40
40
  }
41
41
  }
42
42
  try {
43
- const keyBase64 = readFileSync(keyPath, 'utf8');
44
- return Buffer.from(keyBase64, 'base64');
43
+ const keyBase64 = readFileSync(keyPath, "utf8");
44
+ return Buffer.from(keyBase64, "base64");
45
45
  }
46
46
  catch {
47
47
  // Backup the corrupted key file before throwing
@@ -49,7 +49,9 @@ export function getOrCreateEncryptionKey(config) {
49
49
  const backupPath = `${keyPath}.corrupted.${Date.now()}`;
50
50
  copyFileSync(keyPath, backupPath);
51
51
  }
52
- catch { /* backup failed, continue */ }
52
+ catch {
53
+ /* backup failed, continue */
54
+ }
53
55
  throw new Error(`Encryption key file is corrupted or unreadable: ${keyPath}. ` +
54
56
  `All previously encrypted data will be unrecoverable. ` +
55
57
  `Delete the key file to generate a new one.`);
@@ -68,63 +70,63 @@ export function encryptString(plaintext, key) {
68
70
  const hmacSalt = Buffer.concat([salt, Buffer.from([1, 2, 3, 4])]);
69
71
  const hmacKey = scryptSync(key, hmacSalt, KEY_LENGTH);
70
72
  // Encrypt with AES-256-CBC
71
- const cipher = createCipheriv('aes-256-cbc', encKey, iv);
72
- let encrypted = cipher.update(plaintext, 'utf8', 'base64');
73
- encrypted += cipher.final('base64');
73
+ const cipher = createCipheriv("aes-256-cbc", encKey, iv);
74
+ let encrypted = cipher.update(plaintext, "utf8", "base64");
75
+ encrypted += cipher.final("base64");
74
76
  // Create HMAC for authentication
75
- const hmac = createHmac('sha256', hmacKey);
77
+ const hmac = createHmac("sha256", hmacKey);
76
78
  hmac.update(iv);
77
- hmac.update(Buffer.from(encrypted, 'base64'));
79
+ hmac.update(Buffer.from(encrypted, "base64"));
78
80
  const authTag = hmac.digest();
79
81
  // Format: salt:iv:authTag:encrypted
80
- return `${salt.toString('base64')}:${iv.toString('base64')}:${authTag.toString('base64')}:${encrypted}`;
82
+ return `${salt.toString("base64")}:${iv.toString("base64")}:${authTag.toString("base64")}:${encrypted}`;
81
83
  }
82
84
  /**
83
85
  * Decrypt a string using AES-256-CBC + HMAC
84
86
  */
85
87
  export function decryptString(encryptedText, key) {
86
- const parts = encryptedText.split(':');
88
+ const parts = encryptedText.split(":");
87
89
  if (parts.length !== 4) {
88
- throw new Error('Invalid encrypted format');
90
+ throw new Error("Invalid encrypted format");
89
91
  }
90
- const salt = Buffer.from(parts[0], 'base64');
91
- const iv = Buffer.from(parts[1], 'base64');
92
- const authTag = Buffer.from(parts[2], 'base64');
92
+ const salt = Buffer.from(parts[0], "base64");
93
+ const iv = Buffer.from(parts[1], "base64");
94
+ const authTag = Buffer.from(parts[2], "base64");
93
95
  const encrypted = parts[3];
94
96
  // Derive encryption key and HMAC key from the main key
95
97
  const encKey = scryptSync(key, salt, KEY_LENGTH);
96
98
  const hmacSalt = Buffer.concat([salt, Buffer.from([1, 2, 3, 4])]);
97
99
  const hmacKey = scryptSync(key, hmacSalt, KEY_LENGTH);
98
100
  // Verify HMAC
99
- const hmac = createHmac('sha256', hmacKey);
101
+ const hmac = createHmac("sha256", hmacKey);
100
102
  hmac.update(iv);
101
- hmac.update(Buffer.from(encrypted, 'base64'));
103
+ hmac.update(Buffer.from(encrypted, "base64"));
102
104
  const computedTag = hmac.digest();
103
105
  if (!computedTag.equals(authTag)) {
104
- throw new Error('Authentication failed: invalid tag');
106
+ throw new Error("Authentication failed: invalid tag");
105
107
  }
106
108
  // Decrypt with AES-256-CBC
107
- const decipher = createDecipheriv('aes-256-cbc', encKey, iv);
108
- let decrypted = decipher.update(encrypted, 'base64', 'utf8');
109
- decrypted += decipher.final('utf8');
109
+ const decipher = createDecipheriv("aes-256-cbc", encKey, iv);
110
+ let decrypted = decipher.update(encrypted, "base64", "utf8");
111
+ decrypted += decipher.final("utf8");
110
112
  return decrypted;
111
113
  }
112
- import { createHmac } from 'node:crypto';
114
+ import { createHmac } from "node:crypto";
113
115
  /**
114
116
  * Check if a string looks like it's encrypted
115
117
  */
116
118
  export function isEncryptedString(value) {
117
119
  // Encrypted strings have the format: salt:iv:authTag:encrypted
118
120
  // Each part is base64 encoded and separated by colons
119
- const parts = value.split(':');
121
+ const parts = value.split(":");
120
122
  if (parts.length !== 4)
121
123
  return false;
122
124
  // Check if all parts are valid base64
123
125
  try {
124
- Buffer.from(parts[0], 'base64');
125
- Buffer.from(parts[1], 'base64');
126
- Buffer.from(parts[2], 'base64');
127
- Buffer.from(parts[3], 'base64');
126
+ Buffer.from(parts[0], "base64");
127
+ Buffer.from(parts[1], "base64");
128
+ Buffer.from(parts[2], "base64");
129
+ Buffer.from(parts[3], "base64");
128
130
  return true;
129
131
  }
130
132
  catch {
@@ -135,28 +137,28 @@ export function isEncryptedString(value) {
135
137
  * Sensitive field names that should be encrypted
136
138
  */
137
139
  export const SENSITIVE_FIELD_NAMES = [
138
- 'api_key',
139
- 'apikey',
140
- 'apiKey',
141
- 'token',
142
- 'access_token',
143
- 'accessToken',
144
- 'password',
145
- 'passwd',
146
- 'pwd',
147
- 'secret',
148
- 'private_key',
149
- 'privateKey',
150
- 'auth',
151
- 'authorization',
152
- 'key',
140
+ "api_key",
141
+ "apikey",
142
+ "apiKey",
143
+ "token",
144
+ "access_token",
145
+ "accessToken",
146
+ "password",
147
+ "passwd",
148
+ "pwd",
149
+ "secret",
150
+ "private_key",
151
+ "privateKey",
152
+ "auth",
153
+ "authorization",
154
+ "key",
153
155
  ];
154
156
  /**
155
157
  * Check if a field name indicates sensitive data
156
158
  */
157
159
  export function isSensitiveFieldName(fieldName) {
158
160
  const lowerFieldName = fieldName.toLowerCase();
159
- return SENSITIVE_FIELD_NAMES.some(name => lowerFieldName.includes(name));
161
+ return SENSITIVE_FIELD_NAMES.some((name) => lowerFieldName.includes(name));
160
162
  }
161
163
  /**
162
164
  * Encrypt sensitive fields in an object
@@ -164,12 +166,12 @@ export function isSensitiveFieldName(fieldName) {
164
166
  export function encryptSensitiveFields(obj, key) {
165
167
  const result = {};
166
168
  for (const [fieldName, value] of Object.entries(obj)) {
167
- if (isSensitiveFieldName(fieldName) && typeof value === 'string' && !isEncryptedString(value)) {
169
+ if (isSensitiveFieldName(fieldName) && typeof value === "string" && !isEncryptedString(value)) {
168
170
  result[fieldName] = encryptString(value, key);
169
171
  }
170
- else if (typeof value === 'object' && value !== null) {
172
+ else if (typeof value === "object" && value !== null) {
171
173
  if (Array.isArray(value)) {
172
- result[fieldName] = value.map(item => typeof item === 'object' && item !== null && !(item instanceof RegExp)
174
+ result[fieldName] = value.map((item) => typeof item === "object" && item !== null && !(item instanceof RegExp)
173
175
  ? encryptSensitiveFields(item, key)
174
176
  : item);
175
177
  }
@@ -192,7 +194,7 @@ export function encryptSensitiveFields(obj, key) {
192
194
  export function decryptSensitiveFields(obj, key) {
193
195
  const result = {};
194
196
  for (const [fieldName, value] of Object.entries(obj)) {
195
- if (isSensitiveFieldName(fieldName) && typeof value === 'string' && isEncryptedString(value)) {
197
+ if (isSensitiveFieldName(fieldName) && typeof value === "string" && isEncryptedString(value)) {
196
198
  try {
197
199
  result[fieldName] = decryptString(value, key);
198
200
  }
@@ -201,9 +203,9 @@ export function decryptSensitiveFields(obj, key) {
201
203
  result[fieldName] = value;
202
204
  }
203
205
  }
204
- else if (typeof value === 'object' && value !== null) {
206
+ else if (typeof value === "object" && value !== null) {
205
207
  if (Array.isArray(value)) {
206
- result[fieldName] = value.map(item => typeof item === 'object' && item !== null && !(item instanceof RegExp)
208
+ result[fieldName] = value.map((item) => typeof item === "object" && item !== null && !(item instanceof RegExp)
207
209
  ? decryptSensitiveFields(item, key)
208
210
  : item);
209
211
  }
@@ -48,15 +48,7 @@ export function isUrlAllowed(url, securityConfig) {
48
48
  export function sanitizeUrl(url) {
49
49
  try {
50
50
  const urlObj = new URL(url);
51
- const sensitiveKeys = [
52
- "api_key",
53
- "token",
54
- "password",
55
- "secret",
56
- "access_token",
57
- "auth",
58
- "key",
59
- ];
51
+ const sensitiveKeys = ["api_key", "token", "password", "secret", "access_token", "auth", "key"];
60
52
  const sanitizedParams = new URLSearchParams(urlObj.search);
61
53
  for (const key of sensitiveKeys) {
62
54
  if (sanitizedParams.has(key)) {
@@ -199,9 +199,7 @@ function matchesGlobPattern(targetPath, baseDir, pattern) {
199
199
  .replace(/\?/g, ".")
200
200
  .replace(/\./g, "\\.");
201
201
  const regex = new RegExp(`^${regexPattern}$`);
202
- const relativePath = resolvedTargetPath
203
- .slice(resolvedBaseDir.length + 1)
204
- .replace(/\\/g, "/");
202
+ const relativePath = resolvedTargetPath.slice(resolvedBaseDir.length + 1).replace(/\\/g, "/");
205
203
  return regex.test(relativePath);
206
204
  }
207
205
  // Exact match
@@ -1,4 +1,4 @@
1
- import { mergeSecurityConfig, } from "../../config/security";
1
+ import { mergeSecurityConfig } from "../../config/security";
2
2
  /**
3
3
  * Strict security policy - maximum security, minimum flexibility
4
4
  * Recommended for: production environments, sensitive data, multi-user systems
@@ -70,18 +70,7 @@ export const STRICT_POLICY = {
70
70
  "-R",
71
71
  "--no-clobber",
72
72
  ],
73
- dangerousOperators: [
74
- ">",
75
- ">>",
76
- "2>",
77
- "2>>",
78
- "|",
79
- "&&",
80
- "||",
81
- ";",
82
- "&",
83
- "`",
84
- ],
73
+ dangerousOperators: [">", ">>", "2>", "2>>", "|", "&&", "||", ";", "&", "`"],
85
74
  logCommands: true,
86
75
  },
87
76
  paths: {
@@ -299,12 +288,7 @@ export const BALANCED_POLICY = {
299
288
  auditNotifier: {
300
289
  enabled: false,
301
290
  minSeverity: "medium",
302
- eventTypes: [
303
- "security_block",
304
- "bash_command",
305
- "file_operation",
306
- "network_request",
307
- ],
291
+ eventTypes: ["security_block", "bash_command", "file_operation", "network_request"],
308
292
  maxRetries: 3,
309
293
  },
310
294
  },
@@ -1,4 +1,4 @@
1
- import { readFileSync, writeFileSync, existsSync, readdirSync, unlinkSync, } from "fs";
1
+ import { readFileSync, writeFileSync, existsSync, readdirSync, unlinkSync } from "fs";
2
2
  import { join } from "path";
3
3
  import { homedir } from "os";
4
4
  import { encryptString, decryptString, isEncryptedString, ConfigEncryptor, } from "./encryption";
@@ -1,7 +1,7 @@
1
- import { join, resolve } from 'path';
2
- import { homedir } from 'os';
3
- import { mkdirSync, existsSync } from 'fs';
4
- import { DEFAULT_SECURITY_CONFIG } from '../../config/security';
1
+ import { join, resolve } from "path";
2
+ import { homedir } from "os";
3
+ import { mkdirSync, existsSync } from "fs";
4
+ import { DEFAULT_SECURITY_CONFIG } from "../../config/security";
5
5
  /**
6
6
  * Default session isolation configuration
7
7
  */
@@ -16,8 +16,8 @@ export const DEFAULT_SESSION_ISOLATION = {
16
16
  export function createSessionContext(sessionId, projectDir, isolationConfig, securityOverrides) {
17
17
  const config = { ...DEFAULT_SESSION_ISOLATION, ...isolationConfig };
18
18
  // Create session-specific temporary directory
19
- const baseDir = config.baseDir || join(homedir(), '.mma', 'sessions', sessionId);
20
- const tempDir = join(baseDir, 'temp');
19
+ const baseDir = config.baseDir || join(homedir(), ".mma", "sessions", sessionId);
20
+ const tempDir = join(baseDir, "temp");
21
21
  if (config.isolateTempFiles && !existsSync(tempDir)) {
22
22
  try {
23
23
  mkdirSync(tempDir, { recursive: true, mode: 0o700 });
@@ -84,11 +84,11 @@ export function isPathInSessionScope(sessionContext, path) {
84
84
  const workingDir = resolve(sessionContext.workingDir);
85
85
  const tempDir = resolve(sessionContext.tempDir);
86
86
  // Check if path is within working directory
87
- if (resolvedPath.startsWith(workingDir + '/') || resolvedPath.startsWith(workingDir + '\\')) {
87
+ if (resolvedPath.startsWith(workingDir + "/") || resolvedPath.startsWith(workingDir + "\\")) {
88
88
  return true;
89
89
  }
90
90
  // Check if path is within temp directory
91
- if (resolvedPath.startsWith(tempDir + '/') || resolvedPath.startsWith(tempDir + '\\')) {
91
+ if (resolvedPath.startsWith(tempDir + "/") || resolvedPath.startsWith(tempDir + "\\")) {
92
92
  return true;
93
93
  }
94
94
  return false;
@@ -1,3 +1,3 @@
1
- export { SessionStore } from './store';
2
- export { SessionManager } from './manager';
3
- export { SessionModule } from './module';
1
+ export { SessionStore } from "./store";
2
+ export { SessionManager } from "./manager";
3
+ export { SessionModule } from "./module";
@@ -1,6 +1,6 @@
1
- import { t } from '../../i18n/index';
1
+ import { t } from "../../i18n/index";
2
2
  export class SessionModule {
3
- name = 'session';
3
+ name = "session";
4
4
  manager;
5
5
  constructor(manager) {
6
6
  this.manager = manager;
@@ -10,15 +10,15 @@ export class SessionModule {
10
10
  if (!meta || meta.messageCount === 0)
11
11
  return null;
12
12
  return {
13
- content: t('session.info', { name: meta.name, id: meta.id, count: meta.messageCount }),
14
- priority: 'low',
13
+ content: t("session.info", { name: meta.name, id: meta.id, count: meta.messageCount }),
14
+ priority: "low",
15
15
  essential: false,
16
16
  estimatedTokens: 20,
17
17
  };
18
18
  }
19
19
  getPlugin() {
20
20
  return {
21
- name: 'session',
21
+ name: "session",
22
22
  };
23
23
  }
24
24
  }
@@ -72,9 +72,7 @@ export class SessionStore {
72
72
  return null;
73
73
  try {
74
74
  const raw = readFileSync(path, "utf-8");
75
- const content = this.encryptor
76
- ? this.encryptor.decryptFileContent(raw)
77
- : raw;
75
+ const content = this.encryptor ? this.encryptor.decryptFileContent(raw) : raw;
78
76
  const meta = JSON.parse(content);
79
77
  this._metaCache.set(id, meta);
80
78
  return meta;
@@ -129,9 +127,7 @@ export class SessionStore {
129
127
  .map(parseLine)
130
128
  .filter((m) => m !== null);
131
129
  }
132
- return lines
133
- .map(parseLine)
134
- .filter((m) => m !== null);
130
+ return lines.map(parseLine).filter((m) => m !== null);
135
131
  }
136
132
  catch {
137
133
  return [];
@@ -177,9 +173,7 @@ export class SessionStore {
177
173
  .map(parseLine)
178
174
  .filter((e) => e !== null);
179
175
  }
180
- return lines
181
- .map(parseLine)
182
- .filter((e) => e !== null);
176
+ return lines.map(parseLine).filter((e) => e !== null);
183
177
  }
184
178
  catch {
185
179
  return [];
@@ -0,0 +1,27 @@
1
+ const MIN_WORD_LENGTH = 4;
2
+ export class SkillsMatcher {
3
+ match(taskDescription, skills, maxResults = 3) {
4
+ const taskWords = taskDescription.toLowerCase().split(/\W+/).filter(w => w.length >= MIN_WORD_LENGTH);
5
+ const scored = skills.map(skill => {
6
+ const allKeywords = [
7
+ skill.name.toLowerCase(),
8
+ skill.description.toLowerCase(),
9
+ ...skill.keywords.map(k => k.toLowerCase()),
10
+ ];
11
+ let score = 0;
12
+ for (const word of taskWords) {
13
+ for (const kw of allKeywords) {
14
+ if (kw === word || (kw.length >= MIN_WORD_LENGTH && kw.includes(word))) {
15
+ score++;
16
+ }
17
+ }
18
+ }
19
+ return { skill, score };
20
+ });
21
+ return scored
22
+ .filter(s => s.score > 0)
23
+ .sort((a, b) => b.score - a.score)
24
+ .slice(0, maxResults)
25
+ .map(s => s.skill);
26
+ }
27
+ }
@@ -38,8 +38,7 @@ export class SkillsModule {
38
38
  }
39
39
  search(query) {
40
40
  const q = query.toLowerCase();
41
- return this.availableSkills.filter((s) => s.name.toLowerCase().includes(q) ||
42
- s.description.toLowerCase().includes(q));
41
+ return this.availableSkills.filter((s) => s.name.toLowerCase().includes(q) || s.description.toLowerCase().includes(q));
43
42
  }
44
43
  getBudget() {
45
44
  return {
@@ -1,32 +1,96 @@
1
+ import { platform } from "os";
2
+ import { resolveSpawnCommand } from "../lsp/command";
3
+ /** Numeric semver comparison (x.y.z, prerelease ignored). */
4
+ export function semverGt(a, b) {
5
+ const pa = a
6
+ .replace(/^v/, "")
7
+ .split(/[.-]/)
8
+ .map((p) => Number.parseInt(p, 10) || 0);
9
+ const pb = b
10
+ .replace(/^v/, "")
11
+ .split(/[.-]/)
12
+ .map((p) => Number.parseInt(p, 10) || 0);
13
+ for (let i = 0; i < 3; i++) {
14
+ const na = pa[i] ?? 0;
15
+ const nb = pb[i] ?? 0;
16
+ if (na !== nb)
17
+ return na > nb;
18
+ }
19
+ return false;
20
+ }
21
+ const defaultRunner = async (command, args, options) => {
22
+ const { execFile } = await import("child_process");
23
+ return new Promise((resolve) => {
24
+ execFile(command, args, options, (err) => resolve({ error: err?.message }));
25
+ });
26
+ };
1
27
  export class Updater {
2
28
  currentVersion;
3
29
  packageName;
4
- constructor(currentVersion, packageName) {
30
+ runner;
31
+ constructor(currentVersion, packageName, runner) {
5
32
  this.currentVersion = currentVersion;
6
33
  this.packageName = packageName;
34
+ this.runner = runner ?? defaultRunner;
7
35
  }
8
36
  getCurrentVersion() {
9
37
  return this.currentVersion;
10
38
  }
39
+ getPackageName() {
40
+ return this.packageName;
41
+ }
11
42
  async check() {
12
43
  try {
13
44
  const response = await fetch(`https://registry.npmjs.org/${this.packageName}`, {
14
- headers: { Accept: 'application/vnd.npm.install-v1+json' },
45
+ headers: { Accept: "application/vnd.npm.install-v1+json" },
15
46
  signal: AbortSignal.timeout(5000),
16
47
  });
17
48
  if (!response.ok) {
18
- return { updateAvailable: false, current: this.currentVersion, error: `HTTP ${response.status}` };
49
+ return {
50
+ updateAvailable: false,
51
+ current: this.currentVersion,
52
+ error: `HTTP ${response.status}`,
53
+ };
19
54
  }
20
55
  const data = await response.json();
21
- const latest = data['dist-tags']?.latest;
56
+ const latest = data["dist-tags"]?.latest;
22
57
  if (!latest) {
23
- return { updateAvailable: false, current: this.currentVersion, error: 'No latest tag' };
58
+ return { updateAvailable: false, current: this.currentVersion, error: "No latest tag" };
24
59
  }
25
- const updateAvailable = latest !== this.currentVersion;
60
+ const updateAvailable = semverGt(latest, this.currentVersion);
26
61
  return { updateAvailable, current: this.currentVersion, latest };
27
62
  }
28
63
  catch (e) {
29
64
  return { updateAvailable: false, current: this.currentVersion, error: e.message };
30
65
  }
31
66
  }
67
+ /**
68
+ * Install the latest version globally via `npm install -g`.
69
+ * Returns an InstallResult (never throws).
70
+ */
71
+ async install(latest) {
72
+ try {
73
+ const { command, args } = this.buildInstallCommand(latest);
74
+ const res = await this.runner(command, args, {
75
+ timeout: 120000,
76
+ windowsHide: true,
77
+ });
78
+ if (res.error)
79
+ return { success: false, error: res.error };
80
+ return { success: true, installed: latest };
81
+ }
82
+ catch (e) {
83
+ const msg = e?.message ?? String(e);
84
+ return { success: false, error: msg.split("\n")[0] };
85
+ }
86
+ }
87
+ buildInstallCommand(latest) {
88
+ const npmArgs = ["install", "-g", `${this.packageName}@${latest}`];
89
+ const command = resolveSpawnCommand("npm");
90
+ if (platform() === "win32" && /\.(cmd|bat)$/i.test(command)) {
91
+ // Node cannot spawn .cmd/.bat shims directly (spawn EINVAL) — route via cmd.exe.
92
+ return { command: "cmd.exe", args: ["/c", command, ...npmArgs] };
93
+ }
94
+ return { command, args: npmArgs };
95
+ }
32
96
  }
@@ -1 +1,2 @@
1
- export { Updater } from './checker';
1
+ export { Updater } from "./checker";
2
+ export { UpdaterModule } from "./module";