@aiwg/cli 2026.10.1 → 2026.10.4
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/README.md +2 -2
- package/agentic/code/providers/capability-matrix.yaml +270 -0
- package/agentic/code/providers/model-capabilities.v1.json +3183 -24
- package/bin/aiwg.mjs +135 -47
- package/dist/src/a2a/codecs.js +9 -0
- package/dist/src/agents/grounding/knowledge-base.js +54 -6
- package/dist/src/artifacts/browser-export.js +6 -0
- package/dist/src/artifacts/checksum-manifest.js +1 -0
- package/dist/src/artifacts/citation-parser.js +173 -92
- package/dist/src/artifacts/citation-table-schema.js +59 -0
- package/dist/src/artifacts/cli.js +57 -18
- package/dist/src/artifacts/corpus-tools/snapshot.js +1 -1
- package/dist/src/artifacts/corpus-tools/vision-extract.js +107 -15
- package/dist/src/artifacts/corpus-views/ref-parser.js +3 -1
- package/dist/src/artifacts/corpus-views/renderers.js +3 -3
- package/dist/src/artifacts/discovery-eval.js +6 -4
- package/dist/src/artifacts/fortemi-core-query-adapter.js +36 -3
- package/dist/src/artifacts/fortemi-core-sync.js +166 -40
- package/dist/src/artifacts/framework-capabilities.js +171 -0
- package/dist/src/artifacts/framework-index-paths.js +73 -0
- package/dist/src/artifacts/framework-source-digest.js +75 -0
- package/dist/src/artifacts/index-builder.js +285 -131
- package/dist/src/artifacts/index-files.js +132 -18
- package/dist/src/artifacts/index-preparation.js +65 -0
- package/dist/src/artifacts/index-refresh.js +576 -0
- package/dist/src/artifacts/index-worker.mjs +9 -0
- package/dist/src/artifacts/prebuilt-build-lock.js +47 -4
- package/dist/src/artifacts/prebuilt-deployment.js +46 -0
- package/dist/src/artifacts/prebuilt-relations.js +80 -0
- package/dist/src/artifacts/query-engine.js +410 -88
- package/dist/src/artifacts/relations-builder.js +168 -0
- package/dist/src/artifacts/relations.js +148 -0
- package/dist/src/artifacts/repair.js +9 -4
- package/dist/src/artifacts/stats.js +5 -2
- package/dist/src/artifacts/types.js +30 -10
- package/dist/src/artifacts/watcher.js +1 -1
- package/dist/src/auth/credential-store.js +2 -1
- package/dist/src/catalog/cli.d.mts +5 -0
- package/dist/src/channel/manager.d.mts +20 -0
- package/dist/src/channel/manager.mjs +78 -12
- package/dist/src/cli/child-process-result.js +40 -0
- package/dist/src/cli/command-log.js +2 -3
- package/dist/src/cli/env.js +38 -16
- package/dist/src/cli/flag-parsing.js +236 -0
- package/dist/src/cli/handlers/artifacts.js +3 -0
- package/dist/src/cli/handlers/auth.js +3 -0
- package/dist/src/cli/handlers/decision.js +218 -5
- package/dist/src/cli/handlers/diagnose.js +13 -5
- package/dist/src/cli/handlers/effect.js +4 -2
- package/dist/src/cli/handlers/evidence.js +3 -0
- package/dist/src/cli/handlers/feedback.js +14 -5
- package/dist/src/cli/handlers/help.js +1 -0
- package/dist/src/cli/handlers/index.js +3 -1
- package/dist/src/cli/handlers/init.js +248 -30
- package/dist/src/cli/handlers/lint.js +5 -0
- package/dist/src/cli/handlers/mc.js +23 -0
- package/dist/src/cli/handlers/models.js +169 -10
- package/dist/src/cli/handlers/ralph-launcher.js +2 -0
- package/dist/src/cli/handlers/ralph.js +3 -0
- package/dist/src/cli/handlers/refresh.js +28 -0
- package/dist/src/cli/handlers/regenerate.js +14 -0
- package/dist/src/cli/handlers/resource-versions.js +3 -0
- package/dist/src/cli/handlers/rules.js +195 -0
- package/dist/src/cli/handlers/run.js +4 -0
- package/dist/src/cli/handlers/runtime-info.js +25 -11
- package/dist/src/cli/handlers/script-runner.js +19 -8
- package/dist/src/cli/handlers/session.js +406 -62
- package/dist/src/cli/handlers/sessions.js +243 -76
- package/dist/src/cli/handlers/setup-manifest.js +474 -79
- package/dist/src/cli/handlers/setup.js +180 -39
- package/dist/src/cli/handlers/steward.js +11 -0
- package/dist/src/cli/handlers/subcommands.js +60 -3
- package/dist/src/cli/handlers/team.js +18 -8
- package/dist/src/cli/handlers/uhp.js +3 -0
- package/dist/src/cli/handlers/use.js +307 -55
- package/dist/src/cli/handlers/utilities.js +125 -26
- package/dist/src/cli/handlers/version.js +8 -0
- package/dist/src/cli/prompt-utils.js +72 -0
- package/dist/src/cli/provider-resolution.js +11 -0
- package/dist/src/cli/router.js +15 -1
- package/dist/src/cli/services/deployment-verification.js +19 -9
- package/dist/src/cli/services/provider-prepare-coordinator.js +24 -0
- package/dist/src/cli/skill-usage.js +2 -3
- package/dist/src/config/aiwg-config.js +349 -35
- package/dist/src/config/cli.js +256 -23
- package/dist/src/config/decision-config.js +106 -0
- package/dist/src/config/dispatch-thresholds-runtime.d.mts +8 -0
- package/dist/src/config/dispatch-thresholds-runtime.mjs +38 -0
- package/dist/src/config/gitignore.js +50 -0
- package/dist/src/config/index-graph-overrides.js +36 -0
- package/dist/src/config/project-artifacts-health.d.mts +37 -0
- package/dist/src/config/project-artifacts-health.mjs +14 -3
- package/dist/src/config/project-artifacts-runtime.d.mts +16 -0
- package/dist/src/config/user-config-dir.d.mts +9 -0
- package/dist/src/config/user-config.js +38 -11
- package/dist/src/config/user-registry.js +23 -15
- package/dist/src/config/user-state.js +7 -0
- package/dist/src/decision/adapters/jev.js +75 -27
- package/dist/src/decision/ask.js +203 -43
- package/dist/src/decision/context-live-qualification.js +2 -2
- package/dist/src/decision/driver.js +14 -7
- package/dist/src/decision/ensemble/aggregate.js +23 -10
- package/dist/src/decision/ensemble-study/multifact-study.js +6 -2
- package/dist/src/decision/entry.js +25 -6
- package/dist/src/decision/graph-live-qualification.js +1 -1
- package/dist/src/decision/graph-run.js +9 -0
- package/dist/src/decision/heldout/collector.js +15 -14
- package/dist/src/decision/heldout/contract.js +30 -7
- package/dist/src/decision/index.js +1 -0
- package/dist/src/decision/interop.js +60 -8
- package/dist/src/decision/jev-credentials.js +15 -1
- package/dist/src/decision/ledger.js +942 -0
- package/dist/src/decision/offload-activation.js +137 -0
- package/dist/src/decision/offload.js +550 -0
- package/dist/src/decision/qualification/load.js +4 -6
- package/dist/src/decision/savings.js +28 -0
- package/dist/src/decision/validate.js +6 -3
- package/dist/src/effects/dispatch-verifier.js +252 -0
- package/dist/src/effects/index.js +1 -0
- package/dist/src/effects/types.js +5 -1
- package/dist/src/effects/verifiers/git.js +2 -1
- package/dist/src/effects/verifiers/tracker.js +26 -14
- package/dist/src/extensions/commands/definitions.js +19 -0
- package/dist/src/extensions/project-local-remove.js +36 -1
- package/dist/src/features/installer.js +2 -2
- package/dist/src/flow/graph-metadata.js +5 -0
- package/dist/src/gates/providers/loader.js +64 -18
- package/dist/src/installation/manager-command.d.mts +18 -0
- package/dist/src/installation/manager-command.mjs +15 -3
- package/dist/src/installation/manager.d.mts +49 -0
- package/dist/src/installation/manager.mjs +95 -5
- package/dist/src/issues/cli.js +4 -4
- package/dist/src/issues/local.js +24 -5
- package/dist/src/jobs/runner.js +1 -1
- package/dist/src/lint/cli.js +27 -1
- package/dist/src/lint/loader.js +2 -1
- package/dist/src/lint/reporters.js +25 -1
- package/dist/src/lint/rule-activation.js +43 -0
- package/dist/src/lint/runner.js +137 -62
- package/dist/src/mcp/adapters/codex-runtime.js +366 -56
- package/dist/src/mcp/claude-install.d.mts +4 -0
- package/dist/src/mcp/claude-install.mjs +41 -0
- package/dist/src/mcp/cli.d.mts +1 -0
- package/dist/src/mcp/cli.mjs +620 -85
- package/dist/src/mcp/config-file.d.mts +28 -0
- package/dist/src/mcp/config-file.mjs +279 -0
- package/dist/src/mcp/config-layers.d.mts +36 -0
- package/dist/src/mcp/config-layers.mjs +348 -0
- package/dist/src/mcp/credentials.d.mts +51 -0
- package/dist/src/mcp/credentials.mjs +484 -0
- package/dist/src/mcp/grok-build-config.d.mts +6 -0
- package/dist/src/mcp/grok-build-config.mjs +17 -27
- package/dist/src/mcp/helpers.mjs +109 -4
- package/dist/src/mcp/muse-native-config.d.mts +13 -0
- package/dist/src/mcp/muse-native-config.mjs +1 -0
- package/dist/src/mcp/omp-config.d.mts +4 -0
- package/dist/src/mcp/omp-config.mjs +50 -29
- package/dist/src/mcp/profiles.js +97 -23
- package/dist/src/mcp/profiles.mjs +99 -27
- package/dist/src/mcp/registry.js +248 -131
- package/dist/src/mcp/registry.mjs +169 -100
- package/dist/src/mcp/server-config.d.mts +3 -0
- package/dist/src/mcp/server-config.mjs +89 -0
- package/dist/src/mcp/server.mjs +127 -82
- package/dist/src/mcp/test-simple.mjs +2 -1
- package/dist/src/mcp/toml-editor.d.mts +4 -0
- package/dist/src/mcp/toml-string.d.mts +1 -0
- package/dist/src/mcp/toml-string.mjs +9 -0
- package/dist/src/mcp/toml-strip-mcp.d.mts +1 -0
- package/dist/src/mcp/toml-strip-mcp.mjs +135 -0
- package/dist/src/mcp/tool-filters.d.mts +46 -0
- package/dist/src/mcp/tool-filters.mjs +357 -0
- package/dist/src/mcp/tools/agentic-sandbox.mjs +3 -1
- package/dist/src/mcp/tools/decision.mjs +22 -3
- package/dist/src/mcp/tools/discovery.mjs +62 -55
- package/dist/src/mcp/tools/orchestration.mjs +50 -2
- package/dist/src/mcp/tools/subsystems.mjs +39 -8
- package/dist/src/mcp/tools/worker-invoke.mjs +2856 -0
- package/dist/src/mcp/tools/worker-shell-control.mjs +289 -0
- package/dist/src/mcp/tools/worker-stdin-runtime.mjs +79 -0
- package/dist/src/memory/project-registry.js +2 -2
- package/dist/src/mission-protocol/codecs.js +647 -1
- package/dist/src/mission-protocol/types.js +110 -0
- package/dist/src/mission-protocol/worker-status.js +2 -0
- package/dist/src/models/config-loader.js +76 -14
- package/dist/src/models/grok-build-models.js +93 -41
- package/dist/src/models/model-capabilities.v1.json +3183 -24
- package/dist/src/models/model-discovery.js +931 -67
- package/dist/src/models/provider-policy.js +719 -11
- package/dist/src/models/qualification-digest.d.mts +2 -0
- package/dist/src/models/qualification-digest.mjs +17 -0
- package/dist/src/models/resolver.js +261 -0
- package/dist/src/models/worker-direct-readiness.js +550 -0
- package/dist/src/models/worker-dispatch-offer.js +169 -0
- package/dist/src/models/worker-health-readiness.js +144 -0
- package/dist/src/models/worker-installation.d.mts +39 -0
- package/dist/src/models/worker-installation.mjs +986 -0
- package/dist/src/models/wrapper-route.js +18 -0
- package/dist/src/ops/registry.js +9 -14
- package/dist/src/plugins/standalone-packager.js +4 -3
- package/dist/src/process/bounded-child-process.d.mts +84 -0
- package/dist/src/process/bounded-child-process.mjs +430 -0
- package/dist/src/process/cli-command.d.mts +16 -0
- package/dist/src/process/cli-command.mjs +75 -0
- package/dist/src/process/signal-child.d.mts +6 -0
- package/dist/src/process/signal-child.mjs +7 -0
- package/dist/src/providers/capability-matrix.js +8 -0
- package/dist/src/providers/capability-matrix.yaml +270 -0
- package/dist/src/providers/deployed-skill-roots.js +20 -0
- package/dist/src/providers/grok-build-acp.js +3 -2
- package/dist/src/providers/grok-build-headless.js +53 -23
- package/dist/src/providers/grok-build-inspect.js +1 -0
- package/dist/src/providers/omp-agent.d.mts +8 -0
- package/dist/src/providers/omp-diagnostics.d.mts +1 -0
- package/dist/src/providers/omp-paths.d.mts +13 -0
- package/dist/src/providers/provider-definitions.js +9 -1
- package/dist/src/providers/provider-definitions.mjs +9 -2
- package/dist/src/providers/skill-ownership.d.mts +9 -0
- package/dist/src/providers/skill-ownership.mjs +88 -0
- package/dist/src/rules/activation.d.mts +21 -0
- package/dist/src/rules/activation.mjs +62 -0
- package/dist/src/rules/inline-budget.d.mts +26 -0
- package/dist/src/rules/inline-budget.mjs +66 -0
- package/dist/src/rules/inventory.js +129 -0
- package/dist/src/rules/pin-state.js +73 -0
- package/dist/src/rules/pins.d.mts +14 -0
- package/dist/src/rules/pins.mjs +112 -0
- package/dist/src/rules/rule-frontmatter.schema.json +197 -0
- package/dist/src/serve/executor-registry.js +16 -24
- package/dist/src/serve/fleet-mission-conductor.js +9 -0
- package/dist/src/serve/mission-conductor.js +47 -12
- package/dist/src/serve/shared-host-scheduler.js +2 -0
- package/dist/src/serve/worker-invocation.js +558 -0
- package/dist/src/serve/worker-stdin-frame.d.mts +10 -0
- package/dist/src/serve/worker-stdin-frame.mjs +107 -0
- package/dist/src/sessions/adapter-registry.js +49 -0
- package/dist/src/sessions/adapters/claude.js +39 -54
- package/dist/src/sessions/adapters/codex.js +82 -67
- package/dist/src/sessions/adapters/copilot.js +4 -4
- package/dist/src/sessions/adapters/cursor.js +40 -57
- package/dist/src/sessions/adapters/deepseek-harness.js +15 -12
- package/dist/src/sessions/adapters/factory.js +31 -27
- package/dist/src/sessions/adapters/generic.js +43 -9
- package/dist/src/sessions/adapters/grok-build.js +11 -11
- package/dist/src/sessions/adapters/grokbot-locator.js +111 -0
- package/dist/src/sessions/adapters/grokbot.js +434 -24
- package/dist/src/sessions/adapters/hermes.js +27 -29
- package/dist/src/sessions/adapters/muse-workspace-scan.js +128 -0
- package/dist/src/sessions/adapters/muse.js +200 -104
- package/dist/src/sessions/adapters/omp.js +28 -14
- package/dist/src/sessions/adapters/openclaw.js +89 -79
- package/dist/src/sessions/adapters/opencode.js +27 -24
- package/dist/src/sessions/adapters/openhuman.js +18 -60
- package/dist/src/sessions/adapters/pi.js +30 -14
- package/dist/src/sessions/adapters/warp.js +3 -3
- package/dist/src/sessions/adapters/windsurf.js +19 -48
- package/dist/src/sessions/batch-contracts.js +25 -5
- package/dist/src/sessions/batch-import.js +72 -51
- package/dist/src/sessions/candidates.js +47 -11
- package/dist/src/sessions/contracts.js +75 -2
- package/dist/src/sessions/coverage-preflight.js +134 -0
- package/dist/src/sessions/discovery.js +3 -0
- package/dist/src/sessions/export-plan.js +4 -3
- package/dist/src/sessions/grok-build-cli.js +67 -5
- package/dist/src/sessions/import-exclusions.js +72 -0
- package/dist/src/sessions/import-lease.js +34 -18
- package/dist/src/sessions/import-limits.js +37 -0
- package/dist/src/sessions/importer.js +84 -46
- package/dist/src/sessions/index.js +2 -0
- package/dist/src/sessions/lifecycle.js +45 -0
- package/dist/src/sessions/policy.js +133 -25
- package/dist/src/sessions/readers.js +106 -53
- package/dist/src/sessions/refresh.js +106 -0
- package/dist/src/sessions/repository.js +199 -63
- package/dist/src/sessions/workspace-discovery.js +315 -88
- package/dist/src/sessions/workspace-matching.js +107 -0
- package/dist/src/skills/adapters/clawhub.js +10 -8
- package/dist/src/skills/adapters/openclaw.js +10 -7
- package/dist/src/skills/importer.js +1 -0
- package/dist/src/smiths/context-pipeline/aiwg-md.js +11 -0
- package/dist/src/smiths/context-pipeline/claude-hook.js +3 -26
- package/dist/src/smiths/context-pipeline/finalization.js +6 -5
- package/dist/src/smiths/context-pipeline/generator.js +21 -4
- package/dist/src/smiths/context-pipeline/parallelism-section.js +195 -14
- package/dist/src/smiths/context-pipeline/workspace-context.js +116 -9
- package/dist/src/smiths/hook-bridge/codex-translator.js +6 -5
- package/dist/src/smiths/mcpsmith/example.js +1 -1
- package/dist/src/smiths/mcpsmith/generator.js +1 -1
- package/dist/src/smiths/toolsmith/runtime-discovery.d.mts +10 -0
- package/dist/src/smiths/toolsmith/runtime-discovery.mjs +85 -31
- package/dist/src/storage/backends/fs.js +18 -76
- package/dist/src/storage/backends/logseq.js +68 -7
- package/dist/src/storage/backends/obsidian.js +1 -0
- package/dist/src/storage/fortemi-qualification-receipt.js +2 -2
- package/dist/src/testing/generators/test-code-generator.js +57 -30
- package/dist/src/tracker/forge-http.js +2 -1
- package/dist/src/uhp/client.js +11 -1
- package/dist/src/uhp/mission.js +5 -0
- package/dist/src/update/checker.d.mts +2 -0
- package/dist/src/update/checker.mjs +5 -5
- package/dist/src/update/notifier.mjs +54 -2
- package/dist/src/update/service.d.mts +22 -0
- package/dist/src/utils/contained-filesystem.d.mts +37 -0
- package/dist/src/utils/contained-filesystem.mjs +683 -0
- package/dist/src/utils/temporary-directory.js +36 -0
- package/dist/src/writing/example-generator.js +1 -1
- package/dist/src/writing/fidelity.js +18 -1
- package/package.json +32 -2
- package/schemas/mission-v1.schema.json +2429 -0
- package/schemas/models/model-catalog.v1.schema.json +301 -0
- package/schemas/models/model-config-compatibility.v1.schema.json +281 -0
- package/schemas/models/model-policy.v1.schema.json +14 -0
- package/schemas/models/provider-model-capabilities.v1.schema.json +1072 -0
- package/tools/_resolve-impl.mjs +39 -2
- package/tools/agents/deploy-agents.mjs +64 -23
- package/tools/agents/providers/base.mjs +153 -166
- package/tools/agents/providers/claude.mjs +8 -24
- package/tools/agents/providers/codex.mjs +18 -11
- package/tools/agents/providers/cursor.mjs +19 -18
- package/tools/agents/providers/grok-build.mjs +3 -1
- package/tools/agents/providers/grokbot.mjs +11 -12
- package/tools/agents/providers/hermes.mjs +62 -49
- package/tools/agents/providers/omp.mjs +5 -2
- package/tools/agents/providers/openclaw-translator.mjs +19 -5
- package/tools/agents/providers/openclaw.mjs +9 -4
- package/tools/agents/providers/pi.mjs +5 -0
- package/tools/agents/providers/provider-prepare-channel.mjs +5 -0
- package/tools/agents/providers/provider-prepare-coordinator.mjs +247 -0
- package/tools/agents/providers/provider-prepare-worker.mjs +84 -0
- package/tools/agents/providers/rule-delivery.mjs +1270 -0
- package/tools/agents/providers/warp.mjs +20 -26
- package/tools/cli/decision-offload-report.mjs +40 -0
- package/tools/cli/workspace-status.mjs +862 -0
- package/tools/plugin/package-plugins.mjs +186 -8
- package/tools/plugin/plugin-links.mjs +114 -0
- package/tools/providers/antigravity-transport.mjs +3 -1
- package/tools/providers/deepseek-harness-transport.mjs +360 -0
- package/tools/providers/omp-transport.mjs +194 -0
- package/tools/ralph-external/lib/claude-adapter.mjs +389 -0
- package/tools/ralph-external/lib/codex-adapter.mjs +432 -0
- package/tools/ralph-external/lib/consecutive-failure-circuit-breaker.mjs +1748 -0
- package/tools/ralph-external/lib/deepseek-harness-adapter.mjs +397 -0
- package/tools/ralph-external/lib/factory-adapter.mjs +503 -0
- package/tools/ralph-external/lib/launcher-recovery-evidence.mjs +55 -0
- package/tools/ralph-external/lib/muse-adapter.mjs +364 -0
- package/tools/ralph-external/lib/omp-adapter.mjs +315 -0
- package/tools/ralph-external/lib/opencode-adapter.mjs +575 -0
- package/tools/ralph-external/lib/openhuman-adapter.mjs +75 -0
- package/tools/ralph-external/lib/pi-adapter.mjs +329 -0
- package/tools/ralph-external/lib/provider-adapter.mjs +748 -0
- package/tools/ralph-external/lib/recovery-probe.mjs +72 -0
- package/tools/ralph-external/lib/session-environment.mjs +28 -0
- package/tools/ralph-external/lib/systemd-client.mjs +49 -0
- package/tools/ralph-external/lib/worker-health.mjs +439 -0
- package/tools/ralph-external/lib/worker-isolation.mjs +717 -0
- package/tools/ralph-external/lib/worker-overflow.mjs +50 -0
- package/tools/ralph-external/process-monitor.mjs +1017 -0
- package/tools/ralph-external/session-launcher.mjs +5057 -0
- package/tools/scripts/resource-preflight.mjs +541 -0
- package/tools/skills/deploy-skills-codex.mjs +2 -1
- package/vscode-extension/schemas/aiwg.config.v1.json +2916 -0
package/dist/src/mcp/helpers.mjs
CHANGED
|
@@ -5,11 +5,113 @@
|
|
|
5
5
|
* @issues #1311 (scope split), #1312 (command-run), #1313 (discover + pairs)
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
+
import { signalChild } from '../process/signal-child.mjs';
|
|
8
9
|
import { spawn } from 'node:child_process';
|
|
9
10
|
import { StringDecoder } from 'node:string_decoder';
|
|
10
11
|
import fs from 'node:fs/promises';
|
|
11
12
|
import path from 'node:path';
|
|
12
13
|
|
|
14
|
+
/** Existing MCP owners have not qualified worker contracts; fail before handoff. */
|
|
15
|
+
const MISSION_WORKER_SCHEMA_ID = 'https://aiwg.io/schemas/mission.aiwg.io/v1';
|
|
16
|
+
const MISSION_WORKER_DEFINITIONS = new Set([
|
|
17
|
+
'workerArtifact',
|
|
18
|
+
'workerEffort',
|
|
19
|
+
'workerTuple',
|
|
20
|
+
'workerLimits',
|
|
21
|
+
'workerSelectionIntent',
|
|
22
|
+
'workerBrief',
|
|
23
|
+
'workerClaims',
|
|
24
|
+
'workerUsage',
|
|
25
|
+
'workerExecution',
|
|
26
|
+
'workerVerification',
|
|
27
|
+
'workerResult',
|
|
28
|
+
'workerDispatch',
|
|
29
|
+
'workerDirectIntent',
|
|
30
|
+
'workerDirectBrief',
|
|
31
|
+
'workerResolvedInvocation',
|
|
32
|
+
'workerDirectVerification',
|
|
33
|
+
'workerDirectResult',
|
|
34
|
+
'workerHealthSummary',
|
|
35
|
+
'workerEffectiveHealth',
|
|
36
|
+
]);
|
|
37
|
+
|
|
38
|
+
// Resolve against the owning schema, never infer ownership from a $defs name.
|
|
39
|
+
function workerContractReference(reference, base = 'https://aiwg.invalid/local-schema') {
|
|
40
|
+
if (typeof reference !== 'string') return false;
|
|
41
|
+
try {
|
|
42
|
+
const resolved = new URL(reference, base);
|
|
43
|
+
const fragment = decodeURIComponent(resolved.hash).split('/');
|
|
44
|
+
resolved.hash = '';
|
|
45
|
+
return resolved.href === MISSION_WORKER_SCHEMA_ID
|
|
46
|
+
&& fragment[0] === '#' && fragment[1] === '$defs'
|
|
47
|
+
&& MISSION_WORKER_DEFINITIONS.has(fragment[2]);
|
|
48
|
+
} catch {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function assertNoWorkerBinding(value, consumer) {
|
|
54
|
+
const seenExtensions = new Set();
|
|
55
|
+
const seenRecords = new Set();
|
|
56
|
+
const seenSchemas = new Map();
|
|
57
|
+
function unsupported() {
|
|
58
|
+
const error = new Error(`OG_CONTRACT_UNSUPPORTED: ${consumer} does not advertise worker contract support.`);
|
|
59
|
+
error.code = 'OG_CONTRACT_UNSUPPORTED';
|
|
60
|
+
throw error;
|
|
61
|
+
}
|
|
62
|
+
function extensions(item) {
|
|
63
|
+
if (!item || typeof item !== 'object' || Array.isArray(item) || seenExtensions.has(item)) return;
|
|
64
|
+
seenExtensions.add(item);
|
|
65
|
+
if (Object.hasOwn(item, 'aiwg.worker-dispatch')) unsupported();
|
|
66
|
+
for (const [key, child] of Object.entries(item)) if (key.startsWith('aiwg.source.')) {
|
|
67
|
+
extensions(child);
|
|
68
|
+
record(child);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
function schema(item, inheritedBase = 'https://aiwg.invalid/local-schema') {
|
|
72
|
+
if (!item || typeof item !== 'object' || Array.isArray(item)) return;
|
|
73
|
+
let base = inheritedBase;
|
|
74
|
+
if (typeof item.$id === 'string') {
|
|
75
|
+
try { base = new URL(item.$id, inheritedBase).href; } catch { /* invalid schemas belong to validation */ }
|
|
76
|
+
}
|
|
77
|
+
const bases = seenSchemas.get(item) ?? new Set();
|
|
78
|
+
if (bases.has(base)) return;
|
|
79
|
+
bases.add(base);
|
|
80
|
+
seenSchemas.set(item, bases);
|
|
81
|
+
if (workerContractReference(item.$ref, base) || workerContractReference(item.schemaRef, base)) unsupported();
|
|
82
|
+
// Only JSON Schema subschemas inherit an owner. const/default/examples
|
|
83
|
+
// and properties' names are data, not active references or extensions.
|
|
84
|
+
for (const key of ['$defs', 'definitions', 'properties', 'patternProperties', 'dependentSchemas', 'dependencies']) {
|
|
85
|
+
for (const child of Object.values(item[key] ?? {})) schema(child, base);
|
|
86
|
+
}
|
|
87
|
+
for (const key of ['allOf', 'anyOf', 'oneOf', 'prefixItems']) {
|
|
88
|
+
if (Array.isArray(item[key])) for (const child of item[key]) schema(child, base);
|
|
89
|
+
}
|
|
90
|
+
for (const key of ['items', 'additionalItems', 'additionalProperties', 'unevaluatedItems', 'unevaluatedProperties', 'contains', 'propertyNames', 'not', 'if', 'then', 'else']) {
|
|
91
|
+
if (Array.isArray(item[key])) for (const child of item[key]) schema(child, base);
|
|
92
|
+
else schema(item[key], base);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
function record(item) {
|
|
96
|
+
if (!item || typeof item !== 'object' || Array.isArray(item) || seenRecords.has(item)) return;
|
|
97
|
+
seenRecords.add(item);
|
|
98
|
+
// All mission-v1 carriers: /extensions and /status/artifacts/*/extensions.
|
|
99
|
+
extensions(item.extensions);
|
|
100
|
+
if (Array.isArray(item.status?.artifacts)) item.status.artifacts.forEach(record);
|
|
101
|
+
// Native protocols also use metadata and top-level artifacts.
|
|
102
|
+
extensions(item.metadata);
|
|
103
|
+
if (Array.isArray(item.artifacts)) item.artifacts.forEach(record);
|
|
104
|
+
if (typeof item.$ref === 'string' || typeof item.schemaRef === 'string'
|
|
105
|
+
|| typeof item.$id === 'string' || typeof item.$schema === 'string') schema(item);
|
|
106
|
+
schema(item.schema);
|
|
107
|
+
if (workerContractReference(item.output?.schemaRef)) unsupported();
|
|
108
|
+
// Existing sandbox response envelopes own these records explicitly.
|
|
109
|
+
record(item.workload);
|
|
110
|
+
if (item.document_type === 'inventory' && Array.isArray(item.records)) item.records.forEach(record);
|
|
111
|
+
}
|
|
112
|
+
record(value);
|
|
113
|
+
}
|
|
114
|
+
|
|
13
115
|
/**
|
|
14
116
|
* Resolve AIWG_ROOT, with safe fallback to known install locations.
|
|
15
117
|
*/
|
|
@@ -49,11 +151,14 @@ function parseProjectArtifactLocation(contents) {
|
|
|
49
151
|
return null;
|
|
50
152
|
}
|
|
51
153
|
|
|
52
|
-
|
|
154
|
+
// MCP artifact I/O resolves pointers and environment configuration when each
|
|
155
|
+
// project is first trusted, then pins the resulting store.
|
|
156
|
+
export async function resolveProjectAiwgDir(projectDir, { allowLocationPointer = true, env = process.env } = {}) {
|
|
53
157
|
for (const key of ARTIFACT_PATH_ENV_ALIASES) {
|
|
54
|
-
const value =
|
|
158
|
+
const value = env[key];
|
|
55
159
|
if (typeof value === 'string' && value.trim()) return expandProjectArtifactPath(value, projectDir);
|
|
56
160
|
}
|
|
161
|
+
if (!allowLocationPointer) return path.resolve(projectDir, '.aiwg');
|
|
57
162
|
try {
|
|
58
163
|
const pointer = await fs.readFile(path.join(projectDir, PROJECT_AIWG_LOCATION_FILE), 'utf-8');
|
|
59
164
|
const configured = parseProjectArtifactLocation(pointer);
|
|
@@ -183,11 +288,11 @@ export function runAiwgCli(args, { cwd, env, timeoutMs = 120_000, input } = {})
|
|
|
183
288
|
// owned child one second to exit gracefully, then escalate cleanup.
|
|
184
289
|
reject(err);
|
|
185
290
|
killTimer = setTimeout(() => {
|
|
186
|
-
try { proc
|
|
291
|
+
try { signalChild(proc, 'SIGKILL'); } catch { /* child may already have exited */ }
|
|
187
292
|
}, 1000);
|
|
188
293
|
killTimer.unref?.();
|
|
189
294
|
// Install cleanup first: kill() can synchronously trigger close in an adapter.
|
|
190
|
-
try { proc
|
|
295
|
+
try { signalChild(proc, 'SIGTERM'); } catch { /* retain the original failure */ }
|
|
191
296
|
};
|
|
192
297
|
const timer = setTimeout(() => {
|
|
193
298
|
rejectAndTerminate(new Error(`aiwg ${args[0] || ''} timed out after ${timeoutMs}ms`));
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export type MuseNativeInspection = {
|
|
2
|
+
cli: string;
|
|
3
|
+
hooks: 'absent' | 'managed' | 'operator-only';
|
|
4
|
+
mcp: 'absent' | 'configured' | 'unreadable';
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
export function inspectMuseNative(options?: {
|
|
8
|
+
projectDir?: string;
|
|
9
|
+
env?: Record<string, string | undefined>;
|
|
10
|
+
userHome?: string;
|
|
11
|
+
}): Promise<MuseNativeInspection>;
|
|
12
|
+
|
|
13
|
+
export function isMuseDeployedProject(projectDir?: string): Promise<boolean>;
|
|
@@ -50,6 +50,7 @@ function inspectMuseCli(env) {
|
|
|
50
50
|
env,
|
|
51
51
|
encoding: 'utf8',
|
|
52
52
|
timeout: MUSE_VERSION_TIMEOUT_MS,
|
|
53
|
+
killSignal: 'SIGKILL',
|
|
53
54
|
});
|
|
54
55
|
if (result.error?.code === 'ENOENT') return 'absent';
|
|
55
56
|
if (result.error?.code === 'ETIMEDOUT') return 'present (version timeout)';
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export function ompServerConfig(server: Record<string, any>): Record<string, any>;
|
|
2
|
+
export function manageOmpMcp(configPath: string, servers: Array<{name: string; [key: string]: any}>, options?: {dryRun?: boolean; remove?: string[]; userScope?: boolean; projectRoot?: string}): Promise<{
|
|
3
|
+
configPath: string; serversInjected: string[]; alreadyPresent: string[]; removed: string[];
|
|
4
|
+
}>;
|
|
@@ -1,22 +1,37 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { assertConfigDestination, assertNoSymlinkParents, writeConfigAtomic } from './config-file.mjs';
|
|
2
|
+
import { readFile, mkdir, lstat, unlink, open } from 'node:fs/promises';
|
|
3
|
+
import { dirname, parse, resolve } from 'node:path';
|
|
4
|
+
import { createHash } from 'node:crypto';
|
|
4
5
|
|
|
5
6
|
const hash = value => createHash('sha256').update(JSON.stringify(value)).digest('hex');
|
|
6
7
|
const record = value => value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
8
|
+
/**
|
|
9
|
+
* Reject a symlinked ancestor, regardless of whether the caller passed a
|
|
10
|
+
* projectRoot: a symlinked parent can redirect the write outside the intended
|
|
11
|
+
* directory either way.
|
|
12
|
+
*
|
|
13
|
+
* - With a projectRoot, the walk stops there (matching
|
|
14
|
+
* assertConfigDestination) so a deliberately symlinked project root
|
|
15
|
+
* itself - a dotfile-manager alias - is not rejected as its own ancestor.
|
|
16
|
+
* - Without a projectRoot, an explicit `userScope: true` means the caller
|
|
17
|
+
* knows this is a private home-scope destination (symlinked dotfile-
|
|
18
|
+
* manager parents there are tolerated, same as every other provider's
|
|
19
|
+
* home-scope target); the walk is skipped.
|
|
20
|
+
* - Without a projectRoot and without an explicit userScope, there is no
|
|
21
|
+
* known safe boundary, so every ancestor up to the filesystem root is
|
|
22
|
+
* checked - the conservative default for a caller that supplied neither.
|
|
23
|
+
*/
|
|
24
|
+
async function rejectSymlinkAncestors(file, projectRoot, userScope) {
|
|
25
|
+
if (projectRoot) {
|
|
26
|
+
await assertNoSymlinkParents(file, resolve(projectRoot));
|
|
27
|
+
return;
|
|
16
28
|
}
|
|
29
|
+
if (userScope === true) return;
|
|
30
|
+
await assertNoSymlinkParents(file, parse(resolve(file)).root);
|
|
17
31
|
}
|
|
18
|
-
async function readObject(file) {
|
|
19
|
-
await
|
|
32
|
+
async function readObject(file, projectRoot, userScope) {
|
|
33
|
+
await rejectSymlinkAncestors(file, projectRoot, userScope);
|
|
34
|
+
await assertConfigDestination(file, projectRoot);
|
|
20
35
|
try {
|
|
21
36
|
if ((await lstat(file)).isSymbolicLink()) throw new Error('OMP MCP configuration cannot be a symbolic link');
|
|
22
37
|
const data = JSON.parse(await readFile(file, 'utf8'));
|
|
@@ -28,13 +43,10 @@ async function readObject(file) {
|
|
|
28
43
|
throw error;
|
|
29
44
|
}
|
|
30
45
|
}
|
|
31
|
-
async function atomic(file, data) {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
await writeFile(temporary, JSON.stringify(data, null, 2) + '\n', { flag: 'wx', mode: 0o600 });
|
|
36
|
-
await rename(temporary, file);
|
|
37
|
-
} finally { await unlink(temporary).catch(() => {}); }
|
|
46
|
+
async function atomic(file, data, options) {
|
|
47
|
+
// OMP files can carry auth/oauth secrets, so they are always owner-only even
|
|
48
|
+
// for project-scope destinations. Containment still follows projectRoot.
|
|
49
|
+
await writeConfigAtomic(file, JSON.stringify(data, null, 2) + '\n', { ...options, userScope: true });
|
|
38
50
|
}
|
|
39
51
|
|
|
40
52
|
export function ompServerConfig(server) {
|
|
@@ -74,14 +86,21 @@ export function ompServerConfig(server) {
|
|
|
74
86
|
config.headers[header] = '${' + variable + '}';
|
|
75
87
|
}
|
|
76
88
|
}
|
|
89
|
+
if (server.envFrom) {
|
|
90
|
+
config.env = { ...config.env };
|
|
91
|
+
for (const [key, variable] of Object.entries(server.envFrom)) {
|
|
92
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(variable)) throw new Error('Invalid MCP env-from environment reference');
|
|
93
|
+
config.env[key] = '${' + variable + '}';
|
|
94
|
+
}
|
|
95
|
+
}
|
|
77
96
|
return config;
|
|
78
97
|
}
|
|
79
98
|
|
|
80
99
|
/** Hash-only receipt: never saves an extra copy of operator configuration. */
|
|
81
|
-
async function manageLockedOmpMcp(configPath, servers, { dryRun = false, remove = [] } = {}) {
|
|
100
|
+
async function manageLockedOmpMcp(configPath, servers, { dryRun = false, remove = [], userScope, projectRoot } = {}) {
|
|
82
101
|
const receiptPath = `${configPath}.aiwg-ownership.json`;
|
|
83
|
-
const existing = await readObject(configPath);
|
|
84
|
-
const receipt = await readObject(receiptPath);
|
|
102
|
+
const existing = await readObject(configPath, projectRoot, userScope);
|
|
103
|
+
const receipt = await readObject(receiptPath, projectRoot, userScope);
|
|
85
104
|
if (receipt.servers !== undefined && (!record(receipt.servers) || receipt.schema !== 'aiwg.omp-mcp-ownership.v1')) throw new Error('Invalid OMP MCP ownership receipt');
|
|
86
105
|
if (existing.mcpServers !== undefined && !record(existing.mcpServers)) throw new Error('Invalid OMP mcpServers object');
|
|
87
106
|
const next = { ...(existing.mcpServers || {}) };
|
|
@@ -105,10 +124,10 @@ async function manageLockedOmpMcp(configPath, servers, { dryRun = false, remove
|
|
|
105
124
|
if (owned[name]) { delete next[name]; delete owned[name]; result.removed.push(name); }
|
|
106
125
|
}
|
|
107
126
|
if (!dryRun) {
|
|
108
|
-
if (hash(await readObject(configPath)) !== hash(existing) || hash(await readObject(receiptPath)) !== hash(receipt)) throw new Error('OMP MCP configuration changed during injection; retry after reviewing the operator edit');
|
|
127
|
+
if (hash(await readObject(configPath, projectRoot, userScope)) !== hash(existing) || hash(await readObject(receiptPath, projectRoot, userScope)) !== hash(receipt)) throw new Error('OMP MCP configuration changed during injection; retry after reviewing the operator edit');
|
|
109
128
|
// If interrupted between writes, a retry fails closed on the hash mismatch.
|
|
110
|
-
await atomic(configPath, { ...existing, mcpServers: next });
|
|
111
|
-
await atomic(receiptPath, { schema: 'aiwg.omp-mcp-ownership.v1', servers: owned });
|
|
129
|
+
await atomic(configPath, { ...existing, mcpServers: next }, { userScope, projectRoot });
|
|
130
|
+
await atomic(receiptPath, { schema: 'aiwg.omp-mcp-ownership.v1', servers: owned }, { userScope: true, projectRoot });
|
|
112
131
|
}
|
|
113
132
|
return result;
|
|
114
133
|
}
|
|
@@ -116,9 +135,11 @@ async function manageLockedOmpMcp(configPath, servers, { dryRun = false, remove
|
|
|
116
135
|
/** Serialize AIWG writers; operator edits detected before committing a replacement. */
|
|
117
136
|
export async function manageOmpMcp(configPath, servers, options = {}) {
|
|
118
137
|
if (options.dryRun) return manageLockedOmpMcp(configPath, servers, options);
|
|
119
|
-
await
|
|
138
|
+
await rejectSymlinkAncestors(configPath, options.projectRoot, options.userScope);
|
|
139
|
+
await assertConfigDestination(configPath, options.projectRoot);
|
|
120
140
|
const lockPath = `${configPath}.aiwg-lock`;
|
|
121
|
-
await
|
|
141
|
+
await rejectSymlinkAncestors(lockPath, options.projectRoot, options.userScope);
|
|
142
|
+
await assertConfigDestination(lockPath, options.projectRoot);
|
|
122
143
|
await mkdir(dirname(configPath), { recursive: true });
|
|
123
144
|
let lock;
|
|
124
145
|
try { lock = await open(lockPath, 'wx', 0o600); }
|
package/dist/src/mcp/profiles.js
CHANGED
|
@@ -9,7 +9,9 @@
|
|
|
9
9
|
*
|
|
10
10
|
* @implements #889
|
|
11
11
|
*/
|
|
12
|
-
import {
|
|
12
|
+
import { resolveCredentialPolicy } from './credentials.mjs';
|
|
13
|
+
import { isLowerLayerEntry, loadLayered, resolveProfileExtends, resolveConfigLayers, saveLayerData } from './config-layers.mjs';
|
|
14
|
+
import { readFile, writeFile } from 'fs/promises';
|
|
13
15
|
import { resolve } from 'path';
|
|
14
16
|
import { resolveConfigDir } from '../config/user-config.js';
|
|
15
17
|
// ─────────────────────────────────────────────
|
|
@@ -66,8 +68,12 @@ export const PRESET_PROFILES = {
|
|
|
66
68
|
export class McpProfileRegistry {
|
|
67
69
|
configDir;
|
|
68
70
|
cache = null;
|
|
71
|
+
/** Configuration layers, lowest precedence first; null for a single directory */
|
|
72
|
+
layers;
|
|
73
|
+
layering = null;
|
|
69
74
|
constructor(configDirOverride) {
|
|
70
|
-
this.
|
|
75
|
+
this.layers = resolveConfigLayers(configDirOverride);
|
|
76
|
+
this.configDir = this.layers ? this.layers[this.layers.length - 1] : resolveConfigDir(configDirOverride);
|
|
71
77
|
}
|
|
72
78
|
getPath() {
|
|
73
79
|
return resolve(this.configDir, PROFILES_FILENAME);
|
|
@@ -75,6 +81,12 @@ export class McpProfileRegistry {
|
|
|
75
81
|
async load() {
|
|
76
82
|
if (this.cache)
|
|
77
83
|
return this.cache;
|
|
84
|
+
if (this.layers) {
|
|
85
|
+
const layered = await loadLayered(this.layers, PROFILES_FILENAME, 'profiles', DEFAULT_DATA);
|
|
86
|
+
this.cache = layered.data;
|
|
87
|
+
this.layering = layered.layering;
|
|
88
|
+
return this.cache;
|
|
89
|
+
}
|
|
78
90
|
const filePath = this.getPath();
|
|
79
91
|
try {
|
|
80
92
|
const content = await readFile(filePath, 'utf-8');
|
|
@@ -88,13 +100,24 @@ export class McpProfileRegistry {
|
|
|
88
100
|
catch {
|
|
89
101
|
this.cache = { ...DEFAULT_DATA, profiles: {} };
|
|
90
102
|
}
|
|
103
|
+
try {
|
|
104
|
+
resolveCredentialPolicy({ registryPolicy: this.cache.credentialPolicy, env: {} });
|
|
105
|
+
}
|
|
106
|
+
catch (error) {
|
|
107
|
+
this.cache = null;
|
|
108
|
+
throw new Error(`Invalid MCP credential policy in ${filePath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
109
|
+
}
|
|
91
110
|
return this.cache;
|
|
92
111
|
}
|
|
93
|
-
async save() {
|
|
94
|
-
if (!
|
|
112
|
+
async save(data = this.cache) {
|
|
113
|
+
if (!data)
|
|
95
114
|
return;
|
|
96
|
-
await
|
|
97
|
-
|
|
115
|
+
await saveLayerData(this.getPath(), data, 'profiles', this.layers, this.layering);
|
|
116
|
+
this.cache = data;
|
|
117
|
+
if (this.layers) {
|
|
118
|
+
this.clearCache();
|
|
119
|
+
await this.load();
|
|
120
|
+
}
|
|
98
121
|
}
|
|
99
122
|
validateName(name) {
|
|
100
123
|
if (!NAME_RE.test(name)) {
|
|
@@ -122,11 +145,13 @@ export class McpProfileRegistry {
|
|
|
122
145
|
}
|
|
123
146
|
async add(profile, serverRegistry) {
|
|
124
147
|
this.validateName(profile.name);
|
|
125
|
-
const data = await this.load();
|
|
148
|
+
const data = structuredClone(await this.load());
|
|
126
149
|
if (data.profiles[profile.name]) {
|
|
127
150
|
throw new Error(`Profile "${profile.name}" already exists. Use "aiwg mcp profile edit" to modify it.`);
|
|
128
151
|
}
|
|
129
152
|
await this.validateServers(profile.servers ?? [], serverRegistry);
|
|
153
|
+
if (profile.extends?.length)
|
|
154
|
+
resolveProfileExtends(profile.name, { ...data.profiles, [profile.name]: profile });
|
|
130
155
|
data.profiles[profile.name] = {
|
|
131
156
|
...profile,
|
|
132
157
|
servers: profile.servers ?? [],
|
|
@@ -134,18 +159,28 @@ export class McpProfileRegistry {
|
|
|
134
159
|
createdAt: new Date().toISOString(),
|
|
135
160
|
updatedAt: new Date().toISOString(),
|
|
136
161
|
};
|
|
137
|
-
await this.save();
|
|
162
|
+
await this.save(data);
|
|
138
163
|
}
|
|
139
164
|
async get(name) {
|
|
140
165
|
const data = await this.load();
|
|
141
166
|
return data.profiles[name];
|
|
142
167
|
}
|
|
168
|
+
/**
|
|
169
|
+
* Return a profile with its `extends` chain applied across all configuration
|
|
170
|
+
* layers. Use get() for the profile exactly as stored.
|
|
171
|
+
*/
|
|
172
|
+
async resolve(name) {
|
|
173
|
+
const data = await this.load();
|
|
174
|
+
if (!data.profiles[name])
|
|
175
|
+
return undefined;
|
|
176
|
+
return resolveProfileExtends(name, data.profiles);
|
|
177
|
+
}
|
|
143
178
|
async list() {
|
|
144
179
|
const data = await this.load();
|
|
145
180
|
return Object.values(data.profiles);
|
|
146
181
|
}
|
|
147
182
|
async edit(name, changes, serverRegistry) {
|
|
148
|
-
const data = await this.load();
|
|
183
|
+
const data = structuredClone(await this.load());
|
|
149
184
|
const existing = data.profiles[name];
|
|
150
185
|
if (!existing)
|
|
151
186
|
throw new Error(`Profile "${name}" not found.`);
|
|
@@ -162,20 +197,45 @@ export class McpProfileRegistry {
|
|
|
162
197
|
if (changes.removeServers && changes.removeServers.length > 0) {
|
|
163
198
|
current.servers = current.servers.filter((s) => !(changes.removeServers ?? []).includes(s));
|
|
164
199
|
}
|
|
200
|
+
if (changes.clearToolFilters) {
|
|
201
|
+
const overrides = { ...(current.providerOverrides ?? {}) };
|
|
202
|
+
delete overrides[changes.clearToolFilters];
|
|
203
|
+
current.providerOverrides = overrides;
|
|
204
|
+
}
|
|
205
|
+
for (const [provider, override] of Object.entries(changes.providerOverrides ?? {})) {
|
|
206
|
+
const overrides = { ...(current.providerOverrides ?? {}) };
|
|
207
|
+
const previous = overrides[provider] ?? {};
|
|
208
|
+
const merge = (before, added) => (added ? [...new Set([...(before ?? []), ...added])] : before);
|
|
209
|
+
overrides[provider] = {
|
|
210
|
+
...previous,
|
|
211
|
+
...(override.toolDeny ? { toolDeny: merge(previous.toolDeny, override.toolDeny) } : {}),
|
|
212
|
+
...(override.toolAllow ? { toolAllow: merge(previous.toolAllow, override.toolAllow) } : {}),
|
|
213
|
+
};
|
|
214
|
+
current.providerOverrides = overrides;
|
|
215
|
+
}
|
|
165
216
|
current.updatedAt = new Date().toISOString();
|
|
166
217
|
data.profiles[name] = current;
|
|
167
|
-
await this.save();
|
|
218
|
+
await this.save(data);
|
|
168
219
|
return data.profiles[name];
|
|
169
220
|
}
|
|
170
221
|
async remove(name) {
|
|
171
|
-
const data = await this.load();
|
|
222
|
+
const data = structuredClone(await this.load());
|
|
172
223
|
if (!data.profiles[name])
|
|
173
224
|
throw new Error(`Profile "${name}" not found.`);
|
|
225
|
+
if (this.layering && isLowerLayerEntry(this.layering, name)) {
|
|
226
|
+
throw new Error(`Profile "${name}" is defined in a lower configuration layer; remove it there.`);
|
|
227
|
+
}
|
|
228
|
+
const dependents = Object.entries(data.profiles)
|
|
229
|
+
.filter(([other, profile]) => other !== name && profile.extends?.includes(name))
|
|
230
|
+
.map(([other]) => other);
|
|
231
|
+
if (dependents.length) {
|
|
232
|
+
throw new Error(`Cannot remove profile "${name}": extended by ${dependents.join(', ')}.`);
|
|
233
|
+
}
|
|
174
234
|
delete data.profiles[name];
|
|
175
|
-
await this.save();
|
|
235
|
+
await this.save(data);
|
|
176
236
|
}
|
|
177
237
|
async resolveServers(name, serverRegistry) {
|
|
178
|
-
const profile = await this.
|
|
238
|
+
const profile = await this.resolve(name);
|
|
179
239
|
if (!profile)
|
|
180
240
|
throw new Error(`Profile "${name}" not found.`);
|
|
181
241
|
if (profile.servers.includes('__all__') && serverRegistry) {
|
|
@@ -194,20 +254,27 @@ export class McpProfileRegistry {
|
|
|
194
254
|
async importFrom(filePath) {
|
|
195
255
|
const content = await readFile(filePath, 'utf-8');
|
|
196
256
|
const imported = JSON.parse(content);
|
|
197
|
-
const data = await this.load();
|
|
257
|
+
const data = structuredClone(await this.load());
|
|
198
258
|
let added = 0;
|
|
199
259
|
let updated = 0;
|
|
200
|
-
const
|
|
260
|
+
const invalidNames = [];
|
|
261
|
+
const profiles = imported.profiles ?? (typeof imported === 'object' && !Array.isArray(imported)
|
|
262
|
+
? imported
|
|
263
|
+
: {});
|
|
264
|
+
const candidate = { ...data.profiles };
|
|
201
265
|
for (const [name, profile] of Object.entries(profiles)) {
|
|
202
266
|
try {
|
|
203
267
|
this.validateName(name);
|
|
204
268
|
}
|
|
205
|
-
catch {
|
|
269
|
+
catch (error) {
|
|
270
|
+
// Collected and refused below with the complete graph-validation pass,
|
|
271
|
+
// instead of silently dropping named profiles from the import.
|
|
272
|
+
invalidNames.push(`${name}: ${error instanceof Error ? error.message : String(error)}`);
|
|
206
273
|
continue;
|
|
207
274
|
}
|
|
208
|
-
if (
|
|
209
|
-
|
|
210
|
-
...
|
|
275
|
+
if (candidate[name]) {
|
|
276
|
+
candidate[name] = {
|
|
277
|
+
...candidate[name],
|
|
211
278
|
...profile,
|
|
212
279
|
name,
|
|
213
280
|
updatedAt: new Date().toISOString(),
|
|
@@ -215,9 +282,10 @@ export class McpProfileRegistry {
|
|
|
215
282
|
updated++;
|
|
216
283
|
}
|
|
217
284
|
else {
|
|
218
|
-
|
|
285
|
+
candidate[name] = {
|
|
219
286
|
name,
|
|
220
287
|
description: profile.description,
|
|
288
|
+
...(profile.extends ? { extends: profile.extends } : {}),
|
|
221
289
|
servers: profile.servers ?? [],
|
|
222
290
|
providerOverrides: profile.providerOverrides ?? {},
|
|
223
291
|
createdAt: new Date().toISOString(),
|
|
@@ -226,7 +294,13 @@ export class McpProfileRegistry {
|
|
|
226
294
|
added++;
|
|
227
295
|
}
|
|
228
296
|
}
|
|
229
|
-
|
|
297
|
+
if (invalidNames.length) {
|
|
298
|
+
throw new Error(`Refusing to import profile(s) with invalid names: ${invalidNames.join('; ')}`);
|
|
299
|
+
}
|
|
300
|
+
for (const name of Object.keys(candidate))
|
|
301
|
+
resolveProfileExtends(name, candidate);
|
|
302
|
+
data.profiles = candidate;
|
|
303
|
+
await this.save(data);
|
|
230
304
|
return { added, updated };
|
|
231
305
|
}
|
|
232
306
|
async exportTo(filePath, profileName) {
|
|
@@ -240,7 +314,7 @@ export class McpProfileRegistry {
|
|
|
240
314
|
await writeFile(filePath, JSON.stringify({ profiles: toExport }, null, 2) + '\n', 'utf-8');
|
|
241
315
|
}
|
|
242
316
|
async initPresets() {
|
|
243
|
-
const data = await this.load();
|
|
317
|
+
const data = structuredClone(await this.load());
|
|
244
318
|
let added = 0;
|
|
245
319
|
for (const [name, preset] of Object.entries(PRESET_PROFILES)) {
|
|
246
320
|
if (!data.profiles[name]) {
|
|
@@ -253,7 +327,7 @@ export class McpProfileRegistry {
|
|
|
253
327
|
added++;
|
|
254
328
|
}
|
|
255
329
|
}
|
|
256
|
-
await this.save();
|
|
330
|
+
await this.save(data);
|
|
257
331
|
return { added, total: Object.keys(PRESET_PROFILES).length };
|
|
258
332
|
}
|
|
259
333
|
clearCache() {
|