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 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
@@ -0,0 +1,6 @@
1
+ from __future__ import annotations as _annotations
2
+
3
+ from .cli import main
4
+
5
+ if __name__ == "__main__":
6
+ main()
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
@@ -0,0 +1,5 @@
1
+ from __future__ import annotations as _annotations
2
+
3
+ from ._native import version as _native_version
4
+
5
+ __version__ = _native_version()
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,5 @@
1
+ from __future__ import annotations as _annotations
2
+
3
+ from .native_tools import BudgetOverrides, vsh_run
4
+
5
+ __all__ = ("BudgetOverrides", "vsh_run")
@@ -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()
@@ -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
vsh/mcp/server.py ADDED
@@ -0,0 +1,8 @@
1
+ from __future__ import annotations as _annotations
2
+
3
+ from fastmcp import FastMCP
4
+
5
+ from .surface import register_vsh_surface
6
+
7
+ mcp = FastMCP("vsh")
8
+ register_vsh_surface(mcp)