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
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/** Команды в довесок к bash-blacklist, если визард включил блок. */
|
|
2
|
+
export const SECURITY_BASH_BLOCKLIST = [
|
|
3
|
+
"rm",
|
|
4
|
+
"dd",
|
|
5
|
+
"chmod",
|
|
6
|
+
"wget",
|
|
7
|
+
"curl",
|
|
8
|
+
"scp",
|
|
9
|
+
"ssh",
|
|
10
|
+
"nc",
|
|
11
|
+
"netcat",
|
|
12
|
+
"sudo",
|
|
13
|
+
"su",
|
|
14
|
+
"kill",
|
|
15
|
+
"pkill",
|
|
16
|
+
"killall",
|
|
17
|
+
"shutdown",
|
|
18
|
+
"reboot",
|
|
19
|
+
];
|
|
20
|
+
/**
|
|
21
|
+
* Применить ответы first-run визарда на загруженный конфиг.
|
|
22
|
+
* Провайдер заменяется **новым объектом**, а не мутируется на месте: `loadConfig`
|
|
23
|
+
* может повесить сессионный `MMA_PROVIDER_*` env-оверрайд, чей `envValue`
|
|
24
|
+
* указывает на загруженный объект `config.provider`. `stripEnvOverrides` в
|
|
25
|
+
* `saveConfig` матчит этот объект по identity и откатил бы весь провайдер к
|
|
26
|
+
* env/дефолту, молча потеряв провайдер/URL/ключ визарда. Замена объекта
|
|
27
|
+
* сохраняет протухшую identity оверрайда — вычищается только env-ключ, а ответы
|
|
28
|
+
* персистятся. Активная запись `provider.entries[]` тоже переписывается
|
|
29
|
+
* (type/baseUrl/apiKey и окно контекста), соседи и их тюнинг целы.
|
|
30
|
+
*/
|
|
31
|
+
export function applyAnswersToConfig(config, a) {
|
|
32
|
+
config.provider = {
|
|
33
|
+
...config.provider,
|
|
34
|
+
type: a.provider,
|
|
35
|
+
baseUrl: a.apiBase,
|
|
36
|
+
apiKey: a.apiKey,
|
|
37
|
+
};
|
|
38
|
+
config.model = a.model;
|
|
39
|
+
config.contextWindow = a.contextWindow;
|
|
40
|
+
config.maxToolIterations = a.maxToolIterations;
|
|
41
|
+
config.locale = a.locale;
|
|
42
|
+
// Мультиконфиги резолвят активный провайдер из `entries[]`, так что ответы
|
|
43
|
+
// пишутся на активную запись — без потери соседей и тюнинга записи
|
|
44
|
+
// (cache, retry, rateLimits). Осознанный кастомный label сохраняется,
|
|
45
|
+
// generic/дефолтный заменяется.
|
|
46
|
+
if ((config.provider.entries?.length ?? 0) > 0) {
|
|
47
|
+
const entries = config.provider.entries ?? [];
|
|
48
|
+
const activeName = config.provider.active || entries[0]?.label || entries[0]?.type;
|
|
49
|
+
const idx = entries.findIndex((e) => e.label === activeName || e.type === activeName);
|
|
50
|
+
const target = idx >= 0 ? idx : 0;
|
|
51
|
+
const current = entries[target];
|
|
52
|
+
const keepLabel = Boolean(current.label && current.label !== current.type);
|
|
53
|
+
const updated = {
|
|
54
|
+
...current,
|
|
55
|
+
type: a.provider,
|
|
56
|
+
baseUrl: a.apiBase,
|
|
57
|
+
apiKey: a.apiKey,
|
|
58
|
+
contextWindow: a.contextWindow,
|
|
59
|
+
...(keepLabel ? {} : { label: a.provider }),
|
|
60
|
+
};
|
|
61
|
+
const next = entries.slice();
|
|
62
|
+
next[target] = updated;
|
|
63
|
+
config.provider.entries = next;
|
|
64
|
+
config.provider.active = updated.label || updated.type;
|
|
65
|
+
}
|
|
66
|
+
if (config.security) {
|
|
67
|
+
config.security.enabled = true;
|
|
68
|
+
config.security.bash.enabled = true;
|
|
69
|
+
config.security.bash.blockDangerousFlags =
|
|
70
|
+
a.securityFlagsBlock || config.security.bash.blockDangerousFlags;
|
|
71
|
+
if (a.securityBashBlock) {
|
|
72
|
+
config.security.bash.blacklist = [
|
|
73
|
+
...new Set([...config.security.bash.blacklist, ...SECURITY_BASH_BLOCKLIST]),
|
|
74
|
+
];
|
|
75
|
+
}
|
|
76
|
+
config.security.paths.enabled = a.securityPathsRestrict;
|
|
77
|
+
}
|
|
78
|
+
return config;
|
|
79
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
// Discovery провайдеров для first-run визарда: скан локальных портов, модели, chat-проба.
|
|
2
|
+
/**
|
|
3
|
+
* Хелперы discovery провайдеров для first-run визарда.
|
|
4
|
+
* Вынесены как есть (минус терминальный UI) из `src/cli/setup.ts`, чтобы
|
|
5
|
+
* браузерный визард переиспользовал ту же логику проб. Чисто сетевые хелперы:
|
|
6
|
+
* ни терминала, ни output-канала.
|
|
7
|
+
*/
|
|
8
|
+
import { probeChat } from "../providers/preflight";
|
|
9
|
+
import { SETUP_SCAN_TIMEOUT_MS as SCAN_TIMEOUT_MS, SETUP_MODELS_TIMEOUT_MS as MODELS_TIMEOUT_MS, SETUP_CHAT_TIMEOUT_MS as CHAT_TIMEOUT_MS, } from "../../config/timeouts";
|
|
10
|
+
/** Локальные серверы для скана. */
|
|
11
|
+
const KNOWN_PORTS = [
|
|
12
|
+
{ port: 1234, label: "LM Studio" },
|
|
13
|
+
{ port: 11434, label: "Ollama" },
|
|
14
|
+
{ port: 8080, label: "vLLM / custom" },
|
|
15
|
+
{ port: 4891, label: "GPT4All" },
|
|
16
|
+
];
|
|
17
|
+
/** Проба известных локальных портов на OpenAI-совместимый `/v1/models`. */
|
|
18
|
+
export async function scanLocalServers() {
|
|
19
|
+
const found = [];
|
|
20
|
+
for (const { port, label } of KNOWN_PORTS) {
|
|
21
|
+
try {
|
|
22
|
+
const controller = new AbortController();
|
|
23
|
+
const timeout = setTimeout(() => controller.abort(), SCAN_TIMEOUT_MS);
|
|
24
|
+
const resp = await fetch(`http://localhost:${port}/v1/models`, {
|
|
25
|
+
signal: controller.signal,
|
|
26
|
+
});
|
|
27
|
+
clearTimeout(timeout);
|
|
28
|
+
if (resp.ok) {
|
|
29
|
+
found.push({ label, url: `http://localhost:${port}/v1` });
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
/* порт недоступен */
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return found;
|
|
37
|
+
}
|
|
38
|
+
/** Id моделей из OpenAI-совместимого `/models`; любая неудача — []. */
|
|
39
|
+
export async function fetchProviderModels(apiBase, apiKey) {
|
|
40
|
+
try {
|
|
41
|
+
const url = `${apiBase.replace(/\/+$/, "")}/models`;
|
|
42
|
+
const controller = new AbortController();
|
|
43
|
+
const timeout = setTimeout(() => controller.abort(), MODELS_TIMEOUT_MS);
|
|
44
|
+
const resp = await fetch(url, {
|
|
45
|
+
headers: {
|
|
46
|
+
Authorization: `Bearer ${apiKey}`,
|
|
47
|
+
"Content-Type": "application/json",
|
|
48
|
+
},
|
|
49
|
+
signal: controller.signal,
|
|
50
|
+
});
|
|
51
|
+
clearTimeout(timeout);
|
|
52
|
+
if (!resp.ok)
|
|
53
|
+
return [];
|
|
54
|
+
const data = (await resp.json());
|
|
55
|
+
const list = Array.isArray(data.data) ? data.data : [];
|
|
56
|
+
return list
|
|
57
|
+
.map((m) => m.id || m.name || m.model || "")
|
|
58
|
+
.filter((id) => Boolean(id));
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
return [];
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/** Дешёвая chat-проба: `true`, если модель отвечает. Не бросает. */
|
|
65
|
+
export async function testProviderChat(apiBase, apiKey, model) {
|
|
66
|
+
try {
|
|
67
|
+
return (await probeChat({ baseUrl: apiBase, apiKey, model }, CHAT_TIMEOUT_MS)) !== "";
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
+
// Загрузчик скиллов: рекурсивный скан .md-файлов с frontmatter-метаданными.
|
|
1
2
|
import { readdirSync, readFileSync, existsSync, statSync } from "fs";
|
|
2
3
|
import { join } from "path";
|
|
4
|
+
/** Загрузчик скиллов из каталогов (встроенные, глобальные, проектные). */
|
|
3
5
|
export class SkillsLoader {
|
|
6
|
+
/** Рекурсивно грузит все скиллы из каталога; нет каталога — пусто. */
|
|
4
7
|
loadFromDir(dirPath) {
|
|
5
8
|
if (!existsSync(dirPath))
|
|
6
9
|
return [];
|
|
@@ -32,7 +35,11 @@ export class SkillsLoader {
|
|
|
32
35
|
}
|
|
33
36
|
}
|
|
34
37
|
parseSkillFile(content, sourcePath) {
|
|
35
|
-
|
|
38
|
+
// Сначала нормализуем CRLF: у Windows-чекаута/авторского скилла разделитель
|
|
39
|
+
// `---\r\n`, который LF-паттерн молча не матчил (все bundled-скиллы
|
|
40
|
+
// отбрасывались без предупреждения).
|
|
41
|
+
const normalized = content.replace(/\r\n/g, "\n");
|
|
42
|
+
const match = normalized.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
|
|
36
43
|
if (!match)
|
|
37
44
|
return null;
|
|
38
45
|
const frontmatter = this.parseFrontmatter(match[1]);
|
|
@@ -68,6 +75,7 @@ export class SkillsLoader {
|
|
|
68
75
|
}
|
|
69
76
|
return result;
|
|
70
77
|
}
|
|
78
|
+
/** Грузит скиллы из трёх источников по приоритету: встроенные, глобальные, проектные. */
|
|
71
79
|
loadFromAllSources(builtinDir, globalDir, projectDir) {
|
|
72
80
|
return [
|
|
73
81
|
...this.loadFromDir(builtinDir),
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { t } from "../../i18n/index";
|
|
2
2
|
import { estimateTokens } from "../../llm/token-counter";
|
|
3
|
+
/** Модуль скиллов: каталог доступных, загрузка в бюджет, промпт-блок. */
|
|
3
4
|
export class SkillsModule {
|
|
4
5
|
name = "skills";
|
|
5
6
|
availableSkills;
|
|
@@ -10,6 +11,7 @@ export class SkillsModule {
|
|
|
10
11
|
this.availableSkills = availableSkills;
|
|
11
12
|
this.budget = budget;
|
|
12
13
|
}
|
|
14
|
+
/** Загружает скилл по имени с проверкой бюджета токенов. */
|
|
13
15
|
loadByName(name) {
|
|
14
16
|
if (this.loadedSkills.has(name)) {
|
|
15
17
|
return { success: false, message: t("skill.already_loaded", { name }) };
|
|
@@ -20,6 +22,7 @@ export class SkillsModule {
|
|
|
20
22
|
}
|
|
21
23
|
return this.tryLoad(skill);
|
|
22
24
|
}
|
|
25
|
+
/** Выгружает скилл и возвращает его токены в бюджет. */
|
|
23
26
|
unload(name) {
|
|
24
27
|
const skill = this.loadedSkills.get(name);
|
|
25
28
|
if (!skill)
|
|
@@ -28,19 +31,24 @@ export class SkillsModule {
|
|
|
28
31
|
this.currentTokens -= this.estimateTokens(skill.content);
|
|
29
32
|
return true;
|
|
30
33
|
}
|
|
34
|
+
/** Загруженные скиллы. */
|
|
31
35
|
getLoaded() {
|
|
32
36
|
return Array.from(this.loadedSkills.values());
|
|
33
37
|
}
|
|
38
|
+
/** Все доступные (загруженные и нет) скиллы. */
|
|
34
39
|
getAvailable() {
|
|
35
40
|
return this.availableSkills;
|
|
36
41
|
}
|
|
42
|
+
/** Ищет скилл по точному имени среди доступных. */
|
|
37
43
|
findByName(name) {
|
|
38
44
|
return this.availableSkills.find((s) => s.name === name);
|
|
39
45
|
}
|
|
46
|
+
/** Подстрочный поиск по именам и описаниям (без учёта регистра). */
|
|
40
47
|
search(query) {
|
|
41
48
|
const q = query.toLowerCase();
|
|
42
49
|
return this.availableSkills.filter((s) => s.name.toLowerCase().includes(q) || s.description.toLowerCase().includes(q));
|
|
43
50
|
}
|
|
51
|
+
/** Бюджет токенов: занято, всего, остаток. */
|
|
44
52
|
getBudget() {
|
|
45
53
|
return {
|
|
46
54
|
used: this.currentTokens,
|
|
@@ -48,10 +56,12 @@ export class SkillsModule {
|
|
|
48
56
|
remaining: this.budget - this.currentTokens,
|
|
49
57
|
};
|
|
50
58
|
}
|
|
59
|
+
/** Тело загруженного скилла; null, если не загружен. */
|
|
51
60
|
getSkillContent(name) {
|
|
52
61
|
const skill = this.loadedSkills.get(name);
|
|
53
62
|
return skill ? skill.content : null;
|
|
54
63
|
}
|
|
64
|
+
/** Системный блок: каталог доступных + тела загруженных скиллов. */
|
|
55
65
|
getSystemPromptBlock() {
|
|
56
66
|
const lines = [];
|
|
57
67
|
if (this.availableSkills.length > 0) {
|
|
@@ -82,6 +92,7 @@ export class SkillsModule {
|
|
|
82
92
|
estimatedTokens: tokens,
|
|
83
93
|
};
|
|
84
94
|
}
|
|
95
|
+
/** Плагин-заглушка: модуль работает через системный блок, хуки не нужны. */
|
|
85
96
|
getPlugin() {
|
|
86
97
|
return {
|
|
87
98
|
name: "skills",
|
|
@@ -1,19 +1,20 @@
|
|
|
1
|
+
// Чтение CHANGELOG.md из установленного пакета и вырезка секций версий.
|
|
1
2
|
import { readFileSync, existsSync } from "fs";
|
|
2
3
|
import { dirname, join } from "path";
|
|
3
4
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
5
|
+
* Читает bundled CHANGELOG.md из установленного npm-пакета.
|
|
6
|
+
* Резолвится от местоположения этого файла (dist/modules/updater/) вверх
|
|
7
|
+
* до корня пакета.
|
|
7
8
|
*/
|
|
8
9
|
export function readChangelog(packageName) {
|
|
9
10
|
try {
|
|
10
|
-
//
|
|
11
|
+
// Идём вверх от этого файла в поисках package.json
|
|
11
12
|
let dir = dirname(import.meta.url);
|
|
12
|
-
//
|
|
13
|
+
// file:// URL в путь на POSIX, иначе используем путь как есть
|
|
13
14
|
if (dir.startsWith("file://")) {
|
|
14
15
|
dir = decodeURIComponent(dir.slice(7));
|
|
15
16
|
}
|
|
16
|
-
//
|
|
17
|
+
// Ищем корень пакета (package.json с совпадающим именем)
|
|
17
18
|
for (let i = 0; i < 10; i++) {
|
|
18
19
|
const pkgPath = join(dir, "package.json");
|
|
19
20
|
if (existsSync(pkgPath)) {
|
|
@@ -35,13 +36,13 @@ export function readChangelog(packageName) {
|
|
|
35
36
|
}
|
|
36
37
|
}
|
|
37
38
|
/**
|
|
38
|
-
*
|
|
39
|
-
*
|
|
39
|
+
* Вырезает записи между версиями (старая эксклюзивно, новая инклюзивно).
|
|
40
|
+
* Возвращает сырые markdown-секции.
|
|
40
41
|
*
|
|
41
42
|
* parseChangelog(changelog, "0.55.1", "0.56.1") =>
|
|
42
43
|
* "## [0.56.1] - ...\n...\n## [0.56.0] - ...\n..."
|
|
43
44
|
*/
|
|
44
|
-
export function extractChangelogRange(changelog, fromVersion,
|
|
45
|
+
export function extractChangelogRange(changelog, fromVersion, _toVersion) {
|
|
45
46
|
const lines = changelog.split("\n");
|
|
46
47
|
const sections = [];
|
|
47
48
|
let capturing = false;
|
|
@@ -51,12 +52,12 @@ export function extractChangelogRange(changelog, fromVersion, toVersion) {
|
|
|
51
52
|
if (match) {
|
|
52
53
|
const ver = match[1];
|
|
53
54
|
if (ver === fromVersion) {
|
|
54
|
-
//
|
|
55
|
+
// Стоп перед этой секцией (эксклюзивно).
|
|
55
56
|
foundFrom = true;
|
|
56
57
|
break;
|
|
57
58
|
}
|
|
58
59
|
if (capturing) {
|
|
59
|
-
//
|
|
60
|
+
// Новый заголовок секции — отделяем пустой строкой.
|
|
60
61
|
sections.push("");
|
|
61
62
|
}
|
|
62
63
|
capturing = true;
|
|
@@ -65,14 +66,12 @@ export function extractChangelogRange(changelog, fromVersion, toVersion) {
|
|
|
65
66
|
sections.push(line);
|
|
66
67
|
}
|
|
67
68
|
}
|
|
68
|
-
//
|
|
69
|
+
// Версия "from" так и не нашлась — валидного диапазона нет, возвращаем null.
|
|
69
70
|
if (!foundFrom)
|
|
70
71
|
return null;
|
|
71
72
|
return sections.length > 0 ? sections.join("\n").trim() : null;
|
|
72
73
|
}
|
|
73
|
-
/**
|
|
74
|
-
* Get the latest changelog entry (most recent version).
|
|
75
|
-
*/
|
|
74
|
+
/** Берёт свежайшую запись changelog (самая верхняя версия). */
|
|
76
75
|
export function extractLatestChangelog(changelog) {
|
|
77
76
|
const lines = changelog.split("\n");
|
|
78
77
|
const sections = [];
|
|
@@ -82,7 +81,7 @@ export function extractLatestChangelog(changelog) {
|
|
|
82
81
|
const match = line.match(/^## \[(\d+\.\d+\.\d+)\]/);
|
|
83
82
|
if (match) {
|
|
84
83
|
if (found)
|
|
85
|
-
break; //
|
|
84
|
+
break; // Стоп на втором заголовке секции.
|
|
86
85
|
capturing = true;
|
|
87
86
|
found = true;
|
|
88
87
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
// Проверка обновлений в npm-реестре и установка свежей версии глобально.
|
|
1
2
|
import { platform } from "os";
|
|
2
3
|
import { resolveSpawnCommand } from "../lsp/command";
|
|
3
|
-
/**
|
|
4
|
+
/** Численное сравнение semver (x.y.z, пререлизы игнорируются). */
|
|
4
5
|
export function semverGt(a, b) {
|
|
5
6
|
const pa = a
|
|
6
7
|
.replace(/^v/, "")
|
|
@@ -24,6 +25,7 @@ const defaultRunner = async (command, args, options) => {
|
|
|
24
25
|
execFile(command, args, options, (err) => resolve({ error: err?.message }));
|
|
25
26
|
});
|
|
26
27
|
};
|
|
28
|
+
/** Апдейтер пакета: опрос npm-реестра и глобальная установка. */
|
|
27
29
|
export class Updater {
|
|
28
30
|
currentVersion;
|
|
29
31
|
packageName;
|
|
@@ -33,12 +35,15 @@ export class Updater {
|
|
|
33
35
|
this.packageName = packageName;
|
|
34
36
|
this.runner = runner ?? defaultRunner;
|
|
35
37
|
}
|
|
38
|
+
/** Текущая версия пакета. */
|
|
36
39
|
getCurrentVersion() {
|
|
37
40
|
return this.currentVersion;
|
|
38
41
|
}
|
|
42
|
+
/** Имя пакета в реестре. */
|
|
39
43
|
getPackageName() {
|
|
40
44
|
return this.packageName;
|
|
41
45
|
}
|
|
46
|
+
/** Спрашивает реестр о latest-теге; офлайн/ошибка — флаг false с причиной. */
|
|
42
47
|
async check() {
|
|
43
48
|
try {
|
|
44
49
|
const response = await fetch(`https://registry.npmjs.org/${this.packageName}`, {
|
|
@@ -65,8 +70,8 @@ export class Updater {
|
|
|
65
70
|
}
|
|
66
71
|
}
|
|
67
72
|
/**
|
|
68
|
-
*
|
|
69
|
-
*
|
|
73
|
+
* Ставит свежую версию глобально через `npm install -g`.
|
|
74
|
+
* Возвращает InstallResult (не бросает).
|
|
70
75
|
*/
|
|
71
76
|
async install(latest) {
|
|
72
77
|
try {
|
|
@@ -88,7 +93,7 @@ export class Updater {
|
|
|
88
93
|
const npmArgs = ["install", "-g", `${this.packageName}@${latest}`];
|
|
89
94
|
const command = resolveSpawnCommand("npm");
|
|
90
95
|
if (platform() === "win32" && /\.(cmd|bat)$/i.test(command)) {
|
|
91
|
-
// Node
|
|
96
|
+
// Node не спавнит .cmd/.bat-шимы напрямую (spawn EINVAL) — идём через cmd.exe.
|
|
92
97
|
return { command: "cmd.exe", args: ["/c", command, ...npmArgs] };
|
|
93
98
|
}
|
|
94
99
|
return { command, args: npmArgs };
|
|
@@ -1,12 +1,13 @@
|
|
|
1
|
+
// Детект dev-запуска по расширению точки входа (для запрета самообновления из чекаута).
|
|
1
2
|
/**
|
|
2
|
-
* True
|
|
3
|
+
* True, когда запущенная точка входа — TypeScript-исходник, т.е. dev-прогон
|
|
3
4
|
* (`bun run src/cli/main.ts`, `bun --watch src/cli/main.ts`, tsx/ts-node).
|
|
4
|
-
*
|
|
5
|
-
*
|
|
5
|
+
* Опубликованный пакет грузится как сбандленный `dist/main.js` через
|
|
6
|
+
* `bin/mma.mjs`, так что для реальных установок это false.
|
|
6
7
|
*
|
|
7
|
-
* Dev
|
|
8
|
-
*
|
|
9
|
-
*
|
|
8
|
+
* Dev-прогоны НЕ должны самообновляться: версия package.json в чекауте вправе
|
|
9
|
+
* отставать от npm, а молча `npm install -g`-шить поверх пользовательского
|
|
10
|
+
* глобала из dev-дерева — деструктивно и неожиданно.
|
|
10
11
|
*/
|
|
11
12
|
export function isDevEntryPath(path) {
|
|
12
13
|
const clean = path.split(/[?#]/)[0];
|
|
@@ -4,11 +4,10 @@ import { t } from "../../i18n/index";
|
|
|
4
4
|
import { pc } from "../../ui/colors";
|
|
5
5
|
import { defaultOutputBus } from "../../output";
|
|
6
6
|
/**
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* until the next launch.
|
|
7
|
+
* Модуль самообновления. При старте — АСИНХРОННАЯ (неблокирующая) проверка,
|
|
8
|
+
* чтобы агент никогда не ждал сеть. При включённом `autoInstall` свежая версия
|
|
9
|
+
* ставится глобально через `npm install -g` в фоне, пользователь уведомляется;
|
|
10
|
+
* запущенный процесс до следующего старта работает на старой версии.
|
|
12
11
|
*/
|
|
13
12
|
export class UpdaterModule {
|
|
14
13
|
name = "updater";
|
|
@@ -17,9 +16,9 @@ export class UpdaterModule {
|
|
|
17
16
|
logger;
|
|
18
17
|
started = false;
|
|
19
18
|
timer = null;
|
|
20
|
-
/**
|
|
19
|
+
/** Промис текущего витка run-loop (резолвится по устаканиванию). */
|
|
21
20
|
idlePromise = null;
|
|
22
|
-
/**
|
|
21
|
+
/** Тест-хук: отключает реальные npm-установки. */
|
|
23
22
|
installEnabled = true;
|
|
24
23
|
constructor(config, currentVersion, packageName, logger, installRunner) {
|
|
25
24
|
this.config = config;
|
|
@@ -30,20 +29,22 @@ export class UpdaterModule {
|
|
|
30
29
|
error: (m) => defaultOutputBus.log("error", "updater", m),
|
|
31
30
|
};
|
|
32
31
|
}
|
|
33
|
-
/**
|
|
32
|
+
/** Тест-хук: запрещает модулю реально гонять npm install. */
|
|
34
33
|
setInstallEnabled(enabled) {
|
|
35
34
|
this.installEnabled = enabled;
|
|
36
35
|
}
|
|
36
|
+
/** Доступ к underlying-апдейтеру (версия, пакет). */
|
|
37
37
|
getUpdater() {
|
|
38
38
|
return this.updater;
|
|
39
39
|
}
|
|
40
|
+
/** True, если фоновая проверка запущена. */
|
|
40
41
|
isStarted() {
|
|
41
42
|
return this.started;
|
|
42
43
|
}
|
|
43
44
|
/**
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
45
|
+
* Стартует фоновую проверку. Возвращается сразу (fire-and-forget), чтобы
|
|
46
|
+
* вызывающий не ждал реестр. `intervalMs > 0` — вечный цикл перепроверок
|
|
47
|
+
* (поведение "auto-update daemon").
|
|
47
48
|
*/
|
|
48
49
|
start() {
|
|
49
50
|
if (this.started || !this.config.enabled)
|
|
@@ -51,6 +52,7 @@ export class UpdaterModule {
|
|
|
51
52
|
this.started = true;
|
|
52
53
|
this.idlePromise = this.runLoop();
|
|
53
54
|
}
|
|
55
|
+
/** Останавливает фоновый цикл и снимает таймер. */
|
|
54
56
|
stop() {
|
|
55
57
|
this.started = false;
|
|
56
58
|
if (this.timer) {
|
|
@@ -59,9 +61,9 @@ export class UpdaterModule {
|
|
|
59
61
|
}
|
|
60
62
|
}
|
|
61
63
|
/**
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
* process.exit()
|
|
64
|
+
* Ждёт устаканивания текущего цикла check/install, но не дольше timeoutMs.
|
|
65
|
+
* Нужно one-shot CLI-прогонам, чтобы фоновый `npm install -g` не убивался
|
|
66
|
+
* process.exit() до завершения. No-op, если модуль не стартовал.
|
|
65
67
|
*/
|
|
66
68
|
async waitForIdle(timeoutMs = 130000) {
|
|
67
69
|
const idle = this.idlePromise;
|
|
@@ -82,7 +84,7 @@ export class UpdaterModule {
|
|
|
82
84
|
}
|
|
83
85
|
}
|
|
84
86
|
}
|
|
85
|
-
/**
|
|
87
|
+
/** Одна проверка; при доступном апдейте — опциональная установка. */
|
|
86
88
|
async runOnce() {
|
|
87
89
|
const result = await this.updater.check();
|
|
88
90
|
if (result.error) {
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
// Профиль пользователя: сбор фактов окружения, персист в profile.json, преференсы.
|
|
1
2
|
import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
|
|
2
3
|
import { join } from "path";
|
|
3
|
-
import { homedir, hostname, platform, type } from "os";
|
|
4
|
+
import { homedir, hostname, platform, type, } from "os";
|
|
4
5
|
import { env } from "process";
|
|
5
6
|
import { ProfileCompressor } from "./compressor";
|
|
7
|
+
/** Профиль пользователя с персистом в `<dir>/profile.json`. */
|
|
6
8
|
export class UserProfile {
|
|
7
9
|
profileDir;
|
|
8
10
|
info = null;
|
|
@@ -10,6 +12,7 @@ export class UserProfile {
|
|
|
10
12
|
constructor(profileDir) {
|
|
11
13
|
this.profileDir = profileDir;
|
|
12
14
|
}
|
|
15
|
+
/** Собирает факты окружения (ОС, шелл, дом, node) в свежий снимок. */
|
|
13
16
|
collect() {
|
|
14
17
|
this.info = {
|
|
15
18
|
platform: platform(),
|
|
@@ -22,12 +25,14 @@ export class UserProfile {
|
|
|
22
25
|
};
|
|
23
26
|
return this.info;
|
|
24
27
|
}
|
|
28
|
+
/** Сохраняет текущий снимок и преференсы в profile.json. */
|
|
25
29
|
save() {
|
|
26
30
|
if (!existsSync(this.profileDir)) {
|
|
27
31
|
mkdirSync(this.profileDir, { recursive: true });
|
|
28
32
|
}
|
|
29
33
|
writeFileSync(join(this.profileDir, "profile.json"), JSON.stringify({ ...this.info, preferences: this.preferences }, null, 2), "utf-8");
|
|
30
34
|
}
|
|
35
|
+
/** Загружает снимок из profile.json; null, если файла нет или он бит. */
|
|
31
36
|
load() {
|
|
32
37
|
const path = join(this.profileDir, "profile.json");
|
|
33
38
|
if (!existsSync(path))
|
|
@@ -50,15 +55,19 @@ export class UserProfile {
|
|
|
50
55
|
return null;
|
|
51
56
|
}
|
|
52
57
|
}
|
|
58
|
+
/** Последний собранный/загруженный снимок (null до collect/load). */
|
|
53
59
|
getInfo() {
|
|
54
60
|
return this.info;
|
|
55
61
|
}
|
|
62
|
+
/** Записывает преференс (в файл попадёт при save). */
|
|
56
63
|
setPreference(key, value) {
|
|
57
64
|
this.preferences[key] = value;
|
|
58
65
|
}
|
|
66
|
+
/** Читает преференс; undefined, если не задан. */
|
|
59
67
|
getPreference(key) {
|
|
60
68
|
return this.preferences[key];
|
|
61
69
|
}
|
|
70
|
+
/** Компактная строка профиля для промпта (собирает снимок при нужде). */
|
|
62
71
|
compress() {
|
|
63
72
|
if (!this.info)
|
|
64
73
|
this.collect();
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { readBodyError, sendJson } from "./util";
|
|
2
|
+
import { readJsonBody } from "./body";
|
|
3
|
+
import { resolveAnswer } from "./prompt-io";
|
|
4
|
+
/**
|
|
5
|
+
* Тело: `{ id: string, value: unknown }`. `value` — строка для free-text
|
|
6
|
+
* вопросов и `number[]` индексов опций для вопросов с выбором.
|
|
7
|
+
*/
|
|
8
|
+
export function handleAnswerPost(req, res, s) {
|
|
9
|
+
readJsonBody(req, s.maxBodyBytes)
|
|
10
|
+
.then((rec) => {
|
|
11
|
+
if (!rec) {
|
|
12
|
+
sendJson(res, 400, { error: "invalid body" });
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
const id = typeof rec.id === "string" ? rec.id : "";
|
|
16
|
+
if (!id) {
|
|
17
|
+
sendJson(res, 400, { error: "invalid body" });
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
if (!resolveAnswer(s, id, rec.value)) {
|
|
21
|
+
sendJson(res, 404, { error: "unknown id" });
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
sendJson(res, 200, { ok: true });
|
|
25
|
+
})
|
|
26
|
+
.catch((err) => {
|
|
27
|
+
readBodyError(req, res, err);
|
|
28
|
+
});
|
|
29
|
+
}
|