vsh-python 0.3.1__cp313-cp313-win_amd64.whl
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.
- THIRD_PARTY_NOTICES.md +28 -0
- vsh/__init__.py +39 -0
- vsh/__main__.py +6 -0
- vsh/_native.cp313-win_amd64.pyd +0 -0
- vsh/_native.pyi +204 -0
- vsh/_version.py +5 -0
- vsh/cli.py +56 -0
- vsh/mcp/__init__.py +5 -0
- vsh/mcp/codemode_server.py +125 -0
- vsh/mcp/native_tools.py +157 -0
- vsh/mcp/prompts.py +32 -0
- vsh/mcp/server.py +8 -0
- vsh/mcp/surface.py +17 -0
- vsh/py.typed +1 -0
- vsh_python-0.3.1.data/scripts/vsh-monty-worker.exe +0 -0
- vsh_python-0.3.1.dist-info/METADATA +199 -0
- vsh_python-0.3.1.dist-info/RECORD +21 -0
- vsh_python-0.3.1.dist-info/WHEEL +4 -0
- vsh_python-0.3.1.dist-info/entry_points.txt +3 -0
- vsh_python-0.3.1.dist-info/licenses/LICENSE +190 -0
- vsh_python-0.3.1.dist-info/sboms/vsh-python.cyclonedx.json +8538 -0
THIRD_PARTY_NOTICES.md
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Third-party notices
|
|
2
|
+
|
|
3
|
+
VSH links and adapts the public typed execution/protocol seams of Monty 0.0.21,
|
|
4
|
+
including the `monty`, `monty-types`, `monty-proto`, and `monty-alloc` crates.
|
|
5
|
+
Monty is maintained by Pydantic and distributed under the MIT License:
|
|
6
|
+
https://github.com/pydantic/monty/tree/v0.0.21
|
|
7
|
+
|
|
8
|
+
The MIT License (MIT)
|
|
9
|
+
|
|
10
|
+
Copyright (c) 2017 to present Pydantic Services Inc. and individual contributors.
|
|
11
|
+
|
|
12
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|
13
|
+
this software and associated documentation files (the "Software"), to deal in
|
|
14
|
+
the Software without restriction, including without limitation the rights to
|
|
15
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
|
16
|
+
the Software, and to permit persons to whom the Software is furnished to do so,
|
|
17
|
+
subject to the following conditions:
|
|
18
|
+
|
|
19
|
+
The above copyright notice and this permission notice shall be included in all
|
|
20
|
+
copies or substantial portions of the Software.
|
|
21
|
+
|
|
22
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
23
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
24
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
25
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
26
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
27
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
28
|
+
SOFTWARE.
|
vsh/__init__.py
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
from __future__ import annotations as _annotations
|
|
2
|
+
|
|
3
|
+
from ._native import (
|
|
4
|
+
ExecutionBudget,
|
|
5
|
+
Receipt,
|
|
6
|
+
ReceiptDetail,
|
|
7
|
+
RecoveryReport,
|
|
8
|
+
RunMode,
|
|
9
|
+
RunRequest,
|
|
10
|
+
Runtime,
|
|
11
|
+
VshExecutionError,
|
|
12
|
+
VshInternalError,
|
|
13
|
+
VshRecoveryError,
|
|
14
|
+
VshRuntimeError,
|
|
15
|
+
VshStaleError,
|
|
16
|
+
VshStateError,
|
|
17
|
+
engine_kind,
|
|
18
|
+
normalize_path,
|
|
19
|
+
)
|
|
20
|
+
from ._version import __version__
|
|
21
|
+
|
|
22
|
+
__all__ = (
|
|
23
|
+
"__version__",
|
|
24
|
+
"engine_kind",
|
|
25
|
+
"ExecutionBudget",
|
|
26
|
+
"normalize_path",
|
|
27
|
+
"Receipt",
|
|
28
|
+
"ReceiptDetail",
|
|
29
|
+
"RecoveryReport",
|
|
30
|
+
"RunMode",
|
|
31
|
+
"RunRequest",
|
|
32
|
+
"Runtime",
|
|
33
|
+
"VshExecutionError",
|
|
34
|
+
"VshInternalError",
|
|
35
|
+
"VshRecoveryError",
|
|
36
|
+
"VshRuntimeError",
|
|
37
|
+
"VshStaleError",
|
|
38
|
+
"VshStateError",
|
|
39
|
+
)
|
vsh/__main__.py
ADDED
|
Binary file
|
vsh/_native.pyi
ADDED
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
"""Typed surface of the PyO3-backed VSH native module."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Mapping
|
|
6
|
+
from enum import Enum
|
|
7
|
+
from os import PathLike
|
|
8
|
+
from typing import overload
|
|
9
|
+
|
|
10
|
+
__version__: str
|
|
11
|
+
|
|
12
|
+
class VshRuntimeError(RuntimeError):
|
|
13
|
+
"""Base exception for typed native VSH failures."""
|
|
14
|
+
|
|
15
|
+
class VshExecutionError(VshRuntimeError):
|
|
16
|
+
"""Monty compilation, execution, or hard-budget failure."""
|
|
17
|
+
|
|
18
|
+
class VshStateError(VshRuntimeError):
|
|
19
|
+
"""Transaction lifecycle, approval, reservation, or replay failure."""
|
|
20
|
+
|
|
21
|
+
class VshStaleError(VshRuntimeError):
|
|
22
|
+
"""Host dependencies changed after virtual execution."""
|
|
23
|
+
|
|
24
|
+
class VshRecoveryError(VshRuntimeError):
|
|
25
|
+
"""Durable recovery is required or could not prove ownership."""
|
|
26
|
+
|
|
27
|
+
class VshInternalError(VshRuntimeError):
|
|
28
|
+
"""A contained internal panic or invariant failure."""
|
|
29
|
+
|
|
30
|
+
class RunMode(Enum):
|
|
31
|
+
PREVIEW: RunMode
|
|
32
|
+
AUTO: RunMode
|
|
33
|
+
|
|
34
|
+
class ReceiptDetail(Enum):
|
|
35
|
+
COMPACT: ReceiptDetail
|
|
36
|
+
FULL: ReceiptDetail
|
|
37
|
+
|
|
38
|
+
class ExecutionBudget:
|
|
39
|
+
def __init__(
|
|
40
|
+
self,
|
|
41
|
+
*,
|
|
42
|
+
max_program_bytes: int | None = ...,
|
|
43
|
+
max_duration_ms: int | None = ...,
|
|
44
|
+
max_recursion_depth: int | None = ...,
|
|
45
|
+
max_memory_bytes: int | None = ...,
|
|
46
|
+
max_os_calls: int | None = ...,
|
|
47
|
+
max_read_bytes: int | None = ...,
|
|
48
|
+
max_write_bytes: int | None = ...,
|
|
49
|
+
max_io_call_bytes: int | None = ...,
|
|
50
|
+
max_path_bytes: int | None = ...,
|
|
51
|
+
max_directory_entries: int | None = ...,
|
|
52
|
+
max_output_bytes: int | None = ...,
|
|
53
|
+
max_result_bytes: int | None = ...,
|
|
54
|
+
max_exception_bytes: int | None = ...,
|
|
55
|
+
) -> None: ...
|
|
56
|
+
@property
|
|
57
|
+
def max_program_bytes(self) -> int: ...
|
|
58
|
+
@property
|
|
59
|
+
def max_duration_ms(self) -> int: ...
|
|
60
|
+
@property
|
|
61
|
+
def max_recursion_depth(self) -> int: ...
|
|
62
|
+
@property
|
|
63
|
+
def max_memory_bytes(self) -> int: ...
|
|
64
|
+
@property
|
|
65
|
+
def max_os_calls(self) -> int: ...
|
|
66
|
+
@property
|
|
67
|
+
def max_read_bytes(self) -> int: ...
|
|
68
|
+
@property
|
|
69
|
+
def max_write_bytes(self) -> int: ...
|
|
70
|
+
@property
|
|
71
|
+
def max_io_call_bytes(self) -> int: ...
|
|
72
|
+
@property
|
|
73
|
+
def max_path_bytes(self) -> int: ...
|
|
74
|
+
@property
|
|
75
|
+
def max_directory_entries(self) -> int: ...
|
|
76
|
+
@property
|
|
77
|
+
def max_output_bytes(self) -> int: ...
|
|
78
|
+
@property
|
|
79
|
+
def max_result_bytes(self) -> int: ...
|
|
80
|
+
@property
|
|
81
|
+
def max_exception_bytes(self) -> int: ...
|
|
82
|
+
|
|
83
|
+
class RunRequest:
|
|
84
|
+
def __init__(
|
|
85
|
+
self,
|
|
86
|
+
code: str,
|
|
87
|
+
*,
|
|
88
|
+
intent: str | None = ...,
|
|
89
|
+
mode: RunMode | None = ...,
|
|
90
|
+
detail: ReceiptDetail | None = ...,
|
|
91
|
+
budget: ExecutionBudget | None = ...,
|
|
92
|
+
) -> None: ...
|
|
93
|
+
@property
|
|
94
|
+
def code(self) -> str: ...
|
|
95
|
+
@property
|
|
96
|
+
def intent(self) -> str | None: ...
|
|
97
|
+
@property
|
|
98
|
+
def mode(self) -> RunMode: ...
|
|
99
|
+
@property
|
|
100
|
+
def detail(self) -> ReceiptDetail: ...
|
|
101
|
+
@property
|
|
102
|
+
def budget(self) -> ExecutionBudget: ...
|
|
103
|
+
|
|
104
|
+
class Receipt:
|
|
105
|
+
@property
|
|
106
|
+
def transaction(self) -> str: ...
|
|
107
|
+
@property
|
|
108
|
+
def base_snapshot(self) -> str: ...
|
|
109
|
+
@property
|
|
110
|
+
def state(self) -> str: ...
|
|
111
|
+
@property
|
|
112
|
+
def decision(self) -> str: ...
|
|
113
|
+
@property
|
|
114
|
+
def diff(self) -> str: ...
|
|
115
|
+
@property
|
|
116
|
+
def changed_paths(self) -> int: ...
|
|
117
|
+
@property
|
|
118
|
+
def changes(self) -> list[tuple[str, str]]: ...
|
|
119
|
+
@property
|
|
120
|
+
def result(self) -> object: ...
|
|
121
|
+
@property
|
|
122
|
+
def result_repr(self) -> str: ...
|
|
123
|
+
@property
|
|
124
|
+
def stdout(self) -> str: ...
|
|
125
|
+
@property
|
|
126
|
+
def risk_flags(self) -> list[str]: ...
|
|
127
|
+
@property
|
|
128
|
+
def deny_reason(self) -> str | None: ...
|
|
129
|
+
@property
|
|
130
|
+
def os_calls(self) -> int: ...
|
|
131
|
+
@property
|
|
132
|
+
def read_bytes(self) -> int: ...
|
|
133
|
+
@property
|
|
134
|
+
def write_bytes(self) -> int: ...
|
|
135
|
+
@property
|
|
136
|
+
def directory_entries(self) -> int: ...
|
|
137
|
+
@property
|
|
138
|
+
def output_bytes(self) -> int: ...
|
|
139
|
+
@property
|
|
140
|
+
def denied_accesses(self) -> int: ...
|
|
141
|
+
@property
|
|
142
|
+
def result_bytes(self) -> int: ...
|
|
143
|
+
@property
|
|
144
|
+
def committed(self) -> bool: ...
|
|
145
|
+
@property
|
|
146
|
+
def commit_operations(self) -> int | None: ...
|
|
147
|
+
@property
|
|
148
|
+
def verified_paths(self) -> int | None: ...
|
|
149
|
+
@property
|
|
150
|
+
def cleanup_pending(self) -> bool: ...
|
|
151
|
+
def timings_ns(self) -> Mapping[str, int]: ...
|
|
152
|
+
|
|
153
|
+
class RecoveryReport:
|
|
154
|
+
@property
|
|
155
|
+
def finalized_commits(self) -> int: ...
|
|
156
|
+
@property
|
|
157
|
+
def rolled_back(self) -> int: ...
|
|
158
|
+
@property
|
|
159
|
+
def cleaned(self) -> int: ...
|
|
160
|
+
@property
|
|
161
|
+
def orphaned(self) -> int: ...
|
|
162
|
+
@property
|
|
163
|
+
def conflicts(self) -> list[tuple[str, str | None, str]]: ...
|
|
164
|
+
|
|
165
|
+
class Runtime:
|
|
166
|
+
@staticmethod
|
|
167
|
+
def open(
|
|
168
|
+
workspace: str | PathLike[str],
|
|
169
|
+
*,
|
|
170
|
+
data_directory: str | PathLike[str] | None = ...,
|
|
171
|
+
policy: str = ...,
|
|
172
|
+
worker_path: str | PathLike[str] | None = ...,
|
|
173
|
+
) -> Runtime: ...
|
|
174
|
+
def run(self, request: RunRequest) -> Receipt: ...
|
|
175
|
+
@overload
|
|
176
|
+
def preview(self, request: RunRequest) -> Receipt: ...
|
|
177
|
+
@overload
|
|
178
|
+
def preview(
|
|
179
|
+
self,
|
|
180
|
+
request: str,
|
|
181
|
+
*,
|
|
182
|
+
intent: str | None = ...,
|
|
183
|
+
detail: ReceiptDetail | None = ...,
|
|
184
|
+
budget: ExecutionBudget | None = ...,
|
|
185
|
+
) -> Receipt: ...
|
|
186
|
+
def discard_preview(self, transaction: str) -> bool: ...
|
|
187
|
+
def approve(
|
|
188
|
+
self,
|
|
189
|
+
transaction: str,
|
|
190
|
+
principal: str,
|
|
191
|
+
issued_at_unix_ms: int,
|
|
192
|
+
expires_at_unix_ms: int,
|
|
193
|
+
) -> str: ...
|
|
194
|
+
def commit(self, transaction: str, now_unix_ms: int) -> Receipt: ...
|
|
195
|
+
def recover(self) -> RecoveryReport: ...
|
|
196
|
+
|
|
197
|
+
def version() -> str:
|
|
198
|
+
"""Return the native VSH semantic version."""
|
|
199
|
+
|
|
200
|
+
def engine_kind() -> str:
|
|
201
|
+
"""Return the native engine identity."""
|
|
202
|
+
|
|
203
|
+
def normalize_path(path: str) -> str:
|
|
204
|
+
"""Normalize a workspace-relative virtual path."""
|
vsh/_version.py
ADDED
vsh/cli.py
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""Dependency-free CLI composition over the native PyO3 runtime."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import json
|
|
7
|
+
from collections.abc import Sequence
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
from . import __version__
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _parser() -> argparse.ArgumentParser:
|
|
14
|
+
parser = argparse.ArgumentParser(prog="vsh", description="Run VSH's native Rust engine")
|
|
15
|
+
parser.add_argument("--version", action="version", version=__version__)
|
|
16
|
+
commands = parser.add_subparsers(dest="command", required=True)
|
|
17
|
+
|
|
18
|
+
run = commands.add_parser("run", help="run one Monty transaction")
|
|
19
|
+
source = run.add_mutually_exclusive_group(required=True)
|
|
20
|
+
source.add_argument("--code", help="Monty source text")
|
|
21
|
+
source.add_argument("--file", type=Path, help="read Monty source from a UTF-8 file")
|
|
22
|
+
source.add_argument("--transaction", help="promote an exact preview transaction")
|
|
23
|
+
run.add_argument("--workspace", type=Path, default=Path.cwd())
|
|
24
|
+
run.add_argument("--intent")
|
|
25
|
+
run.add_argument("--mode", choices=("preview", "auto"), default="preview")
|
|
26
|
+
run.add_argument("--policy", choices=("balanced", "strict", "paranoid"), default="balanced")
|
|
27
|
+
run.add_argument("--detail", choices=("compact", "full"), default="compact")
|
|
28
|
+
|
|
29
|
+
commands.add_parser("serve", help="serve the single-tool MCP surface over stdio")
|
|
30
|
+
return parser
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def main(argv: Sequence[str] | None = None) -> None:
|
|
34
|
+
"""Run the VSH CLI."""
|
|
35
|
+
arguments = _parser().parse_args(argv)
|
|
36
|
+
if arguments.command == "serve":
|
|
37
|
+
from .mcp.server import mcp
|
|
38
|
+
|
|
39
|
+
mcp.run()
|
|
40
|
+
return
|
|
41
|
+
|
|
42
|
+
from .mcp.native_tools import vsh_run
|
|
43
|
+
|
|
44
|
+
code = arguments.code
|
|
45
|
+
if arguments.file is not None:
|
|
46
|
+
code = arguments.file.read_text(encoding="utf-8")
|
|
47
|
+
payload = vsh_run(
|
|
48
|
+
code,
|
|
49
|
+
transaction=arguments.transaction,
|
|
50
|
+
workspace_root=str(arguments.workspace),
|
|
51
|
+
intent=arguments.intent,
|
|
52
|
+
mode=arguments.mode,
|
|
53
|
+
policy=arguments.policy,
|
|
54
|
+
detail=arguments.detail,
|
|
55
|
+
)
|
|
56
|
+
print(json.dumps(payload, ensure_ascii=False, separators=(",", ":")))
|
vsh/mcp/__init__.py
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
from __future__ import annotations as _annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from fastmcp import FastMCP
|
|
7
|
+
|
|
8
|
+
from vsh import __version__
|
|
9
|
+
|
|
10
|
+
from .prompts import register_codemode_prompts
|
|
11
|
+
from .surface import register_vsh_agent_surface, register_vsh_surface
|
|
12
|
+
|
|
13
|
+
CODEMODE_SERVER_NAME = "vsh-codemode"
|
|
14
|
+
|
|
15
|
+
CODEMODE_INSTRUCTIONS = """\
|
|
16
|
+
vsh CodeMode MCP server.
|
|
17
|
+
|
|
18
|
+
The server exposes exactly one normal tool: `vsh_run`. Its `code` argument is one Monty
|
|
19
|
+
Python program executed against an immutable workspace snapshot and copy-on-write Rust
|
|
20
|
+
VirtualFs. Use pathlib-style filesystem operations under `/workspace`.
|
|
21
|
+
|
|
22
|
+
`mode="preview"` guarantees no host mutation. `mode="auto"` asks the native policy to
|
|
23
|
+
commit the exact canonical diff; denied and escalated transactions remain virtual. Put
|
|
24
|
+
the complete multi-file operation in one program so it stays one transaction, one policy
|
|
25
|
+
decision, and one Python-to-Rust boundary call. To promote an auto-approved preview, pass
|
|
26
|
+
its returned `transaction` with no code and `mode="auto"`; VSH revalidates dependencies
|
|
27
|
+
before commit. Never emulate a shell or use a second simulation path.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
_CUSTOM_SECTION_HEADER = "Project-specific instructions:"
|
|
31
|
+
|
|
32
|
+
__all__ = (
|
|
33
|
+
"CODEMODE_INSTRUCTIONS",
|
|
34
|
+
"CODEMODE_SERVER_NAME",
|
|
35
|
+
"build_codemode_instructions",
|
|
36
|
+
"codemode_mcp",
|
|
37
|
+
"create_agent_codemode_server",
|
|
38
|
+
"create_codemode_server",
|
|
39
|
+
"load_custom_instructions",
|
|
40
|
+
"main",
|
|
41
|
+
"run_codemode_server",
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def build_codemode_instructions(*, custom_instructions: str | None = None) -> str:
|
|
46
|
+
"""Merge built-in CodeMode guidance with optional project-specific text."""
|
|
47
|
+
if custom_instructions is None:
|
|
48
|
+
return CODEMODE_INSTRUCTIONS
|
|
49
|
+
|
|
50
|
+
trimmed = custom_instructions.strip()
|
|
51
|
+
if not trimmed:
|
|
52
|
+
return CODEMODE_INSTRUCTIONS
|
|
53
|
+
|
|
54
|
+
return f"{CODEMODE_INSTRUCTIONS.rstrip()}\n\n---\n\n{_CUSTOM_SECTION_HEADER}\n{trimmed}\n"
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def load_custom_instructions(
|
|
58
|
+
*,
|
|
59
|
+
inline: str | None = None,
|
|
60
|
+
instructions_file: str | Path | None = None,
|
|
61
|
+
) -> str | None:
|
|
62
|
+
"""Resolve custom instructions from CLI args or environment variables."""
|
|
63
|
+
parts: list[str] = []
|
|
64
|
+
|
|
65
|
+
if instructions_file is not None:
|
|
66
|
+
parts.append(Path(instructions_file).read_text(encoding="utf-8").strip())
|
|
67
|
+
else:
|
|
68
|
+
env_file = os.environ.get("VSH_CODEMODE_INSTRUCTIONS_FILE")
|
|
69
|
+
if env_file:
|
|
70
|
+
parts.append(Path(env_file).read_text(encoding="utf-8").strip())
|
|
71
|
+
|
|
72
|
+
if inline is not None:
|
|
73
|
+
parts.append(inline.strip())
|
|
74
|
+
else:
|
|
75
|
+
env_inline = os.environ.get("VSH_CODEMODE_INSTRUCTIONS")
|
|
76
|
+
if env_inline:
|
|
77
|
+
parts.append(env_inline.strip())
|
|
78
|
+
|
|
79
|
+
merged = "\n\n".join(part for part in parts if part)
|
|
80
|
+
return merged or None
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def create_codemode_server(*, custom_instructions: str | None = None) -> FastMCP:
|
|
84
|
+
"""Build the CodeMode-oriented FastMCP server."""
|
|
85
|
+
server = FastMCP(
|
|
86
|
+
CODEMODE_SERVER_NAME,
|
|
87
|
+
instructions=build_codemode_instructions(custom_instructions=custom_instructions),
|
|
88
|
+
version=__version__,
|
|
89
|
+
)
|
|
90
|
+
register_vsh_surface(server)
|
|
91
|
+
register_codemode_prompts(server)
|
|
92
|
+
return server
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def create_agent_codemode_server() -> FastMCP:
|
|
96
|
+
"""Build a minimal CodeMode MCP server for pydantic-ai agent runs."""
|
|
97
|
+
server = FastMCP(
|
|
98
|
+
CODEMODE_SERVER_NAME,
|
|
99
|
+
instructions=None,
|
|
100
|
+
version=__version__,
|
|
101
|
+
)
|
|
102
|
+
register_vsh_agent_surface(server)
|
|
103
|
+
return server
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def run_codemode_server(
|
|
107
|
+
*,
|
|
108
|
+
inline: str | None = None,
|
|
109
|
+
instructions_file: str | Path | None = None,
|
|
110
|
+
) -> None:
|
|
111
|
+
"""Run the CodeMode MCP server, optionally with custom instructions."""
|
|
112
|
+
custom = load_custom_instructions(inline=inline, instructions_file=instructions_file)
|
|
113
|
+
if custom is None:
|
|
114
|
+
codemode_mcp.run()
|
|
115
|
+
return
|
|
116
|
+
|
|
117
|
+
create_codemode_server(custom_instructions=custom).run()
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
codemode_mcp = create_codemode_server()
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def main() -> None:
|
|
124
|
+
"""Run the vsh CodeMode MCP server over stdio."""
|
|
125
|
+
run_codemode_server()
|
vsh/mcp/native_tools.py
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
"""One compact MCP tool over the PyO3-backed VSH runtime."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import time
|
|
7
|
+
from functools import lru_cache
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Literal, TypedDict
|
|
10
|
+
|
|
11
|
+
from vsh import ExecutionBudget, Receipt, ReceiptDetail, RunMode, RunRequest, Runtime
|
|
12
|
+
|
|
13
|
+
RunModeName = Literal["preview", "auto"]
|
|
14
|
+
PolicyName = Literal["balanced", "strict", "paranoid"]
|
|
15
|
+
DetailName = Literal["compact", "full"]
|
|
16
|
+
|
|
17
|
+
_MAX_INLINE_CHARS = 64 * 1024
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class BudgetOverrides(TypedDict, total=False):
|
|
21
|
+
"""Optional native execution-budget overrides accepted by ``vsh_run``."""
|
|
22
|
+
|
|
23
|
+
max_program_bytes: int
|
|
24
|
+
max_duration_ms: int
|
|
25
|
+
max_recursion_depth: int
|
|
26
|
+
max_memory_bytes: int
|
|
27
|
+
max_os_calls: int
|
|
28
|
+
max_read_bytes: int
|
|
29
|
+
max_write_bytes: int
|
|
30
|
+
max_io_call_bytes: int
|
|
31
|
+
max_path_bytes: int
|
|
32
|
+
max_directory_entries: int
|
|
33
|
+
max_output_bytes: int
|
|
34
|
+
max_result_bytes: int
|
|
35
|
+
max_exception_bytes: int
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@lru_cache(maxsize=16)
|
|
39
|
+
def _runtime_for(workspace: str, policy: PolicyName, worker_identity: str | None) -> Runtime:
|
|
40
|
+
# ``worker_identity`` intentionally participates in cache identity. Runtime.open resolves
|
|
41
|
+
# the trusted path itself, including wheel-local scripts, and the model cannot override it.
|
|
42
|
+
del worker_identity
|
|
43
|
+
return Runtime.open(workspace, policy=policy)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _bounded_text(value: str) -> tuple[str, bool]:
|
|
47
|
+
if len(value) <= _MAX_INLINE_CHARS:
|
|
48
|
+
return value, False
|
|
49
|
+
return f"{value[:_MAX_INLINE_CHARS]}…", True
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _receipt_payload(receipt: Receipt) -> dict[str, object]:
|
|
53
|
+
result_repr, result_truncated = _bounded_text(receipt.result_repr)
|
|
54
|
+
stdout, stdout_truncated = _bounded_text(receipt.stdout)
|
|
55
|
+
return {
|
|
56
|
+
"transaction": receipt.transaction,
|
|
57
|
+
"base_snapshot": receipt.base_snapshot,
|
|
58
|
+
"state": receipt.state,
|
|
59
|
+
"decision": receipt.decision,
|
|
60
|
+
"diff": receipt.diff,
|
|
61
|
+
"changed_paths": receipt.changed_paths,
|
|
62
|
+
"changes": [{"path": path, "kind": kind} for path, kind in receipt.changes],
|
|
63
|
+
"result_repr": result_repr,
|
|
64
|
+
"result_truncated": result_truncated,
|
|
65
|
+
"stdout": stdout,
|
|
66
|
+
"stdout_truncated": stdout_truncated,
|
|
67
|
+
"risk_flags": list(receipt.risk_flags),
|
|
68
|
+
"deny_reason": receipt.deny_reason,
|
|
69
|
+
"execution": {
|
|
70
|
+
"os_calls": receipt.os_calls,
|
|
71
|
+
"read_bytes": receipt.read_bytes,
|
|
72
|
+
"write_bytes": receipt.write_bytes,
|
|
73
|
+
"directory_entries": receipt.directory_entries,
|
|
74
|
+
"output_bytes": receipt.output_bytes,
|
|
75
|
+
"denied_accesses": receipt.denied_accesses,
|
|
76
|
+
"result_bytes": receipt.result_bytes,
|
|
77
|
+
},
|
|
78
|
+
"commit": {
|
|
79
|
+
"committed": receipt.committed,
|
|
80
|
+
"operations": receipt.commit_operations,
|
|
81
|
+
"verified_paths": receipt.verified_paths,
|
|
82
|
+
"cleanup_pending": receipt.cleanup_pending,
|
|
83
|
+
},
|
|
84
|
+
"timings_ns": dict(receipt.timings_ns()),
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def vsh_run(
|
|
89
|
+
code: str | None = None,
|
|
90
|
+
*,
|
|
91
|
+
transaction: str | None = None,
|
|
92
|
+
workspace_root: str | None = None,
|
|
93
|
+
intent: str | None = None,
|
|
94
|
+
mode: RunModeName = "preview",
|
|
95
|
+
policy: PolicyName = "balanced",
|
|
96
|
+
detail: DetailName = "compact",
|
|
97
|
+
budget: BudgetOverrides | None = None,
|
|
98
|
+
) -> dict[str, object]:
|
|
99
|
+
"""Execute Monty code against one Rust VirtualFs transaction.
|
|
100
|
+
|
|
101
|
+
``preview`` never changes host files. A later call may promote its exact artifact by passing
|
|
102
|
+
the returned ``transaction`` with ``mode="auto"`` and no code. Otherwise ``auto`` executes and
|
|
103
|
+
commits only a deterministic native auto-approval in one call. Denied or escalated
|
|
104
|
+
transactions remain non-mutating. The receipt is compact and JSON-safe, while all simulation,
|
|
105
|
+
policy, revalidation, and commit semantics stay inside the Rust core.
|
|
106
|
+
"""
|
|
107
|
+
if mode not in {"preview", "auto"}:
|
|
108
|
+
raise ValueError(f"unknown run mode: {mode!r}")
|
|
109
|
+
if detail not in {"compact", "full"}:
|
|
110
|
+
raise ValueError(f"unknown receipt detail: {detail!r}")
|
|
111
|
+
if policy not in {"balanced", "strict", "paranoid"}:
|
|
112
|
+
raise ValueError(f"unknown policy profile: {policy!r}")
|
|
113
|
+
|
|
114
|
+
workspace = Path(workspace_root or os.getcwd()).resolve(strict=True)
|
|
115
|
+
if not workspace.is_dir():
|
|
116
|
+
raise NotADirectoryError(f"workspace root is not a directory: {workspace}")
|
|
117
|
+
|
|
118
|
+
runtime = _runtime_for(str(workspace), policy, os.environ.get("VSH_MONTY_WORKER"))
|
|
119
|
+
if transaction is not None:
|
|
120
|
+
if code is not None:
|
|
121
|
+
raise ValueError("pass either code or a preview transaction, not both")
|
|
122
|
+
if mode != "auto":
|
|
123
|
+
raise ValueError("a preview transaction can only be resumed with mode='auto'")
|
|
124
|
+
now_unix_ms = time.time_ns() // 1_000_000
|
|
125
|
+
return _receipt_payload(runtime.commit(transaction, now_unix_ms))
|
|
126
|
+
if code is None:
|
|
127
|
+
raise ValueError("code is required unless a preview transaction is supplied")
|
|
128
|
+
|
|
129
|
+
if budget is None:
|
|
130
|
+
native_budget = ExecutionBudget()
|
|
131
|
+
else:
|
|
132
|
+
native_budget = ExecutionBudget(
|
|
133
|
+
max_program_bytes=budget.get("max_program_bytes"),
|
|
134
|
+
max_duration_ms=budget.get("max_duration_ms"),
|
|
135
|
+
max_recursion_depth=budget.get("max_recursion_depth"),
|
|
136
|
+
max_memory_bytes=budget.get("max_memory_bytes"),
|
|
137
|
+
max_os_calls=budget.get("max_os_calls"),
|
|
138
|
+
max_read_bytes=budget.get("max_read_bytes"),
|
|
139
|
+
max_write_bytes=budget.get("max_write_bytes"),
|
|
140
|
+
max_io_call_bytes=budget.get("max_io_call_bytes"),
|
|
141
|
+
max_path_bytes=budget.get("max_path_bytes"),
|
|
142
|
+
max_directory_entries=budget.get("max_directory_entries"),
|
|
143
|
+
max_output_bytes=budget.get("max_output_bytes"),
|
|
144
|
+
max_result_bytes=budget.get("max_result_bytes"),
|
|
145
|
+
max_exception_bytes=budget.get("max_exception_bytes"),
|
|
146
|
+
)
|
|
147
|
+
request = RunRequest(
|
|
148
|
+
code,
|
|
149
|
+
intent=intent,
|
|
150
|
+
mode=RunMode.AUTO if mode == "auto" else RunMode.PREVIEW,
|
|
151
|
+
detail=ReceiptDetail.FULL if detail == "full" else ReceiptDetail.COMPACT,
|
|
152
|
+
budget=native_budget,
|
|
153
|
+
)
|
|
154
|
+
return _receipt_payload(runtime.run(request))
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
__all__ = ("BudgetOverrides", "vsh_run")
|
vsh/mcp/prompts.py
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
from __future__ import annotations as _annotations
|
|
2
|
+
|
|
3
|
+
from fastmcp import FastMCP
|
|
4
|
+
|
|
5
|
+
__all__ = ("register_codemode_prompts",)
|
|
6
|
+
|
|
7
|
+
_RUN_PROMPT = """\
|
|
8
|
+
Use the single `vsh_run` tool for workspace simulation and mutation.
|
|
9
|
+
|
|
10
|
+
- Send one Monty Python program containing the complete filesystem transaction.
|
|
11
|
+
- Use `mode="preview"` first when the user wants to inspect effects.
|
|
12
|
+
- To apply an auto-approved preview, call `vsh_run` again with its `transaction`, no `code`, and
|
|
13
|
+
`mode="auto"`; dependency revalidation still occurs before mutation.
|
|
14
|
+
- Otherwise use `mode="auto"` with code only when the user requested the change; Rust policy
|
|
15
|
+
still decides whether the exact virtual transaction may commit.
|
|
16
|
+
- Read `state`, `decision`, `changes`, `result_repr`, and `stdout` from the receipt.
|
|
17
|
+
- A `denied` or `pending_approval` receipt never means the host change was applied.
|
|
18
|
+
- Do not emulate shell commands or call an alternate simulator.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def register_codemode_prompts(mcp: FastMCP) -> None:
|
|
23
|
+
"""Register guidance for the single native transaction tool."""
|
|
24
|
+
|
|
25
|
+
@mcp.prompt(
|
|
26
|
+
name="vsh_run_transaction",
|
|
27
|
+
title="Run one VSH transaction",
|
|
28
|
+
description="Compose one bounded Monty transaction over the native Rust engine.",
|
|
29
|
+
tags={"native", "transaction", "vsh"},
|
|
30
|
+
)
|
|
31
|
+
def run_transaction() -> str:
|
|
32
|
+
return _RUN_PROMPT
|