Strengthen builder sandbox validation
All checks were successful
build / build (push) Successful in 15s
All checks were successful
build / build (push) Successful in 15s
This commit is contained in:
2
a2a.yaml
2
a2a.yaml
@@ -1,5 +1,5 @@
|
||||
name: agent-builder
|
||||
version: 0.1.4
|
||||
version: 0.1.5
|
||||
entrypoint: agent:AgentBuilder
|
||||
description: Generate, test, and deploy new a2a agents from natural language.
|
||||
expose:
|
||||
|
||||
2
agent.py
2
agent.py
@@ -49,7 +49,7 @@ class AgentBuilder(A2AAgent[BuilderConfig, NoAuth]):
|
||||
"Writes the project into the user's workspace, validates it in a "
|
||||
"sandbox, then ships it via the control plane."
|
||||
)
|
||||
version = "0.1.4"
|
||||
version = "0.1.5"
|
||||
|
||||
config_model = BuilderConfig
|
||||
auth_model = NoAuth
|
||||
|
||||
@@ -485,6 +485,145 @@ def build_tools(ctx: ToolContext) -> list[Any]:
|
||||
"fi\n"
|
||||
"echo '--- card ---'\n"
|
||||
"a2a card --project .\n"
|
||||
"echo '--- undefined-name check ---'\n"
|
||||
"python - <<'PY'\n"
|
||||
"import ast, builtins, pathlib, sys\n"
|
||||
"path = pathlib.Path('agent.py')\n"
|
||||
"if not path.exists():\n"
|
||||
" sys.exit(0)\n"
|
||||
"tree = ast.parse(path.read_text(), filename=str(path))\n"
|
||||
"builtins_scope = set(dir(builtins)) | {'__name__', '__file__', '__package__'}\n"
|
||||
"globals_scope = set(builtins_scope)\n"
|
||||
"def bind_target(target, scope):\n"
|
||||
" if isinstance(target, ast.Name):\n"
|
||||
" scope.add(target.id)\n"
|
||||
" elif isinstance(target, (ast.Tuple, ast.List)):\n"
|
||||
" for item in target.elts:\n"
|
||||
" bind_target(item, scope)\n"
|
||||
"for node in tree.body:\n"
|
||||
" if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef)):\n"
|
||||
" globals_scope.add(node.name)\n"
|
||||
" elif isinstance(node, ast.Import):\n"
|
||||
" for alias in node.names:\n"
|
||||
" globals_scope.add((alias.asname or alias.name.split('.')[0]))\n"
|
||||
" elif isinstance(node, ast.ImportFrom):\n"
|
||||
" for alias in node.names:\n"
|
||||
" if alias.name != '*':\n"
|
||||
" globals_scope.add(alias.asname or alias.name)\n"
|
||||
" elif isinstance(node, (ast.Assign, ast.AnnAssign, ast.AugAssign)):\n"
|
||||
" targets = getattr(node, 'targets', None) or [getattr(node, 'target', None)]\n"
|
||||
" for target in targets:\n"
|
||||
" bind_target(target, globals_scope)\n"
|
||||
"class UndefinedNameVisitor(ast.NodeVisitor):\n"
|
||||
" def __init__(self):\n"
|
||||
" self.scopes = [set(globals_scope)]\n"
|
||||
" self.errors = []\n"
|
||||
" def _defined(self, name):\n"
|
||||
" return any(name in scope for scope in reversed(self.scopes))\n"
|
||||
" def _bind_args(self, args, scope):\n"
|
||||
" for arg in list(args.posonlyargs) + list(args.args) + list(args.kwonlyargs):\n"
|
||||
" scope.add(arg.arg)\n"
|
||||
" if args.vararg:\n"
|
||||
" scope.add(args.vararg.arg)\n"
|
||||
" if args.kwarg:\n"
|
||||
" scope.add(args.kwarg.arg)\n"
|
||||
" def visit_FunctionDef(self, node):\n"
|
||||
" self.visit_function(node)\n"
|
||||
" def visit_AsyncFunctionDef(self, node):\n"
|
||||
" self.visit_function(node)\n"
|
||||
" def visit_function(self, node):\n"
|
||||
" for dec in node.decorator_list:\n"
|
||||
" self.visit(dec)\n"
|
||||
" for default in list(node.args.defaults) + list(node.args.kw_defaults):\n"
|
||||
" if default is not None:\n"
|
||||
" self.visit(default)\n"
|
||||
" scope = set()\n"
|
||||
" self._bind_args(node.args, scope)\n"
|
||||
" self.scopes.append(scope)\n"
|
||||
" for stmt in node.body:\n"
|
||||
" self.visit(stmt)\n"
|
||||
" self.scopes.pop()\n"
|
||||
" def visit_ClassDef(self, node):\n"
|
||||
" for dec in node.decorator_list:\n"
|
||||
" self.visit(dec)\n"
|
||||
" for base in node.bases:\n"
|
||||
" self.visit(base)\n"
|
||||
" self.scopes.append(set())\n"
|
||||
" for stmt in node.body:\n"
|
||||
" self.visit(stmt)\n"
|
||||
" self.scopes.pop()\n"
|
||||
" def visit_Import(self, node):\n"
|
||||
" for alias in node.names:\n"
|
||||
" self.scopes[-1].add(alias.asname or alias.name.split('.')[0])\n"
|
||||
" def visit_ImportFrom(self, node):\n"
|
||||
" for alias in node.names:\n"
|
||||
" if alias.name != '*':\n"
|
||||
" self.scopes[-1].add(alias.asname or alias.name)\n"
|
||||
" def visit_Assign(self, node):\n"
|
||||
" self.visit(node.value)\n"
|
||||
" for target in node.targets:\n"
|
||||
" bind_target(target, self.scopes[-1])\n"
|
||||
" def visit_AnnAssign(self, node):\n"
|
||||
" if node.annotation:\n"
|
||||
" self.visit(node.annotation)\n"
|
||||
" if node.value:\n"
|
||||
" self.visit(node.value)\n"
|
||||
" bind_target(node.target, self.scopes[-1])\n"
|
||||
" def visit_For(self, node):\n"
|
||||
" self.visit(node.iter)\n"
|
||||
" bind_target(node.target, self.scopes[-1])\n"
|
||||
" for stmt in node.body + node.orelse:\n"
|
||||
" self.visit(stmt)\n"
|
||||
" def visit_AsyncFor(self, node):\n"
|
||||
" self.visit_For(node)\n"
|
||||
" def visit_With(self, node):\n"
|
||||
" for item in node.items:\n"
|
||||
" self.visit(item.context_expr)\n"
|
||||
" if item.optional_vars:\n"
|
||||
" bind_target(item.optional_vars, self.scopes[-1])\n"
|
||||
" for stmt in node.body:\n"
|
||||
" self.visit(stmt)\n"
|
||||
" def visit_AsyncWith(self, node):\n"
|
||||
" self.visit_With(node)\n"
|
||||
" def visit_ExceptHandler(self, node):\n"
|
||||
" if node.type:\n"
|
||||
" self.visit(node.type)\n"
|
||||
" if node.name:\n"
|
||||
" self.scopes[-1].add(node.name)\n"
|
||||
" for stmt in node.body:\n"
|
||||
" self.visit(stmt)\n"
|
||||
" def _visit_comp(self, node):\n"
|
||||
" self.scopes.append(set())\n"
|
||||
" for gen in node.generators:\n"
|
||||
" self.visit(gen.iter)\n"
|
||||
" bind_target(gen.target, self.scopes[-1])\n"
|
||||
" for cond in gen.ifs:\n"
|
||||
" self.visit(cond)\n"
|
||||
" for field in ('elt', 'key', 'value'):\n"
|
||||
" value = getattr(node, field, None)\n"
|
||||
" if value is not None:\n"
|
||||
" self.visit(value)\n"
|
||||
" self.scopes.pop()\n"
|
||||
" def visit_ListComp(self, node):\n"
|
||||
" self._visit_comp(node)\n"
|
||||
" def visit_SetComp(self, node):\n"
|
||||
" self._visit_comp(node)\n"
|
||||
" def visit_DictComp(self, node):\n"
|
||||
" self._visit_comp(node)\n"
|
||||
" def visit_GeneratorExp(self, node):\n"
|
||||
" self._visit_comp(node)\n"
|
||||
" def visit_Name(self, node):\n"
|
||||
" if isinstance(node.ctx, ast.Load) and not self._defined(node.id):\n"
|
||||
" self.errors.append(f'{path}:{node.lineno}:{node.col_offset}: undefined name {node.id!r}')\n"
|
||||
" elif isinstance(node.ctx, (ast.Store, ast.Del)):\n"
|
||||
" self.scopes[-1].add(node.id)\n"
|
||||
"visitor = UndefinedNameVisitor()\n"
|
||||
"visitor.visit(tree)\n"
|
||||
"if visitor.errors:\n"
|
||||
" print('\\n'.join(visitor.errors))\n"
|
||||
" sys.exit(1)\n"
|
||||
"print('ok')\n"
|
||||
"PY\n"
|
||||
"if grep -q '^frontend:' a2a.yaml 2>/dev/null; then\n"
|
||||
" echo '--- frontend ---'\n"
|
||||
" a2a frontend info --project . || true\n"
|
||||
|
||||
Reference in New Issue
Block a user