dsh-plugin-wsl-env 0.7.4 → 0.8.0

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.
Files changed (79) hide show
  1. package/CHANGELOG.md +298 -0
  2. package/CONTRIBUTING.md +129 -39
  3. package/README.md +20 -20
  4. package/docs/ARCHITECTURE.md +6 -3
  5. package/docs/LIMITATIONS.md +13 -0
  6. package/docs/README.zh.md +20 -20
  7. package/lib/agent-confined.d.ts +42 -0
  8. package/lib/agent-confined.js +25 -41
  9. package/lib/agent-errors.d.ts +31 -0
  10. package/lib/agent-errors.js +19 -16
  11. package/lib/agent-exec.d.ts +159 -0
  12. package/lib/agent-exec.js +151 -165
  13. package/lib/agent-protocol.d.ts +192 -0
  14. package/lib/agent-protocol.js +76 -100
  15. package/lib/agent-shared.d.ts +27 -0
  16. package/lib/agent-shared.js +12 -12
  17. package/lib/agent.d.ts +353 -0
  18. package/lib/agent.js +536 -552
  19. package/lib/auto-preset.d.ts +110 -0
  20. package/lib/auto-preset.js +157 -141
  21. package/lib/bwrap.d.ts +90 -0
  22. package/lib/bwrap.js +18 -18
  23. package/lib/file-reference-wsl.d.ts +160 -0
  24. package/lib/file-reference-wsl.js +192 -173
  25. package/lib/fs-decisions.d.ts +107 -0
  26. package/lib/fs-decisions.js +64 -60
  27. package/lib/fs-routing.d.ts +182 -0
  28. package/lib/fs-routing.js +173 -116
  29. package/lib/fs-substrate.d.ts +258 -0
  30. package/lib/fs-substrate.js +248 -248
  31. package/lib/fs.d.ts +10 -0
  32. package/lib/fs.js +5 -0
  33. package/lib/fsio-agent.d.ts +307 -0
  34. package/lib/fsio-agent.js +414 -410
  35. package/lib/fsio-text.d.ts +115 -0
  36. package/lib/fsio-text.js +104 -84
  37. package/lib/git-route.d.ts +100 -0
  38. package/lib/git-route.js +46 -47
  39. package/lib/index.d.ts +1003 -0
  40. package/lib/index.js +1101 -997
  41. package/lib/listing.d.ts +114 -0
  42. package/lib/listing.js +68 -55
  43. package/lib/paths.d.ts +182 -0
  44. package/lib/paths.js +136 -105
  45. package/lib/picker.d.ts +266 -0
  46. package/lib/picker.js +371 -351
  47. package/lib/ports.d.ts +44 -0
  48. package/lib/ports.js +39 -29
  49. package/lib/preset-choice.d.ts +33 -0
  50. package/lib/preset-choice.js +15 -7
  51. package/lib/probe-cache.d.ts +40 -0
  52. package/lib/probe-cache.js +35 -29
  53. package/lib/sandbox-core.d.ts +79 -0
  54. package/lib/sandbox-core.js +71 -88
  55. package/lib/sandbox.d.ts +48 -0
  56. package/lib/sandbox.js +4 -4
  57. package/lib/search-exec.d.ts +96 -0
  58. package/lib/search-exec.js +52 -79
  59. package/lib/search-route.d.ts +75 -0
  60. package/lib/search-route.js +30 -31
  61. package/lib/shell-env.d.ts +88 -0
  62. package/lib/shell-env.js +78 -70
  63. package/lib/shell.d.ts +10 -0
  64. package/lib/shell.js +5 -0
  65. package/lib/subprocess.d.ts +313 -0
  66. package/lib/subprocess.js +267 -262
  67. package/lib/terminal-activity.d.ts +98 -0
  68. package/lib/terminal-activity.js +29 -39
  69. package/lib/terminal-route.d.ts +27 -0
  70. package/lib/terminal-route.js +16 -8
  71. package/lib/watcher.d.ts +77 -0
  72. package/lib/watcher.js +120 -126
  73. package/lib/workspace-files-route.d.ts +132 -0
  74. package/lib/workspace-files-route.js +73 -81
  75. package/lib/workspace-files-wsl.d.ts +257 -0
  76. package/lib/workspace-files-wsl.js +311 -251
  77. package/lib/wsl.d.ts +266 -0
  78. package/lib/wsl.js +144 -151
  79. package/package.json +68 -20
package/lib/agent-exec.js CHANGED
@@ -22,183 +22,169 @@
22
22
  * Pure shaping over an injected runner, so the handle semantics are
23
23
  * unit-testable without a distro.
24
24
  *
25
+ * This is a TypeScript source built to `lib/agent-exec.js`; edit THIS file and
26
+ * run `pnpm run build` — the artifact under `lib/` is generated, and `pnpm test`
27
+ * fails when it drifts.
28
+ *
25
29
  * @module dsh-plugin-wsl/agent-exec
26
30
  */
27
-
28
31
  import { CwdError } from "./agent-errors.js";
29
-
30
32
  /**
31
33
  * A memory-backed output reader in the seam's shape.
32
- * @param {Buffer} buffer - the complete stream.
33
- * @returns {{readFrom: (fromByte: number) => {text: string, lossy: boolean, nextOffset: number}}}
34
+ *
35
+ * @param buffer - the complete stream.
36
+ * @returns the reader over it.
34
37
  */
35
38
  function memoryReader(buffer) {
36
- return {
37
- readFrom(fromByte) {
38
- const start = Math.min(fromByte, buffer.length);
39
- return {
40
- text: buffer.subarray(start).toString("utf8"),
41
- lossy: false,
42
- nextOffset: buffer.length,
43
- };
44
- },
45
- };
39
+ return {
40
+ readFrom(fromByte) {
41
+ const start = Math.min(fromByte, buffer.length);
42
+ return {
43
+ text: buffer.subarray(start).toString("utf8"),
44
+ lossy: false,
45
+ nextOffset: buffer.length,
46
+ };
47
+ },
48
+ };
46
49
  }
47
-
48
50
  /**
49
51
  * Run one command through the agent and present it as an execution handle.
50
52
  *
51
- * @param {object} request - the execution request.
52
- * @param {object} request.agent - the {@link WslAgent}-like runner: `exec`
53
- * accepting `{cwd, argv, timeoutMs, signal}` and resolving
54
- * `{exitCode, stdout, stderr}`; throws `AgentUnavailableError` when out.
55
- * @param {string} request.cwd - Linux workdir.
56
- * @param {string[]} request.argv - the distro-side argv, bwrap-wrapped when confined.
57
- * @param {number} [request.timeoutMs] - whole-command budget; the agent TERMs
58
- * at the ceiling second and KILLs after its grace.
59
- * @param {number} [request.maxOutputBytes] - per-stream capture ceiling the
60
- * agent enforces; the handle reports a cut stream as `truncated: true`.
61
- * @param {AbortSignal} [request.signal] - the caller's cancellation.
62
- * @param {(proc: object) => void} [request.onStarted] - provider-facts hook,
63
- * called synchronously before the handle can settle.
64
- * @returns {object} the execution handle (`status`, `exitCode`, `signal`,
65
- * `observed`, `done`, `readOutput`, `kill`, `result`).
53
+ * @param request - the execution request.
54
+ * @returns the execution handle (`status`, `exitCode`, `signal`, `observed`,
55
+ * `done`, `readOutput`, `kill`, `result`).
66
56
  */
67
57
  export function agentExecutionHandle({ agent, cwd, argv, timeoutMs = 0, maxOutputBytes = 0, signal, onStarted }) {
68
- // Our own controller is the kill channel; the caller's signal races it.
69
- const controller = new AbortController();
70
- let timedOut = false;
71
- let killed = false;
72
- let callerAborted = false;
73
- if (signal) {
74
- if (signal.aborted) callerAborted = true;
75
- else signal.addEventListener("abort", () => { callerAborted = true; controller.abort(signal.reason); }, { once: true });
76
- }
77
- if (timeoutMs > 0) {
78
- setTimeout(() => {
79
- timedOut = true;
80
- controller.abort(new Error("BASH_TIMEOUT"));
81
- }, timeoutMs).unref?.();
82
- }
83
-
84
- const settle = agent.exec({ cwd, argv, timeoutMs, maxOutputBytes, signal: controller.signal }).then(
85
- (result) => ({ ok: true, result }),
86
- (error) => {
87
- // A workdir the distro cannot enter is the command's own failure, not
88
- // the agent's: synthesize the relay-shaped stderr the one-shot path
89
- // produces, so the executor's `workdirFailure` check reports it the
90
- // same way. An agent that is actually OUT propagates for fallback.
91
- if (error instanceof CwdError) {
92
- return {
93
- ok: true,
94
- result: {
95
- exitCode: 1,
96
- stdout: Buffer.alloc(0),
97
- stderr: Buffer.from(`<3>WSL: CreateProcessCommon: chdir(${error.linuxPath}) failed: No such file or directory`, "utf8"),
98
- },
99
- };
100
- }
101
- return { ok: false, error };
102
- },
103
- );
104
-
105
- /** @type {Buffer} */
106
- let stdout = Buffer.alloc(0);
107
- /** @type {Buffer} */
108
- let stderr = Buffer.alloc(0);
109
- /** The agent's per-stream capture-cap flags, surfaced as `truncated`. */
110
- let stdoutTruncated = false;
111
- let stderrTruncated = false;
112
- let exitOutcome = { exitCode: null, signal: "SIGTERM" };
113
-
114
- let settleError;
115
- const done = settle.then(({ ok, result, error }) => {
116
- if (ok) {
117
- stdout = result.stdout;
118
- stderr = result.stderr;
119
- stdoutTruncated = result.truncated?.stdout === true;
120
- stderrTruncated = result.truncated?.stderr === true;
121
- // The distro timeout and kill() both mean "we stopped it"; a genuine
122
- // exit keeps its code.
123
- if (timedOut || killed || callerAborted) exitOutcome = { exitCode: null, signal: "SIGTERM" };
124
- else exitOutcome = { exitCode: result.exitCode, signal: null };
125
- } else if (timedOut || killed) {
126
- // OUR kill (the in-distro timeout or kill()): the agent's rejection is
127
- // the abort plumbing, not a failure — settle as the killed outcome the
128
- // one-shot path reports for the same situation.
129
- stdout = Buffer.alloc(0);
130
- stderr = Buffer.alloc(0);
131
- } else {
132
- // The caller's cancellation rejections propagate (upstream keeps
133
- // cancellation as a rejection), as does a genuinely out-of-service
134
- // agent — that one is the executor's fallback cue.
135
- settleError = error;
58
+ // Our own controller is the kill channel; the caller's signal races it.
59
+ const controller = new AbortController();
60
+ let timedOut = false;
61
+ let killed = false;
62
+ let callerAborted = false;
63
+ if (signal) {
64
+ if (signal.aborted)
65
+ callerAborted = true;
66
+ else
67
+ signal.addEventListener("abort", () => { callerAborted = true; controller.abort(signal.reason); }, { once: true });
68
+ }
69
+ if (timeoutMs > 0) {
70
+ setTimeout(() => {
71
+ timedOut = true;
72
+ controller.abort(new Error("BASH_TIMEOUT"));
73
+ }, timeoutMs).unref?.();
136
74
  }
137
- return exitOutcome;
138
- });
139
-
140
- const stdoutReader = () => memoryReader(stdout);
141
- const stderrReader = () => memoryReader(stderr);
142
-
143
- let stdoutOffset = 0;
144
- let stderrOffset = 0;
145
-
146
- /** @type {object} */
147
- const proc = {
148
- status: "running",
149
- exitCode: null,
150
- signal: null,
151
- observed: {
152
- get stdout() { return stdoutReader(); },
153
- get stderr() { return stderrReader(); },
154
- },
155
- done: done.then(() => {
156
- proc.status = exitOutcome.signal !== null || exitOutcome.exitCode === null ? "killed" : "completed";
157
- proc.exitCode = exitOutcome.exitCode;
158
- proc.signal = exitOutcome.signal;
159
- }),
160
- readOutput() {
161
- const out = stdoutReader().readFrom(stdoutOffset);
162
- const err = stderrReader().readFrom(stderrOffset);
163
- stdoutOffset = out.nextOffset;
164
- stderrOffset = err.nextOffset;
165
- const separator = out.text.length > 0 && !out.text.endsWith("\n") ? "\n" : "";
166
- return {
167
- delta: out.text + (err.text.length > 0 ? `${separator}[stderr]\n${err.text}` : ""),
168
- lossy: false,
169
- };
170
- },
171
- kill() {
172
- if (proc.status !== "running") return false;
173
- // Upstream marks the handle killed SYNCHRONOUSLY, before the process is
174
- // observed to die — the second kill() must already be a no-op.
175
- proc.status = "killed";
176
- killed = true;
177
- controller.abort(new Error("killed"));
178
- return true;
179
- },
180
- result() {
181
- return done.then(() => {
182
- // The spawn-equivalent failure (the agent being out) rejects here,
183
- // exactly like upstream's `running.done` rejection carried by result().
184
- if (settleError !== undefined) throw settleError;
185
- // `truncated` is REQUIRED by the bash tool's canonical result — it
186
- // copies the field unconditionally, and an `undefined` fails the wire's
187
- // lossless-JSON snapshot ("value is not lossless JSON"). The agent's
188
- // capture-cap flags are what make it TRUE instead of a perma-lie.
189
- const output = (text, truncated) => ({ text, lossy: false, truncated });
190
- return {
191
- exitCode: proc.exitCode,
192
- signal: proc.signal,
193
- timedOut,
194
- aborted: callerAborted && !timedOut,
195
- timeoutMs,
196
- stdout: output(stdout.toString("utf8"), stdoutTruncated),
197
- stderr: output(stderr.toString("utf8"), stderrTruncated),
198
- };
199
- });
200
- },
201
- };
202
- onStarted?.(proc);
203
- return proc;
75
+ const settle = agent.exec({ cwd, argv, timeoutMs, maxOutputBytes, signal: controller.signal }).then((result) => ({ ok: true, result }), (error) => {
76
+ // A workdir the distro cannot enter is the command's own failure, not
77
+ // the agent's: synthesize the relay-shaped stderr the one-shot path
78
+ // produces, so the executor's `workdirFailure` check reports it the
79
+ // same way. An agent that is actually OUT propagates for fallback.
80
+ if (error instanceof CwdError) {
81
+ return {
82
+ ok: true,
83
+ result: {
84
+ exitCode: 1,
85
+ stdout: Buffer.alloc(0),
86
+ stderr: Buffer.from(`<3>WSL: CreateProcessCommon: chdir(${error.linuxPath}) failed: No such file or directory`, "utf8"),
87
+ },
88
+ };
89
+ }
90
+ return { ok: false, error };
91
+ });
92
+ let stdout = Buffer.alloc(0);
93
+ let stderr = Buffer.alloc(0);
94
+ /** The agent's per-stream capture-cap flags, surfaced as `truncated`. */
95
+ let stdoutTruncated = false;
96
+ let stderrTruncated = false;
97
+ let exitOutcome = { exitCode: null, signal: "SIGTERM" };
98
+ let settleError;
99
+ const done = settle.then(({ ok, result, error }) => {
100
+ if (ok) {
101
+ stdout = result.stdout;
102
+ stderr = result.stderr;
103
+ stdoutTruncated = result.truncated?.stdout === true;
104
+ stderrTruncated = result.truncated?.stderr === true;
105
+ // The distro timeout and kill() both mean "we stopped it"; a genuine
106
+ // exit keeps its code.
107
+ if (timedOut || killed || callerAborted)
108
+ exitOutcome = { exitCode: null, signal: "SIGTERM" };
109
+ else
110
+ exitOutcome = { exitCode: result.exitCode, signal: null };
111
+ }
112
+ else if (timedOut || killed) {
113
+ // OUR kill (the in-distro timeout or kill()): the agent's rejection is
114
+ // the abort plumbing, not a failure — settle as the killed outcome the
115
+ // one-shot path reports for the same situation.
116
+ stdout = Buffer.alloc(0);
117
+ stderr = Buffer.alloc(0);
118
+ }
119
+ else {
120
+ // The caller's cancellation rejections propagate (upstream keeps
121
+ // cancellation as a rejection), as does a genuinely out-of-service
122
+ // agent — that one is the executor's fallback cue.
123
+ settleError = error;
124
+ }
125
+ return exitOutcome;
126
+ });
127
+ const stdoutReader = () => memoryReader(stdout);
128
+ const stderrReader = () => memoryReader(stderr);
129
+ let stdoutOffset = 0;
130
+ let stderrOffset = 0;
131
+ const proc = {
132
+ status: "running",
133
+ exitCode: null,
134
+ signal: null,
135
+ observed: {
136
+ get stdout() { return stdoutReader(); },
137
+ get stderr() { return stderrReader(); },
138
+ },
139
+ done: done.then(() => {
140
+ proc.status = exitOutcome.signal !== null || exitOutcome.exitCode === null ? "killed" : "completed";
141
+ proc.exitCode = exitOutcome.exitCode;
142
+ proc.signal = exitOutcome.signal;
143
+ }),
144
+ readOutput() {
145
+ const out = stdoutReader().readFrom(stdoutOffset);
146
+ const err = stderrReader().readFrom(stderrOffset);
147
+ stdoutOffset = out.nextOffset;
148
+ stderrOffset = err.nextOffset;
149
+ const separator = out.text.length > 0 && !out.text.endsWith("\n") ? "\n" : "";
150
+ return {
151
+ delta: out.text + (err.text.length > 0 ? `${separator}[stderr]\n${err.text}` : ""),
152
+ lossy: false,
153
+ };
154
+ },
155
+ kill() {
156
+ if (proc.status !== "running")
157
+ return false;
158
+ // Upstream marks the handle killed SYNCHRONOUSLY, before the process is
159
+ // observed to die — the second kill() must already be a no-op.
160
+ proc.status = "killed";
161
+ killed = true;
162
+ controller.abort(new Error("killed"));
163
+ return true;
164
+ },
165
+ result() {
166
+ return done.then(() => {
167
+ // The spawn-equivalent failure (the agent being out) rejects here,
168
+ // exactly like upstream's `running.done` rejection carried by result().
169
+ if (settleError !== undefined)
170
+ throw settleError;
171
+ // `truncated` is REQUIRED by the bash tool's canonical result — it
172
+ // copies the field unconditionally, and an `undefined` fails the wire's
173
+ // lossless-JSON snapshot ("value is not lossless JSON"). The agent's
174
+ // capture-cap flags are what make it TRUE instead of a perma-lie.
175
+ const output = (text, truncated) => ({ text, lossy: false, truncated });
176
+ return {
177
+ exitCode: proc.exitCode,
178
+ signal: proc.signal,
179
+ timedOut,
180
+ aborted: callerAborted && !timedOut,
181
+ timeoutMs,
182
+ stdout: output(stdout.toString("utf8"), stdoutTruncated),
183
+ stderr: output(stderr.toString("utf8"), stderrTruncated),
184
+ };
185
+ });
186
+ },
187
+ };
188
+ onStarted?.(proc);
189
+ return proc;
204
190
  }
@@ -0,0 +1,192 @@
1
+ /**
2
+ * Wire protocol for the resident in-distro agent (`agent/wsl-agent.sh`).
3
+ *
4
+ * Pure by construction — no Node API, no I/O — so the frame codecs and the
5
+ * line parser are unit-testable without a distro. The protocol itself is line
6
+ * oriented with `|` field separators and single-line base64 payloads; see the
7
+ * agent script's header for the grammar. Base64 was chosen over JSON because
8
+ * the agent must run under a bare POSIX sh with nothing but coreutils.
9
+ *
10
+ * This module is the migration's first TypeScript source. The build emits it
11
+ * to `lib/agent-protocol.js`; edit THIS file and run `pnpm run build` — the
12
+ * artifact under `lib/` is generated, and `pnpm test` fails when it drifts.
13
+ *
14
+ * @module dsh-plugin-wsl/agent-protocol
15
+ */
16
+ /** The protocol version this host side speaks; the agent must match. */
17
+ export declare const PROTOCOL_VERSION = 4;
18
+ /**
19
+ * Upper bound on one `read` op's payload, set by the HOST looping over windowed
20
+ * requests. Keeping each frame's decoded payload here means the agent's RES
21
+ * line stays modest even for a large file; the host accumulates chunks.
22
+ */
23
+ export declare const FS_READ_CHUNK_BYTES: number;
24
+ /** The agent identifies itself with this name in its HELLO line. */
25
+ export declare const AGENT_NAME = "wsl-agent";
26
+ /**
27
+ * Rough ceiling on one protocol line, chosen well above the executor's own
28
+ * 64 KB output budget spill: stdout and stderr ride ONE line each, so a frame
29
+ * is as large as the largest capture the caller asked for. Documented so a
30
+ * future streaming design does not inherit it silently.
31
+ */
32
+ export declare const MAX_FRAME_BYTES: number;
33
+ /**
34
+ * Encode a buffer as the protocol's single-line base64 (no line wraps, no
35
+ * padding surprises — standard base64 including `=` padding).
36
+ *
37
+ * @param value - the payload to encode.
38
+ * @returns the wire form.
39
+ */
40
+ export declare function encodeB64(value: string | Uint8Array): string;
41
+ /**
42
+ * Decode one protocol base64 field.
43
+ *
44
+ * @param value - the wire form.
45
+ * @returns the decoded bytes; a malformed field decodes to empty rather than
46
+ * throwing, mirroring the agent's own lenient decode.
47
+ */
48
+ export declare function decodeB64(value: string): Buffer;
49
+ /** One EXEC frame's inputs. */
50
+ export interface ExecFrameRequest {
51
+ /** Caller-chosen request id (unique per agent). */
52
+ id: string;
53
+ /** Linux path to cd into before exec. */
54
+ cwd: string;
55
+ /** The argv to execute, already bwrap-wrapped. */
56
+ argv: string[];
57
+ /**
58
+ * In-distro timeout; sub-second remainders round up, and `<= 0` disables the
59
+ * timeout.
60
+ */
61
+ timeoutMs?: number;
62
+ /**
63
+ * Per-stream capture ceiling the AGENT enforces: stdout and stderr are each
64
+ * cut at this many bytes and the RES line reports what was cut. 0 (the
65
+ * default) keeps the historical unbounded capture.
66
+ */
67
+ maxOutputBytes?: number;
68
+ }
69
+ /**
70
+ * Build the request lines for one EXEC frame: a header line naming the cwd,
71
+ * the whole-request timeout in whole seconds (0 = none), the argv word count
72
+ * and the per-stream output capture budget in bytes (0 = unlimited), followed
73
+ * by one base64 line per argv word.
74
+ *
75
+ * @param request - the execution request.
76
+ * @returns the lines to write to the agent's stdin, in order.
77
+ */
78
+ export declare function encodeExecFrame({ id, cwd, argv, timeoutMs, maxOutputBytes }: ExecFrameRequest): string[];
79
+ /**
80
+ * Build a SETENV request line. Note the agent's decode strips trailing
81
+ * newlines from the value — the same leniency the shell itself applies.
82
+ *
83
+ * @param key - the variable name.
84
+ * @param value - the variable value.
85
+ * @returns the line to write.
86
+ */
87
+ export declare function encodeSetEnv(key: string, value: string): string;
88
+ /**
89
+ * Build a KILL request line.
90
+ *
91
+ * @param id - the request id to interrupt.
92
+ * @returns the line to write.
93
+ */
94
+ export declare function encodeKill(id: string): string;
95
+ /** One FS frame's inputs. */
96
+ export interface FsFrameRequest {
97
+ /** Caller-chosen request id (unique per agent). */
98
+ id: string;
99
+ /** One of the op names `encodeFsFrame` documents. */
100
+ op: string;
101
+ /** Op arguments, base64-line encoded by the frame. */
102
+ args: string[];
103
+ /**
104
+ * In-distro timeout; sub-second remainders round up, and `<= 0` disables the
105
+ * timeout.
106
+ */
107
+ timeoutMs?: number;
108
+ }
109
+ /**
110
+ * Build the request lines for one FS frame — the filesystem-substrate request
111
+ * that lets the model's file tools run inside the distro. The shape mirrors
112
+ * EXEC: a header naming the op, the timeout in whole seconds and the argument
113
+ * count, followed by one base64 line per argument.
114
+ *
115
+ * Ops (see `agent/wsl-agent.sh` for the server side and the wire grammar):
116
+ * `stat`, `lstat`, `list`, `realpath`, `read`, `write`. Responses reuse the
117
+ * EXEC `ACK`/`RES`/`ERR` lines, so the request machinery (pending map, KILL,
118
+ * the watchdog) is shared unchanged.
119
+ *
120
+ * The argument contract: callers pass PLAINTEXT values here; this frame is the
121
+ * single base64 boundary, and the agent decodes each argument once. A write's
122
+ * content is no exception — the agent pipes its still-encoded wire line
123
+ * straight to `base64 -d`, so raw bytes never pass through a shell variable.
124
+ *
125
+ * @param request - the filesystem request.
126
+ * @returns the lines to write to the agent's stdin, in order.
127
+ */
128
+ export declare function encodeFsFrame({ id, op, args, timeoutMs }: FsFrameRequest): string[];
129
+ /**
130
+ * `HELLO|name|version|digest` — `digest` is the sha256 of the script file the
131
+ * agent is executing (empty when it could not hash it); the host compares it
132
+ * against its own shipped copy.
133
+ */
134
+ export interface HelloMessage {
135
+ type: "hello";
136
+ name: string;
137
+ version: number;
138
+ digest: string;
139
+ }
140
+ /** `PONG` — the liveness answer to `PING`. */
141
+ export interface PongMessage {
142
+ type: "pong";
143
+ }
144
+ /**
145
+ * `ACK|id` — the agent dequeued the request for execution; the host arms its
146
+ * stuck-request watchdog here, so queue time never counts against a budget.
147
+ */
148
+ export interface AckMessage {
149
+ type: "ack";
150
+ id: string;
151
+ }
152
+ /** `RES|id|exitCode|stdout|stderr` — one settled request, outputs as Buffers. */
153
+ export interface ResultMessage {
154
+ type: "result";
155
+ id: string;
156
+ exitCode: number;
157
+ stdout: Buffer;
158
+ stderr: Buffer;
159
+ truncated: {
160
+ stdout: boolean;
161
+ stderr: boolean;
162
+ };
163
+ }
164
+ /**
165
+ * `ERR|id|reason|message` — a refused request; `id` is `""` when the agent
166
+ * reports a request-less problem (a protocol violation).
167
+ */
168
+ export interface AgentErrorMessage {
169
+ type: "agentError";
170
+ id: string;
171
+ reason: string;
172
+ message: string;
173
+ }
174
+ /**
175
+ * Any other line — never thrown; the host decides whether an unrecognized line
176
+ * is fatal. (Anything during the handshake is; after it, garbage on stdout is
177
+ * logged and dropped, because a `RES` for a request the host already gave up
178
+ * on is expected noise.)
179
+ */
180
+ export interface UnknownMessage {
181
+ type: "unknown";
182
+ line: string;
183
+ }
184
+ /** The line parser's verdict: the union of every message shape above. */
185
+ export type AgentMessage = HelloMessage | PongMessage | AckMessage | ResultMessage | AgentErrorMessage | UnknownMessage;
186
+ /**
187
+ * Parse one line of the agent's stdout.
188
+ *
189
+ * @param line - a line without its terminator.
190
+ * @returns the line's verdict; the message interfaces document each variant.
191
+ */
192
+ export declare function parseAgentLine(line: string): AgentMessage;