shell/ compatibility extension — a DROPPABLE classpath plug-in (drop the
jar, drop the feature). Bound only when the user-owned shell toggle is ON
(default ON; flip it OFF in Settings or in vis.yml via toggles: {shell: false}
to drop the tools). The OS process jail is the containment layer while active.
ONE model-facing binding — shell — bound BARE in the flat Python sandbox
next to git / cat / grep. There is no shell_run / shell_bg /
shell_logs / shell_send quartet any more: four names for ONE subsystem
meant four call shapes and four result shapes for what is a single process
lifecycle. One tool, one op grammar, one TOTAL result map:
RUN (default) await shell({"commands": ["ls"]}) — bash -lc in the
workspace root, waits up to a timeout. Output is bounded at READ time to a
head+tail budget per stream, so only the MIDDLE of a huge stream is
dropped, never its start or end (a chatty-then-killed command cannot
balloon the heap). A non-zero exit is DATA the model reads, not an error.
BACKGROUND await shell({"commands": ["npm run dev"], "op": "background", "id": "dev"})
— an id makes it background (the op may stay implicit): spawned under a
REAL pty, its merged output pumped into a bounded ring buffer, registered
as a session RESOURCE. Prefer this for long builds, test suites, servers,
watchers, and interactive commands; reserve run for short bounded work.
WAIT / LOGS / SEND / STOP await shell({"op": "wait", "id": "dev"})
— wait on the host for completion and receive the final log tail, snapshot
the ring buffer immediately with logs, type {"text": "…"} into the
pty, or kill the tree.
EVERY public call takes exactly one map. Process commands are its non-empty
commands string array; command strings and command arrays are never
positional. text is the map field only for send keystrokes. Resource IDs
live in that same map for background/wait/logs/send/stop.
Every op answers a stage-SCOPED total key set ([[result-core]] plus that one
stage's own keys): a key the stage owns is nil / false / 0 / [] instead of
absent, so model Python indexes it without a KeyError, while another stage's
keys are simply not there to carry nothing. A run always has one result entry
per command under commands, holding that command line and its bytes.
The shell toggle is registered HERE, extension-owned under the vis namespace.
`shell/` compatibility extension — a DROPPABLE classpath plug-in (drop the
jar, drop the feature). Bound only when the user-owned `shell` toggle is ON
(default ON; flip it OFF in Settings or in `vis.yml` via `toggles: {shell: false}`
to drop the tools). The OS process jail is the containment layer while active.
ONE model-facing binding — `shell` — bound BARE in the flat Python sandbox
next to `git` / `cat` / `grep`. There is no `shell_run` / `shell_bg` /
`shell_logs` / `shell_send` quartet any more: four names for ONE subsystem
meant four call shapes and four result shapes for what is a single process
lifecycle. One tool, one `op` grammar, one TOTAL result map:
1. RUN (default) `await shell({"commands": ["ls"]})` — `bash -lc` in the
workspace root, waits up to a timeout. Output is bounded at READ time to a
head+tail budget per stream, so only the MIDDLE of a huge stream is
dropped, never its start or end (a chatty-then-killed command cannot
balloon the heap). A non-zero exit is DATA the model reads, not an error.
2. BACKGROUND `await shell({"commands": ["npm run dev"], "op": "background", "id": "dev"})`
— an `id` makes it background (the op may stay implicit): spawned under a
REAL pty, its merged output pumped into a bounded ring buffer, registered
as a session RESOURCE. Prefer this for long builds, test suites, servers,
watchers, and interactive commands; reserve run for short bounded work.
3. WAIT / LOGS / SEND / STOP `await shell({"op": "wait", "id": "dev"})`
— wait on the host for completion and receive the final log tail, snapshot
the ring buffer immediately with `logs`, type `{"text": "…"}` into the
pty, or kill the tree.
EVERY public call takes exactly one map. Process commands are its non-empty
`commands` string array; command strings and command arrays are never
positional. `text` is the map field only for `send` keystrokes. Resource IDs
live in that same map for background/wait/logs/send/stop.
Every op answers a stage-SCOPED total key set ([[result-core]] plus that one
stage's own keys): a key the stage owns is nil / false / 0 / [] instead of
absent, so model Python indexes it without a KeyError, while another stage's
keys are simply not there to carry nothing. A run always has one result entry
per command under `commands`, holding that command line and its bytes.
The `shell` toggle is registered HERE, extension-owned under the vis namespace.(jailed-shell env opts)Run a Python extension's shell request through the invoking session's jail.
The only public form is vis.shell({"commands": ["ls"]}); it shares
the same one-map grammar as the native shell tool.
Run a Python extension's shell request through the invoking session's jail.
The only public form is `vis.shell({"commands": ["ls"]})`; it shares
the same one-map grammar as the native shell tool.(run-argv env argv)(run-argv env argv opts)Run ONE literal argv through the SAME bounded machinery shell runs its own
commands with: cwd authorization, process-jail policy, head+tail capped
capture, timeout and kill-tree. Returns that command's own total entry — the
SAME command-result map (command, stdout, stderr, exit, duration_ms,
timed_out, *_omitted_chars) shell puts under commands, carrying the
request's :dir/:timeout-secs as metadata, so there is one command shape for
both tools and no envelope to unwrap.
No shell is involved — each element reaches the process verbatim, so nothing
needs quoting. The git tool is a USER of this: every git command is a
bounded shell command, so both tools share one runner, one jail and one
capture policy.
Run ONE literal argv through the SAME bounded machinery `shell` runs its own commands with: cwd authorization, process-jail policy, head+tail capped capture, timeout and kill-tree. Returns that command's own total entry — the SAME `command-result` map (`command`, `stdout`, `stderr`, `exit`, `duration_ms`, `timed_out`, `*_omitted_chars`) `shell` puts under `commands`, carrying the request's `:dir`/`:timeout-secs` as metadata, so there is one command shape for both tools and no envelope to unwrap. No shell is involved — each element reaches the process verbatim, so nothing needs quoting. The `git` tool is a USER of this: every git command is a bounded shell command, so both tools share one runner, one jail and one capture policy.
(shell opts)shell(opts) runs bounded commands or manages background PTYs. Await it in python_execution; pass one map, never positional args.
Examples: await shell({"commands": ["git status"]}) await shell({"commands": ["npm test"], "op": "background", "id": "tests"}) await shell({"op": "wait", "id": "tests", "timeout_secs": 300}) await shell({"op": "logs", "id": "tests"}) await shell({"op": "send", "id": "tests", "text": "y"}) await shell({"op": "stop", "id": "tests"})
commands is a non-empty string array. op defaults to run, or background when id is supplied:
run: blocking bash -lc; cwd defaults to workspace root; timeout_secs defaults to 120 (max 600); nonzero exit is data.background: returns immediately without a timeout and owns a session resource; use for long or interactive work.wait: host-side bounded wait for completion; returns the final lines tail and does not stop a timed-out process. Use concurrent waits with await gather(...) instead of sleeping or polling in Python.logs: immediate snapshot of the last 200 lines by default; n max 2000.send: writes text verbatim; is_enter defaults true.stop: kills the process tree and drops its logs/resource.Results share stage, id, cwd, commands, started, exit, duration_ms, timed_out, and note; stages add process/status/log/send fields. Background log output is in lines; foreground command output is under each command's stdout/stderr.
`shell(opts)` runs bounded commands or manages background PTYs. Await it in `python_execution`; pass one map, never positional args.
Examples:
await shell({"commands": ["git status"]})
await shell({"commands": ["npm test"], "op": "background", "id": "tests"})
await shell({"op": "wait", "id": "tests", "timeout_secs": 300})
await shell({"op": "logs", "id": "tests"})
await shell({"op": "send", "id": "tests", "text": "y"})
await shell({"op": "stop", "id": "tests"})
`commands` is a non-empty string array. `op` defaults to `run`, or `background` when `id` is supplied:
- `run`: blocking `bash -lc`; `cwd` defaults to workspace root; `timeout_secs` defaults to 120 (max 600); nonzero exit is data.
- `background`: returns immediately without a timeout and owns a session resource; use for long or interactive work.
- `wait`: host-side bounded wait for completion; returns the final `lines` tail and does not stop a timed-out process. Use concurrent waits with `await gather(...)` instead of sleeping or polling in Python.
- `logs`: immediate snapshot of the last 200 `lines` by default; `n` max 2000.
- `send`: writes `text` verbatim; `is_enter` defaults true.
- `stop`: kills the process tree and drops its logs/resource.
Results share `stage`, `id`, `cwd`, `commands`, `started`, `exit`, `duration_ms`, `timed_out`, and `note`; stages add process/status/log/send fields. Background log output is in `lines`; foreground command output is under each command's `stdout`/`stderr`.(shell-attach-command _parsed residual)vis-agent extension shell attach <id> — the human-side passthrough: join a live
background shell's PTY in your OWN terminal (finish a browser OAuth, answer a
prompt only a person can), then Ctrl-] to detach with the child untouched.
--socket PATH targets an explicit socket; otherwise the newest shell whose
id matches. Returns the attach exit code.
`vis-agent extension shell attach <id>` — the human-side passthrough: join a live background shell's PTY in your OWN terminal (finish a browser OAuth, answer a prompt only a person can), then Ctrl-] to detach with the child untouched. `--socket PATH` targets an explicit socket; otherwise the newest shell whose id matches. Returns the attach exit code.
CLI surface mounted under vis-agent extension shell. Only attach for now — the human
passthrough onto a background PTY the agent spawned.
CLI surface mounted under `vis-agent extension shell`. Only `attach` for now — the human passthrough onto a background PTY the agent spawned.
(shell-dispatch env opts)One shell lifecycle grammar. Every public call takes exactly one options map;
process commands are the commands string array in that map.
One shell lifecycle grammar. Every public call takes exactly one options map; process commands are the `commands` string array in that map.
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |