Exec tool sets
The host starts the process with ProcessStartInfo.ArgumentList (no
bash -lc, no one-line command string). Working directory is the
workspace root. The child environment inherits the host process and
also receives five variables, rewritten for that process only:
| Variable | Value |
|---|---|
CRYSTAL_WORKSPACE |
Absolute workspace root |
CRYSTAL_SESSION |
Current session id |
CRYSTAL_APPROVAL |
Current approval mode (plan, default, edit, review, audit, or full) |
CRYSTAL_TOOL_DATA |
Absolute global data directory for this set, {home}/data/tools/<directory> |
CRYSTAL_PROJECT_DATA |
Absolute per-workspace data directory for this set, <workspace>/.crystal/data/tools/<directory> |
Secrets are not added. CRYSTAL_HOME is not set for the tool. The two
data directories are host-resolved and created lazily the first time the
tool runs. A directory that cannot be created is not reported, so create
it when it is missing. They are separate from the set install directory;
keep them free of secrets.
A bare executable name is resolved inside the set directory when that file exists; otherwise it may PATH-search. A relative path that contains a directory separator must stay inside the set directory. An absolute path is used as given.
Final argv is: set command + this tool’s command + mapped flags
from this tool’s argv map. For shorthand, there is no extra suffix.
Stdin JSON (stdin: true, default): write the fenced argument
object as UTF-8 JSON without a BOM, then close stdin. Same body for a
one-tool or many-tool set. The child already knows which subcommand it
is from argv.
Argv (argv map): for each listed property that is present, append
flag, then the scalar string form. Allowed JSON kinds: string,
number, boolean. A string array appends flag value for each
element. Objects, nulls, and mixed arrays refuse the call with a
model-visible English error. Property order follows the argv object
order in the file.
Both: stdin gets the full fenced object and argv gets prefix, suffix, and mapped scalars.
Stdin off: stdin is still redirected and closed so the child does not block.
Content output ("output": "content", default text): stdout must
be one UTF-8 JSON object. text is an optional string. images is an
optional array of at most 8 objects. Each image sets mimeType
(image/png, image/jpeg, image/gif, or image/webp) and exactly
one of base64 or path. base64 may contain whitespace. path must
be a regular file inside the workspace; credential paths, directories,
and pipes are rejected. Bytes must match the declared type and
the 20 MiB host limit. The host opens a path only when the image will
be attached, and that read observes cancellation. Stderr is appended to
text. A non-zero exit fails the call and does not attach images. On a
text-only turn, a result that contains images fails with “This model
cannot accept tool images.” On an image-capable turn the same tool is
also registered as a multimodal tool, and accepted images follow the
existing marker path. Invalid JSON fails the call and keeps a preview of
stdout. Large images should use path, because
stdout is still truncated to the tool output limit before parsing.
Non-zero exit is ToolResultStatus.Failure. The timeout starts when the
process starts and covers stdin writing, process exit, and stdout/stderr
reading. Image files named by content JSON are not part of that process
timeout. "timeoutSeconds": "unlimited" disables this per-call timeout; user and
turn cancellation still apply. Timeout, cancellation, and stdin failure
kill the process tree when the OS allows it. For output: text, stdout
then stderr are concatenated, truncated, and returned as ToolOutput text.
Several exec tools should live in one set when they share a binary.
That is the same unit as a multi-ITool assembly: one install, one
ALC or one executable, many catalog entries.