@hydraharness/harness-shell 0.1.1-rc.10

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 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 ADDED
@@ -0,0 +1,51 @@
1
+ # @hydraharness/harness-shell
2
+
3
+ The **`ShellExecutor`** (`ctx.shell`) defines WHAT a bash backend does — run foreground commands and start background processes — without saying HOW. Job ids, ownership, collection, cancellation, and notices belong to the generic `ctx.jobs` runtime.
4
+
5
+ This package owns the Service Definition role of the bash capability, split so each role can evolve (and be swapped) independently:
6
+
7
+ | Package | Role |
8
+ |---|---|
9
+ | `@hydraharness/harness-shell` (this) | Service Definition: abstract service + vocabulary types |
10
+ | `@hydraharness/harness-bash-local` | Service Provider: local subprocesses |
11
+ | `@hydraharness/harness-bash-sandbox` | Service Provider: `@hydraharness/harness-bash-local`'s mechanics with every spawn confined via [`ctx.sandbox`](../../sandbox/sandbox/), denials reported as result facts |
12
+ | `@hydraharness/harness-tool-bash` | the model-facing tool schemas over `ctx.shell` |
13
+
14
+ The split is a standard capability seam ([capability-seams Agent Note](../../../.agents/notes/implemented/architecture/2026-06-13-capability-seams.md)): `@hydraharness/harness-bash-sandbox` is a sandboxing executor behind the same Service Definition — the Consumer detects its `sandboxMode` capability and adds escalation fields without importing the provider — and a containerized or remote executor slots in the same way.
15
+
16
+ ## Service API (`ctx.shell`)
17
+
18
+ | Member | Semantics |
19
+ |---|---|
20
+ | `run(spec)` | Foreground execution. Resolves when the command finishes. **Rejects only for infrastructure failures** (unusable workdir, missing shell, pre-aborted signal); nonzero exits, timeout kills, and abort kills resolve with a descriptive `ShellRunResult`. |
21
+ | `start(spec)` | Background execution. Returns a task-free `ShellProcess` handle immediately; **no timeout applies**. The caller may adapt it into `ctx.jobs`. |
22
+ | `sandboxMode` | The capability fact for the tool layer: the default mode a SANDBOXING executor confines under (`undefined` in the base class — "this executor does not sandbox"). `@hydraharness/harness-tool-bash` reads it at registration to advertise the escalation fields only when the composition honors them. |
23
+ | `ShellProcess.readOutput()` | **Incremental** output read — consecutive reads never re-deliver. Reads that lost data to buffer bounds flag `lossy` and point at full-stream spill files. |
24
+ | `ShellProcess.kill()` | Kill the process group. Returns `false` when it already finished. |
25
+
26
+ Implementations subclass `ShellExecutor` and implement the abstract methods. Disposal must kill every running process and await its exit.
27
+
28
+ `SHELL_SETTINGS_NAMESPACE` (`bash`) is exported here rather than by a provider because it names the capability, not an implementation. A host composes exactly one provider of `ctx.shell` — the win32 layer swaps the POSIX rows for the pwsh ones, and mounting both fails loud on a duplicate service registration — so every provider can register this one namespace with its own schema and composition entry without two of them ever colliding, and a `settings.yaml` carried between platforms keeps resolving on both.
29
+
30
+ ## Vocabulary
31
+
32
+ `ShellExecRequest` (command, workdir?, timeoutMs?, stdoutMaxBytes?, signal?, stdin?, env?, hydraEnv?, sandboxPolicy?) resolves to `ShellExecSpec` (command, workdir, timeoutMs, stdoutMaxBytes, signal?, stdin?, env?, hydraEnv?, sandboxPolicy) before execution. `stdoutMaxBytes` is a trusted foreground-run capture budget for consumers that must parse complete bounded stdout; the model-facing bash tool does not expose it. `sandboxPolicy` is optional on the request and required-but-nullable on the resolved spec: it carries the complete per-call mode and workspace root. The sandbox tool path resolves it from the calling session through `ctx.sandboxPolicy`; a direct sandbox-executor caller falls back to deployment policy, while a non-sandboxing executor carries the field and confines nothing.
33
+
34
+ The per-session sandbox-mode override vocabulary (the `'sandbox/mode'` event, the `effectiveSandboxMode(events)` fold, and the `setSandboxMode(session, mode)` write path) is NOT here — it is policy state shared by every enforcing family, owned by [`@hydraharness/harness-sandbox-policy`](../../sandbox/sandbox-policy/). `run()` returns `ShellRunResult`; `start()` returns `ShellProcess`, whose incremental read and kill methods are adapted by `@hydraharness/harness-tool-bash` into a generic task registration. A sandboxing executor stamps `ShellSandboxInfo` on foreground results and settled process handles. See `src/types.ts` and [subsystems/shell.md](../../../docs/subsystems/shell.md).
35
+
36
+ `stdin` and ordinary `env` are set by in-process plugins (the hooks bridges, native plugins) to feed a hook command its JSON payload and `CLAUDE_PROJECT_DIR`/`CLAUDE_PLUGIN_ROOT` values. `hydraEnv` is a separate trusted overlay restricted by type to managed keys; the exported `HYDRA_ENV_PREFIX` is the single source for that namespace, its `HydraEnvironmentKey` template type, executor scrubbing, registry validation, derived built-in names, and model guidance. Model bash uses the current snapshot collected by `ctx.shellEnv`. Implementations remove inherited managed keys, then merge `hydraEnv` after ordinary `env`, so an omitted current fact cannot fall back to stale ambient state and an `env` entry cannot displace a managed value. The model-facing tool exposes none of these as parameters. All three remain optional on the resolved spec; absent means no input/overlay. See [the bash-stdin-env Agent Note](../../../.agents/notes/implemented/architecture/2026-06-30-bash-stdin-env-trusted-plugin-api.md) and [the session environment Agent Note](../../../.agents/notes/implemented/feature/2026-07-10-agent-session-identity-and-log-location.md).
37
+
38
+ The exported `parseExitStatus` (with `ParsedExitStatus`) is the shared rendering contract half of the shell tools: the inverse of the `[exit code: N]` / `[killed by signal: X]` markers `@hydraharness/harness-tool-bash`'s `renderResult` and `@hydraharness/harness-tool-pwsh`'s `renderPwshResult` append. Both tools' `presentResult` use it to split the rendered text into the terminal card's output body and its exit-status pill; it lives with the Service Definition so the two tools never drift on the marker contract.
39
+
40
+ ## Model Experience
41
+
42
+ Indirectly, through `@hydraharness/harness-tool-bash`, which turns executor output and sandbox facts into guidance and retained tool-result tokens.
43
+
44
+ #### KV Cache effect
45
+
46
+ No direct invalidation; the named consumer owns any request-prefix changes.
47
+
48
+ ## Known Limitations and Deferred Work
49
+
50
+ - **No interactive-input vocabulary** — `stdin` is written once at spawn and closed; the seam has no channel to feed a running task and no PTY session concept.
51
+ - **Foreground timeouts are always executor-owned** — a caller-owned-deadline mode on the seam is explicitly deferred by [the tool-call timeout-policy Agent Note](../../../.agents/notes/implemented/architecture/2026-07-07-tool-call-timeout-policy.md).
package/lib/index.js ADDED
@@ -0,0 +1,96 @@
1
+ import { Service } from "@hydraharness/cordis";
2
+ import { settingsNamespace } from "@hydraharness/harness-settings";
3
+ import { HYDRA_ENV_PREFIX } from "@hydraharness/harness-subprocess";
4
+ //#region lib/types/render.js
5
+ /**
6
+ * Shared rendering helpers for the shell tools (`@hydraharness/harness-tool-bash`,
7
+ * `@hydraharness/harness-tool-pwsh`): the exit-status marker contract the tools' renderers
8
+ * emit and the presentation layer parses back.
9
+ * @module @hydraharness/harness-shell/render
10
+ */
11
+ /**
12
+ * Split a rendered shell-tool result string into its output body and the
13
+ * structured exit status — the inverse of the `[exit code: N]` /
14
+ * `[killed by signal: X]` markers the shell tools' renderers append. A killed
15
+ * marker yields `signal`; otherwise a non-zero marker yields `exitCode`;
16
+ * absent both means a clean exit 0.
17
+ *
18
+ * The consumed marker is removed from `body` because a terminal presentation
19
+ * shows the exit status as its own pill: leaving the marker in the output
20
+ * would render the exit twice. Other markers (timeout, sandbox denial) carry
21
+ * facts no pill shows, so they stay in the body.
22
+ *
23
+ * Replay only retains the rendered content text, not the original
24
+ * `ShellRunResult`, so terminal presentation must recover the exit pill here.
25
+ * Requiring a leading newline and the end of the string keeps ordinary output
26
+ * that merely ends with marker-like text from matching unless the final line
27
+ * is indistinguishable from a real marker.
28
+ * @param text - rendered model-facing shell-tool result.
29
+ * @returns the marker-free body plus the recovered terminal exit code or signal.
30
+ */
31
+ function parseExitStatus(text) {
32
+ const signal = /\n\[killed by signal: ([^\]\n]+)\]$/.exec(text);
33
+ if (signal?.[1] !== void 0) return {
34
+ body: text.slice(0, signal.index),
35
+ signal: signal[1]
36
+ };
37
+ const exit = /\n\[exit code: (\d+)\]$/.exec(text);
38
+ if (exit?.[1] !== void 0) return {
39
+ body: text.slice(0, exit.index),
40
+ exitCode: Number(exit[1])
41
+ };
42
+ return {
43
+ body: text,
44
+ exitCode: 0
45
+ };
46
+ }
47
+ //#endregion
48
+ //#region lib/types/index.js
49
+ /**
50
+ * Service Definition for the `ctx.shell` capability seam, covering foreground commands and background process
51
+ * handles. Job ids, ownership, polling, and notices belong to
52
+ * `@hydraharness/harness-jobs`, keeping executors independent of sessions.
53
+ * @module @hydraharness/harness-shell
54
+ */
55
+ /**
56
+ * Settings namespace of this capability, owned here rather than by either
57
+ * executor family because it names the capability, not an implementation: a
58
+ * host composes exactly one provider of `ctx.shell` (the win32 layer swaps the
59
+ * POSIX rows for the pwsh ones, and mounting both fails loud on a duplicate
60
+ * service registration), so the providers share one namespace without ever
61
+ * registering it twice, and a settings document carried between platforms
62
+ * keeps resolving on both.
63
+ */
64
+ const SHELL_SETTINGS_NAMESPACE = settingsNamespace("shell");
65
+ /**
66
+ * Abstract bash execution service. Subclass, implement the abstract methods,
67
+ * and load the subclass as a plugin — it registers as `ctx.shell` (one
68
+ * implementation per context; loading a second throws, which is cordis'
69
+ * standard duplicate-service behavior).
70
+ *
71
+ * Implementations must honor these semantics:
72
+ * - {@link run} rejects only for infrastructure failures. Nonzero exits,
73
+ * timeout kills, and abort kills resolve with a {@link ShellRunResult}.
74
+ * - {@link start} returns immediately; no timeout applies to background
75
+ * processes. `done` settles at process close and never rejects; spawn
76
+ * failures settle as `killed` with the error on stderr.
77
+ * - {@link ShellProcess.readOutput} is incremental: consecutive reads never
78
+ * repeat output. Lossy reads report truncation and available spill files.
79
+ * - A still-running background process is stopped and awaited when its
80
+ * owning composition tears down. With the subprocess seam that
81
+ * boundary is `ctx.subprocess` disposal, so a background process survives
82
+ * an executor-only reload.
83
+ */
84
+ var ShellExecutor = class extends Service {
85
+ constructor(ctx) {
86
+ super(ctx, "shell");
87
+ }
88
+ /**
89
+ * The sandbox mode this executor applies by default, or `undefined` when it
90
+ * does not sandbox commands.
91
+ * @returns the configured default sandbox mode, when supported.
92
+ */
93
+ get sandboxMode() {}
94
+ };
95
+ //#endregion
96
+ export { HYDRA_ENV_PREFIX, SHELL_SETTINGS_NAMESPACE, ShellExecutor, ShellExecutor as default, parseExitStatus };
@@ -0,0 +1,17 @@
1
+ //#region lib/types/invariant.js
2
+ /** Package-owned invariant companion for the bash seam. @module @hydraharness/harness-shell/invariant */
3
+ const PACKAGE_NAME = "@hydraharness/harness-shell";
4
+ /** Cordis companion plugin name. */
5
+ const name = "shell-invariant";
6
+ /** Service required before the companion can reserve package ownership. */
7
+ const inject = ["invariants"];
8
+ /** No runtime invariant: this stateless Service Definition owns request/result types, while executors and policy own observations. */
9
+ const install = () => {};
10
+ /**
11
+ * Register the bash invariant companion.
12
+ * @param ctx - Cordis context carrying the invariant service.
13
+ * @returns the installed registration's disposer after setup succeeds.
14
+ */
15
+ const apply = (ctx) => Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install));
16
+ //#endregion
17
+ export { apply, inject, name };
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Service Definition for the `ctx.shell` capability seam, covering foreground commands and background process
3
+ * handles. Job ids, ownership, polling, and notices belong to
4
+ * `@hydraharness/harness-jobs`, keeping executors independent of sessions.
5
+ * @module @hydraharness/harness-shell
6
+ */
7
+ import { Context, Service } from '@hydraharness/cordis';
8
+ import type { SandboxMode } from '@hydraharness/harness-sandbox';
9
+ import type { ShellExecRequest, ShellExecSpec, ShellProcess, ShellRunResult } from './types.ts';
10
+ /**
11
+ * Settings namespace of this capability, owned here rather than by either
12
+ * executor family because it names the capability, not an implementation: a
13
+ * host composes exactly one provider of `ctx.shell` (the win32 layer swaps the
14
+ * POSIX rows for the pwsh ones, and mounting both fails loud on a duplicate
15
+ * service registration), so the providers share one namespace without ever
16
+ * registering it twice, and a settings document carried between platforms
17
+ * keeps resolving on both.
18
+ */
19
+ export declare const SHELL_SETTINGS_NAMESPACE: import("@hydraharness/harness-settings").SettingsNamespace;
20
+ export { HYDRA_ENV_PREFIX } from './types.ts';
21
+ export type { ShellExecRequest, ShellExecSpec, ShellProcess, ShellProcessRead, ShellProcessStatus, ShellRunResult, ShellSandboxInfo, CollectedOutput, HydraEnvironment, HydraEnvironmentKey, } from './types.ts';
22
+ export { parseExitStatus } from './render.ts';
23
+ export type { ParsedExitStatus } from './render.ts';
24
+ declare module '@hydraharness/cordis' {
25
+ interface Context {
26
+ shell: ShellExecutor;
27
+ }
28
+ }
29
+ /**
30
+ * Abstract bash execution service. Subclass, implement the abstract methods,
31
+ * and load the subclass as a plugin — it registers as `ctx.shell` (one
32
+ * implementation per context; loading a second throws, which is cordis'
33
+ * standard duplicate-service behavior).
34
+ *
35
+ * Implementations must honor these semantics:
36
+ * - {@link run} rejects only for infrastructure failures. Nonzero exits,
37
+ * timeout kills, and abort kills resolve with a {@link ShellRunResult}.
38
+ * - {@link start} returns immediately; no timeout applies to background
39
+ * processes. `done` settles at process close and never rejects; spawn
40
+ * failures settle as `killed` with the error on stderr.
41
+ * - {@link ShellProcess.readOutput} is incremental: consecutive reads never
42
+ * repeat output. Lossy reads report truncation and available spill files.
43
+ * - A still-running background process is stopped and awaited when its
44
+ * owning composition tears down. With the subprocess seam that
45
+ * boundary is `ctx.subprocess` disposal, so a background process survives
46
+ * an executor-only reload.
47
+ */
48
+ export declare abstract class ShellExecutor extends Service {
49
+ constructor(ctx: Context);
50
+ /**
51
+ * The sandbox mode this executor applies by default, or `undefined` when it
52
+ * does not sandbox commands.
53
+ * @returns the configured default sandbox mode, when supported.
54
+ */
55
+ get sandboxMode(): SandboxMode | undefined;
56
+ /**
57
+ * Apply implementation-owned defaults and caps to a request before execution.
58
+ * @param request - the caller's request; omitted fields get this
59
+ * implementation's defaults, capped fields are clamped.
60
+ * @returns the fully-specified spec to hand to {@link run}/{@link start}.
61
+ */
62
+ abstract resolve(request: ShellExecRequest): ShellExecSpec;
63
+ /**
64
+ * Run a command in the foreground; resolves when it finishes.
65
+ * @param spec - a resolved spec from {@link resolve}, never a raw request.
66
+ * @returns the outcome; nonzero exits, timeout kills, and abort kills
67
+ * resolve with a descriptive result rather than reject.
68
+ */
69
+ abstract run(spec: ShellExecSpec): Promise<ShellRunResult>;
70
+ /**
71
+ * Start a background process and return its handle immediately.
72
+ * @param spec - a resolved spec from {@link resolve}, never a raw request.
73
+ * @returns the live process handle (reads, kill, quiescence promise).
74
+ */
75
+ abstract start(spec: ShellExecSpec): ShellProcess;
76
+ }
77
+ export default ShellExecutor;
78
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,13 @@
1
+ /** Package-owned invariant companion for the bash seam. @module @hydraharness/harness-shell/invariant */
2
+ import type { Context } from '@hydraharness/cordis';
3
+ /** Cordis companion plugin name. */
4
+ export declare const name = "shell-invariant";
5
+ /** Service required before the companion can reserve package ownership. */
6
+ export declare const inject: string[];
7
+ /**
8
+ * Register the bash invariant companion.
9
+ * @param ctx - Cordis context carrying the invariant service.
10
+ * @returns the installed registration's disposer after setup succeeds.
11
+ */
12
+ export declare const apply: (ctx: Context) => Promise<() => void>;
13
+ //# sourceMappingURL=invariant.d.ts.map
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Shared rendering helpers for the shell tools (`@hydraharness/harness-tool-bash`,
3
+ * `@hydraharness/harness-tool-pwsh`): the exit-status marker contract the tools' renderers
4
+ * emit and the presentation layer parses back.
5
+ * @module @hydraharness/harness-shell/render
6
+ */
7
+ /**
8
+ * The exit status recovered from a rendered result, with the output body that
9
+ * status was split off from.
10
+ */
11
+ export type ParsedExitStatus = {
12
+ body: string;
13
+ } & ({
14
+ exitCode: number;
15
+ } | {
16
+ signal: string;
17
+ });
18
+ /**
19
+ * Split a rendered shell-tool result string into its output body and the
20
+ * structured exit status — the inverse of the `[exit code: N]` /
21
+ * `[killed by signal: X]` markers the shell tools' renderers append. A killed
22
+ * marker yields `signal`; otherwise a non-zero marker yields `exitCode`;
23
+ * absent both means a clean exit 0.
24
+ *
25
+ * The consumed marker is removed from `body` because a terminal presentation
26
+ * shows the exit status as its own pill: leaving the marker in the output
27
+ * would render the exit twice. Other markers (timeout, sandbox denial) carry
28
+ * facts no pill shows, so they stay in the body.
29
+ *
30
+ * Replay only retains the rendered content text, not the original
31
+ * `ShellRunResult`, so terminal presentation must recover the exit pill here.
32
+ * Requiring a leading newline and the end of the string keeps ordinary output
33
+ * that merely ends with marker-like text from matching unless the final line
34
+ * is indistinguishable from a real marker.
35
+ * @param text - rendered model-facing shell-tool result.
36
+ * @returns the marker-free body plus the recovered terminal exit code or signal.
37
+ */
38
+ export declare function parseExitStatus(text: string): ParsedExitStatus;
39
+ //# sourceMappingURL=render.d.ts.map
@@ -0,0 +1,175 @@
1
+ /**
2
+ * Execution types for the bash executor seam. Background job semantics belong
3
+ * to `@hydraharness/harness-jobs`; this seam exposes only process handles. The
4
+ * managed-environment and captured-output vocabulary is owned by the
5
+ * subprocess seam and re-exported here so bash consumers keep one import
6
+ * root.
7
+ * @module hydra-shell/types
8
+ */
9
+ import type { SandboxEnforcement, SandboxExecutionPolicy, SandboxMode } from '@hydraharness/harness-sandbox';
10
+ import type { CollectedOutput, HydraEnvironment } from '@hydraharness/harness-subprocess';
11
+ export { HYDRA_ENV_PREFIX } from '@hydraharness/harness-subprocess';
12
+ export type { CollectedOutput, HydraEnvironment, HydraEnvironmentKey } from '@hydraharness/harness-subprocess';
13
+ /**
14
+ * Sandbox facts for one run, present iff a sandboxing executor handled it.
15
+ * Facts are reported independently of process exit status so callers can
16
+ * distinguish command failures from policy denials and runner failures.
17
+ */
18
+ export interface ShellSandboxInfo {
19
+ /** The mode the command actually ran under. */
20
+ mode: SandboxMode;
21
+ /** Whether the sandbox denied a file operation. */
22
+ denied: boolean;
23
+ /** How completely the selected runner enforced the requested mode. */
24
+ enforcement?: SandboxEnforcement;
25
+ /** Whether the sandbox runner failed before the command could run. */
26
+ runnerFailed?: boolean;
27
+ }
28
+ /**
29
+ * A caller's execution REQUEST: `workdir` and `timeoutMs` are optional and
30
+ * filled by {@link ShellExecutor.resolve} from the implementation's config.
31
+ * This is the model-/plugin-facing shape; pass it to `resolve()` to obtain a
32
+ * fully-resolved {@link ShellExecSpec}.
33
+ */
34
+ export interface ShellExecRequest {
35
+ command: string;
36
+ /** Working directory override (default: implementation-configured). */
37
+ workdir?: string | undefined;
38
+ /** Timeout override in milliseconds (implementations cap it). */
39
+ timeoutMs?: number | undefined;
40
+ /**
41
+ * Foreground stdout capture budget in bytes. Absent uses the executor's
42
+ * default output cap. Trusted in-process consumers use this when they must
43
+ * parse complete stdout up to their own bounded limit; the model-facing bash
44
+ * tool does not expose it as a parameter.
45
+ */
46
+ stdoutMaxBytes?: number | undefined;
47
+ /** Abort signal — implementations kill the command when it fires. */
48
+ signal?: AbortSignal | undefined;
49
+ /**
50
+ * Bytes to write to the command's stdin, then close it. Absent leaves stdin
51
+ * closed/empty (the default for model-driven tool calls). Set by in-process
52
+ * plugins (e.g. the hooks bridges, which write a hook command's JSON payload
53
+ * to its stdin); the model-facing bash tool does not expose it as a parameter
54
+ * (a model that needs stdin uses shell syntax like a heredoc or a pipe).
55
+ */
56
+ stdin?: string | undefined;
57
+ /**
58
+ * Ordinary environment entries for the command, merged after the credential
59
+ * scrub. Managed facts belong in {@link hydraEnv}, which merges after this
60
+ * map, so an entry here can never displace one. Set by in-process plugins
61
+ * (the hooks bridges set `CLAUDE_PROJECT_DIR`, `CLAUDE_PLUGIN_ROOT`, …); the
62
+ * model-facing bash tool does not expose it as a parameter.
63
+ */
64
+ env?: Record<string, string> | undefined;
65
+ /**
66
+ * Harness-owned `HYDRA_*` variables for this execution (typed to managed
67
+ * keys). Executors discard ambient `HYDRA_*` entries before merging this
68
+ * snapshot last, so an unavailable current fact cannot inherit a stale
69
+ * value from the harness process and a caller {@link env} entry cannot
70
+ * displace a managed one.
71
+ */
72
+ hydraEnv?: HydraEnvironment | undefined;
73
+ /** Fully resolved per-call sandbox policy; sandboxing executors default it. */
74
+ sandboxPolicy?: SandboxExecutionPolicy | undefined;
75
+ }
76
+ /**
77
+ * A resolved execution spec. {@link ShellExecutor.resolve} fills and caps the
78
+ * required fields; {@link ShellExecutor.start} ignores `timeoutMs` because
79
+ * background processes have no executor timeout.
80
+ */
81
+ export interface ShellExecSpec {
82
+ command: string;
83
+ workdir: string;
84
+ timeoutMs: number;
85
+ /**
86
+ * Resolved foreground stdout capture budget in bytes. `run()` uses it for
87
+ * stdout; background jobs and stderr keep the executor's own output cap.
88
+ */
89
+ stdoutMaxBytes: number;
90
+ /** Abort signal — implementations kill the command when it fires. */
91
+ signal?: AbortSignal | undefined;
92
+ /** Bytes to write to stdin before closing it; absent means no stdin. */
93
+ stdin?: string | undefined;
94
+ /**
95
+ * Ordinary environment entries carried through from
96
+ * {@link ShellExecRequest.env}; {@link hydraEnv} still merges after them.
97
+ * OPTIONAL on the spec for the same reason as `stdin`: absent means no
98
+ * ordinary extra environment.
99
+ */
100
+ env?: Record<string, string> | undefined;
101
+ /** Managed `HYDRA_*` snapshot (typed to managed keys); merges after {@link env}. */
102
+ hydraEnv?: HydraEnvironment | undefined;
103
+ /** Resolved sandbox policy; ignored by executors that do not confine. */
104
+ sandboxPolicy: SandboxExecutionPolicy | undefined;
105
+ }
106
+ /** The outcome of one completed (or killed) foreground run. */
107
+ export interface ShellRunResult {
108
+ /** Exit code; null when the process died from a signal. */
109
+ exitCode: number | null;
110
+ /** Terminating signal (e.g. 'SIGTERM'); null on normal exit. */
111
+ signal: NodeJS.Signals | null;
112
+ /**
113
+ * True when the executor's own timeout was the FIRST cause to cut the command
114
+ * short. Mutually exclusive with {@link aborted}: one fused deadline drives
115
+ * both the timeout and the caller's cancellation, so a timeout and an abort
116
+ * racing before process close report the single first-abort cause, not both
117
+ * (see the [timeout-library Agent Note](../../../../.agents/notes/implemented/architecture/2026-07-06-timeout-deadline-library.md)).
118
+ */
119
+ timedOut: boolean;
120
+ /**
121
+ * True when the caller's `AbortSignal` was the FIRST cause to kill the command
122
+ * (and it was not the executor's own timeout). Mutually exclusive with
123
+ * {@link timedOut} — see there for the first-cause classification.
124
+ */
125
+ aborted: boolean;
126
+ /** The effective timeout applied to this run (after defaulting/capping). */
127
+ timeoutMs: number;
128
+ stdout: CollectedOutput;
129
+ stderr: CollectedOutput;
130
+ /** Sandbox execution facts, absent for an unsandboxed executor. */
131
+ sandbox?: ShellSandboxInfo;
132
+ }
133
+ /** Lifecycle of a background process. */
134
+ export type ShellProcessStatus = 'running' | 'completed' | 'killed';
135
+ /** One incremental {@link ShellProcess.readOutput} read. */
136
+ export interface ShellProcessRead {
137
+ /** Output produced since the previous read (stderr in a marked section). */
138
+ delta: string;
139
+ /** True when truncation dropped unread bytes the delta cannot include. */
140
+ lossy: boolean;
141
+ /** Full stdout spill file, when stdout truncation occurred and a safe path is available. */
142
+ stdoutSpillPath?: string;
143
+ /** Full stderr spill file, when stderr truncation occurred and a safe path is available. */
144
+ stderrSpillPath?: string;
145
+ }
146
+ /**
147
+ * A background process handle returned by {@link ShellExecutor.start}. It is the
148
+ * only access path; buffered output remains readable after exit. Composition
149
+ * teardown (the subprocess service's disposal) kills running processes and
150
+ * awaits {@link done}; an executor-only reload leaves them running.
151
+ */
152
+ export interface ShellProcess {
153
+ /** Process lifecycle state (settled exactly once). */
154
+ status: ShellProcessStatus;
155
+ /** Exit code once finished (null = killed by signal / still running). */
156
+ exitCode: number | null;
157
+ /** Terminating signal name, when signal-killed. */
158
+ signal: NodeJS.Signals | null;
159
+ /** Resolves when the underlying process closes (never rejects — a spawn failure settles as `killed` with the error on stderr). */
160
+ readonly done: Promise<void>;
161
+ /** Sandbox facts, stamped once a confined process settles. */
162
+ sandbox?: ShellSandboxInfo;
163
+ /**
164
+ * Read output produced since the previous read (consuming — consecutive
165
+ * reads never re-deliver). Reads that lost data flag `lossy` and point at
166
+ * full-stream spill files when available.
167
+ */
168
+ readOutput(): ShellProcessRead;
169
+ /**
170
+ * Kill the process group. Returns false when it had already finished
171
+ * (no-op); idempotent.
172
+ */
173
+ kill(): boolean;
174
+ }
175
+ //# sourceMappingURL=types.d.ts.map
package/package.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "@hydraharness/harness-shell",
3
+ "description": "Abstract bash executor seam (ctx.shell) for the Hydra harness",
4
+ "version": "0.1.1-rc.10",
5
+ "publishConfig": {
6
+ "access": "public"
7
+ },
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/MaiHongPhong2010/Hydra-Harness.git",
11
+ "directory": "packages/shell/shell"
12
+ },
13
+ "type": "module",
14
+ "main": "lib/index.js",
15
+ "types": "lib/types/index.d.ts",
16
+ "exports": {
17
+ ".": {
18
+ "types": "./lib/types/index.d.ts",
19
+ "default": "./lib/index.js"
20
+ },
21
+ "./invariant": {
22
+ "types": "./lib/types/invariant.d.ts",
23
+ "default": "./lib/invariant.js"
24
+ },
25
+ "./src/*": "./src/*",
26
+ "./package.json": "./package.json"
27
+ },
28
+ "files": [
29
+ "lib/index.js",
30
+ "lib/invariant.js",
31
+ "lib/types/**/*.d.ts"
32
+ ],
33
+ "license": "MIT",
34
+ "peerDependencies": {
35
+ "@hydraharness/harness-invariants": "^0.1.1-rc.10",
36
+ "@hydraharness/harness-subprocess": "^0.1.1-rc.10",
37
+ "@hydraharness/cordis": "^4.0.2",
38
+ "@hydraharness/harness-sandbox": "^0.1.1-rc.10",
39
+ "@hydraharness/harness-settings": "^0.1.1-rc.10"
40
+ },
41
+ "devDependencies": {
42
+ "@hydraharness/harness-invariants": "^0.1.1-rc.10",
43
+ "@hydraharness/harness-sandbox": "^0.1.1-rc.10",
44
+ "@hydraharness/harness-subprocess": "^0.1.1-rc.10",
45
+ "@hydraharness/cordis": "^4.0.2",
46
+ "@hydraharness/harness-settings": "^0.1.1-rc.10"
47
+ }
48
+ }