micro-models-agent 0.47.1 → 0.48.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 (218) hide show
  1. package/README.md +358 -312
  2. package/dist/cli/commands.js +323 -0
  3. package/dist/cli/completer.js +167 -0
  4. package/dist/cli/index.js +2 -0
  5. package/dist/cli/main.js +165 -0
  6. package/dist/cli/plugin-commands.js +36 -0
  7. package/dist/cli/repl-commands.js +661 -0
  8. package/dist/cli/repl.js +616 -0
  9. package/dist/cli/run-result.js +22 -0
  10. package/dist/cli/security-commands.js +164 -0
  11. package/dist/cli/setup.js +231 -0
  12. package/dist/config/config.js +249 -0
  13. package/dist/config/defaults.js +124 -0
  14. package/dist/config/experts.js +15 -0
  15. package/dist/config/index.js +3 -0
  16. package/dist/config/security.js +193 -0
  17. package/dist/config/types.js +1 -0
  18. package/dist/core/agent-moe.js +102 -0
  19. package/dist/core/agent.js +886 -0
  20. package/dist/core/bootstrap.js +404 -0
  21. package/dist/core/index.js +2 -0
  22. package/dist/core/prompt-builder.js +76 -0
  23. package/dist/core/session-logger.js +197 -0
  24. package/dist/core/types.js +1 -0
  25. package/dist/core/version.js +24 -0
  26. package/dist/core/workspace.js +76 -0
  27. package/dist/i18n/en.json +598 -0
  28. package/dist/i18n/index.js +46 -0
  29. package/dist/i18n/ru.json +598 -0
  30. package/dist/index.js +22 -0
  31. package/dist/llm/image-utils.js +143 -0
  32. package/dist/llm/index.js +4 -0
  33. package/dist/llm/model-loader.js +78 -0
  34. package/dist/llm/openai-compat.js +359 -0
  35. package/dist/llm/orchestrator.js +198 -0
  36. package/dist/llm/provider.js +10 -0
  37. package/dist/llm/response.js +39 -0
  38. package/dist/llm/token-counter.js +39 -0
  39. package/dist/llm/types.js +1 -0
  40. package/dist/logger/app-logger.js +143 -0
  41. package/dist/logger/file-log.js +151 -0
  42. package/dist/logger/index.js +1 -0
  43. package/dist/main.js +677 -358
  44. package/dist/migration/backup.js +45 -0
  45. package/dist/migration/detect.js +50 -0
  46. package/dist/migration/index.js +2 -0
  47. package/dist/modules/artifacts/store.js +61 -0
  48. package/dist/modules/browser/actions.js +76 -0
  49. package/dist/modules/browser/bridge-client.js +199 -0
  50. package/dist/modules/browser/bridge-path.js +10 -0
  51. package/dist/modules/browser/bridge-server.mjs +202 -202
  52. package/dist/modules/browser/cookie-store.js +24 -0
  53. package/dist/modules/browser/driver.js +136 -0
  54. package/dist/modules/browser/index.js +7 -0
  55. package/dist/modules/browser/module.js +29 -0
  56. package/dist/modules/browser/session.js +338 -0
  57. package/dist/modules/browser/snapshot.js +148 -0
  58. package/dist/modules/browser/types.js +12 -0
  59. package/dist/modules/certification/cli.js +174 -0
  60. package/dist/modules/certification/fact-checker.js +82 -0
  61. package/dist/modules/certification/loader.js +105 -0
  62. package/dist/modules/certification/manifest.js +50 -0
  63. package/dist/modules/certification/runner.js +159 -0
  64. package/dist/modules/certification/scenarios.js +124 -0
  65. package/dist/modules/certification/types.js +1 -0
  66. package/dist/modules/context/chunk-query.js +100 -0
  67. package/dist/modules/context/fact-extractor.js +162 -0
  68. package/dist/modules/context/history.js +15 -0
  69. package/dist/modules/context/index.js +1 -0
  70. package/dist/modules/context/manager.js +423 -0
  71. package/dist/modules/execution/audit-runners.js +152 -0
  72. package/dist/modules/execution/auditor.js +218 -0
  73. package/dist/modules/execution/execution-plugin.js +272 -0
  74. package/dist/modules/execution/index.js +8 -0
  75. package/dist/modules/execution/module.js +436 -0
  76. package/dist/modules/execution/moe-executor.js +291 -0
  77. package/dist/modules/execution/plan-coverage.js +68 -0
  78. package/dist/modules/execution/plan-persister.js +46 -0
  79. package/dist/modules/execution/plan-store.js +157 -0
  80. package/dist/modules/execution/plan-tool.js +508 -0
  81. package/dist/modules/execution/plan-validator.js +153 -0
  82. package/dist/modules/execution/planner.js +90 -0
  83. package/dist/modules/execution/stuck-detector.js +510 -0
  84. package/dist/modules/execution/tracker.js +67 -0
  85. package/dist/modules/execution/types.js +1 -0
  86. package/dist/modules/execution/verifier.js +222 -0
  87. package/dist/modules/execution/windows-commands.js +41 -0
  88. package/dist/modules/hallucination/confidence.js +66 -0
  89. package/dist/modules/hallucination/consistency.js +26 -0
  90. package/dist/modules/hallucination/detector.js +43 -0
  91. package/dist/modules/hallucination/factual.js +129 -0
  92. package/dist/modules/hallucination/index.js +5 -0
  93. package/dist/modules/hallucination/js-identifiers.js +262 -0
  94. package/dist/modules/hallucination/llm-judge.js +101 -0
  95. package/dist/modules/index.js +5 -0
  96. package/dist/modules/indexer/cache.js +40 -0
  97. package/dist/modules/indexer/index.js +3 -0
  98. package/dist/modules/indexer/module.js +245 -0
  99. package/dist/modules/indexer/project-profile.js +183 -0
  100. package/dist/modules/indexer/walker.js +101 -0
  101. package/dist/modules/lsp/check-tool.js +58 -0
  102. package/dist/modules/lsp/client.js +278 -0
  103. package/dist/modules/lsp/command.js +60 -0
  104. package/dist/modules/lsp/config.js +135 -0
  105. package/dist/modules/lsp/index.js +3 -0
  106. package/dist/modules/lsp/module.js +232 -0
  107. package/dist/modules/lsp/probe.js +76 -0
  108. package/dist/modules/lsp/project-root.js +32 -0
  109. package/dist/modules/lsp/startup-check.js +141 -0
  110. package/dist/modules/lsp/types.js +1 -0
  111. package/dist/modules/mcp/client.js +399 -0
  112. package/dist/modules/mcp/index.js +3 -0
  113. package/dist/modules/mcp/module.js +142 -0
  114. package/dist/modules/mcp/registry.js +15 -0
  115. package/dist/modules/memory/index.js +1 -0
  116. package/dist/modules/memory/module.js +96 -0
  117. package/dist/modules/memory/search.js +42 -0
  118. package/dist/modules/memory/store.js +69 -0
  119. package/dist/modules/pipelines/engine.js +60 -0
  120. package/dist/modules/pipelines/index.js +3 -0
  121. package/dist/modules/pipelines/parser.js +56 -0
  122. package/dist/modules/pipelines/template.js +14 -0
  123. package/dist/modules/plugins/builtin/lint-on-write.js +231 -0
  124. package/dist/modules/plugins/builtin/notify.js +9 -0
  125. package/dist/modules/plugins/index.js +1 -0
  126. package/dist/modules/plugins/loader.js +70 -0
  127. package/dist/modules/plugins/manager.js +217 -0
  128. package/dist/modules/plugins/types.js +1 -0
  129. package/dist/modules/processes/detect.js +34 -0
  130. package/dist/modules/processes/index.js +2 -0
  131. package/dist/modules/processes/registry.js +327 -0
  132. package/dist/modules/processes/runner.js +23 -0
  133. package/dist/modules/registry.js +47 -0
  134. package/dist/modules/security/audit-log.js +136 -0
  135. package/dist/modules/security/audit-notifier.js +292 -0
  136. package/dist/modules/security/command-validator.js +205 -0
  137. package/dist/modules/security/content-scanner.js +53 -0
  138. package/dist/modules/security/data-sanitizer.js +89 -0
  139. package/dist/modules/security/encryption.js +242 -0
  140. package/dist/modules/security/index.js +14 -0
  141. package/dist/modules/security/network-validator.js +71 -0
  142. package/dist/modules/security/path-validator.js +207 -0
  143. package/dist/modules/security/rate-limiter.js +119 -0
  144. package/dist/modules/security/security-policies.js +531 -0
  145. package/dist/modules/security/session-encryption.js +210 -0
  146. package/dist/modules/security/session-isolation.js +95 -0
  147. package/dist/modules/session/index.js +3 -0
  148. package/dist/modules/session/manager.js +172 -0
  149. package/dist/modules/session/module.js +24 -0
  150. package/dist/modules/session/store.js +222 -0
  151. package/dist/modules/session/types.js +1 -0
  152. package/dist/modules/skills/index.js +2 -0
  153. package/dist/modules/skills/loader.js +72 -0
  154. package/dist/modules/skills/matcher.js +27 -0
  155. package/dist/modules/skills/module.js +129 -0
  156. package/dist/modules/types.js +1 -0
  157. package/dist/modules/updater/checker.js +96 -0
  158. package/dist/modules/updater/index.js +2 -0
  159. package/dist/modules/updater/module.js +116 -0
  160. package/dist/modules/user-profile/compressor.js +16 -0
  161. package/dist/modules/user-profile/index.js +1 -0
  162. package/dist/modules/user-profile/profile.js +68 -0
  163. package/dist/skills/builtin/git.md +36 -36
  164. package/dist/skills/builtin/typescript.md +35 -35
  165. package/dist/tools/approve.js +32 -0
  166. package/dist/tools/attach-image.js +89 -0
  167. package/dist/tools/bash.js +496 -0
  168. package/dist/tools/browser.js +114 -0
  169. package/dist/tools/chunk-query.js +99 -0
  170. package/dist/tools/create-dir.js +55 -0
  171. package/dist/tools/delete-file.js +62 -0
  172. package/dist/tools/download-file.js +116 -0
  173. package/dist/tools/edit-file.js +79 -0
  174. package/dist/tools/enable-tools.js +58 -0
  175. package/dist/tools/executor.js +144 -0
  176. package/dist/tools/file-info.js +46 -0
  177. package/dist/tools/filter-tools.js +17 -0
  178. package/dist/tools/glob-tool.js +26 -0
  179. package/dist/tools/grep-tool.js +84 -0
  180. package/dist/tools/hidden-tools-block.js +37 -0
  181. package/dist/tools/index.js +78 -0
  182. package/dist/tools/list-dir.js +48 -0
  183. package/dist/tools/load-skill.js +42 -0
  184. package/dist/tools/mcp-call.js +68 -0
  185. package/dist/tools/move-file.js +85 -0
  186. package/dist/tools/path-utils.js +51 -0
  187. package/dist/tools/pipeline-run.js +144 -0
  188. package/dist/tools/preview.js +2 -0
  189. package/dist/tools/process-kill.js +29 -0
  190. package/dist/tools/process-list.js +36 -0
  191. package/dist/tools/process-log.js +45 -0
  192. package/dist/tools/question.js +140 -0
  193. package/dist/tools/read-file.js +91 -0
  194. package/dist/tools/recall.js +117 -0
  195. package/dist/tools/registry.js +47 -0
  196. package/dist/tools/remember.js +67 -0
  197. package/dist/tools/scope-check.js +30 -0
  198. package/dist/tools/search-history.js +84 -0
  199. package/dist/tools/subagent.js +196 -0
  200. package/dist/tools/types.js +1 -0
  201. package/dist/tools/user-input.js +123 -0
  202. package/dist/tools/web-browse.js +86 -0
  203. package/dist/tools/web-fetch.js +98 -0
  204. package/dist/tools/web-search.js +78 -0
  205. package/dist/tools/write-file.js +81 -0
  206. package/dist/ui/box.js +77 -0
  207. package/dist/ui/colors.js +4 -0
  208. package/dist/ui/diff.js +178 -0
  209. package/dist/ui/index.js +6 -0
  210. package/dist/ui/line-editor.js +703 -0
  211. package/dist/ui/line-math.js +69 -0
  212. package/dist/ui/md-formatter.js +212 -0
  213. package/dist/ui/output.js +13 -0
  214. package/dist/ui/plan-view.js +103 -0
  215. package/dist/ui/renderer.js +209 -0
  216. package/dist/ui/spinner.js +70 -0
  217. package/dist/ui/table.js +144 -0
  218. package/package.json +48 -48
package/dist/main.js CHANGED
@@ -1942,9 +1942,9 @@ function mergeSecurityConfig(userConfig) {
1942
1942
  var DEFAULT_SECURITY_CONFIG;
1943
1943
  var init_security = __esm(() => {
1944
1944
  DEFAULT_SECURITY_CONFIG = {
1945
- enabled: false,
1945
+ enabled: true,
1946
1946
  bash: {
1947
- enabled: false,
1947
+ enabled: true,
1948
1948
  blacklist: [
1949
1949
  "rm",
1950
1950
  "dd",
@@ -1983,10 +1983,24 @@ var init_security = __esm(() => {
1983
1983
  "umount",
1984
1984
  "powershell",
1985
1985
  "pwsh",
1986
- "cmd"
1986
+ "cmd",
1987
+ "bash",
1988
+ "sh",
1989
+ "zsh",
1990
+ "dash",
1991
+ "python",
1992
+ "python3",
1993
+ "node",
1994
+ "bun",
1995
+ "perl",
1996
+ "ruby",
1997
+ "del",
1998
+ "rd",
1999
+ "erase",
2000
+ "taskkill"
1987
2001
  ],
1988
2002
  whitelist: [],
1989
- blockDangerousFlags: false,
2003
+ blockDangerousFlags: true,
1990
2004
  logCommands: true,
1991
2005
  dangerousFlags: [
1992
2006
  "--force",
@@ -2003,7 +2017,7 @@ var init_security = __esm(() => {
2003
2017
  "-y",
2004
2018
  "--no-confirm"
2005
2019
  ],
2006
- dangerousOperators: [">", ">>", "2>", "2>>", "`"]
2020
+ dangerousOperators: [">", ">>", "2>", "2>>", ";", "&&", "||", "|", "`", "$("]
2007
2021
  },
2008
2022
  paths: {
2009
2023
  enabled: false,
@@ -2026,7 +2040,7 @@ var init_security = __esm(() => {
2026
2040
  allowed: []
2027
2041
  },
2028
2042
  network: {
2029
- enabled: false,
2043
+ enabled: true,
2030
2044
  deniedDomains: ["localhost", "127.0.0.1", "::1"],
2031
2045
  allowedDomains: [],
2032
2046
  requestTimeout: 15000,
@@ -2357,7 +2371,8 @@ var init_en = __esm(() => {
2357
2371
  "file.deleted": "Deleted {path}",
2358
2372
  "file.updated": "Updated {path}",
2359
2373
  "file.written": "Written {path}",
2360
- "file.notfound": "File not found: {path}",
2374
+ "file.notfound": `File not found: {path}
2375
+ This does NOT mean the project is missing — the path may be wrong. Run list_dir on the working directory first; create files only after confirming what exists.`,
2361
2376
  "file.dir_notfound": "Directory not found: {path}",
2362
2377
  "file.path_not_allowed": "Path not allowed: {path}",
2363
2378
  "file.path_not_allowed_short": "Path not allowed",
@@ -2369,7 +2384,8 @@ var init_en = __esm(() => {
2369
2384
  Use read_file on {path} to see the current content before editing — the target may differ (whitespace, line endings) or was already replaced.`,
2370
2385
  "file.moved": "Moved {from} → {to}",
2371
2386
  "file.not_found_short": "Not found: {path}",
2372
- "file.notfound_resolved": "File not found: {path} (resolved to {resolved})",
2387
+ "file.notfound_resolved": `File not found: {path} (resolved to {resolved})
2388
+ The path was joined onto the working directory because it does not exist as given. Run list_dir to find the correct path.`,
2373
2389
  "file.empty": "(empty)",
2374
2390
  "file.no_matches": "No matches",
2375
2391
  "file.truncated": `
@@ -2867,6 +2883,7 @@ Available commands:`,
2867
2883
  "setup.security_header": `
2868
2884
  --- Security ---`,
2869
2885
  "setup.security_status_off": " Security: OFF (default — all commands allowed)",
2886
+ "setup.security_status_on": " Security: ON (balanced policy by default)",
2870
2887
  "setup.security_configure": " Configure security? (y/N)",
2871
2888
  "setup.security_bash_block": " Block dangerous commands (rm, sudo, ssh, etc.)? (y/N)",
2872
2889
  "setup.security_flags_block": " Block dangerous flags (--force, -rf, etc.)? (y/N)",
@@ -2922,6 +2939,7 @@ Apply a matching solution from these results. If none is relevant — do NOT rep
2922
2939
  "exec.error_search_failed": 'Web search returned nothing for "{query}".',
2923
2940
  "exec.error_search_no_query": "Error output is not meaningful — skipping the web search.",
2924
2941
  "exec.npm_exec_hint": '"could not determine executable to run" — no "bin" for that package/script. Use "npm run <script>" (script must exist in package.json) or "bunx <pkg>" for a package that declares a bin.',
2942
+ "exec.task_reminder": "Task: {task}. Continue making progress — do not repeat failed actions.",
2925
2943
  "hall.max_retries_exhausted": "Model returned empty or insufficient responses after multiple retries",
2926
2944
  "hall.short_response": "Response too short or empty",
2927
2945
  "hall.repetitive": "Response too repetitive ({pct}% overlap)",
@@ -3030,7 +3048,22 @@ Apply a matching solution from these results. If none is relevant — do NOT rep
3030
3048
  "tool.friendly.enable_tools": "Enable tools",
3031
3049
  "cli.provider_base_hint": "Base URL set to: {baseUrl}",
3032
3050
  "repl.cost": "Total cost: {cost}",
3033
- "repl.tokens": "Tokens used: {tokens}"
3051
+ "repl.tokens": "Tokens used: {tokens}",
3052
+ "repl.ctrl_c_interrupt": `
3053
+ [Ctrl+C] Stopping agent... (press again to force)`,
3054
+ "exec.stop_directive": 'STOP. Step {stepId} ("{description}") took {iterations} iterations with no progress. DO NOT continue this step. Immediately call: plan update step={stepId} status=done (if code works despite warnings) OR plan update step={stepId} status=skipped note="reason". Do NOT make any other tool calls before updating the plan.',
3055
+ "exec.test_runner_fail": "[test-runner] {framework}: {failed} test(s) FAILING, {passed} passing — do NOT mark verification steps as done while tests fail. Investigate the failures, fix the code, then re-run the tests.",
3056
+ "exec.test_runner_pass": "[test-runner] {framework}: all {passed} test(s) passing.",
3057
+ "plan.subtasks_done": "All sub-tasks done — call plan update step={stepId} status=done to complete this step.",
3058
+ "plan.no_subtasks": "(no sub-tasks)",
3059
+ "plan.step_label": "Step {stepId}: {description}",
3060
+ "exec.hint_deps": "Check if a lock file exists (package-lock.json, poetry.lock). If missing, run the install command first.",
3061
+ "exec.hint_test": "Make sure the source files exist and have real code before running tests.",
3062
+ "exec.hint_build": "Check that all dependencies are installed and source files are not empty.",
3063
+ "exec.hint_deploy": "Verify credentials and network access before deploying.",
3064
+ "exec.hint_repetitive": "You are calling the same tool repeatedly with the same arguments. Try a different approach.",
3065
+ "exec.hint_consecutive": "Multiple different tools are failing. Check if the environment is set up correctly.",
3066
+ "exec.hint_read_only": "You have made many read-only tool calls (read_file/glob/grep/browser) without writing anything. Stop exploring — make the edit/write the task needs, or use the plan tool to decide next steps."
3034
3067
  };
3035
3068
  });
3036
3069
 
@@ -3042,8 +3075,10 @@ var init_ru = __esm(() => {
3042
3075
  "file.deleted": "Удалён {path}",
3043
3076
  "file.updated": "Обновлён {path}",
3044
3077
  "file.written": "Записан {path}",
3045
- "file.notfound": "Файл не найден: {path}",
3046
- "file.dir_notfound": "Каталог не найден: {path}",
3078
+ "file.notfound": `Файл не найден: {path}
3079
+ Это НЕ значит, что проект отсутствует — возможно, путь неверный. Выполни list_dir по рабочей директории; создавай файлы только после проверки того, что уже есть.`,
3080
+ "file.dir_notfound": `Каталог не найден: {path}
3081
+ Это НЕ значит, что проект отсутствует — возможно, путь неверный. Выполни list_dir по рабочей директории (и родителю этого пути), чтобы найти реальное расположение, прежде что-то создавать.`,
3047
3082
  "file.path_not_allowed": "Путь недопустим: {path}",
3048
3083
  "file.path_not_allowed_short": "Путь недопустим",
3049
3084
  "file.is_directory": "Это каталог, используйте rmdir: {path}",
@@ -3550,6 +3585,7 @@ var init_ru = __esm(() => {
3550
3585
  "setup.security_header": `
3551
3586
  --- Безопасность ---`,
3552
3587
  "setup.security_status_off": " Безопасность: ВЫКЛ (по умолчанию — все команды разрешены)",
3588
+ "setup.security_status_on": " Безопасность: ВКЛ (по умолчанию — политика balanced)",
3553
3589
  "setup.security_configure": " Настроить безопасность? (y/N)",
3554
3590
  "setup.security_bash_block": " Блокировать опасные команды (rm, sudo, ssh и т.д.)? (y/N)",
3555
3591
  "setup.security_flags_block": " Блокировать опасные флаги (--force, -rf и т.д.)? (y/N)",
@@ -3605,6 +3641,7 @@ var init_ru = __esm(() => {
3605
3641
  "exec.error_search_failed": 'Поиск в интернете для "{query}" ничего не дал.',
3606
3642
  "exec.error_search_no_query": "Текст ошибки незначимый — поиск в интернете пропущен.",
3607
3643
  "exec.npm_exec_hint": '"could not determine executable to run" — у пакета/скрипта нет "bin". Используй "npm run <script>" (скрипт должен быть в package.json) или "bunx <pkg>" для пакета с объявленным bin.',
3644
+ "exec.task_reminder": "Задача: {task}. Продолжай работу — не повторяй неудачные действия.",
3608
3645
  "hall.max_retries_exhausted": "Модель вернула пустой или недостаточный ответ после нескольких попыток",
3609
3646
  "hall.short_response": "Слишком короткий или пустой ответ",
3610
3647
  "hall.repetitive": "Слишком повторяющийся ответ ({pct}% совпадение)",
@@ -3700,7 +3737,8 @@ var init_ru = __esm(() => {
3700
3737
  "ctx.delta_pos": "контекст +{tokens}",
3701
3738
  "ctx.delta_neg": "контекст -{tokens} ↓",
3702
3739
  "ctx.delta_zero": "контекст ±0",
3703
- "file.notfound_resolved": "Файл не найден: {path} (резолвится в {resolved})",
3740
+ "file.notfound_resolved": `Файл не найден: {path} (резолвится в {resolved})
3741
+ Путь был склеен с рабочей директорией, потому что в исходном виде он не существует. Выполни list_dir, чтобы найти корректный путь.`,
3704
3742
  "bash.echo_write_blocked": "Запись файлов через echo/printf ненадёжна в Windows cmd.exe (кавычки и многострочность ломаются). Используй инструмент write_file вместо этого (цель: {path}).",
3705
3743
  "updater.check_error": "[updater] Ошибка проверки обновления: {error}",
3706
3744
  "updater.available": "[updater] Доступно обновление: {current} → {latest}. Выполните `npm install -g micro-models-agent` для обновления.",
@@ -3715,7 +3753,22 @@ var init_ru = __esm(() => {
3715
3753
  "tool.friendly.enable_tools": "Включить тулы",
3716
3754
  "cli.provider_base_hint": "Базовый URL установлен: {baseUrl}",
3717
3755
  "repl.cost": "Итого потрачено: {cost}",
3718
- "repl.tokens": "Потрачено токенов: {tokens}"
3756
+ "repl.tokens": "Потрачено токенов: {tokens}",
3757
+ "repl.ctrl_c_interrupt": `
3758
+ [Ctrl+C] Остановка агента... (ещё раз — принудительно)`,
3759
+ "exec.stop_directive": 'STOP. Step {stepId} ("{description}") took {iterations} iterations with no progress. DO NOT continue this step. Immediately call: plan update step={stepId} status=done (if code works despite warnings) OR plan update step={stepId} status=skipped note="reason". Do NOT make any other tool calls before updating the plan.',
3760
+ "exec.test_runner_fail": "[test-runner] {framework}: {failed} test(s) FAILING, {passed} passing — do NOT mark verification steps as done while tests fail. Investigate the failures, fix the code, then re-run the tests.",
3761
+ "exec.test_runner_pass": "[test-runner] {framework}: all {passed} test(s) passing.",
3762
+ "plan.subtasks_done": "All sub-tasks done — call plan update step={stepId} status=done to complete this step.",
3763
+ "plan.no_subtasks": "(no sub-tasks)",
3764
+ "plan.step_label": "Step {stepId}: {description}",
3765
+ "exec.hint_deps": "Check if a lock file exists (package-lock.json, poetry.lock). If missing, run the install command first.",
3766
+ "exec.hint_test": "Make sure the source files exist and have real code before running tests.",
3767
+ "exec.hint_build": "Check that all dependencies are installed and source files are not empty.",
3768
+ "exec.hint_deploy": "Verify credentials and network access before deploying.",
3769
+ "exec.hint_repetitive": "You are calling the same tool repeatedly with the same arguments. Try a different approach.",
3770
+ "exec.hint_consecutive": "Multiple different tools are failing. Check if the environment is set up correctly.",
3771
+ "exec.hint_read_only": "You have made many read-only tool calls (read_file/glob/grep/browser) without writing anything. Stop exploring — make the edit/write the task needs, or use the plan tool to decide next steps."
3719
3772
  };
3720
3773
  });
3721
3774
 
@@ -3886,7 +3939,7 @@ function validateExpertConfig(config, allToolTags) {
3886
3939
  }
3887
3940
 
3888
3941
  // src/modules/security/encryption.ts
3889
- import { createCipheriv, createDecipheriv, randomBytes, scryptSync } from "node:crypto";
3942
+ import { createCipheriv, createDecipheriv, randomBytes, scryptSync, timingSafeEqual } from "node:crypto";
3890
3943
  import { homedir } from "os";
3891
3944
  import { join as join3 } from "path";
3892
3945
  import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, existsSync as existsSync3, copyFileSync as copyFileSync2 } from "fs";
@@ -3903,8 +3956,8 @@ function getOrCreateEncryptionKey(config) {
3903
3956
  try {
3904
3957
  writeFileSync2(keyPath, key.toString("base64"), { mode: 384 });
3905
3958
  return key;
3906
- } catch {
3907
- return key;
3959
+ } catch (e) {
3960
+ throw new Error(`Cannot write encryption key to ${keyPath}: ${e instanceof Error ? e.message : String(e)}`);
3908
3961
  }
3909
3962
  }
3910
3963
  try {
@@ -3949,7 +4002,7 @@ function decryptString(encryptedText, key) {
3949
4002
  hmac.update(iv);
3950
4003
  hmac.update(Buffer.from(encrypted, "base64"));
3951
4004
  const computedTag = hmac.digest();
3952
- if (!computedTag.equals(authTag)) {
4005
+ if (computedTag.length !== authTag.length || !timingSafeEqual(computedTag, authTag)) {
3953
4006
  throw new Error("Authentication failed: invalid tag");
3954
4007
  }
3955
4008
  const decipher = createDecipheriv("aes-256-cbc", encKey, iv);
@@ -4133,6 +4186,22 @@ function normalizeLspServerArgs(config) {
4133
4186
  }
4134
4187
  }
4135
4188
  }
4189
+ function normalizeStaleSecurityConfig(config) {
4190
+ const sec = config.security;
4191
+ if (!sec || sec.enabled !== false)
4192
+ return;
4193
+ const bash = sec.bash;
4194
+ if (!bash || bash.enabled !== false || bash.blockDangerousFlags !== false)
4195
+ return;
4196
+ const LEGACY_OPERATORS = [">", ">>", "2>", "2>>", "`"];
4197
+ if (JSON.stringify(bash.dangerousOperators ?? []) !== JSON.stringify(LEGACY_OPERATORS))
4198
+ return;
4199
+ const defaultCmds = new Set(DEFAULT_SECURITY_CONFIG.bash.blacklist);
4200
+ const blacklist = Array.isArray(bash.blacklist) ? bash.blacklist : [];
4201
+ if (blacklist.some((cmd) => !defaultCmds.has(cmd)))
4202
+ return;
4203
+ config.security = JSON.parse(JSON.stringify(DEFAULT_SECURITY_CONFIG));
4204
+ }
4136
4205
  function loadJSON(path) {
4137
4206
  try {
4138
4207
  if (existsSync4(path)) {
@@ -4206,6 +4275,7 @@ function loadConfig(options) {
4206
4275
  config.security.contentScan.dangerousPatterns = restoreDangerousPatterns(config.security.contentScan.dangerousPatterns, DEFAULT_SECURITY_CONFIG.contentScan.dangerousPatterns);
4207
4276
  }
4208
4277
  normalizeLspServerArgs(config);
4278
+ normalizeStaleSecurityConfig(config);
4209
4279
  config = applyEnvVars(config);
4210
4280
  try {
4211
4281
  const encryptor = new ConfigEncryptor;
@@ -4568,19 +4638,19 @@ class Logger {
4568
4638
  this.fileLog.cleanupOldLogs(maxDays, maxFiles);
4569
4639
  }
4570
4640
  logLLMRequest(model, messagesCount, promptPreview, caller) {
4571
- this.fileLog.logLLMRequest(model, messagesCount, promptPreview, caller);
4641
+ this.fileLog.logLLMRequest(model, messagesCount, sanitizeLogMessage(promptPreview), caller);
4572
4642
  }
4573
4643
  logLLMResponse(model, responseLength, genTimeMs, error, caller) {
4574
4644
  this.fileLog.logLLMResponse(model, responseLength, genTimeMs, error, caller);
4575
4645
  }
4576
4646
  logToolCall(tool, preview, result) {
4577
- this.fileLog.logToolCall(tool, preview, result);
4647
+ this.fileLog.logToolCall(tool, sanitizeLogMessage(preview), result !== undefined ? sanitizeLogMessage(result) : undefined);
4578
4648
  }
4579
4649
  logToolOutput(tool, output, exitCode) {
4580
- this.fileLog.logToolOutput(tool, output, exitCode);
4650
+ this.fileLog.logToolOutput(tool, sanitizeLogMessage(output), exitCode);
4581
4651
  }
4582
4652
  logREPL(tag, content) {
4583
- this.fileLog.logREPL(tag, content);
4653
+ this.fileLog.logREPL(tag, sanitizeLogMessage(content));
4584
4654
  }
4585
4655
  child(prefix) {
4586
4656
  const childLogger = new Logger(this.level, this.prefix ? `${this.prefix}:${prefix}` : prefix);
@@ -4651,6 +4721,24 @@ class Logger {
4651
4721
  type,
4652
4722
  meta: this.sanitizeMeta(data)
4653
4723
  }) + `
4724
+ `, "utf-8");
4725
+ } catch {}
4726
+ }
4727
+ logSilent(level, msg, meta) {
4728
+ const logTarget = this.sessionDir ?? this.logDir;
4729
+ if (!logTarget)
4730
+ return;
4731
+ const ts = new Date().toISOString();
4732
+ const sanitizedMsg = sanitizeLogMessage(msg);
4733
+ const sanitizedMeta = meta ? this.sanitizeMeta(meta) : undefined;
4734
+ try {
4735
+ appendFileSync2(join6(logTarget, "app.jsonl"), JSON.stringify({
4736
+ level,
4737
+ ts,
4738
+ prefix: this.prefix,
4739
+ msg: sanitizedMsg,
4740
+ meta: sanitizedMeta ?? null
4741
+ }) + `
4654
4742
  `, "utf-8");
4655
4743
  } catch {}
4656
4744
  }
@@ -5244,15 +5332,11 @@ class OpenAICompatProvider {
5244
5332
  const streamResult = this.doStream(messages, tools, signal, options);
5245
5333
  let hasToolCall = false;
5246
5334
  let hasText = false;
5247
- let reasoningAcc = "";
5248
5335
  for await (const chunk of streamResult) {
5249
5336
  if (chunk.type === "tool_call")
5250
5337
  hasToolCall = true;
5251
5338
  if (chunk.type === "text")
5252
5339
  hasText = true;
5253
- if (chunk.type === "reasoning" && chunk.content) {
5254
- reasoningAcc += chunk.content;
5255
- }
5256
5340
  yield chunk;
5257
5341
  }
5258
5342
  if (!hasToolCall && !hasText) {
@@ -5297,30 +5381,7 @@ class OpenAICompatProvider {
5297
5381
  maxTokens: options?.maxTokens ?? this.config.maxCompletionTokens ?? 4096,
5298
5382
  reasoningEffort: options?.reasoningEffort
5299
5383
  });
5300
- const headers = {
5301
- "Content-Type": "application/json"
5302
- };
5303
- if (this.config.apiKey && this.config.apiKey !== "not-needed") {
5304
- headers["Authorization"] = `Bearer ${this.config.apiKey}`;
5305
- }
5306
- const controller = new AbortController;
5307
- let timedOut = false;
5308
- const timeoutId = setTimeout(() => {
5309
- timedOut = true;
5310
- controller.abort();
5311
- }, REQUEST_TIMEOUT_MS);
5312
- const abortSignal = (() => {
5313
- if (!signal)
5314
- return controller.signal;
5315
- try {
5316
- return AbortSignal.any([controller.signal, signal]);
5317
- } catch {
5318
- signal.addEventListener("abort", () => controller.abort(), {
5319
- once: true
5320
- });
5321
- return controller.signal;
5322
- }
5323
- })();
5384
+ const { headers, abortSignal, cleanup, isTimeout, flagTimeout, controller } = this.buildRequestSetup(signal);
5324
5385
  let response;
5325
5386
  try {
5326
5387
  response = await this.fetchWithRetry(`${this.config.baseUrl}/chat/completions`, {
@@ -5337,7 +5398,7 @@ class OpenAICompatProvider {
5337
5398
  throw wrapped;
5338
5399
  }
5339
5400
  if (!response.ok) {
5340
- clearTimeout(timeoutId);
5401
+ cleanup();
5341
5402
  const errorText = await response.text();
5342
5403
  const err = new Error(t("error.llm_api", {
5343
5404
  status: response.status,
@@ -5349,7 +5410,7 @@ class OpenAICompatProvider {
5349
5410
  }
5350
5411
  const reader = response.body?.getReader();
5351
5412
  if (!reader) {
5352
- clearTimeout(timeoutId);
5413
+ cleanup();
5353
5414
  throw new Error(t("error.no_response_body"));
5354
5415
  }
5355
5416
  const decoder = new TextDecoder;
@@ -5359,18 +5420,18 @@ class OpenAICompatProvider {
5359
5420
  let sawDone = false;
5360
5421
  const readIdle = () => new Promise((resolve, reject) => {
5361
5422
  const idleTimer = setTimeout(() => {
5362
- timedOut = true;
5423
+ flagTimeout();
5363
5424
  controller.abort();
5364
5425
  }, idleTimeoutMs);
5365
5426
  reader.read().then((result) => {
5366
5427
  clearTimeout(idleTimer);
5367
- if (timedOut)
5428
+ if (isTimeout())
5368
5429
  reject(new Error(t("error.llm_stream_idle", { timeout: idleTimeoutMs })));
5369
5430
  else
5370
5431
  resolve(result);
5371
5432
  }, (err) => {
5372
5433
  clearTimeout(idleTimer);
5373
- if (timedOut)
5434
+ if (isTimeout())
5374
5435
  reject(new Error(t("error.llm_stream_idle", { timeout: idleTimeoutMs })));
5375
5436
  else
5376
5437
  reject(err);
@@ -5457,20 +5518,12 @@ class OpenAICompatProvider {
5457
5518
  yield { type: "done", usage };
5458
5519
  }
5459
5520
  } finally {
5460
- clearTimeout(timeoutId);
5521
+ cleanup();
5461
5522
  reader.releaseLock();
5462
5523
  }
5463
5524
  return sawDone;
5464
5525
  }
5465
- async doNonStreaming(messages, tools, signal, options) {
5466
- const body = buildRequestBody({
5467
- model: this.model,
5468
- messages,
5469
- tools,
5470
- stream: false,
5471
- maxTokens: options?.maxTokens ?? this.config.maxCompletionTokens ?? 4096,
5472
- reasoningEffort: options?.reasoningEffort
5473
- });
5526
+ buildRequestSetup(signal) {
5474
5527
  const headers = {
5475
5528
  "Content-Type": "application/json"
5476
5529
  };
@@ -5495,6 +5548,27 @@ class OpenAICompatProvider {
5495
5548
  return controller.signal;
5496
5549
  }
5497
5550
  })();
5551
+ return {
5552
+ headers,
5553
+ abortSignal,
5554
+ cleanup: () => clearTimeout(timeoutId),
5555
+ isTimeout: () => timedOut,
5556
+ flagTimeout: () => {
5557
+ timedOut = true;
5558
+ },
5559
+ controller
5560
+ };
5561
+ }
5562
+ async doNonStreaming(messages, tools, signal, options) {
5563
+ const body = buildRequestBody({
5564
+ model: this.model,
5565
+ messages,
5566
+ tools,
5567
+ stream: false,
5568
+ maxTokens: options?.maxTokens ?? this.config.maxCompletionTokens ?? 4096,
5569
+ reasoningEffort: options?.reasoningEffort
5570
+ });
5571
+ const { headers, abortSignal, cleanup, isTimeout } = this.buildRequestSetup(signal);
5498
5572
  try {
5499
5573
  const response = await this.fetchWithRetry(`${this.config.baseUrl}/chat/completions`, {
5500
5574
  method: "POST",
@@ -5547,12 +5621,12 @@ class OpenAICompatProvider {
5547
5621
  }
5548
5622
  return chunks;
5549
5623
  } catch (err) {
5550
- if (timedOut && err?.name === "AbortError") {
5624
+ if (isTimeout() && err?.name === "AbortError") {
5551
5625
  throw new Error(t("error.llm_timeout", { timeout: REQUEST_TIMEOUT_MS }));
5552
5626
  }
5553
5627
  throw err instanceof Error ? err : new Error(String(err));
5554
5628
  } finally {
5555
- clearTimeout(timeoutId);
5629
+ cleanup();
5556
5630
  }
5557
5631
  }
5558
5632
  countTokens(text) {
@@ -5920,17 +5994,76 @@ var init_executor = __esm(() => {
5920
5994
  init_i18n();
5921
5995
  });
5922
5996
 
5997
+ // src/tools/path-utils.ts
5998
+ import { resolve, normalize, dirname as dirname2, basename, sep, relative, isAbsolute } from "path";
5999
+ import { existsSync as existsSync7 } from "fs";
6000
+ function isInsideDir(targetPath, dirPath) {
6001
+ const norm = (p) => process.platform === "win32" ? p.toLowerCase() : p;
6002
+ const t2 = norm(resolve(targetPath));
6003
+ const d = norm(resolve(dirPath));
6004
+ if (t2 === d)
6005
+ return true;
6006
+ const rel = relative(d, t2);
6007
+ return rel.length > 0 && !isAbsolute(rel) && !rel.startsWith("..");
6008
+ }
6009
+ function matchesScopeEntry(targetResolved, entryResolved) {
6010
+ if (targetResolved === entryResolved)
6011
+ return true;
6012
+ return isInsideDir(targetResolved, entryResolved);
6013
+ }
6014
+ function safeResolvePath(baseDir, userPath) {
6015
+ const asIs = resolve(normalize(userPath));
6016
+ if (userPath.startsWith("/") || userPath.startsWith("\\")) {
6017
+ if (existsSync7(asIs) || existsSync7(dirname2(asIs)))
6018
+ return asIs;
6019
+ }
6020
+ const norm = normalize(userPath);
6021
+ const stripped = norm.replace(/^[/\\]/, "");
6022
+ const resolved = resolve(baseDir, stripped);
6023
+ if (existsSync7(resolved) || existsSync7(dirname2(resolved)))
6024
+ return resolved;
6025
+ const baseNorm = normalize(baseDir);
6026
+ let cur = baseNorm;
6027
+ while (cur && cur !== dirname2(cur)) {
6028
+ const name = basename(cur);
6029
+ if (!name) {
6030
+ cur = dirname2(cur);
6031
+ continue;
6032
+ }
6033
+ let idx = stripped.toLowerCase().indexOf(name.toLowerCase());
6034
+ while (idx >= 0) {
6035
+ const afterIdx = idx + name.length;
6036
+ const afterChar = stripped[afterIdx];
6037
+ if (afterChar && afterChar !== "\\" && afterChar !== "/") {
6038
+ const fixed = stripped.slice(0, afterIdx) + sep + stripped.slice(afterIdx);
6039
+ const fixedResolved = resolve(baseDir, normalize(fixed));
6040
+ if (existsSync7(fixedResolved) || existsSync7(dirname2(fixedResolved))) {
6041
+ return fixedResolved;
6042
+ }
6043
+ const fromParent = resolve(dirname2(cur), normalize(fixed));
6044
+ if (existsSync7(fromParent) || existsSync7(dirname2(fromParent))) {
6045
+ return fromParent;
6046
+ }
6047
+ }
6048
+ idx = stripped.toLowerCase().indexOf(name.toLowerCase(), idx + 1);
6049
+ }
6050
+ cur = dirname2(cur);
6051
+ }
6052
+ return resolved;
6053
+ }
6054
+ var init_path_utils = () => {};
6055
+
5923
6056
  // src/modules/security/path-validator.ts
5924
6057
  var exports_path_validator = {};
5925
6058
  __export(exports_path_validator, {
5926
6059
  isPathWritable: () => isPathWritable,
5927
6060
  isPathInScope: () => isPathInScope
5928
6061
  });
5929
- import { resolve, normalize } from "path";
6062
+ import { resolve as resolve2, normalize as normalize2 } from "path";
5930
6063
  function isPathInScope(baseDir, targetPath, scope, securityConfig) {
5931
- const baseResolved = resolve(baseDir);
5932
- const targetResolved = resolve(baseDir, normalize(targetPath));
5933
- if (!targetResolved.startsWith(baseResolved)) {
6064
+ const baseResolved = resolve2(baseDir);
6065
+ const targetResolved = resolve2(baseDir, normalize2(targetPath));
6066
+ if (!isInsideDir(targetResolved, baseResolved)) {
5934
6067
  return {
5935
6068
  allowed: false,
5936
6069
  reason: "Path is outside the base working directory"
@@ -5938,8 +6071,8 @@ function isPathInScope(baseDir, targetPath, scope, securityConfig) {
5938
6071
  }
5939
6072
  if (!securityConfig?.enabled) {
5940
6073
  if (scope) {
5941
- const isReadScope = scope.read_only_files.some((f) => targetResolved.startsWith(resolve(baseDir, f)));
5942
- const isWriteScope = scope.allowed_files.some((f) => targetResolved.startsWith(resolve(baseDir, f)));
6074
+ const isReadScope = scope.read_only_files.some((f) => matchesScopeEntry(targetResolved, resolve2(baseDir, f)));
6075
+ const isWriteScope = scope.allowed_files.some((f) => matchesScopeEntry(targetResolved, resolve2(baseDir, f)));
5943
6076
  if (isWriteScope)
5944
6077
  return { allowed: true };
5945
6078
  if (isReadScope)
@@ -5979,8 +6112,8 @@ function isPathInScope(baseDir, targetPath, scope, securityConfig) {
5979
6112
  if (!scope) {
5980
6113
  return { allowed: true };
5981
6114
  }
5982
- const isRead = scope.read_only_files.some((f) => targetResolved.startsWith(resolve(baseDir, f)));
5983
- const isWrite = scope.allowed_files.some((f) => targetResolved.startsWith(resolve(baseDir, f)));
6115
+ const isRead = scope.read_only_files.some((f) => matchesScopeEntry(targetResolved, resolve2(baseDir, f)));
6116
+ const isWrite = scope.allowed_files.some((f) => matchesScopeEntry(targetResolved, resolve2(baseDir, f)));
5984
6117
  if (isWrite)
5985
6118
  return { allowed: true };
5986
6119
  if (isRead)
@@ -5991,9 +6124,9 @@ function isPathInScope(baseDir, targetPath, scope, securityConfig) {
5991
6124
  };
5992
6125
  }
5993
6126
  function isPathWritable(baseDir, targetPath, scope, securityConfig) {
5994
- const baseResolved = resolve(baseDir);
5995
- const targetResolved = resolve(baseDir, normalize(targetPath));
5996
- if (!targetResolved.startsWith(baseResolved)) {
6127
+ const baseResolved = resolve2(baseDir);
6128
+ const targetResolved = resolve2(baseDir, normalize2(targetPath));
6129
+ if (!isInsideDir(targetResolved, baseResolved)) {
5997
6130
  return {
5998
6131
  allowed: false,
5999
6132
  reason: "Path is outside the base working directory"
@@ -6001,7 +6134,7 @@ function isPathWritable(baseDir, targetPath, scope, securityConfig) {
6001
6134
  }
6002
6135
  if (!securityConfig?.enabled) {
6003
6136
  if (scope) {
6004
- const isWriteScope = scope.allowed_files.some((f) => targetResolved.startsWith(resolve(baseDir, f)));
6137
+ const isWriteScope = scope.allowed_files.some((f) => matchesScopeEntry(targetResolved, resolve2(baseDir, f)));
6005
6138
  if (isWriteScope)
6006
6139
  return { allowed: true };
6007
6140
  return {
@@ -6039,7 +6172,7 @@ function isPathWritable(baseDir, targetPath, scope, securityConfig) {
6039
6172
  if (!scope) {
6040
6173
  return { allowed: true };
6041
6174
  }
6042
- const isWrite = scope.allowed_files.some((f) => targetResolved.startsWith(resolve(baseDir, f)));
6175
+ const isWrite = scope.allowed_files.some((f) => matchesScopeEntry(targetResolved, resolve2(baseDir, f)));
6043
6176
  if (isWrite)
6044
6177
  return { allowed: true };
6045
6178
  return {
@@ -6048,25 +6181,19 @@ function isPathWritable(baseDir, targetPath, scope, securityConfig) {
6048
6181
  };
6049
6182
  }
6050
6183
  function matchesGlobPattern(targetPath, baseDir, pattern) {
6051
- const resolvedBaseDir = resolve(baseDir);
6052
- const resolvedTargetPath = resolve(targetPath);
6184
+ const resolvedBaseDir = resolve2(baseDir);
6185
+ const resolvedTargetPath = resolve2(targetPath);
6053
6186
  if (pattern.endsWith("/")) {
6054
6187
  const dirPattern = pattern.slice(0, -1);
6055
- const resolvedDirPattern = resolve(baseDir, dirPattern);
6056
- if (resolvedTargetPath === resolvedDirPattern) {
6057
- return true;
6058
- }
6059
- if (resolvedTargetPath.startsWith(resolvedDirPattern + "/") || resolvedTargetPath.startsWith(resolvedDirPattern + "\\")) {
6060
- return true;
6061
- }
6062
- return false;
6188
+ const resolvedDirPattern = resolve2(baseDir, dirPattern);
6189
+ return isInsideDir(resolvedTargetPath, resolvedDirPattern);
6063
6190
  }
6064
6191
  if (pattern.includes("**/")) {
6065
6192
  const parts = pattern.split("**/");
6066
6193
  const prefix = parts[0];
6067
6194
  const suffix = parts[1];
6068
- const resolvedPrefix = resolve(baseDir, prefix);
6069
- if (!resolvedTargetPath.startsWith(resolvedPrefix + "/") && !resolvedTargetPath.startsWith(resolvedPrefix + "\\")) {
6195
+ const resolvedPrefix = resolve2(baseDir, prefix);
6196
+ if (!isInsideDir(resolvedTargetPath, resolvedPrefix)) {
6070
6197
  return false;
6071
6198
  }
6072
6199
  const remainingPath = resolvedTargetPath.slice(resolvedPrefix.length);
@@ -6079,8 +6206,8 @@ function matchesGlobPattern(targetPath, baseDir, pattern) {
6079
6206
  const parts = pattern.split("**");
6080
6207
  const prefix = parts[0];
6081
6208
  const suffix = parts[1];
6082
- const resolvedPrefix = resolve(baseDir, prefix);
6083
- if (!resolvedTargetPath.startsWith(resolvedPrefix + "/") && !resolvedTargetPath.startsWith(resolvedPrefix + "\\")) {
6209
+ const resolvedPrefix = resolve2(baseDir, prefix);
6210
+ if (!isInsideDir(resolvedTargetPath, resolvedPrefix)) {
6084
6211
  return false;
6085
6212
  }
6086
6213
  if (!suffix) {
@@ -6094,9 +6221,15 @@ function matchesGlobPattern(targetPath, baseDir, pattern) {
6094
6221
  const relativePath = resolvedTargetPath.slice(resolvedBaseDir.length + 1).replace(/\\/g, "/");
6095
6222
  return regex.test(relativePath);
6096
6223
  }
6097
- return resolvedTargetPath === resolve(baseDir, pattern);
6224
+ const exactTarget = resolve2(baseDir, pattern);
6225
+ if (process.platform === "win32") {
6226
+ return resolvedTargetPath.toLowerCase() === exactTarget.toLowerCase();
6227
+ }
6228
+ return resolvedTargetPath === exactTarget;
6098
6229
  }
6099
- var init_path_validator = () => {};
6230
+ var init_path_validator = __esm(() => {
6231
+ init_path_utils();
6232
+ });
6100
6233
 
6101
6234
  // src/modules/security/audit-notifier.ts
6102
6235
  var exports_audit_notifier = {};
@@ -6106,8 +6239,8 @@ __export(exports_audit_notifier, {
6106
6239
  DEFAULT_AUDIT_NOTIFIER_CONFIG: () => DEFAULT_AUDIT_NOTIFIER_CONFIG,
6107
6240
  AuditNotifier: () => AuditNotifier
6108
6241
  });
6109
- import { writeFileSync as writeFileSync4, appendFileSync as appendFileSync3, existsSync as existsSync7, mkdirSync as mkdirSync5 } from "fs";
6110
- import { join as join7, dirname as dirname2 } from "path";
6242
+ import { writeFileSync as writeFileSync4, appendFileSync as appendFileSync3, existsSync as existsSync8, mkdirSync as mkdirSync5 } from "fs";
6243
+ import { join as join7, dirname as dirname3 } from "path";
6111
6244
  import { homedir as homedir2 } from "os";
6112
6245
  import { readFileSync as readFileSync4 } from "fs";
6113
6246
 
@@ -6134,7 +6267,7 @@ class AuditNotifier {
6134
6267
  }
6135
6268
  ensureLogDirectory() {
6136
6269
  if (this.config.filePath) {
6137
- const dir = dirname2(this.config.filePath);
6270
+ const dir = dirname3(this.config.filePath);
6138
6271
  mkdirSync5(dir, { recursive: true });
6139
6272
  }
6140
6273
  }
@@ -6251,13 +6384,13 @@ class AuditNotifier {
6251
6384
  } catch (error) {
6252
6385
  item.retries++;
6253
6386
  const delay = Math.pow(2, item.retries) * 1000;
6254
- await new Promise((resolve2) => setTimeout(resolve2, delay));
6387
+ await new Promise((resolve3) => setTimeout(resolve3, delay));
6255
6388
  }
6256
6389
  }
6257
6390
  this.isProcessing = false;
6258
6391
  }
6259
6392
  readNotifications(limit = 100) {
6260
- if (!this.config.filePath || !existsSync7(this.config.filePath)) {
6393
+ if (!this.config.filePath || !existsSync8(this.config.filePath)) {
6261
6394
  return [];
6262
6395
  }
6263
6396
  try {
@@ -6331,21 +6464,21 @@ var init_audit_notifier = __esm(() => {
6331
6464
  });
6332
6465
 
6333
6466
  // src/modules/security/audit-log.ts
6334
- import { existsSync as existsSync8, mkdirSync as mkdirSync6, appendFileSync as appendFileSync4 } from "fs";
6335
- import { resolve as resolve2, join as join8 } from "path";
6467
+ import { existsSync as existsSync9, mkdirSync as mkdirSync6, appendFileSync as appendFileSync4 } from "fs";
6468
+ import { resolve as resolve3, join as join8 } from "path";
6336
6469
  import { homedir as homedir3 } from "os";
6337
6470
  function getAuditDir() {
6338
6471
  return _sessionAuditDir ?? _globalAuditDir;
6339
6472
  }
6340
6473
  function setAuditSessionDir(dir) {
6341
6474
  _sessionAuditDir = dir;
6342
- if (!existsSync8(dir)) {
6475
+ if (!existsSync9(dir)) {
6343
6476
  mkdirSync6(dir, { recursive: true, mode: 448 });
6344
6477
  }
6345
6478
  }
6346
6479
  function logAudit(entry) {
6347
6480
  const dir = getAuditDir();
6348
- if (!existsSync8(dir)) {
6481
+ if (!existsSync9(dir)) {
6349
6482
  mkdirSync6(dir, { recursive: true, mode: 448 });
6350
6483
  }
6351
6484
  try {
@@ -6410,59 +6543,75 @@ function logSecurityBlock(sessionId, action, reason, details) {
6410
6543
  var _globalAuditDir, _sessionAuditDir = null;
6411
6544
  var init_audit_log = __esm(() => {
6412
6545
  init_audit_notifier();
6413
- _globalAuditDir = resolve2(homedir3(), ".mma", "logs");
6546
+ _globalAuditDir = resolve3(homedir3(), ".mma", "logs");
6414
6547
  });
6415
6548
 
6416
- // src/tools/path-utils.ts
6417
- import { resolve as resolve3, normalize as normalize2, dirname as dirname3, basename, sep } from "path";
6418
- import { existsSync as existsSync9 } from "fs";
6419
- function safeResolvePath(baseDir, userPath) {
6420
- const norm = normalize2(userPath);
6421
- const stripped = norm.replace(/^[/\\]/, "");
6422
- const resolved = resolve3(baseDir, stripped);
6423
- if (existsSync9(resolved) || existsSync9(dirname3(resolved)))
6424
- return resolved;
6425
- const baseNorm = normalize2(baseDir);
6426
- let cur = baseNorm;
6427
- while (cur && cur !== dirname3(cur)) {
6428
- const name = basename(cur);
6429
- if (!name) {
6430
- cur = dirname3(cur);
6431
- continue;
6432
- }
6433
- let idx = stripped.toLowerCase().indexOf(name.toLowerCase());
6434
- while (idx >= 0) {
6435
- const afterIdx = idx + name.length;
6436
- const afterChar = stripped[afterIdx];
6437
- if (afterChar && afterChar !== "\\" && afterChar !== "/") {
6438
- const fixed = stripped.slice(0, afterIdx) + sep + stripped.slice(afterIdx);
6439
- const fixedResolved = resolve3(baseDir, normalize2(fixed));
6440
- if (existsSync9(fixedResolved) || existsSync9(dirname3(fixedResolved))) {
6441
- return fixedResolved;
6442
- }
6443
- const fromParent = resolve3(dirname3(cur), normalize2(fixed));
6444
- if (existsSync9(fromParent) || existsSync9(dirname3(fromParent))) {
6445
- return fromParent;
6549
+ // src/tools/read-file.ts
6550
+ import { readFileSync as readFileSync5, existsSync as existsSync10, statSync as statSync2, openSync, readSync, closeSync } from "fs";
6551
+ import { extname } from "path";
6552
+ function readLineSlice(path, offset, limit) {
6553
+ const fd = openSync(path, "r");
6554
+ try {
6555
+ const buf = Buffer.alloc(STREAM_CHUNK);
6556
+ let pos = 0;
6557
+ let carry = "";
6558
+ let lineNumber = 1;
6559
+ let total = 0;
6560
+ const collected = [];
6561
+ const endLine = offset + limit;
6562
+ while (true) {
6563
+ const bytes = readSync(fd, buf, 0, STREAM_CHUNK, pos);
6564
+ if (bytes === 0)
6565
+ break;
6566
+ pos += bytes;
6567
+ const chunk = carry + buf.toString("utf-8", 0, bytes);
6568
+ const parts = chunk.split(`
6569
+ `);
6570
+ carry = parts.pop() ?? "";
6571
+ for (const line of parts) {
6572
+ total++;
6573
+ if (lineNumber >= offset && lineNumber < endLine)
6574
+ collected.push(line);
6575
+ lineNumber++;
6576
+ }
6577
+ if (lineNumber >= endLine && collected.length >= limit) {
6578
+ const rest = [];
6579
+ while (true) {
6580
+ const n = readSync(fd, buf, 0, STREAM_CHUNK, pos);
6581
+ if (n === 0)
6582
+ break;
6583
+ pos += n;
6584
+ rest.push(Buffer.from(buf.subarray(0, n)));
6446
6585
  }
6586
+ const tail = Buffer.concat([...rest, Buffer.from(carry, "utf-8")]);
6587
+ for (let i = 0;i < tail.length; i++)
6588
+ if (tail[i] === 10)
6589
+ total++;
6590
+ total++;
6591
+ return { selected: collected.join(`
6592
+ `), total };
6447
6593
  }
6448
- idx = stripped.toLowerCase().indexOf(name.toLowerCase(), idx + 1);
6449
6594
  }
6450
- cur = dirname3(cur);
6595
+ if (carry.length > 0 || total === 0) {
6596
+ total++;
6597
+ if (lineNumber >= offset && lineNumber < endLine)
6598
+ collected.push(carry);
6599
+ }
6600
+ return { selected: collected.join(`
6601
+ `), total };
6602
+ } finally {
6603
+ closeSync(fd);
6451
6604
  }
6452
- return resolved;
6453
6605
  }
6454
- var init_path_utils = () => {};
6455
-
6456
- // src/tools/read-file.ts
6457
- import { readFileSync as readFileSync5, existsSync as existsSync10 } from "fs";
6458
- import { extname } from "path";
6459
- var DEFAULT_LIMIT = 300, readFileTool;
6606
+ var DEFAULT_LIMIT = 300, LARGE_FILE_BYTES, STREAM_CHUNK, readFileTool;
6460
6607
  var init_read_file = __esm(() => {
6461
6608
  init_i18n();
6462
6609
  init_path_validator();
6463
6610
  init_security();
6464
6611
  init_audit_log();
6465
6612
  init_path_utils();
6613
+ LARGE_FILE_BYTES = 1024 * 1024;
6614
+ STREAM_CHUNK = 256 * 1024;
6466
6615
  readFileTool = {
6467
6616
  name: "read_file",
6468
6617
  icon: "\uD83D\uDC40",
@@ -6506,15 +6655,25 @@ var init_read_file = __esm(() => {
6506
6655
  }) : t("file.notfound", { path });
6507
6656
  return { success: false, output };
6508
6657
  }
6509
- const content = readFileSync5(resolved, "utf-8");
6510
- const lines = content.split(`
6511
- `);
6512
- const total = lines.length;
6513
6658
  const offset = args.offset || 1;
6514
6659
  const limit = args.limit || DEFAULT_LIMIT;
6515
- const end = Math.min(offset - 1 + limit, total);
6516
- const selected = lines.slice(offset - 1, end).join(`
6660
+ let lines;
6661
+ let total;
6662
+ let selected;
6663
+ if (statSync2(resolved).size > LARGE_FILE_BYTES) {
6664
+ const slice = readLineSlice(resolved, offset, limit);
6665
+ total = slice.total;
6666
+ selected = slice.selected;
6667
+ } else {
6668
+ const content = readFileSync5(resolved, "utf-8");
6669
+ lines = content.split(`
6670
+ `);
6671
+ total = lines.length;
6672
+ const end2 = Math.min(offset - 1 + limit, total);
6673
+ selected = lines.slice(offset - 1, end2).join(`
6517
6674
  `);
6675
+ }
6676
+ const end = Math.min(offset - 1 + limit, total);
6518
6677
  const ext = extname(resolved) || "(no extension)";
6519
6678
  const header = t("file.read_header", {
6520
6679
  path,
@@ -6583,6 +6742,16 @@ var init_content_scanner = __esm(() => {
6583
6742
  });
6584
6743
 
6585
6744
  // src/modules/security/session-isolation.ts
6745
+ var exports_session_isolation = {};
6746
+ __export(exports_session_isolation, {
6747
+ isPathInSessionScope: () => isPathInSessionScope,
6748
+ getSessionTempDir: () => getSessionTempDir,
6749
+ getSessionSecurityConfig: () => getSessionSecurityConfig,
6750
+ createSessionFilePath: () => createSessionFilePath,
6751
+ createSessionContext: () => createSessionContext,
6752
+ cleanupSessionTempDir: () => cleanupSessionTempDir,
6753
+ DEFAULT_SESSION_ISOLATION: () => DEFAULT_SESSION_ISOLATION
6754
+ });
6586
6755
  import { join as join9, resolve as resolve5 } from "path";
6587
6756
  import { homedir as homedir4 } from "os";
6588
6757
  import { mkdirSync as mkdirSync7, existsSync as existsSync11 } from "fs";
@@ -6618,6 +6787,26 @@ function getSessionSecurityConfig(globalConfig, sessionContext) {
6618
6787
  contentScan: { ...globalSecurity.contentScan, ...sessionOverrides.contentScan }
6619
6788
  };
6620
6789
  }
6790
+ function getSessionTempDir(sessionContext) {
6791
+ return sessionContext.tempDir;
6792
+ }
6793
+ function cleanupSessionTempDir(sessionContext) {}
6794
+ function createSessionFilePath(sessionContext, relativePath, useTempDir = false) {
6795
+ const baseDir = useTempDir ? sessionContext.tempDir : sessionContext.workingDir;
6796
+ return resolve5(baseDir, relativePath);
6797
+ }
6798
+ function isPathInSessionScope(sessionContext, path) {
6799
+ const resolvedPath = resolve5(path);
6800
+ const workingDir = resolve5(sessionContext.workingDir);
6801
+ const tempDir = resolve5(sessionContext.tempDir);
6802
+ if (resolvedPath.startsWith(workingDir + "/") || resolvedPath.startsWith(workingDir + "\\")) {
6803
+ return true;
6804
+ }
6805
+ if (resolvedPath.startsWith(tempDir + "/") || resolvedPath.startsWith(tempDir + "\\")) {
6806
+ return true;
6807
+ }
6808
+ return false;
6809
+ }
6621
6810
  var DEFAULT_SESSION_ISOLATION;
6622
6811
  var init_session_isolation = __esm(() => {
6623
6812
  init_security();
@@ -7010,7 +7199,7 @@ var init_glob_tool = __esm(() => {
7010
7199
  });
7011
7200
 
7012
7201
  // src/tools/grep-tool.ts
7013
- import { execFileSync } from "child_process";
7202
+ import { spawn } from "child_process";
7014
7203
  import { resolve as resolve7 } from "path";
7015
7204
  function truncateLines(output) {
7016
7205
  const lines = output.split(`
@@ -7021,6 +7210,40 @@ function truncateLines(output) {
7021
7210
  `) + `
7022
7211
  ... (${lines.length - MAX_PREVIEW_LINES} more lines)`;
7023
7212
  }
7213
+ function runSearch(bin, args, cwd, timeoutMs = 30000) {
7214
+ return new Promise((resolvePromise, reject) => {
7215
+ const child = spawn(bin, args, { cwd, windowsHide: true });
7216
+ let out = "";
7217
+ let err = "";
7218
+ let timedOut = false;
7219
+ const timer = setTimeout(() => {
7220
+ timedOut = true;
7221
+ child.kill();
7222
+ }, timeoutMs);
7223
+ child.stdout?.on("data", (d) => {
7224
+ out += d;
7225
+ });
7226
+ child.stderr?.on("data", (d) => {
7227
+ err += d;
7228
+ });
7229
+ child.on("error", (e) => {
7230
+ clearTimeout(timer);
7231
+ reject(e);
7232
+ });
7233
+ child.on("close", (code) => {
7234
+ clearTimeout(timer);
7235
+ if (timedOut) {
7236
+ reject(new Error(`search timed out after ${timeoutMs}ms`));
7237
+ } else if (code === 1) {
7238
+ resolvePromise("");
7239
+ } else if (code === 0) {
7240
+ resolvePromise(out);
7241
+ } else {
7242
+ reject(new Error(err || `exit ${code}`));
7243
+ }
7244
+ });
7245
+ });
7246
+ }
7024
7247
  var grepTool;
7025
7248
  var init_grep_tool = __esm(() => {
7026
7249
  init_i18n();
@@ -7054,28 +7277,23 @@ var init_grep_tool = __esm(() => {
7054
7277
  }
7055
7278
  logBashCommand(ctx.sessionId, `rg ${rgArgs.join(" ")}`, false, "grep-tool");
7056
7279
  try {
7057
- const output = execFileSync("rg", rgArgs, {
7058
- encoding: "utf-8",
7059
- maxBuffer: 1024 * 1024,
7060
- cwd: ctx.baseDir
7061
- });
7280
+ const output = await runSearch("rg", rgArgs, ctx.baseDir);
7062
7281
  return {
7063
7282
  success: true,
7064
7283
  output: truncateLines(output || t("file.no_matches"))
7065
7284
  };
7066
7285
  } catch (e) {
7067
- if (e.status === 1)
7286
+ if (e.code === "ENOENT") {} else if (/timed out/.test(e.message)) {
7287
+ return { success: false, output: t("error.grep_failed", { message: e.message }) };
7288
+ } else if (e.status === 1) {
7068
7289
  return { success: true, output: t("file.no_matches") };
7290
+ }
7069
7291
  try {
7070
7292
  const grepArgs = ["-rn", pattern, searchPath];
7071
7293
  if (args.include) {
7072
7294
  grepArgs.push("--include", String(args.include));
7073
7295
  }
7074
- const output = execFileSync("grep", grepArgs, {
7075
- encoding: "utf-8",
7076
- maxBuffer: 1024 * 1024,
7077
- cwd: ctx.baseDir
7078
- });
7296
+ const output = await runSearch("grep", grepArgs, ctx.baseDir);
7079
7297
  return {
7080
7298
  success: true,
7081
7299
  output: truncateLines(output || t("file.no_matches"))
@@ -7094,7 +7312,7 @@ var init_grep_tool = __esm(() => {
7094
7312
  });
7095
7313
 
7096
7314
  // src/tools/list-dir.ts
7097
- import { readdirSync as readdirSync4, statSync as statSync2, existsSync as existsSync13 } from "fs";
7315
+ import { readdirSync as readdirSync4, statSync as statSync3, existsSync as existsSync13 } from "fs";
7098
7316
  import { resolve as resolve8 } from "path";
7099
7317
  var listDirTool;
7100
7318
  var init_list_dir = __esm(() => {
@@ -7132,7 +7350,7 @@ var init_list_dir = __esm(() => {
7132
7350
  const entries = readdirSync4(resolved);
7133
7351
  const lines = entries.map((e) => {
7134
7352
  const full = resolve8(resolved, e);
7135
- return statSync2(full).isDirectory() ? `${e}/` : e;
7353
+ return statSync3(full).isDirectory() ? `${e}/` : e;
7136
7354
  });
7137
7355
  if (lines.length === 0) {
7138
7356
  return { success: true, output: t("file.empty") };
@@ -7200,7 +7418,7 @@ var init_create_dir = __esm(() => {
7200
7418
  });
7201
7419
 
7202
7420
  // src/tools/delete-file.ts
7203
- import { unlinkSync as unlinkSync3, existsSync as existsSync15, statSync as statSync3, readFileSync as readFileSync8 } from "fs";
7421
+ import { unlinkSync as unlinkSync3, existsSync as existsSync15, statSync as statSync4, readFileSync as readFileSync8 } from "fs";
7204
7422
  var deleteFileTool;
7205
7423
  var init_delete_file = __esm(() => {
7206
7424
  init_i18n();
@@ -7247,7 +7465,7 @@ var init_delete_file = __esm(() => {
7247
7465
  if (!existsSync15(resolved)) {
7248
7466
  return { success: false, output: t("file.notfound", { path }) };
7249
7467
  }
7250
- if (statSync3(resolved).isDirectory()) {
7468
+ if (statSync4(resolved).isDirectory()) {
7251
7469
  return { success: false, output: t("file.is_directory", { path }) };
7252
7470
  }
7253
7471
  const content = readFileSync8(resolved, "utf-8");
@@ -7344,7 +7562,7 @@ var init_move_file = __esm(() => {
7344
7562
  });
7345
7563
 
7346
7564
  // src/tools/file-info.ts
7347
- import { statSync as statSync4, existsSync as existsSync17 } from "fs";
7565
+ import { statSync as statSync5, existsSync as existsSync17 } from "fs";
7348
7566
  var fileInfoTool;
7349
7567
  var init_file_info = __esm(() => {
7350
7568
  init_i18n();
@@ -7378,7 +7596,7 @@ var init_file_info = __esm(() => {
7378
7596
  if (!existsSync17(resolved)) {
7379
7597
  return { success: false, output: t("file.not_found_short", { path }) };
7380
7598
  }
7381
- const stat = statSync4(resolved);
7599
+ const stat = statSync5(resolved);
7382
7600
  return {
7383
7601
  success: true,
7384
7602
  output: JSON.stringify({
@@ -7450,6 +7668,16 @@ function isCommandAllowed(command, securityConfig) {
7450
7668
  reason: `Shell wrapper "${baseForShellCheck}" is blocked — use the bash tool directly`
7451
7669
  };
7452
7670
  }
7671
+ const INTERPRETER_FLAGS = /(?:^|\s)(?:bash|sh|zsh|dash|python|python3|node|bun|perl|ruby)\s+(?:-c|-e|--command|--eval)\b/;
7672
+ if (INTERPRETER_FLAGS.test(trimmedCommand)) {
7673
+ return { allowed: false, reason: "Interpreter -c/-e invocation is blocked" };
7674
+ }
7675
+ if (/\r?\n/.test(trimmedCommand)) {
7676
+ return { allowed: false, reason: "Multi-line commands are blocked" };
7677
+ }
7678
+ if (/[;&|]/.test(trimmedCommand) && /(^|[;&|\s])(rm|del|rd|dd|chmod|chown|wget|curl|nc|netcat|shutdown|reboot|mkfs|fdisk)\b/.test(trimmedCommand)) {
7679
+ return { allowed: false, reason: "Command chaining to a dangerous command is blocked" };
7680
+ }
7453
7681
  if (config.blockDangerousFlags) {
7454
7682
  for (const op of config.dangerousOperators || []) {
7455
7683
  if (containsOperator(trimmedCommand, op)) {
@@ -7550,7 +7778,7 @@ var init_command_validator = __esm(() => {
7550
7778
  });
7551
7779
 
7552
7780
  // src/modules/processes/registry.ts
7553
- import { spawn, spawnSync } from "child_process";
7781
+ import { spawn as spawn2, spawnSync } from "child_process";
7554
7782
  import { platform } from "os";
7555
7783
  function getOemDecoder() {
7556
7784
  if (oemDecoder !== undefined)
@@ -7646,7 +7874,7 @@ class ProcessRegistry {
7646
7874
  this.procs.set(id, entry);
7647
7875
  let child;
7648
7876
  try {
7649
- child = spawn(command, {
7877
+ child = spawn2(command, {
7650
7878
  cwd,
7651
7879
  shell: true,
7652
7880
  windowsHide: true,
@@ -8071,7 +8299,7 @@ ${redirected.output}`
8071
8299
  const workdir = args.workdir ? String(args.workdir) : ctx.baseDir;
8072
8300
  const appConfig = ctx.config || {};
8073
8301
  const fullSecurityConfig = ctx.sessionContext ? getSessionSecurityConfig(appConfig, ctx.sessionContext) : appConfig.security || DEFAULT_SECURITY_CONFIG;
8074
- const securityConfig = fullSecurityConfig.bash || DEFAULT_SECURITY_CONFIG.bash;
8302
+ const securityConfig = fullSecurityConfig.enabled === false ? { ...fullSecurityConfig.bash || DEFAULT_SECURITY_CONFIG.bash, enabled: false } : fullSecurityConfig.bash || DEFAULT_SECURITY_CONFIG.bash;
8075
8303
  const validation = isCommandAllowed(command, securityConfig);
8076
8304
  if (!validation.allowed) {
8077
8305
  logSecurityBlock(ctx.sessionId, "bash_command", validation.reason || "Command blocked by security policy", sanitizeCommandForLog(originalCommand));
@@ -8111,11 +8339,15 @@ Hint: ${cliHint}`;
8111
8339
  }
8112
8340
  const testRun = detectTestResults(output2);
8113
8341
  if (testRun && testRun.failed > 0) {
8114
- output2 = `[test-runner] ${testRun.framework}: ${testRun.failed} test(s) FAILING, ${testRun.passed} passing — do NOT mark verification steps as done while tests fail. Investigate the failures, fix the code, then re-run the tests.
8342
+ output2 = t("exec.test_runner_fail", {
8343
+ framework: testRun.framework,
8344
+ failed: String(testRun.failed),
8345
+ passed: String(testRun.passed)
8346
+ }) + `
8115
8347
 
8116
- ` + output2;
8348
+ ${output2}`;
8117
8349
  } else if (testRun && testRun.failed === 0 && testRun.passed > 0) {
8118
- output2 = `[test-runner] ${testRun.framework}: all ${testRun.passed} test(s) passing.
8350
+ output2 = `${t("exec.test_runner_pass", { framework: testRun.framework, passed: String(testRun.passed) })}
8119
8351
 
8120
8352
  ${output2}`;
8121
8353
  }
@@ -8432,15 +8664,16 @@ class SessionLogger {
8432
8664
  });
8433
8665
  }
8434
8666
  logUser(content) {
8667
+ const sanitized = sanitizeLogMessage(content);
8435
8668
  this.session?.appendMessage({
8436
8669
  role: "user",
8437
- content,
8670
+ content: sanitized,
8438
8671
  timestamp: new Date().toISOString()
8439
8672
  });
8440
8673
  this.session?.appendLog({
8441
8674
  ts: new Date().toISOString(),
8442
8675
  type: "user",
8443
- content
8676
+ content: sanitized
8444
8677
  });
8445
8678
  }
8446
8679
  logAssistant(content, reasoning, toolCalls, iteration) {
@@ -8488,14 +8721,14 @@ class SessionLogger {
8488
8721
  type: "tool_call",
8489
8722
  tool: call.name,
8490
8723
  tool_call_id: call.id,
8491
- args: call.arguments,
8724
+ args: JSON.parse(sanitizeLogMessage(JSON.stringify(call.arguments))),
8492
8725
  iteration
8493
8726
  });
8494
8727
  }
8495
8728
  logToolResult(call, result, duration, iteration) {
8496
8729
  this.session?.appendMessage({
8497
8730
  role: "tool",
8498
- content: result.output.slice(0, 500),
8731
+ content: sanitizeLogMessage(result.output.slice(0, 500)),
8499
8732
  name: call.name,
8500
8733
  timestamp: new Date().toISOString()
8501
8734
  });
@@ -8505,7 +8738,7 @@ class SessionLogger {
8505
8738
  tool: call.name,
8506
8739
  tool_call_id: call.id,
8507
8740
  success: result.success,
8508
- content: result.output.slice(0, 1000),
8741
+ content: sanitizeLogMessage(result.output.slice(0, 1000)),
8509
8742
  diff: result.diff,
8510
8743
  duration,
8511
8744
  iteration
@@ -8610,6 +8843,7 @@ class SessionLogger {
8610
8843
  }
8611
8844
  var init_session_logger = __esm(() => {
8612
8845
  init_audit_log();
8846
+ init_data_sanitizer();
8613
8847
  });
8614
8848
 
8615
8849
  // node_modules/jsonrepair/lib/esm/utils/JSONRepairError.js
@@ -9950,26 +10184,26 @@ class StuckDetector {
9950
10184
  const hints = [];
9951
10185
  const desc = this.currentStepDescription.toLowerCase();
9952
10186
  if (desc.includes("install") || desc.includes("npm") || desc.includes("pip")) {
9953
- hints.push("Check if a lock file exists (package-lock.json, poetry.lock). If missing, run the install command first.");
10187
+ hints.push(t("exec.hint_deps"));
9954
10188
  }
9955
10189
  if (desc.includes("test") || desc.includes("spec")) {
9956
- hints.push("Make sure the source files exist and have real code before running tests.");
10190
+ hints.push(t("exec.hint_test"));
9957
10191
  }
9958
10192
  if (desc.includes("build") || desc.includes("compile")) {
9959
- hints.push("Check that all dependencies are installed and source files are not empty.");
10193
+ hints.push(t("exec.hint_build"));
9960
10194
  }
9961
10195
  if (desc.includes("deploy") || desc.includes("publish")) {
9962
- hints.push("Verify credentials and network access before deploying.");
10196
+ hints.push(t("exec.hint_deploy"));
9963
10197
  }
9964
10198
  const primary = this.getPrimaryReason();
9965
10199
  if (this.hasRepetitiveToolCalls() && primary !== "repetitive") {
9966
- hints.push("You are calling the same tool repeatedly with the same arguments. Try a different approach.");
10200
+ hints.push(t("exec.hint_repetitive"));
9967
10201
  }
9968
10202
  if (this.hasConsecutiveFailures() && primary !== "consecutive") {
9969
- hints.push("Multiple different tools are failing. Check if the environment is set up correctly.");
10203
+ hints.push(t("exec.hint_consecutive"));
9970
10204
  }
9971
10205
  if (this.hasReadOnlyLoop() && primary !== "read-only") {
9972
- hints.push("You have made many read-only tool calls (read_file/glob/grep/browser) without writing anything. Stop exploring — make the edit/write the task needs, or use the plan tool to decide next steps.");
10206
+ hints.push(t("exec.hint_read_only"));
9973
10207
  }
9974
10208
  return hints;
9975
10209
  }
@@ -10264,7 +10498,7 @@ var init_stuck_detector = __esm(() => {
10264
10498
 
10265
10499
  // src/modules/artifacts/store.ts
10266
10500
  import { existsSync as existsSync19, mkdirSync as mkdirSync11, readFileSync as readFileSync10, writeFileSync as writeFileSync7 } from "node:fs";
10267
- import { resolve as resolve10, relative, isAbsolute } from "node:path";
10501
+ import { resolve as resolve10, relative as relative2, isAbsolute as isAbsolute2 } from "node:path";
10268
10502
 
10269
10503
  class ArtifactStore {
10270
10504
  root;
@@ -10281,10 +10515,10 @@ class ArtifactStore {
10281
10515
  return cleaned;
10282
10516
  }
10283
10517
  static isInside(root, candidate) {
10284
- const rel = relative(root, candidate);
10518
+ const rel = relative2(root, candidate);
10285
10519
  if (rel === "")
10286
10520
  return true;
10287
- return !rel.startsWith("..") && !isAbsolute(rel);
10521
+ return !rel.startsWith("..") && !isAbsolute2(rel);
10288
10522
  }
10289
10523
  save(name, content, ext = "md") {
10290
10524
  const safe = ArtifactStore.sanitizeName(name);
@@ -10383,7 +10617,6 @@ async function executeSubtask(subtask, deps, _sharedContext) {
10383
10617
  }
10384
10618
  const maxAttempts = expertConfig.max_attempts || 3;
10385
10619
  let lastError = "";
10386
- let lastResult = null;
10387
10620
  const stuckDetector = new StuckDetector(maxAttempts * 2, maxAttempts);
10388
10621
  for (let attempt = 1;attempt <= maxAttempts; attempt++) {
10389
10622
  stuckDetector.recordIteration(1);
@@ -10447,7 +10680,6 @@ ${(subtask.success_criteria || []).map((c) => `- ${c}`).join(`
10447
10680
  };
10448
10681
  }
10449
10682
  lastError = result.output;
10450
- lastResult = result;
10451
10683
  stuckDetector.recordToolError("subagent", result.output);
10452
10684
  if (attempt < maxAttempts) {
10453
10685
  const hints2 = stuckDetector.getActionableHints();
@@ -10566,21 +10798,36 @@ class MoEExecutor {
10566
10798
  }
10567
10799
  for (let waveIdx = 0;waveIdx < waves.length; waveIdx++) {
10568
10800
  const wave = waves[waveIdx];
10569
- const wavePromises = wave.map((subtask) => executeSubtask(subtask, this.deps, plan.shared_context).then((result) => {
10801
+ const maxParallel = this.deps.config?.security?.rateLimits?.maxParallelTasks ?? 5;
10802
+ let active = 0;
10803
+ const waiters = [];
10804
+ const acquire = () => active < maxParallel ? Promise.resolve(active++) : new Promise((resolve11) => waiters.push(() => resolve11(active++)));
10805
+ const release = () => {
10806
+ active--;
10807
+ const next = waiters.shift();
10808
+ if (next)
10809
+ next();
10810
+ };
10811
+ const wavePromises = wave.map(async (subtask) => {
10812
+ await acquire();
10813
+ let result;
10814
+ try {
10815
+ result = await executeSubtask(subtask, this.deps, plan.shared_context);
10816
+ } catch (e) {
10817
+ result = {
10818
+ subtaskId: subtask.id,
10819
+ success: false,
10820
+ summary: `Unhandled error: ${subtask.id}`,
10821
+ result: "",
10822
+ error: e instanceof Error ? e.message : String(e),
10823
+ durationMs: 0
10824
+ };
10825
+ } finally {
10826
+ release();
10827
+ }
10570
10828
  results.push(result);
10571
10829
  return result;
10572
- }).catch((e) => {
10573
- const errResult = {
10574
- subtaskId: subtask.id,
10575
- success: false,
10576
- summary: `Unhandled error: ${subtask.id}`,
10577
- result: "",
10578
- error: e.message,
10579
- durationMs: 0
10580
- };
10581
- results.push(errResult);
10582
- return errResult;
10583
- }));
10830
+ });
10584
10831
  await Promise.all(wavePromises);
10585
10832
  }
10586
10833
  const failed = results.filter((r) => !r.success);
@@ -10589,9 +10836,6 @@ class MoEExecutor {
10589
10836
  }
10590
10837
  return { success: failed.length === 0, results, errors, warnings };
10591
10838
  }
10592
- getChainMaxAttempts() {
10593
- return 3;
10594
- }
10595
10839
  }
10596
10840
  var TRANSIENT_ERROR_PATTERNS;
10597
10841
  var init_moe_executor = __esm(() => {
@@ -11179,7 +11423,7 @@ var init_auditor = __esm(() => {
11179
11423
  // src/modules/execution/verifier.ts
11180
11424
  import { existsSync as existsSync23 } from "fs";
11181
11425
  import { resolve as resolve13, extname as extname2, join as join14 } from "path";
11182
- import { spawn as spawn2 } from "child_process";
11426
+ import { spawn as spawn3 } from "child_process";
11183
11427
 
11184
11428
  class StepVerifier {
11185
11429
  baseDir;
@@ -11341,7 +11585,7 @@ class StepVerifier {
11341
11585
  }
11342
11586
  runAsync(command, cwd, timeoutMs) {
11343
11587
  return new Promise((resolve14, reject) => {
11344
- const child = spawn2(command, {
11588
+ const child = spawn3(command, {
11345
11589
  cwd,
11346
11590
  shell: true,
11347
11591
  windowsHide: true,
@@ -11664,6 +11908,9 @@ class Agent {
11664
11908
  get contextManager() {
11665
11909
  return this.deps.contextManager;
11666
11910
  }
11911
+ getModule(name) {
11912
+ return this.deps.moduleRegistry?.get(name);
11913
+ }
11667
11914
  setScope() {
11668
11915
  if (this.deps.scope) {
11669
11916
  this.deps.toolExecutor.setScope(this.deps.scope);
@@ -12397,7 +12644,6 @@ ${warnLine}
12397
12644
  });
12398
12645
  this.deps.llmProvider = newProvider;
12399
12646
  this.deps.toolExecutor.updateProvider(newProvider);
12400
- this.deps.toolExecutor.ctx.llmProvider = newProvider;
12401
12647
  const newTokenCounter = new TokenCounter2(config.model);
12402
12648
  this.deps.contextManager.resize(config.contextWindow, config.contextBudget, newTokenCounter);
12403
12649
  this.deps.config = config;
@@ -12734,6 +12980,15 @@ class ContextManager {
12734
12980
  getMessageCount() {
12735
12981
  return this.messages.length;
12736
12982
  }
12983
+ tokenCache = new WeakMap;
12984
+ estimateMessageTokensCached(m) {
12985
+ let t2 = this.tokenCache.get(m);
12986
+ if (t2 === undefined) {
12987
+ t2 = this.estimateMessageTokens(m);
12988
+ this.tokenCache.set(m, t2);
12989
+ }
12990
+ return t2;
12991
+ }
12737
12992
  estimateMessageTokens(m) {
12738
12993
  const text = getMessageText(m.content);
12739
12994
  if (this.tokenCounter) {
@@ -12778,7 +13033,7 @@ class ContextManager {
12778
13033
  needsCompaction() {
12779
13034
  if (this.iterationsSinceCompaction >= COMPACTION_INTERVAL)
12780
13035
  return true;
12781
- const totalTokens = this.messages.reduce((sum, m) => sum + this.estimateMessageTokens(m), 0);
13036
+ const totalTokens = this.messages.reduce((sum, m) => sum + this.estimateMessageTokensCached(m), 0);
12782
13037
  return totalTokens > this.budget.history * this.compactionThreshold;
12783
13038
  }
12784
13039
  compact() {
@@ -12889,7 +13144,7 @@ ${lines.join(`
12889
13144
  this.sessionMission = "";
12890
13145
  }
12891
13146
  getEstimatedTokens() {
12892
- return this.messages.reduce((sum, m) => sum + this.estimateMessageTokens(m), 0) + this.toolTokens;
13147
+ return this.messages.reduce((sum, m) => sum + this.estimateMessageTokensCached(m), 0) + this.toolTokens;
12893
13148
  }
12894
13149
  setToolTokens(tokens) {
12895
13150
  this.toolTokens = tokens;
@@ -13000,7 +13255,7 @@ var init_confidence = __esm(() => {
13000
13255
 
13001
13256
  // src/modules/hallucination/factual.ts
13002
13257
  import { existsSync as existsSync24, readdirSync as readdirSync7 } from "fs";
13003
- import { resolve as resolve14, isAbsolute as isAbsolute2, join as join15 } from "path";
13258
+ import { resolve as resolve14, isAbsolute as isAbsolute3, join as join15 } from "path";
13004
13259
 
13005
13260
  class FactualCheck {
13006
13261
  baseDir;
@@ -13039,7 +13294,7 @@ class FactualCheck {
13039
13294
  return { status: "pass" };
13040
13295
  }
13041
13296
  pathExists(fp) {
13042
- if (isAbsolute2(fp))
13297
+ if (isAbsolute3(fp))
13043
13298
  return existsSync24(fp);
13044
13299
  if (this.knownFiles.has(fp))
13045
13300
  return true;
@@ -13705,7 +13960,6 @@ var init_subagent = __esm(() => {
13705
13960
  const fullTask = context ? `${task}
13706
13961
 
13707
13962
  Context: ${context}` : task;
13708
- let scopeRestored = false;
13709
13963
  try {
13710
13964
  const result = await subAgent.run(fullTask);
13711
13965
  if (!result.success) {
@@ -13742,9 +13996,7 @@ ${result.text}
13742
13996
  Iterations: ${result.iterationCount}`
13743
13997
  };
13744
13998
  } finally {
13745
- if (!scopeRestored) {
13746
- ctx.toolExecutor.setScope(parentScope || { allowed_files: [], read_only_files: [] });
13747
- }
13999
+ ctx.toolExecutor.setScope(parentScope);
13748
14000
  }
13749
14001
  } catch (e) {
13750
14002
  return { success: false, output: `Sub-agent error: ${e.message}` };
@@ -13758,20 +14010,22 @@ import { resolve as resolve15, normalize as normalize4 } from "path";
13758
14010
  function isPathInScope2(baseDir, targetPath, scope) {
13759
14011
  const baseResolved = resolve15(baseDir);
13760
14012
  const targetResolved = resolve15(baseDir, normalize4(targetPath));
13761
- if (!targetResolved.startsWith(baseResolved)) {
14013
+ if (!isInsideDir(targetResolved, baseResolved)) {
13762
14014
  return { allowed: false, reason: "Path is outside the base working directory" };
13763
14015
  }
13764
14016
  if (!scope)
13765
14017
  return { allowed: true };
13766
- const isRead = scope.read_only_files.some((f) => targetResolved.startsWith(resolve15(baseDir, f)));
13767
- const isWrite = scope.allowed_files.some((f) => targetResolved.startsWith(resolve15(baseDir, f)));
14018
+ const isRead = scope.read_only_files.some((f) => matchesScopeEntry(targetResolved, resolve15(baseDir, f)));
14019
+ const isWrite = scope.allowed_files.some((f) => matchesScopeEntry(targetResolved, resolve15(baseDir, f)));
13768
14020
  if (isWrite)
13769
14021
  return { allowed: true };
13770
14022
  if (isRead)
13771
14023
  return { allowed: true, reason: "Read-only file" };
13772
14024
  return { allowed: false, reason: "Path is not within the allowed scope for this sub-agent" };
13773
14025
  }
13774
- var init_scope_check = () => {};
14026
+ var init_scope_check = __esm(() => {
14027
+ init_path_utils();
14028
+ });
13775
14029
 
13776
14030
  // src/modules/context/chunk-query.ts
13777
14031
  function splitTextIntoChunks(text, chunkChars) {
@@ -13978,6 +14232,16 @@ ${res.synthesis || res.answers.join(`
13978
14232
  });
13979
14233
 
13980
14234
  // src/modules/security/network-validator.ts
14235
+ var exports_network_validator = {};
14236
+ __export(exports_network_validator, {
14237
+ sanitizeUrl: () => sanitizeUrl,
14238
+ isUrlAllowed: () => isUrlAllowed,
14239
+ isIpPrivate: () => isIpPrivate
14240
+ });
14241
+ function isIpPrivate(hostname) {
14242
+ const h = hostname.toLowerCase().replace(/^\[|\]$/g, "");
14243
+ return /^(localhost|127\.0\.0\.1|0\.0\.0\.0|10\.|192\.168\.|169\.254\.|172\.(1[6-9]|2\d|3[01])\.)/.test(h) || h === "::1" || /^(fc|fd)/.test(h) || h.includes(":") && h.startsWith("fe80");
14244
+ }
13981
14245
  function isUrlAllowed(url, securityConfig) {
13982
14246
  if (!securityConfig?.enabled) {
13983
14247
  return { allowed: true };
@@ -13985,6 +14249,13 @@ function isUrlAllowed(url, securityConfig) {
13985
14249
  try {
13986
14250
  const urlObj = new URL(url);
13987
14251
  const domain = urlObj.hostname.toLowerCase();
14252
+ const scheme = urlObj.protocol.toLowerCase();
14253
+ if (scheme !== "http:" && scheme !== "https:") {
14254
+ return { allowed: false, reason: `Scheme "${scheme}" is not allowed` };
14255
+ }
14256
+ if (isIpPrivate(domain)) {
14257
+ return { allowed: false, reason: `Private address "${domain}" is blocked (SSRF)` };
14258
+ }
13988
14259
  if (securityConfig.deniedDomains?.length) {
13989
14260
  for (const deniedDomain of securityConfig.deniedDomains) {
13990
14261
  const denied = deniedDomain.toLowerCase();
@@ -14177,21 +14448,40 @@ var init_web_fetch = __esm(() => {
14177
14448
  }
14178
14449
  try {
14179
14450
  const response = await fetch(url, {
14180
- signal: AbortSignal.timeout(securityConfig?.requestTimeout || 15000)
14451
+ signal: AbortSignal.timeout(securityConfig?.requestTimeout || 15000),
14452
+ redirect: "manual"
14181
14453
  });
14182
- if (!response.ok) {
14454
+ let finalResponse = response;
14455
+ let finalUrl = url;
14456
+ if (response.status >= 300 && response.status < 400) {
14457
+ const loc = response.headers.get("location");
14458
+ if (!loc) {
14459
+ return { success: false, output: t("error.fetch_failed", { message: "Redirect without location" }) };
14460
+ }
14461
+ finalUrl = new URL(loc, url).toString();
14462
+ const recheck = isUrlAllowed(finalUrl, securityConfig);
14463
+ if (!recheck.allowed) {
14464
+ logSecurityBlock(ctx.sessionId, "network_request", recheck.reason || "redirect blocked", sanitizeUrl(finalUrl));
14465
+ return { success: false, output: `[SECURITY BLOCKED] redirect: ${recheck.reason}` };
14466
+ }
14467
+ finalResponse = await fetch(finalUrl, {
14468
+ signal: AbortSignal.timeout(securityConfig?.requestTimeout || 15000),
14469
+ redirect: "follow"
14470
+ });
14471
+ }
14472
+ if (!finalResponse.ok) {
14183
14473
  return {
14184
14474
  success: false,
14185
14475
  output: t("error.http", {
14186
- status: response.status,
14187
- statusText: response.statusText
14476
+ status: finalResponse.status,
14477
+ statusText: finalResponse.statusText
14188
14478
  })
14189
14479
  };
14190
14480
  }
14191
- const contentType = response.headers.get("content-type") || "";
14192
- const text = await response.text();
14481
+ const contentType = finalResponse.headers.get("content-type") || "";
14482
+ const text = await finalResponse.text();
14193
14483
  const cleaned = contentType.includes("html") ? stripHtml(text) : text;
14194
- logNetworkRequest(ctx.sessionId, sanitizeUrl(url), true, `Status: ${response.status}`);
14484
+ logNetworkRequest(ctx.sessionId, sanitizeUrl(finalUrl), true, `Status: ${finalResponse.status}`);
14195
14485
  const fullChars = cleaned.length;
14196
14486
  const fullLines = cleaned.split(`
14197
14487
  `).length;
@@ -14753,7 +15043,7 @@ ${logs.join(`
14753
15043
  });
14754
15044
 
14755
15045
  // src/modules/mcp/client.ts
14756
- import { spawn as spawn3 } from "child_process";
15046
+ import { spawn as spawn4 } from "child_process";
14757
15047
 
14758
15048
  class MCPClient {
14759
15049
  serverName;
@@ -14860,7 +15150,7 @@ class MCPClient {
14860
15150
  }
14861
15151
  connectStdio() {
14862
15152
  return new Promise((resolve17, reject) => {
14863
- const child = spawn3(this.config.command, this.config.args || [], {
15153
+ const child = spawn4(this.config.command, this.config.args || [], {
14864
15154
  env: { ...process.env, ...this.config.env },
14865
15155
  stdio: ["pipe", "pipe", "pipe"]
14866
15156
  });
@@ -15657,7 +15947,7 @@ var exports_bridge_client = {};
15657
15947
  __export(exports_bridge_client, {
15658
15948
  BridgeDriver: () => BridgeDriver
15659
15949
  });
15660
- import { spawn as spawn4 } from "child_process";
15950
+ import { spawn as spawn5 } from "child_process";
15661
15951
  import { createInterface } from "readline";
15662
15952
  import { dirname as dirname10, join as join22 } from "path";
15663
15953
  import { fileURLToPath } from "url";
@@ -15733,7 +16023,7 @@ class BridgeDriver {
15733
16023
  }
15734
16024
  spawnBridge() {
15735
16025
  const script = bridgeScriptPath();
15736
- const proc = spawn4("node", [script], { stdio: ["pipe", "pipe", "pipe"], windowsHide: true });
16026
+ const proc = spawn5("node", [script], { stdio: ["pipe", "pipe", "pipe"], windowsHide: true });
15737
16027
  this.proc = proc;
15738
16028
  this.lastStderr = [];
15739
16029
  proc.stderr.on("data", (chunk) => {
@@ -16427,6 +16717,9 @@ class BrowserSession {
16427
16717
  async open(url) {
16428
16718
  if (!url)
16429
16719
  return { success: false, output: t("browser.url_required") };
16720
+ if (/^(file|chrome|about|data|javascript|view-source):/i.test(url)) {
16721
+ return { success: false, output: `[SECURITY BLOCKED] scheme not allowed: ${url}` };
16722
+ }
16430
16723
  if (!url.startsWith("http://") && !url.startsWith("https://") && !url.startsWith("file://")) {
16431
16724
  const isLocalhost = /^(localhost|127\.0\.0\.1|0\.0\.0\.0)(:\d+)?$/i.test(url);
16432
16725
  url = isLocalhost ? "http://" + url : "https://" + url;
@@ -16854,6 +17147,14 @@ var init_attach_image = __esm(() => {
16854
17147
  const result = await bufferToDataUrl(clipBuf);
16855
17148
  dataUrl = result.dataUrl;
16856
17149
  } else if (source.startsWith("http://") || source.startsWith("https://")) {
17150
+ const { isUrlAllowed: isUrlAllowed2 } = await Promise.resolve().then(() => (init_network_validator(), exports_network_validator));
17151
+ const { getSessionSecurityConfig: getSessionSecurityConfig2 } = await Promise.resolve().then(() => (init_session_isolation(), exports_session_isolation));
17152
+ const networkConfig = ctx.sessionContext ? getSessionSecurityConfig2(ctx.config, ctx.sessionContext).network : ctx.config?.security?.network;
17153
+ const urlCheck = isUrlAllowed2(source, networkConfig);
17154
+ if (!urlCheck.allowed) {
17155
+ logSecurityBlock(ctx.sessionId, "network_request", urlCheck.reason ?? "attach_image URL blocked", source);
17156
+ return { success: false, output: `[SECURITY BLOCKED] ${urlCheck.reason}` };
17157
+ }
16857
17158
  const result = await loadUrlAsDataUrl(source);
16858
17159
  dataUrl = result.dataUrl;
16859
17160
  } else {
@@ -17070,6 +17371,7 @@ class ModuleRegistry {
17070
17371
  modules = new Map;
17071
17372
  register(mod) {
17072
17373
  this.modules.set(mod.name, mod);
17374
+ return this;
17073
17375
  }
17074
17376
  get(name) {
17075
17377
  return this.modules.get(name);
@@ -17115,7 +17417,7 @@ class ModuleRegistry {
17115
17417
  }
17116
17418
 
17117
17419
  // src/modules/plugins/loader.ts
17118
- import { readdirSync as readdirSync9, existsSync as existsSync31, statSync as statSync5 } from "fs";
17420
+ import { readdirSync as readdirSync9, existsSync as existsSync31, statSync as statSync6 } from "fs";
17119
17421
  import { join as join25, basename as basename3 } from "path";
17120
17422
 
17121
17423
  class PluginLoader {
@@ -17125,7 +17427,7 @@ class PluginLoader {
17125
17427
  const entries = readdirSync9(dirPath).sort();
17126
17428
  for (const entry of entries) {
17127
17429
  const fullPath = join25(dirPath, entry);
17128
- const stat = statSync5(fullPath);
17430
+ const stat = statSync6(fullPath);
17129
17431
  if (stat.isFile()) {
17130
17432
  if (!entry.endsWith(".ts") && !entry.endsWith(".js"))
17131
17433
  continue;
@@ -17182,7 +17484,7 @@ var init_loader = __esm(() => {
17182
17484
  });
17183
17485
 
17184
17486
  // src/modules/plugins/builtin/lint-on-write.ts
17185
- import { spawn as spawn5, execSync } from "child_process";
17487
+ import { spawn as spawn6, execSync } from "child_process";
17186
17488
  import { existsSync as existsSync32, readFileSync as readFileSync17 } from "fs";
17187
17489
  import { resolve as resolve18, extname as extname4, join as join26 } from "path";
17188
17490
  import { platform as platform5 } from "os";
@@ -17365,7 +17667,7 @@ class LintOnWritePlugin {
17365
17667
  }
17366
17668
  function runAsync(command, cwd, timeoutMs, signal) {
17367
17669
  return new Promise((resolve19, reject) => {
17368
- const child = spawn5(command, {
17670
+ const child = spawn6(command, {
17369
17671
  cwd,
17370
17672
  shell: true,
17371
17673
  windowsHide: true,
@@ -17989,7 +18291,11 @@ Last compile error: ${first[1]}`;
17989
18291
  const step = deps.trackerRef.current?.getCurrentStep();
17990
18292
  ctx.contextManager.addMessage({
17991
18293
  role: "user",
17992
- content: `<system-summary>STOP. Step ${step?.id ?? "?"} ("${step?.description ?? ""}") took ${deps.stuckDetector.getIterationsOnCurrentStep()} iterations with no progress. DO NOT continue this step. Immediately call: plan update step=${step?.id ?? "?"} status=done (if code works despite warnings) OR plan update step=${step?.id ?? "?"} status=skipped note="reason". Do NOT make any other tool calls before updating the plan.</system-summary>`
18294
+ content: `<system-summary>${t("exec.stop_directive", {
18295
+ stepId: String(step?.id ?? "?"),
18296
+ description: step?.description ?? "",
18297
+ iterations: String(deps.stuckDetector.getIterationsOnCurrentStep())
18298
+ })}</system-summary>`
17993
18299
  });
17994
18300
  }
17995
18301
  }
@@ -18669,16 +18975,16 @@ ${progress}${vacuousNote}`,
18669
18975
  output += ` (${doneCount}/${subs.length})`;
18670
18976
  if (doneCount === subs.length) {
18671
18977
  output += `
18672
- All sub-tasks done — call plan update step=${currentStep.id} status=done to complete this step.`;
18978
+ ${t("plan.subtasks_done", { stepId: String(currentStep.id) })}`;
18673
18979
  }
18674
18980
  return { success: true, output, display };
18675
18981
  }
18676
18982
  if (action === "list") {
18677
18983
  const subs = currentStep.subtasks ?? [];
18678
- const lines = subs.length ? subs.map((s) => `${s.done ? "[x]" : "[ ]"} ${s.text}`) : ["(no sub-tasks)"];
18984
+ const lines = subs.length ? subs.map((s) => `${s.done ? "[x]" : "[ ]"} ${s.text}`) : [t("plan.no_subtasks")];
18679
18985
  return {
18680
18986
  success: true,
18681
- output: `Step ${currentStep.id}: ${currentStep.description}
18987
+ output: `${t("plan.step_label", { stepId: String(currentStep.id), description: currentStep.description })}
18682
18988
  ${lines.join(`
18683
18989
  `)}`
18684
18990
  };
@@ -19373,7 +19679,7 @@ class SessionStore {
19373
19679
  return this.encryptor?.isEnabled() ?? false;
19374
19680
  }
19375
19681
  init() {
19376
- mkdirSync15(this.baseDir, { recursive: true });
19682
+ mkdirSync15(this.baseDir, { recursive: true, mode: 448 });
19377
19683
  }
19378
19684
  sessionDir(id) {
19379
19685
  return join29(this.baseDir, id);
@@ -19393,12 +19699,12 @@ class SessionStore {
19393
19699
  saveMeta(id, meta) {
19394
19700
  this._metaCache.set(id, meta);
19395
19701
  const dir = this.sessionDir(id);
19396
- mkdirSync15(dir, { recursive: true });
19702
+ mkdirSync15(dir, { recursive: true, mode: 448 });
19397
19703
  const content = JSON.stringify(meta, null, 2);
19398
19704
  if (this.encryptor) {
19399
- writeFileSync12(this.metaPath(id), this.encryptor.encryptFileContent(content), "utf-8");
19705
+ writeFileSync12(this.metaPath(id), this.encryptor.encryptFileContent(content), { encoding: "utf-8", mode: 384 });
19400
19706
  } else {
19401
- writeFileSync12(this.metaPath(id), content, "utf-8");
19707
+ writeFileSync12(this.metaPath(id), content, { encoding: "utf-8", mode: 384 });
19402
19708
  }
19403
19709
  }
19404
19710
  loadMeta(id) {
@@ -19420,14 +19726,14 @@ class SessionStore {
19420
19726
  }
19421
19727
  appendMessage(id, msg) {
19422
19728
  const dir = this.sessionDir(id);
19423
- mkdirSync15(dir, { recursive: true });
19729
+ mkdirSync15(dir, { recursive: true, mode: 448 });
19424
19730
  const line = JSON.stringify(msg);
19425
19731
  if (this.encryptor?.isEnabled()) {
19426
19732
  appendFileSync6(this.historyPath(id), this.encryptor.encryptFileContent(line) + `
19427
- `, "utf-8");
19733
+ `, { encoding: "utf-8", mode: 384 });
19428
19734
  } else {
19429
19735
  appendFileSync6(this.historyPath(id), line + `
19430
- `, "utf-8");
19736
+ `, { encoding: "utf-8", mode: 384 });
19431
19737
  }
19432
19738
  const meta = this.loadMeta(id);
19433
19739
  if (meta) {
@@ -19467,14 +19773,14 @@ class SessionStore {
19467
19773
  }
19468
19774
  appendSessionLog(id, entry) {
19469
19775
  const dir = this.sessionDir(id);
19470
- mkdirSync15(dir, { recursive: true });
19776
+ mkdirSync15(dir, { recursive: true, mode: 448 });
19471
19777
  const line = JSON.stringify(entry);
19472
19778
  if (this.encryptor?.isEnabled()) {
19473
19779
  appendFileSync6(this.sessionLogPath(id), this.encryptor.encryptFileContent(line) + `
19474
- `, "utf-8");
19780
+ `, { encoding: "utf-8", mode: 384 });
19475
19781
  } else {
19476
19782
  appendFileSync6(this.sessionLogPath(id), line + `
19477
- `, "utf-8");
19783
+ `, { encoding: "utf-8", mode: 384 });
19478
19784
  }
19479
19785
  }
19480
19786
  loadSessionLog(id) {
@@ -19819,7 +20125,7 @@ class UserProfile {
19819
20125
  var init_profile = () => {};
19820
20126
 
19821
20127
  // src/modules/skills/loader.ts
19822
- import { readdirSync as readdirSync13, readFileSync as readFileSync23, existsSync as existsSync38, statSync as statSync6 } from "fs";
20128
+ import { readdirSync as readdirSync13, readFileSync as readFileSync23, existsSync as existsSync38, statSync as statSync7 } from "fs";
19823
20129
  import { join as join31 } from "path";
19824
20130
 
19825
20131
  class SkillsLoader {
@@ -19834,7 +20140,7 @@ class SkillsLoader {
19834
20140
  const entries = readdirSync13(dirPath);
19835
20141
  for (const entry of entries) {
19836
20142
  const fullPath = join31(dirPath, entry);
19837
- const stat = statSync6(fullPath);
20143
+ const stat = statSync7(fullPath);
19838
20144
  if (stat.isDirectory()) {
19839
20145
  this.scanDir(fullPath, skills);
19840
20146
  continue;
@@ -20077,10 +20383,29 @@ var init_browser2 = __esm(() => {
20077
20383
  });
20078
20384
 
20079
20385
  // src/modules/lsp/client.ts
20080
- import { spawn as spawn6, execSync as execSync2 } from "child_process";
20386
+ import { spawn as spawn7, execSync as execSync2 } from "child_process";
20081
20387
  import { resolve as resolve20 } from "path";
20082
20388
  import { platform as platform8 } from "os";
20083
20389
 
20390
+ class FileOnlyLogger {
20391
+ logger;
20392
+ constructor(logger) {
20393
+ this.logger = logger;
20394
+ }
20395
+ debug(msg, meta) {
20396
+ this.logger.logSilent("debug", msg, meta);
20397
+ }
20398
+ info(msg, meta) {
20399
+ this.logger.logSilent("info", msg, meta);
20400
+ }
20401
+ warn(msg, meta) {
20402
+ this.logger.logSilent("warn", msg, meta);
20403
+ }
20404
+ error(msg, meta) {
20405
+ this.logger.logSilent("error", msg, meta);
20406
+ }
20407
+ }
20408
+
20084
20409
  class LspClient {
20085
20410
  logger = null;
20086
20411
  setLogger(logger) {
@@ -20175,10 +20500,26 @@ class LspClient {
20175
20500
  throw new Error(`${config.command} not found in PATH`);
20176
20501
  }
20177
20502
  }
20503
+ let effectiveCommand = config.command;
20504
+ let effectiveArgs = config.args ?? [];
20505
+ if (config.command === "npx" && effectiveArgs.length > 0) {
20506
+ const binaryName = this.extractBinaryFromNpxArgs(effectiveArgs);
20507
+ if (binaryName) {
20508
+ const whichCmd = platform8() === "win32" ? `where ${binaryName}` : `which ${binaryName}`;
20509
+ try {
20510
+ execSync2(whichCmd, { stdio: "pipe", timeout: 3000 });
20511
+ effectiveCommand = binaryName;
20512
+ effectiveArgs = effectiveArgs.filter((a) => a !== "--yes" && a !== "--package" && a !== binaryName && !this.isPackageVersion(a));
20513
+ this.logger?.debug(`LSP: using globally installed ${binaryName} (skipped npx)`);
20514
+ } catch {
20515
+ this.logger?.debug(`LSP: ${binaryName} not found globally, falling back to npx`);
20516
+ }
20517
+ }
20518
+ }
20178
20519
  return new Promise((resolve21, reject) => {
20179
- const args = config.args ?? [];
20520
+ const args = effectiveArgs;
20180
20521
  const isWin = platform8() === "win32";
20181
- let spawnCommand = resolveSpawnCommand(config.command);
20522
+ let spawnCommand = resolveSpawnCommand(effectiveCommand);
20182
20523
  let spawnArgs = args;
20183
20524
  const spawnOpts = {
20184
20525
  stdio: ["pipe", "pipe", "pipe"],
@@ -20190,7 +20531,7 @@ class LspClient {
20190
20531
  spawnArgs = [];
20191
20532
  spawnOpts.shell = true;
20192
20533
  }
20193
- const proc = spawn6(spawnCommand, spawnArgs, spawnOpts);
20534
+ const proc = spawn7(spawnCommand, spawnArgs, spawnOpts);
20194
20535
  proc.on("error", reject);
20195
20536
  proc.stdout.on("data", (chunk) => {
20196
20537
  this.handleData(chunk);
@@ -20368,6 +20709,22 @@ class LspClient {
20368
20709
  };
20369
20710
  return map[ext ?? ""] ?? "plaintext";
20370
20711
  }
20712
+ extractBinaryFromNpxArgs(args) {
20713
+ for (let i = args.length - 1;i >= 0; i--) {
20714
+ const arg = args[i];
20715
+ if (arg === "--stdio")
20716
+ continue;
20717
+ if (arg.startsWith("--"))
20718
+ return null;
20719
+ if (this.isPackageVersion(arg))
20720
+ continue;
20721
+ return arg;
20722
+ }
20723
+ return null;
20724
+ }
20725
+ isPackageVersion(s) {
20726
+ return /^[\w-]+@[\d.+*-]/.test(s);
20727
+ }
20371
20728
  }
20372
20729
  var DEFAULT_TIMEOUT = 15000;
20373
20730
  var init_client2 = __esm(() => {
@@ -20377,7 +20734,7 @@ var init_client2 = __esm(() => {
20377
20734
 
20378
20735
  // src/modules/lsp/check-tool.ts
20379
20736
  import { readdir, stat } from "fs/promises";
20380
- import { resolve as resolve21, relative as relative2 } from "path";
20737
+ import { resolve as resolve21, relative as relative3 } from "path";
20381
20738
  async function collectCheckFiles(resolved, config) {
20382
20739
  const info = await stat(resolved).catch(() => null);
20383
20740
  if (!info)
@@ -20406,7 +20763,7 @@ async function walkDir(dir, depth, out, config) {
20406
20763
  }
20407
20764
  function formatCheckDiagnostics(items, baseDir) {
20408
20765
  return items.slice(0, 40).map(({ file, diag }) => {
20409
- const rel = relative2(baseDir, file).replace(/\\/g, "/");
20766
+ const rel = relative3(baseDir, file).replace(/\\/g, "/");
20410
20767
  const line = diag.range.start.line + 1;
20411
20768
  const col = diag.range.start.character + 1;
20412
20769
  const sev = severityLabels[diag.severity] ?? "unknown";
@@ -20428,7 +20785,7 @@ var init_check_tool = __esm(() => {
20428
20785
 
20429
20786
  // src/modules/lsp/module.ts
20430
20787
  import { existsSync as existsSync39 } from "fs";
20431
- import { relative as relative3, resolve as resolve22 } from "path";
20788
+ import { relative as relative4, resolve as resolve22 } from "path";
20432
20789
 
20433
20790
  class LspModule {
20434
20791
  name = "lsp";
@@ -20595,7 +20952,7 @@ ${items}`;
20595
20952
  const diags = await this.client.checkFile(file, ctx.baseDir, serverConfig, projectRoot);
20596
20953
  this.failuresByServer.set(key, 0);
20597
20954
  checked++;
20598
- checkedFiles.push(relative3(ctx.baseDir, file).replace(/\\/g, "/"));
20955
+ checkedFiles.push(relative4(ctx.baseDir, file).replace(/\\/g, "/"));
20599
20956
  for (const d of diags) {
20600
20957
  if (d.severity === 1)
20601
20958
  errors.push({ file, diag: d });
@@ -20676,7 +21033,7 @@ var init_lsp = __esm(() => {
20676
21033
  // src/modules/lsp/startup-check.ts
20677
21034
  import { existsSync as existsSync40 } from "fs";
20678
21035
  import { join as join32 } from "path";
20679
- import { spawn as spawn7 } from "child_process";
21036
+ import { spawn as spawn8 } from "child_process";
20680
21037
  async function runStartupHealthCheck(config, baseDir, deps = {}) {
20681
21038
  if (!config.enabled)
20682
21039
  return null;
@@ -20738,7 +21095,7 @@ async function runCheck(config, baseDir, deps) {
20738
21095
  }
20739
21096
  async function runTscDefault(projectRoot, timeoutMs) {
20740
21097
  return new Promise((resolve23) => {
20741
- const child = spawn7("npx", ["tsc", "--noEmit", "--skipLibCheck"], {
21098
+ const child = spawn8("npx", ["tsc", "--noEmit", "--skipLibCheck"], {
20742
21099
  cwd: projectRoot,
20743
21100
  shell: true,
20744
21101
  windowsHide: true,
@@ -20802,8 +21159,8 @@ var init_startup_check = __esm(() => {
20802
21159
  });
20803
21160
 
20804
21161
  // src/modules/indexer/walker.ts
20805
- import { readdirSync as readdirSync14, readFileSync as readFileSync24, statSync as statSync7, existsSync as existsSync41, watch } from "fs";
20806
- import { join as join33, relative as relative4, extname as extname5 } from "path";
21162
+ import { readdirSync as readdirSync14, readFileSync as readFileSync24, statSync as statSync8, existsSync as existsSync41, watch } from "fs";
21163
+ import { join as join33, relative as relative5, extname as extname5 } from "path";
20807
21164
 
20808
21165
  class Indexer {
20809
21166
  baseDir;
@@ -20842,8 +21199,8 @@ class Indexer {
20842
21199
  if (count >= this.MAX_FILES)
20843
21200
  return;
20844
21201
  const fullPath = join33(dir, entry);
20845
- const relPath = relative4(this.baseDir, fullPath);
20846
- const stat2 = statSync7(fullPath);
21202
+ const relPath = relative5(this.baseDir, fullPath);
21203
+ const stat2 = statSync8(fullPath);
20847
21204
  if (stat2.isDirectory()) {
20848
21205
  if (!IGNORE_DIRS.has(entry)) {
20849
21206
  walkDir2(fullPath);
@@ -21817,7 +22174,9 @@ function buildSystemInfo(config, baseDir, profileCompressed) {
21817
22174
  const lines = [
21818
22175
  `You are MMA v2, an AI coding agent for small models (${config.model}). Date: ${now}. Workspace: ${baseDir}. ${profileCompressed}.`,
21819
22176
  `Reply in the user's language. Use tools for file ops (read/write/edit/delete), search (glob/grep), shell (bash), web, subagents, browser, MCP. Explain briefly if not obvious. On tool failure: analyze, fix the call, retry up to 2x with different approaches, then ask the user.`,
21820
- `Design: YAGNI (no unneeded code), KISS (simple over clever), DRY (reuse existing utilities).`
22177
+ `Design: YAGNI (no unneeded code), KISS (simple over clever), DRY (reuse existing utilities).`,
22178
+ `SCOPE DISCIPLINE: do ONLY what the user explicitly asked — no extra features, files, refactors, "improvements", or fixes beyond the request. Read-only requests ("расскажи", "покажи", "объясни", "check") mean READ-ONLY: inspect and answer, never create/modify/delete anything. If the task is ambiguous (what to create, where, which variant) or the request implies action on something you could not find — STOP and ask the user a short clarifying question in plain text instead of guessing.`,
22179
+ `NOT FOUND ≠ MISSING PROJECT: a "not found" tool result means the PATH was wrong (typo, different location), not that the project does not exist. Before creating or scaffolding ANY project/files: first run list_dir on the working directory to see what is already there; if a user-named path is not found, list its parent directory to locate the real path. NEVER create a new project when the user asked about an existing one — inspect first, create only after confirming the workspace is empty AND the user asked for creation.`
21821
22180
  ];
21822
22181
  if (isWin) {
21823
22182
  lines.push(`Windows (PowerShell): use list_dir/read_file/delete_file/create_dir tools instead of dir/type/del/mkdir. No PowerShell cmdlets (Get-Content, Select-Object, Write-Output), no head/tail/grep/cat. Use forward slashes in paths. CWD: ${baseDir}`);
@@ -22013,7 +22372,8 @@ async function bootstrap(configDir, projectDir, noAgentsMd, exitOnComplete) {
22013
22372
  pluginManager.register(browserPlugin);
22014
22373
  }
22015
22374
  }
22016
- const lspModule = new LspModule(config.lsp, undefined, logger);
22375
+ const lspLogger = new FileOnlyLogger(logger);
22376
+ const lspModule = new LspModule(config.lsp, undefined, lspLogger);
22017
22377
  moduleRegistry.register(lspModule);
22018
22378
  const lspPlugin = lspModule.getPlugin();
22019
22379
  if (lspPlugin) {
@@ -22021,7 +22381,7 @@ async function bootstrap(configDir, projectDir, noAgentsMd, exitOnComplete) {
22021
22381
  pluginManager.register(lspPlugin);
22022
22382
  }
22023
22383
  let startupCheckBlock = null;
22024
- const startupCheckPromise = !exitOnComplete ? runStartupHealthCheck(config.lsp ?? DEFAULT_LSP_CONFIG, baseDir, { logger }) : Promise.resolve(null);
22384
+ const startupCheckPromise = !exitOnComplete ? runStartupHealthCheck(config.lsp ?? DEFAULT_LSP_CONFIG, baseDir, { logger: lspLogger }) : Promise.resolve(null);
22025
22385
  const moduleTools = moduleRegistry.collectToolDefinitions();
22026
22386
  for (const tool of moduleTools) {
22027
22387
  toolRegistry.register(tool);
@@ -22147,6 +22507,7 @@ async function bootstrap(configDir, projectDir, noAgentsMd, exitOnComplete) {
22147
22507
  },
22148
22508
  sessionManager,
22149
22509
  memoryStore,
22510
+ moduleRegistry,
22150
22511
  exitOnComplete
22151
22512
  };
22152
22513
  const agent = new Agent(agentDeps);
@@ -22879,7 +23240,7 @@ async function runSetup(externalRl) {
22879
23240
  const contextWindow = parseInt(await ask(rl, t("setup.context_window"), "32768"));
22880
23241
  const maxIterations = parseInt(await ask(rl, t("setup.max_iters"), "1000"));
22881
23242
  console.log(t("setup.security_header"));
22882
- console.log(pc2.dim(t("setup.security_status_off")));
23243
+ console.log(pc2.dim(t("setup.security_status_on")));
22883
23244
  const configureSecurity = await ask(rl, t("setup.security_configure"), "n");
22884
23245
  let securityBashBlock = false;
22885
23246
  let securityFlagsBlock = false;
@@ -30240,7 +30601,7 @@ var init_loader3 = __esm(() => {
30240
30601
  });
30241
30602
 
30242
30603
  // src/modules/certification/fact-checker.ts
30243
- import { existsSync as existsSync49, readFileSync as readFileSync32, statSync as statSync8 } from "fs";
30604
+ import { existsSync as existsSync49, readFileSync as readFileSync32, statSync as statSync9 } from "fs";
30244
30605
  import { join as join43 } from "path";
30245
30606
  function checkSandbox(sandboxDir, checks, exitCode, output) {
30246
30607
  const failures = [];
@@ -30286,14 +30647,14 @@ function runCheck2(sandboxDir, check, exitCode, output) {
30286
30647
  }
30287
30648
  function isFile(p) {
30288
30649
  try {
30289
- return existsSync49(p) && statSync8(p).isFile();
30650
+ return existsSync49(p) && statSync9(p).isFile();
30290
30651
  } catch {
30291
30652
  return false;
30292
30653
  }
30293
30654
  }
30294
30655
  function isDir(p) {
30295
30656
  try {
30296
- return existsSync49(p) && statSync8(p).isDirectory();
30657
+ return existsSync49(p) && statSync9(p).isDirectory();
30297
30658
  } catch {
30298
30659
  return false;
30299
30660
  }
@@ -30323,7 +30684,7 @@ function describe(check) {
30323
30684
  var init_fact_checker = () => {};
30324
30685
 
30325
30686
  // src/modules/certification/runner.ts
30326
- import { spawn as spawn8 } from "child_process";
30687
+ import { spawn as spawn9 } from "child_process";
30327
30688
  import { existsSync as existsSync50, mkdirSync as mkdirSync19, rmSync as rmSync4, cpSync as cpSync2 } from "fs";
30328
30689
  import { platform as platform10 } from "os";
30329
30690
  import { join as join44, resolve as resolve24, dirname as dirname15 } from "path";
@@ -30434,7 +30795,7 @@ function killTree2(child) {
30434
30795
  if (!pid)
30435
30796
  return;
30436
30797
  if (platform10() === "win32") {
30437
- spawn8("taskkill", ["/pid", String(pid), "/T", "/F"], {
30798
+ spawn9("taskkill", ["/pid", String(pid), "/T", "/F"], {
30438
30799
  windowsHide: true,
30439
30800
  stdio: "ignore"
30440
30801
  });
@@ -30449,7 +30810,7 @@ function killTree2(child) {
30449
30810
  }
30450
30811
  }
30451
30812
  var defaultRunner2 = (env3, cwd, args, timeoutMs) => new Promise((resolvePromise) => {
30452
- const child = spawn8(process.execPath, args, {
30813
+ const child = spawn9(process.execPath, args, {
30453
30814
  cwd,
30454
30815
  env: env3,
30455
30816
  windowsHide: true,
@@ -30659,24 +31020,8 @@ __export(exports_repl_commands, {
30659
31020
  registerAllCommands: () => registerAllCommands,
30660
31021
  COMMAND_GROUPS: () => COMMAND_GROUPS
30661
31022
  });
30662
- import { join as join47, dirname as dirname18 } from "path";
31023
+ import { join as join47 } from "path";
30663
31024
  import { homedir as homedir17 } from "os";
30664
- import { existsSync as existsSync53, readFileSync as readFileSync35 } from "fs";
30665
- import { fileURLToPath as fileURLToPath6 } from "url";
30666
- function readVersion3() {
30667
- const here = dirname18(fileURLToPath6(import.meta.url));
30668
- const candidates = [join47(here, "..", "..", "package.json"), join47(here, "..", "package.json")];
30669
- for (const p of candidates) {
30670
- if (existsSync53(p)) {
30671
- try {
30672
- const raw = JSON.parse(readFileSync35(p, "utf8"));
30673
- if (raw.version)
30674
- return raw.version;
30675
- } catch {}
30676
- }
30677
- }
30678
- return "0.0.0";
30679
- }
30680
31025
  function registerAllCommands(ctx) {
30681
31026
  registerBuiltinCommands(ctx);
30682
31027
  registerMmaCommands(ctx);
@@ -30733,7 +31078,7 @@ function registerMmaCommands(ctx) {
30733
31078
  }
30734
31079
  try {
30735
31080
  const { loadFileAsDataUrl: loadFileAsDataUrl2, loadUrlAsDataUrl: loadUrlAsDataUrl2, readClipboardImage: readClipboardImage2 } = await Promise.resolve().then(() => (init_image_utils(), exports_image_utils));
30736
- const { existsSync: existsSync54 } = await import("fs");
31081
+ const { existsSync: existsSync52 } = await import("fs");
30737
31082
  const { resolve: resolve25 } = await import("path");
30738
31083
  let dataUrl;
30739
31084
  let label;
@@ -30753,7 +31098,7 @@ function registerMmaCommands(ctx) {
30753
31098
  label = source;
30754
31099
  } else {
30755
31100
  const absPath = resolve25(process.cwd(), source);
30756
- if (!existsSync54(absPath)) {
31101
+ if (!existsSync52(absPath)) {
30757
31102
  console.log(pc2.red(t("image.not_found", { path: source })));
30758
31103
  return;
30759
31104
  }
@@ -30979,10 +31324,7 @@ Excluded blocks: ${info.excluded.length}`));
30979
31324
  usage: t("repl.reload_usage"),
30980
31325
  action: async () => {
30981
31326
  console.log(pc2.yellow(t("repl.reloading")));
30982
- if (ctx.sessionManager && ctx.config.session.autoSave) {
30983
- const active = ctx.sessionManager.getActiveMeta();
30984
- if (active) {}
30985
- }
31327
+ if (ctx.sessionManager && ctx.config.session.autoSave) {}
30986
31328
  ctx.agent.shutdown();
30987
31329
  const { loadConfig: loadConfig2 } = await Promise.resolve().then(() => (init_config2(), exports_config));
30988
31330
  const { homedir: homedir18 } = await import("os");
@@ -31046,7 +31388,7 @@ Excluded blocks: ${info.excluded.length}`));
31046
31388
  usage: t("repl.lsp_usage"),
31047
31389
  action: async (args) => {
31048
31390
  const subcommand = args[0] || "status";
31049
- const lspModule = ctx.agent.deps?.moduleRegistry?.getModules?.()?.find((m) => m.name === "lsp");
31391
+ const lspModule = ctx.agent.getModule("lsp");
31050
31392
  if (!lspModule) {
31051
31393
  console.log(pc2.yellow(t("repl.lsp_not_available")));
31052
31394
  return;
@@ -31338,11 +31680,12 @@ function registerSkillCommands(ctx) {
31338
31680
  var version2, COMMAND_GROUPS;
31339
31681
  var init_repl_commands = __esm(() => {
31340
31682
  init_colors();
31683
+ init_version();
31341
31684
  init_table();
31342
31685
  init_i18n();
31343
31686
  init_setup();
31344
31687
  init_config2();
31345
- version2 = readVersion3();
31688
+ version2 = readMmaVersion();
31346
31689
  COMMAND_GROUPS = {
31347
31690
  help: "general",
31348
31691
  exit: "general",
@@ -31390,9 +31733,8 @@ init_bootstrap();
31390
31733
  init_config2();
31391
31734
  init_setup();
31392
31735
  init_i18n();
31393
- import { join as join46, dirname as dirname17 } from "path";
31736
+ import { join as join46 } from "path";
31394
31737
  import { homedir as homedir16 } from "os";
31395
- import { existsSync as existsSync52, readFileSync as readFileSync34 } from "fs";
31396
31738
 
31397
31739
  // src/cli/security-commands.ts
31398
31740
  init_bootstrap();
@@ -32028,22 +32370,8 @@ function createPluginCommand(program2) {
32028
32370
 
32029
32371
  // src/cli/commands.ts
32030
32372
  init_setup();
32031
- import { fileURLToPath as fileURLToPath5 } from "url";
32032
- function readVersion2() {
32033
- const here = dirname17(fileURLToPath5(import.meta.url));
32034
- const candidates = [join46(here, "..", "..", "package.json"), join46(here, "..", "package.json")];
32035
- for (const p of candidates) {
32036
- if (existsSync52(p)) {
32037
- try {
32038
- const raw = JSON.parse(readFileSync34(p, "utf8"));
32039
- if (raw.version)
32040
- return raw.version;
32041
- } catch {}
32042
- }
32043
- }
32044
- return "0.0.0";
32045
- }
32046
- var version = readVersion2();
32373
+ init_version();
32374
+ var version = readMmaVersion();
32047
32375
  function createProgram() {
32048
32376
  const program2 = new Command().name("mma").description(t("cli.description")).version(version).option("--no-agents-md", t("cli.no_agents_md")).option("-d, --dir <path>", t("cli.dir")).option("-e, --exit-on-complete", t("cli.exit_on_complete")).option("-j, --json", t("cli.json"));
32049
32377
  program2.command("init").description(t("cli.init")).action(async () => {
@@ -32058,9 +32386,9 @@ function createProgram() {
32058
32386
  config.maxToolIterations = answers.maxToolIterations;
32059
32387
  config.locale = answers.locale;
32060
32388
  if (config.security) {
32061
- config.security.enabled = answers.securityBashBlock || answers.securityFlagsBlock || !answers.securityPathsDeny;
32062
- config.security.bash.enabled = config.security.enabled;
32063
- config.security.bash.blockDangerousFlags = answers.securityFlagsBlock;
32389
+ config.security.enabled = true;
32390
+ config.security.bash.enabled = true;
32391
+ config.security.bash.blockDangerousFlags = answers.securityFlagsBlock || config.security.bash.blockDangerousFlags;
32064
32392
  if (answers.securityBashBlock) {
32065
32393
  config.security.bash.blacklist = [
32066
32394
  ...new Set([
@@ -33025,7 +33353,7 @@ class LineEditor {
33025
33353
  }
33026
33354
 
33027
33355
  // src/cli/repl.ts
33028
- import { existsSync as existsSync55, readFileSync as readFileSync37, writeFileSync as writeFileSync17 } from "fs";
33356
+ import { existsSync as existsSync53, readFileSync as readFileSync35, writeFileSync as writeFileSync17 } from "fs";
33029
33357
  import { join as join49 } from "path";
33030
33358
  import { homedir as homedir18 } from "os";
33031
33359
 
@@ -33630,6 +33958,7 @@ async function probeLspServers(config, baseDir, deps = {}) {
33630
33958
  }
33631
33959
 
33632
33960
  // src/cli/repl.ts
33961
+ init_client2();
33633
33962
  init_config();
33634
33963
  init_session_logger();
33635
33964
 
@@ -33637,14 +33966,14 @@ init_session_logger();
33637
33966
  init_colors();
33638
33967
  init_js_identifiers();
33639
33968
  init_i18n();
33640
- import { existsSync as existsSync54, readFileSync as readFileSync36 } from "fs";
33969
+ import { existsSync as existsSync52, readFileSync as readFileSync34 } from "fs";
33641
33970
  import { join as join48 } from "path";
33642
33971
  function readActivePlan(baseDir) {
33643
33972
  const p = join48(baseDir, ".mma", "plans", "active.json");
33644
- if (!existsSync54(p))
33973
+ if (!existsSync52(p))
33645
33974
  return null;
33646
33975
  try {
33647
- const raw = readFileSync36(p, "utf-8");
33976
+ const raw = readFileSync34(p, "utf-8");
33648
33977
  if (!raw.trim())
33649
33978
  return null;
33650
33979
  const parsed = JSON.parse(raw);
@@ -33771,7 +34100,7 @@ class Repl {
33771
34100
  pendingClipboardImage = null;
33772
34101
  exitOnClose = false;
33773
34102
  activePlan = null;
33774
- lastPlanSig = "";
34103
+ lastPlanSig = null;
33775
34104
  rl;
33776
34105
  agent;
33777
34106
  config;
@@ -33781,7 +34110,7 @@ class Repl {
33781
34110
  logger;
33782
34111
  slog;
33783
34112
  envReport;
33784
- constructor(agent, config, sessionManager, skillsModule, pluginManager, configDir, baseDir, noAgentsMd, logger, exitOnClose, envReport) {
34113
+ constructor(agent, config, sessionManager, skillsModule, pluginManager, configDir, baseDir, noAgentsMd, logger, exitOnClose, envReport, historyPath) {
33785
34114
  this.agent = agent;
33786
34115
  this.config = config;
33787
34116
  this.exitOnClose = exitOnClose === true;
@@ -33794,7 +34123,7 @@ class Repl {
33794
34123
  this.configDir = configDir || join49(homedir18(), ".mma");
33795
34124
  this.baseDir = baseDir || process.cwd();
33796
34125
  this.noAgentsMd = noAgentsMd === true;
33797
- this.historyPath = join49(homedir18(), ".mma", "repl-history");
34126
+ this.historyPath = historyPath ?? join49(homedir18(), ".mma", "repl-history");
33798
34127
  this.loadHistory();
33799
34128
  this.rl = process.stdin.isTTY ? new LineEditor({
33800
34129
  input: process.stdin,
@@ -33827,9 +34156,9 @@ class Repl {
33827
34156
  this.setupListeners();
33828
34157
  }
33829
34158
  loadHistory() {
33830
- if (existsSync55(this.historyPath)) {
34159
+ if (existsSync53(this.historyPath)) {
33831
34160
  try {
33832
- const raw = readFileSync37(this.historyPath, "utf-8");
34161
+ const raw = readFileSync35(this.historyPath, "utf-8");
33833
34162
  this.history = raw.split(`
33834
34163
  `).filter(Boolean).slice(-this.maxHistory);
33835
34164
  } catch {
@@ -33942,8 +34271,7 @@ class Repl {
33942
34271
  let forceExitTimer = null;
33943
34272
  process.on("SIGINT", () => {
33944
34273
  if (this.agentRunning) {
33945
- console.log(pc2.yellow(`
33946
- [Ctrl+C] Остановка агента... (ещё раз — принудительно)`));
34274
+ console.log(pc2.yellow(t("repl.ctrl_c_interrupt")));
33947
34275
  this.agent.shutdown();
33948
34276
  this.agentRunning = false;
33949
34277
  if (forceExitTimer)
@@ -34073,6 +34401,10 @@ ${t("image.clipboard_empty")}`));
34073
34401
  return;
34074
34402
  }
34075
34403
  this.activePlan = plan;
34404
+ if (this.lastPlanSig === null) {
34405
+ this.lastPlanSig = sig;
34406
+ return;
34407
+ }
34076
34408
  if (sig !== this.lastPlanSig) {
34077
34409
  this.lastPlanSig = sig;
34078
34410
  renderer.planBlock(formatPlanChecklist(plan));
@@ -34213,7 +34545,7 @@ ${t("image.clipboard_empty")}`));
34213
34545
  join49(this.baseDir, ".mma", "AGENTS.md"),
34214
34546
  join49(this.configDir, "AGENTS.md")
34215
34547
  ];
34216
- const foundAgents = agentsMdCandidates.filter((p) => existsSync55(p));
34548
+ const foundAgents = agentsMdCandidates.filter((p) => existsSync53(p));
34217
34549
  if (foundAgents.length > 0) {
34218
34550
  for (const p of foundAgents) {
34219
34551
  row(t("repl.agents_label"), pc2.dim(p));
@@ -34259,7 +34591,8 @@ ${t("image.clipboard_empty")}`));
34259
34591
  async probeLspBanner() {
34260
34592
  const config = this.config.lsp ?? DEFAULT_LSP_CONFIG;
34261
34593
  try {
34262
- const summary = await probeLspServers(config, this.baseDir, { logger: this.logger });
34594
+ const lspOnlyLogger = this.logger ? new FileOnlyLogger(this.logger) : undefined;
34595
+ const summary = await probeLspServers(config, this.baseDir, { logger: lspOnlyLogger });
34263
34596
  this.slog.logSessionStart({
34264
34597
  environment: this.envReport,
34265
34598
  lspProbe: {
@@ -34322,10 +34655,9 @@ init_setup();
34322
34655
  init_config2();
34323
34656
  init_i18n();
34324
34657
  init_colors();
34325
- import { existsSync as existsSync56, readFileSync as readFileSync38 } from "fs";
34326
- import { join as join51, dirname as dirname19 } from "path";
34658
+ import { existsSync as existsSync54 } from "fs";
34659
+ import { join as join51 } from "path";
34327
34660
  import { homedir as homedir20 } from "os";
34328
- import { fileURLToPath as fileURLToPath7 } from "url";
34329
34661
 
34330
34662
  // src/modules/updater/index.ts
34331
34663
  init_checker();
@@ -34472,23 +34804,10 @@ function installCrashHandlers() {
34472
34804
  }
34473
34805
 
34474
34806
  // src/cli/main.ts
34475
- function readVersion4() {
34476
- const here = dirname19(fileURLToPath7(import.meta.url));
34477
- const candidates = [join51(here, "..", "..", "package.json"), join51(here, "..", "package.json")];
34478
- for (const p of candidates) {
34479
- if (existsSync56(p)) {
34480
- try {
34481
- const raw = JSON.parse(readFileSync38(p, "utf8"));
34482
- if (raw.version)
34483
- return raw.version;
34484
- } catch {}
34485
- }
34486
- }
34487
- return "0.0.0";
34488
- }
34807
+ init_version();
34489
34808
  function startAutoUpdate(config) {
34490
34809
  try {
34491
- const module = new UpdaterModule(config.updater, readVersion4(), "micro-models-agent", {
34810
+ const module = new UpdaterModule(config.updater, readMmaVersion(), "micro-models-agent", {
34492
34811
  info: (m) => process.stderr.write(pc2.dim(m) + `
34493
34812
  `),
34494
34813
  warn: (m) => process.stderr.write(pc2.yellow(m) + `
@@ -34564,7 +34883,7 @@ async function main() {
34564
34883
  process.exit(exitCode);
34565
34884
  } else {
34566
34885
  const configPath = join51(homedir20(), ".mma", "config.json");
34567
- if (!existsSync56(configPath)) {
34886
+ if (!existsSync54(configPath)) {
34568
34887
  console.log(pc2.yellow(`
34569
34888
  ` + t("cli.first_run") + `
34570
34889
  `));
@@ -34581,9 +34900,9 @@ async function main() {
34581
34900
  config2.maxToolIterations = answers.maxToolIterations;
34582
34901
  config2.locale = answers.locale;
34583
34902
  if (config2.security) {
34584
- config2.security.enabled = answers.securityBashBlock || answers.securityFlagsBlock || !answers.securityPathsDeny;
34585
- config2.security.bash.enabled = config2.security.enabled;
34586
- config2.security.bash.blockDangerousFlags = answers.securityFlagsBlock;
34903
+ config2.security.enabled = true;
34904
+ config2.security.bash.enabled = true;
34905
+ config2.security.bash.blockDangerousFlags = answers.securityFlagsBlock || config2.security.bash.blockDangerousFlags;
34587
34906
  if (answers.securityBashBlock) {
34588
34907
  config2.security.bash.blacklist = [
34589
34908
  ...new Set([