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.
- package/CHANGELOG.md +298 -0
- package/CONTRIBUTING.md +129 -39
- package/README.md +20 -20
- package/docs/ARCHITECTURE.md +6 -3
- package/docs/LIMITATIONS.md +13 -0
- package/docs/README.zh.md +20 -20
- package/lib/agent-confined.d.ts +42 -0
- package/lib/agent-confined.js +25 -41
- package/lib/agent-errors.d.ts +31 -0
- package/lib/agent-errors.js +19 -16
- package/lib/agent-exec.d.ts +159 -0
- package/lib/agent-exec.js +151 -165
- package/lib/agent-protocol.d.ts +192 -0
- package/lib/agent-protocol.js +76 -100
- package/lib/agent-shared.d.ts +27 -0
- package/lib/agent-shared.js +12 -12
- package/lib/agent.d.ts +353 -0
- package/lib/agent.js +536 -552
- package/lib/auto-preset.d.ts +110 -0
- package/lib/auto-preset.js +157 -141
- package/lib/bwrap.d.ts +90 -0
- package/lib/bwrap.js +18 -18
- package/lib/file-reference-wsl.d.ts +160 -0
- package/lib/file-reference-wsl.js +192 -173
- package/lib/fs-decisions.d.ts +107 -0
- package/lib/fs-decisions.js +64 -60
- package/lib/fs-routing.d.ts +182 -0
- package/lib/fs-routing.js +173 -116
- package/lib/fs-substrate.d.ts +258 -0
- package/lib/fs-substrate.js +248 -248
- package/lib/fs.d.ts +10 -0
- package/lib/fs.js +5 -0
- package/lib/fsio-agent.d.ts +307 -0
- package/lib/fsio-agent.js +414 -410
- package/lib/fsio-text.d.ts +115 -0
- package/lib/fsio-text.js +104 -84
- package/lib/git-route.d.ts +100 -0
- package/lib/git-route.js +46 -47
- package/lib/index.d.ts +1003 -0
- package/lib/index.js +1101 -997
- package/lib/listing.d.ts +114 -0
- package/lib/listing.js +68 -55
- package/lib/paths.d.ts +182 -0
- package/lib/paths.js +136 -105
- package/lib/picker.d.ts +266 -0
- package/lib/picker.js +371 -351
- package/lib/ports.d.ts +44 -0
- package/lib/ports.js +39 -29
- package/lib/preset-choice.d.ts +33 -0
- package/lib/preset-choice.js +15 -7
- package/lib/probe-cache.d.ts +40 -0
- package/lib/probe-cache.js +35 -29
- package/lib/sandbox-core.d.ts +79 -0
- package/lib/sandbox-core.js +71 -88
- package/lib/sandbox.d.ts +48 -0
- package/lib/sandbox.js +4 -4
- package/lib/search-exec.d.ts +96 -0
- package/lib/search-exec.js +52 -79
- package/lib/search-route.d.ts +75 -0
- package/lib/search-route.js +30 -31
- package/lib/shell-env.d.ts +88 -0
- package/lib/shell-env.js +78 -70
- package/lib/shell.d.ts +10 -0
- package/lib/shell.js +5 -0
- package/lib/subprocess.d.ts +313 -0
- package/lib/subprocess.js +267 -262
- package/lib/terminal-activity.d.ts +98 -0
- package/lib/terminal-activity.js +29 -39
- package/lib/terminal-route.d.ts +27 -0
- package/lib/terminal-route.js +16 -8
- package/lib/watcher.d.ts +77 -0
- package/lib/watcher.js +120 -126
- package/lib/workspace-files-route.d.ts +132 -0
- package/lib/workspace-files-route.js +73 -81
- package/lib/workspace-files-wsl.d.ts +257 -0
- package/lib/workspace-files-wsl.js +311 -251
- package/lib/wsl.d.ts +266 -0
- package/lib/wsl.js +144 -151
- 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
|
-
*
|
|
33
|
-
* @
|
|
34
|
+
*
|
|
35
|
+
* @param buffer - the complete stream.
|
|
36
|
+
* @returns the reader over it.
|
|
34
37
|
*/
|
|
35
38
|
function memoryReader(buffer) {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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
|
|
52
|
-
* @
|
|
53
|
-
*
|
|
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
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
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;
|