@kb-labs/host-agent-fs 0.2.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.d.ts +93 -0
- package/dist/index.js +314 -0
- package/dist/index.js.map +1 -0
- package/package.json +38 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { CapabilityCall } from '@kb-labs/host-agent-contracts';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* FilesystemHandler — handles capability calls for adapter: 'filesystem'
|
|
5
|
+
*
|
|
6
|
+
* Supported methods:
|
|
7
|
+
* readFile(path) → string (utf-8)
|
|
8
|
+
* writeFile(path, content) → void
|
|
9
|
+
* listDir(path) → string[]
|
|
10
|
+
* stat(path) → { size, isFile, isDir, mtime }
|
|
11
|
+
* exists(path) → boolean
|
|
12
|
+
*
|
|
13
|
+
* Security: all paths are validated against allowedPaths allowlist.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
interface FilesystemHandlerOptions {
|
|
17
|
+
/** Allowlisted root paths — requests outside these are rejected */
|
|
18
|
+
allowedPaths: string[];
|
|
19
|
+
}
|
|
20
|
+
declare class FilesystemHandler {
|
|
21
|
+
private readonly opts;
|
|
22
|
+
constructor(opts: FilesystemHandlerOptions);
|
|
23
|
+
handle(call: CapabilityCall): Promise<unknown>;
|
|
24
|
+
private argString;
|
|
25
|
+
private validatePath;
|
|
26
|
+
private readFile;
|
|
27
|
+
private writeFileMethod;
|
|
28
|
+
private listDir;
|
|
29
|
+
private statMethod;
|
|
30
|
+
private exists;
|
|
31
|
+
/**
|
|
32
|
+
* Recursively collect all files under workspacePath and return them
|
|
33
|
+
* as an array of { path, content } entries (utf-8 text files only).
|
|
34
|
+
* Binary files are skipped. Excluded dirs (node_modules, .git, dist) are skipped.
|
|
35
|
+
*/
|
|
36
|
+
private fetchWorkspace;
|
|
37
|
+
private collectFiles;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* SearchHandler — handles capability calls for adapter: 'search'
|
|
42
|
+
*
|
|
43
|
+
* Supported methods:
|
|
44
|
+
* grep(pattern, directory, options?) → GrepResult
|
|
45
|
+
* glob(pattern, directory, options?) → GlobResult
|
|
46
|
+
*
|
|
47
|
+
* Security: all paths are validated against allowedPaths allowlist.
|
|
48
|
+
*
|
|
49
|
+
* @see ADR-0017: Workspace Agent Architecture (Phase 3)
|
|
50
|
+
*/
|
|
51
|
+
|
|
52
|
+
interface SearchHandlerOptions {
|
|
53
|
+
allowedPaths: string[];
|
|
54
|
+
}
|
|
55
|
+
declare class SearchHandler {
|
|
56
|
+
private readonly opts;
|
|
57
|
+
constructor(opts: SearchHandlerOptions);
|
|
58
|
+
handle(call: CapabilityCall): Promise<unknown>;
|
|
59
|
+
private argString;
|
|
60
|
+
private validatePath;
|
|
61
|
+
private grep;
|
|
62
|
+
private glob;
|
|
63
|
+
private hasRipgrep;
|
|
64
|
+
private shellEscape;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* ShellHandler — handles capability calls for adapter: 'shell'
|
|
69
|
+
*
|
|
70
|
+
* Supported methods:
|
|
71
|
+
* exec(command, options?) → ShellResult
|
|
72
|
+
*
|
|
73
|
+
* Security:
|
|
74
|
+
* - Paths validated against allowedPaths
|
|
75
|
+
* - Dangerous commands blocked (rm -rf /, fork bombs, etc.)
|
|
76
|
+
* - Timeout enforcement
|
|
77
|
+
*
|
|
78
|
+
* @see ADR-0017: Workspace Agent Architecture (Phase 3)
|
|
79
|
+
*/
|
|
80
|
+
|
|
81
|
+
interface ShellHandlerOptions {
|
|
82
|
+
allowedPaths: string[];
|
|
83
|
+
}
|
|
84
|
+
declare class ShellHandler {
|
|
85
|
+
private readonly opts;
|
|
86
|
+
constructor(opts: ShellHandlerOptions);
|
|
87
|
+
handle(call: CapabilityCall): Promise<unknown>;
|
|
88
|
+
private argString;
|
|
89
|
+
private validatePath;
|
|
90
|
+
private exec;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export { FilesystemHandler, type FilesystemHandlerOptions, SearchHandler, type SearchHandlerOptions, ShellHandler, type ShellHandlerOptions };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
import { readFile, writeFile, readdir, stat } from 'fs/promises';
|
|
2
|
+
import { resolve, normalize, sep, join, relative } from 'path';
|
|
3
|
+
import { execSync } from 'child_process';
|
|
4
|
+
|
|
5
|
+
// src/filesystem-handler.ts
|
|
6
|
+
var FETCH_EXCLUDE = [
|
|
7
|
+
"node_modules",
|
|
8
|
+
".git",
|
|
9
|
+
"dist",
|
|
10
|
+
".kb/cache",
|
|
11
|
+
".kb/runtime",
|
|
12
|
+
".kb/logs"
|
|
13
|
+
];
|
|
14
|
+
var FilesystemHandler = class {
|
|
15
|
+
constructor(opts) {
|
|
16
|
+
this.opts = opts;
|
|
17
|
+
}
|
|
18
|
+
async handle(call) {
|
|
19
|
+
switch (call.method) {
|
|
20
|
+
case "readFile":
|
|
21
|
+
return this.readFile(this.argString(call.args, 0));
|
|
22
|
+
case "writeFile":
|
|
23
|
+
return this.writeFileMethod(this.argString(call.args, 0), this.argString(call.args, 1));
|
|
24
|
+
case "listDir":
|
|
25
|
+
return this.listDir(this.argString(call.args, 0));
|
|
26
|
+
case "stat":
|
|
27
|
+
return this.statMethod(this.argString(call.args, 0));
|
|
28
|
+
case "exists":
|
|
29
|
+
return this.exists(this.argString(call.args, 0));
|
|
30
|
+
case "fetchWorkspace":
|
|
31
|
+
return this.fetchWorkspace(this.argString(call.args, 0));
|
|
32
|
+
default:
|
|
33
|
+
throw new Error(`Unknown filesystem method: ${call.method}`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
argString(args, index) {
|
|
37
|
+
const val = args[index];
|
|
38
|
+
if (typeof val !== "string" || val.length === 0) {
|
|
39
|
+
throw new Error(`Expected non-empty string at args[${index}]`);
|
|
40
|
+
}
|
|
41
|
+
return val;
|
|
42
|
+
}
|
|
43
|
+
validatePath(filePath) {
|
|
44
|
+
const resolved = resolve(normalize(filePath));
|
|
45
|
+
const allowed = this.opts.allowedPaths.some((p) => {
|
|
46
|
+
const base = resolve(normalize(p));
|
|
47
|
+
return resolved === base || resolved.startsWith(base + sep);
|
|
48
|
+
});
|
|
49
|
+
if (!allowed) {
|
|
50
|
+
throw new Error(`Access denied: ${filePath}`);
|
|
51
|
+
}
|
|
52
|
+
return resolved;
|
|
53
|
+
}
|
|
54
|
+
async readFile(filePath) {
|
|
55
|
+
return readFile(this.validatePath(filePath), "utf-8");
|
|
56
|
+
}
|
|
57
|
+
async writeFileMethod(filePath, content) {
|
|
58
|
+
await writeFile(this.validatePath(filePath), content, "utf-8");
|
|
59
|
+
}
|
|
60
|
+
async listDir(dirPath) {
|
|
61
|
+
return readdir(this.validatePath(dirPath));
|
|
62
|
+
}
|
|
63
|
+
async statMethod(filePath) {
|
|
64
|
+
const s = await stat(this.validatePath(filePath));
|
|
65
|
+
return { size: s.size, isFile: s.isFile(), isDir: s.isDirectory(), mtime: s.mtimeMs };
|
|
66
|
+
}
|
|
67
|
+
async exists(filePath) {
|
|
68
|
+
try {
|
|
69
|
+
await stat(this.validatePath(filePath));
|
|
70
|
+
return true;
|
|
71
|
+
} catch {
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Recursively collect all files under workspacePath and return them
|
|
77
|
+
* as an array of { path, content } entries (utf-8 text files only).
|
|
78
|
+
* Binary files are skipped. Excluded dirs (node_modules, .git, dist) are skipped.
|
|
79
|
+
*/
|
|
80
|
+
async fetchWorkspace(workspacePath) {
|
|
81
|
+
const root = this.validatePath(workspacePath);
|
|
82
|
+
const files = [];
|
|
83
|
+
await this.collectFiles(root, root, files);
|
|
84
|
+
return files;
|
|
85
|
+
}
|
|
86
|
+
async collectFiles(root, dir, out) {
|
|
87
|
+
let entries;
|
|
88
|
+
try {
|
|
89
|
+
entries = await readdir(dir);
|
|
90
|
+
} catch {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
for (const entry of entries) {
|
|
94
|
+
const fullPath = join(dir, entry);
|
|
95
|
+
const relPath = relative(root, fullPath);
|
|
96
|
+
if (FETCH_EXCLUDE.some((ex) => relPath === ex || relPath.startsWith(ex + sep) || entry === ex)) {
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
let s;
|
|
100
|
+
try {
|
|
101
|
+
s = await stat(fullPath);
|
|
102
|
+
} catch {
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
if (s.isDirectory()) {
|
|
106
|
+
await this.collectFiles(root, fullPath, out);
|
|
107
|
+
} else if (s.isFile() && s.size < 5 * 1024 * 1024) {
|
|
108
|
+
try {
|
|
109
|
+
const content = await readFile(fullPath, "utf-8");
|
|
110
|
+
out.push({ path: relPath, content });
|
|
111
|
+
} catch {
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
var DEFAULT_EXCLUDES = ["node_modules", ".git", "dist", ".next", ".kb/cache", ".kb/runtime"];
|
|
118
|
+
var DEFAULT_MAX_RESULTS = 100;
|
|
119
|
+
var SEARCH_TIMEOUT_MS = 15e3;
|
|
120
|
+
var SEARCH_MAX_BUFFER = 5 * 1024 * 1024;
|
|
121
|
+
var SearchHandler = class {
|
|
122
|
+
constructor(opts) {
|
|
123
|
+
this.opts = opts;
|
|
124
|
+
}
|
|
125
|
+
async handle(call) {
|
|
126
|
+
switch (call.method) {
|
|
127
|
+
case "grep":
|
|
128
|
+
return this.grep(
|
|
129
|
+
this.argString(call.args, 0),
|
|
130
|
+
this.argString(call.args, 1),
|
|
131
|
+
call.args[2] ?? {}
|
|
132
|
+
);
|
|
133
|
+
case "glob":
|
|
134
|
+
return this.glob(
|
|
135
|
+
this.argString(call.args, 0),
|
|
136
|
+
this.argString(call.args, 1),
|
|
137
|
+
call.args[2] ?? {}
|
|
138
|
+
);
|
|
139
|
+
default:
|
|
140
|
+
throw new Error(`Unknown search method: ${call.method}`);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
argString(args, index) {
|
|
144
|
+
const val = args[index];
|
|
145
|
+
if (typeof val !== "string" || val.length === 0) {
|
|
146
|
+
throw new Error(`Expected non-empty string at args[${index}]`);
|
|
147
|
+
}
|
|
148
|
+
return val;
|
|
149
|
+
}
|
|
150
|
+
validatePath(filePath) {
|
|
151
|
+
const resolved = resolve(normalize(filePath));
|
|
152
|
+
const allowed = this.opts.allowedPaths.some((p) => {
|
|
153
|
+
const base = resolve(normalize(p));
|
|
154
|
+
return resolved === base || resolved.startsWith(base + sep);
|
|
155
|
+
});
|
|
156
|
+
if (!allowed) {
|
|
157
|
+
throw new Error(`Access denied: ${filePath}`);
|
|
158
|
+
}
|
|
159
|
+
return resolved;
|
|
160
|
+
}
|
|
161
|
+
grep(pattern, directory, options) {
|
|
162
|
+
const dir = this.validatePath(directory);
|
|
163
|
+
const maxResults = options.maxResults ?? DEFAULT_MAX_RESULTS;
|
|
164
|
+
const excludes = options.excludes ?? DEFAULT_EXCLUDES;
|
|
165
|
+
const excludeFlags = excludes.map((d) => `--exclude-dir=${d}`).join(" ");
|
|
166
|
+
const includeFlags = options.includes ? options.includes.map((ext) => `--include='${ext}'`).join(" ") : "";
|
|
167
|
+
const contextFlag = options.contextLines ? `-C ${options.contextLines}` : "";
|
|
168
|
+
const cmd = this.hasRipgrep() ? `rg --no-heading --line-number --max-count=${maxResults} ${excludes.map((d) => `--glob='!${d}'`).join(" ")} ${options.includes ? options.includes.map((ext) => `--glob='${ext}'`).join(" ") : ""} ${contextFlag ? `-C ${options.contextLines}` : ""} -- ${this.shellEscape(pattern)} ${this.shellEscape(dir)}` : `grep -rn ${excludeFlags} ${includeFlags} ${contextFlag} -m ${maxResults} -- ${this.shellEscape(pattern)} ${this.shellEscape(dir)}`;
|
|
169
|
+
try {
|
|
170
|
+
const stdout = execSync(cmd, {
|
|
171
|
+
timeout: SEARCH_TIMEOUT_MS,
|
|
172
|
+
maxBuffer: SEARCH_MAX_BUFFER,
|
|
173
|
+
encoding: "utf-8",
|
|
174
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
175
|
+
});
|
|
176
|
+
const lines = stdout.trim().split("\n").filter(Boolean);
|
|
177
|
+
const matches = [];
|
|
178
|
+
for (const line of lines) {
|
|
179
|
+
const match = line.match(/^(.+?):(\d+):(.*)$/);
|
|
180
|
+
if (match) {
|
|
181
|
+
matches.push({
|
|
182
|
+
file: match[1],
|
|
183
|
+
line: parseInt(match[2], 10),
|
|
184
|
+
content: match[3]
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return {
|
|
189
|
+
matches: matches.slice(0, maxResults),
|
|
190
|
+
truncated: matches.length >= maxResults,
|
|
191
|
+
totalMatches: matches.length
|
|
192
|
+
};
|
|
193
|
+
} catch (err) {
|
|
194
|
+
if (err.status === 1) {
|
|
195
|
+
return { matches: [], truncated: false, totalMatches: 0 };
|
|
196
|
+
}
|
|
197
|
+
throw err;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
glob(pattern, directory, options) {
|
|
201
|
+
const dir = this.validatePath(directory);
|
|
202
|
+
const maxResults = options.maxResults ?? DEFAULT_MAX_RESULTS;
|
|
203
|
+
const excludes = options.excludes ?? DEFAULT_EXCLUDES;
|
|
204
|
+
const excludeFlags = excludes.map((d) => `! -path "*/${d}/*"`).join(" ");
|
|
205
|
+
const cmd = `find ${this.shellEscape(dir)} -type f -name ${this.shellEscape(pattern)} ${excludeFlags} | head -n ${maxResults + 1}`;
|
|
206
|
+
try {
|
|
207
|
+
const stdout = execSync(cmd, {
|
|
208
|
+
timeout: SEARCH_TIMEOUT_MS,
|
|
209
|
+
maxBuffer: SEARCH_MAX_BUFFER,
|
|
210
|
+
encoding: "utf-8",
|
|
211
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
212
|
+
});
|
|
213
|
+
const files = stdout.trim().split("\n").filter(Boolean);
|
|
214
|
+
const truncated = files.length > maxResults;
|
|
215
|
+
const result = truncated ? files.slice(0, maxResults) : files;
|
|
216
|
+
return {
|
|
217
|
+
files: result,
|
|
218
|
+
truncated,
|
|
219
|
+
totalFiles: files.length
|
|
220
|
+
};
|
|
221
|
+
} catch {
|
|
222
|
+
return { files: [], truncated: false, totalFiles: 0 };
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
hasRipgrep() {
|
|
226
|
+
try {
|
|
227
|
+
execSync("rg --version", { stdio: "pipe", timeout: 2e3 });
|
|
228
|
+
return true;
|
|
229
|
+
} catch {
|
|
230
|
+
return false;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
shellEscape(s) {
|
|
234
|
+
return `'${s.replace(/'/g, `'\\''`)}'`;
|
|
235
|
+
}
|
|
236
|
+
};
|
|
237
|
+
var DEFAULT_TIMEOUT_MS = 3e4;
|
|
238
|
+
var MAX_BUFFER = 10 * 1024 * 1024;
|
|
239
|
+
var BLOCKED_COMMANDS = [
|
|
240
|
+
/rm\s+-rf\s+\//,
|
|
241
|
+
/rm\s+-rf\s+\/\*/,
|
|
242
|
+
/mkfs/,
|
|
243
|
+
/dd\s+if=/,
|
|
244
|
+
/:.*\(\)\s*\{\s*:\s*\|.*&\s*\}\s*;/,
|
|
245
|
+
// fork bomb
|
|
246
|
+
/chmod\s+-R\s+777\s+\//,
|
|
247
|
+
/chown\s+-R/,
|
|
248
|
+
/>\s*\/dev\/sda/,
|
|
249
|
+
/mv\s+\/\*/
|
|
250
|
+
];
|
|
251
|
+
var ShellHandler = class {
|
|
252
|
+
constructor(opts) {
|
|
253
|
+
this.opts = opts;
|
|
254
|
+
}
|
|
255
|
+
async handle(call) {
|
|
256
|
+
switch (call.method) {
|
|
257
|
+
case "exec":
|
|
258
|
+
return this.exec(
|
|
259
|
+
this.argString(call.args, 0),
|
|
260
|
+
call.args[1] ?? {}
|
|
261
|
+
);
|
|
262
|
+
default:
|
|
263
|
+
throw new Error(`Unknown shell method: ${call.method}`);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
argString(args, index) {
|
|
267
|
+
const val = args[index];
|
|
268
|
+
if (typeof val !== "string" || val.length === 0) {
|
|
269
|
+
throw new Error(`Expected non-empty string at args[${index}]`);
|
|
270
|
+
}
|
|
271
|
+
return val;
|
|
272
|
+
}
|
|
273
|
+
validatePath(filePath) {
|
|
274
|
+
const resolved = resolve(normalize(filePath));
|
|
275
|
+
const allowed = this.opts.allowedPaths.some((p) => {
|
|
276
|
+
const base = resolve(normalize(p));
|
|
277
|
+
return resolved === base || resolved.startsWith(base + sep);
|
|
278
|
+
});
|
|
279
|
+
if (!allowed) {
|
|
280
|
+
throw new Error(`Access denied: ${filePath}`);
|
|
281
|
+
}
|
|
282
|
+
return resolved;
|
|
283
|
+
}
|
|
284
|
+
exec(command, options) {
|
|
285
|
+
for (const pattern of BLOCKED_COMMANDS) {
|
|
286
|
+
if (pattern.test(command)) {
|
|
287
|
+
throw new Error(`Blocked command: ${command}`);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
const cwd = options.cwd ? this.validatePath(options.cwd) : this.opts.allowedPaths[0] ? resolve(normalize(this.opts.allowedPaths[0])) : process.cwd();
|
|
291
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
292
|
+
try {
|
|
293
|
+
const stdout = execSync(command, {
|
|
294
|
+
cwd,
|
|
295
|
+
timeout: timeoutMs,
|
|
296
|
+
maxBuffer: MAX_BUFFER,
|
|
297
|
+
encoding: "utf-8",
|
|
298
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
299
|
+
env: options.env ? { ...process.env, ...options.env } : void 0
|
|
300
|
+
});
|
|
301
|
+
return { stdout: stdout ?? "", stderr: "", exitCode: 0 };
|
|
302
|
+
} catch (err) {
|
|
303
|
+
return {
|
|
304
|
+
stdout: err.stdout ?? "",
|
|
305
|
+
stderr: err.stderr ?? err.message ?? "",
|
|
306
|
+
exitCode: err.status ?? 1
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
};
|
|
311
|
+
|
|
312
|
+
export { FilesystemHandler, SearchHandler, ShellHandler };
|
|
313
|
+
//# sourceMappingURL=index.js.map
|
|
314
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/filesystem-handler.ts","../src/search-handler.ts","../src/shell-handler.ts"],"names":["resolve","normalize","sep","execSync"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * FilesystemHandler — handles capability calls for adapter: 'filesystem'\n *\n * Supported methods:\n * readFile(path) → string (utf-8)\n * writeFile(path, content) → void\n * listDir(path) → string[]\n * stat(path) → { size, isFile, isDir, mtime }\n * exists(path) → boolean\n *\n * Security: all paths are validated against allowedPaths allowlist.\n */\n\nimport { readFile, writeFile, readdir, stat } from 'node:fs/promises';\nimport { resolve, normalize, sep, relative, join } from 'node:path';\nimport type { CapabilityCall } from '@kb-labs/host-agent-contracts';\n\n/** A single file entry in a workspace snapshot */\nexport interface WorkspaceFile {\n path: string; // relative path from workspace root\n content: string; // utf-8 content\n}\n\n/** Patterns always excluded from workspace fetch */\nconst FETCH_EXCLUDE = [\n 'node_modules',\n '.git',\n 'dist',\n '.kb/cache',\n '.kb/runtime',\n '.kb/logs',\n];\n\nexport interface FilesystemHandlerOptions {\n /** Allowlisted root paths — requests outside these are rejected */\n allowedPaths: string[];\n}\n\nexport class FilesystemHandler {\n constructor(private readonly opts: FilesystemHandlerOptions) {}\n\n async handle(call: CapabilityCall): Promise<unknown> {\n switch (call.method) {\n case 'readFile': return this.readFile(this.argString(call.args, 0));\n case 'writeFile': return this.writeFileMethod(this.argString(call.args, 0), this.argString(call.args, 1));\n case 'listDir': return this.listDir(this.argString(call.args, 0));\n case 'stat': return this.statMethod(this.argString(call.args, 0));\n case 'exists': return this.exists(this.argString(call.args, 0));\n case 'fetchWorkspace': return this.fetchWorkspace(this.argString(call.args, 0));\n default:\n throw new Error(`Unknown filesystem method: ${call.method}`);\n }\n }\n\n private argString(args: unknown[], index: number): string {\n const val = args[index];\n if (typeof val !== 'string' || val.length === 0) {\n throw new Error(`Expected non-empty string at args[${index}]`);\n }\n return val;\n }\n\n private validatePath(filePath: string): string {\n const resolved = resolve(normalize(filePath));\n // Append sep to prevent partial prefix match: /home/user2 starting with /home/user\n const allowed = this.opts.allowedPaths.some((p) => {\n const base = resolve(normalize(p));\n return resolved === base || resolved.startsWith(base + sep);\n });\n if (!allowed) { throw new Error(`Access denied: ${filePath}`); }\n return resolved;\n }\n\n private async readFile(filePath: string): Promise<string> {\n return readFile(this.validatePath(filePath), 'utf-8');\n }\n\n private async writeFileMethod(filePath: string, content: string): Promise<void> {\n await writeFile(this.validatePath(filePath), content, 'utf-8');\n }\n\n private async listDir(dirPath: string): Promise<string[]> {\n return readdir(this.validatePath(dirPath));\n }\n\n private async statMethod(filePath: string): Promise<{ size: number; isFile: boolean; isDir: boolean; mtime: number }> {\n const s = await stat(this.validatePath(filePath));\n return { size: s.size, isFile: s.isFile(), isDir: s.isDirectory(), mtime: s.mtimeMs };\n }\n\n private async exists(filePath: string): Promise<boolean> {\n try {\n await stat(this.validatePath(filePath));\n return true;\n } catch {\n return false;\n }\n }\n\n /**\n * Recursively collect all files under workspacePath and return them\n * as an array of { path, content } entries (utf-8 text files only).\n * Binary files are skipped. Excluded dirs (node_modules, .git, dist) are skipped.\n */\n private async fetchWorkspace(workspacePath: string): Promise<WorkspaceFile[]> {\n const root = this.validatePath(workspacePath);\n const files: WorkspaceFile[] = [];\n await this.collectFiles(root, root, files);\n return files;\n }\n\n private async collectFiles(root: string, dir: string, out: WorkspaceFile[]): Promise<void> {\n let entries: string[];\n try {\n entries = await readdir(dir);\n } catch {\n return;\n }\n\n for (const entry of entries) {\n const fullPath = join(dir, entry);\n const relPath = relative(root, fullPath);\n\n // Skip excluded patterns (check each segment)\n if (FETCH_EXCLUDE.some((ex) => relPath === ex || relPath.startsWith(ex + sep) || entry === ex)) {\n continue;\n }\n\n let s: Awaited<ReturnType<typeof stat>>;\n try {\n s = await stat(fullPath);\n } catch {\n continue;\n }\n\n if (s.isDirectory()) {\n await this.collectFiles(root, fullPath, out);\n } else if (s.isFile() && s.size < 5 * 1024 * 1024) { // skip files > 5MB\n try {\n const content = await readFile(fullPath, 'utf-8');\n out.push({ path: relPath, content });\n } catch {\n // skip binary or unreadable files\n }\n }\n }\n }\n}\n","/**\n * SearchHandler — handles capability calls for adapter: 'search'\n *\n * Supported methods:\n * grep(pattern, directory, options?) → GrepResult\n * glob(pattern, directory, options?) → GlobResult\n *\n * Security: all paths are validated against allowedPaths allowlist.\n *\n * @see ADR-0017: Workspace Agent Architecture (Phase 3)\n */\n\nimport { execSync } from 'node:child_process';\nimport { resolve, normalize, sep } from 'node:path';\nimport type { CapabilityCall } from '@kb-labs/host-agent-contracts';\n\ninterface GrepMatch {\n file: string;\n line: number;\n content: string;\n}\n\ninterface GrepResult {\n matches: GrepMatch[];\n truncated: boolean;\n totalMatches: number;\n}\n\ninterface GlobResult {\n files: string[];\n truncated: boolean;\n totalFiles: number;\n}\n\ninterface GrepOptions {\n includes?: string[];\n excludes?: string[];\n maxResults?: number;\n contextLines?: number;\n}\n\ninterface GlobOptions {\n excludes?: string[];\n maxResults?: number;\n}\n\nconst DEFAULT_EXCLUDES = ['node_modules', '.git', 'dist', '.next', '.kb/cache', '.kb/runtime'];\nconst DEFAULT_MAX_RESULTS = 100;\nconst SEARCH_TIMEOUT_MS = 15_000;\nconst SEARCH_MAX_BUFFER = 5 * 1024 * 1024; // 5MB\n\nexport interface SearchHandlerOptions {\n allowedPaths: string[];\n}\n\nexport class SearchHandler {\n constructor(private readonly opts: SearchHandlerOptions) {}\n\n async handle(call: CapabilityCall): Promise<unknown> {\n switch (call.method) {\n case 'grep':\n return this.grep(\n this.argString(call.args, 0),\n this.argString(call.args, 1),\n (call.args[2] as GrepOptions) ?? {},\n );\n case 'glob':\n return this.glob(\n this.argString(call.args, 0),\n this.argString(call.args, 1),\n (call.args[2] as GlobOptions) ?? {},\n );\n default:\n throw new Error(`Unknown search method: ${call.method}`);\n }\n }\n\n private argString(args: unknown[], index: number): string {\n const val = args[index];\n if (typeof val !== 'string' || val.length === 0) {\n throw new Error(`Expected non-empty string at args[${index}]`);\n }\n return val;\n }\n\n private validatePath(filePath: string): string {\n const resolved = resolve(normalize(filePath));\n const allowed = this.opts.allowedPaths.some((p) => {\n const base = resolve(normalize(p));\n return resolved === base || resolved.startsWith(base + sep);\n });\n if (!allowed) { throw new Error(`Access denied: ${filePath}`); }\n return resolved;\n }\n\n private grep(pattern: string, directory: string, options: GrepOptions): GrepResult {\n const dir = this.validatePath(directory);\n const maxResults = options.maxResults ?? DEFAULT_MAX_RESULTS;\n const excludes = options.excludes ?? DEFAULT_EXCLUDES;\n\n const excludeFlags = excludes.map(d => `--exclude-dir=${d}`).join(' ');\n const includeFlags = options.includes\n ? options.includes.map(ext => `--include='${ext}'`).join(' ')\n : '';\n const contextFlag = options.contextLines ? `-C ${options.contextLines}` : '';\n\n // Try rg first, fallback to grep\n const cmd = this.hasRipgrep()\n ? `rg --no-heading --line-number --max-count=${maxResults} ${excludes.map(d => `--glob='!${d}'`).join(' ')} ${options.includes ? options.includes.map(ext => `--glob='${ext}'`).join(' ') : ''} ${contextFlag ? `-C ${options.contextLines}` : ''} -- ${this.shellEscape(pattern)} ${this.shellEscape(dir)}`\n : `grep -rn ${excludeFlags} ${includeFlags} ${contextFlag} -m ${maxResults} -- ${this.shellEscape(pattern)} ${this.shellEscape(dir)}`;\n\n try {\n const stdout = execSync(cmd, {\n timeout: SEARCH_TIMEOUT_MS,\n maxBuffer: SEARCH_MAX_BUFFER,\n encoding: 'utf-8',\n stdio: ['pipe', 'pipe', 'pipe'],\n });\n\n const lines = stdout.trim().split('\\n').filter(Boolean);\n const matches: GrepMatch[] = [];\n\n for (const line of lines) {\n const match = line.match(/^(.+?):(\\d+):(.*)$/);\n if (match) {\n matches.push({\n file: match[1]!,\n line: parseInt(match[2]!, 10),\n content: match[3]!,\n });\n }\n }\n\n return {\n matches: matches.slice(0, maxResults),\n truncated: matches.length >= maxResults,\n totalMatches: matches.length,\n };\n } catch (err: any) {\n // grep returns exit code 1 when no matches found\n if (err.status === 1) {\n return { matches: [], truncated: false, totalMatches: 0 };\n }\n throw err;\n }\n }\n\n private glob(pattern: string, directory: string, options: GlobOptions): GlobResult {\n const dir = this.validatePath(directory);\n const maxResults = options.maxResults ?? DEFAULT_MAX_RESULTS;\n const excludes = options.excludes ?? DEFAULT_EXCLUDES;\n\n const excludeFlags = excludes.map(d => `! -path \"*/${d}/*\"`).join(' ');\n const cmd = `find ${this.shellEscape(dir)} -type f -name ${this.shellEscape(pattern)} ${excludeFlags} | head -n ${maxResults + 1}`;\n\n try {\n const stdout = execSync(cmd, {\n timeout: SEARCH_TIMEOUT_MS,\n maxBuffer: SEARCH_MAX_BUFFER,\n encoding: 'utf-8',\n stdio: ['pipe', 'pipe', 'pipe'],\n });\n\n const files = stdout.trim().split('\\n').filter(Boolean);\n const truncated = files.length > maxResults;\n const result = truncated ? files.slice(0, maxResults) : files;\n\n return {\n files: result,\n truncated,\n totalFiles: files.length,\n };\n } catch {\n return { files: [], truncated: false, totalFiles: 0 };\n }\n }\n\n private hasRipgrep(): boolean {\n try {\n execSync('rg --version', { stdio: 'pipe', timeout: 2000 });\n return true;\n } catch {\n return false;\n }\n }\n\n private shellEscape(s: string): string {\n return `'${s.replace(/'/g, `'\\\\''`)}'`;\n }\n}\n","/**\n * ShellHandler — handles capability calls for adapter: 'shell'\n *\n * Supported methods:\n * exec(command, options?) → ShellResult\n *\n * Security:\n * - Paths validated against allowedPaths\n * - Dangerous commands blocked (rm -rf /, fork bombs, etc.)\n * - Timeout enforcement\n *\n * @see ADR-0017: Workspace Agent Architecture (Phase 3)\n */\n\nimport { execSync } from 'node:child_process';\nimport { resolve, normalize, sep } from 'node:path';\nimport type { CapabilityCall } from '@kb-labs/host-agent-contracts';\n\ninterface ShellResult {\n stdout: string;\n stderr: string;\n exitCode: number;\n}\n\ninterface ShellOptions {\n cwd?: string;\n timeoutMs?: number;\n env?: Record<string, string>;\n}\n\nconst DEFAULT_TIMEOUT_MS = 30_000;\nconst MAX_BUFFER = 10 * 1024 * 1024; // 10MB\n\n/** Commands that are always blocked */\nconst BLOCKED_COMMANDS = [\n /rm\\s+-rf\\s+\\//,\n /rm\\s+-rf\\s+\\/\\*/,\n /mkfs/,\n /dd\\s+if=/,\n /:.*\\(\\)\\s*\\{\\s*:\\s*\\|.*&\\s*\\}\\s*;/, // fork bomb\n /chmod\\s+-R\\s+777\\s+\\//,\n /chown\\s+-R/,\n />\\s*\\/dev\\/sda/,\n /mv\\s+\\/\\*/,\n];\n\nexport interface ShellHandlerOptions {\n allowedPaths: string[];\n}\n\nexport class ShellHandler {\n constructor(private readonly opts: ShellHandlerOptions) {}\n\n async handle(call: CapabilityCall): Promise<unknown> {\n switch (call.method) {\n case 'exec':\n return this.exec(\n this.argString(call.args, 0),\n (call.args[1] as ShellOptions) ?? {},\n );\n default:\n throw new Error(`Unknown shell method: ${call.method}`);\n }\n }\n\n private argString(args: unknown[], index: number): string {\n const val = args[index];\n if (typeof val !== 'string' || val.length === 0) {\n throw new Error(`Expected non-empty string at args[${index}]`);\n }\n return val;\n }\n\n private validatePath(filePath: string): string {\n const resolved = resolve(normalize(filePath));\n const allowed = this.opts.allowedPaths.some((p) => {\n const base = resolve(normalize(p));\n return resolved === base || resolved.startsWith(base + sep);\n });\n if (!allowed) { throw new Error(`Access denied: ${filePath}`); }\n return resolved;\n }\n\n private exec(command: string, options: ShellOptions): ShellResult {\n // 1. Check for blocked commands\n for (const pattern of BLOCKED_COMMANDS) {\n if (pattern.test(command)) {\n throw new Error(`Blocked command: ${command}`);\n }\n }\n\n // 2. Resolve and validate cwd\n const cwd = options.cwd\n ? this.validatePath(options.cwd)\n : this.opts.allowedPaths[0]\n ? resolve(normalize(this.opts.allowedPaths[0]))\n : process.cwd();\n\n const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n\n // 3. Execute\n try {\n const stdout = execSync(command, {\n cwd,\n timeout: timeoutMs,\n maxBuffer: MAX_BUFFER,\n encoding: 'utf-8',\n stdio: ['pipe', 'pipe', 'pipe'],\n env: options.env ? { ...process.env, ...options.env } : undefined,\n });\n\n return { stdout: stdout ?? '', stderr: '', exitCode: 0 };\n } catch (err: any) {\n return {\n stdout: err.stdout ?? '',\n stderr: err.stderr ?? err.message ?? '',\n exitCode: err.status ?? 1,\n };\n }\n }\n}\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@kb-labs/host-agent-fs",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"types": "./dist/index.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"import": "./dist/index.js",
|
|
10
|
+
"types": "./dist/index.d.ts"
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"dist",
|
|
15
|
+
"README.md"
|
|
16
|
+
],
|
|
17
|
+
"sideEffects": false,
|
|
18
|
+
"scripts": {
|
|
19
|
+
"clean": "rimraf dist",
|
|
20
|
+
"build": "tsup",
|
|
21
|
+
"dev": "tsup --watch",
|
|
22
|
+
"type-check": "tsc --noEmit",
|
|
23
|
+
"lint": "eslint .",
|
|
24
|
+
"lint:fix": "eslint . --fix",
|
|
25
|
+
"test": "vitest run -c ../../vitest.config.ts",
|
|
26
|
+
"test:watch": "vitest -c ../../vitest.config.ts"
|
|
27
|
+
},
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"@kb-labs/host-agent-contracts": "^0.2.0"
|
|
30
|
+
},
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"@kb-labs/devkit": "link:../../../kb-labs-devkit",
|
|
33
|
+
"@types/node": "^20",
|
|
34
|
+
"rimraf": "^6",
|
|
35
|
+
"tsup": "^8.5.0",
|
|
36
|
+
"vitest": "^3.2.4"
|
|
37
|
+
}
|
|
38
|
+
}
|