micro-models-agent 2.16.1 → 2.17.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 +28 -0
- package/dist/assets/app.js +1 -314
- package/dist/assets/hljs.js +5 -1244
- package/dist/assets/index.html +11 -0
- package/dist/assets/js/api.js +1 -28
- package/dist/assets/js/core/menus.js +1 -0
- package/dist/assets/js/core/store/feed.js +1 -0
- package/dist/assets/js/core/store/index.js +1 -0
- package/dist/assets/js/core/store/session.js +1 -0
- package/dist/assets/js/core/store/settings.js +1 -0
- package/dist/assets/js/core/store/tabs.js +1 -0
- package/dist/assets/js/dialog.js +1 -105
- package/dist/assets/js/dom.js +1 -178
- package/dist/assets/js/feed/core.js +1 -0
- package/dist/assets/js/feed/history.js +1 -0
- package/dist/assets/js/feed/plan.js +1 -0
- package/dist/assets/js/feed/question.js +3 -0
- package/dist/assets/js/feed/sidebar.js +1 -0
- package/dist/assets/js/feed/stream.js +1 -0
- package/dist/assets/js/feed/turn.js +5 -0
- package/dist/assets/js/feed.js +1 -1250
- package/dist/assets/js/format.js +2 -122
- package/dist/assets/js/i18n.js +1 -46
- package/dist/assets/js/rail.js +1 -114
- package/dist/assets/js/session/cmdk.js +1 -0
- package/dist/assets/js/session/composer.js +1 -0
- package/dist/assets/js/session/sessions-menu.js +1 -0
- package/dist/assets/js/session/tabs.js +1 -0
- package/dist/assets/js/settings/controls.js +1 -0
- package/dist/assets/js/settings/editors.js +1 -0
- package/dist/assets/js/settings/form.js +1 -0
- package/dist/assets/js/settings/modal.js +1 -0
- package/dist/assets/js/settings/panels.js +1 -0
- package/dist/assets/js/settings/providers.js +1 -0
- package/dist/assets/js/settings/search.js +1 -0
- package/dist/assets/js/settings/subdialog.js +1 -0
- package/dist/assets/js/settings/write.js +1 -0
- package/dist/assets/js/settings.js +1 -1914
- package/dist/assets/js/sidebar/core.js +1 -0
- package/dist/assets/js/sidebar/lsp.js +2 -0
- package/dist/assets/js/sidebar/map.js +1 -0
- package/dist/assets/js/sidebar/state.js +1 -0
- package/dist/assets/js/sidebar/trace.js +5 -0
- package/dist/assets/js/sidebar.js +1 -705
- package/dist/assets/js/store.js +1 -82
- package/dist/assets/js/subdialog.js +1 -0
- package/dist/assets/md.js +1 -291
- package/dist/assets/setup.js +3 -816
- package/dist/assets/smd.js +29 -1665
- package/dist/assets/styles.css +1 -1598
- package/dist/certification/certifications.json +1 -494
- package/dist/i18n/en.json +1 -1851
- package/dist/i18n/ru.json +1 -1854
- package/dist/main.js +815 -55797
- package/dist/modules/browser/bridge-server.mjs +2 -252
- package/package.json +10 -5
- package/dist/assets/js/settings-controls.js +0 -452
- package/dist/assets/js/tabs.js +0 -867
- package/dist/cli/cache-line.js +0 -31
- package/dist/cli/command-suggest.js +0 -39
- package/dist/cli/commands.js +0 -781
- package/dist/cli/completer.js +0 -268
- package/dist/cli/index.js +0 -3
- package/dist/cli/json-payload.js +0 -32
- package/dist/cli/main.js +0 -346
- package/dist/cli/onboarding-commands.js +0 -126
- package/dist/cli/plugin-commands.js +0 -38
- package/dist/cli/relaunch.js +0 -40
- package/dist/cli/repl-commands-lsp.js +0 -75
- package/dist/cli/repl-commands-skills.js +0 -105
- package/dist/cli/repl-commands.js +0 -1218
- package/dist/cli/repl.js +0 -1238
- package/dist/cli/run-result.js +0 -38
- package/dist/cli/security-commands.js +0 -177
- package/dist/cli/setup-order.js +0 -6
- package/dist/cli/setup-prompt.js +0 -50
- package/dist/cli/setup.js +0 -161
- package/dist/cli/web-command.js +0 -98
- package/dist/config/budget.js +0 -48
- package/dist/config/config.js +0 -345
- package/dist/config/defaults.js +0 -197
- package/dist/config/domains.js +0 -135
- package/dist/config/env.js +0 -46
- package/dist/config/experts.js +0 -17
- package/dist/config/index.js +0 -5
- package/dist/config/paths.js +0 -56
- package/dist/config/redact.js +0 -44
- package/dist/config/registry-extra.js +0 -334
- package/dist/config/registry.js +0 -584
- package/dist/config/security.js +0 -212
- package/dist/config/timeouts.js +0 -74
- package/dist/config/types.js +0 -1
- package/dist/config/utils.js +0 -57
- package/dist/core/agent/artifact-progress.js +0 -59
- package/dist/core/agent/artifact-resolve.js +0 -79
- package/dist/core/agent/audit-gate.js +0 -53
- package/dist/core/agent/compaction.js +0 -89
- package/dist/core/agent/constants.js +0 -90
- package/dist/core/agent/context-log.js +0 -454
- package/dist/core/agent/context-renderer.js +0 -45
- package/dist/core/agent/critic-gate.js +0 -132
- package/dist/core/agent/hallucination-gate.js +0 -197
- package/dist/core/agent/loop-state.js +0 -68
- package/dist/core/agent/prefix-monitor.js +0 -101
- package/dist/core/agent/reasoning-resolver.js +0 -65
- package/dist/core/agent/token-tracker.js +0 -99
- package/dist/core/agent/tool-batch.js +0 -312
- package/dist/core/agent/tool-images.js +0 -18
- package/dist/core/agent/tool-output.js +0 -116
- package/dist/core/agent/tool-pairing.js +0 -60
- package/dist/core/agent-moe.js +0 -248
- package/dist/core/agent.js +0 -1299
- package/dist/core/bootstrap/agent-phase.js +0 -66
- package/dist/core/bootstrap/config-phase.js +0 -86
- package/dist/core/bootstrap/modules-phase.js +0 -269
- package/dist/core/bootstrap/provider-phase.js +0 -183
- package/dist/core/bootstrap/session-phase.js +0 -204
- package/dist/core/bootstrap/state.js +0 -94
- package/dist/core/bootstrap/workspace-phase.js +0 -115
- package/dist/core/bootstrap.js +0 -112
- package/dist/core/crash-handler.js +0 -62
- package/dist/core/environment.js +0 -224
- package/dist/core/host-plan.js +0 -43
- package/dist/core/host.js +0 -23
- package/dist/core/index.js +0 -3
- package/dist/core/prompt-builder.js +0 -95
- package/dist/core/prompt-overflow.js +0 -306
- package/dist/core/session-logger.js +0 -375
- package/dist/core/system-prompt.js +0 -117
- package/dist/core/types.js +0 -1
- package/dist/core/version.js +0 -28
- package/dist/core/web-host.js +0 -34
- package/dist/core/workspace.js +0 -80
- package/dist/i18n/index.js +0 -80
- package/dist/index.js +0 -30
- package/dist/llm/cache-usage.js +0 -76
- package/dist/llm/image-utils.js +0 -138
- package/dist/llm/index.js +0 -4
- package/dist/llm/llm-errors.js +0 -50
- package/dist/llm/model-loader.js +0 -86
- package/dist/llm/models-dev.js +0 -121
- package/dist/llm/openai-compat.js +0 -756
- package/dist/llm/orchestrator.js +0 -294
- package/dist/llm/provider-budget.js +0 -68
- package/dist/llm/provider-context.js +0 -233
- package/dist/llm/provider.js +0 -9
- package/dist/llm/response.js +0 -44
- package/dist/llm/stream-state.js +0 -26
- package/dist/llm/token-counter.js +0 -83
- package/dist/llm/types.js +0 -1
- package/dist/logger/app-logger.js +0 -212
- package/dist/logger/file-log.js +0 -160
- package/dist/logger/index.js +0 -2
- package/dist/migration/backup.js +0 -56
- package/dist/migration/detect.js +0 -61
- package/dist/migration/index.js +0 -3
- package/dist/modules/artifacts/store.js +0 -62
- package/dist/modules/browser/actions.js +0 -79
- package/dist/modules/browser/bridge-client.js +0 -203
- package/dist/modules/browser/bridge-path.js +0 -10
- package/dist/modules/browser/console-buffer.js +0 -37
- package/dist/modules/browser/cookie-store.js +0 -29
- package/dist/modules/browser/driver-types.js +0 -1
- package/dist/modules/browser/driver.js +0 -186
- package/dist/modules/browser/index.js +0 -9
- package/dist/modules/browser/module.js +0 -30
- package/dist/modules/browser/session.js +0 -322
- package/dist/modules/browser/snapshot.js +0 -154
- package/dist/modules/browser/types.js +0 -13
- package/dist/modules/certification/cli.js +0 -264
- package/dist/modules/certification/fact-checker.js +0 -90
- package/dist/modules/certification/loader.js +0 -127
- package/dist/modules/certification/manifest.js +0 -231
- package/dist/modules/certification/runner.js +0 -233
- package/dist/modules/certification/scenarios.js +0 -586
- package/dist/modules/certification/syntax-scenarios.js +0 -50
- package/dist/modules/certification/types.js +0 -1
- package/dist/modules/context/chunk-query.js +0 -122
- package/dist/modules/context/fact-extractor.js +0 -170
- package/dist/modules/context/history.js +0 -15
- package/dist/modules/context/index.js +0 -2
- package/dist/modules/context/manager.js +0 -482
- package/dist/modules/critic/client.js +0 -176
- package/dist/modules/critic/evidence.js +0 -81
- package/dist/modules/critic/provider.js +0 -45
- package/dist/modules/critic/tools.js +0 -78
- package/dist/modules/critic/types.js +0 -1
- package/dist/modules/execution/audit-runners.js +0 -206
- package/dist/modules/execution/auditor.js +0 -222
- package/dist/modules/execution/execution-plugin.js +0 -478
- package/dist/modules/execution/index.js +0 -8
- package/dist/modules/execution/input-from.js +0 -70
- package/dist/modules/execution/module.js +0 -891
- package/dist/modules/execution/moe-executor.js +0 -443
- package/dist/modules/execution/plan-actions.js +0 -642
- package/dist/modules/execution/plan-checks.js +0 -35
- package/dist/modules/execution/plan-coverage.js +0 -71
- package/dist/modules/execution/plan-persister.js +0 -46
- package/dist/modules/execution/plan-steps.js +0 -31
- package/dist/modules/execution/plan-store.js +0 -274
- package/dist/modules/execution/plan-tool.js +0 -249
- package/dist/modules/execution/plan-validator.js +0 -197
- package/dist/modules/execution/planner.js +0 -103
- package/dist/modules/execution/stuck-detector.js +0 -834
- package/dist/modules/execution/tracker.js +0 -92
- package/dist/modules/execution/transient-error.js +0 -30
- package/dist/modules/execution/types.js +0 -1
- package/dist/modules/execution/verifier.js +0 -343
- package/dist/modules/execution/windows-commands.js +0 -52
- package/dist/modules/hallucination/artifact-claim.js +0 -93
- package/dist/modules/hallucination/confidence.js +0 -81
- package/dist/modules/hallucination/consistency.js +0 -37
- package/dist/modules/hallucination/detector.js +0 -57
- package/dist/modules/hallucination/factual.js +0 -205
- package/dist/modules/hallucination/index.js +0 -6
- package/dist/modules/hallucination/js-identifiers.js +0 -261
- package/dist/modules/hallucination/llm-judge.js +0 -116
- package/dist/modules/hallucination/success-claim.js +0 -59
- package/dist/modules/index.js +0 -5
- package/dist/modules/indexer/cache.js +0 -88
- package/dist/modules/indexer/index.js +0 -4
- package/dist/modules/indexer/map-command.js +0 -33
- package/dist/modules/indexer/map-select.js +0 -87
- package/dist/modules/indexer/module.js +0 -293
- package/dist/modules/indexer/project-profile.js +0 -184
- package/dist/modules/indexer/symbols.js +0 -189
- package/dist/modules/indexer/walker.js +0 -285
- package/dist/modules/lsp/check-tool.js +0 -61
- package/dist/modules/lsp/client.js +0 -406
- package/dist/modules/lsp/command.js +0 -53
- package/dist/modules/lsp/config.js +0 -189
- package/dist/modules/lsp/index.js +0 -4
- package/dist/modules/lsp/module.js +0 -295
- package/dist/modules/lsp/probe.js +0 -86
- package/dist/modules/lsp/project-root.js +0 -69
- package/dist/modules/lsp/startup-check.js +0 -154
- package/dist/modules/lsp/types.js +0 -1
- package/dist/modules/mcp/client.js +0 -445
- package/dist/modules/mcp/index.js +0 -4
- package/dist/modules/mcp/module.js +0 -295
- package/dist/modules/mcp/registry.js +0 -20
- package/dist/modules/memory/index.js +0 -2
- package/dist/modules/memory/module.js +0 -101
- package/dist/modules/memory/search.js +0 -45
- package/dist/modules/memory/store.js +0 -107
- package/dist/modules/onboarding/init.js +0 -47
- package/dist/modules/onboarding/meet.js +0 -30
- package/dist/modules/onboarding/mode.js +0 -30
- package/dist/modules/onboarding/prompts.js +0 -43
- package/dist/modules/pipelines/engine.js +0 -70
- package/dist/modules/pipelines/index.js +0 -4
- package/dist/modules/pipelines/parser.js +0 -59
- package/dist/modules/pipelines/template.js +0 -18
- package/dist/modules/plugins/builtin/lint-on-write.js +0 -377
- package/dist/modules/plugins/builtin/notify.js +0 -11
- package/dist/modules/plugins/index.js +0 -2
- package/dist/modules/plugins/loader.js +0 -85
- package/dist/modules/plugins/manager.js +0 -244
- package/dist/modules/plugins/types.js +0 -1
- package/dist/modules/pricing/index.js +0 -70
- package/dist/modules/pricing/prices.js +0 -147
- package/dist/modules/processes/detect.js +0 -34
- package/dist/modules/processes/index.js +0 -4
- package/dist/modules/processes/kill-tree.js +0 -52
- package/dist/modules/processes/registry.js +0 -294
- package/dist/modules/processes/runner.js +0 -23
- package/dist/modules/providers/cache.js +0 -23
- package/dist/modules/providers/capabilities.js +0 -28
- package/dist/modules/providers/create.js +0 -21
- package/dist/modules/providers/factory.js +0 -28
- package/dist/modules/providers/fallback.js +0 -81
- package/dist/modules/providers/health.js +0 -47
- package/dist/modules/providers/index.js +0 -6
- package/dist/modules/providers/manager.js +0 -207
- package/dist/modules/providers/naming.js +0 -15
- package/dist/modules/providers/preflight.js +0 -71
- package/dist/modules/providers/presets.js +0 -232
- package/dist/modules/providers/registry.js +0 -27
- package/dist/modules/providers/types.js +0 -1
- package/dist/modules/reasoning/policy.js +0 -42
- package/dist/modules/reasoning/probe.js +0 -116
- package/dist/modules/registry.js +0 -48
- package/dist/modules/security/audit-log.js +0 -119
- package/dist/modules/security/audit-notifier.js +0 -267
- package/dist/modules/security/command-validator.js +0 -272
- package/dist/modules/security/content-scanner.js +0 -53
- package/dist/modules/security/data-sanitizer.js +0 -82
- package/dist/modules/security/encryption.js +0 -216
- package/dist/modules/security/index.js +0 -15
- package/dist/modules/security/network-validator.js +0 -236
- package/dist/modules/security/path-validator.js +0 -261
- package/dist/modules/security/protected-paths.js +0 -73
- package/dist/modules/security/rate-limiter.js +0 -98
- package/dist/modules/security/security-policies.js +0 -499
- package/dist/modules/security/session-encryption.js +0 -182
- package/dist/modules/security/session-isolation.js +0 -75
- package/dist/modules/session/auto-name.js +0 -63
- package/dist/modules/session/export.js +0 -98
- package/dist/modules/session/index.js +0 -4
- package/dist/modules/session/manager.js +0 -234
- package/dist/modules/session/module.js +0 -33
- package/dist/modules/session/store.js +0 -295
- package/dist/modules/session/types.js +0 -1
- package/dist/modules/setup/answers.js +0 -35
- package/dist/modules/setup/apply.js +0 -79
- package/dist/modules/setup/discovery.js +0 -72
- package/dist/modules/skills/index.js +0 -3
- package/dist/modules/skills/loader.js +0 -86
- package/dist/modules/skills/matcher.js +0 -27
- package/dist/modules/skills/module.js +0 -141
- package/dist/modules/types.js +0 -1
- package/dist/modules/updater/changelog-reader.js +0 -93
- package/dist/modules/updater/checker.js +0 -101
- package/dist/modules/updater/dev-detect.js +0 -18
- package/dist/modules/updater/index.js +0 -4
- package/dist/modules/updater/module.js +0 -129
- package/dist/modules/user-profile/compressor.js +0 -18
- package/dist/modules/user-profile/index.js +0 -2
- package/dist/modules/user-profile/profile.js +0 -77
- package/dist/modules/webui/answer-api.js +0 -29
- package/dist/modules/webui/assets.js +0 -50
- package/dist/modules/webui/body.js +0 -21
- package/dist/modules/webui/commands-settings.js +0 -209
- package/dist/modules/webui/commands-shared.js +0 -60
- package/dist/modules/webui/commands.js +0 -174
- package/dist/modules/webui/config-api.js +0 -134
- package/dist/modules/webui/file-api.js +0 -202
- package/dist/modules/webui/forward.js +0 -159
- package/dist/modules/webui/history-api.js +0 -112
- package/dist/modules/webui/host-state.js +0 -60
- package/dist/modules/webui/i18n-api.js +0 -9
- package/dist/modules/webui/limits.js +0 -6
- package/dist/modules/webui/log-forward.js +0 -20
- package/dist/modules/webui/memory-commands.js +0 -64
- package/dist/modules/webui/model-cache.js +0 -18
- package/dist/modules/webui/module.js +0 -148
- package/dist/modules/webui/persist.js +0 -24
- package/dist/modules/webui/prompt-io.js +0 -97
- package/dist/modules/webui/providers-api.js +0 -337
- package/dist/modules/webui/providers-view.js +0 -56
- package/dist/modules/webui/reasoning-view.js +0 -15
- package/dist/modules/webui/run-agent.js +0 -81
- package/dist/modules/webui/server.js +0 -284
- package/dist/modules/webui/sessions-api.js +0 -178
- package/dist/modules/webui/settings-api.js +0 -561
- package/dist/modules/webui/setup-api.js +0 -201
- package/dist/modules/webui/state.js +0 -74
- package/dist/modules/webui/util.js +0 -261
- package/dist/output/bus.js +0 -37
- package/dist/output/channel.js +0 -285
- package/dist/output/format.js +0 -15
- package/dist/output/index.js +0 -8
- package/dist/output/json-sink.js +0 -24
- package/dist/output/machine.js +0 -9
- package/dist/output/session-sink.js +0 -27
- package/dist/output/types.js +0 -1
- package/dist/tools/approve.js +0 -41
- package/dist/tools/attach-image.js +0 -102
- package/dist/tools/auto-fixer.js +0 -180
- package/dist/tools/auto-tags.js +0 -83
- package/dist/tools/bash.js +0 -649
- package/dist/tools/browser.js +0 -160
- package/dist/tools/chunk-query.js +0 -112
- package/dist/tools/create-dir.js +0 -60
- package/dist/tools/delete-file.js +0 -67
- package/dist/tools/download-file.js +0 -142
- package/dist/tools/edit-file.js +0 -136
- package/dist/tools/enable-tools.js +0 -59
- package/dist/tools/executor.js +0 -304
- package/dist/tools/file-info.js +0 -49
- package/dist/tools/filter-tools.js +0 -17
- package/dist/tools/glob-tool.js +0 -37
- package/dist/tools/grep-tool.js +0 -142
- package/dist/tools/hidden-tools-block.js +0 -37
- package/dist/tools/index.js +0 -88
- package/dist/tools/list-dir.js +0 -53
- package/dist/tools/load-skill.js +0 -51
- package/dist/tools/mcp-call.js +0 -80
- package/dist/tools/meet-done.js +0 -14
- package/dist/tools/move-file.js +0 -94
- package/dist/tools/path-utils.js +0 -108
- package/dist/tools/pipeline-run.js +0 -167
- package/dist/tools/preview.js +0 -2
- package/dist/tools/process-ids.js +0 -31
- package/dist/tools/process-kill.js +0 -33
- package/dist/tools/process-list.js +0 -38
- package/dist/tools/process-log.js +0 -49
- package/dist/tools/process-wait.js +0 -63
- package/dist/tools/prompt-io.js +0 -29
- package/dist/tools/question.js +0 -288
- package/dist/tools/read-file.js +0 -200
- package/dist/tools/recall.js +0 -124
- package/dist/tools/registry.js +0 -48
- package/dist/tools/remember.js +0 -72
- package/dist/tools/scope-check.js +0 -32
- package/dist/tools/scope-request.js +0 -96
- package/dist/tools/search-history.js +0 -88
- package/dist/tools/session-info.js +0 -46
- package/dist/tools/set-thinking.js +0 -75
- package/dist/tools/subagent.js +0 -257
- package/dist/tools/syntax-validator.js +0 -379
- package/dist/tools/types.js +0 -1
- package/dist/tools/user-input.js +0 -134
- package/dist/tools/web-browse.js +0 -93
- package/dist/tools/web-fetch.js +0 -108
- package/dist/tools/web-search.js +0 -107
- package/dist/tools/write-file.js +0 -128
- package/dist/ui/action-log.js +0 -13
- package/dist/ui/box.js +0 -78
- package/dist/ui/colors.js +0 -6
- package/dist/ui/diff.js +0 -189
- package/dist/ui/index.js +0 -7
- package/dist/ui/line-editor.js +0 -1083
- package/dist/ui/line-math.js +0 -98
- package/dist/ui/md-formatter.js +0 -322
- package/dist/ui/output.js +0 -19
- package/dist/ui/plan-view.js +0 -109
- package/dist/ui/renderer.js +0 -589
- package/dist/ui/spinner.js +0 -91
- package/dist/ui/table.js +0 -142
- package/dist/ui/theme.js +0 -143
- package/dist/utils/child-run.js +0 -20
- package/dist/utils/error.js +0 -4
- package/dist/utils/index.js +0 -6
- package/dist/utils/retry.js +0 -17
- package/dist/utils/sleep.js +0 -23
- package/dist/utils/truncate.js +0 -9
|
@@ -1,443 +0,0 @@
|
|
|
1
|
-
import { getExpertConfig } from "../../config/experts";
|
|
2
|
-
import { filterToolsByTags } from "../../tools/filter-tools";
|
|
3
|
-
import { StuckDetector } from "./stuck-detector";
|
|
4
|
-
import { t } from "../../i18n/index";
|
|
5
|
-
import { ArtifactStore } from "../artifacts/store";
|
|
6
|
-
import { buildInputSection } from "./input-from";
|
|
7
|
-
import { parseScopeRequest } from "../../tools/scope-request";
|
|
8
|
-
import { isTransientError } from "./transient-error";
|
|
9
|
-
import { sleep } from "../../utils/sleep";
|
|
10
|
-
import { backoffDelay } from "../../utils/retry";
|
|
11
|
-
import { errMsg } from "../../utils";
|
|
12
|
-
/** Макс. циклов расширения scope на подзадачу. */
|
|
13
|
-
export const MAX_SCOPE_EXPANSION_CYCLES = 2;
|
|
14
|
-
/** Униформная ошибка прерывания для цикла агента (err.name === "AbortError"). */
|
|
15
|
-
export function newAbortError() {
|
|
16
|
-
const e = new Error(t("tool.aborted", { name: "MoE execution" }));
|
|
17
|
-
e.name = "AbortError";
|
|
18
|
-
return e;
|
|
19
|
-
}
|
|
20
|
-
/**
|
|
21
|
-
* Сборка детального сообщения об ошибке подзадачи: базовый текст + подсказки
|
|
22
|
-
* stuck-детектора + совет про load_skill. Дублировался 2× в executeSubtask.
|
|
23
|
-
*/
|
|
24
|
-
function buildErrorDetail(stuckDetector, baseError, lastError) {
|
|
25
|
-
const hints = stuckDetector.getActionableHints();
|
|
26
|
-
let errorDetail = baseError;
|
|
27
|
-
if (hints.length > 0) {
|
|
28
|
-
errorDetail += `\n\n${t("exec.hints", { hints: hints.map((h) => `- ${h}`).join("\n") })}`;
|
|
29
|
-
}
|
|
30
|
-
if (lastError) {
|
|
31
|
-
errorDetail +=
|
|
32
|
-
`\n\n${t("exec.skills_hint")}`;
|
|
33
|
-
}
|
|
34
|
-
return errorDetail;
|
|
35
|
-
}
|
|
36
|
-
/** Топосорт подзадач в волны готовых (зависимости удовлетворены). Пусто при цикле. */
|
|
37
|
-
export function topologicalSort(subtasks) {
|
|
38
|
-
const sorted = [];
|
|
39
|
-
const remaining = new Set(subtasks.map((s) => s.id));
|
|
40
|
-
const subtaskMap = new Map(subtasks.map((s) => [s.id, s]));
|
|
41
|
-
while (remaining.size > 0) {
|
|
42
|
-
const ready = [];
|
|
43
|
-
for (const id of remaining) {
|
|
44
|
-
const sub = subtaskMap.get(id);
|
|
45
|
-
const depsSatisfied = (sub.depends_on || []).every((d) => !remaining.has(d));
|
|
46
|
-
if (depsSatisfied) {
|
|
47
|
-
ready.push(sub);
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
if (ready.length === 0) {
|
|
51
|
-
break;
|
|
52
|
-
}
|
|
53
|
-
sorted.push(ready);
|
|
54
|
-
for (const r of ready) {
|
|
55
|
-
remaining.delete(r.id);
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
return sorted;
|
|
59
|
-
}
|
|
60
|
-
async function executeSubtask(subtask, deps, depResults, sharedContext, onUsage) {
|
|
61
|
-
const startTime = Date.now();
|
|
62
|
-
const expertConfig = getExpertConfig(deps.config, subtask.expert_tag);
|
|
63
|
-
if (!expertConfig) {
|
|
64
|
-
return {
|
|
65
|
-
subtaskId: subtask.id,
|
|
66
|
-
success: false,
|
|
67
|
-
summary: `Unknown expert_tag: ${subtask.expert_tag}`,
|
|
68
|
-
result: "",
|
|
69
|
-
error: `No expert config found for tag "${subtask.expert_tag}"`,
|
|
70
|
-
durationMs: Date.now() - startTime,
|
|
71
|
-
};
|
|
72
|
-
}
|
|
73
|
-
const subagentTool = deps.toolRegistry.get("subagent");
|
|
74
|
-
if (!subagentTool) {
|
|
75
|
-
return {
|
|
76
|
-
subtaskId: subtask.id,
|
|
77
|
-
success: false,
|
|
78
|
-
summary: "Subagent tool not registered",
|
|
79
|
-
result: "",
|
|
80
|
-
error: "Subagent tool not found in registry",
|
|
81
|
-
durationMs: Date.now() - startTime,
|
|
82
|
-
};
|
|
83
|
-
}
|
|
84
|
-
const allTools = deps.toolRegistry.getAll();
|
|
85
|
-
const filteredTools = filterToolsByTags(allTools, expertConfig.tool_tags);
|
|
86
|
-
if (filteredTools.length === 0) {
|
|
87
|
-
return {
|
|
88
|
-
subtaskId: subtask.id,
|
|
89
|
-
success: false,
|
|
90
|
-
summary: `No tools available for expert "${subtask.expert_tag}" (tags: ${expertConfig.tool_tags.join(", ")})`,
|
|
91
|
-
result: "",
|
|
92
|
-
error: "No matching tools",
|
|
93
|
-
durationMs: Date.now() - startTime,
|
|
94
|
-
};
|
|
95
|
-
}
|
|
96
|
-
const maxAttempts = expertConfig.max_attempts || 3;
|
|
97
|
-
let lastError = "";
|
|
98
|
-
const stuckDetector = new StuckDetector(maxAttempts * 2, maxAttempts);
|
|
99
|
-
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
100
|
-
// Стабильный id между попытками: передача `attempt` ресетила состояние
|
|
101
|
-
// детектора на каждом ретрае (новый stepId → resetStepState), ошибки тулов
|
|
102
|
-
// не накапливались и exhausted-retries диагностика всегда пустовала.
|
|
103
|
-
stuckDetector.recordIteration(1);
|
|
104
|
-
try {
|
|
105
|
-
const allowedFiles = subtask.allowed_files || [];
|
|
106
|
-
const readOnlyFiles = subtask.read_only_files || [];
|
|
107
|
-
const scopePrompt = allowedFiles.length > 0 || readOnlyFiles.length > 0
|
|
108
|
-
? `\n\nAllowed files to write: ${allowedFiles.join(", ") || "(none)"}\nRead-only files: ${readOnlyFiles.join(", ") || "(none)"}`
|
|
109
|
-
: "";
|
|
110
|
-
const sharedRules = sharedContext?.rules;
|
|
111
|
-
const sharedContextPrompt = sharedRules && sharedRules.length > 0
|
|
112
|
-
? `\n\nShared rules:\n${sharedRules.map((r) => `- ${r}`).join("\n")}`
|
|
113
|
-
: "";
|
|
114
|
-
const taskPrompt = `${subtask.description}\n\nExpected output: ${subtask.expected_output}\nSuccess criteria:\n${(subtask.success_criteria || []).map((c) => `- ${c}`).join("\n")}${scopePrompt}${sharedContextPrompt}${buildInputSection(subtask, depResults, deps.config.subagent?.maxSummaryChars ?? 2000)}`;
|
|
115
|
-
const subagentArgs = {
|
|
116
|
-
task: taskPrompt,
|
|
117
|
-
allowed_files: allowedFiles,
|
|
118
|
-
read_only_files: readOnlyFiles,
|
|
119
|
-
tool_tags: expertConfig.tool_tags,
|
|
120
|
-
};
|
|
121
|
-
if (deps.config.subagent?.resultMode === "file") {
|
|
122
|
-
subagentArgs.result_mode = "file";
|
|
123
|
-
subagentArgs.artifact_name = subtask.id;
|
|
124
|
-
}
|
|
125
|
-
const subagentStart = Date.now();
|
|
126
|
-
deps.onTool?.({
|
|
127
|
-
type: "start",
|
|
128
|
-
tool: "subagent",
|
|
129
|
-
args: { subtask: subtask.id, description: subtask.description },
|
|
130
|
-
});
|
|
131
|
-
const result = await subagentTool.handler({
|
|
132
|
-
config: deps.config,
|
|
133
|
-
baseDir: deps.baseDir,
|
|
134
|
-
logger: deps.logger,
|
|
135
|
-
llmProvider: deps.llmProvider,
|
|
136
|
-
toolExecutor: deps.toolExecutor,
|
|
137
|
-
sessionId: deps.sessionId || "moe",
|
|
138
|
-
}, subagentArgs);
|
|
139
|
-
if (result.usage) {
|
|
140
|
-
onUsage?.(subtask.expert_tag, result.usage);
|
|
141
|
-
}
|
|
142
|
-
deps.onTool?.({
|
|
143
|
-
type: "end",
|
|
144
|
-
tool: "subagent",
|
|
145
|
-
args: { subtask: subtask.id, description: subtask.description },
|
|
146
|
-
duration: Date.now() - subagentStart,
|
|
147
|
-
error: !result.success,
|
|
148
|
-
});
|
|
149
|
-
if (result.success) {
|
|
150
|
-
if (deps.config.subagent?.resultMode === "file") {
|
|
151
|
-
// Структурное поле из тула subagent — языконезависимо (парсинг
|
|
152
|
-
// локализованной строки вывода ломался под ru).
|
|
153
|
-
const abs = result.artifactPath ?? "";
|
|
154
|
-
if (abs) {
|
|
155
|
-
const store = new ArtifactStore({
|
|
156
|
-
baseDir: deps.baseDir,
|
|
157
|
-
sessionId: deps.sessionId || "moe",
|
|
158
|
-
});
|
|
159
|
-
const full = store.read(abs.split(/[\\/]/).pop() || ""); // только имя файла, в jail root
|
|
160
|
-
deps.logger.debug(`MoE subtask "${subtask.id}" attached artifact ${abs} (${full?.length ?? 0} chars) to Router`);
|
|
161
|
-
return {
|
|
162
|
-
subtaskId: subtask.id,
|
|
163
|
-
success: true,
|
|
164
|
-
summary: `Artifact: ${abs}`,
|
|
165
|
-
result: full ?? result.output,
|
|
166
|
-
durationMs: Date.now() - startTime,
|
|
167
|
-
};
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
return {
|
|
171
|
-
subtaskId: subtask.id,
|
|
172
|
-
success: true,
|
|
173
|
-
summary: `Completed: ${subtask.id}`,
|
|
174
|
-
result: result.output,
|
|
175
|
-
durationMs: Date.now() - startTime,
|
|
176
|
-
};
|
|
177
|
-
}
|
|
178
|
-
lastError = result.output;
|
|
179
|
-
stuckDetector.recordToolError("subagent", result.output);
|
|
180
|
-
// Инжектим actionable-хинты в промпт следующего ретрая
|
|
181
|
-
if (attempt < maxAttempts) {
|
|
182
|
-
const hints = stuckDetector.getActionableHints();
|
|
183
|
-
const recovery = stuckDetector.getRecoveryMessage();
|
|
184
|
-
if (hints.length > 0) {
|
|
185
|
-
deps.logger.debug(`Hint for subtask "${subtask.id}" retry: ${hints.join("; ")}`);
|
|
186
|
-
}
|
|
187
|
-
if (recovery) {
|
|
188
|
-
deps.logger.debug(`Recovery for subtask "${subtask.id}": ${recovery}`);
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
if (isTransientError(lastError) && attempt < maxAttempts) {
|
|
192
|
-
const delay = backoffDelay(attempt - 1, 1000, 8000);
|
|
193
|
-
deps.logger.debug(`Retrying subtask "${subtask.id}" (attempt ${attempt}/${maxAttempts}) after ${delay}ms: transient error`);
|
|
194
|
-
await sleep(delay, deps.signal);
|
|
195
|
-
continue;
|
|
196
|
-
}
|
|
197
|
-
// Нетранзиентный фейл — собираем детальную ошибку с хинтами
|
|
198
|
-
const errorDetail = buildErrorDetail(stuckDetector, result.output, lastError);
|
|
199
|
-
return {
|
|
200
|
-
subtaskId: subtask.id,
|
|
201
|
-
success: false,
|
|
202
|
-
summary: `Failed: ${subtask.id}`,
|
|
203
|
-
result: result.output,
|
|
204
|
-
error: errorDetail,
|
|
205
|
-
scopeRequest: parseScopeRequest(result.output) ?? undefined,
|
|
206
|
-
durationMs: Date.now() - startTime,
|
|
207
|
-
};
|
|
208
|
-
}
|
|
209
|
-
catch (e) {
|
|
210
|
-
lastError = e.message;
|
|
211
|
-
stuckDetector.recordToolError("subagent", e.message);
|
|
212
|
-
// Прерывание юзера (Esc) — НИКОГДА не транзиент: пробрасываем сразу, чтобы
|
|
213
|
-
// барьер волны и цикл агента увидели abort вместо ретрая.
|
|
214
|
-
if (e?.name === "AbortError") {
|
|
215
|
-
throw e;
|
|
216
|
-
}
|
|
217
|
-
if (isTransientError(lastError) && attempt < maxAttempts) {
|
|
218
|
-
const delay = backoffDelay(attempt - 1, 1000, 8000);
|
|
219
|
-
deps.logger.debug(`Retrying subtask "${subtask.id}" (attempt ${attempt}/${maxAttempts}) after ${delay}ms: ${lastError}`);
|
|
220
|
-
await sleep(delay, deps.signal);
|
|
221
|
-
continue;
|
|
222
|
-
}
|
|
223
|
-
const errorDetail = buildErrorDetail(stuckDetector, e.message, lastError);
|
|
224
|
-
return {
|
|
225
|
-
subtaskId: subtask.id,
|
|
226
|
-
success: false,
|
|
227
|
-
summary: `Error: ${subtask.id}`,
|
|
228
|
-
result: "",
|
|
229
|
-
error: errorDetail,
|
|
230
|
-
durationMs: Date.now() - startTime,
|
|
231
|
-
};
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
// Ретраи исчерпаны — собираем итоговый фейл
|
|
235
|
-
const stuckReason = stuckDetector.getStuckReason();
|
|
236
|
-
let finalError = lastError;
|
|
237
|
-
if (stuckReason) {
|
|
238
|
-
finalError += `\n${stuckReason}`;
|
|
239
|
-
}
|
|
240
|
-
return {
|
|
241
|
-
subtaskId: subtask.id,
|
|
242
|
-
success: false,
|
|
243
|
-
summary: `Failed after ${maxAttempts} attempts: ${subtask.id}`,
|
|
244
|
-
result: "",
|
|
245
|
-
error: finalError,
|
|
246
|
-
durationMs: Date.now() - startTime,
|
|
247
|
-
};
|
|
248
|
-
}
|
|
249
|
-
/** Исполнитель MoE-плана: волны, параллелизм, scope-ретраи, usage по тегам. */
|
|
250
|
-
export class MoEExecutor {
|
|
251
|
-
deps;
|
|
252
|
-
usageByTag = new Map();
|
|
253
|
-
constructor(deps) {
|
|
254
|
-
this.deps = deps;
|
|
255
|
-
}
|
|
256
|
-
/** Агрегированный usage токенов по expert_tag всех исполненных подзадач. */
|
|
257
|
-
getUsageByTag() {
|
|
258
|
-
return Object.fromEntries(this.usageByTag);
|
|
259
|
-
}
|
|
260
|
-
recordUsage(expertTag, usage) {
|
|
261
|
-
const entry = this.usageByTag.get(expertTag) ?? {
|
|
262
|
-
promptTokens: 0,
|
|
263
|
-
completionTokens: 0,
|
|
264
|
-
totalTokens: 0,
|
|
265
|
-
subtasks: 0,
|
|
266
|
-
};
|
|
267
|
-
entry.promptTokens += usage.promptTokens;
|
|
268
|
-
entry.completionTokens += usage.completionTokens;
|
|
269
|
-
entry.totalTokens += usage.totalTokens;
|
|
270
|
-
entry.subtasks += 1;
|
|
271
|
-
this.usageByTag.set(expertTag, entry);
|
|
272
|
-
}
|
|
273
|
-
/**
|
|
274
|
-
* Исполняет план волнами топосорта с капом параллелизма. Поддерживает
|
|
275
|
-
* селективный re-plan (skipIds + carriedResults). Abort (Esc) останавливает
|
|
276
|
-
* на барьере волны.
|
|
277
|
-
* @param opts.skipIds подзадачи, НЕ переисполняемые — их прошлые успешные
|
|
278
|
-
* результаты инжектятся как carriedResults, чтобы резолвились зависимости
|
|
279
|
-
* переисполняемых.
|
|
280
|
-
*/
|
|
281
|
-
async executePlan(plan, opts) {
|
|
282
|
-
const skipIds = opts?.skipIds;
|
|
283
|
-
const carried = opts?.carriedResults ?? [];
|
|
284
|
-
const results = carried.map((r) => ({ ...r }));
|
|
285
|
-
const errors = [];
|
|
286
|
-
const warnings = [];
|
|
287
|
-
const subtasks = skipIds && skipIds.size > 0 ? plan.subtasks.filter((s) => !skipIds.has(s.id)) : plan.subtasks;
|
|
288
|
-
// Селективный re-plan, где все подзадачи уже успешны: исполнять нечего.
|
|
289
|
-
// Раньше проваливалось в topologicalSort([]) и репортилось bogus
|
|
290
|
-
// "possible cycle", сжигая оставшиеся re-plan циклы.
|
|
291
|
-
if (subtasks.length === 0) {
|
|
292
|
-
return { success: true, results, errors: [], warnings };
|
|
293
|
-
}
|
|
294
|
-
const waves = topologicalSort(subtasks);
|
|
295
|
-
if (waves.length === 0) {
|
|
296
|
-
return {
|
|
297
|
-
success: false,
|
|
298
|
-
results: [],
|
|
299
|
-
errors: ["Failed to topologically sort subtasks (possible cycle)"],
|
|
300
|
-
warnings: [],
|
|
301
|
-
};
|
|
302
|
-
}
|
|
303
|
-
// Проверяем, что все подзадачи вошли — ловим молчаливые дропы от висячих зависимостей
|
|
304
|
-
const sortedCount = waves.flat().length;
|
|
305
|
-
if (sortedCount < subtasks.length) {
|
|
306
|
-
const missing = subtasks
|
|
307
|
-
.filter((s) => !waves.flat().some((w) => w.id === s.id))
|
|
308
|
-
.map((s) => s.id);
|
|
309
|
-
return {
|
|
310
|
-
success: false,
|
|
311
|
-
results: [],
|
|
312
|
-
errors: [
|
|
313
|
-
`Missing subtasks after topological sort: ${missing.join(", ")} (dangling or invalid depends_on)`,
|
|
314
|
-
],
|
|
315
|
-
warnings: [],
|
|
316
|
-
};
|
|
317
|
-
}
|
|
318
|
-
for (let waveIdx = 0; waveIdx < waves.length; waveIdx++) {
|
|
319
|
-
const wave = waves[waveIdx];
|
|
320
|
-
const depResults = new Map(results.map((r) => [r.subtaskId, r]));
|
|
321
|
-
// Кап конкурентных подзадач на волну (rateLimits.maxParallelTasks, по умолчанию 5).
|
|
322
|
-
const maxParallel = this.deps.config?.security?.rateLimits?.maxParallelTasks ?? 5;
|
|
323
|
-
let active = 0;
|
|
324
|
-
const waiters = [];
|
|
325
|
-
const acquire = () => active < maxParallel
|
|
326
|
-
? Promise.resolve(active++)
|
|
327
|
-
: new Promise((resolve) => waiters.push(() => resolve(active++)));
|
|
328
|
-
const release = () => {
|
|
329
|
-
active--;
|
|
330
|
-
const next = waiters.shift();
|
|
331
|
-
if (next)
|
|
332
|
-
next();
|
|
333
|
-
};
|
|
334
|
-
const wavePromises = wave.map(async (subtask) => {
|
|
335
|
-
await acquire();
|
|
336
|
-
if (this.deps.signal?.aborted) {
|
|
337
|
-
// Проверяем после acquire: подзадачи, queued за летящей, не стартуют,
|
|
338
|
-
// если Esc нажат посреди волны.
|
|
339
|
-
release();
|
|
340
|
-
throw newAbortError();
|
|
341
|
-
}
|
|
342
|
-
this.deps.onEvent?.({
|
|
343
|
-
type: "moe_subtask",
|
|
344
|
-
data: { subtaskId: subtask.id, expertTag: subtask.expert_tag, status: "start" },
|
|
345
|
-
});
|
|
346
|
-
let result;
|
|
347
|
-
try {
|
|
348
|
-
result = await executeSubtask(subtask, this.deps, depResults, plan.shared_context, (tag, usage) => this.recordUsage(tag, usage));
|
|
349
|
-
if (!result.success && this.deps.onScopeRequest) {
|
|
350
|
-
result = await this.resolveScopeAndRetry(subtask, result, depResults, plan.shared_context);
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
catch (e) {
|
|
354
|
-
if (e instanceof Error && e.name === "AbortError")
|
|
355
|
-
throw e;
|
|
356
|
-
result = {
|
|
357
|
-
subtaskId: subtask.id,
|
|
358
|
-
success: false,
|
|
359
|
-
summary: `Unhandled error: ${subtask.id}`,
|
|
360
|
-
result: "",
|
|
361
|
-
error: errMsg(e),
|
|
362
|
-
durationMs: 0,
|
|
363
|
-
};
|
|
364
|
-
}
|
|
365
|
-
finally {
|
|
366
|
-
release();
|
|
367
|
-
}
|
|
368
|
-
this.deps.onEvent?.({
|
|
369
|
-
type: "moe_subtask",
|
|
370
|
-
data: {
|
|
371
|
-
subtaskId: subtask.id,
|
|
372
|
-
expertTag: subtask.expert_tag,
|
|
373
|
-
status: result.success ? "done" : "failed",
|
|
374
|
-
durationMs: result.durationMs,
|
|
375
|
-
summary: result.summary,
|
|
376
|
-
},
|
|
377
|
-
});
|
|
378
|
-
results.push(result);
|
|
379
|
-
return result;
|
|
380
|
-
});
|
|
381
|
-
await Promise.all(wavePromises);
|
|
382
|
-
// Барьер волны: при прерывании встаём перед следующей волной, а не
|
|
383
|
-
// стартуем новых сабагентов после Esc.
|
|
384
|
-
if (this.deps.signal?.aborted)
|
|
385
|
-
throw newAbortError();
|
|
386
|
-
}
|
|
387
|
-
const failed = results.filter((r) => !r.success);
|
|
388
|
-
if (failed.length > 0) {
|
|
389
|
-
errors.push(...failed.map((f) => `[${f.subtaskId}] ${f.error || f.summary}`));
|
|
390
|
-
}
|
|
391
|
-
return {
|
|
392
|
-
success: failed.length === 0,
|
|
393
|
-
results,
|
|
394
|
-
errors,
|
|
395
|
-
warnings,
|
|
396
|
-
...(carried.length > 0 ? { carriedSubtaskIds: carried.map((r) => r.subtaskId) } : {}),
|
|
397
|
-
};
|
|
398
|
-
}
|
|
399
|
-
/**
|
|
400
|
-
* Scope-протокол (спек §Post-MVP): <scope-request> упавшего сабагента идёт
|
|
401
|
-
* роутеру; approve расширяет scope подзадачи и переисполняет ТОЛЬКО её,
|
|
402
|
-
* reject фейлит её (чинится полным re-plan). Расширение капировано, не
|
|
403
|
-
* автоматически.
|
|
404
|
-
*/
|
|
405
|
-
async resolveScopeAndRetry(subtask, failed, depResults, sharedContext) {
|
|
406
|
-
let current = failed;
|
|
407
|
-
// Расширение scope применяем к ЛОКАЛЬНОЙ копии: оригинальный объект плана
|
|
408
|
-
// остаётся неизменным для verifier/re-plan (иначе верификация видит scope,
|
|
409
|
-
// которого не было в утверждённом плане).
|
|
410
|
-
let scoped = subtask;
|
|
411
|
-
for (let cycle = 0; cycle < MAX_SCOPE_EXPANSION_CYCLES; cycle++) {
|
|
412
|
-
const req = current.scopeRequest;
|
|
413
|
-
if (!req)
|
|
414
|
-
return current;
|
|
415
|
-
const decision = await this.deps.onScopeRequest(subtask.id, req);
|
|
416
|
-
if (decision.action !== "approve") {
|
|
417
|
-
this.deps.logger.info?.(`MoE: router rejected scope request for "${subtask.id}" (${req.reason})`);
|
|
418
|
-
return {
|
|
419
|
-
...current,
|
|
420
|
-
error: `${current.error}\nRouter rejected scope request for files: ${req.files.join(", ")}`,
|
|
421
|
-
};
|
|
422
|
-
}
|
|
423
|
-
scoped = {
|
|
424
|
-
...scoped,
|
|
425
|
-
allowed_files: uniq([...(scoped.allowed_files || []), ...decision.write]),
|
|
426
|
-
read_only_files: uniq([...(scoped.read_only_files || []), ...decision.read]),
|
|
427
|
-
};
|
|
428
|
-
this.deps.logger.info?.(`MoE: approved scope expansion for "${subtask.id}": +write [${decision.write.join(", ")}] +read [${decision.read.join(", ")}]`);
|
|
429
|
-
const retry = await executeSubtask(scoped, this.deps, depResults, sharedContext, (tag, usage) => this.recordUsage(tag, usage));
|
|
430
|
-
if (retry.success || !retry.scopeRequest)
|
|
431
|
-
return retry;
|
|
432
|
-
current = retry;
|
|
433
|
-
}
|
|
434
|
-
return {
|
|
435
|
-
...current,
|
|
436
|
-
summary: `Failed after ${MAX_SCOPE_EXPANSION_CYCLES} scope expansions: ${subtask.id}`,
|
|
437
|
-
error: `${current.error}\nScope expansion limit (${MAX_SCOPE_EXPANSION_CYCLES}) reached`,
|
|
438
|
-
};
|
|
439
|
-
}
|
|
440
|
-
}
|
|
441
|
-
function uniq(arr) {
|
|
442
|
-
return Array.from(new Set(arr));
|
|
443
|
-
}
|