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/move-file.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/** Тул перемещения/переименования файла или каталога. */
|
|
1
2
|
import { renameSync, existsSync, mkdirSync } from "fs";
|
|
2
3
|
import { resolve, normalize, dirname } from "path";
|
|
3
4
|
import { t } from "../i18n/index";
|
|
@@ -6,6 +7,8 @@ import { logFileWrite, logSecurityBlock } from "../modules/security/audit-log";
|
|
|
6
7
|
import { getSessionSecurityConfig } from "../modules/security/session-isolation";
|
|
7
8
|
import { generateMoveDiff } from "../ui/diff";
|
|
8
9
|
import { safeResolvePath } from "./path-utils";
|
|
10
|
+
import { protectedPathBlock } from "../modules/security/protected-paths";
|
|
11
|
+
/** Перемещает/переименовывает; обе стороны проверяются по скоупу. */
|
|
9
12
|
export const moveFileTool = {
|
|
10
13
|
name: "move_file",
|
|
11
14
|
icon: "📦",
|
|
@@ -25,11 +28,10 @@ export const moveFileTool = {
|
|
|
25
28
|
const baseDir = resolve(ctx.baseDir);
|
|
26
29
|
const fromResolved = safeResolvePath(baseDir, normalize(fromPath));
|
|
27
30
|
const toResolved = safeResolvePath(baseDir, normalize(toPath));
|
|
28
|
-
// Get session-specific security config
|
|
29
31
|
const securityConfig = ctx.sessionContext
|
|
30
32
|
? getSessionSecurityConfig(ctx.config, ctx.sessionContext)
|
|
31
33
|
: ctx.config.security;
|
|
32
|
-
//
|
|
34
|
+
// Лимит файловых операций.
|
|
33
35
|
const maxFileOps = securityConfig?.maxFileOperations ?? 100;
|
|
34
36
|
const currentCount = ctx.fileOperationsCount ?? 0;
|
|
35
37
|
if (currentCount >= maxFileOps) {
|
|
@@ -39,8 +41,8 @@ export const moveFileTool = {
|
|
|
39
41
|
output: `[SECURITY BLOCKED] Maximum file operations (${maxFileOps}) exceeded`,
|
|
40
42
|
};
|
|
41
43
|
}
|
|
42
|
-
//
|
|
43
|
-
//
|
|
44
|
+
// Права источника — по РЕЗОЛВЛЕННОМУ пути (по нему же работает renameSync),
|
|
45
|
+
// никогда по сырому аргументу.
|
|
44
46
|
const fromCheck = isPathWritable(ctx.baseDir, fromResolved, ctx.scope, ctx.config.security?.paths);
|
|
45
47
|
if (!fromCheck.allowed) {
|
|
46
48
|
logSecurityBlock(ctx.sessionId, "file_write", fromCheck.reason || "Source path not allowed", fromPath);
|
|
@@ -51,7 +53,7 @@ export const moveFileTool = {
|
|
|
51
53
|
}),
|
|
52
54
|
};
|
|
53
55
|
}
|
|
54
|
-
//
|
|
56
|
+
// Права приёмника — тоже по резолвленному.
|
|
55
57
|
const toCheck = isPathWritable(ctx.baseDir, toResolved, ctx.scope, ctx.config.security?.paths);
|
|
56
58
|
if (!toCheck.allowed) {
|
|
57
59
|
logSecurityBlock(ctx.sessionId, "file_write", toCheck.reason || "Destination path not allowed", toPath);
|
|
@@ -62,6 +64,13 @@ export const moveFileTool = {
|
|
|
62
64
|
}),
|
|
63
65
|
};
|
|
64
66
|
}
|
|
67
|
+
// Защищённые пути с любой стороны (.git/, .env, ключи) — спросить юзера.
|
|
68
|
+
const fromProtected = protectedPathBlock(ctx, fromResolved, fromPath);
|
|
69
|
+
if (fromProtected)
|
|
70
|
+
return fromProtected;
|
|
71
|
+
const toProtected = protectedPathBlock(ctx, toResolved, toPath);
|
|
72
|
+
if (toProtected)
|
|
73
|
+
return toProtected;
|
|
65
74
|
if (!existsSync(fromResolved)) {
|
|
66
75
|
return {
|
|
67
76
|
success: false,
|
|
@@ -74,9 +83,7 @@ export const moveFileTool = {
|
|
|
74
83
|
}
|
|
75
84
|
renameSync(fromResolved, toResolved);
|
|
76
85
|
const diff = generateMoveDiff(fromPath, toPath);
|
|
77
|
-
// Increment file operations counter
|
|
78
86
|
ctx.fileOperationsCount = currentCount + 1;
|
|
79
|
-
// Log file move
|
|
80
87
|
logFileWrite(ctx.sessionId, `${fromPath} -> ${toPath}`, true, "File moved");
|
|
81
88
|
return {
|
|
82
89
|
success: true,
|
package/dist/tools/path-utils.js
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
|
+
/** Утилиты путей: нормализация, jail-проверки, резолв от baseDir. */
|
|
1
2
|
import { resolve, normalize, dirname, basename, sep, relative, isAbsolute } from "path";
|
|
2
3
|
import { existsSync } from "fs";
|
|
3
4
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
5
|
+
* Бэкслэши → прямые слэши для показа и сравнения.
|
|
6
|
+
* На POSIX no-op; на Windows `src\foo\bar.ts` → `src/foo/bar.ts`.
|
|
6
7
|
*/
|
|
7
8
|
export function toForwardSlash(p) {
|
|
8
9
|
return p.replace(/\\/g, "/");
|
|
9
10
|
}
|
|
10
11
|
/**
|
|
11
|
-
* True
|
|
12
|
+
* True, если targetPath равен dirPath или внутри него.
|
|
12
13
|
*
|
|
13
|
-
*
|
|
14
|
-
* -
|
|
15
|
-
*
|
|
16
|
-
* -
|
|
17
|
-
* before comparison so "E:\Proj" and "e:\proj" resolve to the same dir.
|
|
14
|
+
* Кроссплатформенно:
|
|
15
|
+
* - через path.relative(), а не raw startsWith: сосед с общим префиксом
|
|
16
|
+
* ("src" vs "src-other") не пройдёт;
|
|
17
|
+
* - на Windows ФС регистронезависима: обе стороны lowercased до сравнения.
|
|
18
18
|
*/
|
|
19
19
|
export function isInsideDir(targetPath, dirPath) {
|
|
20
20
|
const norm = (p) => (process.platform === "win32" ? p.toLowerCase() : p);
|
|
@@ -23,12 +23,13 @@ export function isInsideDir(targetPath, dirPath) {
|
|
|
23
23
|
if (t === d)
|
|
24
24
|
return true;
|
|
25
25
|
const rel = relative(d, t);
|
|
26
|
-
|
|
26
|
+
// Побег наверх — только реальный `..`/`../…`. Файл буквально `..foo`
|
|
27
|
+
// даёт rel === "..foo" и легально внутри.
|
|
28
|
+
return rel.length > 0 && !isAbsolute(rel) && rel !== ".." && !rel.startsWith(`..${sep}`);
|
|
27
29
|
}
|
|
28
30
|
/**
|
|
29
|
-
* True
|
|
30
|
-
*
|
|
31
|
-
* isInsideDir).
|
|
31
|
+
* True, если targetPath — сам scope-entry или внутри него. Проверка
|
|
32
|
+
* сепараторов и регистра — как в isInsideDir.
|
|
32
33
|
*/
|
|
33
34
|
export function matchesScopeEntry(targetResolved, entryResolved) {
|
|
34
35
|
if (targetResolved === entryResolved)
|
|
@@ -36,33 +37,32 @@ export function matchesScopeEntry(targetResolved, entryResolved) {
|
|
|
36
37
|
return isInsideDir(targetResolved, entryResolved);
|
|
37
38
|
}
|
|
38
39
|
/**
|
|
39
|
-
*
|
|
40
|
+
* Резолв пользовательского пути от baseDir.
|
|
40
41
|
*
|
|
41
|
-
*
|
|
42
|
-
* 1.
|
|
43
|
-
* 2.
|
|
44
|
-
* →
|
|
42
|
+
* Чинит типовые ошибки модели:
|
|
43
|
+
* 1. Ведущий слэш: "/testing/cat3" → относительно baseDir.
|
|
44
|
+
* 2. Пропущенный сепаратор: "E:\agent_test" при baseDir "E:\agent\_test"
|
|
45
|
+
* → вставка недостающего бэкслэша.
|
|
45
46
|
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
47
|
+
* Для новых файлов обязан существовать родитель — проверяется он, а не
|
|
48
|
+
* полный путь, иначе write_file не сработает.
|
|
48
49
|
*/
|
|
49
50
|
export function safeResolvePath(baseDir, userPath) {
|
|
50
|
-
//
|
|
51
|
+
// Существующий абсолютный путь — как есть.
|
|
51
52
|
const asIs = resolve(normalize(userPath));
|
|
52
53
|
if (userPath.startsWith("/") || userPath.startsWith("\\")) {
|
|
53
54
|
if (existsSync(asIs) || existsSync(dirname(asIs)))
|
|
54
55
|
return asIs;
|
|
55
56
|
}
|
|
56
57
|
const norm = normalize(userPath);
|
|
57
|
-
//
|
|
58
|
-
//
|
|
59
|
-
//
|
|
60
|
-
// only applies as a fallback when the absolute path does not resolve.
|
|
58
|
+
// Абсолютные пути — дословно, БЕЗ сдирания ведущего слэша и перерезолва
|
|
59
|
+
// от baseDir (иначе /var/folders/... станет baseDir/var/folders/... на POSIX).
|
|
60
|
+
// Эвристика ведущего слэша ниже — только fallback, когда путь не на диске.
|
|
61
61
|
if (isAbsolute(norm)) {
|
|
62
62
|
if (existsSync(norm) || existsSync(dirname(norm)))
|
|
63
63
|
return norm;
|
|
64
|
-
//
|
|
65
|
-
//
|
|
64
|
+
// Абсолютный, но ещё не на диске (новый файл без существующих dirs).
|
|
65
|
+
// Восстановление ошибки модели: ведущий слэш мог значить "от baseDir".
|
|
66
66
|
const stripped = norm.replace(/^[/\\]/, "");
|
|
67
67
|
const relativeCandidate = resolve(baseDir, stripped);
|
|
68
68
|
if (existsSync(relativeCandidate) || existsSync(dirname(relativeCandidate))) {
|
|
@@ -72,11 +72,11 @@ export function safeResolvePath(baseDir, userPath) {
|
|
|
72
72
|
}
|
|
73
73
|
const stripped = norm.replace(/^[/\\]/, "");
|
|
74
74
|
const resolved = resolve(baseDir, stripped);
|
|
75
|
-
//
|
|
75
|
+
// Быстрый путь: существует сам (или родитель для новых файлов).
|
|
76
76
|
if (existsSync(resolved) || existsSync(dirname(resolved)))
|
|
77
77
|
return resolved;
|
|
78
78
|
const baseNorm = normalize(baseDir);
|
|
79
|
-
//
|
|
79
|
+
// Подъём по предкам в поиске пропущенных сепараторов.
|
|
80
80
|
let cur = baseNorm;
|
|
81
81
|
while (cur && cur !== dirname(cur)) {
|
|
82
82
|
const name = basename(cur);
|
|
@@ -94,7 +94,7 @@ export function safeResolvePath(baseDir, userPath) {
|
|
|
94
94
|
if (existsSync(fixedResolved) || existsSync(dirname(fixedResolved))) {
|
|
95
95
|
return fixedResolved;
|
|
96
96
|
}
|
|
97
|
-
//
|
|
97
|
+
// Проба от родителя предка.
|
|
98
98
|
const fromParent = resolve(dirname(cur), normalize(fixed));
|
|
99
99
|
if (existsSync(fromParent) || existsSync(dirname(fromParent))) {
|
|
100
100
|
return fromParent;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/** Тул прогона именованного YAML-пайплайна (DAG субагентов). */
|
|
1
2
|
import { t } from "../i18n/index";
|
|
2
3
|
import { PipelineEngine } from "../modules/pipelines/engine";
|
|
3
4
|
import { PipelineParser } from "../modules/pipelines/parser";
|
|
@@ -8,9 +9,10 @@ import { PluginManager } from "../modules/plugins/manager";
|
|
|
8
9
|
import { HallucinationDetector } from "../modules/hallucination/detector";
|
|
9
10
|
import { logSecurityBlock } from "../modules/security/audit-log";
|
|
10
11
|
import { getSessionSecurityConfig } from "../modules/security/session-isolation";
|
|
11
|
-
|
|
12
|
+
/** Максимум параллельных шагов / попыток шага. */
|
|
12
13
|
const MAX_CONCURRENT = 3;
|
|
13
14
|
const MAX_ATTEMPTS = 3;
|
|
15
|
+
/** Прогоняет шаг пайплайна субагентом с ретраями (параметры/выходы — в промпт). */
|
|
14
16
|
async function runStep(ctx, step, params, outputs) {
|
|
15
17
|
const prompt = TemplateEngine.render(step.prompt, params, outputs);
|
|
16
18
|
let lastError = "";
|
|
@@ -51,6 +53,7 @@ async function runStep(ctx, step, params, outputs) {
|
|
|
51
53
|
}
|
|
52
54
|
return { ok: false, output: "", error: lastError };
|
|
53
55
|
}
|
|
56
|
+
/** Запускает DAG субагентов в порядке зависимостей. Параметры: name, yaml, params. */
|
|
54
57
|
export const pipelineRunTool = {
|
|
55
58
|
name: "pipeline_run",
|
|
56
59
|
icon: "▶️",
|
|
@@ -75,14 +78,18 @@ export const pipelineRunTool = {
|
|
|
75
78
|
if (!ctx.llmProvider || !ctx.toolExecutor) {
|
|
76
79
|
return {
|
|
77
80
|
success: false,
|
|
78
|
-
output: "
|
|
81
|
+
output: t("tool.pipeline_cannot_run"),
|
|
79
82
|
};
|
|
80
83
|
}
|
|
81
84
|
const securityConfig = ctx.sessionContext
|
|
82
85
|
? getSessionSecurityConfig(ctx.config, ctx.sessionContext)
|
|
83
86
|
: ctx.config.security;
|
|
84
87
|
const maxDepth = securityConfig?.maxRecursionDepth ?? 3;
|
|
85
|
-
|
|
88
|
+
// Глубина — на SHARED-контексте исполнителя (вложенные субагенты/пайплайны
|
|
89
|
+
// читают её там); `ctx.recursionDepth` снапшотится раз и не инкрементится,
|
|
90
|
+
// поэтому старый гард не срабатывал. Зеркало subagent.ts.
|
|
91
|
+
const executor = ctx.toolExecutor;
|
|
92
|
+
const currentDepth = executor?.getRecursionDepth?.() ?? ctx.recursionDepth ?? 0;
|
|
86
93
|
if (currentDepth >= maxDepth) {
|
|
87
94
|
logSecurityBlock(ctx.sessionId, "bash_command", `Maximum recursion depth (${maxDepth}) exceeded`, name);
|
|
88
95
|
return {
|
|
@@ -90,9 +97,12 @@ export const pipelineRunTool = {
|
|
|
90
97
|
output: `[SECURITY BLOCKED] Maximum pipeline recursion depth (${maxDepth}) exceeded`,
|
|
91
98
|
};
|
|
92
99
|
}
|
|
100
|
+
// Свежий движок на вызов: module-level синглтон сбрасывался конкурентными
|
|
101
|
+
// вызовами (MoE-волны делят исполнитель) и портил состояние шагов.
|
|
102
|
+
const engine = new PipelineEngine();
|
|
103
|
+
executor?.setRecursionDepth?.(currentDepth + 1);
|
|
93
104
|
try {
|
|
94
105
|
const pipeline = PipelineParser.parse(yaml);
|
|
95
|
-
engine.reset();
|
|
96
106
|
const order = engine.resolveDependencies(pipeline.steps);
|
|
97
107
|
const completed = new Set();
|
|
98
108
|
const outputs = {};
|
|
@@ -130,16 +140,28 @@ export const pipelineRunTool = {
|
|
|
130
140
|
if (failed.size > 0) {
|
|
131
141
|
return {
|
|
132
142
|
success: false,
|
|
133
|
-
output:
|
|
143
|
+
output: t("tool.pipeline_failed", {
|
|
144
|
+
name: pipeline.name,
|
|
145
|
+
done: String(completed.size),
|
|
146
|
+
total: String(pipeline.steps.length),
|
|
147
|
+
logs: logs.join("\n"),
|
|
148
|
+
}),
|
|
134
149
|
};
|
|
135
150
|
}
|
|
136
151
|
return {
|
|
137
152
|
success: true,
|
|
138
|
-
output:
|
|
153
|
+
output: t("tool.pipeline_completed", {
|
|
154
|
+
name: pipeline.name,
|
|
155
|
+
steps: String(completed.size),
|
|
156
|
+
logs: logs.join("\n"),
|
|
157
|
+
}),
|
|
139
158
|
};
|
|
140
159
|
}
|
|
141
160
|
catch (e) {
|
|
142
|
-
return { success: false, output:
|
|
161
|
+
return { success: false, output: t("tool.pipeline_error", { error: e.message }) };
|
|
162
|
+
}
|
|
163
|
+
finally {
|
|
164
|
+
executor?.setRecursionDepth?.(currentDepth);
|
|
143
165
|
}
|
|
144
166
|
},
|
|
145
167
|
};
|
package/dist/tools/preview.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/** Максимум строк в превью вывода тулов. */
|
|
2
2
|
export const MAX_PREVIEW_LINES = 15;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/** Общие хелперы process_*-тулов: хинт без id, валидация + lookup. */
|
|
2
|
+
import { processRegistry } from "../modules/processes";
|
|
3
|
+
import { t } from "../i18n/index";
|
|
4
|
+
/**
|
|
5
|
+
* Общий ответ "id не дан" для process_*-тулов: хинт использования плюс
|
|
6
|
+
* известные сейчас id (или пометка пустого реестра). Одна копия, чтобы
|
|
7
|
+
* process-kill/log/wait не расходились.
|
|
8
|
+
*/
|
|
9
|
+
export function missingProcessIdHint() {
|
|
10
|
+
const ids = processRegistry.list().map((p) => p.id);
|
|
11
|
+
const listPart = ids.length
|
|
12
|
+
? `\n${t("proc.list_header")}: ${ids.join(", ")}`
|
|
13
|
+
: `\n${t("proc.missing_id_list")}`;
|
|
14
|
+
return `${t("proc.missing_id")}${listPart}`;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Общая валидация id + lookup в реестре для process_*-тулов. Возвращает запись
|
|
18
|
+
* либо `{ ok: false, output }` для прямого возврата (нет id — хинт,
|
|
19
|
+
* неизвестный id — not-found).
|
|
20
|
+
*/
|
|
21
|
+
export function lookupProcess(args) {
|
|
22
|
+
if (args.id === undefined || String(args.id).trim() === "") {
|
|
23
|
+
return { ok: false, output: missingProcessIdHint() };
|
|
24
|
+
}
|
|
25
|
+
const id = String(args.id);
|
|
26
|
+
const entry = processRegistry.get(id);
|
|
27
|
+
if (!entry) {
|
|
28
|
+
return { ok: false, output: t("proc.not_found", { id }) };
|
|
29
|
+
}
|
|
30
|
+
return { ok: true, id, entry };
|
|
31
|
+
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { processRegistry } from "../modules/processes";
|
|
2
2
|
import { t } from "../i18n/index";
|
|
3
|
+
import { lookupProcess } from "./process-ids";
|
|
4
|
+
/** Останавливает фоновый процесс по id из bash/process_list. */
|
|
3
5
|
export const processKillTool = {
|
|
4
6
|
name: "process_kill",
|
|
5
7
|
icon: "⛔",
|
|
@@ -12,22 +14,13 @@ export const processKillTool = {
|
|
|
12
14
|
},
|
|
13
15
|
required: ["id"],
|
|
14
16
|
},
|
|
15
|
-
handler: async (
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
? `\n${t("proc.list_header")}: ${ids.join(", ")}`
|
|
23
|
-
: `\n${t("proc.missing_id_list")}`;
|
|
24
|
-
return { success: false, output: `${t("proc.missing_id")}${listPart}` };
|
|
25
|
-
}
|
|
26
|
-
const id = String(args.id);
|
|
27
|
-
const entry = processRegistry.get(id);
|
|
28
|
-
if (!entry) {
|
|
29
|
-
return { success: false, output: t("proc.not_found", { id }) };
|
|
30
|
-
}
|
|
17
|
+
handler: async (_ctx, args) => {
|
|
18
|
+
// Без id отвечаем использованием: голое "not found: undefined" не
|
|
19
|
+
// подсказывает верный формат.
|
|
20
|
+
const found = lookupProcess(args);
|
|
21
|
+
if (!found.ok)
|
|
22
|
+
return { success: false, output: found.output };
|
|
23
|
+
const { id, entry } = found;
|
|
31
24
|
const killed = processRegistry.kill(id);
|
|
32
25
|
if (!killed) {
|
|
33
26
|
return { success: false, output: t("proc.kill_failed", { id }) };
|
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import { processRegistry } from "../modules/processes";
|
|
2
2
|
import { t } from "../i18n/index";
|
|
3
3
|
import { MAX_PREVIEW_LINES } from "./preview";
|
|
4
|
+
import { lookupProcess } from "./process-ids";
|
|
4
5
|
const DEFAULT_TAIL = MAX_PREVIEW_LINES;
|
|
6
|
+
/** Потолок из описания тула; тул вне усечения по бюджету. */
|
|
7
|
+
const MAX_TAIL = 300;
|
|
8
|
+
/** Показывает буферизированный вывод фонового процесса (последние N строк). */
|
|
5
9
|
export const processLogTool = {
|
|
6
10
|
name: "process_log",
|
|
7
11
|
icon: "📜",
|
|
8
|
-
description: `Show the buffered output of a background process started via bash. Shows the last ${DEFAULT_TAIL} lines by default. Use after starting a dev server to verify it came up without errors, and while it runs to check its state.`,
|
|
12
|
+
description: `Show the buffered output of a background process started via bash. Shows the last ${DEFAULT_TAIL} lines by default. Use after starting a dev server to verify it came up without errors, and while it runs to check its state. For a finite task that should complete, prefer process_wait over polling this tool.`,
|
|
9
13
|
tags: ["shell"],
|
|
10
14
|
boundedOutput: true,
|
|
11
15
|
parameters: {
|
|
@@ -19,26 +23,17 @@ export const processLogTool = {
|
|
|
19
23
|
},
|
|
20
24
|
required: ["id"],
|
|
21
25
|
},
|
|
22
|
-
handler: async (
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
? `\n${t("proc.list_header")}: ${ids.join(", ")}`
|
|
28
|
-
: `\n${t("proc.missing_id_list")}`;
|
|
29
|
-
return { success: false, output: `${t("proc.missing_id")}${listPart}` };
|
|
30
|
-
}
|
|
31
|
-
const id = String(args.id);
|
|
32
|
-
const entry = processRegistry.get(id);
|
|
33
|
-
if (!entry) {
|
|
34
|
-
return { success: false, output: t("proc.not_found", { id }) };
|
|
35
|
-
}
|
|
26
|
+
handler: async (_ctx, args) => {
|
|
27
|
+
const found = lookupProcess(args);
|
|
28
|
+
if (!found.ok)
|
|
29
|
+
return { success: false, output: found.output };
|
|
30
|
+
const { id, entry } = found;
|
|
36
31
|
const status = entry.status === "running"
|
|
37
32
|
? t("proc.status_running")
|
|
38
33
|
: entry.status === "exited"
|
|
39
34
|
? t("proc.status_exited")
|
|
40
35
|
: t("proc.status_killed");
|
|
41
|
-
const tail = typeof args.tail === "number" ? args.tail : DEFAULT_TAIL;
|
|
36
|
+
const tail = Math.min(MAX_TAIL, Math.max(1, typeof args.tail === "number" ? Math.floor(args.tail) : DEFAULT_TAIL));
|
|
42
37
|
const log = processRegistry.getLog(id, tail);
|
|
43
38
|
if (!log) {
|
|
44
39
|
return {
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { processRegistry } from "../modules/processes";
|
|
2
|
+
import { t } from "../i18n/index";
|
|
3
|
+
import { lookupProcess } from "./process-ids";
|
|
4
|
+
import { PROCESS_WAIT_DEFAULT_MS as DEFAULT_WAIT_MS, PROCESS_WAIT_MAX_MS as MAX_WAIT_MS, } from "../config/timeouts";
|
|
5
|
+
/**
|
|
6
|
+
* Дефолтное ожидание: хватает на типичный тест/билд, но в пределах таймаута
|
|
7
|
+
* исполнителя. `timeoutMs` тула даёт запас под явное долгое ожидание
|
|
8
|
+
* (кламп MAX_WAIT_MS).
|
|
9
|
+
*/
|
|
10
|
+
const DEFAULT_TAIL = 20;
|
|
11
|
+
const MAX_TAIL = 100;
|
|
12
|
+
/** Ждёт выхода процесса до timeoutMs; отчитывается кодом и хвостом вывода. */
|
|
13
|
+
export const processWaitTool = {
|
|
14
|
+
name: "process_wait",
|
|
15
|
+
icon: "⏳",
|
|
16
|
+
description: "Wait for a background process started via bash to finish, instead of polling process_log or sleeping (ping/timeout/sleep commands). Blocks up to timeoutMs, then reports whether the process exited (with its exit code and recent output) or is still running. For dev servers/watchers that never exit, use process_log to inspect and process_kill to stop them.",
|
|
17
|
+
tags: ["shell"],
|
|
18
|
+
boundedOutput: true,
|
|
19
|
+
timeoutMs: 120000,
|
|
20
|
+
parameters: {
|
|
21
|
+
type: "object",
|
|
22
|
+
properties: {
|
|
23
|
+
id: { type: "string", description: "Process id from bash output or process_list" },
|
|
24
|
+
timeoutMs: {
|
|
25
|
+
type: "number",
|
|
26
|
+
description: `How long to wait in milliseconds (default: ${DEFAULT_WAIT_MS}, max ${MAX_WAIT_MS})`,
|
|
27
|
+
},
|
|
28
|
+
tail: {
|
|
29
|
+
type: "number",
|
|
30
|
+
description: `Number of trailing output lines to include (default: ${DEFAULT_TAIL}, max ${MAX_TAIL})`,
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
required: ["id"],
|
|
34
|
+
},
|
|
35
|
+
handler: async (_ctx, args) => {
|
|
36
|
+
const found = lookupProcess(args);
|
|
37
|
+
if (!found.ok)
|
|
38
|
+
return { success: false, output: found.output };
|
|
39
|
+
const { id } = found;
|
|
40
|
+
const waitMs = Math.min(MAX_WAIT_MS, Math.max(1000, typeof args.timeoutMs === "number" ? Math.floor(args.timeoutMs) : DEFAULT_WAIT_MS));
|
|
41
|
+
const started = Date.now();
|
|
42
|
+
await processRegistry.waitForExit(id, waitMs);
|
|
43
|
+
const elapsed = Date.now() - started;
|
|
44
|
+
const entry = processRegistry.get(id);
|
|
45
|
+
if (!entry) {
|
|
46
|
+
return { success: false, output: t("proc.not_found", { id }) };
|
|
47
|
+
}
|
|
48
|
+
const tail = Math.min(MAX_TAIL, Math.max(1, typeof args.tail === "number" ? Math.floor(args.tail) : DEFAULT_TAIL));
|
|
49
|
+
const log = processRegistry.getLog(id, tail);
|
|
50
|
+
const logPart = log ? `\n${log}` : `\n${t("proc.log_empty")}`;
|
|
51
|
+
if (entry.status !== "running") {
|
|
52
|
+
const status = entry.status === "exited" ? t("proc.status_exited") : t("proc.status_killed");
|
|
53
|
+
return {
|
|
54
|
+
success: true,
|
|
55
|
+
output: `${t("proc.wait_exited", { id, ms: elapsed, status, code: entry.exitCode ?? "?" })}${logPart}`,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
return {
|
|
59
|
+
success: true,
|
|
60
|
+
output: `${t("proc.wait_running", { id, ms: elapsed })}\n${t("proc.wait_hint", { id })}${logPart}`,
|
|
61
|
+
};
|
|
62
|
+
},
|
|
63
|
+
};
|
package/dist/tools/prompt-io.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
/** Терминал-независимая поверхность промпта для интерактивных тулов. */
|
|
1
2
|
import { formatMenu, choicePrompt, parseSelection, CUSTOM_INDEX, } from "./user-input";
|
|
2
3
|
import { t } from "../i18n/index";
|
|
3
|
-
/**
|
|
4
|
+
/** Собирает PromptIO из инжектируемых ask/print (тестируемо, без терминала). */
|
|
4
5
|
export function createPromptIO(deps) {
|
|
5
6
|
return {
|
|
6
7
|
async askText(question) {
|
|
@@ -9,8 +10,8 @@ export function createPromptIO(deps) {
|
|
|
9
10
|
async askChoice(question, options, opts = {}) {
|
|
10
11
|
const multiple = opts.multiple ?? false;
|
|
11
12
|
const entryCount = options.length + (opts.allowCustom ? 1 : 0);
|
|
12
|
-
//
|
|
13
|
-
//
|
|
13
|
+
// Выбирать нечего (и кастома нет) — parseSelection не сойдётся никогда,
|
|
14
|
+
// поэтому выходим без печати мёртвого меню и вечного цикла.
|
|
14
15
|
if (entryCount === 0)
|
|
15
16
|
return [];
|
|
16
17
|
const customEntry = options.length;
|