@theokit/sdk-tools 0.20.1 → 0.21.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/dist/index.cjs +49 -137
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +18 -106
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1,106 +1,18 @@
|
|
|
1
1
|
import { rm, mkdir, writeFile, readFile, readdir, open, stat, copyFile } from 'fs/promises';
|
|
2
|
-
import { dirname, relative, join, isAbsolute
|
|
2
|
+
import { dirname, relative, join, isAbsolute } from 'path';
|
|
3
3
|
import { Tool, ConfigurationError } from '@theokit/sdk';
|
|
4
4
|
import { z } from 'zod';
|
|
5
|
-
import {
|
|
6
|
-
import { safeFilenameForId, safePathJoin as safePathJoin$1 } from '@theokit/sdk/path-safety';
|
|
5
|
+
import { safePathJoin, assertNoSymlinkEscape, PathTraversalError, ForbiddenPathError, isForbiddenPath, safeFilenameForId } from '@theokit/sdk/path-safety';
|
|
7
6
|
import { replaceFileAtomic } from '@theokit/sdk/persistence';
|
|
8
7
|
import { resolveFilesystem, FileNotFoundError, FilesystemSecurityError, FilesystemReadOnlyError, StaleFileError, FilesystemError } from '@theokit/sdk/filesystem';
|
|
9
8
|
import { spawn } from 'child_process';
|
|
9
|
+
import { existsSync, statSync, mkdirSync, writeFileSync, readFileSync, readdirSync } from 'fs';
|
|
10
10
|
import { resolveSandbox } from '@theokit/sdk/sandbox';
|
|
11
11
|
import { resolveInteractive, InteractiveUnavailableError, NoSuchSessionError } from '@theokit/sdk/interactive';
|
|
12
12
|
import { lookup } from 'dns/promises';
|
|
13
13
|
import { isIP } from 'net';
|
|
14
14
|
|
|
15
15
|
// src/apply-patch.ts
|
|
16
|
-
var PathTraversalError = class extends ConfigurationError {
|
|
17
|
-
name = "PathTraversalError";
|
|
18
|
-
constructor(input, resolvedPath) {
|
|
19
|
-
super(`Path traversal attempt: ${input} \u2192 ${resolvedPath}`, {
|
|
20
|
-
code: "path_traversal"
|
|
21
|
-
});
|
|
22
|
-
}
|
|
23
|
-
};
|
|
24
|
-
var ForbiddenPathError = class extends ConfigurationError {
|
|
25
|
-
name = "ForbiddenPathError";
|
|
26
|
-
constructor(path) {
|
|
27
|
-
super(
|
|
28
|
-
`Path '${path}' is in the sensitive-file blocklist (.env, .git/, node_modules/, .theo/, lock files)`,
|
|
29
|
-
{
|
|
30
|
-
code: "forbidden_path"
|
|
31
|
-
}
|
|
32
|
-
);
|
|
33
|
-
}
|
|
34
|
-
};
|
|
35
|
-
function safePathJoin(base, ...parts) {
|
|
36
|
-
if (base === "") {
|
|
37
|
-
throw new Error("safePathJoin: base must be non-empty");
|
|
38
|
-
}
|
|
39
|
-
const baseResolved = resolve(base);
|
|
40
|
-
const target = resolve(base, ...parts);
|
|
41
|
-
if (target !== baseResolved && !target.startsWith(baseResolved + sep)) {
|
|
42
|
-
throw new PathTraversalError(parts.join("/"), target);
|
|
43
|
-
}
|
|
44
|
-
return target;
|
|
45
|
-
}
|
|
46
|
-
function assertNoSymlinkEscape(path, base) {
|
|
47
|
-
let baseResolved;
|
|
48
|
-
try {
|
|
49
|
-
baseResolved = realpathSync(base);
|
|
50
|
-
} catch {
|
|
51
|
-
baseResolved = resolve(base);
|
|
52
|
-
}
|
|
53
|
-
const resolved = realpathOfDeepestExisting(path);
|
|
54
|
-
if (resolved === void 0) return;
|
|
55
|
-
if (resolved !== baseResolved && !resolved.startsWith(baseResolved + sep)) {
|
|
56
|
-
throw new PathTraversalError(`symlink ${path}`, resolved);
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
function realpathOfDeepestExisting(path) {
|
|
60
|
-
try {
|
|
61
|
-
return realpathSync(path);
|
|
62
|
-
} catch {
|
|
63
|
-
}
|
|
64
|
-
try {
|
|
65
|
-
const stat3 = lstatSync(path);
|
|
66
|
-
if (stat3.isSymbolicLink()) {
|
|
67
|
-
const target = readlinkSync(path);
|
|
68
|
-
const parentReal = realpathOfDeepestExisting(dirname(path));
|
|
69
|
-
const parentBase = parentReal ?? dirname(path);
|
|
70
|
-
return resolve(parentBase, target);
|
|
71
|
-
}
|
|
72
|
-
} catch {
|
|
73
|
-
}
|
|
74
|
-
let cursor = dirname(path);
|
|
75
|
-
let suffix = path.slice(cursor.length);
|
|
76
|
-
while (cursor !== dirname(cursor)) {
|
|
77
|
-
try {
|
|
78
|
-
const real = realpathSync(cursor);
|
|
79
|
-
return resolve(real, `.${suffix}`);
|
|
80
|
-
} catch {
|
|
81
|
-
suffix = path.slice(dirname(cursor).length);
|
|
82
|
-
cursor = dirname(cursor);
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
return void 0;
|
|
86
|
-
}
|
|
87
|
-
var LOCK_FILES = /* @__PURE__ */ new Set(["pnpm-lock.yaml", "package-lock.json", "yarn.lock", "bun.lockb"]);
|
|
88
|
-
function isForbiddenPath(input) {
|
|
89
|
-
const normalized = input.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
90
|
-
if (normalized.length === 0) return false;
|
|
91
|
-
const segments = normalized.split("/").filter((s) => s.length > 0);
|
|
92
|
-
if (segments.length === 0) return false;
|
|
93
|
-
const first = segments[0];
|
|
94
|
-
if (first === ".env.example") return false;
|
|
95
|
-
if (first === ".env") return true;
|
|
96
|
-
if (/^\.env\./.test(first)) return true;
|
|
97
|
-
if (first === ".git") return true;
|
|
98
|
-
if (first === "node_modules") return true;
|
|
99
|
-
if (first === ".theo") return true;
|
|
100
|
-
const basename = segments[segments.length - 1];
|
|
101
|
-
if (LOCK_FILES.has(basename)) return true;
|
|
102
|
-
return false;
|
|
103
|
-
}
|
|
104
16
|
|
|
105
17
|
// src/internal/v4a-patch.ts
|
|
106
18
|
var BEGIN = "*** Begin Patch";
|
|
@@ -462,7 +374,7 @@ function createSessionArtifactStore(options) {
|
|
|
462
374
|
const idStrategy = options.idStrategy ?? ((id) => safeFilenameForId(id));
|
|
463
375
|
const extension = options.extension ?? ".md";
|
|
464
376
|
function path(id) {
|
|
465
|
-
return safePathJoin
|
|
377
|
+
return safePathJoin(dir, `${idStrategy(id)}${extension}`);
|
|
466
378
|
}
|
|
467
379
|
async function write(id, content) {
|
|
468
380
|
const target = path(id);
|
|
@@ -798,25 +710,25 @@ function createSettleGate(timer) {
|
|
|
798
710
|
}
|
|
799
711
|
};
|
|
800
712
|
}
|
|
801
|
-
function armTimeoutKill(child, timeoutMs, onTimeout,
|
|
713
|
+
function armTimeoutKill(child, timeoutMs, onTimeout, resolve) {
|
|
802
714
|
const timer = setTimeout(() => {
|
|
803
715
|
gate.fire(() => {
|
|
804
716
|
try {
|
|
805
717
|
process.kill(-(child.pid ?? 0), "SIGKILL");
|
|
806
718
|
} catch {
|
|
807
719
|
}
|
|
808
|
-
|
|
720
|
+
resolve(onTimeout());
|
|
809
721
|
});
|
|
810
722
|
}, timeoutMs);
|
|
811
723
|
const gate = createSettleGate(timer);
|
|
812
724
|
return gate;
|
|
813
725
|
}
|
|
814
|
-
function attachChildSettlers(child, gate, onClose, onError,
|
|
726
|
+
function attachChildSettlers(child, gate, onClose, onError, resolve) {
|
|
815
727
|
child.on("close", (code) => {
|
|
816
|
-
gate.fire(() =>
|
|
728
|
+
gate.fire(() => resolve(onClose(code)));
|
|
817
729
|
});
|
|
818
730
|
child.on("error", (err) => {
|
|
819
|
-
gate.fire(() =>
|
|
731
|
+
gate.fire(() => resolve(onError(err)));
|
|
820
732
|
});
|
|
821
733
|
}
|
|
822
734
|
|
|
@@ -883,7 +795,7 @@ function formatGitResult(result, timeoutMs) {
|
|
|
883
795
|
return JSON.stringify({ ok: true, diff: result.stdout, truncated: result.truncated });
|
|
884
796
|
}
|
|
885
797
|
function runGitProcess(cwd, args, timeoutMs, maxStdoutBytes) {
|
|
886
|
-
return new Promise((
|
|
798
|
+
return new Promise((resolve) => {
|
|
887
799
|
const child = spawn("git", args, { cwd, detached: true, stdio: ["ignore", "pipe", "pipe"] });
|
|
888
800
|
const stdoutChunks = [];
|
|
889
801
|
const stderrChunks = [];
|
|
@@ -893,7 +805,7 @@ function runGitProcess(cwd, args, timeoutMs, maxStdoutBytes) {
|
|
|
893
805
|
child,
|
|
894
806
|
timeoutMs,
|
|
895
807
|
() => ({ kind: "timeout" }),
|
|
896
|
-
|
|
808
|
+
resolve
|
|
897
809
|
);
|
|
898
810
|
child.stdout.on("data", (chunk) => {
|
|
899
811
|
if (gate.settled()) return;
|
|
@@ -923,7 +835,7 @@ function runGitProcess(cwd, args, timeoutMs, maxStdoutBytes) {
|
|
|
923
835
|
return code === 0 ? { kind: "ok", stdout, truncated } : { kind: "error", stderr };
|
|
924
836
|
},
|
|
925
837
|
(err) => ({ kind: "error", stderr: err.message }),
|
|
926
|
-
|
|
838
|
+
resolve
|
|
927
839
|
);
|
|
928
840
|
});
|
|
929
841
|
}
|
|
@@ -2076,7 +1988,7 @@ function appendCapped(chunks, chunk, current, cap) {
|
|
|
2076
1988
|
return current + chunk.length;
|
|
2077
1989
|
}
|
|
2078
1990
|
function runProcess(cwd, command, args, timeoutMs, maxStdoutBytes) {
|
|
2079
|
-
return new Promise((
|
|
1991
|
+
return new Promise((resolve) => {
|
|
2080
1992
|
const child = spawn(command, args, {
|
|
2081
1993
|
cwd,
|
|
2082
1994
|
detached: true,
|
|
@@ -2089,7 +2001,7 @@ function runProcess(cwd, command, args, timeoutMs, maxStdoutBytes) {
|
|
|
2089
2001
|
child,
|
|
2090
2002
|
timeoutMs,
|
|
2091
2003
|
() => ({ kind: "timeout" }),
|
|
2092
|
-
|
|
2004
|
+
resolve
|
|
2093
2005
|
);
|
|
2094
2006
|
child.stdout.on("data", (chunk) => {
|
|
2095
2007
|
if (gate.settled()) return;
|
|
@@ -2108,7 +2020,7 @@ function runProcess(cwd, command, args, timeoutMs, maxStdoutBytes) {
|
|
|
2108
2020
|
exitCode: code ?? 0
|
|
2109
2021
|
}),
|
|
2110
2022
|
(err) => ({ kind: "spawn_error", message: err.message }),
|
|
2111
|
-
|
|
2023
|
+
resolve
|
|
2112
2024
|
);
|
|
2113
2025
|
});
|
|
2114
2026
|
}
|
|
@@ -2359,7 +2271,7 @@ function createShellTool(opts) {
|
|
|
2359
2271
|
});
|
|
2360
2272
|
}
|
|
2361
2273
|
function runShell(cwd, command, timeoutMs) {
|
|
2362
|
-
return new Promise((
|
|
2274
|
+
return new Promise((resolve) => {
|
|
2363
2275
|
const child = spawn("/bin/sh", ["-c", command], {
|
|
2364
2276
|
cwd,
|
|
2365
2277
|
detached: true,
|
|
@@ -2373,7 +2285,7 @@ function runShell(cwd, command, timeoutMs) {
|
|
|
2373
2285
|
child,
|
|
2374
2286
|
timeoutMs,
|
|
2375
2287
|
() => ({ kind: "timeout" }),
|
|
2376
|
-
(result) =>
|
|
2288
|
+
(result) => resolve(formatResult(result, timeoutMs))
|
|
2377
2289
|
);
|
|
2378
2290
|
child.stdout.on("data", (chunk) => {
|
|
2379
2291
|
if (gate.settled()) return;
|
|
@@ -2411,7 +2323,7 @@ function runShell(cwd, command, timeoutMs) {
|
|
|
2411
2323
|
exitCode: code
|
|
2412
2324
|
}),
|
|
2413
2325
|
(err) => ({ kind: "error", message: err.message }),
|
|
2414
|
-
(result) =>
|
|
2326
|
+
(result) => resolve(formatResult(result, timeoutMs))
|
|
2415
2327
|
);
|
|
2416
2328
|
});
|
|
2417
2329
|
}
|