@code-yeongyu/senpi-agent-core 2026.7.25-2
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/README.md +508 -0
- package/dist/agent-loop.d.ts +31 -0
- package/dist/agent-loop.d.ts.map +1 -0
- package/dist/agent-loop.js +851 -0
- package/dist/agent-loop.js.map +1 -0
- package/dist/agent.d.ts +140 -0
- package/dist/agent.d.ts.map +1 -0
- package/dist/agent.js +464 -0
- package/dist/agent.js.map +1 -0
- package/dist/harness/agent-harness.d.ts +94 -0
- package/dist/harness/agent-harness.d.ts.map +1 -0
- package/dist/harness/agent-harness.js +988 -0
- package/dist/harness/agent-harness.js.map +1 -0
- package/dist/harness/compaction/branch-summarization.d.ts +55 -0
- package/dist/harness/compaction/branch-summarization.d.ts.map +1 -0
- package/dist/harness/compaction/branch-summarization.js +172 -0
- package/dist/harness/compaction/branch-summarization.js.map +1 -0
- package/dist/harness/compaction/compaction.d.ts +107 -0
- package/dist/harness/compaction/compaction.d.ts.map +1 -0
- package/dist/harness/compaction/compaction.js +588 -0
- package/dist/harness/compaction/compaction.js.map +1 -0
- package/dist/harness/compaction/utils.d.ts +36 -0
- package/dist/harness/compaction/utils.d.ts.map +1 -0
- package/dist/harness/compaction/utils.js +136 -0
- package/dist/harness/compaction/utils.js.map +1 -0
- package/dist/harness/env/nodejs.d.ts +45 -0
- package/dist/harness/env/nodejs.d.ts.map +1 -0
- package/dist/harness/env/nodejs.js +620 -0
- package/dist/harness/env/nodejs.js.map +1 -0
- package/dist/harness/messages.d.ts +52 -0
- package/dist/harness/messages.d.ts.map +1 -0
- package/dist/harness/messages.js +102 -0
- package/dist/harness/messages.js.map +1 -0
- package/dist/harness/prompt-templates.d.ts +48 -0
- package/dist/harness/prompt-templates.d.ts.map +1 -0
- package/dist/harness/prompt-templates.js +230 -0
- package/dist/harness/prompt-templates.js.map +1 -0
- package/dist/harness/session/jsonl-repo.d.ts +26 -0
- package/dist/harness/session/jsonl-repo.d.ts.map +1 -0
- package/dist/harness/session/jsonl-repo.js +100 -0
- package/dist/harness/session/jsonl-repo.js.map +1 -0
- package/dist/harness/session/jsonl-storage.d.ts +42 -0
- package/dist/harness/session/jsonl-storage.d.ts.map +1 -0
- package/dist/harness/session/jsonl-storage.js +286 -0
- package/dist/harness/session/jsonl-storage.js.map +1 -0
- package/dist/harness/session/memory-repo.d.ts +18 -0
- package/dist/harness/session/memory-repo.d.ts.map +1 -0
- package/dist/harness/session/memory-repo.js +44 -0
- package/dist/harness/session/memory-repo.js.map +1 -0
- package/dist/harness/session/memory-storage.d.ts +33 -0
- package/dist/harness/session/memory-storage.d.ts.map +1 -0
- package/dist/harness/session/memory-storage.js +163 -0
- package/dist/harness/session/memory-storage.js.map +1 -0
- package/dist/harness/session/repo-utils.d.ts +11 -0
- package/dist/harness/session/repo-utils.d.ts.map +1 -0
- package/dist/harness/session/repo-utils.js +39 -0
- package/dist/harness/session/repo-utils.js.map +1 -0
- package/dist/harness/session/session.d.ts +49 -0
- package/dist/harness/session/session.d.ts.map +1 -0
- package/dist/harness/session/session.js +261 -0
- package/dist/harness/session/session.js.map +1 -0
- package/dist/harness/skills.d.ts +44 -0
- package/dist/harness/skills.d.ts.map +1 -0
- package/dist/harness/skills.js +311 -0
- package/dist/harness/skills.js.map +1 -0
- package/dist/harness/system-prompt.d.ts +3 -0
- package/dist/harness/system-prompt.d.ts.map +1 -0
- package/dist/harness/system-prompt.js +30 -0
- package/dist/harness/system-prompt.js.map +1 -0
- package/dist/harness/tools/bash.d.ts +27 -0
- package/dist/harness/tools/bash.d.ts.map +1 -0
- package/dist/harness/tools/bash.js +132 -0
- package/dist/harness/tools/bash.js.map +1 -0
- package/dist/harness/tools/edit-diff.d.ts +89 -0
- package/dist/harness/tools/edit-diff.d.ts.map +1 -0
- package/dist/harness/tools/edit-diff.js +386 -0
- package/dist/harness/tools/edit-diff.js.map +1 -0
- package/dist/harness/tools/edit.d.ts +19 -0
- package/dist/harness/tools/edit.d.ts.map +1 -0
- package/dist/harness/tools/edit.js +95 -0
- package/dist/harness/tools/edit.js.map +1 -0
- package/dist/harness/tools/file-mutation-queue.d.ts +4 -0
- package/dist/harness/tools/file-mutation-queue.d.ts.map +1 -0
- package/dist/harness/tools/file-mutation-queue.js +46 -0
- package/dist/harness/tools/file-mutation-queue.js.map +1 -0
- package/dist/harness/tools/image.d.ts +3 -0
- package/dist/harness/tools/image.d.ts.map +1 -0
- package/dist/harness/tools/image.js +106 -0
- package/dist/harness/tools/image.js.map +1 -0
- package/dist/harness/tools/index.d.ts +6 -0
- package/dist/harness/tools/index.d.ts.map +1 -0
- package/dist/harness/tools/index.js +5 -0
- package/dist/harness/tools/index.js.map +1 -0
- package/dist/harness/tools/path-utils.d.ts +4 -0
- package/dist/harness/tools/path-utils.d.ts.map +1 -0
- package/dist/harness/tools/path-utils.js +26 -0
- package/dist/harness/tools/path-utils.js.map +1 -0
- package/dist/harness/tools/read.d.ts +34 -0
- package/dist/harness/tools/read.d.ts.map +1 -0
- package/dist/harness/tools/read.js +110 -0
- package/dist/harness/tools/read.js.map +1 -0
- package/dist/harness/tools/tool-context.d.ts +6 -0
- package/dist/harness/tools/tool-context.d.ts.map +1 -0
- package/dist/harness/tools/tool-context.js +2 -0
- package/dist/harness/tools/tool-context.js.map +1 -0
- package/dist/harness/tools/write.d.ts +11 -0
- package/dist/harness/tools/write.d.ts.map +1 -0
- package/dist/harness/tools/write.js +31 -0
- package/dist/harness/tools/write.js.map +1 -0
- package/dist/harness/types.d.ts +682 -0
- package/dist/harness/types.d.ts.map +1 -0
- package/dist/harness/types.js +83 -0
- package/dist/harness/types.js.map +1 -0
- package/dist/harness/utils/shell-output.d.ts +22 -0
- package/dist/harness/utils/shell-output.d.ts.map +1 -0
- package/dist/harness/utils/shell-output.js +181 -0
- package/dist/harness/utils/shell-output.js.map +1 -0
- package/dist/harness/utils/truncate.d.ts +70 -0
- package/dist/harness/utils/truncate.d.ts.map +1 -0
- package/dist/harness/utils/truncate.js +296 -0
- package/dist/harness/utils/truncate.js.map +1 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +30 -0
- package/dist/index.js.map +1 -0
- package/dist/node.d.ts +3 -0
- package/dist/node.d.ts.map +1 -0
- package/dist/node.js +3 -0
- package/dist/node.js.map +1 -0
- package/dist/proxy.d.ts +71 -0
- package/dist/proxy.d.ts.map +1 -0
- package/dist/proxy.js +280 -0
- package/dist/proxy.js.map +1 -0
- package/dist/stream-fn.d.ts +10 -0
- package/dist/stream-fn.d.ts.map +1 -0
- package/dist/stream-fn.js +17 -0
- package/dist/stream-fn.js.map +1 -0
- package/dist/types.d.ts +415 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +61 -0
|
@@ -0,0 +1,620 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
3
|
+
import { constants, createReadStream } from "node:fs";
|
|
4
|
+
import { access, appendFile, lstat, mkdir, mkdtemp, readdir, readFile, realpath, rm, writeFile, } from "node:fs/promises";
|
|
5
|
+
import { homedir, tmpdir } from "node:os";
|
|
6
|
+
import { isAbsolute, join, resolve } from "node:path";
|
|
7
|
+
import { createInterface } from "node:readline";
|
|
8
|
+
import { fileURLToPath } from "node:url";
|
|
9
|
+
import { ExecutionError, err, FileError, ok, toError, } from "../types.js";
|
|
10
|
+
const MAX_TIMEOUT_MS = 2_147_483_647;
|
|
11
|
+
const MAX_TIMEOUT_SECONDS = MAX_TIMEOUT_MS / 1000;
|
|
12
|
+
const EXIT_STDIO_GRACE_MS = 100;
|
|
13
|
+
function resolveTimeoutMs(timeout) {
|
|
14
|
+
if (timeout === undefined)
|
|
15
|
+
return ok(undefined);
|
|
16
|
+
if (!Number.isFinite(timeout) || timeout <= 0) {
|
|
17
|
+
return err(new ExecutionError("timeout", "Invalid timeout: must be a finite number of seconds"));
|
|
18
|
+
}
|
|
19
|
+
const timeoutMs = timeout * 1000;
|
|
20
|
+
if (timeoutMs > MAX_TIMEOUT_MS) {
|
|
21
|
+
return err(new ExecutionError("timeout", `Invalid timeout: maximum is ${MAX_TIMEOUT_SECONDS} seconds`));
|
|
22
|
+
}
|
|
23
|
+
return ok(timeoutMs);
|
|
24
|
+
}
|
|
25
|
+
function resolvePath(cwd, path) {
|
|
26
|
+
let normalized = path;
|
|
27
|
+
if (normalized === "~") {
|
|
28
|
+
normalized = homedir();
|
|
29
|
+
}
|
|
30
|
+
else if (normalized.startsWith("~/") || (process.platform === "win32" && normalized.startsWith("~\\"))) {
|
|
31
|
+
normalized = join(homedir(), normalized.slice(2));
|
|
32
|
+
}
|
|
33
|
+
else if (normalized.startsWith("file://")) {
|
|
34
|
+
try {
|
|
35
|
+
normalized = fileURLToPath(normalized);
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
// Keep malformed URLs as ordinary paths so filesystem methods preserve their non-throwing contract.
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return isAbsolute(normalized) ? resolve(normalized) : resolve(cwd, normalized);
|
|
42
|
+
}
|
|
43
|
+
function fileKindFromStats(stats) {
|
|
44
|
+
if (stats.isFile())
|
|
45
|
+
return "file";
|
|
46
|
+
if (stats.isDirectory())
|
|
47
|
+
return "directory";
|
|
48
|
+
if (stats.isSymbolicLink())
|
|
49
|
+
return "symlink";
|
|
50
|
+
return undefined;
|
|
51
|
+
}
|
|
52
|
+
function fileInfoFromStats(path, stats) {
|
|
53
|
+
const kind = fileKindFromStats(stats);
|
|
54
|
+
if (!kind)
|
|
55
|
+
return err(new FileError("invalid", "Unsupported file type", path));
|
|
56
|
+
return ok({
|
|
57
|
+
name: path.replace(/\/+$/, "").split("/").pop() ?? path,
|
|
58
|
+
path,
|
|
59
|
+
kind,
|
|
60
|
+
size: stats.size,
|
|
61
|
+
mtimeMs: stats.mtimeMs,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
function isNodeError(error) {
|
|
65
|
+
return error instanceof Error && "code" in error;
|
|
66
|
+
}
|
|
67
|
+
function toFileError(error, path) {
|
|
68
|
+
if (error instanceof FileError)
|
|
69
|
+
return error;
|
|
70
|
+
const cause = toError(error);
|
|
71
|
+
if (isNodeError(error)) {
|
|
72
|
+
const message = error.message;
|
|
73
|
+
switch (error.code) {
|
|
74
|
+
case "ABORT_ERR":
|
|
75
|
+
return new FileError("aborted", message, path, cause);
|
|
76
|
+
case "ENOENT":
|
|
77
|
+
return new FileError("not_found", message, path, cause);
|
|
78
|
+
case "EACCES":
|
|
79
|
+
case "EPERM":
|
|
80
|
+
return new FileError("permission_denied", message, path, cause);
|
|
81
|
+
case "ENOTDIR":
|
|
82
|
+
return new FileError("not_directory", message, path, cause);
|
|
83
|
+
case "EISDIR":
|
|
84
|
+
return new FileError("is_directory", message, path, cause);
|
|
85
|
+
case "EINVAL":
|
|
86
|
+
return new FileError("invalid", message, path, cause);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return new FileError("unknown", cause.message, path, cause);
|
|
90
|
+
}
|
|
91
|
+
function abortResult(signal, path) {
|
|
92
|
+
return signal?.aborted ? err(new FileError("aborted", "aborted", path)) : undefined;
|
|
93
|
+
}
|
|
94
|
+
async function pathExists(path) {
|
|
95
|
+
try {
|
|
96
|
+
await access(path, constants.F_OK);
|
|
97
|
+
return true;
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
async function runCommand(command, args, timeoutMs) {
|
|
104
|
+
return await new Promise((resolve) => {
|
|
105
|
+
let stdout = "";
|
|
106
|
+
let child;
|
|
107
|
+
try {
|
|
108
|
+
child = spawn(command, args, {
|
|
109
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
110
|
+
windowsHide: true,
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
resolve({ stdout: "", status: null });
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
const timeout = setTimeout(() => {
|
|
118
|
+
if (child.pid)
|
|
119
|
+
killProcessTree(child.pid);
|
|
120
|
+
}, timeoutMs);
|
|
121
|
+
child.stdout?.setEncoding("utf8");
|
|
122
|
+
child.stdout?.on("data", (chunk) => {
|
|
123
|
+
stdout += chunk;
|
|
124
|
+
});
|
|
125
|
+
child.on("error", () => {
|
|
126
|
+
clearTimeout(timeout);
|
|
127
|
+
resolve({ stdout: "", status: null });
|
|
128
|
+
});
|
|
129
|
+
child.on("close", (status) => {
|
|
130
|
+
clearTimeout(timeout);
|
|
131
|
+
resolve({ stdout, status });
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
async function findBashOnPath() {
|
|
136
|
+
const result = process.platform === "win32"
|
|
137
|
+
? await runCommand("where", ["bash.exe"], 5000)
|
|
138
|
+
: await runCommand("which", ["bash"], 5000);
|
|
139
|
+
if (result.status !== 0 || !result.stdout)
|
|
140
|
+
return null;
|
|
141
|
+
const firstMatch = result.stdout.trim().split(/\r?\n/)[0];
|
|
142
|
+
return firstMatch && (await pathExists(firstMatch)) ? firstMatch : null;
|
|
143
|
+
}
|
|
144
|
+
function isLegacyWslBashPath(path) {
|
|
145
|
+
const normalized = path.replace(/\//g, "\\").toLowerCase();
|
|
146
|
+
return /^[a-z]:\\windows\\(?:system32|sysnative)\\bash\.exe$/.test(normalized);
|
|
147
|
+
}
|
|
148
|
+
function getBashShellConfig(shell) {
|
|
149
|
+
return isLegacyWslBashPath(shell) ? { shell, args: ["-s"], commandTransport: "stdin" } : { shell, args: ["-c"] };
|
|
150
|
+
}
|
|
151
|
+
async function getShellConfig(customShellPath) {
|
|
152
|
+
if (customShellPath) {
|
|
153
|
+
if (await pathExists(customShellPath)) {
|
|
154
|
+
return ok(getBashShellConfig(customShellPath));
|
|
155
|
+
}
|
|
156
|
+
return err(new ExecutionError("shell_unavailable", `Custom shell path not found: ${customShellPath}`));
|
|
157
|
+
}
|
|
158
|
+
if (process.platform === "win32") {
|
|
159
|
+
const candidates = [];
|
|
160
|
+
const programFiles = process.env.ProgramFiles;
|
|
161
|
+
if (programFiles)
|
|
162
|
+
candidates.push(`${programFiles}\\Git\\bin\\bash.exe`);
|
|
163
|
+
const programFilesX86 = process.env["ProgramFiles(x86)"];
|
|
164
|
+
if (programFilesX86)
|
|
165
|
+
candidates.push(`${programFilesX86}\\Git\\bin\\bash.exe`);
|
|
166
|
+
for (const candidate of candidates) {
|
|
167
|
+
if (await pathExists(candidate)) {
|
|
168
|
+
return ok(getBashShellConfig(candidate));
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
const bashOnPath = await findBashOnPath();
|
|
172
|
+
if (bashOnPath) {
|
|
173
|
+
return ok(getBashShellConfig(bashOnPath));
|
|
174
|
+
}
|
|
175
|
+
return err(new ExecutionError("shell_unavailable", `No bash shell found. Options:\n` +
|
|
176
|
+
` 1. Install Git for Windows: https://git-scm.com/download/win\n` +
|
|
177
|
+
` 2. Add your bash to PATH (Cygwin, MSYS2, etc.)\n` +
|
|
178
|
+
" 3. Configure an explicit shellPath\n\n" +
|
|
179
|
+
`Searched Git Bash in:\n${candidates.map((path) => ` ${path}`).join("\n")}`));
|
|
180
|
+
}
|
|
181
|
+
if (await pathExists("/bin/bash")) {
|
|
182
|
+
return ok(getBashShellConfig("/bin/bash"));
|
|
183
|
+
}
|
|
184
|
+
const bashOnPath = await findBashOnPath();
|
|
185
|
+
if (bashOnPath) {
|
|
186
|
+
return ok(getBashShellConfig(bashOnPath));
|
|
187
|
+
}
|
|
188
|
+
return ok({ shell: "sh", args: ["-c"] });
|
|
189
|
+
}
|
|
190
|
+
function getShellEnv(baseEnv, extraEnv, inheritEnv = true) {
|
|
191
|
+
if (!inheritEnv)
|
|
192
|
+
return { ...extraEnv };
|
|
193
|
+
return {
|
|
194
|
+
...process.env,
|
|
195
|
+
...baseEnv,
|
|
196
|
+
...extraEnv,
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
function killProcessTree(pid) {
|
|
200
|
+
if (process.platform === "win32") {
|
|
201
|
+
try {
|
|
202
|
+
spawn("taskkill", ["/F", "/T", "/PID", String(pid)], {
|
|
203
|
+
stdio: "ignore",
|
|
204
|
+
detached: true,
|
|
205
|
+
windowsHide: true,
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
catch {
|
|
209
|
+
// Ignore errors.
|
|
210
|
+
}
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
try {
|
|
214
|
+
process.kill(-pid, "SIGKILL");
|
|
215
|
+
}
|
|
216
|
+
catch {
|
|
217
|
+
try {
|
|
218
|
+
process.kill(pid, "SIGKILL");
|
|
219
|
+
}
|
|
220
|
+
catch {
|
|
221
|
+
// Process already dead.
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
function waitForChildProcess(child) {
|
|
226
|
+
return new Promise((resolvePromise, reject) => {
|
|
227
|
+
let settled = false;
|
|
228
|
+
let exited = false;
|
|
229
|
+
let exitCode = null;
|
|
230
|
+
let postExitTimer;
|
|
231
|
+
let stdoutEnded = child.stdout === null;
|
|
232
|
+
let stderrEnded = child.stderr === null;
|
|
233
|
+
const cleanup = () => {
|
|
234
|
+
if (postExitTimer)
|
|
235
|
+
clearTimeout(postExitTimer);
|
|
236
|
+
child.removeListener("error", onError);
|
|
237
|
+
child.removeListener("exit", onExit);
|
|
238
|
+
child.removeListener("close", onClose);
|
|
239
|
+
child.stdout?.removeListener("end", onStdoutEnd);
|
|
240
|
+
child.stderr?.removeListener("end", onStderrEnd);
|
|
241
|
+
child.stdout?.removeListener("data", onData);
|
|
242
|
+
child.stderr?.removeListener("data", onData);
|
|
243
|
+
};
|
|
244
|
+
const finalize = (code) => {
|
|
245
|
+
if (settled)
|
|
246
|
+
return;
|
|
247
|
+
settled = true;
|
|
248
|
+
cleanup();
|
|
249
|
+
child.stdout?.destroy();
|
|
250
|
+
child.stderr?.destroy();
|
|
251
|
+
resolvePromise(code);
|
|
252
|
+
};
|
|
253
|
+
const maybeFinalizeAfterExit = () => {
|
|
254
|
+
if (exited && stdoutEnded && stderrEnded)
|
|
255
|
+
finalize(exitCode);
|
|
256
|
+
};
|
|
257
|
+
const armIdleTimer = () => {
|
|
258
|
+
if (postExitTimer)
|
|
259
|
+
clearTimeout(postExitTimer);
|
|
260
|
+
postExitTimer = setTimeout(() => finalize(exitCode), EXIT_STDIO_GRACE_MS);
|
|
261
|
+
};
|
|
262
|
+
const onData = () => {
|
|
263
|
+
if (exited && !settled)
|
|
264
|
+
armIdleTimer();
|
|
265
|
+
};
|
|
266
|
+
const onStdoutEnd = () => {
|
|
267
|
+
stdoutEnded = true;
|
|
268
|
+
maybeFinalizeAfterExit();
|
|
269
|
+
};
|
|
270
|
+
const onStderrEnd = () => {
|
|
271
|
+
stderrEnded = true;
|
|
272
|
+
maybeFinalizeAfterExit();
|
|
273
|
+
};
|
|
274
|
+
const onError = (error) => {
|
|
275
|
+
if (settled)
|
|
276
|
+
return;
|
|
277
|
+
settled = true;
|
|
278
|
+
cleanup();
|
|
279
|
+
reject(error);
|
|
280
|
+
};
|
|
281
|
+
const onExit = (code) => {
|
|
282
|
+
exited = true;
|
|
283
|
+
exitCode = code;
|
|
284
|
+
maybeFinalizeAfterExit();
|
|
285
|
+
if (!settled)
|
|
286
|
+
armIdleTimer();
|
|
287
|
+
};
|
|
288
|
+
const onClose = (code) => finalize(code);
|
|
289
|
+
child.stdout?.once("end", onStdoutEnd);
|
|
290
|
+
child.stderr?.once("end", onStderrEnd);
|
|
291
|
+
child.stdout?.on("data", onData);
|
|
292
|
+
child.stderr?.on("data", onData);
|
|
293
|
+
child.once("error", onError);
|
|
294
|
+
child.once("exit", onExit);
|
|
295
|
+
child.once("close", onClose);
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
export class NodeExecutionEnv {
|
|
299
|
+
constructor(options) {
|
|
300
|
+
this.activeChildPids = new Set();
|
|
301
|
+
this.cwd = options.cwd;
|
|
302
|
+
this.shellPath = options.shellPath;
|
|
303
|
+
this.shellEnv = options.shellEnv;
|
|
304
|
+
}
|
|
305
|
+
async absolutePath(path) {
|
|
306
|
+
return ok(resolvePath(this.cwd, path));
|
|
307
|
+
}
|
|
308
|
+
async joinPath(parts) {
|
|
309
|
+
return ok(join(...parts));
|
|
310
|
+
}
|
|
311
|
+
async exec(command, options) {
|
|
312
|
+
if (options?.abortSignal?.aborted)
|
|
313
|
+
return err(new ExecutionError("aborted", "aborted"));
|
|
314
|
+
const timeoutMsResult = resolveTimeoutMs(options?.timeout);
|
|
315
|
+
if (!timeoutMsResult.ok)
|
|
316
|
+
return err(timeoutMsResult.error);
|
|
317
|
+
const timeoutMs = timeoutMsResult.value;
|
|
318
|
+
const cwd = options?.cwd ? resolvePath(this.cwd, options.cwd) : this.cwd;
|
|
319
|
+
const shellConfig = await getShellConfig(this.shellPath);
|
|
320
|
+
if (!shellConfig.ok)
|
|
321
|
+
return shellConfig;
|
|
322
|
+
try {
|
|
323
|
+
await access(cwd, constants.F_OK);
|
|
324
|
+
}
|
|
325
|
+
catch (error) {
|
|
326
|
+
const cause = toError(error);
|
|
327
|
+
return err(new ExecutionError("spawn_error", `Working directory does not exist: ${cwd}\nCannot execute bash commands.`, cause));
|
|
328
|
+
}
|
|
329
|
+
return await new Promise((resolvePromise) => {
|
|
330
|
+
let stdout = "";
|
|
331
|
+
let stderr = "";
|
|
332
|
+
let settled = false;
|
|
333
|
+
let timedOut = false;
|
|
334
|
+
let callbackError;
|
|
335
|
+
let child;
|
|
336
|
+
let timeoutId;
|
|
337
|
+
const onAbort = () => {
|
|
338
|
+
if (child?.pid) {
|
|
339
|
+
killProcessTree(child.pid);
|
|
340
|
+
}
|
|
341
|
+
};
|
|
342
|
+
const settle = (result) => {
|
|
343
|
+
if (timeoutId)
|
|
344
|
+
clearTimeout(timeoutId);
|
|
345
|
+
if (options?.abortSignal)
|
|
346
|
+
options.abortSignal.removeEventListener("abort", onAbort);
|
|
347
|
+
if (child?.pid)
|
|
348
|
+
this.activeChildPids.delete(child.pid);
|
|
349
|
+
if (settled)
|
|
350
|
+
return;
|
|
351
|
+
settled = true;
|
|
352
|
+
resolvePromise(result);
|
|
353
|
+
};
|
|
354
|
+
try {
|
|
355
|
+
const commandFromStdin = shellConfig.value.commandTransport === "stdin";
|
|
356
|
+
child = spawn(shellConfig.value.shell, commandFromStdin ? shellConfig.value.args : [...shellConfig.value.args, command], {
|
|
357
|
+
cwd,
|
|
358
|
+
detached: process.platform !== "win32",
|
|
359
|
+
env: getShellEnv(this.shellEnv, options?.env, options?.inheritEnv),
|
|
360
|
+
stdio: [commandFromStdin ? "pipe" : "ignore", "pipe", "pipe"],
|
|
361
|
+
windowsHide: true,
|
|
362
|
+
});
|
|
363
|
+
if (child.pid)
|
|
364
|
+
this.activeChildPids.add(child.pid);
|
|
365
|
+
if (commandFromStdin) {
|
|
366
|
+
child.stdin?.on("error", () => { });
|
|
367
|
+
child.stdin?.end(command);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
catch (error) {
|
|
371
|
+
const cause = toError(error);
|
|
372
|
+
settle(err(new ExecutionError("spawn_error", cause.message, cause)));
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
timeoutId =
|
|
376
|
+
timeoutMs !== undefined
|
|
377
|
+
? setTimeout(() => {
|
|
378
|
+
timedOut = true;
|
|
379
|
+
if (child?.pid) {
|
|
380
|
+
killProcessTree(child.pid);
|
|
381
|
+
}
|
|
382
|
+
}, timeoutMs)
|
|
383
|
+
: undefined;
|
|
384
|
+
if (options?.abortSignal) {
|
|
385
|
+
if (options.abortSignal.aborted) {
|
|
386
|
+
onAbort();
|
|
387
|
+
}
|
|
388
|
+
else {
|
|
389
|
+
options.abortSignal.addEventListener("abort", onAbort, { once: true });
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
child.stdout?.setEncoding("utf8");
|
|
393
|
+
child.stderr?.setEncoding("utf8");
|
|
394
|
+
child.stdout?.on("data", (chunk) => {
|
|
395
|
+
stdout += chunk;
|
|
396
|
+
try {
|
|
397
|
+
options?.onStdout?.(chunk);
|
|
398
|
+
}
|
|
399
|
+
catch (error) {
|
|
400
|
+
const cause = toError(error);
|
|
401
|
+
callbackError = new ExecutionError("callback_error", cause.message, cause);
|
|
402
|
+
onAbort();
|
|
403
|
+
}
|
|
404
|
+
});
|
|
405
|
+
child.stderr?.on("data", (chunk) => {
|
|
406
|
+
stderr += chunk;
|
|
407
|
+
try {
|
|
408
|
+
options?.onStderr?.(chunk);
|
|
409
|
+
}
|
|
410
|
+
catch (error) {
|
|
411
|
+
const cause = toError(error);
|
|
412
|
+
callbackError = new ExecutionError("callback_error", cause.message, cause);
|
|
413
|
+
onAbort();
|
|
414
|
+
}
|
|
415
|
+
});
|
|
416
|
+
void waitForChildProcess(child).then((code) => {
|
|
417
|
+
if (callbackError) {
|
|
418
|
+
settle(err(callbackError));
|
|
419
|
+
return;
|
|
420
|
+
}
|
|
421
|
+
if (timedOut) {
|
|
422
|
+
settle(err(new ExecutionError("timeout", `timeout:${options?.timeout}`)));
|
|
423
|
+
return;
|
|
424
|
+
}
|
|
425
|
+
if (options?.abortSignal?.aborted) {
|
|
426
|
+
settle(err(new ExecutionError("aborted", "aborted")));
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
429
|
+
settle(ok({ stdout, stderr, exitCode: code ?? 0 }));
|
|
430
|
+
}, (error) => settle(err(new ExecutionError("spawn_error", error.message, error))));
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
async readTextFile(path, abortSignal) {
|
|
434
|
+
const resolved = resolvePath(this.cwd, path);
|
|
435
|
+
const aborted = abortResult(abortSignal, resolved);
|
|
436
|
+
if (aborted)
|
|
437
|
+
return aborted;
|
|
438
|
+
try {
|
|
439
|
+
return ok(await readFile(resolved, { encoding: "utf8", signal: abortSignal }));
|
|
440
|
+
}
|
|
441
|
+
catch (error) {
|
|
442
|
+
return err(toFileError(error, resolved));
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
async readTextLines(path, options) {
|
|
446
|
+
const resolved = resolvePath(this.cwd, path);
|
|
447
|
+
const aborted = abortResult(options?.abortSignal, resolved);
|
|
448
|
+
if (aborted)
|
|
449
|
+
return aborted;
|
|
450
|
+
if (options?.maxLines !== undefined && options.maxLines <= 0)
|
|
451
|
+
return ok([]);
|
|
452
|
+
let stream;
|
|
453
|
+
let lineReader;
|
|
454
|
+
try {
|
|
455
|
+
stream = createReadStream(resolved, { encoding: "utf8", signal: options?.abortSignal });
|
|
456
|
+
lineReader = createInterface({ input: stream, crlfDelay: Infinity });
|
|
457
|
+
const lines = [];
|
|
458
|
+
for await (const line of lineReader) {
|
|
459
|
+
const loopAbort = abortResult(options?.abortSignal, resolved);
|
|
460
|
+
if (loopAbort)
|
|
461
|
+
return loopAbort;
|
|
462
|
+
lines.push(line);
|
|
463
|
+
if (options?.maxLines !== undefined && lines.length >= options.maxLines)
|
|
464
|
+
break;
|
|
465
|
+
}
|
|
466
|
+
const afterReadAbort = abortResult(options?.abortSignal, resolved);
|
|
467
|
+
if (afterReadAbort)
|
|
468
|
+
return afterReadAbort;
|
|
469
|
+
return ok(lines);
|
|
470
|
+
}
|
|
471
|
+
catch (error) {
|
|
472
|
+
return err(toFileError(error, resolved));
|
|
473
|
+
}
|
|
474
|
+
finally {
|
|
475
|
+
lineReader?.close();
|
|
476
|
+
stream?.destroy();
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
async readBinaryFile(path, abortSignal) {
|
|
480
|
+
const resolved = resolvePath(this.cwd, path);
|
|
481
|
+
const aborted = abortResult(abortSignal, resolved);
|
|
482
|
+
if (aborted)
|
|
483
|
+
return aborted;
|
|
484
|
+
try {
|
|
485
|
+
return ok(await readFile(resolved, { signal: abortSignal }));
|
|
486
|
+
}
|
|
487
|
+
catch (error) {
|
|
488
|
+
return err(toFileError(error, resolved));
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
async writeFile(path, content, abortSignal) {
|
|
492
|
+
const resolved = resolvePath(this.cwd, path);
|
|
493
|
+
const aborted = abortResult(abortSignal, resolved);
|
|
494
|
+
if (aborted)
|
|
495
|
+
return aborted;
|
|
496
|
+
try {
|
|
497
|
+
await mkdir(resolve(resolved, ".."), { recursive: true });
|
|
498
|
+
const afterMkdirAbort = abortResult(abortSignal, resolved);
|
|
499
|
+
if (afterMkdirAbort)
|
|
500
|
+
return afterMkdirAbort;
|
|
501
|
+
await writeFile(resolved, content, { signal: abortSignal });
|
|
502
|
+
return ok(undefined);
|
|
503
|
+
}
|
|
504
|
+
catch (error) {
|
|
505
|
+
return err(toFileError(error, resolved));
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
async appendFile(path, content) {
|
|
509
|
+
const resolved = resolvePath(this.cwd, path);
|
|
510
|
+
try {
|
|
511
|
+
await mkdir(resolve(resolved, ".."), { recursive: true });
|
|
512
|
+
await appendFile(resolved, content);
|
|
513
|
+
return ok(undefined);
|
|
514
|
+
}
|
|
515
|
+
catch (error) {
|
|
516
|
+
return err(toFileError(error, resolved));
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
async fileInfo(path) {
|
|
520
|
+
const resolved = resolvePath(this.cwd, path);
|
|
521
|
+
try {
|
|
522
|
+
return fileInfoFromStats(resolved, await lstat(resolved));
|
|
523
|
+
}
|
|
524
|
+
catch (error) {
|
|
525
|
+
return err(toFileError(error, resolved));
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
async listDir(path, abortSignal) {
|
|
529
|
+
const resolved = resolvePath(this.cwd, path);
|
|
530
|
+
const aborted = abortResult(abortSignal, resolved);
|
|
531
|
+
if (aborted)
|
|
532
|
+
return aborted;
|
|
533
|
+
try {
|
|
534
|
+
const entries = await readdir(resolved, { withFileTypes: true });
|
|
535
|
+
const infos = [];
|
|
536
|
+
for (const entry of entries) {
|
|
537
|
+
const loopAbort = abortResult(abortSignal, resolved);
|
|
538
|
+
if (loopAbort)
|
|
539
|
+
return loopAbort;
|
|
540
|
+
const entryPath = resolve(resolved, entry.name);
|
|
541
|
+
try {
|
|
542
|
+
const info = fileInfoFromStats(entryPath, await lstat(entryPath));
|
|
543
|
+
if (info.ok)
|
|
544
|
+
infos.push(info.value);
|
|
545
|
+
}
|
|
546
|
+
catch (error) {
|
|
547
|
+
return err(toFileError(error, entryPath));
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
return ok(infos);
|
|
551
|
+
}
|
|
552
|
+
catch (error) {
|
|
553
|
+
return err(toFileError(error, resolved));
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
async canonicalPath(path) {
|
|
557
|
+
const resolved = resolvePath(this.cwd, path);
|
|
558
|
+
try {
|
|
559
|
+
return ok(await realpath(resolved));
|
|
560
|
+
}
|
|
561
|
+
catch (error) {
|
|
562
|
+
return err(toFileError(error, resolved));
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
async exists(path) {
|
|
566
|
+
const result = await this.fileInfo(path);
|
|
567
|
+
if (result.ok)
|
|
568
|
+
return ok(true);
|
|
569
|
+
if (result.error.code === "not_found")
|
|
570
|
+
return ok(false);
|
|
571
|
+
return err(result.error);
|
|
572
|
+
}
|
|
573
|
+
async createDir(path, options) {
|
|
574
|
+
const resolved = resolvePath(this.cwd, path);
|
|
575
|
+
try {
|
|
576
|
+
await mkdir(resolved, { recursive: options?.recursive ?? true });
|
|
577
|
+
return ok(undefined);
|
|
578
|
+
}
|
|
579
|
+
catch (error) {
|
|
580
|
+
return err(toFileError(error, resolved));
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
async remove(path, options) {
|
|
584
|
+
const resolved = resolvePath(this.cwd, path);
|
|
585
|
+
try {
|
|
586
|
+
await rm(resolved, { recursive: options?.recursive ?? false, force: options?.force ?? false });
|
|
587
|
+
return ok(undefined);
|
|
588
|
+
}
|
|
589
|
+
catch (error) {
|
|
590
|
+
return err(toFileError(error, resolved));
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
async createTempDir(prefix = "tmp-") {
|
|
594
|
+
try {
|
|
595
|
+
return ok(await mkdtemp(join(tmpdir(), prefix)));
|
|
596
|
+
}
|
|
597
|
+
catch (error) {
|
|
598
|
+
return err(toFileError(error));
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
async createTempFile(options) {
|
|
602
|
+
const dir = await this.createTempDir("tmp-");
|
|
603
|
+
if (!dir.ok)
|
|
604
|
+
return dir;
|
|
605
|
+
const filePath = join(dir.value, `${options?.prefix ?? ""}${randomUUID()}${options?.suffix ?? ""}`);
|
|
606
|
+
try {
|
|
607
|
+
await writeFile(filePath, "");
|
|
608
|
+
return ok(filePath);
|
|
609
|
+
}
|
|
610
|
+
catch (error) {
|
|
611
|
+
return err(toFileError(error, filePath));
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
async cleanup() {
|
|
615
|
+
for (const pid of this.activeChildPids)
|
|
616
|
+
killProcessTree(pid);
|
|
617
|
+
this.activeChildPids.clear();
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
//# sourceMappingURL=nodejs.js.map
|