Skip to content

Commit bc71cc9

Browse files
committed
Add --raw output mode, glob auto-excludes, and output guidance
- Add --raw flag to both monty and hyperlight codeact scripts for direct stdout/stderr output instead of JSON envelope - Add glob auto-exclude for common dirs (.venv, node_modules, __pycache__, .git, target, dist, build) with exclude_dirs param - Raise glob cap from 200 to 10000 with truncation warning - Add output minimization guidance to instructions template - Add glob auto-exclude documentation to instructions
1 parent 7e303ad commit bc71cc9

3 files changed

Lines changed: 76 additions & 11 deletions

File tree

‎plugins/codeact/instructions/codeact.instructions.md.tmpl‎

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ cross-references file sets, or needs ≥5 sequential tool calls.
1010
### Invoke
1111

1212
```bash
13-
{{CODEACT_DIR}}/scripts/codeact --auto --workspace . --code '<python>'
13+
{{CODEACT_DIR}}/scripts/codeact --auto --raw --workspace . --code '<python>'
1414
```
1515

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

3341
{{SYNTAX}}
3442

‎plugins/codeact/skills/hyperlight-codeact/scripts/codeact.py‎

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -808,6 +808,8 @@ def main() -> None:
808808
ap.add_argument("--stack-size", help="Sandbox stack (e.g. '35Mi').")
809809
ap.add_argument("--allowed-domains", nargs="*", default=[],
810810
help="Domains reachable via sandbox http_get/http_post.")
811+
ap.add_argument("--raw", action="store_true",
812+
help="On success, print stdout/stderr directly instead of JSON envelope.")
811813
ap.add_argument("--workspace",
812814
help="Restrict file/sql tools to this directory tree.")
813815
args = ap.parse_args()
@@ -902,7 +904,18 @@ def main() -> None:
902904
"success": False,
903905
}
904906

905-
print(json.dumps(output, indent=2))
907+
if args.raw and output["success"]:
908+
sys.stdout.write(output["stdout"])
909+
if output["stderr"]:
910+
sys.stderr.write(output["stderr"])
911+
sys.exit(0)
912+
elif args.raw and not output["success"]:
913+
if output["stdout"]:
914+
sys.stdout.write(output["stdout"])
915+
sys.stderr.write(output["stderr"])
916+
sys.exit(1)
917+
else:
918+
print(json.dumps(output, indent=2))
906919

907920

908921
if __name__ == "__main__":

‎plugins/codeact/skills/monty-codeact/scripts/codeact.py‎

Lines changed: 50 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -96,11 +96,16 @@ def discover_tools() -> list[dict[str, Any]]:
9696
tools.append({
9797
"name": "glob",
9898
"cli_equivalent": "glob",
99-
"description": "Find files matching a glob pattern (max 200).",
99+
"description": "Find files matching a glob pattern. Auto-excludes "
100+
".venv, node_modules, __pycache__, .git, target, dist, "
101+
"build. Pass exclude_dirs=[] to include everything.",
100102
"parameters": {
101103
"pattern": {"type": "string", "required": True,
102104
"description": "Glob pattern, e.g. '**/*.py'."},
103105
"paths": {"type": "string", "required": False, "default": "."},
106+
"exclude_dirs": {"type": "array", "required": False,
107+
"description": "Directories to exclude. Defaults to "
108+
"common virtual/build dirs. Pass [] to disable."},
104109
},
105110
"implementation": {"type": "builtin"},
106111
})
@@ -374,8 +379,26 @@ def _edit(path="", old_str="", new_str=""):
374379
return f"Edited {path}"
375380

376381

377-
def _glob(pattern="**/*", paths="."):
382+
_DEFAULT_EXCLUDE_DIRS = {
383+
".venv", "venv", "node_modules", "__pycache__", ".git",
384+
".tox", ".mypy_cache", ".pytest_cache", ".ruff_cache",
385+
"target", "dist", "build", ".next", ".nuxt",
386+
}
387+
388+
def _glob(pattern="**/*", paths=".", exclude_dirs=None):
378389
base = _check_workspace(Path(paths))
390+
# Directories to skip unless user explicitly globs into them
391+
skip = _DEFAULT_EXCLUDE_DIRS if exclude_dirs is None else set(exclude_dirs)
392+
393+
def _should_include(p: Path) -> bool:
394+
if not p.is_file():
395+
return False
396+
# Skip files inside excluded directories
397+
for part in p.parts:
398+
if part in skip:
399+
return False
400+
return True
401+
379402
# Support brace expansion: {a,b} → run multiple globs and merge
380403
if "{" in pattern and "}" in pattern:
381404
prefix = pattern[:pattern.index("{")]
@@ -386,10 +409,18 @@ def _glob(pattern="**/*", paths="."):
386409
expanded = [prefix + alt + suffix for alt in alternatives]
387410
all_matches: list[str] = []
388411
for p in expanded:
389-
all_matches.extend(str(m) for m in base.glob(p) if m.is_file())
390-
matches = sorted(set(all_matches))[:200]
412+
all_matches.extend(str(m) for m in base.glob(p) if _should_include(m))
413+
matches = sorted(set(all_matches))
391414
else:
392-
matches = sorted(str(p) for p in base.glob(pattern) if p.is_file())[:200]
415+
matches = sorted(str(p) for p in base.glob(pattern) if _should_include(p))
416+
417+
# Safety cap at 10000 with a warning if truncated
418+
cap = 10000
419+
if len(matches) > cap:
420+
print(f"⚠ glob matched {len(matches)} files, returning first {cap}. "
421+
f"Use a more specific pattern.", file=sys.stderr)
422+
matches = matches[:cap]
423+
393424
# Return workspace-relative paths so sandbox code doesn't need to strip prefixes
394425
if _WORKSPACE_ROOT is not None:
395426
root = str(_WORKSPACE_ROOT) + "/"
@@ -717,6 +748,8 @@ def main() -> None:
717748
help="Max execution steps (Monty limit).")
718749
ap.add_argument("--max-memory", type=int, default=None,
719750
help="Max memory in bytes (Monty limit).")
751+
ap.add_argument("--raw", action="store_true",
752+
help="On success, print stdout/stderr directly instead of JSON envelope.")
720753
args = ap.parse_args()
721754

722755
# ---- discovery / instructions ----
@@ -823,7 +856,18 @@ def print_callback(stream: str, text: str) -> None:
823856
"success": False,
824857
}
825858

826-
print(json.dumps(output, indent=2, default=str))
859+
if args.raw and output["success"]:
860+
sys.stdout.write(output["stdout"])
861+
if output["stderr"]:
862+
sys.stderr.write(output["stderr"])
863+
sys.exit(0)
864+
elif args.raw and not output["success"]:
865+
if output["stdout"]:
866+
sys.stdout.write(output["stdout"])
867+
sys.stderr.write(output["stderr"])
868+
sys.exit(1)
869+
else:
870+
print(json.dumps(output, indent=2, default=str))
827871

828872

829873
if __name__ == "__main__":

0 commit comments

Comments
 (0)