Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 12 additions & 4 deletions plugins/codeact/instructions/codeact.instructions.md.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ cross-references file sets, or needs ≥5 sequential tool calls.
### Invoke

```bash
{{CODEACT_DIR}}/scripts/codeact --auto --workspace . --code '<python>'
{{CODEACT_DIR}}/scripts/codeact --auto --raw --workspace . --code '<python>'
```

### Critical rules
Expand All @@ -25,10 +25,18 @@ cross-references file sets, or needs ≥5 sequential tool calls.
- `mcp_call` → **string**
- `bash` → **dict** with stdout/stderr/returncode
3. **No `os.path`, no `os.walk`** — use `glob` to find files, `view` to read them.
4. **Wrap file reads in try/except.** Print partial results as you go.
5. **Use double quotes in Python code** to avoid shell quoting conflicts with
4. **`glob` auto-excludes** `.venv`, `node_modules`, `__pycache__`, `.git`, `target`,
`dist`, `build`, and similar directories. If you need files from those dirs,
pass `exclude_dirs=[]` to disable filtering.
5. **Wrap file reads in try/except.** Print partial results as you go.
6. **Use double quotes in Python code** to avoid shell quoting conflicts with
`--code '...'`. Write `"string"` not `'string'` inside codeact programs.
6. Skip codeact for ≤5 files or single grep→view→done workflows.
7. Skip codeact for ≤5 files or single grep→view→done workflows.
8. **Keep output minimal.** Only print what the caller actually needs —
summaries, counts, and key findings. Do NOT dump raw file contents or
hundreds of unfiltered lines. If a list could be long, truncate or
aggregate inside the program. The output must fit comfortably in a
single tool response (~5 KB) to avoid extra read round-trips.

{{SYNTAX}}

Expand Down
15 changes: 14 additions & 1 deletion plugins/codeact/skills/hyperlight-codeact/scripts/codeact.py
Original file line number Diff line number Diff line change
Expand Up @@ -808,6 +808,8 @@ def main() -> None:
ap.add_argument("--stack-size", help="Sandbox stack (e.g. '35Mi').")
ap.add_argument("--allowed-domains", nargs="*", default=[],
help="Domains reachable via sandbox http_get/http_post.")
ap.add_argument("--raw", action="store_true",
help="On success, print stdout/stderr directly instead of JSON envelope.")
ap.add_argument("--workspace",
help="Restrict file/sql tools to this directory tree.")
args = ap.parse_args()
Expand Down Expand Up @@ -902,7 +904,18 @@ def main() -> None:
"success": False,
}

print(json.dumps(output, indent=2))
if args.raw and output["success"]:
sys.stdout.write(output["stdout"])
if output["stderr"]:
sys.stderr.write(output["stderr"])
sys.exit(0)
elif args.raw and not output["success"]:
if output["stdout"]:
sys.stdout.write(output["stdout"])
sys.stderr.write(output["stderr"])
sys.exit(1)
else:
print(json.dumps(output, indent=2))


if __name__ == "__main__":
Expand Down
56 changes: 50 additions & 6 deletions plugins/codeact/skills/monty-codeact/scripts/codeact.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,11 +96,16 @@ def discover_tools() -> list[dict[str, Any]]:
tools.append({
"name": "glob",
"cli_equivalent": "glob",
"description": "Find files matching a glob pattern (max 200).",
"description": "Find files matching a glob pattern. Auto-excludes "
".venv, node_modules, __pycache__, .git, target, dist, "
"build. Pass exclude_dirs=[] to include everything.",
"parameters": {
"pattern": {"type": "string", "required": True,
"description": "Glob pattern, e.g. '**/*.py'."},
"paths": {"type": "string", "required": False, "default": "."},
"exclude_dirs": {"type": "array", "required": False,
"description": "Directories to exclude. Defaults to "
"common virtual/build dirs. Pass [] to disable."},
},
"implementation": {"type": "builtin"},
})
Expand Down Expand Up @@ -374,8 +379,26 @@ def _edit(path="", old_str="", new_str=""):
return f"Edited {path}"


def _glob(pattern="**/*", paths="."):
_DEFAULT_EXCLUDE_DIRS = {
".venv", "venv", "node_modules", "__pycache__", ".git",
".tox", ".mypy_cache", ".pytest_cache", ".ruff_cache",
"target", "dist", "build", ".next", ".nuxt",
}

def _glob(pattern="**/*", paths=".", exclude_dirs=None):
base = _check_workspace(Path(paths))
# Directories to skip unless user explicitly globs into them
skip = _DEFAULT_EXCLUDE_DIRS if exclude_dirs is None else set(exclude_dirs)

def _should_include(p: Path) -> bool:
if not p.is_file():
return False
# Skip files inside excluded directories
for part in p.parts:
if part in skip:
return False
return True

# Support brace expansion: {a,b} → run multiple globs and merge
if "{" in pattern and "}" in pattern:
prefix = pattern[:pattern.index("{")]
Expand All @@ -386,10 +409,18 @@ def _glob(pattern="**/*", paths="."):
expanded = [prefix + alt + suffix for alt in alternatives]
all_matches: list[str] = []
for p in expanded:
all_matches.extend(str(m) for m in base.glob(p) if m.is_file())
matches = sorted(set(all_matches))[:200]
all_matches.extend(str(m) for m in base.glob(p) if _should_include(m))
matches = sorted(set(all_matches))
else:
matches = sorted(str(p) for p in base.glob(pattern) if p.is_file())[:200]
matches = sorted(str(p) for p in base.glob(pattern) if _should_include(p))

# Safety cap at 10000 with a warning if truncated
cap = 10000
if len(matches) > cap:
print(f"⚠ glob matched {len(matches)} files, returning first {cap}. "
f"Use a more specific pattern.", file=sys.stderr)
matches = matches[:cap]

# Return workspace-relative paths so sandbox code doesn't need to strip prefixes
if _WORKSPACE_ROOT is not None:
root = str(_WORKSPACE_ROOT) + "/"
Expand Down Expand Up @@ -717,6 +748,8 @@ def main() -> None:
help="Max execution steps (Monty limit).")
ap.add_argument("--max-memory", type=int, default=None,
help="Max memory in bytes (Monty limit).")
ap.add_argument("--raw", action="store_true",
help="On success, print stdout/stderr directly instead of JSON envelope.")
args = ap.parse_args()

# ---- discovery / instructions ----
Expand Down Expand Up @@ -823,7 +856,18 @@ def print_callback(stream: str, text: str) -> None:
"success": False,
}

print(json.dumps(output, indent=2, default=str))
if args.raw and output["success"]:
sys.stdout.write(output["stdout"])
if output["stderr"]:
sys.stderr.write(output["stderr"])
sys.exit(0)
elif args.raw and not output["success"]:
if output["stdout"]:
sys.stdout.write(output["stdout"])
sys.stderr.write(output["stderr"])
sys.exit(1)
else:
print(json.dumps(output, indent=2, default=str))


if __name__ == "__main__":
Expand Down