mvge 0.1.1__tar.gz
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.
- mvge-0.1.1/PKG-INFO +18 -0
- mvge-0.1.1/pyproject.toml +112 -0
- mvge-0.1.1/pyproject.toml.orig +101 -0
- mvge-0.1.1/src/mvge/__init__.py +7 -0
- mvge-0.1.1/src/mvge/__main__.py +6 -0
- mvge-0.1.1/src/mvge/agent/__init__.py +100 -0
- mvge-0.1.1/src/mvge/agent/agent_name.py +152 -0
- mvge-0.1.1/src/mvge/agent/agent_session.py +712 -0
- mvge-0.1.1/src/mvge/agent/auth.py +111 -0
- mvge-0.1.1/src/mvge/agent/commands.py +773 -0
- mvge-0.1.1/src/mvge/agent/compatibility.py +175 -0
- mvge-0.1.1/src/mvge/agent/config_manager.py +331 -0
- mvge-0.1.1/src/mvge/agent/environment.py +775 -0
- mvge-0.1.1/src/mvge/agent/function_spell.py +546 -0
- mvge-0.1.1/src/mvge/agent/harness/__init__.py +35 -0
- mvge-0.1.1/src/mvge/agent/harness/_state.py +64 -0
- mvge-0.1.1/src/mvge/agent/harness/capabilities.py +66 -0
- mvge-0.1.1/src/mvge/agent/harness/compaction/__init__.py +35 -0
- mvge-0.1.1/src/mvge/agent/harness/compaction/compaction.py +400 -0
- mvge-0.1.1/src/mvge/agent/harness/compaction/compaction_runner.py +357 -0
- mvge-0.1.1/src/mvge/agent/harness/harness.py +1599 -0
- mvge-0.1.1/src/mvge/agent/harness/reload.py +121 -0
- mvge-0.1.1/src/mvge/agent/installer.py +495 -0
- mvge-0.1.1/src/mvge/agent/mvge.py +1536 -0
- mvge-0.1.1/src/mvge/agent/protocol.py +220 -0
- mvge-0.1.1/src/mvge/agent/py.typed +0 -0
- mvge-0.1.1/src/mvge/agent/rune_lifecycle.py +222 -0
- mvge-0.1.1/src/mvge/agent/sigil_payloads.py +100 -0
- mvge-0.1.1/src/mvge/agent/snapshot.py +230 -0
- mvge-0.1.1/src/mvge/agent/sub_mvge.py +406 -0
- mvge-0.1.1/src/mvge/cli/__init__.py +3 -0
- mvge-0.1.1/src/mvge/cli/__main__.py +6 -0
- mvge-0.1.1/src/mvge/cli/agent_factory.py +158 -0
- mvge-0.1.1/src/mvge/cli/agent_name.py +35 -0
- mvge-0.1.1/src/mvge/cli/approval_binding.py +145 -0
- mvge-0.1.1/src/mvge/cli/approval_presenter.py +584 -0
- mvge-0.1.1/src/mvge/cli/approval_types.py +72 -0
- mvge-0.1.1/src/mvge/cli/commands/__init__.py +1 -0
- mvge-0.1.1/src/mvge/cli/commands/build.py +184 -0
- mvge-0.1.1/src/mvge/cli/commands/config.py +200 -0
- mvge-0.1.1/src/mvge/cli/commands/dispatcher.py +183 -0
- mvge-0.1.1/src/mvge/cli/commands/info.py +273 -0
- mvge-0.1.1/src/mvge/cli/commands/mvge.py +96 -0
- mvge-0.1.1/src/mvge/cli/commands/repl.py +774 -0
- mvge-0.1.1/src/mvge/cli/commands/rune.py +95 -0
- mvge-0.1.1/src/mvge/cli/commands/setup.py +532 -0
- mvge-0.1.1/src/mvge/cli/commands/tome.py +437 -0
- mvge-0.1.1/src/mvge/cli/commands/tui.py +560 -0
- mvge-0.1.1/src/mvge/cli/console.py +74 -0
- mvge-0.1.1/src/mvge/cli/dynamic_commands.py +313 -0
- mvge-0.1.1/src/mvge/cli/formatting.py +344 -0
- mvge-0.1.1/src/mvge/cli/main.py +731 -0
- mvge-0.1.1/src/mvge/cli/py.typed +0 -0
- mvge-0.1.1/src/mvge/cli/rpc.py +479 -0
- mvge-0.1.1/src/mvge/core/__init__.py +211 -0
- mvge-0.1.1/src/mvge/core/abort.py +105 -0
- mvge-0.1.1/src/mvge/core/approval.py +221 -0
- mvge-0.1.1/src/mvge/core/channel.py +140 -0
- mvge-0.1.1/src/mvge/core/constants.py +69 -0
- mvge-0.1.1/src/mvge/core/dispatcher.py +656 -0
- mvge-0.1.1/src/mvge/core/errors.py +185 -0
- mvge-0.1.1/src/mvge/core/event_bus.py +45 -0
- mvge-0.1.1/src/mvge/core/events.py +92 -0
- mvge-0.1.1/src/mvge/core/invocations.py +135 -0
- mvge-0.1.1/src/mvge/core/layers.py +386 -0
- mvge-0.1.1/src/mvge/core/loop.py +511 -0
- mvge-0.1.1/src/mvge/core/py.typed +0 -0
- mvge-0.1.1/src/mvge/core/sandbox.py +342 -0
- mvge-0.1.1/src/mvge/core/spell_schema.py +176 -0
- mvge-0.1.1/src/mvge/core/spells.py +384 -0
- mvge-0.1.1/src/mvge/core/truncate.py +218 -0
- mvge-0.1.1/src/mvge/core/worklog.py +132 -0
- mvge-0.1.1/src/mvge/provider/__init__.py +74 -0
- mvge-0.1.1/src/mvge/provider/base.py +159 -0
- mvge-0.1.1/src/mvge/provider/model_registry.py +553 -0
- mvge-0.1.1/src/mvge/provider/models.json +8011 -0
- mvge-0.1.1/src/mvge/provider/py.typed +0 -0
- mvge-0.1.1/src/mvge/provider/realm_seam.py +212 -0
- mvge-0.1.1/src/mvge/provider/realms.py +197 -0
- mvge-0.1.1/src/mvge/provider/registry.py +454 -0
- mvge-0.1.1/src/mvge/provider/retry.py +551 -0
- mvge-0.1.1/src/mvge/provider/sse.py +755 -0
- mvge-0.1.1/src/mvge/py.typed +0 -0
- mvge-0.1.1/src/mvge/runes/__init__.py +112 -0
- mvge-0.1.1/src/mvge/runes/catalog.py +215 -0
- mvge-0.1.1/src/mvge/runes/codecs.py +139 -0
- mvge-0.1.1/src/mvge/runes/contract.py +240 -0
- mvge-0.1.1/src/mvge/runes/deps.py +168 -0
- mvge-0.1.1/src/mvge/runes/installer.py +415 -0
- mvge-0.1.1/src/mvge/runes/loader.py +442 -0
- mvge-0.1.1/src/mvge/runes/manifest.py +135 -0
- mvge-0.1.1/src/mvge/runes/py.typed +0 -0
- mvge-0.1.1/src/mvge/runes/rune_api.py +328 -0
- mvge-0.1.1/src/mvge/runes/rune_audit.py +174 -0
- mvge-0.1.1/src/mvge/runes/rune_runner.py +1123 -0
- mvge-0.1.1/src/mvge/runes/skill_catalog.py +96 -0
- mvge-0.1.1/src/mvge/runes/skill_installer.py +102 -0
- mvge-0.1.1/src/mvge/runes/trust.py +256 -0
- mvge-0.1.1/src/mvge/runes/types.py +585 -0
- mvge-0.1.1/src/mvge/runes/watcher.py +301 -0
- mvge-0.1.1/src/mvge/tome/__init__.py +29 -0
- mvge-0.1.1/src/mvge/tome/codec.py +305 -0
- mvge-0.1.1/src/mvge/tome/handle.py +1446 -0
- mvge-0.1.1/src/mvge/tome/py.typed +0 -0
- mvge-0.1.1/src/mvge/tome/types.py +145 -0
mvge-0.1.1/PKG-INFO
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: mvge
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: mvge — a Python-based AI coding agent
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
Requires-Dist: click>=8.0
|
|
7
|
+
Requires-Dist: httpx>=0.28.1
|
|
8
|
+
Requires-Dist: packaging>=24.0
|
|
9
|
+
Requires-Dist: portalocker>=3.0.0
|
|
10
|
+
Requires-Dist: prompt-toolkit>=3.0
|
|
11
|
+
Requires-Dist: pydantic>=2.0
|
|
12
|
+
Requires-Dist: python-dotenv>=1.2.2
|
|
13
|
+
Requires-Dist: pyyaml>=6.0
|
|
14
|
+
Requires-Dist: rich>=13.0
|
|
15
|
+
Requires-Dist: typer>=0.27.1
|
|
16
|
+
Requires-Dist: watchdog>=6.0.0
|
|
17
|
+
Requires-Python: >=3.13
|
|
18
|
+
Project-URL: Repository, https://github.com/IAmNo1Special/mvge
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "mvge"
|
|
3
|
+
version = "0.1.1"
|
|
4
|
+
description = "mvge — a Python-based AI coding agent"
|
|
5
|
+
requires-python = ">=3.13"
|
|
6
|
+
license = "MIT"
|
|
7
|
+
dependencies = [
|
|
8
|
+
"click>=8.0",
|
|
9
|
+
"httpx>=0.28.1",
|
|
10
|
+
"packaging>=24.0",
|
|
11
|
+
"portalocker>=3.0.0",
|
|
12
|
+
"prompt-toolkit>=3.0",
|
|
13
|
+
"pydantic>=2.0",
|
|
14
|
+
"python-dotenv>=1.2.2",
|
|
15
|
+
"pyyaml>=6.0",
|
|
16
|
+
"rich>=13.0",
|
|
17
|
+
"typer>=0.27.1",
|
|
18
|
+
"watchdog>=6.0.0",
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
[project.scripts]
|
|
22
|
+
mvge = "mvge.cli.main:main"
|
|
23
|
+
|
|
24
|
+
[project.urls]
|
|
25
|
+
Repository = "https://github.com/IAmNo1Special/mvge"
|
|
26
|
+
|
|
27
|
+
[build-system]
|
|
28
|
+
requires = ["uv_build>=0.11.2,<0.14"]
|
|
29
|
+
build-backend = "uv_build"
|
|
30
|
+
|
|
31
|
+
[dependency-groups]
|
|
32
|
+
dev = [
|
|
33
|
+
"git-cliff>=2.13.1",
|
|
34
|
+
"mypy>=2.3.1",
|
|
35
|
+
"pre-commit>=4.6.2",
|
|
36
|
+
"pytest>=9.1.1",
|
|
37
|
+
"pytest-asyncio>=1.4.0",
|
|
38
|
+
"pytest-cov>=7.1.0",
|
|
39
|
+
"ruff>=0.16.2",
|
|
40
|
+
"types-pyyaml>=6.0.12.20260815",
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
[tool.ruff]
|
|
44
|
+
line-length = 88
|
|
45
|
+
target-version = "py313"
|
|
46
|
+
exclude = ["prototypes"]
|
|
47
|
+
|
|
48
|
+
[tool.ruff.lint]
|
|
49
|
+
select = [
|
|
50
|
+
"E",
|
|
51
|
+
"W",
|
|
52
|
+
"F",
|
|
53
|
+
"I",
|
|
54
|
+
"N",
|
|
55
|
+
"UP",
|
|
56
|
+
"B",
|
|
57
|
+
"SIM",
|
|
58
|
+
"C4",
|
|
59
|
+
"PT",
|
|
60
|
+
"T20",
|
|
61
|
+
"RSE",
|
|
62
|
+
"PGH",
|
|
63
|
+
]
|
|
64
|
+
|
|
65
|
+
[tool.ruff.lint.per-file-ignores]
|
|
66
|
+
"tests/**" = [
|
|
67
|
+
"T20",
|
|
68
|
+
"PT001",
|
|
69
|
+
]
|
|
70
|
+
"*/__init__.py" = [
|
|
71
|
+
"E402",
|
|
72
|
+
"F401",
|
|
73
|
+
]
|
|
74
|
+
"scripts/**" = ["T20"]
|
|
75
|
+
|
|
76
|
+
[tool.mypy]
|
|
77
|
+
python_version = "3.13"
|
|
78
|
+
strict = true
|
|
79
|
+
warn_unreachable = true
|
|
80
|
+
exclude = [
|
|
81
|
+
"(^|/)(tests|scripts|prototypes)/",
|
|
82
|
+
'test_keys\.py',
|
|
83
|
+
"benchmarks",
|
|
84
|
+
]
|
|
85
|
+
|
|
86
|
+
[tool.pytest.ini_options]
|
|
87
|
+
testpaths = [
|
|
88
|
+
"tests",
|
|
89
|
+
"scripts/tests",
|
|
90
|
+
]
|
|
91
|
+
pythonpath = [
|
|
92
|
+
".",
|
|
93
|
+
"src",
|
|
94
|
+
"scripts",
|
|
95
|
+
"tests/unit/provider",
|
|
96
|
+
]
|
|
97
|
+
python_files = "*.py"
|
|
98
|
+
python_classes = "Test*"
|
|
99
|
+
python_functions = "test_*"
|
|
100
|
+
consider_namespace_packages = false
|
|
101
|
+
addopts = "--import-mode=importlib -W ignore::RuntimeWarning"
|
|
102
|
+
|
|
103
|
+
[tool.coverage.run]
|
|
104
|
+
branch = true
|
|
105
|
+
source = ["mvge"]
|
|
106
|
+
omit = [
|
|
107
|
+
"src/mvge/__main__.py",
|
|
108
|
+
"src/mvge/cli/__main__.py",
|
|
109
|
+
]
|
|
110
|
+
|
|
111
|
+
[tool.coverage.report]
|
|
112
|
+
fail_under = 90
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "mvge"
|
|
3
|
+
version = "0.1.1"
|
|
4
|
+
description = "mvge — a Python-based AI coding agent"
|
|
5
|
+
requires-python = ">=3.13"
|
|
6
|
+
license = "MIT"
|
|
7
|
+
dependencies = [
|
|
8
|
+
"click>=8.0",
|
|
9
|
+
"httpx>=0.28.1",
|
|
10
|
+
"packaging>=24.0",
|
|
11
|
+
"portalocker>=3.0.0",
|
|
12
|
+
"prompt-toolkit>=3.0",
|
|
13
|
+
"pydantic>=2.0",
|
|
14
|
+
"python-dotenv>=1.2.2",
|
|
15
|
+
"pyyaml>=6.0",
|
|
16
|
+
"rich>=13.0",
|
|
17
|
+
"typer>=0.27.1",
|
|
18
|
+
"watchdog>=6.0.0",
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
[project.scripts]
|
|
22
|
+
mvge = "mvge.cli.main:main"
|
|
23
|
+
|
|
24
|
+
[build-system]
|
|
25
|
+
requires = ["uv_build>=0.11.2,<0.14"]
|
|
26
|
+
build-backend = "uv_build"
|
|
27
|
+
|
|
28
|
+
[dependency-groups]
|
|
29
|
+
dev = [
|
|
30
|
+
"git-cliff>=2.13.1",
|
|
31
|
+
"mypy>=2.3.1",
|
|
32
|
+
"pre-commit>=4.6.2",
|
|
33
|
+
"pytest>=9.1.1",
|
|
34
|
+
"pytest-asyncio>=1.4.0",
|
|
35
|
+
"pytest-cov>=7.1.0",
|
|
36
|
+
"ruff>=0.16.2",
|
|
37
|
+
"types-pyyaml>=6.0.12.20260815",
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
[project.urls]
|
|
41
|
+
Repository = "https://github.com/IAmNo1Special/mvge"
|
|
42
|
+
|
|
43
|
+
[tool.ruff]
|
|
44
|
+
line-length = 88
|
|
45
|
+
target-version = "py313"
|
|
46
|
+
exclude = ["prototypes"]
|
|
47
|
+
|
|
48
|
+
[tool.ruff.lint]
|
|
49
|
+
select = [
|
|
50
|
+
"E",
|
|
51
|
+
"W",
|
|
52
|
+
"F",
|
|
53
|
+
"I",
|
|
54
|
+
"N",
|
|
55
|
+
"UP",
|
|
56
|
+
"B",
|
|
57
|
+
"SIM",
|
|
58
|
+
"C4",
|
|
59
|
+
"PT",
|
|
60
|
+
"T20",
|
|
61
|
+
"RSE",
|
|
62
|
+
"PGH",
|
|
63
|
+
]
|
|
64
|
+
|
|
65
|
+
[tool.ruff.lint.per-file-ignores]
|
|
66
|
+
"tests/**" = ["T20", "PT001"]
|
|
67
|
+
"*/__init__.py" = ["E402", "F401"]
|
|
68
|
+
# Release tooling reports to a terminal; printing is its interface, not a slip.
|
|
69
|
+
# mypy already excludes /scripts/ for the same reason.
|
|
70
|
+
"scripts/**" = ["T20"]
|
|
71
|
+
|
|
72
|
+
[tool.mypy]
|
|
73
|
+
python_version = "3.13"
|
|
74
|
+
strict = true
|
|
75
|
+
warn_unreachable = true
|
|
76
|
+
exclude = ["(^|/)(tests|scripts|prototypes)/", "test_keys\\.py", "benchmarks"]
|
|
77
|
+
|
|
78
|
+
[tool.pytest.ini_options]
|
|
79
|
+
testpaths = [
|
|
80
|
+
"tests",
|
|
81
|
+
"scripts/tests",
|
|
82
|
+
]
|
|
83
|
+
pythonpath = [".", "src", "scripts", "tests/unit/provider"]
|
|
84
|
+
python_files = "*.py"
|
|
85
|
+
python_classes = "Test*"
|
|
86
|
+
python_functions = "test_*"
|
|
87
|
+
consider_namespace_packages = false
|
|
88
|
+
addopts = "--import-mode=importlib -W ignore::RuntimeWarning"
|
|
89
|
+
|
|
90
|
+
[tool.coverage.run]
|
|
91
|
+
branch = true
|
|
92
|
+
source = [
|
|
93
|
+
"mvge",
|
|
94
|
+
]
|
|
95
|
+
omit = [
|
|
96
|
+
"src/mvge/__main__.py",
|
|
97
|
+
"src/mvge/cli/__main__.py",
|
|
98
|
+
]
|
|
99
|
+
|
|
100
|
+
[tool.coverage.report]
|
|
101
|
+
fail_under = 90
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
from mvge.agent.agent_session import MvgeTome
|
|
2
|
+
from mvge.agent.auth import load_api_key_from_auth, save_api_key_to_auth
|
|
3
|
+
from mvge.agent.commands import (
|
|
4
|
+
SLASH_COMMANDS,
|
|
5
|
+
CommandAction,
|
|
6
|
+
CommandDispatcher,
|
|
7
|
+
CommandOutcome,
|
|
8
|
+
)
|
|
9
|
+
from mvge.agent.environment import (
|
|
10
|
+
AgentConfig,
|
|
11
|
+
MvgeEnvironment,
|
|
12
|
+
PromptSource,
|
|
13
|
+
ResolvedPrompt,
|
|
14
|
+
)
|
|
15
|
+
from mvge.agent.function_spell import (
|
|
16
|
+
FunctionSpell,
|
|
17
|
+
PEP723Metadata,
|
|
18
|
+
PEP723ScriptSpell,
|
|
19
|
+
RuneSpellWrapper,
|
|
20
|
+
SpellUnion,
|
|
21
|
+
coerce_spell,
|
|
22
|
+
discover_spells_from_dir,
|
|
23
|
+
parse_pep723_metadata,
|
|
24
|
+
)
|
|
25
|
+
from mvge.agent.harness import MvgeHarness
|
|
26
|
+
from mvge.agent.installer import (
|
|
27
|
+
DEFAULT_PROFILES,
|
|
28
|
+
ProfileInstallResult,
|
|
29
|
+
fetch_marketplace_mvges,
|
|
30
|
+
fetch_marketplace_profiles,
|
|
31
|
+
install_mvge,
|
|
32
|
+
install_profile,
|
|
33
|
+
list_installed_mvges,
|
|
34
|
+
uninstall_mvge,
|
|
35
|
+
)
|
|
36
|
+
from mvge.agent.mvge import Mvge
|
|
37
|
+
from mvge.agent.protocol import AgentFactory, MvgeAgent, ReloadResult
|
|
38
|
+
from mvge.agent.snapshot import (
|
|
39
|
+
RuntimeSnapshot,
|
|
40
|
+
SnapshotConfigEntry,
|
|
41
|
+
SnapshotDiagnostic,
|
|
42
|
+
SnapshotPrompt,
|
|
43
|
+
SnapshotRune,
|
|
44
|
+
SnapshotSkill,
|
|
45
|
+
SnapshotSpell,
|
|
46
|
+
SpellSource,
|
|
47
|
+
)
|
|
48
|
+
from mvge.agent.sub_mvge import (
|
|
49
|
+
SubMvgeManager,
|
|
50
|
+
SubMvgeRecord,
|
|
51
|
+
SubMvgeSpec,
|
|
52
|
+
SubMvgeState,
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
__all__ = [
|
|
56
|
+
"AgentConfig",
|
|
57
|
+
"AgentFactory",
|
|
58
|
+
"CommandAction",
|
|
59
|
+
"CommandDispatcher",
|
|
60
|
+
"CommandOutcome",
|
|
61
|
+
"DEFAULT_PROFILES",
|
|
62
|
+
"FunctionSpell",
|
|
63
|
+
"Mvge",
|
|
64
|
+
"MvgeAgent",
|
|
65
|
+
"MvgeEnvironment",
|
|
66
|
+
"MvgeHarness",
|
|
67
|
+
"MvgeTome",
|
|
68
|
+
"PEP723Metadata",
|
|
69
|
+
"PEP723ScriptSpell",
|
|
70
|
+
"ProfileInstallResult",
|
|
71
|
+
"PromptSource",
|
|
72
|
+
"ResolvedPrompt",
|
|
73
|
+
"ReloadResult",
|
|
74
|
+
"RuneSpellWrapper",
|
|
75
|
+
"RuntimeSnapshot",
|
|
76
|
+
"SLASH_COMMANDS",
|
|
77
|
+
"SnapshotConfigEntry",
|
|
78
|
+
"SnapshotDiagnostic",
|
|
79
|
+
"SnapshotPrompt",
|
|
80
|
+
"SnapshotRune",
|
|
81
|
+
"SnapshotSkill",
|
|
82
|
+
"SnapshotSpell",
|
|
83
|
+
"SpellSource",
|
|
84
|
+
"SpellUnion",
|
|
85
|
+
"SubMvgeManager",
|
|
86
|
+
"SubMvgeRecord",
|
|
87
|
+
"SubMvgeSpec",
|
|
88
|
+
"SubMvgeState",
|
|
89
|
+
"coerce_spell",
|
|
90
|
+
"discover_spells_from_dir",
|
|
91
|
+
"fetch_marketplace_mvges",
|
|
92
|
+
"fetch_marketplace_profiles",
|
|
93
|
+
"install_mvge",
|
|
94
|
+
"install_profile",
|
|
95
|
+
"list_installed_mvges",
|
|
96
|
+
"load_api_key_from_auth",
|
|
97
|
+
"parse_pep723_metadata",
|
|
98
|
+
"save_api_key_to_auth",
|
|
99
|
+
"uninstall_mvge",
|
|
100
|
+
]
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
"""Which Mvge an invocation runs.
|
|
2
|
+
|
|
3
|
+
Every question about an agent name is answered here, because a second
|
|
4
|
+
answerer is how the engine came to carry a marketplace artifact's slug:
|
|
5
|
+
two call sites, two different rules, the same hardcoded name, and a
|
|
6
|
+
second precedence pass that ADR-0014 was written to end.
|
|
7
|
+
|
|
8
|
+
The engine discovers what is installed and never names one. That is what
|
|
9
|
+
lets the marketplace change its catalog, rename an artifact, or ship a
|
|
10
|
+
second Mvge without a release here.
|
|
11
|
+
|
|
12
|
+
The Summoner's own naming always wins. Discovery only speaks when they
|
|
13
|
+
said nothing actionable and there is nothing to guess wrong.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import re
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
|
|
21
|
+
from mvge.core.constants import DEFAULT_AGENT_NAME
|
|
22
|
+
from mvge.core.layers import agents_dir
|
|
23
|
+
|
|
24
|
+
AGENT_NAME_PATTERN = re.compile(r"^[a-zA-Z0-9_-]+$")
|
|
25
|
+
|
|
26
|
+
#: The one name the engine answers for without looking at the disk. It names
|
|
27
|
+
#: the engine's own fallback agent, not an artifact anyone ships, so it is a
|
|
28
|
+
#: constant rather than a discovery result.
|
|
29
|
+
ENGINE_DEFAULT_AGENT = DEFAULT_AGENT_NAME
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def installed_agent_names(*, agent_config_base: Path | None = None) -> list[str]:
|
|
33
|
+
"""Agent names with an agent-scope directory, sorted.
|
|
34
|
+
|
|
35
|
+
The agent scope is ``<global>/agents/<name>/``. Only directories count:
|
|
36
|
+
a file at that depth is not an agent, and `sys.path` receives the agent
|
|
37
|
+
root, so a stray file here is a latent import collision rather than a Mvge.
|
|
38
|
+
"""
|
|
39
|
+
base = agent_config_base if agent_config_base is not None else agents_dir()
|
|
40
|
+
if not base.is_dir():
|
|
41
|
+
return []
|
|
42
|
+
return sorted(child.name for child in base.iterdir() if child.is_dir())
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def is_known_agent(
|
|
46
|
+
agent_name: str,
|
|
47
|
+
*,
|
|
48
|
+
agent_config_base: Path | None = None,
|
|
49
|
+
project_dir: Path | None = None,
|
|
50
|
+
) -> bool:
|
|
51
|
+
"""Whether an agent name is valid and usable.
|
|
52
|
+
|
|
53
|
+
An agent is known when the name is syntactically right *and* something
|
|
54
|
+
backs it: the engine's own default, an agent-scope directory, or a
|
|
55
|
+
project-scope one. A syntactically valid name with nothing behind it is
|
|
56
|
+
a typo, and saying so is the point.
|
|
57
|
+
"""
|
|
58
|
+
if not isinstance(agent_name, str) or not AGENT_NAME_PATTERN.match(agent_name):
|
|
59
|
+
return False
|
|
60
|
+
|
|
61
|
+
if agent_name == DEFAULT_AGENT_NAME:
|
|
62
|
+
return True
|
|
63
|
+
|
|
64
|
+
base = (
|
|
65
|
+
agent_config_base.expanduser()
|
|
66
|
+
if agent_config_base is not None
|
|
67
|
+
else agents_dir()
|
|
68
|
+
)
|
|
69
|
+
if (base / agent_name).is_dir():
|
|
70
|
+
return True
|
|
71
|
+
|
|
72
|
+
proj_dir = project_dir or Path.cwd()
|
|
73
|
+
return (proj_dir / ".agents" / "agents" / agent_name).is_dir()
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def validate_agent_name(
|
|
77
|
+
agent_name: str,
|
|
78
|
+
*,
|
|
79
|
+
agent_config_base: Path | None = None,
|
|
80
|
+
project_dir: Path | None = None,
|
|
81
|
+
allow_create: bool = False,
|
|
82
|
+
) -> None:
|
|
83
|
+
"""Validate that an agent name exists and is syntactically valid.
|
|
84
|
+
|
|
85
|
+
Raises ValueError with an actionable message if invalid or unknown.
|
|
86
|
+
"""
|
|
87
|
+
if not isinstance(agent_name, str) or not AGENT_NAME_PATTERN.match(agent_name):
|
|
88
|
+
raise ValueError(
|
|
89
|
+
f"Invalid agent name '{agent_name}'. Agent names must contain "
|
|
90
|
+
"only alphanumeric characters, dashes, and underscores."
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
if allow_create:
|
|
94
|
+
return
|
|
95
|
+
|
|
96
|
+
if is_known_agent(
|
|
97
|
+
agent_name, agent_config_base=agent_config_base, project_dir=project_dir
|
|
98
|
+
):
|
|
99
|
+
return
|
|
100
|
+
|
|
101
|
+
base = (
|
|
102
|
+
agent_config_base.expanduser()
|
|
103
|
+
if agent_config_base is not None
|
|
104
|
+
else agents_dir()
|
|
105
|
+
)
|
|
106
|
+
agent_dir = base / agent_name
|
|
107
|
+
raise ValueError(
|
|
108
|
+
f"Unknown agent '{agent_name}'. Agent configuration or directory "
|
|
109
|
+
f"does not exist at '{agent_dir}'. Configure it with "
|
|
110
|
+
f"'mvge config set --agent-name {agent_name} <key> <value>'."
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def resolve_active_agent_name(
|
|
115
|
+
requested: str = DEFAULT_AGENT_NAME,
|
|
116
|
+
*,
|
|
117
|
+
agent_config_base: Path | None = None,
|
|
118
|
+
) -> str:
|
|
119
|
+
"""The Mvge an invocation runs, given what the Summoner asked for.
|
|
120
|
+
|
|
121
|
+
Three rules, in order. Each is a reason not to guess.
|
|
122
|
+
|
|
123
|
+
1. **What they asked for, when it is backed.** A name with an agent-scope
|
|
124
|
+
directory is that Mvge, spelled their way.
|
|
125
|
+
2. **What is installed, when they named nothing.** ``DEFAULT_AGENT_NAME``
|
|
126
|
+
means the Summoner asked for no particular Mvge. With exactly one
|
|
127
|
+
agent in the agent scope, adopt it: one candidate means there is
|
|
128
|
+
nothing to guess wrong, and it is how a first run works with an
|
|
129
|
+
installed Mvge and no flag.
|
|
130
|
+
3. **Their name, unchanged.** Any name other than the default was a
|
|
131
|
+
request, so it survives even when nothing backs it -- an unknown name
|
|
132
|
+
then surfaces where it is actionable instead of being silently swapped
|
|
133
|
+
for another. Two or more installed agents is the same case: a real
|
|
134
|
+
choice, and an engine that picks for you here is the second precedence
|
|
135
|
+
pass this module exists to end.
|
|
136
|
+
"""
|
|
137
|
+
if not isinstance(requested, str) or not AGENT_NAME_PATTERN.match(requested):
|
|
138
|
+
raise ValueError(
|
|
139
|
+
f"Invalid agent name '{requested}'. Agent names must contain "
|
|
140
|
+
"only alphanumeric characters, dashes, and underscores."
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
base = agent_config_base if agent_config_base is not None else agents_dir()
|
|
144
|
+
if (base / requested).is_dir():
|
|
145
|
+
return requested
|
|
146
|
+
|
|
147
|
+
if requested == DEFAULT_AGENT_NAME:
|
|
148
|
+
candidates = installed_agent_names(agent_config_base=agent_config_base)
|
|
149
|
+
if len(candidates) == 1:
|
|
150
|
+
return candidates[0]
|
|
151
|
+
|
|
152
|
+
return requested
|