@hydraharness/harness-tool-bash 0.0.0-stage → 0.1.1-rc.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +139 -2
- package/lib/index.js +454 -0
- package/lib/invariant.js +23 -0
- package/lib/types/background.d.ts +19 -0
- package/lib/types/index.d.ts +22 -0
- package/lib/types/invariant.d.ts +16 -0
- package/lib/types/render.d.ts +38 -0
- package/package.json +75 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 DeepSeek
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,140 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @hydraharness/harness-tool-bash
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
The model-facing `bash` tool registered over the `ctx.shell` executor seam. Foreground execution stays behind that seam; a background process handle is registered with the generic `ctx.jobs` runtime and controlled through `job_output`, `job_list`, and `job_kill` from `@hydraharness/harness-tool-jobs`.
|
|
4
|
+
|
|
5
|
+
Requires a loaded executor Service Provider (e.g. `@hydraharness/harness-bash-local`) and the [`@hydraharness/harness-shell-env`](../shell-env/README.md) registry; the plugin stays pending until every injected service exists (`inject: ['tools', 'bash', 'systemPrompt', 'bashEnv']`). The tool contract is bash-dialect — mount a bash-parsing executor.
|
|
6
|
+
|
|
7
|
+
The package root exposes only the Cordis plugin contract (`name`, `inject`, `Config`, `apply`); result rendering and background-process adaptation remain package-internal.
|
|
8
|
+
|
|
9
|
+
The plugin also contributes the `tool:bash` prompt section (order 105): check the `[exit code: N]` marker on every result and investigate failures before moving on.
|
|
10
|
+
|
|
11
|
+
## Tools
|
|
12
|
+
|
|
13
|
+
### `bash`
|
|
14
|
+
|
|
15
|
+
| Arg | Type | Notes |
|
|
16
|
+
|---|---|---|
|
|
17
|
+
| `command` | string (required) | Run via `bash -c`. No state persists between calls — use `workdir`, not `cd`. |
|
|
18
|
+
| `description` | string (required) | One-line, active-voice summary of the command (5-10 words), for UI/log display only — no effect on execution. |
|
|
19
|
+
| `timeoutMs` | number | Timeout override in milliseconds. The executor applies its configured default and cap. |
|
|
20
|
+
| `workdir` | string | Working directory for this call. Defaults to the filesystem identity of the calling agent's session cwd (`session.header.cwd`) so each session runs in its own workspace; a relative `workdir` is resolved against that same identity. |
|
|
21
|
+
| `changed_paths` | string[] | Optional explicit paths changed by a successful foreground command; used only to refresh workspace instructions, never inferred from shell text. |
|
|
22
|
+
| `run_in_background` | boolean | Return a job id immediately; no timeout applies. |
|
|
23
|
+
| `sandbox_permissions` | string enum | ADVERTISED ONLY when the mounted executor sandboxes (`ctx.shell.sandboxMode` reports a confining default): the wider mode a denied command needs, from the closed target vocabulary `workspace-write`/`danger-full-access` (never cut down to the executor's default — the effective mode is per-session; strict widening is checked at execution against it, and a non-widening request fails without prompting anyone). |
|
|
24
|
+
| `justification` | string | Required together with `sandbox_permissions` (each without the other is a validation error): one sentence for the user explaining why this exact command needs the wider access. |
|
|
25
|
+
|
|
26
|
+
`command`, `workdir`, and `timeoutMs` are resolved against the executor's config defaults via `ctx.shell.resolve()` before execution, so the Service Definition (`ShellExecSpec`) receives explicit `workdir`/`timeoutMs` values. The workdir default is applied in the tool layer from the calling agent's `session.header.cwd` BEFORE `resolve()` — the per-session cwd must come from `exec.agent`, since N sessions share one executor; only when no session cwd is available does the executor fall back to its own config / `process.cwd()`. When sandbox policy is present, the tool reuses its already-canonical `workspaceRoot` as the workdir base so confinement and process launch cannot resolve the same session spelling differently.
|
|
27
|
+
|
|
28
|
+
### Managed shell environment
|
|
29
|
+
|
|
30
|
+
Every foreground and background model bash call receives a freshly collected trusted `HYDRA_*` environment through the shared [`@hydraharness/harness-shell-env`](../shell-env/README.md) registry: `HYDRA_HOME` (the absolute Harness home), `HYDRA_SHELL=1`, the agent's `HYDRA_SESSION_ID`, and `HYDRA_SESSION_JSONL` when the active persistence backend locates one. The registry contract — contributor registration, loud duplicate/undeclared-key failure, the built-in reservations, and the contributor example — lives in that package's README. The snapshot passes through the dedicated `ShellExecRequest.hydraEnv` channel; the local executor removes all inherited `HYDRA_*` before merging it, so nested harnesses and concurrent parent/child agents cannot leak stale identities, and `process.env` is never modified. The tool description teaches the generic `$HYDRA_*` convention rather than naming persistence-specific variables or adding a permanent system-prompt section.
|
|
31
|
+
|
|
32
|
+
Result text contains stdout, an optional `[stderr]` section, then applicable sandbox-denial, timeout, signal, exit-code, and truncation markers. Timeout is reported independently of final exit status; nonzero exit remains a model-interpreted result rather than `isError`. Truncation links a safe complete spill file or reports it unavailable. Only infrastructure failures such as spawn errors and aborts produce `isError`.
|
|
33
|
+
|
|
34
|
+
The canonical success is `{ kind: 'foreground', ...ShellRunResult }` for a completed foreground process or `{ kind: 'background', jobId }` for a published task. The Native renderer preserves the text above, including exactly `started background job <id>`; programmatic consumers use the typed fields without parsing those strings. Executor stream caps remain acquisition limits on `ShellRunResult` and carry their spill paths.
|
|
35
|
+
|
|
36
|
+
A foreground call may declare `changed_paths`. After a successful foreground result, `agent-instructions` resolves those exact strings against the effective workdir and uses them as context-refresh hints; it never infers paths from shell text. Failed, aborted, and background calls do not refresh instructions, and these hints are not mutation records.
|
|
37
|
+
|
|
38
|
+
When `run_in_background` is true, this plugin preflights `ctx.jobs.start()` before spawning, registers the calling agent as owner, and adapts the returned `ShellProcess` handle into generic cancel/done/incremental-output hooks. The job runtime owns ids, cross-session isolation, completion notices, waiting, and disposal cleanup; this plugin only maps bash exit/sandbox facts into job output and outcome detail. `enableRunInBackground: false` removes the parameter and rejects a forced background call at execution time.
|
|
39
|
+
|
|
40
|
+
## UI presentation
|
|
41
|
+
|
|
42
|
+
The tool owns its `presentCall`/`presentResult` render intent. A foreground call is a terminal card carrying command, description, cwd, output, and parsed exit status. Because the card shows the exit as its own pill, the `[exit code: N]` / `[killed by signal: …]` marker the parse consumes leaves the output; every other marker (truncation, timeout, sandbox) stays in it. A background start is a generic execute card because it returns only a job id; the generic `job_*` tools own their own cards. These presenters are pure and replay-safe.
|
|
43
|
+
|
|
44
|
+
## The tool builds its request from named args only
|
|
45
|
+
|
|
46
|
+
`ShellExecRequest` carries optional `stdoutMaxBytes`, `stdin`, ordinary `env`, and managed `hydraEnv`, used by trusted in-process plugins and this tool's environment registry. The model-facing tool exposes none of `stdoutMaxBytes`, `stdin`, or `env`: it builds requests from named command/workdir/timeout/signal/sandbox fields plus the registry-collected `hydraEnv`. Extra model keys are ignored and cannot replace managed values. Shell syntax provides equivalent command-level behavior, while the local executor scrubs ambient credentials and stale `HYDRA_*` values. See the [stdin/env Agent Note](../../../.agents/notes/implemented/architecture/2026-06-30-bash-stdin-env-trusted-plugin-api.md).
|
|
47
|
+
|
|
48
|
+
## Permissions and escalation
|
|
49
|
+
|
|
50
|
+
Commands run with the executor's full authority unless a sandboxing executor ([`@hydraharness/harness-bash-sandbox`](../bash-sandbox/)) confines them — the deny-only sandbox reports denials as result facts, rendered here as the denial marker; per-call allow/deny/ask policy is the `tools/pre-execute` waterfall (see docs/architecture.md).
|
|
51
|
+
|
|
52
|
+
Escalating bash calls resolve `ctx.approval` before execution. `allowed-once` applies the requested mode only to that call; rejection, cancellation, unavailability, or missing approval context executes nothing and returns a distinct error. On a real denial, the model may retry the same command once in the same turn with the narrowest sufficient mode and justification; the approval prompt itself is the consent step. Escalation is never speculative, and a disabled or rejected approval is final. The [sandbox Agent Note](../../../.agents/notes/implemented/feature/2026-07-06-sandbox.md) owns the rationale.
|
|
53
|
+
|
|
54
|
+
## Per-session mode switching
|
|
55
|
+
|
|
56
|
+
For sandboxing executors, each call resolves mode as one-shot escalation, then session override, then executor default. Non-sandboxing and agent-less calls carry no session override. The policy owner contributes the current capability-neutral standing mode; denial results still own the operation-specific effective mode and retry guidance. See the [`@hydraharness/harness-shell` fold](../shell/README.md) and [sandbox switching contract](../../../.agents/notes/implemented/feature/2026-07-06-sandbox.md).
|
|
57
|
+
|
|
58
|
+
## Model Experience
|
|
59
|
+
|
|
60
|
+
### System prompt
|
|
61
|
+
|
|
62
|
+
#### What the model sees
|
|
63
|
+
|
|
64
|
+
Every request in this plugin's registration scope contains the bash guidance below. The policy owner contributes current sandbox state through its cache-safe runtime context rather than changing this section. Scoped tool restrictions can hide the schemas without removing this independently registered section.
|
|
65
|
+
|
|
66
|
+
##### Bash guidance
|
|
67
|
+
|
|
68
|
+
```markdown
|
|
69
|
+
Check the [exit code: N] marker on every bash result; investigate failures before moving on.
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
#### Token effect
|
|
73
|
+
|
|
74
|
+
Small fixed input cost per request while the plugin is active, unchanged by sandbox mode or mode switches.
|
|
75
|
+
|
|
76
|
+
#### KV Cache effect
|
|
77
|
+
|
|
78
|
+
Prefix-stable while the registration scope and prompt text are unchanged. Plugin activation or disposal may invalidate reuse from this prompt section; sandbox mode switches do not.
|
|
79
|
+
|
|
80
|
+
### Tool schemas
|
|
81
|
+
|
|
82
|
+
#### What the model sees
|
|
83
|
+
|
|
84
|
+
The model sees the generated [`bash` schema](../../../docs/tool-catalog.md#hydraharness-tool-bash). `run_in_background` appears only when this producer enables it; `sandbox_permissions` and `justification` appear only when the mounted executor advertises sandboxing. Agent-scoped tool restrictions can remove the definition for that agent.
|
|
85
|
+
|
|
86
|
+
#### Token effect
|
|
87
|
+
|
|
88
|
+
Fixed schema cost on every request where the tools are visible; sandbox support adds the escalation fields and its conditional description paragraph.
|
|
89
|
+
|
|
90
|
+
#### KV Cache effect
|
|
91
|
+
|
|
92
|
+
Prefix-stable while visibility, background support, and executor sandbox capabilities are unchanged. A restriction, config change, or executor change may invalidate reuse from the first changed tool definition.
|
|
93
|
+
|
|
94
|
+
### Foreground result
|
|
95
|
+
|
|
96
|
+
#### What the model sees
|
|
97
|
+
|
|
98
|
+
The renderer emits the data-dependent stdout tail, then optional `[stderr]` and the stderr tail. With no output it emits exactly `(no output)`. Conditional lines are exactly `[output truncated; full output: <path-or-(unavailable)>]`, `[sandbox: file access denied under <mode> mode]`, `[timed out after <timeoutMs>ms]`, `[killed by signal: <signal>]`, and `[exit code: <exitCode>]`; the sandbox escalation and runner-failure lines are quoted in [`@hydraharness/harness-bash-sandbox`](../bash-sandbox/README.md).
|
|
99
|
+
|
|
100
|
+
#### Token effect
|
|
101
|
+
|
|
102
|
+
Zero result tokens before a call. Output is bounded per stream, while each emitted line remains in history until compaction.
|
|
103
|
+
|
|
104
|
+
#### KV Cache effect
|
|
105
|
+
|
|
106
|
+
Append-only; newly visible content follows the reusable request prefix and does not invalidate existing KV-cache entries.
|
|
107
|
+
|
|
108
|
+
### Background job context and results
|
|
109
|
+
|
|
110
|
+
#### What the model sees
|
|
111
|
+
|
|
112
|
+
Start returns exactly `started background job <jobId>`. This producer supplies incremental process output, optional `[some output was dropped from memory; full output: <paths-or-(unavailable)>]`, sandbox facts, and terminal detail such as `exit code: <exitCode>` or `signal: <signal>` to the generic job runtime. [`@hydraharness/harness-tool-jobs`](../../jobs/tool-jobs/README.md) owns the visible status line, completion notice, listing, and cancellation response.
|
|
113
|
+
|
|
114
|
+
#### Token effect
|
|
115
|
+
|
|
116
|
+
The start acknowledgement is small and retained; collected output is data-dependent and bounded by the executor's stream buffers. Consuming reads do not repeat prior output.
|
|
117
|
+
|
|
118
|
+
#### KV Cache effect
|
|
119
|
+
|
|
120
|
+
Append-only; newly visible content follows the reusable request prefix and does not invalidate existing KV-cache entries.
|
|
121
|
+
|
|
122
|
+
### Tool errors
|
|
123
|
+
|
|
124
|
+
#### What the model sees
|
|
125
|
+
|
|
126
|
+
Validation and policy failures are normalized as `Error: <message>`. This package's stable messages are `invalid command: expected a non-empty string`, `invalid description: expected a non-empty string`, `invalid timeoutMs: expected a positive number, got <value>`, `invalid escalation: sandbox_permissions requires a justification`, `invalid escalation: justification is only valid together with sandbox_permissions`, `invalid justification: expected a non-empty sentence`, `background execution is disabled for this bash tool`, `background jobs unavailable: load @hydraharness/harness-jobs and @hydraharness/harness-tool-jobs`, `sandbox_permissions is not available in this composition (no sandboxing executor to escalate)`, `sandbox escalation to "<mode>" is not strictly wider than this call's current "<mode>" mode`, the approval-availability/rejection/cancellation variants, and `tool call aborted`.
|
|
127
|
+
|
|
128
|
+
#### Token effect
|
|
129
|
+
|
|
130
|
+
Only the failing call adds these retained tokens; a rejected escalation does not add command output because the command does not run.
|
|
131
|
+
|
|
132
|
+
#### KV Cache effect
|
|
133
|
+
|
|
134
|
+
Append-only; newly visible content follows the reusable request prefix and does not invalidate existing KV-cache entries.
|
|
135
|
+
|
|
136
|
+
## Known Limitations and Deferred Work
|
|
137
|
+
|
|
138
|
+
- **Replay exit pills parse from result text** — output whose final line happens to be exactly `[exit code: N]` / `[killed by signal: …]` shows a wrong pill on session replay and loses that line from the card body, because the parse treats it as the marker it consumes; a display-only known residual.
|
|
139
|
+
- **The `bash` tool opts out of `timeout-policy` budgets** — it keeps the executor-owned `BASH_TIMEOUT` path, per [the tool-call timeout-policy Agent Note](../../../.agents/notes/implemented/architecture/2026-07-07-tool-call-timeout-policy.md).
|
|
140
|
+
- **Background processes have no executor timeout** — callers must use `job_kill`, or rely on owner/service disposal, when work no longer matters.
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,454 @@
|
|
|
1
|
+
import z from "@hydraharness/schemastery";
|
|
2
|
+
import { isAbsolute, resolve } from "node:path";
|
|
3
|
+
import { TOOL_ABORTED, defineTool } from "@hydraharness/harness-tools";
|
|
4
|
+
import { HarnessError } from "@hydraharness/harness-llm";
|
|
5
|
+
import { ESCALATION_TARGETS, approveEscalation, canonicalPath, escalationHintMarker, sandboxDenialMarker, validateEscalationArgs } from "@hydraharness/harness-sandbox";
|
|
6
|
+
import { HYDRA_ENV_PREFIX, parseExitStatus } from "@hydraharness/harness-shell";
|
|
7
|
+
//#region lib/types/background.js
|
|
8
|
+
/**
|
|
9
|
+
* Generic-task adaptation for background bash process handles.
|
|
10
|
+
*
|
|
11
|
+
* @module @hydraharness/harness-tool-bash/background
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* Map a settled background process onto the generic task-outcome vocabulary:
|
|
15
|
+
* `killed` stays `killed` (detail: the signal when one is known), everything
|
|
16
|
+
* else is `completed` with the exit code as detail. A nonzero command exit is
|
|
17
|
+
* reported, not failed, exactly like the foreground rendering.
|
|
18
|
+
* @param proc - the settled process handle.
|
|
19
|
+
* @returns the outcome for the `ctx.jobs` registration.
|
|
20
|
+
*/
|
|
21
|
+
function processOutcome(proc) {
|
|
22
|
+
if (proc.status === "killed") return {
|
|
23
|
+
status: "killed",
|
|
24
|
+
detail: proc.signal !== null ? `signal: ${proc.signal}` : "killed before exit"
|
|
25
|
+
};
|
|
26
|
+
return {
|
|
27
|
+
status: "completed",
|
|
28
|
+
detail: `exit code: ${proc.exitCode ?? 0}`
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
//#endregion
|
|
32
|
+
//#region lib/types/render.js
|
|
33
|
+
/**
|
|
34
|
+
* Model-facing result rendering for the bash tool.
|
|
35
|
+
*
|
|
36
|
+
* @module @hydraharness/harness-tool-bash/render
|
|
37
|
+
*/
|
|
38
|
+
/** Append the truncation notice (with the full-output spill path) to a stream's text. */
|
|
39
|
+
function streamText(output) {
|
|
40
|
+
if (!output.truncated) return output.text;
|
|
41
|
+
return `${output.text}\n[output truncated; full output: ${output.spillPath ?? "(unavailable)"}]`;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Shape one finished run into the text the model sees: stdout, then a marked
|
|
45
|
+
* stderr section, then exit-status markers. Non-zero exits are reported, not
|
|
46
|
+
* errored — the model decides how to react; only infrastructure failures
|
|
47
|
+
* (spawn errors, aborts) surface as isError results.
|
|
48
|
+
* @param result - the completed foreground run from the executor.
|
|
49
|
+
* @param escalationModes - the escalation targets this composition advertises;
|
|
50
|
+
* non-empty adds the same-turn escalation hint after a denial marker
|
|
51
|
+
* (default `[]`: no hint).
|
|
52
|
+
* @returns the model-facing text: output body (or `(no output)`), then any timeout/signal/exit markers, each on its own line.
|
|
53
|
+
*/
|
|
54
|
+
function renderResult(result, escalationModes = []) {
|
|
55
|
+
const out = streamText(result.stdout);
|
|
56
|
+
const err = streamText(result.stderr);
|
|
57
|
+
let body = out;
|
|
58
|
+
if (err.length > 0) {
|
|
59
|
+
if (body.length > 0 && !body.endsWith("\n")) body += "\n";
|
|
60
|
+
body += `[stderr]\n${err}`;
|
|
61
|
+
}
|
|
62
|
+
if (body.length === 0) body = "(no output)";
|
|
63
|
+
const markers = [];
|
|
64
|
+
if (result.sandbox?.denied) {
|
|
65
|
+
markers.push(sandboxDenialMarker(result.sandbox.mode));
|
|
66
|
+
if (escalationModes.length > 0) markers.push(escalationHintMarker("command"));
|
|
67
|
+
}
|
|
68
|
+
if (result.timedOut) markers.push(`[timed out after ${result.timeoutMs}ms]`);
|
|
69
|
+
if (result.signal !== null) markers.push(`[killed by signal: ${result.signal}]`);
|
|
70
|
+
else if (result.exitCode !== 0) markers.push(`[exit code: ${result.exitCode}]`);
|
|
71
|
+
if (markers.length === 0) return body;
|
|
72
|
+
if (!body.endsWith("\n")) body += "\n";
|
|
73
|
+
return body + markers.join("\n");
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Shape one background-process read into the `job_output` delta the model
|
|
77
|
+
* sees: the incremental delta, plus the lossy-read notice (with full-stream
|
|
78
|
+
* spill paths) when in-memory truncation dropped unread bytes. Empty-delta
|
|
79
|
+
* rendering (`(no new output)`) is the generic job controller's job.
|
|
80
|
+
* @param read - one incremental read from the process handle.
|
|
81
|
+
* @param sandbox - settled sandbox facts, when this was a confined process.
|
|
82
|
+
* @param escalationModes - escalation targets advertised by this composition.
|
|
83
|
+
* @returns the delta text with any loss or sandbox notice appended.
|
|
84
|
+
*/
|
|
85
|
+
function renderProcessRead(read, sandbox, escalationModes = []) {
|
|
86
|
+
const notices = [];
|
|
87
|
+
if (read.lossy) {
|
|
88
|
+
const paths = [read.stdoutSpillPath, read.stderrSpillPath].filter((path) => path !== void 0);
|
|
89
|
+
notices.push(`[some output was dropped from memory; full output: ${paths.length > 0 ? paths.join(", ") : "(unavailable)"}]`);
|
|
90
|
+
}
|
|
91
|
+
if (sandbox?.runnerFailed) notices.push(`[sandbox: the sandbox runner itself failed under ${sandbox.mode} mode — the command did not run; this is a sandbox problem, not a command failure]`);
|
|
92
|
+
else if (sandbox?.denied) {
|
|
93
|
+
notices.push(sandboxDenialMarker(sandbox.mode));
|
|
94
|
+
if (escalationModes.length > 0) notices.push(escalationHintMarker("command"));
|
|
95
|
+
}
|
|
96
|
+
if (notices.length === 0) return read.delta;
|
|
97
|
+
return `${read.delta}${read.delta.length > 0 && !read.delta.endsWith("\n") ? "\n" : ""}${notices.join("\n")}`;
|
|
98
|
+
}
|
|
99
|
+
//#endregion
|
|
100
|
+
//#region lib/types/index.js
|
|
101
|
+
/**
|
|
102
|
+
* Model-facing Consumer of the `ctx.shell` capability seam. Background calls
|
|
103
|
+
* register process handles with `ctx.jobs`; their work uses job cancellation
|
|
104
|
+
* rather than the tool-call signal after an id is returned.
|
|
105
|
+
*
|
|
106
|
+
* TODO(permissions): deployment policy belongs in `tools/pre-execute` and
|
|
107
|
+
* sandboxing executors; see docs/architecture.md § Where new behavior goes.
|
|
108
|
+
* @module @hydraharness/harness-tool-bash
|
|
109
|
+
*/
|
|
110
|
+
const name = "tool-bash";
|
|
111
|
+
const inject = [
|
|
112
|
+
"tools",
|
|
113
|
+
"shell",
|
|
114
|
+
"systemPrompt",
|
|
115
|
+
"shellEnv"
|
|
116
|
+
];
|
|
117
|
+
/** Runtime configuration schema for the bash tool plugin. */
|
|
118
|
+
const Config = z.object({ enableRunInBackground: z.boolean().default(true) });
|
|
119
|
+
function validateBashArgs(args) {
|
|
120
|
+
if (args.command.trim().length === 0) throw new Error("invalid command: expected a non-empty string");
|
|
121
|
+
if (args.description.trim().length === 0) throw new Error("invalid description: expected a non-empty string");
|
|
122
|
+
if (args.timeoutMs !== void 0 && (!Number.isFinite(args.timeoutMs) || args.timeoutMs <= 0)) throw new Error(`invalid timeoutMs: expected a positive number, got ${JSON.stringify(args.timeoutMs)}`);
|
|
123
|
+
validateEscalationArgs(args.sandbox_permissions, args.justification);
|
|
124
|
+
if (args.changed_paths?.some((path) => path.length === 0)) throw new Error("invalid changed_paths: expected non-empty file paths");
|
|
125
|
+
}
|
|
126
|
+
function bashDescription(backgroundEnabled, escalationModes) {
|
|
127
|
+
const background = backgroundEnabled ? "Set `run_in_background: true` for long-running commands: the call returns a job id immediately; read its output with `job_output` and stop it with `job_kill`." : "Background execution is not available; long-running commands must finish within the timeout.";
|
|
128
|
+
const base = `Execute a bash command (\`bash -c\`) and return its stdout/stderr. Each call runs in a fresh shell: no state (cwd, variables, functions) persists between calls — pass \`workdir\` instead of using \`cd\`. Non-zero exits are reported as \`[exit code: N]\`. Current harness environment facts are exposed through managed \`$${HYDRA_ENV_PREFIX}*\` variables; inspect them when needed. Commands may run under a file sandbox; a blocked file operation is reported as \`[sandbox: file access denied under <mode> mode]\` — a policy denial, not a bug in the command; do not retry another way. Long output is truncated to its tail; the full output is saved to a file whose path is reported when available. ` + background;
|
|
129
|
+
if (escalationModes.length === 0) return base;
|
|
130
|
+
return base + " Attempting a command the sandbox may deny is safe and expected: run it and read the marker rather than assuming the denial. When a command is denied and a wider mode would let it succeed, escalate immediately in the same turn — the one sanctioned exception to a denial: retry the exact same command once with `sandbox_permissions` (the narrowest wider mode that suffices) plus a one-sentence `justification`. Do not detour through chat to ask permission first — the approval prompt raised by that retry is how the user consents. If the session states approval prompts are disabled, there is no exception: a denial is final — do not set `sandbox_permissions`. Never escalate speculatively: ground the request in a real denial — normally the one this command just hit; escalating up front is fine only when this session already denied the same access. A rejected escalation is final for that command — stop and explain, never work around it — but it does not forbid attempting or escalating other commands later.";
|
|
131
|
+
}
|
|
132
|
+
function presentBashCall(args) {
|
|
133
|
+
if (args.run_in_background === true) return {
|
|
134
|
+
card: "generic",
|
|
135
|
+
title: args.command,
|
|
136
|
+
kind: "execute",
|
|
137
|
+
rawInput: args.command,
|
|
138
|
+
content: [{
|
|
139
|
+
type: "text",
|
|
140
|
+
text: args.description
|
|
141
|
+
}]
|
|
142
|
+
};
|
|
143
|
+
return {
|
|
144
|
+
card: "terminal",
|
|
145
|
+
title: args.command,
|
|
146
|
+
description: args.description,
|
|
147
|
+
...args.workdir !== void 0 ? { cwd: args.workdir } : {}
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Present completed foreground output as a terminal; background acknowledgements
|
|
152
|
+
* and execution errors use generic fenced output without an exit-status pill.
|
|
153
|
+
*/
|
|
154
|
+
function presentBashResult(args, result) {
|
|
155
|
+
const block = result.content.length === 1 ? result.content[0] : void 0;
|
|
156
|
+
if (block === void 0 || block.type !== "text") return void 0;
|
|
157
|
+
const raw = block.text;
|
|
158
|
+
if (typeof args === "object" && args !== null && args.run_in_background === true || result.isError) return {
|
|
159
|
+
card: "generic",
|
|
160
|
+
content: [{
|
|
161
|
+
type: "text",
|
|
162
|
+
text: `\`\`\`console\n${raw.replace(/\n+$/, "")}\n\`\`\``
|
|
163
|
+
}]
|
|
164
|
+
};
|
|
165
|
+
const { body, ...exit } = parseExitStatus(raw);
|
|
166
|
+
return {
|
|
167
|
+
card: "terminal",
|
|
168
|
+
output: body,
|
|
169
|
+
...exit
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Resolve an explicit workdir first, making a relative one session-workspace-relative;
|
|
174
|
+
* otherwise use the filesystem identity of the session cwd and leave executor
|
|
175
|
+
* defaulting as the fallback. A resolved sandbox-policy root wins so workdir
|
|
176
|
+
* and confinement use the exact same per-call identity.
|
|
177
|
+
*/
|
|
178
|
+
function resolveWorkdir(modelWorkdir, exec, policyWorkspaceRoot) {
|
|
179
|
+
const headerCwd = exec.agent?.session.header.cwd;
|
|
180
|
+
const sessionCwd = policyWorkspaceRoot ?? (headerCwd === void 0 ? void 0 : canonicalPath(headerCwd));
|
|
181
|
+
if (modelWorkdir === void 0) return sessionCwd;
|
|
182
|
+
if (sessionCwd !== void 0 && !isAbsolute(modelWorkdir)) return resolve(sessionCwd, modelWorkdir);
|
|
183
|
+
return modelWorkdir;
|
|
184
|
+
}
|
|
185
|
+
/** Detach the executor DTO from readonly Service Definition types into plain JSON data. */
|
|
186
|
+
function canonicalBashResult(result) {
|
|
187
|
+
const output = (stream) => ({
|
|
188
|
+
text: stream.text,
|
|
189
|
+
truncated: stream.truncated,
|
|
190
|
+
...stream.spillPath !== void 0 ? { spillPath: stream.spillPath } : {}
|
|
191
|
+
});
|
|
192
|
+
return {
|
|
193
|
+
exitCode: result.exitCode,
|
|
194
|
+
signal: result.signal,
|
|
195
|
+
timedOut: result.timedOut,
|
|
196
|
+
aborted: result.aborted,
|
|
197
|
+
timeoutMs: result.timeoutMs,
|
|
198
|
+
stdout: output(result.stdout),
|
|
199
|
+
stderr: output(result.stderr),
|
|
200
|
+
...result.sandbox !== void 0 ? { sandbox: {
|
|
201
|
+
mode: result.sandbox.mode,
|
|
202
|
+
denied: result.sandbox.denied,
|
|
203
|
+
...result.sandbox.enforcement !== void 0 ? { enforcement: result.sandbox.enforcement } : {},
|
|
204
|
+
...result.sandbox.runnerFailed !== void 0 ? { runnerFailed: result.sandbox.runnerFailed } : {}
|
|
205
|
+
} } : {}
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
/** Canonical background-handle properties shared by the bash output union. */
|
|
209
|
+
const BACKGROUND_OUTPUT_PROPERTIES = {
|
|
210
|
+
kind: {
|
|
211
|
+
type: "string",
|
|
212
|
+
required: true,
|
|
213
|
+
const: "background"
|
|
214
|
+
},
|
|
215
|
+
jobId: {
|
|
216
|
+
type: "string",
|
|
217
|
+
required: true
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
function apply(ctx, config = {}) {
|
|
221
|
+
const backgroundEnabled = config.enableRunInBackground ?? true;
|
|
222
|
+
const defaultMode = ctx.shell.sandboxMode;
|
|
223
|
+
const escalationModes = defaultMode === void 0 ? [] : ESCALATION_TARGETS;
|
|
224
|
+
const sandboxPolicy = defaultMode === void 0 ? void 0 : ctx.get("sandboxPolicy");
|
|
225
|
+
if (defaultMode !== void 0 && sandboxPolicy === void 0) throw new Error("tool-bash: the mounted bash executor confines but ctx.sandboxPolicy is missing");
|
|
226
|
+
/** Resolve the complete standing policy for this call when a confining executor is mounted. */
|
|
227
|
+
const resolveSandboxPolicy = (exec) => sandboxPolicy?.resolve(exec.agent === void 0 ? {} : { session: exec.agent.session });
|
|
228
|
+
/**
|
|
229
|
+
* Resolve a sandbox-escalation request through `ctx.approval` BEFORE
|
|
230
|
+
* anything executes, delegating the shared fail-closed sequence (strict
|
|
231
|
+
* widening, channel resolution, outcome mapping) to
|
|
232
|
+
* {@link approveEscalation}. This tool contributes only the composition
|
|
233
|
+
* guard (the fields are unadvertised without a sandboxing executor, yet
|
|
234
|
+
* schema validation checks advertised keys only, so an unadvertised
|
|
235
|
+
* `sandbox_permissions` still reaches execute) and the approval
|
|
236
|
+
* ingredients. The shared policy resolver is required whenever the executor
|
|
237
|
+
* advertises confinement, so a split composition fails at tool-plugin load.
|
|
238
|
+
*/
|
|
239
|
+
const approveBashEscalation = (mode, justification, exec, standingPolicy) => {
|
|
240
|
+
if (escalationModes.length === 0) throw new Error("sandbox_permissions is not available in this composition (no sandboxing executor to escalate)");
|
|
241
|
+
const effectiveMode = standingPolicy.mode;
|
|
242
|
+
return approveEscalation({
|
|
243
|
+
requestedMode: mode,
|
|
244
|
+
justification,
|
|
245
|
+
effectiveMode,
|
|
246
|
+
subject: "command"
|
|
247
|
+
}, {
|
|
248
|
+
approver: ctx.get("approval"),
|
|
249
|
+
agent: exec.agent,
|
|
250
|
+
callId: exec.callId,
|
|
251
|
+
toolName: "bash",
|
|
252
|
+
signal: exec.signal
|
|
253
|
+
});
|
|
254
|
+
};
|
|
255
|
+
ctx.systemPrompt.section({
|
|
256
|
+
name: "tool:bash",
|
|
257
|
+
order: 105,
|
|
258
|
+
text: "Check the [exit code: N] marker on every bash result; investigate failures before moving on."
|
|
259
|
+
});
|
|
260
|
+
ctx.tools.register(defineTool({
|
|
261
|
+
name: "bash",
|
|
262
|
+
description: bashDescription(backgroundEnabled, escalationModes),
|
|
263
|
+
parameters: {
|
|
264
|
+
command: {
|
|
265
|
+
type: "string",
|
|
266
|
+
required: true,
|
|
267
|
+
description: "The bash command to execute."
|
|
268
|
+
},
|
|
269
|
+
description: {
|
|
270
|
+
type: "string",
|
|
271
|
+
required: true,
|
|
272
|
+
description: "Clear, concise description of what this command does in active voice, 5-10 words (shown in the UI). Examples: \"ls\" → \"List files in current directory\"; \"git status\" → \"Show working tree status\"; \"npm install\" → \"Install package dependencies\"."
|
|
273
|
+
},
|
|
274
|
+
timeoutMs: {
|
|
275
|
+
type: "number",
|
|
276
|
+
description: "Timeout in milliseconds. The executor applies its configured default and cap, and kills the command on expiry."
|
|
277
|
+
},
|
|
278
|
+
workdir: {
|
|
279
|
+
type: "string",
|
|
280
|
+
description: "Working directory for this command. Defaults to the session workspace; a relative path is resolved against it."
|
|
281
|
+
},
|
|
282
|
+
changed_paths: {
|
|
283
|
+
type: "array",
|
|
284
|
+
items: { type: "string" },
|
|
285
|
+
description: "Foreground files this command changed, declared explicitly for workspace instruction refresh."
|
|
286
|
+
},
|
|
287
|
+
...backgroundEnabled ? { run_in_background: {
|
|
288
|
+
type: "boolean",
|
|
289
|
+
description: "Run in the background and return a job id immediately (collect with job_output, stop with job_kill). No timeout applies."
|
|
290
|
+
} } : {},
|
|
291
|
+
...escalationModes.length > 0 ? {
|
|
292
|
+
sandbox_permissions: {
|
|
293
|
+
type: "string",
|
|
294
|
+
enum: [...escalationModes],
|
|
295
|
+
description: "The wider sandbox mode this command needs. Only valid as a one-shot retry of a command the sandbox just denied; requires justification and user approval."
|
|
296
|
+
},
|
|
297
|
+
justification: {
|
|
298
|
+
type: "string",
|
|
299
|
+
description: "Required with sandbox_permissions: one sentence for the user explaining why this exact command needs the wider access."
|
|
300
|
+
}
|
|
301
|
+
} : {}
|
|
302
|
+
},
|
|
303
|
+
output: {
|
|
304
|
+
schema: { oneOf: [{
|
|
305
|
+
type: "object",
|
|
306
|
+
additionalProperties: false,
|
|
307
|
+
properties: BACKGROUND_OUTPUT_PROPERTIES
|
|
308
|
+
}, {
|
|
309
|
+
type: "object",
|
|
310
|
+
additionalProperties: false,
|
|
311
|
+
properties: {
|
|
312
|
+
kind: {
|
|
313
|
+
type: "string",
|
|
314
|
+
required: true,
|
|
315
|
+
const: "foreground"
|
|
316
|
+
},
|
|
317
|
+
exitCode: {
|
|
318
|
+
required: true,
|
|
319
|
+
oneOf: [{ type: "integer" }, { type: "null" }]
|
|
320
|
+
},
|
|
321
|
+
signal: {
|
|
322
|
+
required: true,
|
|
323
|
+
oneOf: [{ type: "string" }, { type: "null" }]
|
|
324
|
+
},
|
|
325
|
+
timedOut: {
|
|
326
|
+
type: "boolean",
|
|
327
|
+
required: true
|
|
328
|
+
},
|
|
329
|
+
aborted: {
|
|
330
|
+
type: "boolean",
|
|
331
|
+
required: true
|
|
332
|
+
},
|
|
333
|
+
timeoutMs: {
|
|
334
|
+
type: "number",
|
|
335
|
+
required: true
|
|
336
|
+
},
|
|
337
|
+
stdout: {
|
|
338
|
+
type: "object",
|
|
339
|
+
additionalProperties: false,
|
|
340
|
+
required: true,
|
|
341
|
+
properties: {
|
|
342
|
+
text: {
|
|
343
|
+
type: "string",
|
|
344
|
+
required: true
|
|
345
|
+
},
|
|
346
|
+
truncated: {
|
|
347
|
+
type: "boolean",
|
|
348
|
+
required: true
|
|
349
|
+
},
|
|
350
|
+
spillPath: { type: "string" }
|
|
351
|
+
}
|
|
352
|
+
},
|
|
353
|
+
stderr: {
|
|
354
|
+
type: "object",
|
|
355
|
+
additionalProperties: false,
|
|
356
|
+
required: true,
|
|
357
|
+
properties: {
|
|
358
|
+
text: {
|
|
359
|
+
type: "string",
|
|
360
|
+
required: true
|
|
361
|
+
},
|
|
362
|
+
truncated: {
|
|
363
|
+
type: "boolean",
|
|
364
|
+
required: true
|
|
365
|
+
},
|
|
366
|
+
spillPath: { type: "string" }
|
|
367
|
+
}
|
|
368
|
+
},
|
|
369
|
+
sandbox: {
|
|
370
|
+
type: "object",
|
|
371
|
+
additionalProperties: false,
|
|
372
|
+
properties: {
|
|
373
|
+
mode: {
|
|
374
|
+
type: "string",
|
|
375
|
+
required: true
|
|
376
|
+
},
|
|
377
|
+
denied: {
|
|
378
|
+
type: "boolean",
|
|
379
|
+
required: true
|
|
380
|
+
},
|
|
381
|
+
enforcement: { type: "string" },
|
|
382
|
+
runnerFailed: { type: "boolean" }
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
}] },
|
|
387
|
+
render: (_args, value) => [{
|
|
388
|
+
type: "text",
|
|
389
|
+
text: value.kind === "background" ? `started background job ${value.jobId}` : renderResult(value, escalationModes)
|
|
390
|
+
}]
|
|
391
|
+
},
|
|
392
|
+
async execute(args, exec) {
|
|
393
|
+
validateBashArgs(args);
|
|
394
|
+
const standingPolicy = resolveSandboxPolicy(exec);
|
|
395
|
+
const approvedMode = args.sandbox_permissions !== void 0 && args.justification !== void 0 ? await approveBashEscalation(args.sandbox_permissions, args.justification, exec, standingPolicy) : void 0;
|
|
396
|
+
const policy = approvedMode === void 0 ? standingPolicy : {
|
|
397
|
+
...standingPolicy,
|
|
398
|
+
mode: approvedMode
|
|
399
|
+
};
|
|
400
|
+
const workdir = resolveWorkdir(args.workdir, exec, standingPolicy?.workspaceRoot);
|
|
401
|
+
const hydraEnv = ctx.shellEnv.collect(exec);
|
|
402
|
+
const request = {
|
|
403
|
+
command: args.command,
|
|
404
|
+
...workdir !== void 0 ? { workdir } : {},
|
|
405
|
+
...args.timeoutMs !== void 0 ? { timeoutMs: args.timeoutMs } : {},
|
|
406
|
+
hydraEnv,
|
|
407
|
+
...policy !== void 0 ? { sandboxPolicy: policy } : {}
|
|
408
|
+
};
|
|
409
|
+
if (args.run_in_background === true) {
|
|
410
|
+
if (!backgroundEnabled) throw new Error("run_in_background is disabled for this deployment (enableRunInBackground: false)");
|
|
411
|
+
const jobs = ctx.get("jobs");
|
|
412
|
+
if (jobs === void 0) throw new Error("background jobs unavailable: load @hydraharness/harness-jobs and @hydraharness/harness-tool-jobs");
|
|
413
|
+
if (exec.signal.aborted) {
|
|
414
|
+
const error = new HarnessError("tool call aborted", TOOL_ABORTED);
|
|
415
|
+
error.name = "AbortError";
|
|
416
|
+
throw error;
|
|
417
|
+
}
|
|
418
|
+
return {
|
|
419
|
+
kind: "background",
|
|
420
|
+
jobId: jobs.start({
|
|
421
|
+
kind: "bash",
|
|
422
|
+
label: args.command,
|
|
423
|
+
...exec.agent ? { owner: exec.agent } : {},
|
|
424
|
+
run: () => {
|
|
425
|
+
const proc = ctx.shell.start(ctx.shell.resolve(request));
|
|
426
|
+
return {
|
|
427
|
+
cancel: () => void proc.kill(),
|
|
428
|
+
done: proc.done.then(() => processOutcome(proc)),
|
|
429
|
+
readOutput: () => renderProcessRead(proc.readOutput(), proc.sandbox, escalationModes)
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
})
|
|
433
|
+
};
|
|
434
|
+
}
|
|
435
|
+
const result = await ctx.shell.run(ctx.shell.resolve({
|
|
436
|
+
...request,
|
|
437
|
+
signal: exec.signal
|
|
438
|
+
}));
|
|
439
|
+
if (result.aborted) {
|
|
440
|
+
const error = new HarnessError("tool call aborted", TOOL_ABORTED);
|
|
441
|
+
error.name = "AbortError";
|
|
442
|
+
throw error;
|
|
443
|
+
}
|
|
444
|
+
return {
|
|
445
|
+
kind: "foreground",
|
|
446
|
+
...canonicalBashResult(result)
|
|
447
|
+
};
|
|
448
|
+
},
|
|
449
|
+
presentCall: presentBashCall,
|
|
450
|
+
presentResult: presentBashResult
|
|
451
|
+
}));
|
|
452
|
+
}
|
|
453
|
+
//#endregion
|
|
454
|
+
export { Config, apply, inject, name };
|
package/lib/invariant.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
//#region lib/types/invariant.js
|
|
2
|
+
/**
|
|
3
|
+
* Package-owned invariant companion for `@hydraharness/harness-tool-bash`.
|
|
4
|
+
* @module @hydraharness/harness-tool-bash/invariant
|
|
5
|
+
*/
|
|
6
|
+
const PACKAGE_NAME = "@hydraharness/harness-tool-bash";
|
|
7
|
+
/** Cordis companion plugin name. */
|
|
8
|
+
const name = "tool-bash-invariant";
|
|
9
|
+
/** Service required before the companion can reserve package ownership. */
|
|
10
|
+
const inject = ["invariants"];
|
|
11
|
+
/**
|
|
12
|
+
* No runtime invariant: the environment registry validates ownership and collected values at each
|
|
13
|
+
* mutation/read; it publishes no independent snapshot that a companion could cross-check.
|
|
14
|
+
*/
|
|
15
|
+
const install = () => {};
|
|
16
|
+
/**
|
|
17
|
+
* Register this package's invariant companion.
|
|
18
|
+
* @param ctx - Cordis context carrying the invariant service.
|
|
19
|
+
* @returns the installed registration's disposer after setup succeeds.
|
|
20
|
+
*/
|
|
21
|
+
const apply = (ctx) => Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install));
|
|
22
|
+
//#endregion
|
|
23
|
+
export { apply, inject, name };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generic-task adaptation for background bash process handles.
|
|
3
|
+
*
|
|
4
|
+
* @module @hydraharness/harness-tool-bash/background
|
|
5
|
+
*/
|
|
6
|
+
import type { ShellProcess } from '@hydraharness/harness-shell';
|
|
7
|
+
/**
|
|
8
|
+
* Map a settled background process onto the generic task-outcome vocabulary:
|
|
9
|
+
* `killed` stays `killed` (detail: the signal when one is known), everything
|
|
10
|
+
* else is `completed` with the exit code as detail. A nonzero command exit is
|
|
11
|
+
* reported, not failed, exactly like the foreground rendering.
|
|
12
|
+
* @param proc - the settled process handle.
|
|
13
|
+
* @returns the outcome for the `ctx.jobs` registration.
|
|
14
|
+
*/
|
|
15
|
+
export declare function processOutcome(proc: ShellProcess): {
|
|
16
|
+
status: 'completed' | 'killed';
|
|
17
|
+
detail: string;
|
|
18
|
+
};
|
|
19
|
+
//# sourceMappingURL=background.d.ts.map
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Model-facing Consumer of the `ctx.shell` capability seam. Background calls
|
|
3
|
+
* register process handles with `ctx.jobs`; their work uses job cancellation
|
|
4
|
+
* rather than the tool-call signal after an id is returned.
|
|
5
|
+
*
|
|
6
|
+
* TODO(permissions): deployment policy belongs in `tools/pre-execute` and
|
|
7
|
+
* sandboxing executors; see docs/architecture.md § Where new behavior goes.
|
|
8
|
+
* @module @hydraharness/harness-tool-bash
|
|
9
|
+
*/
|
|
10
|
+
import type { Context } from '@hydraharness/cordis';
|
|
11
|
+
import z from '@hydraharness/schemastery';
|
|
12
|
+
export declare const name = "tool-bash";
|
|
13
|
+
export declare const inject: string[];
|
|
14
|
+
/** Configuration for the bash tool. */
|
|
15
|
+
export interface Config {
|
|
16
|
+
/** Expose `run_in_background` (default true); disabled calls are also rejected. */
|
|
17
|
+
enableRunInBackground?: boolean;
|
|
18
|
+
}
|
|
19
|
+
/** Runtime configuration schema for the bash tool plugin. */
|
|
20
|
+
export declare const Config: z<Config>;
|
|
21
|
+
export declare function apply(ctx: Context, config?: Config): void;
|
|
22
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Package-owned invariant companion for `@hydraharness/harness-tool-bash`.
|
|
3
|
+
* @module @hydraharness/harness-tool-bash/invariant
|
|
4
|
+
*/
|
|
5
|
+
import type { Context } from '@hydraharness/cordis';
|
|
6
|
+
/** Cordis companion plugin name. */
|
|
7
|
+
export declare const name = "tool-bash-invariant";
|
|
8
|
+
/** Service required before the companion can reserve package ownership. */
|
|
9
|
+
export declare const inject: string[];
|
|
10
|
+
/**
|
|
11
|
+
* Register this package's invariant companion.
|
|
12
|
+
* @param ctx - Cordis context carrying the invariant service.
|
|
13
|
+
* @returns the installed registration's disposer after setup succeeds.
|
|
14
|
+
*/
|
|
15
|
+
export declare const apply: (ctx: Context) => Promise<() => void>;
|
|
16
|
+
//# sourceMappingURL=invariant.d.ts.map
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Model-facing result rendering for the bash tool.
|
|
3
|
+
*
|
|
4
|
+
* @module @hydraharness/harness-tool-bash/render
|
|
5
|
+
*/
|
|
6
|
+
import type { ShellProcessRead, ShellRunResult, ShellSandboxInfo } from '@hydraharness/harness-shell';
|
|
7
|
+
import type { SandboxMode } from '@hydraharness/harness-sandbox';
|
|
8
|
+
/**
|
|
9
|
+
* Shape one finished run into the text the model sees: stdout, then a marked
|
|
10
|
+
* stderr section, then exit-status markers. Non-zero exits are reported, not
|
|
11
|
+
* errored — the model decides how to react; only infrastructure failures
|
|
12
|
+
* (spawn errors, aborts) surface as isError results.
|
|
13
|
+
* @param result - the completed foreground run from the executor.
|
|
14
|
+
* @param escalationModes - the escalation targets this composition advertises;
|
|
15
|
+
* non-empty adds the same-turn escalation hint after a denial marker
|
|
16
|
+
* (default `[]`: no hint).
|
|
17
|
+
* @returns the model-facing text: output body (or `(no output)`), then any timeout/signal/exit markers, each on its own line.
|
|
18
|
+
*/
|
|
19
|
+
export declare function renderResult(result: ShellRunResult, escalationModes?: readonly SandboxMode[]): string;
|
|
20
|
+
/**
|
|
21
|
+
* Shape one background-process read into the `job_output` delta the model
|
|
22
|
+
* sees: the incremental delta, plus the lossy-read notice (with full-stream
|
|
23
|
+
* spill paths) when in-memory truncation dropped unread bytes. Empty-delta
|
|
24
|
+
* rendering (`(no new output)`) is the generic job controller's job.
|
|
25
|
+
* @param read - one incremental read from the process handle.
|
|
26
|
+
* @param sandbox - settled sandbox facts, when this was a confined process.
|
|
27
|
+
* @param escalationModes - escalation targets advertised by this composition.
|
|
28
|
+
* @returns the delta text with any loss or sandbox notice appended.
|
|
29
|
+
*/
|
|
30
|
+
export declare function renderProcessRead(read: ShellProcessRead, sandbox?: ShellSandboxInfo, escalationModes?: readonly SandboxMode[]): string;
|
|
31
|
+
/**
|
|
32
|
+
* The exit-status parse is the shared marker-contract half of the shell-tool
|
|
33
|
+
* rendering story, owned by `@hydraharness/harness-shell` so `@hydraharness/harness-tool-pwsh` reuses
|
|
34
|
+
* it (its renderer emits the same markers). Re-exported here to keep
|
|
35
|
+
* `../src/render.ts` a single import root for bash-tool consumers.
|
|
36
|
+
*/
|
|
37
|
+
export { parseExitStatus, type ParsedExitStatus } from '@hydraharness/harness-shell';
|
|
38
|
+
//# sourceMappingURL=render.d.ts.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,78 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hydraharness/harness-tool-bash",
|
|
3
|
-
"
|
|
4
|
-
"
|
|
5
|
-
|
|
3
|
+
"description": "Model-facing bash tool with optional generic background-job and sandbox-escalation support",
|
|
4
|
+
"hydra": {
|
|
5
|
+
"plugin": {
|
|
6
|
+
"application": "Let the agent run Bash commands and collect their output."
|
|
7
|
+
}
|
|
8
|
+
},
|
|
9
|
+
"version": "0.1.1-rc.7",
|
|
10
|
+
"publishConfig": {
|
|
11
|
+
"access": "public"
|
|
12
|
+
},
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "git+https://github.com/MaiHongPhong1902/Hydra-Harness.git",
|
|
16
|
+
"directory": "packages/shell/tool-bash"
|
|
17
|
+
},
|
|
18
|
+
"type": "module",
|
|
19
|
+
"main": "lib/index.js",
|
|
20
|
+
"types": "lib/types/index.d.ts",
|
|
21
|
+
"exports": {
|
|
22
|
+
".": {
|
|
23
|
+
"types": "./lib/types/index.d.ts",
|
|
24
|
+
"default": "./lib/index.js"
|
|
25
|
+
},
|
|
26
|
+
"./invariant": {
|
|
27
|
+
"types": "./lib/types/invariant.d.ts",
|
|
28
|
+
"default": "./lib/invariant.js"
|
|
29
|
+
},
|
|
30
|
+
"./src/*": "./src/*",
|
|
31
|
+
"./package.json": "./package.json"
|
|
32
|
+
},
|
|
33
|
+
"files": [
|
|
34
|
+
"lib/index.js",
|
|
35
|
+
"lib/invariant.js",
|
|
36
|
+
"lib/types/**/*.d.ts"
|
|
37
|
+
],
|
|
38
|
+
"license": "MIT",
|
|
39
|
+
"peerDependencies": {
|
|
40
|
+
"@hydraharness/harness-agent": "^0.1.1-rc.7",
|
|
41
|
+
"@hydraharness/harness-shell-env": "^0.1.1-rc.7",
|
|
42
|
+
"@hydraharness/harness-shell": "^0.1.1-rc.7",
|
|
43
|
+
"@hydraharness/harness-invariants": "^0.1.1-rc.7",
|
|
44
|
+
"@hydraharness/harness-llm": "^0.1.1-rc.7",
|
|
45
|
+
"@hydraharness/harness-sandbox-policy": "^0.1.1-rc.7",
|
|
46
|
+
"@hydraharness/harness-sandbox": "^0.1.1-rc.7",
|
|
47
|
+
"@hydraharness/harness-system-prompt": "^0.1.1-rc.7",
|
|
48
|
+
"@hydraharness/harness-jobs": "^0.1.1-rc.7",
|
|
49
|
+
"@hydraharness/harness-tools": "^0.1.1-rc.7",
|
|
50
|
+
"@hydraharness/harness-user-approval": "^0.1.1-rc.7",
|
|
51
|
+
"@hydraharness/cordis": "^4.0.2"
|
|
52
|
+
},
|
|
53
|
+
"dependencies": {
|
|
54
|
+
"@hydraharness/schemastery": "^3.18.2"
|
|
55
|
+
},
|
|
56
|
+
"devDependencies": {
|
|
57
|
+
"@hydraharness/harness-agent": "^0.1.1-rc.7",
|
|
58
|
+
"@hydraharness/harness-agent-loop": "^0.1.1-rc.7",
|
|
59
|
+
"@hydraharness/harness-agent-loop-testkit": "^0.1.1-rc.7",
|
|
60
|
+
"@hydraharness/harness-shell": "^0.1.1-rc.7",
|
|
61
|
+
"@hydraharness/harness-shell-env": "^0.1.1-rc.7",
|
|
62
|
+
"@hydraharness/harness-bash-local": "^0.1.1-rc.7",
|
|
63
|
+
"@hydraharness/harness-invariants": "^0.1.1-rc.7",
|
|
64
|
+
"@hydraharness/harness-llm": "^0.1.1-rc.7",
|
|
65
|
+
"@hydraharness/harness-sandbox": "^0.1.1-rc.7",
|
|
66
|
+
"@hydraharness/harness-sandbox-policy": "^0.1.1-rc.7",
|
|
67
|
+
"@hydraharness/harness-subprocess-local": "^0.1.1-rc.7",
|
|
68
|
+
"@hydraharness/harness-session-persistence-jsonl": "^0.1.1-rc.7",
|
|
69
|
+
"@hydraharness/harness-system-prompt": "^0.1.1-rc.7",
|
|
70
|
+
"@hydraharness/harness-jobs": "^0.1.1-rc.7",
|
|
71
|
+
"@hydraharness/harness-user-approval": "^0.1.1-rc.7",
|
|
72
|
+
"@hydraharness/harness-jobs-local": "^0.1.1-rc.7",
|
|
73
|
+
"@hydraharness/cordis": "^4.0.2",
|
|
74
|
+
"@hydraharness/harness-session": "^0.1.1-rc.7",
|
|
75
|
+
"@hydraharness/harness-tool-jobs": "^0.1.1-rc.7",
|
|
76
|
+
"@hydraharness/harness-tools": "^0.1.1-rc.7"
|
|
77
|
+
}
|
|
6
78
|
}
|