micro-models-agent 2.10.0 → 2.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +33 -0
- package/dist/assets/app.js +22 -4
- package/dist/assets/index.html +29 -0
- package/dist/assets/js/dom.js +2 -1
- package/dist/assets/js/feed.js +38 -26
- package/dist/assets/js/format.js +1 -1
- package/dist/assets/js/rail.js +114 -0
- package/dist/assets/js/settings-controls.js +2 -2
- package/dist/assets/js/settings.js +246 -51
- package/dist/assets/js/store.js +3 -0
- package/dist/assets/js/tabs.js +104 -2
- package/dist/assets/styles.css +215 -66
- package/dist/cli/cache-line.js +1 -0
- package/dist/cli/command-suggest.js +5 -4
- package/dist/cli/commands.js +231 -122
- package/dist/cli/completer.js +132 -31
- package/dist/cli/index.js +1 -0
- package/dist/cli/json-payload.js +4 -4
- package/dist/cli/main.js +166 -61
- package/dist/cli/onboarding-commands.js +126 -0
- package/dist/cli/plugin-commands.js +2 -1
- package/dist/cli/relaunch.js +16 -13
- package/dist/cli/repl-commands-lsp.js +75 -0
- package/dist/cli/repl-commands-skills.js +105 -0
- package/dist/cli/repl-commands.js +589 -266
- package/dist/cli/repl.js +436 -177
- package/dist/cli/run-result.js +5 -6
- package/dist/cli/security-commands.js +37 -28
- package/dist/cli/setup-order.js +4 -55
- package/dist/cli/setup-prompt.js +3 -2
- package/dist/cli/setup.js +27 -107
- package/dist/cli/web-command.js +98 -0
- package/dist/config/budget.js +5 -5
- package/dist/config/config.js +103 -96
- package/dist/config/defaults.js +38 -8
- package/dist/config/domains.js +24 -27
- package/dist/config/env.js +46 -0
- package/dist/config/experts.js +2 -0
- package/dist/config/index.js +1 -0
- package/dist/config/paths.js +56 -0
- package/dist/config/redact.js +44 -0
- package/dist/config/registry-extra.js +334 -0
- package/dist/config/registry.js +584 -0
- package/dist/config/security.js +35 -36
- package/dist/config/timeouts.js +74 -0
- package/dist/config/utils.js +16 -15
- package/dist/core/agent/artifact-progress.js +59 -0
- package/dist/core/agent/artifact-resolve.js +79 -0
- package/dist/core/agent/audit-gate.js +7 -3
- package/dist/core/agent/compaction.js +2 -2
- package/dist/core/agent/constants.js +33 -4
- package/dist/core/agent/context-log.js +454 -0
- package/dist/core/agent/context-renderer.js +5 -0
- package/dist/core/agent/critic-gate.js +132 -0
- package/dist/core/agent/hallucination-gate.js +148 -38
- package/dist/core/agent/loop-state.js +15 -0
- package/dist/core/agent/prefix-monitor.js +1 -1
- package/dist/core/agent/reasoning-resolver.js +10 -1
- package/dist/core/agent/token-tracker.js +7 -4
- package/dist/core/agent/tool-batch.js +93 -18
- package/dist/core/agent/tool-images.js +18 -0
- package/dist/core/agent/tool-output.js +65 -11
- package/dist/core/agent/tool-pairing.js +60 -0
- package/dist/core/agent-moe.js +25 -24
- package/dist/core/agent.js +429 -108
- package/dist/core/bootstrap/agent-phase.js +66 -0
- package/dist/core/bootstrap/config-phase.js +86 -0
- package/dist/core/bootstrap/modules-phase.js +269 -0
- package/dist/core/bootstrap/provider-phase.js +183 -0
- package/dist/core/bootstrap/session-phase.js +204 -0
- package/dist/core/bootstrap/state.js +94 -0
- package/dist/core/bootstrap/workspace-phase.js +115 -0
- package/dist/core/bootstrap.js +92 -660
- package/dist/core/crash-handler.js +17 -7
- package/dist/core/environment.js +37 -12
- package/dist/core/host-plan.js +43 -0
- package/dist/core/host.js +23 -0
- package/dist/core/index.js +1 -0
- package/dist/core/prompt-builder.js +17 -1
- package/dist/core/prompt-overflow.js +42 -43
- package/dist/core/session-logger.js +122 -30
- package/dist/core/system-prompt.js +117 -0
- package/dist/core/version.js +6 -4
- package/dist/core/web-host.js +34 -0
- package/dist/core/workspace.js +19 -15
- package/dist/i18n/en.json +106 -21
- package/dist/i18n/index.js +37 -3
- package/dist/i18n/ru.json +109 -21
- package/dist/index.js +7 -3
- package/dist/llm/image-utils.js +55 -64
- package/dist/llm/llm-errors.js +9 -0
- package/dist/llm/model-loader.js +10 -32
- package/dist/llm/models-dev.js +121 -0
- package/dist/llm/openai-compat.js +146 -73
- package/dist/llm/orchestrator.js +33 -11
- package/dist/llm/provider-budget.js +2 -2
- package/dist/llm/provider-context.js +233 -0
- package/dist/llm/provider.js +1 -1
- package/dist/llm/response.js +5 -0
- package/dist/llm/token-counter.js +27 -11
- package/dist/logger/app-logger.js +48 -22
- package/dist/logger/file-log.js +28 -19
- package/dist/logger/index.js +1 -0
- package/dist/main.js +24285 -22447
- package/dist/migration/backup.js +14 -3
- package/dist/migration/detect.js +12 -3
- package/dist/migration/index.js +1 -0
- package/dist/modules/artifacts/store.js +9 -8
- package/dist/modules/browser/actions.js +6 -0
- package/dist/modules/browser/bridge-client.js +14 -12
- package/dist/modules/browser/bridge-path.js +4 -4
- package/dist/modules/browser/console-buffer.js +37 -0
- package/dist/modules/browser/cookie-store.js +5 -0
- package/dist/modules/browser/driver-types.js +1 -0
- package/dist/modules/browser/driver.js +19 -11
- package/dist/modules/browser/index.js +3 -1
- package/dist/modules/browser/module.js +2 -1
- package/dist/modules/browser/session.js +10 -30
- package/dist/modules/browser/snapshot.js +19 -13
- package/dist/modules/browser/types.js +1 -0
- package/dist/modules/certification/cli.js +28 -20
- package/dist/modules/certification/fact-checker.js +9 -1
- package/dist/modules/certification/loader.js +9 -2
- package/dist/modules/certification/manifest.js +83 -21
- package/dist/modules/certification/runner.js +23 -13
- package/dist/modules/certification/scenarios.js +20 -20
- package/dist/modules/certification/syntax-scenarios.js +1 -2
- package/dist/modules/context/chunk-query.js +11 -9
- package/dist/modules/context/fact-extractor.js +37 -39
- package/dist/modules/context/index.js +1 -0
- package/dist/modules/context/manager.js +106 -80
- package/dist/modules/critic/client.js +176 -0
- package/dist/modules/critic/evidence.js +81 -0
- package/dist/modules/critic/provider.js +45 -0
- package/dist/modules/critic/tools.js +78 -0
- package/dist/modules/critic/types.js +1 -0
- package/dist/modules/execution/audit-runners.js +28 -34
- package/dist/modules/execution/auditor.js +53 -49
- package/dist/modules/execution/execution-plugin.js +142 -102
- package/dist/modules/execution/index.js +1 -1
- package/dist/modules/execution/input-from.js +37 -13
- package/dist/modules/execution/module.js +368 -191
- package/dist/modules/execution/moe-executor.js +53 -26
- package/dist/modules/execution/plan-actions.js +320 -202
- package/dist/modules/execution/plan-checks.js +35 -0
- package/dist/modules/execution/plan-coverage.js +36 -33
- package/dist/modules/execution/plan-steps.js +11 -3
- package/dist/modules/execution/plan-store.js +83 -17
- package/dist/modules/execution/plan-tool.js +89 -35
- package/dist/modules/execution/plan-validator.js +30 -10
- package/dist/modules/execution/planner.js +22 -13
- package/dist/modules/execution/stuck-detector.js +196 -93
- package/dist/modules/execution/tracker.js +22 -8
- package/dist/modules/execution/verifier.js +69 -48
- package/dist/modules/execution/windows-commands.js +12 -12
- package/dist/modules/hallucination/artifact-claim.js +93 -0
- package/dist/modules/hallucination/confidence.js +13 -11
- package/dist/modules/hallucination/consistency.js +10 -2
- package/dist/modules/hallucination/detector.js +10 -5
- package/dist/modules/hallucination/factual.js +43 -26
- package/dist/modules/hallucination/index.js +1 -0
- package/dist/modules/hallucination/js-identifiers.js +44 -45
- package/dist/modules/hallucination/llm-judge.js +17 -12
- package/dist/modules/hallucination/success-claim.js +59 -0
- package/dist/modules/indexer/cache.js +52 -4
- package/dist/modules/indexer/index.js +1 -0
- package/dist/modules/indexer/map-command.js +2 -4
- package/dist/modules/indexer/map-select.js +16 -16
- package/dist/modules/indexer/module.js +47 -12
- package/dist/modules/indexer/project-profile.js +8 -7
- package/dist/modules/indexer/symbols.js +15 -15
- package/dist/modules/indexer/walker.js +159 -29
- package/dist/modules/lsp/check-tool.js +7 -5
- package/dist/modules/lsp/client.js +39 -39
- package/dist/modules/lsp/command.js +17 -24
- package/dist/modules/lsp/config.js +12 -11
- package/dist/modules/lsp/index.js +1 -0
- package/dist/modules/lsp/module.js +48 -46
- package/dist/modules/lsp/probe.js +13 -14
- package/dist/modules/lsp/project-root.js +14 -17
- package/dist/modules/lsp/startup-check.js +18 -16
- package/dist/modules/mcp/client.js +59 -62
- package/dist/modules/mcp/index.js +1 -0
- package/dist/modules/mcp/module.js +66 -37
- package/dist/modules/mcp/registry.js +5 -0
- package/dist/modules/memory/index.js +1 -0
- package/dist/modules/memory/module.js +12 -8
- package/dist/modules/memory/search.js +4 -1
- package/dist/modules/memory/store.js +40 -2
- package/dist/modules/onboarding/init.js +47 -0
- package/dist/modules/onboarding/meet.js +30 -0
- package/dist/modules/onboarding/mode.js +30 -0
- package/dist/modules/onboarding/prompts.js +43 -0
- package/dist/modules/pipelines/engine.js +10 -0
- package/dist/modules/pipelines/index.js +1 -0
- package/dist/modules/pipelines/parser.js +4 -1
- package/dist/modules/pipelines/template.js +4 -0
- package/dist/modules/plugins/builtin/lint-on-write.js +80 -67
- package/dist/modules/plugins/builtin/notify.js +2 -0
- package/dist/modules/plugins/index.js +1 -0
- package/dist/modules/plugins/loader.js +18 -3
- package/dist/modules/plugins/manager.js +37 -17
- package/dist/modules/pricing/index.js +10 -11
- package/dist/modules/pricing/prices.js +12 -12
- package/dist/modules/processes/index.js +1 -0
- package/dist/modules/processes/kill-tree.js +27 -31
- package/dist/modules/processes/registry.js +44 -25
- package/dist/modules/processes/runner.js +4 -4
- package/dist/modules/providers/capabilities.js +28 -0
- package/dist/modules/providers/create.js +3 -4
- package/dist/modules/providers/factory.js +7 -7
- package/dist/modules/providers/fallback.js +12 -12
- package/dist/modules/providers/health.js +4 -4
- package/dist/modules/providers/manager.js +50 -19
- package/dist/modules/providers/naming.js +15 -0
- package/dist/modules/providers/preflight.js +71 -0
- package/dist/modules/providers/presets.js +39 -8
- package/dist/modules/providers/registry.js +7 -2
- package/dist/modules/reasoning/policy.js +8 -6
- package/dist/modules/reasoning/probe.js +30 -25
- package/dist/modules/security/audit-log.js +19 -36
- package/dist/modules/security/audit-notifier.js +35 -75
- package/dist/modules/security/command-validator.js +99 -51
- package/dist/modules/security/content-scanner.js +8 -8
- package/dist/modules/security/data-sanitizer.js +9 -16
- package/dist/modules/security/encryption.js +32 -52
- package/dist/modules/security/index.js +2 -1
- package/dist/modules/security/network-validator.js +120 -43
- package/dist/modules/security/path-validator.js +61 -46
- package/dist/modules/security/protected-paths.js +73 -0
- package/dist/modules/security/rate-limiter.js +17 -49
- package/dist/modules/security/security-policies.js +25 -57
- package/dist/modules/security/session-encryption.js +32 -71
- package/dist/modules/security/session-isolation.js +15 -28
- package/dist/modules/session/auto-name.js +63 -0
- package/dist/modules/session/export.js +98 -0
- package/dist/modules/session/index.js +1 -0
- package/dist/modules/session/manager.js +79 -28
- package/dist/modules/session/module.js +4 -3
- package/dist/modules/session/store.js +57 -20
- package/dist/modules/setup/answers.js +35 -0
- package/dist/modules/setup/apply.js +79 -0
- package/dist/modules/setup/discovery.js +72 -0
- package/dist/modules/skills/index.js +1 -0
- package/dist/modules/skills/loader.js +9 -1
- package/dist/modules/skills/module.js +11 -0
- package/dist/modules/updater/changelog-reader.js +15 -16
- package/dist/modules/updater/checker.js +9 -4
- package/dist/modules/updater/dev-detect.js +7 -6
- package/dist/modules/updater/index.js +1 -0
- package/dist/modules/updater/module.js +17 -15
- package/dist/modules/user-profile/compressor.js +2 -0
- package/dist/modules/user-profile/index.js +1 -0
- package/dist/modules/user-profile/profile.js +10 -1
- package/dist/modules/webui/answer-api.js +29 -0
- package/dist/modules/webui/assets.js +50 -0
- package/dist/modules/webui/body.js +21 -0
- package/dist/modules/webui/commands-settings.js +209 -0
- package/dist/modules/webui/commands-shared.js +60 -0
- package/dist/modules/webui/commands.js +174 -0
- package/dist/modules/webui/config-api.js +134 -0
- package/dist/modules/webui/file-api.js +202 -0
- package/dist/modules/webui/forward.js +159 -0
- package/dist/modules/webui/history-api.js +112 -0
- package/dist/modules/webui/host-state.js +60 -0
- package/dist/modules/webui/i18n-api.js +9 -0
- package/dist/modules/webui/limits.js +6 -0
- package/dist/modules/webui/log-forward.js +20 -0
- package/dist/modules/webui/memory-commands.js +64 -0
- package/dist/modules/webui/model-cache.js +18 -0
- package/dist/modules/webui/module.js +148 -0
- package/dist/modules/webui/persist.js +24 -0
- package/dist/modules/webui/prompt-io.js +97 -0
- package/dist/modules/webui/providers-api.js +337 -0
- package/dist/modules/webui/providers-view.js +56 -0
- package/dist/modules/webui/reasoning-view.js +15 -0
- package/dist/modules/webui/run-agent.js +81 -0
- package/dist/modules/webui/server.js +284 -0
- package/dist/modules/webui/sessions-api.js +178 -0
- package/dist/modules/webui/settings-api.js +561 -0
- package/dist/modules/webui/setup-api.js +201 -0
- package/dist/modules/webui/state.js +74 -0
- package/dist/modules/webui/util.js +261 -0
- package/dist/output/bus.js +7 -2
- package/dist/output/channel.js +80 -28
- package/dist/output/format.js +2 -1
- package/dist/output/index.js +1 -0
- package/dist/output/json-sink.js +5 -3
- package/dist/output/machine.js +4 -3
- package/dist/output/session-sink.js +4 -4
- package/dist/tools/approve.js +4 -0
- package/dist/tools/attach-image.js +5 -4
- package/dist/tools/auto-fixer.js +26 -44
- package/dist/tools/auto-tags.js +83 -0
- package/dist/tools/bash.js +193 -116
- package/dist/tools/browser.js +76 -31
- package/dist/tools/chunk-query.js +14 -6
- package/dist/tools/create-dir.js +9 -5
- package/dist/tools/delete-file.js +9 -5
- package/dist/tools/download-file.js +32 -7
- package/dist/tools/edit-file.js +52 -14
- package/dist/tools/enable-tools.js +6 -6
- package/dist/tools/executor.js +132 -27
- package/dist/tools/file-info.js +3 -1
- package/dist/tools/filter-tools.js +3 -3
- package/dist/tools/glob-tool.js +4 -1
- package/dist/tools/grep-tool.js +13 -10
- package/dist/tools/hidden-tools-block.js +6 -6
- package/dist/tools/index.js +16 -8
- package/dist/tools/list-dir.js +4 -2
- package/dist/tools/load-skill.js +7 -0
- package/dist/tools/mcp-call.js +15 -4
- package/dist/tools/meet-done.js +14 -0
- package/dist/tools/move-file.js +14 -7
- package/dist/tools/path-utils.js +29 -29
- package/dist/tools/pipeline-run.js +29 -7
- package/dist/tools/preview.js +1 -1
- package/dist/tools/process-ids.js +31 -0
- package/dist/tools/process-kill.js +9 -16
- package/dist/tools/process-list.js +1 -0
- package/dist/tools/process-log.js +11 -16
- package/dist/tools/process-wait.js +63 -0
- package/dist/tools/prompt-io.js +4 -3
- package/dist/tools/question.js +172 -25
- package/dist/tools/read-file.js +55 -34
- package/dist/tools/recall.js +14 -8
- package/dist/tools/registry.js +6 -5
- package/dist/tools/remember.js +8 -4
- package/dist/tools/scope-request.js +11 -6
- package/dist/tools/search-history.js +5 -2
- package/dist/tools/session-info.js +2 -0
- package/dist/tools/set-thinking.js +8 -4
- package/dist/tools/subagent.js +59 -39
- package/dist/tools/syntax-validator.js +292 -90
- package/dist/tools/user-input.js +22 -18
- package/dist/tools/web-browse.js +9 -3
- package/dist/tools/web-fetch.js +12 -23
- package/dist/tools/web-search.js +7 -5
- package/dist/tools/write-file.js +45 -15
- package/dist/ui/action-log.js +13 -0
- package/dist/ui/box.js +4 -3
- package/dist/ui/colors.js +2 -0
- package/dist/ui/diff.js +6 -5
- package/dist/ui/index.js +1 -0
- package/dist/ui/line-editor.js +230 -122
- package/dist/ui/line-math.js +10 -2
- package/dist/ui/md-formatter.js +57 -37
- package/dist/ui/output.js +8 -3
- package/dist/ui/plan-view.js +21 -10
- package/dist/ui/renderer.js +393 -67
- package/dist/ui/spinner.js +16 -5
- package/dist/ui/table.js +15 -17
- package/dist/ui/theme.js +143 -0
- package/dist/utils/child-run.js +20 -0
- package/dist/utils/error.js +1 -1
- package/dist/utils/index.js +2 -0
- package/dist/utils/retry.js +2 -2
- package/dist/utils/truncate.js +1 -1
- package/package.json +1 -1
package/dist/tools/bash.js
CHANGED
|
@@ -6,41 +6,43 @@ import { processRegistry, registerKillable, unregisterKillable } from "../module
|
|
|
6
6
|
import { t } from "../i18n/index";
|
|
7
7
|
import { platform } from "os";
|
|
8
8
|
import { MAX_PREVIEW_LINES } from "./preview";
|
|
9
|
+
import { BASH_GRACE_MS as CANONICAL_BASH_GRACE_MS,
|
|
10
|
+
/** Короткое окно для явного `background: true` — ловит ошибки спавна. */
|
|
11
|
+
BASH_SPAWN_SETTLE_MS as SPAWN_SETTLE_MS, } from "../config/timeouts";
|
|
9
12
|
/**
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* "server", etc. runs normally, and any genuinely long-running command is
|
|
14
|
-
* caught regardless of how it is written.
|
|
13
|
+
* Команда, живая после окна ожидания, уходит в фон. Решение — по поведению
|
|
14
|
+
* процесса (всё ещё жив), а не по словам в тексте: упоминание "vite"/"server"
|
|
15
|
+
* выполняется обычно, а любая реально долгая команда ловится как ни пиши.
|
|
15
16
|
*/
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
/** Number of already-emitted lines shown in the background start message. */
|
|
17
|
+
/** Публичный экспорт-алиас: то же значение из реестра (используют тесты). */
|
|
18
|
+
export const BASH_GRACE_MS = CANONICAL_BASH_GRACE_MS;
|
|
19
|
+
/** Строк уже выданного вывода в стартовом сообщении фона. */
|
|
20
20
|
const BG_OUTPUT_PREVIEW_LINES = 15;
|
|
21
21
|
let bashGraceMs = BASH_GRACE_MS;
|
|
22
|
-
/**
|
|
22
|
+
/** Тестовый хук: переопределить окно автобэкграунда. */
|
|
23
23
|
export function setBashGraceMs(ms) {
|
|
24
24
|
bashGraceMs = ms;
|
|
25
25
|
}
|
|
26
26
|
/**
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
* same hint forever (observed: `head` called 5×, `Select-String` 3× in one run).
|
|
27
|
+
* Подряд идущие падения по первому слову команды (сброс при успехе). Модель,
|
|
28
|
+
* долбящую запрещённый Unix в cmd.exe (head/cat/grep/…), первые разы
|
|
29
|
+
* подсказываем; после HARD_BLOCK_THRESHOLD подряд — жёсткий стоп с STOP
|
|
30
|
+
* вместо вечного повтора хинта (наблюдено: `head` 5×, `Select-String` 3×).
|
|
32
31
|
*/
|
|
33
32
|
const FAILING_FIRST_WORDS = new Map();
|
|
34
33
|
const HARD_BLOCK_THRESHOLD = 3;
|
|
35
|
-
|
|
34
|
+
// Module-level Map живёт весь процесс: при смене сессии (REPL /new, /resume)
|
|
35
|
+
// сбрасываем счётчики, чтобы старые падения не влияли на новую задачу.
|
|
36
|
+
let lastFailureSessionId;
|
|
37
|
+
/** Тестовый хук: сбросить счётчики подряд-падений. */
|
|
36
38
|
export function resetBashFailureTracking() {
|
|
37
39
|
FAILING_FIRST_WORDS.clear();
|
|
38
40
|
}
|
|
39
41
|
/**
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
42
|
+
* Детект записи файла через `echo/printf ... > file` — частая привычка модели,
|
|
43
|
+
* ломающаяся в cmd.exe: одиночные кавычки не группируют, `>` относится только
|
|
44
|
+
* к ПОСЛЕДНЕЙ строке многострочной команды, двойные кавычки внутри рвут
|
|
45
|
+
* команду. Возвращает целевой файл или null.
|
|
44
46
|
*/
|
|
45
47
|
export function extractEchoFileWrite(command) {
|
|
46
48
|
if (!/^\s*(?:echo|printf)\b/i.test(command))
|
|
@@ -51,11 +53,10 @@ export function extractEchoFileWrite(command) {
|
|
|
51
53
|
return m[1].replace(/["'']$/g, "");
|
|
52
54
|
}
|
|
53
55
|
/**
|
|
54
|
-
* cmd.exe
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
* "
|
|
58
|
-
* `echo "a;b"` stays intact. (Single quotes are not special in cmd.)
|
|
56
|
+
* В cmd.exe разделитель — `&`, а не `;` (bash). Модель регулярно чейнит через
|
|
57
|
+
* `;` — без трансляции cmd отдаёт `;` первым аргументом команды (напр.
|
|
58
|
+
* `node x.ts;` → ENOENT для "x.ts;"). `;` → `&` только ВНЕ двойных кавычек,
|
|
59
|
+
* чтобы `echo "a;b"` уцелел (одиночные в cmd не спецсимвол).
|
|
59
60
|
*/
|
|
60
61
|
export function translateSemicolonsForCmd(command) {
|
|
61
62
|
let out = "";
|
|
@@ -74,13 +75,12 @@ export function translateSemicolonsForCmd(command) {
|
|
|
74
75
|
function adaptCommandForWindows(command) {
|
|
75
76
|
if (platform() !== "win32")
|
|
76
77
|
return command;
|
|
77
|
-
//
|
|
78
|
-
//
|
|
79
|
-
// forces a successful exit code regardless of the previous command's result.
|
|
78
|
+
// Модель иногда дописывает `|| true` (bash-идиома гашения ошибок), которой
|
|
79
|
+
// PowerShell не знает. Замена на `; exit 0` — успешный код при любом исходе.
|
|
80
80
|
if (/\|\|\s*true\b/.test(command)) {
|
|
81
81
|
command = command.replace(/\s*\|\|\s*true\b/g, "; exit 0");
|
|
82
82
|
}
|
|
83
|
-
// Windows mkdir
|
|
83
|
+
// Windows mkdir не знает -p/--parents, но промежуточные dirs создаёт по умолчанию.
|
|
84
84
|
const trimmed = command.trim();
|
|
85
85
|
if (/^mkdir\s+(-p|--parents)\b/.test(trimmed)) {
|
|
86
86
|
return trimmed.replace(/^mkdir\s+(-p|--parents)\s*/, "mkdir ");
|
|
@@ -88,9 +88,9 @@ function adaptCommandForWindows(command) {
|
|
|
88
88
|
if (/^mkdir\s+(-p|--parents)\s*$/.test(trimmed)) {
|
|
89
89
|
return "mkdir";
|
|
90
90
|
}
|
|
91
|
-
//
|
|
92
|
-
//
|
|
93
|
-
//
|
|
91
|
+
// Трансляция простых Unix-команд в эквиваленты cmd.exe. Переписывается только
|
|
92
|
+
// ведущее слово; флаги сквозняком (ls -la → dir -la, cmd терпит). Формы с
|
|
93
|
+
// пайпом не трогаем — сломаются.
|
|
94
94
|
const firstWord = trimmed.split(/\s+/)[0]?.split(/[\\/]/).pop();
|
|
95
95
|
const translated = firstWord ? UNIX_TO_WIN_TRANSLATE[firstWord] : undefined;
|
|
96
96
|
if (translated &&
|
|
@@ -100,10 +100,10 @@ function adaptCommandForWindows(command) {
|
|
|
100
100
|
!trimmed.includes(";")) {
|
|
101
101
|
return trimmed.replace(firstWord, translated);
|
|
102
102
|
}
|
|
103
|
-
//
|
|
103
|
+
// Адаптации кодировок не надо — UTF-8/OEM-декод в registry.ts.
|
|
104
104
|
return translateSemicolonsForCmd(command);
|
|
105
105
|
}
|
|
106
|
-
/**
|
|
106
|
+
/** Типовой маппинг Unix → Windows для хинтов об ошибках. */
|
|
107
107
|
const UNIX_TO_WIN_HINTS = {
|
|
108
108
|
ls: "Use the list_dir tool instead.",
|
|
109
109
|
pwd: "Use the file_info tool instead.",
|
|
@@ -142,25 +142,23 @@ const UNIX_TO_WIN_HINTS = {
|
|
|
142
142
|
"Get-ChildItem": "Use the list_dir tool instead.",
|
|
143
143
|
"Remove-Item": "Use the delete_file tool instead.",
|
|
144
144
|
};
|
|
145
|
-
/** Unix
|
|
145
|
+
/** Unix-команды, прозрачно транслируемые в эквиваленты cmd.exe. */
|
|
146
146
|
const UNIX_TO_WIN_TRANSLATE = {
|
|
147
147
|
ls: "dir",
|
|
148
148
|
pwd: "cd",
|
|
149
149
|
cat: "type",
|
|
150
150
|
};
|
|
151
151
|
/**
|
|
152
|
-
*
|
|
153
|
-
*
|
|
154
|
-
*
|
|
155
|
-
*
|
|
156
|
-
* redirect into the real tool.
|
|
152
|
+
* Детект перепутанного tool call в shell-команде — напр. `bash` с командой
|
|
153
|
+
* "create_dir path=C:\...\src" или "read_file file=x". Это вызовы тулов, а не
|
|
154
|
+
* команды; прогон через шелл падает. Возвращает имя тула и сырые аргументы
|
|
155
|
+
* для редиректа в настоящий тул.
|
|
157
156
|
*/
|
|
158
157
|
/**
|
|
159
|
-
* Shell
|
|
160
|
-
*
|
|
161
|
-
*
|
|
162
|
-
*
|
|
163
|
-
* and fails with "Unknown tool: echo".
|
|
158
|
+
* Shell-команды, которые НИКОГДА не считать перепутанными tool call. Модель
|
|
159
|
+
* часто пишет файлы через `echo '<код со знаками =>` или читает через `cat` —
|
|
160
|
+
* это команды шелла. Без гарда эвристика редиректа срабатывает на любое
|
|
161
|
+
* `<слово> <текст с =>` и падает с "Unknown tool: echo".
|
|
164
162
|
*/
|
|
165
163
|
const NEVER_TOOL_CALLS = new Set([
|
|
166
164
|
"echo",
|
|
@@ -185,17 +183,18 @@ const NEVER_TOOL_CALLS = new Set([
|
|
|
185
183
|
"sed",
|
|
186
184
|
"awk",
|
|
187
185
|
]);
|
|
186
|
+
/** Определяет перепутанный tool call в команде bash. Возврат: тул и сырые аргументы. */
|
|
188
187
|
export function detectToolCallInBash(command) {
|
|
189
188
|
const match = command.trim().match(/^([\w-]+)\s+(.+)$/s);
|
|
190
189
|
if (!match)
|
|
191
190
|
return null;
|
|
192
191
|
const tool = match[1];
|
|
193
|
-
//
|
|
192
|
+
// Настоящие shell-команды (echo, cat, …) — никогда не перепутанные tool call.
|
|
194
193
|
if (NEVER_TOOL_CALLS.has(tool))
|
|
195
194
|
return null;
|
|
196
195
|
const rest = match[2].trim();
|
|
197
|
-
//
|
|
198
|
-
//
|
|
196
|
+
// Tool call — только если первое слово похоже на snake_case-имя тула, а
|
|
197
|
+
// остаток содержит хотя бы один '=' или JSON-форму.
|
|
199
198
|
if (!/^[a-z][a-z0-9_]+$/.test(tool))
|
|
200
199
|
return null;
|
|
201
200
|
if (!rest.includes("=") && !rest.startsWith("{"))
|
|
@@ -203,9 +202,9 @@ export function detectToolCallInBash(command) {
|
|
|
203
202
|
return { tool, args: rest };
|
|
204
203
|
}
|
|
205
204
|
/**
|
|
206
|
-
*
|
|
207
|
-
*
|
|
208
|
-
*
|
|
205
|
+
* Парсит сырую строку аргументов перепутанного tool call в Record. Понимает
|
|
206
|
+
* JSON-объекты ("{"path": "..."}") и пары key=value ("path=C:\...\src").
|
|
207
|
+
* Значения — дословным текстом. Возврат: словарь аргументов.
|
|
209
208
|
*/
|
|
210
209
|
export function parseToolArgs(raw) {
|
|
211
210
|
const trimmed = raw.trim();
|
|
@@ -218,29 +217,28 @@ export function parseToolArgs(raw) {
|
|
|
218
217
|
}
|
|
219
218
|
}
|
|
220
219
|
const args = {};
|
|
221
|
-
//
|
|
220
|
+
// Токенизация с уважением к кавычкам в значениях.
|
|
222
221
|
const tokens = trimmed.match(/(?:[^\s"']+|"[^"]*"|'[^']*')+/g) ?? [];
|
|
223
222
|
for (const token of tokens) {
|
|
224
223
|
const eq = token.indexOf("=");
|
|
225
224
|
if (eq > 0) {
|
|
226
225
|
const key = token.slice(0, eq);
|
|
227
226
|
const value = token.slice(eq + 1);
|
|
228
|
-
//
|
|
227
|
+
// Окаймляющие кавычки значений — снять.
|
|
229
228
|
args[key] = value.replace(/^["']|["']$/g, "");
|
|
230
229
|
}
|
|
231
230
|
}
|
|
232
231
|
return args;
|
|
233
232
|
}
|
|
234
233
|
/**
|
|
235
|
-
*
|
|
236
|
-
*
|
|
237
|
-
*
|
|
238
|
-
* cannot close a "run the tests" step as done.
|
|
234
|
+
* Детект отчёта тест-раннера в выводе (bun test/vitest/jest/mocha/`go test`/
|
|
235
|
+
* pytest). Не похоже на тесты — null. Нужно, чтобы вставить warning при
|
|
236
|
+
* падениях и модель не закрыла шаг "прогнать тесты" как done.
|
|
239
237
|
*/
|
|
240
238
|
export function detectTestResults(output) {
|
|
241
239
|
if (!output)
|
|
242
240
|
return null;
|
|
243
|
-
//
|
|
241
|
+
// Сначала однозначные фреймворк-специфичные счётчики.
|
|
244
242
|
const jest = output.match(/Tests:\s+(\d+)\s+passed,\s*(\d+)\s+failed/i);
|
|
245
243
|
if (jest) {
|
|
246
244
|
return { framework: "jest", passed: +jest[1], failed: +jest[2] };
|
|
@@ -265,7 +263,7 @@ export function detectTestResults(output) {
|
|
|
265
263
|
failed: pytest[2] ? +pytest[2] : 0,
|
|
266
264
|
};
|
|
267
265
|
}
|
|
268
|
-
// bun test / vitest:
|
|
266
|
+
// bun test / vitest: покейсные маркеры + хвостовое "N pass / M fail".
|
|
269
267
|
const failMarkers = output.match(/\(fail\)/g)?.length ?? 0;
|
|
270
268
|
const passMarkers = output.match(/\(pass\)/g)?.length ?? 0;
|
|
271
269
|
const xMarkers = output.match(/\s×\s/g)?.length ?? 0;
|
|
@@ -285,17 +283,17 @@ export function detectTestResults(output) {
|
|
|
285
283
|
return null;
|
|
286
284
|
}
|
|
287
285
|
/**
|
|
288
|
-
* R5:
|
|
289
|
-
*
|
|
290
|
-
*
|
|
291
|
-
* concrete hint instead of letting the model stare at an empty output.
|
|
286
|
+
* R5: команда запуска скрипта (bun/node/python/…) вышла в 0, но молча.
|
|
287
|
+
* Частая причина в агент-сессиях: нет точки входа — main() с CLI-аргументами
|
|
288
|
+
* никто не вызывает. Конкретный хинт вместо молчаливого пустого вывода.
|
|
292
289
|
*/
|
|
293
290
|
const CLI_FILE_RUN_RE = /\b(bun|node|deno|python|python3|tsx|ts-node|php|ruby|go\s+run)\S*\s+(run\s+)?["']?[\w./\\-]+\.(ts|js|tsx|jsx|mjs|cjs|py)\b/;
|
|
291
|
+
/** Хинт на пустой прогон CLI-файла (код 0, stdout пуст). */
|
|
294
292
|
export function emptyCliRunHint(command, output, code) {
|
|
295
293
|
if (code !== 0 || output.trim())
|
|
296
294
|
return null;
|
|
297
295
|
if (/[>|]/.test(command))
|
|
298
|
-
return null; //
|
|
296
|
+
return null; // редирект/пайп — вывод ушёл мимо
|
|
299
297
|
if (/\b(bun test|vitest|pytest|jest|mocha|--test)\b/i.test(command))
|
|
300
298
|
return null;
|
|
301
299
|
if (!CLI_FILE_RUN_RE.test(command))
|
|
@@ -303,22 +301,83 @@ export function emptyCliRunHint(command, output, code) {
|
|
|
303
301
|
return "the command exited 0 but printed NOTHING to stdout. If this should run a CLI program, the file probably has no entry point: read it with read_file and check the code actually calls its main function with command-line arguments (e.g. main(process.argv[2])) and prints results with console.log.";
|
|
304
302
|
}
|
|
305
303
|
/**
|
|
306
|
-
*
|
|
307
|
-
*
|
|
308
|
-
*
|
|
309
|
-
*
|
|
310
|
-
* the same failing `npx`/`bunx`/`npm run` command).
|
|
304
|
+
* Детект npm/npx/bunx с "could not determine executable to run" — у пакета
|
|
305
|
+
* нет `bin` (или `npm run <script>` назвал несуществующий скрипт). Конкретный
|
|
306
|
+
* хинт вместо слепых ретраев (наблюдено в двух сессиях: модель жгла итерации
|
|
307
|
+
* на повторах той же падающей команды).
|
|
311
308
|
*/
|
|
312
309
|
const NPM_EXEC_RE = /could not determine executable to run/i;
|
|
313
310
|
/**
|
|
314
|
-
*
|
|
315
|
-
*
|
|
316
|
-
*
|
|
317
|
-
*
|
|
311
|
+
* Команда ждала интерактивного ввода без TTY и абортнулась ("Operation
|
|
312
|
+
* cancelled" у Bun/Node или "stdin is not a TTY"). Типично для голых
|
|
313
|
+
* `bun create`/`npm create`. Конкретное неинтерактивное лечение вместо
|
|
314
|
+
* повтора той же команды.
|
|
318
315
|
*/
|
|
319
316
|
const INTERACTIVE_PROMPT_RE = /operation (?:cancelled|canceled|aborted)|stdin is not a tty|not a tty|cannot prompt without a tty/i;
|
|
320
|
-
/**
|
|
321
|
-
const HIDDEN_PROCESS_TOOLS = ["process_list", "process_log", "process_kill"];
|
|
317
|
+
/** Тулы управления процессами за enable_tools (тег "shell"). */
|
|
318
|
+
const HIDDEN_PROCESS_TOOLS = ["process_list", "process_log", "process_kill", "process_wait"];
|
|
319
|
+
/**
|
|
320
|
+
* Read-only префиксы для мемоизации (см. `manualMemo` тула). Эвристика,
|
|
321
|
+
* намеренно узкая: одиночные вызовы, немутирующие по построению. Всё составное
|
|
322
|
+
* (`&`, `|`, `<`, `>`, `;`) — никогда в мемо. TTL 120с в исполнителе
|
|
323
|
+
* ограничивает протухание от внешних правок.
|
|
324
|
+
*/
|
|
325
|
+
const READONLY_COMMAND_PREFIXES = [
|
|
326
|
+
"git status",
|
|
327
|
+
"git log",
|
|
328
|
+
"git diff",
|
|
329
|
+
"git branch",
|
|
330
|
+
"git tag",
|
|
331
|
+
"git rev-parse",
|
|
332
|
+
"git ls-remote",
|
|
333
|
+
"git remote",
|
|
334
|
+
"git show",
|
|
335
|
+
"npm view",
|
|
336
|
+
"npm list",
|
|
337
|
+
"npm config get",
|
|
338
|
+
"npm whoami",
|
|
339
|
+
"npm ping",
|
|
340
|
+
"npm --version",
|
|
341
|
+
"node --version",
|
|
342
|
+
"python --version",
|
|
343
|
+
"python3 --version",
|
|
344
|
+
"bun --version",
|
|
345
|
+
"git --version",
|
|
346
|
+
"dir",
|
|
347
|
+
"ls",
|
|
348
|
+
"echo",
|
|
349
|
+
"type",
|
|
350
|
+
"where",
|
|
351
|
+
"which",
|
|
352
|
+
"cat",
|
|
353
|
+
];
|
|
354
|
+
const READONLY_COMMAND_PATTERNS = READONLY_COMMAND_PREFIXES.map((p) => new RegExp(`^${p.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(\\s|$)`));
|
|
355
|
+
/**
|
|
356
|
+
* Мемоизируема ли (post-adaptation) команда: одиночный read-only вызов,
|
|
357
|
+
* чей успешный результат можно кешировать. Экспортировано для тестов.
|
|
358
|
+
*/
|
|
359
|
+
export function isMemoizableCommand(command) {
|
|
360
|
+
const cmd = command.trim().toLowerCase();
|
|
361
|
+
if (!cmd || /[|<>;&]/.test(cmd))
|
|
362
|
+
return false;
|
|
363
|
+
return READONLY_COMMAND_PATTERNS.some((re) => re.test(cmd));
|
|
364
|
+
}
|
|
365
|
+
// Оборонительный доступ к мемо: самописные стабы ToolExecutor в тестах (и любой
|
|
366
|
+
// будущий минимальный исполнитель) могут не иметь memo-методов — считать промахом.
|
|
367
|
+
function memoRead(ctx, args) {
|
|
368
|
+
const get = ctx.toolExecutor?.memoGet;
|
|
369
|
+
return typeof get === "function" ? get.call(ctx.toolExecutor, "bash", args) : undefined;
|
|
370
|
+
}
|
|
371
|
+
function memoWrite(ctx, args, result) {
|
|
372
|
+
const set = ctx.toolExecutor?.memoSet;
|
|
373
|
+
if (typeof set === "function")
|
|
374
|
+
set.call(ctx.toolExecutor, "bash", args, result);
|
|
375
|
+
}
|
|
376
|
+
function memoClear(ctx) {
|
|
377
|
+
const clear = ctx.toolExecutor?.memoInvalidate;
|
|
378
|
+
if (typeof clear === "function")
|
|
379
|
+
clear.call(ctx.toolExecutor);
|
|
380
|
+
}
|
|
322
381
|
/**
|
|
323
382
|
* Разрешение bash-security-конфига: session-изоляция поверх глобального,
|
|
324
383
|
* мастер-выключатель `enabled: false` гасит все под-модули.
|
|
@@ -340,15 +399,15 @@ function resolveBashSecurityConfig(ctx) {
|
|
|
340
399
|
function decorateCommandOutput(opts) {
|
|
341
400
|
let output = opts.output;
|
|
342
401
|
const { ctx, command, originalCommand, code, testRun, workdir, securityConfig } = opts;
|
|
343
|
-
// R5:
|
|
344
|
-
//
|
|
402
|
+
// R5: скрипт вышел в 0 молча — вероятно, нет точки входа
|
|
403
|
+
// (main с argv никто не вызывает).
|
|
345
404
|
const cliHint = emptyCliRunHint(command, output, code);
|
|
346
405
|
if (cliHint) {
|
|
347
406
|
output = `(exit code 0, no output)\n\nHint: ${cliHint}`;
|
|
348
407
|
}
|
|
349
|
-
//
|
|
350
|
-
//
|
|
351
|
-
//
|
|
408
|
+
// Автоверификация тест-ранов: упавший сьют нельзя отдавать как чистый
|
|
409
|
+
// успех. Видный маркер, который модель не пропустит (типичный провал:
|
|
410
|
+
// тесты падают при нулевом exit code CLI).
|
|
352
411
|
if (testRun && testRun.failed > 0) {
|
|
353
412
|
output =
|
|
354
413
|
t("exec.test_runner_fail", {
|
|
@@ -364,19 +423,21 @@ function decorateCommandOutput(opts) {
|
|
|
364
423
|
if (!output && code !== 0) {
|
|
365
424
|
output = `(exit code ${code})`;
|
|
366
425
|
}
|
|
367
|
-
// npm/npx/bunx: "could not determine executable to run" —
|
|
368
|
-
//
|
|
369
|
-
// before the win32 hint block.
|
|
426
|
+
// npm/npx/bunx: "could not determine executable to run" — у пакета нет
|
|
427
|
+
// записи `bin` (или скрипта нет). Кроссплатформенно — до win32-блока хинтов.
|
|
370
428
|
output = npmExecHint(output);
|
|
371
|
-
//
|
|
429
|
+
// Интерактивный промпт без TTY (Bun/Node "Operation cancelled").
|
|
372
430
|
output = interactivePromptHint(output);
|
|
373
|
-
//
|
|
374
|
-
//
|
|
375
|
-
//
|
|
376
|
-
//
|
|
377
|
-
//
|
|
378
|
-
// `cat` invisible to the hint/block (observed: model ran cat 4+ times).
|
|
431
|
+
// На Windows — хинт про нерабочие Unix-команды и жёсткий стоп повторяющегося
|
|
432
|
+
// падения. Хинт ключуют по ОРИГИНАЛЬНОМУ первому слову (до трансляции
|
|
433
|
+
// adaptCommandForWindows cat→type): трансляция только для исполнения, а
|
|
434
|
+
// модель писала `cat` — его и знает UNIX_TO_WIN_HINTS. Ключ по транслированному
|
|
435
|
+
// слову делал `cat` невидимым для хинта/блока (наблюдено: cat 4+ раз).
|
|
379
436
|
if (platform() === "win32") {
|
|
437
|
+
if (ctx.sessionId !== lastFailureSessionId) {
|
|
438
|
+
lastFailureSessionId = ctx.sessionId;
|
|
439
|
+
FAILING_FIRST_WORDS.clear();
|
|
440
|
+
}
|
|
380
441
|
const originalFirstWord = originalCommand.trim().split(/\s+/)[0]?.split(/[\\/]/).pop();
|
|
381
442
|
if (originalFirstWord && originalFirstWord in UNIX_TO_WIN_HINTS) {
|
|
382
443
|
if (code === 0) {
|
|
@@ -394,15 +455,15 @@ function decorateCommandOutput(opts) {
|
|
|
394
455
|
}
|
|
395
456
|
}
|
|
396
457
|
}
|
|
397
|
-
//
|
|
458
|
+
// Запись результата в аудит-лог.
|
|
398
459
|
if (securityConfig?.logCommands) {
|
|
399
460
|
logBashCommand(ctx.sessionId, sanitizeCommandForLog(command), code === 0, `Working directory: ${workdir}, Output length: ${output.length}`);
|
|
400
461
|
}
|
|
401
462
|
const lines = output.split("\n");
|
|
402
463
|
if (testRun && testRun.failed > 0) {
|
|
403
|
-
//
|
|
404
|
-
//
|
|
405
|
-
//
|
|
464
|
+
// Детали падения сохранить: раннеры печатают passing-маркеры первыми, а
|
|
465
|
+
// ошибки + итог — в самом конце. Короткий head-превью прячет ровно то,
|
|
466
|
+
// что нужно модели для починки.
|
|
406
467
|
const TEST_TAIL_LINES = 400;
|
|
407
468
|
const kept = lines.slice(-TEST_TAIL_LINES);
|
|
408
469
|
const skipped = lines.length - kept.length;
|
|
@@ -417,13 +478,14 @@ function decorateCommandOutput(opts) {
|
|
|
417
478
|
}
|
|
418
479
|
return output;
|
|
419
480
|
}
|
|
481
|
+
/** Дописывает хинт на npm-без-bin (по выводу). */
|
|
420
482
|
export function npmExecHint(output) {
|
|
421
483
|
if (NPM_EXEC_RE.test(output)) {
|
|
422
484
|
return `${output}\n\nHint: ${t("exec.npm_exec_hint")}`;
|
|
423
485
|
}
|
|
424
486
|
return output;
|
|
425
487
|
}
|
|
426
|
-
/**
|
|
488
|
+
/** Дописывает неинтерактивное лечение при аборте на ожидании stdin. */
|
|
427
489
|
export function interactivePromptHint(output) {
|
|
428
490
|
if (INTERACTIVE_PROMPT_RE.test(output)) {
|
|
429
491
|
return `${output}\n\nHint: ${t("exec.interactive_hint")}`;
|
|
@@ -433,7 +495,7 @@ export function interactivePromptHint(output) {
|
|
|
433
495
|
export const bashTool = {
|
|
434
496
|
name: "bash",
|
|
435
497
|
icon: "💻",
|
|
436
|
-
description: "Execute a shell command and return its output. Use for running tests, build, git, and shell operations. Commands that are still running after a few seconds are automatically moved to the background and return a process id — manage them with process_list, process_log, process_kill. Set background=true to return a process id immediately for commands you know are long-running (dev servers, watchers).\n\nWindows notes: the shell is cmd.exe — PowerShell cmdlets (Write-Host, Get-Content, Select-String, Out-File) and bash heredocs (cat << EOF) do NOT work there. Use the read_file/write_file tools instead of cat/echo redirection. Sequential commands: use && (a leading ; is auto-converted to &).",
|
|
498
|
+
description: "Execute a shell command and return its output. Use for running tests, build, git, and shell operations. Commands that are still running after a few seconds are automatically moved to the background and return a process id — manage them with process_list, process_log, process_wait, process_kill. For a finite background task prefer a single process_wait over polling process_log or sleeping (never use ping/timeout/sleep as a delay). Set background=true to return a process id immediately for commands you know are long-running (dev servers, watchers).\n\nWindows notes: the shell is cmd.exe — PowerShell cmdlets (Write-Host, Get-Content, Select-String, Out-File) and bash heredocs (cat << EOF) do NOT work there. Use the read_file/write_file tools instead of cat/echo redirection. Sequential commands: use && (a leading ; is auto-converted to &).",
|
|
437
499
|
tags: ["shell", "code"],
|
|
438
500
|
parameters: {
|
|
439
501
|
type: "object",
|
|
@@ -450,16 +512,17 @@ export const bashTool = {
|
|
|
450
512
|
},
|
|
451
513
|
required: ["command"],
|
|
452
514
|
},
|
|
515
|
+
/** Мемоизация покомандно (см. isMemoizableCommand) — ведётся здесь. */
|
|
516
|
+
manualMemo: true,
|
|
453
517
|
handler: async (ctx, args) => {
|
|
454
518
|
const originalCommand = String(args.command);
|
|
455
|
-
//
|
|
456
|
-
//
|
|
457
|
-
//
|
|
519
|
+
// Модель иногда шлёт вызов тула (напр. "create_dir path=…") командой bash
|
|
520
|
+
// вместо прямого вызова. Редирект в настоящий тул — намерение сработает,
|
|
521
|
+
// а не упадёт в шелле.
|
|
458
522
|
const toolCall = detectToolCallInBash(originalCommand);
|
|
459
523
|
if (toolCall &&
|
|
460
524
|
toolCall.tool !== "bash" &&
|
|
461
|
-
ctx.toolExecutor
|
|
462
|
-
ctx.toolExecutor.hasTool(toolCall.tool)) {
|
|
525
|
+
ctx.toolExecutor?.hasTool(toolCall.tool)) {
|
|
463
526
|
const redirected = await ctx.toolExecutor.executeByName(toolCall.tool, parseToolArgs(toolCall.args), ctx);
|
|
464
527
|
return {
|
|
465
528
|
success: redirected.success,
|
|
@@ -467,11 +530,9 @@ export const bashTool = {
|
|
|
467
530
|
};
|
|
468
531
|
}
|
|
469
532
|
const command = adaptCommandForWindows(originalCommand);
|
|
470
|
-
//
|
|
471
|
-
//
|
|
472
|
-
//
|
|
473
|
-
// that session. Point the model at enable_tools. (k=v forms are handled
|
|
474
|
-
// by the redirect above and reach the real tool.)
|
|
533
|
+
// Discovery скрытых тулов: при сужении набора process_* невидимы модели,
|
|
534
|
+
// и прогон их через cmd.exe даёт лишь "is not recognized" (в одной сессии —
|
|
535
|
+
// три впустую). Указать на enable_tools (k=v-формы ушли редиректом выше).
|
|
475
536
|
const baseCmd = originalCommand.trim().split(/\s+/)[0];
|
|
476
537
|
if (HIDDEN_PROCESS_TOOLS.includes(baseCmd) &&
|
|
477
538
|
ctx.toolExecutor?.hasTool?.(baseCmd)) {
|
|
@@ -480,10 +541,9 @@ export const bashTool = {
|
|
|
480
541
|
output: t("exec.hidden_tool_hint", { tool: baseCmd }),
|
|
481
542
|
};
|
|
482
543
|
}
|
|
483
|
-
// echo/printf
|
|
484
|
-
//
|
|
485
|
-
//
|
|
486
|
-
// produces correct files every time.
|
|
544
|
+
// echo/printf-редирект в файл ненадёжен в cmd.exe (одиночные кавычки не
|
|
545
|
+
// группируют, многострочник рвётся, вложенные двойные кавычки ломают
|
|
546
|
+
// команду). Вести на write_file — с ним файлы корректны всегда.
|
|
487
547
|
if (platform() === "win32") {
|
|
488
548
|
const echoWrite = extractEchoFileWrite(originalCommand);
|
|
489
549
|
if (echoWrite) {
|
|
@@ -498,28 +558,41 @@ export const bashTool = {
|
|
|
498
558
|
const securityConfig = resolveBashSecurityConfig(ctx);
|
|
499
559
|
const validation = isCommandAllowed(command, securityConfig);
|
|
500
560
|
if (!validation.allowed) {
|
|
501
|
-
// Log security block
|
|
502
561
|
logSecurityBlock(ctx.sessionId, "bash_command", validation.reason || "Command blocked by security policy", sanitizeCommandForLog(originalCommand));
|
|
503
562
|
return {
|
|
504
563
|
success: false,
|
|
505
|
-
output: `[SECURITY BLOCKED] Command is not allowed: ${validation.reason}\nHint:
|
|
564
|
+
output: `[SECURITY BLOCKED] Command is not allowed: ${validation.reason}\nHint: run one plain command per tool call. For output redirection write the file with write_file; for chaining run separate calls; use the "workdir" parameter instead of "cd dir && cmd". Do NOT write a wrapper script (.bat/.cmd/.sh/.ps1) to bypass this — the same rule applies when that script runs.`,
|
|
506
565
|
};
|
|
507
566
|
}
|
|
508
|
-
//
|
|
567
|
+
// Лог запуска команды, если включён.
|
|
509
568
|
if (securityConfig?.logCommands) {
|
|
510
569
|
logBashCommand(ctx.sessionId, sanitizeCommandForLog(command), false, // Will be updated after execution
|
|
511
570
|
`Working directory: ${workdir}`);
|
|
512
571
|
}
|
|
572
|
+
// Мемо результата (manualMemo, после security-гейта, чтобы блок никогда не
|
|
573
|
+
// маскировался stored-результатом): одиночные read-only отдаются из мемо
|
|
574
|
+
// исполнителя; всё остальное его сбрасывает. Фоновые запуски не мемоизируются
|
|
575
|
+
// (process id одноразовые).
|
|
576
|
+
const memoKey = { command, workdir };
|
|
577
|
+
const memoizable = args.background !== true && isMemoizableCommand(command);
|
|
578
|
+
if (memoizable) {
|
|
579
|
+
const hit = memoRead(ctx, memoKey);
|
|
580
|
+
if (hit)
|
|
581
|
+
return { ...hit };
|
|
582
|
+
}
|
|
583
|
+
else {
|
|
584
|
+
memoClear(ctx);
|
|
585
|
+
}
|
|
513
586
|
const entry = processRegistry.start(command, workdir, ctx.sessionId);
|
|
514
587
|
if (ctx.activeCallId) {
|
|
515
|
-
//
|
|
588
|
+
// Исполнителю — убить дочерний процесс в grace-окне при аборте.
|
|
516
589
|
registerKillable(ctx.activeCallId, () => processRegistry.kill(entry.id));
|
|
517
590
|
}
|
|
518
591
|
try {
|
|
519
592
|
const settleMs = args.background === true ? SPAWN_SETTLE_MS : bashGraceMs;
|
|
520
593
|
const exited = await processRegistry.waitForExit(entry.id, settleMs);
|
|
521
594
|
if (exited) {
|
|
522
|
-
//
|
|
595
|
+
// Команда уложилась в окно (включая ошибку спавна).
|
|
523
596
|
if (entry.spawnError) {
|
|
524
597
|
return {
|
|
525
598
|
success: false,
|
|
@@ -541,9 +614,13 @@ export const bashTool = {
|
|
|
541
614
|
workdir,
|
|
542
615
|
securityConfig,
|
|
543
616
|
});
|
|
617
|
+
// Успешные read-only прогоны — в мемо (падения могут быть транзиентны).
|
|
618
|
+
if (memoizable && code === 0) {
|
|
619
|
+
memoWrite(ctx, memoKey, { success: true, output });
|
|
620
|
+
}
|
|
544
621
|
return { success: code === 0, output };
|
|
545
622
|
}
|
|
546
|
-
//
|
|
623
|
+
// Всё ещё жива — в фоновые процессы.
|
|
547
624
|
const explicit = args.background === true;
|
|
548
625
|
const preview = entry.log
|
|
549
626
|
.filter((l) => l.trim() !== "")
|