micro-models-agent 2.11.0 → 2.13.1
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 +39 -0
- package/dist/assets/app.js +22 -2
- package/dist/assets/index.html +39 -9
- package/dist/assets/js/dom.js +15 -0
- package/dist/assets/js/feed.js +129 -10
- package/dist/assets/js/sidebar.js +512 -0
- package/dist/assets/js/tabs.js +30 -5
- package/dist/assets/setup.js +267 -8
- package/dist/assets/styles.css +168 -3
- 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 +89 -3
- package/dist/i18n/index.js +37 -3
- package/dist/i18n/ru.json +89 -3
- 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 +888 -139
- 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/question.js
CHANGED
|
@@ -1,23 +1,38 @@
|
|
|
1
|
+
/** Тул вопросов юзеру: меню, memory pre-check, батч-cap, сессионные ответы. */
|
|
1
2
|
import { t } from "../i18n/index";
|
|
2
3
|
import { askUser } from "./user-input";
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
4
|
+
import { MemoryStore } from "../modules/memory/store";
|
|
5
|
+
import { globalMemoryDir } from "../config/paths";
|
|
6
|
+
const DESCRIPTION = `Use this tool when you need to ask the user questions during execution. This allows you to:
|
|
7
|
+
1. Gather user preferences or requirements
|
|
8
|
+
2. Clarify ambiguous instructions
|
|
9
|
+
3. Get decisions on implementation choices as you work
|
|
10
|
+
4. Offer choices to the user about what direction to take.
|
|
11
|
+
|
|
12
|
+
Usage notes:
|
|
13
|
+
- When "custom" is enabled (default), a "Type your own answer" option is added automatically; don't include "Other" or catch-all options
|
|
14
|
+
- Answers are returned as arrays of labels; set "multiple": true to allow selecting more than one
|
|
15
|
+
- If you recommend a specific option, make that the first option in the list and add "(Recommended)" at the end of the label
|
|
16
|
+
- Execution blocks until the user answers every question
|
|
17
|
+
- Omit "options" for a free-text question
|
|
18
|
+
- Never ask choice questions as a plain-text list in your reply — always call
|
|
19
|
+
this tool (it renders interactive buttons and blocks until answered).
|
|
20
|
+
When the user asks for a Q&A session with options, use this tool for every
|
|
21
|
+
question, not chat text
|
|
22
|
+
- Before asking, the tool checks agent memory itself: questions memory
|
|
23
|
+
already answers are skipped (reported back, not asked) — never ask what
|
|
24
|
+
is already known
|
|
25
|
+
- Answers live only in the current session; nothing is written to memory.
|
|
26
|
+
Call "remember" separately to persist something, and only when asked to
|
|
27
|
+
- At most 3 unknown questions are asked per call; keep batches small`;
|
|
28
|
+
/** Проверяет форму опции (label + description — строки). */
|
|
15
29
|
function isOption(val) {
|
|
16
30
|
if (!val || typeof val !== "object")
|
|
17
31
|
return false;
|
|
18
32
|
const opt = val;
|
|
19
33
|
return typeof opt.label === "string" && typeof opt.description === "string";
|
|
20
34
|
}
|
|
35
|
+
/** Нормализует args в спеки (массив + legacy `{ question }`). Битые — пропуск. */
|
|
21
36
|
function normalizeQuestions(args) {
|
|
22
37
|
const raw = args.questions;
|
|
23
38
|
if (Array.isArray(raw)) {
|
|
@@ -29,8 +44,8 @@ function normalizeQuestions(args) {
|
|
|
29
44
|
if (typeof q.question !== "string" || !q.question.trim())
|
|
30
45
|
continue;
|
|
31
46
|
const options = Array.isArray(q.options) ? q.options.filter(isOption) : undefined;
|
|
32
|
-
//
|
|
33
|
-
//
|
|
47
|
+
// Явно данный массив опций без валидных записей — malformed вызов:
|
|
48
|
+
// вопрос пропустить, а не висеть на stdin.
|
|
34
49
|
if (Array.isArray(q.options) && options.length === 0)
|
|
35
50
|
continue;
|
|
36
51
|
specs.push({
|
|
@@ -43,17 +58,120 @@ function normalizeQuestions(args) {
|
|
|
43
58
|
}
|
|
44
59
|
return specs;
|
|
45
60
|
}
|
|
46
|
-
// Legacy
|
|
61
|
+
// Legacy-формат: { question: "..." } — plain free-text вопрос.
|
|
47
62
|
if (typeof args.question === "string" && args.question.trim()) {
|
|
48
63
|
return [{ question: args.question }];
|
|
49
64
|
}
|
|
50
65
|
return [];
|
|
51
66
|
}
|
|
67
|
+
/** Максимум незнакомых вопросов за вызов — большие батчи малые модели теряют. */
|
|
68
|
+
export const MAX_QUESTIONS_PER_CALL = 3;
|
|
69
|
+
/** Токенный bound memory pre-check: хиты репортятся повопросно. */
|
|
70
|
+
const MAX_MEMORY_HITS = 2;
|
|
71
|
+
const MAX_HIT_CHARS = 200;
|
|
72
|
+
/**
|
|
73
|
+
* Делит спеки на известные памяти (репорт, не спрашивать) и незнакомые.
|
|
74
|
+
* Чистая через инжектируемый lookup — тесты не трогают реальный memory dir.
|
|
75
|
+
*/
|
|
76
|
+
export function splitByMemory(specs, lookup) {
|
|
77
|
+
const known = [];
|
|
78
|
+
const unknown = [];
|
|
79
|
+
for (const spec of specs) {
|
|
80
|
+
const hits = lookup(spec);
|
|
81
|
+
if (hits.length > 0)
|
|
82
|
+
known.push({ spec, hits });
|
|
83
|
+
else
|
|
84
|
+
unknown.push(spec);
|
|
85
|
+
}
|
|
86
|
+
return { known, unknown };
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Значимые слова текста: lowercase, unicode-разбивка, длина ≥ 4. Короче
|
|
90
|
+
* ("тип", "для", "что") — шум для overlap-мэтчинга.
|
|
91
|
+
*/
|
|
92
|
+
export function extractKeywords(...texts) {
|
|
93
|
+
const out = new Set();
|
|
94
|
+
for (const text of texts) {
|
|
95
|
+
if (!text)
|
|
96
|
+
continue;
|
|
97
|
+
for (const word of text.toLowerCase().split(/[^\p{L}\p{N}]+/u)) {
|
|
98
|
+
if (word.length >= 4)
|
|
99
|
+
out.add(word);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return [...out];
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Строка памяти считается ответом, только если делит со вопросом не меньше
|
|
106
|
+
* стольких distinct keywords. Одно общее слово ("какой", "город") — обычно
|
|
107
|
+
* совпадение: пропуск на нём ответил бы из stale-данных вместо вопроса.
|
|
108
|
+
* Precision важнее recall: промах просто спросит.
|
|
109
|
+
*/
|
|
110
|
+
const MIN_KEYWORD_OVERLAP = 2;
|
|
111
|
+
/**
|
|
112
|
+
* Keyword-overlap поиск по памяти через инжектируемый покейвордный поиск.
|
|
113
|
+
* Чистая — прод-обвязка (`lookupMemory`) передаёт `MemoryStore.search`.
|
|
114
|
+
*/
|
|
115
|
+
export function collectMemoryHits(searchOne, spec) {
|
|
116
|
+
const keywords = extractKeywords(spec.question, spec.header, ...(spec.options ?? []).flatMap((o) => [o.label, o.description]));
|
|
117
|
+
if (keywords.length === 0)
|
|
118
|
+
return [];
|
|
119
|
+
const lines = new Map();
|
|
120
|
+
for (const kw of keywords) {
|
|
121
|
+
let hits;
|
|
122
|
+
try {
|
|
123
|
+
hits = searchOne(kw);
|
|
124
|
+
}
|
|
125
|
+
catch {
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
for (const r of hits) {
|
|
129
|
+
const key = `${r.file}\n${r.match}`;
|
|
130
|
+
let entry = lines.get(key);
|
|
131
|
+
if (!entry) {
|
|
132
|
+
entry = { text: `[${r.file}] ${r.match}`, matched: new Set() };
|
|
133
|
+
lines.set(key, entry);
|
|
134
|
+
}
|
|
135
|
+
entry.matched.add(kw);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return [...lines.values()]
|
|
139
|
+
.filter((e) => e.matched.size >= MIN_KEYWORD_OVERLAP)
|
|
140
|
+
.slice(0, MAX_MEMORY_HITS)
|
|
141
|
+
.map((e) => e.text.slice(0, MAX_HIT_CHARS));
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Fail-open поиск по памяти: нечитаемая/отсутствующая память вопрос не блокирует.
|
|
145
|
+
* Возврат — короткие `file: match`-сниппеты, токенно ограниченные.
|
|
146
|
+
*/
|
|
147
|
+
function lookupMemory(spec) {
|
|
148
|
+
try {
|
|
149
|
+
const store = new MemoryStore(globalMemoryDir());
|
|
150
|
+
return collectMemoryHits((kw) => store.search(kw), spec);
|
|
151
|
+
}
|
|
152
|
+
catch {
|
|
153
|
+
return [];
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Тестовый шов: `memorySearch` на контексте стабит memory pre-check (тот же
|
|
158
|
+
* override-паттерн, что `ToolContext.agentFactory` — моки Bun-модулей глобальны
|
|
159
|
+
* на процесс, plain vi.mock тут не годится).
|
|
160
|
+
*/
|
|
161
|
+
function resolveSearch(ctx) {
|
|
162
|
+
const stub = ctx
|
|
163
|
+
.memorySearch;
|
|
164
|
+
return typeof stub === "function" ? stub : lookupMemory;
|
|
165
|
+
}
|
|
166
|
+
/** Тул вопросов: выбор/мультивыбор/free-text, memory pre-check, батч-cap 3. */
|
|
52
167
|
export const questionTool = {
|
|
53
168
|
name: "question",
|
|
54
169
|
icon: "❓",
|
|
55
170
|
description: DESCRIPTION,
|
|
56
171
|
tags: ["core"],
|
|
172
|
+
// Интерактивные тулы переживают сужение тегов: спросить модель должна уметь
|
|
173
|
+
// всегда, при любом типе задачи (P5 auto-narrowing).
|
|
174
|
+
alwaysOn: true,
|
|
57
175
|
interactive: true,
|
|
58
176
|
parameters: {
|
|
59
177
|
type: "object",
|
|
@@ -113,10 +231,18 @@ export const questionTool = {
|
|
|
113
231
|
if (questions.length === 0) {
|
|
114
232
|
return { success: false, output: t("tool.question.no_questions") };
|
|
115
233
|
}
|
|
234
|
+
// Memory pre-check: известное памяти не спрашивать. Известные вопросы —
|
|
235
|
+
// репорт, не опрос. В память ничего не пишется: ответы живут только в сессии
|
|
236
|
+
// (persist — отдельным "remember" по просьбе).
|
|
237
|
+
const { known, unknown } = splitByMemory(questions, resolveSearch(ctx));
|
|
238
|
+
// Батч-cap: не больше MAX_QUESTIONS_PER_CALL за раз, остаток — пометкой,
|
|
239
|
+
// чтобы модель довызвала, а не потеряла вопросы молча.
|
|
240
|
+
const truncated = unknown.length - MAX_QUESTIONS_PER_CALL;
|
|
241
|
+
const toAsk = truncated > 0 ? unknown.slice(0, MAX_QUESTIONS_PER_CALL) : unknown;
|
|
116
242
|
const answers = [];
|
|
117
|
-
for (let i = 0; i <
|
|
118
|
-
const q =
|
|
119
|
-
const progress =
|
|
243
|
+
for (let i = 0; i < toAsk.length; i++) {
|
|
244
|
+
const q = toAsk[i];
|
|
245
|
+
const progress = toAsk.length > 1
|
|
120
246
|
? t("tool.question.progress", {
|
|
121
247
|
current: i + 1,
|
|
122
248
|
total: questions.length,
|
|
@@ -130,12 +256,33 @@ export const questionTool = {
|
|
|
130
256
|
progress,
|
|
131
257
|
}, ctx.promptIO));
|
|
132
258
|
}
|
|
133
|
-
const
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
}
|
|
259
|
+
const parts = [];
|
|
260
|
+
if (toAsk.length > 0) {
|
|
261
|
+
const formatted = toAsk
|
|
262
|
+
.map((q, i) => `"${q.question}"="${answers[i].length ? answers[i].join(", ") : t("tool.question.unanswered")}"`)
|
|
263
|
+
.join(", ");
|
|
264
|
+
parts.push(t("tool.question.answered", { formatted }));
|
|
265
|
+
}
|
|
266
|
+
else {
|
|
267
|
+
parts.push(t("tool.question.memory_only"));
|
|
268
|
+
}
|
|
269
|
+
if (known.length > 0) {
|
|
270
|
+
const formatted = known
|
|
271
|
+
.map((k) => `"${k.spec.question}"="${k.hits.join("; ")}"`)
|
|
272
|
+
.join(", ");
|
|
273
|
+
parts.push(t("tool.question.memory_known", { formatted }));
|
|
274
|
+
}
|
|
275
|
+
parts.push(t("tool.question.session_only"));
|
|
276
|
+
if (truncated > 0) {
|
|
277
|
+
parts.push(t("tool.question.batch_truncated", {
|
|
278
|
+
shown: MAX_QUESTIONS_PER_CALL,
|
|
279
|
+
total: unknown.length,
|
|
280
|
+
}));
|
|
281
|
+
}
|
|
282
|
+
// display:"" гасит UI-эхо (tool-batch предпочитает display и пропускает
|
|
283
|
+
// пустые): модели — полный output в role:"tool", чату — только блок вопроса
|
|
284
|
+
// с ↳-ответами. Текст output — model-facing директивы (сессия/память/батч),
|
|
285
|
+
// не контент юзера: и verbose идёт через tool_end, не meta.
|
|
286
|
+
return { success: true, output: parts.join(" "), display: "" };
|
|
140
287
|
},
|
|
141
288
|
};
|
package/dist/tools/read-file.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/** Тул чтения файла: постранично, со стримингом больших файлов. */
|
|
1
2
|
import { readFileSync, existsSync, statSync, openSync, readSync, closeSync } from "fs";
|
|
2
3
|
import { extname } from "path";
|
|
3
4
|
import { t } from "../i18n/index";
|
|
@@ -5,27 +6,27 @@ import { isPathInScope } from "../modules/security/path-validator";
|
|
|
5
6
|
import { DEFAULT_SECURITY_CONFIG } from "../config/security";
|
|
6
7
|
import { logSecurityBlock } from "../modules/security/audit-log";
|
|
7
8
|
import { safeResolvePath } from "./path-utils";
|
|
9
|
+
import { errMsg } from "../utils";
|
|
8
10
|
/**
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* covers typical component files in one call while still capping output.
|
|
11
|
+
* Строк на вызов read_file по умолчанию. Намеренно больше MAX_PREVIEW_LINES
|
|
12
|
+
* (15): дефолт 15 заставлял модель делать 10–17 вызовов на файл, что заливало
|
|
13
|
+
* контекст и дёргало компакцию каждые 15 итераций (наблюдено: 96 вызовов
|
|
14
|
+
* read_file, 11 компакций за ~20 мин со сносом задачи). 300 строк покрывают
|
|
15
|
+
* типичный файл за один вызов, вывод всё равно ограничен.
|
|
15
16
|
*/
|
|
16
17
|
const DEFAULT_LIMIT = 300;
|
|
18
|
+
/** Жёсткий потолок явного `limit` (тул исключён из усечения по бюджету). */
|
|
19
|
+
const MAX_READ_LIMIT = 2000;
|
|
17
20
|
/**
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
* hundred lines).
|
|
21
|
+
* Файлы крупнее — через ограниченный стриминг вместо readFileSync (который
|
|
22
|
+
* грузит весь файл в память ради пары сотен строк).
|
|
21
23
|
*/
|
|
22
24
|
const LARGE_FILE_BYTES = 1024 * 1024;
|
|
23
25
|
const STREAM_CHUNK = 256 * 1024;
|
|
24
26
|
/**
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
* bounded regardless of file size.
|
|
27
|
+
* Ограниченное построчное чтение больших файлов: стримит чанками, собирает
|
|
28
|
+
* только окно [offset, offset+limit), остаток переводов строк считает сырым
|
|
29
|
+
* (без декода) для total. Память ограничена при любом размере файла.
|
|
29
30
|
*/
|
|
30
31
|
function readLineSlice(path, offset, limit) {
|
|
31
32
|
const fd = openSync(path, "r");
|
|
@@ -52,7 +53,7 @@ function readLineSlice(path, offset, limit) {
|
|
|
52
53
|
lineNumber++;
|
|
53
54
|
}
|
|
54
55
|
if (lineNumber >= endLine && collected.length >= limit) {
|
|
55
|
-
//
|
|
56
|
+
// Окно собрано — остаток переводов строк считаем без декодирования.
|
|
56
57
|
const rest = [];
|
|
57
58
|
while (true) {
|
|
58
59
|
const n = readSync(fd, buf, 0, STREAM_CHUNK, pos);
|
|
@@ -65,7 +66,10 @@ function readLineSlice(path, offset, limit) {
|
|
|
65
66
|
for (let i = 0; i < tail.length; i++)
|
|
66
67
|
if (tail[i] === 0x0a)
|
|
67
68
|
total++;
|
|
68
|
-
|
|
69
|
+
// Плюс финальная строка без перевода — только если файл им не
|
|
70
|
+
// кончается, иначе посчитаем фантомную пустую строку.
|
|
71
|
+
if (tail.length > 0 && tail[tail.length - 1] !== 0x0a)
|
|
72
|
+
total++;
|
|
69
73
|
return { selected: collected.join("\n"), total };
|
|
70
74
|
}
|
|
71
75
|
}
|
|
@@ -80,12 +84,14 @@ function readLineSlice(path, offset, limit) {
|
|
|
80
84
|
closeSync(fd);
|
|
81
85
|
}
|
|
82
86
|
}
|
|
87
|
+
/** Читает файл постранично (offset/limit); кешируемый, с cap вывода. */
|
|
83
88
|
export const readFileTool = {
|
|
84
89
|
name: "read_file",
|
|
85
90
|
icon: "👀",
|
|
86
91
|
description: `Read a file from the filesystem. Reads up to ${DEFAULT_LIMIT} lines at a time by default; use offset to page through large files.`,
|
|
87
92
|
tags: ["file", "code"],
|
|
88
93
|
boundedOutput: true,
|
|
94
|
+
cacheable: true,
|
|
89
95
|
parameters: {
|
|
90
96
|
type: "object",
|
|
91
97
|
properties: {
|
|
@@ -107,8 +113,7 @@ export const readFileTool = {
|
|
|
107
113
|
const securityPaths = ctx.config?.security?.paths || DEFAULT_SECURITY_CONFIG.paths;
|
|
108
114
|
const scopeCheck = isPathInScope(ctx.baseDir, resolved, ctx.scope, securityPaths);
|
|
109
115
|
if (!scopeCheck.allowed) {
|
|
110
|
-
|
|
111
|
-
logSecurityBlock(ctx.sessionId || undefined, "file_read", scopeCheck.reason || "Path not allowed", pathStr);
|
|
116
|
+
logSecurityBlock(ctx.sessionId || undefined, "file_read", scopeCheck.reason || "Path not allowed", path);
|
|
112
117
|
return {
|
|
113
118
|
success: false,
|
|
114
119
|
output: t("file.path_not_allowed", {
|
|
@@ -117,8 +122,8 @@ export const readFileTool = {
|
|
|
117
122
|
};
|
|
118
123
|
}
|
|
119
124
|
if (!existsSync(resolved)) {
|
|
120
|
-
//
|
|
121
|
-
// "not found"
|
|
125
|
+
// Для относительных путей показываем, КУДА резолвилось: голое
|
|
126
|
+
// "not found" не даёт модели отличить baseDir от cwd.
|
|
122
127
|
const output = resolved !== path
|
|
123
128
|
? t("file.notfound_resolved", {
|
|
124
129
|
path,
|
|
@@ -127,22 +132,39 @@ export const readFileTool = {
|
|
|
127
132
|
: t("file.notfound", { path });
|
|
128
133
|
return { success: false, output };
|
|
129
134
|
}
|
|
130
|
-
|
|
131
|
-
|
|
135
|
+
// Кламп обоих: `offset: 0` давал slice(-1, end) с ПОСЛЕДНЕЙ строкой, а
|
|
136
|
+
// безграничный `limit` (тул вне усечения по бюджету) тянул бы целый
|
|
137
|
+
// большой файл в контекст.
|
|
138
|
+
const offset = Math.max(1, typeof args.offset === "number" ? Math.floor(args.offset) : 1);
|
|
139
|
+
const limit = Math.min(MAX_READ_LIMIT, Math.max(1, typeof args.limit === "number" ? Math.floor(args.limit) : DEFAULT_LIMIT));
|
|
132
140
|
let lines;
|
|
133
141
|
let total;
|
|
134
142
|
let selected;
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
143
|
+
try {
|
|
144
|
+
if (statSync(resolved).size > LARGE_FILE_BYTES) {
|
|
145
|
+
const slice = readLineSlice(resolved, offset, limit);
|
|
146
|
+
total = slice.total;
|
|
147
|
+
selected = slice.selected;
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
const content = readFileSync(resolved, "utf-8");
|
|
151
|
+
lines = content.split("\n");
|
|
152
|
+
// Файл с финальным переводом даёт фантомный пустой элемент — снять,
|
|
153
|
+
// чтобы счёт строк совпал со стриминг-путем.
|
|
154
|
+
if (lines.length > 1 && lines[lines.length - 1] === "")
|
|
155
|
+
lines.pop();
|
|
156
|
+
total = lines.length;
|
|
157
|
+
const end = Math.min(offset - 1 + limit, total);
|
|
158
|
+
selected = lines.slice(offset - 1, end).join("\n");
|
|
159
|
+
}
|
|
139
160
|
}
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
161
|
+
catch (err) {
|
|
162
|
+
// Файл мог исчезнуть между existsSync и чтением (TOCTOU) или оказаться
|
|
163
|
+
// директорией — возвращаем понятную ошибку, а не голый throw.
|
|
164
|
+
return {
|
|
165
|
+
success: false,
|
|
166
|
+
output: t("file.read_failed", { path, error: errMsg(err) }),
|
|
167
|
+
};
|
|
146
168
|
}
|
|
147
169
|
const end = Math.min(offset - 1 + limit, total);
|
|
148
170
|
const ext = extname(resolved) || "(no extension)";
|
|
@@ -159,9 +181,8 @@ export const readFileTool = {
|
|
|
159
181
|
next: String(end + 1),
|
|
160
182
|
})
|
|
161
183
|
: "";
|
|
162
|
-
//
|
|
163
|
-
//
|
|
164
|
-
// A full read adds nothing — keeps the card to two lines.
|
|
184
|
+
// Компактный display: заголовок уже показывает путь, доп. инфо (диапазон)
|
|
185
|
+
// нужна только частичному чтению. Полное чтение — пусто, карточка в 2 строки.
|
|
165
186
|
const fullRead = offset === 1 && end >= total;
|
|
166
187
|
const display = fullRead
|
|
167
188
|
? ""
|
package/dist/tools/recall.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
import { join } from "path";
|
|
1
|
+
/** Тул чтения межсессионной памяти (поиск, категории, всё целиком). */
|
|
3
2
|
import { t } from "../i18n/index";
|
|
4
3
|
import { MemoryStore } from "../modules/memory/store";
|
|
4
|
+
import { globalMemoryDir } from "../config/paths";
|
|
5
|
+
/** Категории памяти. */
|
|
5
6
|
const CATEGORIES = ["preferences", "conventions", "decisions", "errors", "facts"];
|
|
7
|
+
/** Читает/ищет по памяти. Параметры: query (full-text), category (опционально). */
|
|
6
8
|
export const recallTool = {
|
|
7
9
|
name: "recall",
|
|
8
10
|
icon: "💾",
|
|
@@ -25,18 +27,18 @@ export const recallTool = {
|
|
|
25
27
|
handler: async (_ctx, args) => {
|
|
26
28
|
const query = args.query ? String(args.query) : "";
|
|
27
29
|
const category = args.category ? String(args.category) : "";
|
|
28
|
-
const memoryDir =
|
|
30
|
+
const memoryDir = globalMemoryDir();
|
|
29
31
|
const store = new MemoryStore(memoryDir);
|
|
30
32
|
try {
|
|
31
|
-
//
|
|
33
|
+
// Без запроса и категории — показать всё.
|
|
32
34
|
if (!query && !category) {
|
|
33
35
|
return { success: true, output: formatAll(store) };
|
|
34
36
|
}
|
|
35
|
-
//
|
|
37
|
+
// Только категория — показать её.
|
|
36
38
|
if (!query && category) {
|
|
37
39
|
return { success: true, output: formatCategory(store, category) };
|
|
38
40
|
}
|
|
39
|
-
//
|
|
41
|
+
// Запрос с опциональной категорией — поиск.
|
|
40
42
|
if (category) {
|
|
41
43
|
const results = searchCategory(store, category, query);
|
|
42
44
|
if (results.length === 0) {
|
|
@@ -47,7 +49,7 @@ export const recallTool = {
|
|
|
47
49
|
output: t("tool.recall.search_results", { category, results: results.join("\n") }),
|
|
48
50
|
};
|
|
49
51
|
}
|
|
50
|
-
//
|
|
52
|
+
// Запрос по всем категориям.
|
|
51
53
|
const results = store.search(query);
|
|
52
54
|
if (results.length === 0) {
|
|
53
55
|
return { success: true, output: t("tool.recall.empty", { query }) };
|
|
@@ -59,10 +61,12 @@ export const recallTool = {
|
|
|
59
61
|
};
|
|
60
62
|
}
|
|
61
63
|
catch (err) {
|
|
62
|
-
|
|
64
|
+
// Чтение отсутствующей/битой категории — ошибка, а не пустой успех.
|
|
65
|
+
return { success: false, output: t("tool.memory_error", { error: String(err) }) };
|
|
63
66
|
}
|
|
64
67
|
},
|
|
65
68
|
};
|
|
69
|
+
/** Форматирует всю память (преференсы + последние 5 записей категорий). */
|
|
66
70
|
function formatAll(store) {
|
|
67
71
|
const parts = [];
|
|
68
72
|
const prefs = store.getPreferences();
|
|
@@ -84,6 +88,7 @@ function formatAll(store) {
|
|
|
84
88
|
}
|
|
85
89
|
return parts.length > 0 ? parts.join("\n") : t("tool.recall.no_memory");
|
|
86
90
|
}
|
|
91
|
+
/** Форматирует одну категорию. */
|
|
87
92
|
function formatCategory(store, category) {
|
|
88
93
|
if (category === "preferences") {
|
|
89
94
|
const prefs = store.getPreferences();
|
|
@@ -101,6 +106,7 @@ function formatCategory(store, category) {
|
|
|
101
106
|
return t("tool.recall.no_memory");
|
|
102
107
|
return `${category.charAt(0).toUpperCase() + category.slice(1)} (${entries.length} entries):\n${entries.join("\n")}`;
|
|
103
108
|
}
|
|
109
|
+
/** Ищет запрос внутри категории (регистронезависимо). */
|
|
104
110
|
function searchCategory(store, category, query) {
|
|
105
111
|
if (category === "preferences") {
|
|
106
112
|
const prefs = store.getPreferences();
|
package/dist/tools/registry.js
CHANGED
|
@@ -12,12 +12,15 @@ export class ToolRegistry {
|
|
|
12
12
|
has(name) {
|
|
13
13
|
return this.tools.has(name);
|
|
14
14
|
}
|
|
15
|
+
/** Тулы по именам (отсортированы). */
|
|
15
16
|
list() {
|
|
16
17
|
return Array.from(this.tools.keys()).sort();
|
|
17
18
|
}
|
|
19
|
+
/** Все зарегистрированные определения. */
|
|
18
20
|
getAll() {
|
|
19
21
|
return Array.from(this.tools.values());
|
|
20
22
|
}
|
|
23
|
+
/** Подмножество с любым из тегов (без alwaysOn). */
|
|
21
24
|
getByTags(tags) {
|
|
22
25
|
return this.getAll().filter((t) => {
|
|
23
26
|
if (!t.tags || t.tags.length === 0)
|
|
@@ -26,17 +29,15 @@ export class ToolRegistry {
|
|
|
26
29
|
});
|
|
27
30
|
}
|
|
28
31
|
/**
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
* tool (backward-compatible). Duplicates are impossible because the
|
|
32
|
-
* alwaysOn + tagged sets are unioned by name.
|
|
32
|
+
* Тулы для LLM: всегда `alwaysOn`, плюс совпавшие по тегам. Без тегов —
|
|
33
|
+
* все (backward-compatible). Дубликаты исключены объединением по имени.
|
|
33
34
|
*/
|
|
34
35
|
getAllForLLM(tags) {
|
|
35
36
|
const all = this.getAll();
|
|
36
37
|
const tagSet = new Set(tags ?? []);
|
|
37
38
|
const selected = tagSet.size === 0
|
|
38
39
|
? all
|
|
39
|
-
: all.filter((t) => t.alwaysOn || (t.tags
|
|
40
|
+
: all.filter((t) => t.alwaysOn || (t.tags?.some((tag) => tagSet.has(tag))));
|
|
40
41
|
return selected.map((t) => ({
|
|
41
42
|
name: t.name,
|
|
42
43
|
description: t.description,
|
package/dist/tools/remember.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
import { join } from "path";
|
|
1
|
+
/** Тул записи в межсессионную память (preferences, факты, решения, ошибки). */
|
|
3
2
|
import { t } from "../i18n/index";
|
|
4
3
|
import { MemoryStore } from "../modules/memory/store";
|
|
4
|
+
import { globalMemoryDir } from "../config/paths";
|
|
5
|
+
/** Категории памяти. */
|
|
5
6
|
const CATEGORIES = ["preferences", "conventions", "decisions", "errors", "facts"];
|
|
7
|
+
/** Пишет факт/преференс в глобальную память. Параметры: category, key/value или entry. */
|
|
6
8
|
export const rememberTool = {
|
|
7
9
|
name: "remember",
|
|
8
10
|
icon: "🧠",
|
|
@@ -36,7 +38,7 @@ export const rememberTool = {
|
|
|
36
38
|
if (!CATEGORIES.includes(category)) {
|
|
37
39
|
return { success: false, output: t("tool.invalid_params") };
|
|
38
40
|
}
|
|
39
|
-
const memoryDir =
|
|
41
|
+
const memoryDir = globalMemoryDir();
|
|
40
42
|
const store = new MemoryStore(memoryDir);
|
|
41
43
|
try {
|
|
42
44
|
if (category === "preferences") {
|
|
@@ -62,7 +64,9 @@ export const rememberTool = {
|
|
|
62
64
|
};
|
|
63
65
|
}
|
|
64
66
|
catch (err) {
|
|
65
|
-
|
|
67
|
+
// Упавшая запись — провал: отчёт об успехе заставлял агента (и учёт
|
|
68
|
+
// падений) верить, что память сохранена.
|
|
69
|
+
return { success: false, output: t("tool.memory_error", { error: String(err) }) };
|
|
66
70
|
}
|
|
67
71
|
},
|
|
68
72
|
};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/** Тулы записи (для классификации отказа как write). */
|
|
1
2
|
const WRITE_TOOLS = new Set([
|
|
2
3
|
"write_file",
|
|
3
4
|
"edit_file",
|
|
@@ -7,8 +8,8 @@ const WRITE_TOOLS = new Set([
|
|
|
7
8
|
"download_file",
|
|
8
9
|
]);
|
|
9
10
|
/**
|
|
10
|
-
*
|
|
11
|
-
* ("Path not allowed" / "Путь недопустим")
|
|
11
|
+
* Отказы скоупа идут через i18n-ключи file.path_not_allowed(_short)
|
|
12
|
+
* ("Path not allowed" / "Путь недопустим") и "[SCOPE]"-префикс chunk-query.
|
|
12
13
|
*/
|
|
13
14
|
export function isPathDenialOutput(output) {
|
|
14
15
|
if (!output)
|
|
@@ -17,6 +18,7 @@ export function isPathDenialOutput(output) {
|
|
|
17
18
|
output.includes("Путь недопустим") ||
|
|
18
19
|
output.startsWith("[SCOPE]"));
|
|
19
20
|
}
|
|
21
|
+
/** Извлекает путь из аргументов tool call (по именам ключей, иначе по виду). */
|
|
20
22
|
function pathArgOf(args) {
|
|
21
23
|
if (!args)
|
|
22
24
|
return "";
|
|
@@ -31,30 +33,31 @@ function pathArgOf(args) {
|
|
|
31
33
|
}
|
|
32
34
|
return "";
|
|
33
35
|
}
|
|
34
|
-
/**
|
|
36
|
+
/** Наблюдает результаты тулов субагента, копит scope-отказы по путям. */
|
|
35
37
|
export class ScopeDenialCollector {
|
|
36
38
|
denied = new Map();
|
|
37
39
|
plugin = {
|
|
38
40
|
name: "scope-denial-collector",
|
|
39
41
|
isBuiltin: true,
|
|
40
42
|
onAfterTool: (_ctx, call, result) => {
|
|
41
|
-
const name = call
|
|
43
|
+
const name = call.name;
|
|
42
44
|
if (!name || !result || result.success)
|
|
43
45
|
return;
|
|
44
46
|
if (!isPathDenialOutput(typeof result.output === "string" ? result.output : undefined)) {
|
|
45
47
|
return;
|
|
46
48
|
}
|
|
47
|
-
const path = pathArgOf(call
|
|
49
|
+
const path = pathArgOf(call.arguments);
|
|
48
50
|
if (!path)
|
|
49
51
|
return;
|
|
50
52
|
const write = WRITE_TOOLS.has(name);
|
|
51
53
|
this.denied.set(path, { path, write });
|
|
52
54
|
},
|
|
53
55
|
};
|
|
56
|
+
/** Все отказы дедуплицированно. */
|
|
54
57
|
getDenied() {
|
|
55
58
|
return Array.from(this.denied.values());
|
|
56
59
|
}
|
|
57
|
-
/** read_only=true
|
|
60
|
+
/** read_only=true — только если все отказы были чисто read. */
|
|
58
61
|
toScopeRequest(reason = "sub-agent was denied access to files needed for the task") {
|
|
59
62
|
const all = this.getDenied();
|
|
60
63
|
if (all.length === 0)
|
|
@@ -66,9 +69,11 @@ export class ScopeDenialCollector {
|
|
|
66
69
|
};
|
|
67
70
|
}
|
|
68
71
|
}
|
|
72
|
+
/** Сериализует запрос в `<scope-request>`-блок результата. */
|
|
69
73
|
export function formatScopeRequest(req) {
|
|
70
74
|
return `<scope-request>${JSON.stringify(req)}</scope-request>`;
|
|
71
75
|
}
|
|
76
|
+
/** Парсит `<scope-request>`-блок из текста. Битый/пустой — null. */
|
|
72
77
|
export function parseScopeRequest(text) {
|
|
73
78
|
if (!text)
|
|
74
79
|
return null;
|