@abot-ai/runtime 1.3.0 → 1.3.1
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/scripts/init-runtime.js +4 -13
- package/dist/scripts/runtime-setup-directories.d.ts +2 -0
- package/dist/scripts/runtime-setup-directories.js +38 -0
- package/dist/src/shared/directory-authority/bootstrap.d.ts +1 -0
- package/dist/src/shared/directory-authority/bootstrap.js +118 -0
- package/dist/src/shared/directory-authority/command.d.ts +8 -0
- package/dist/src/shared/directory-authority/command.js +27 -0
- package/dist/src/shared/directory-authority/errors.d.ts +5 -0
- package/dist/src/shared/directory-authority/errors.js +10 -0
- package/dist/src/shared/directory-authority/index.d.ts +3 -0
- package/dist/src/shared/directory-authority/index.js +3 -0
- package/dist/src/shared/directory-authority/protocol.d.ts +11 -0
- package/dist/src/shared/directory-authority/protocol.js +62 -0
- package/dist/src/shared/directory-authority/task.d.ts +7 -0
- package/dist/src/shared/directory-authority/task.js +97 -0
- package/docs/plugins.md +15 -9
- package/package.json +1 -1
- package/plugins/exec/plugin.json +3 -3
- package/plugins/exec/skills/exec_skill/SKILL.md +1 -1
- package/plugins/exec/source/process-manager.ts +1 -20
- package/plugins/exec/source/shell-platform.ts +29 -0
- package/plugins/exec/src/index.cjs +25 -18
- package/plugins/filesystem/source/atomic-write.ts +9 -0
- package/plugins/filesystem/source/bounded-io.ts +5 -99
- package/plugins/filesystem/source/directory-authority-write.ts +64 -0
- package/plugins/filesystem/source/directory-sample-task.ts +23 -0
- package/plugins/filesystem/source/directory-sample.ts +132 -0
- package/plugins/filesystem/source/directory-write-task.ts +160 -0
- package/plugins/filesystem/source/mutation-parent.ts +11 -0
- package/plugins/filesystem/src/index.cjs +693 -118
- package/plugins/local-search/source/index.ts +2 -0
- package/plugins/local-search/source/paths.ts +30 -7
- package/plugins/local-search/source/ripgrep-process.ts +69 -0
- package/plugins/local-search/source/ripgrep.ts +19 -23
- package/plugins/local-search/src/index.cjs +295 -32
|
@@ -98,6 +98,7 @@ export default defineRuntimePlugin((context) => ({
|
|
|
98
98
|
root.commandDirectory,
|
|
99
99
|
maxResults,
|
|
100
100
|
executionContext?.abortSignal,
|
|
101
|
+
root.directoryAuthority,
|
|
101
102
|
);
|
|
102
103
|
sourceTruncated ||= names.truncated;
|
|
103
104
|
for (const rawPath of names.items) {
|
|
@@ -132,6 +133,7 @@ export default defineRuntimePlugin((context) => ({
|
|
|
132
133
|
remaining,
|
|
133
134
|
executionContext?.abortSignal,
|
|
134
135
|
root.stdinFd,
|
|
136
|
+
root.directoryAuthority,
|
|
135
137
|
);
|
|
136
138
|
sourceTruncated ||= content.truncated;
|
|
137
139
|
for (const match of content.items) {
|
|
@@ -9,6 +9,7 @@ import type {
|
|
|
9
9
|
import { resolvePluginPath } from "../../../src/plugin-sdk/index.js";
|
|
10
10
|
|
|
11
11
|
import { LocalSearchError } from "./errors.js";
|
|
12
|
+
import type { SearchDirectoryAuthority } from "./ripgrep-process.js";
|
|
12
13
|
|
|
13
14
|
export type SearchRoot = Readonly<{
|
|
14
15
|
target: ResolvedRuntimeToolPath;
|
|
@@ -16,6 +17,7 @@ export type SearchRoot = Readonly<{
|
|
|
16
17
|
commandDirectory: string;
|
|
17
18
|
commandTarget: string;
|
|
18
19
|
stdinFd?: number;
|
|
20
|
+
directoryAuthority?: SearchDirectoryAuthority;
|
|
19
21
|
close(): Promise<void>;
|
|
20
22
|
}>;
|
|
21
23
|
|
|
@@ -54,11 +56,14 @@ function authorityUnavailable(): LocalSearchError {
|
|
|
54
56
|
async function openSearchAuthority(
|
|
55
57
|
target: ResolvedRuntimeToolPath,
|
|
56
58
|
): Promise<SearchRoot> {
|
|
57
|
-
|
|
58
|
-
process.platform
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
59
|
+
const supportsSearchAuthority = ["linux", "darwin"].includes(
|
|
60
|
+
process.platform,
|
|
61
|
+
);
|
|
62
|
+
if (!supportsSearchAuthority) throw authorityUnavailable();
|
|
63
|
+
const supportsAuthorityOpenFlags =
|
|
64
|
+
Number.isInteger(constants.O_NOFOLLOW) &&
|
|
65
|
+
Number.isInteger(constants.O_NONBLOCK);
|
|
66
|
+
if (!supportsAuthorityOpenFlags) {
|
|
62
67
|
throw authorityUnavailable();
|
|
63
68
|
}
|
|
64
69
|
|
|
@@ -92,7 +97,9 @@ async function openSearchAuthority(
|
|
|
92
97
|
|
|
93
98
|
const [pathIdentity, descriptorIdentity] = await Promise.all([
|
|
94
99
|
stat(canonicalTarget, { bigint: true }),
|
|
95
|
-
|
|
100
|
+
process.platform === "linux"
|
|
101
|
+
? stat(`/proc/self/fd/${handle.fd}`, { bigint: true })
|
|
102
|
+
: handle.stat({ bigint: true }),
|
|
96
103
|
]);
|
|
97
104
|
if (
|
|
98
105
|
!sameIdentity(observed, pathIdentity) ||
|
|
@@ -103,14 +110,30 @@ async function openSearchAuthority(
|
|
|
103
110
|
|
|
104
111
|
const authority = handle;
|
|
105
112
|
const isFile = observed.isFile();
|
|
113
|
+
const needsDirectoryBridge = !isFile && process.platform === "darwin";
|
|
114
|
+
let commandDirectory = "/";
|
|
115
|
+
if (!isFile) {
|
|
116
|
+
commandDirectory =
|
|
117
|
+
process.platform === "linux"
|
|
118
|
+
? `/proc/self/fd/${authority.fd}`
|
|
119
|
+
: canonicalTarget;
|
|
120
|
+
}
|
|
106
121
|
let closed = false;
|
|
107
122
|
handle = undefined;
|
|
108
123
|
return Object.freeze({
|
|
109
124
|
target,
|
|
110
125
|
isFile,
|
|
111
|
-
commandDirectory
|
|
126
|
+
commandDirectory,
|
|
112
127
|
commandTarget: isFile ? "-" : ".",
|
|
113
128
|
...(isFile ? { stdinFd: authority.fd } : {}),
|
|
129
|
+
...(needsDirectoryBridge
|
|
130
|
+
? {
|
|
131
|
+
directoryAuthority: {
|
|
132
|
+
directoryPath: canonicalTarget,
|
|
133
|
+
directoryFd: authority.fd,
|
|
134
|
+
},
|
|
135
|
+
}
|
|
136
|
+
: {}),
|
|
114
137
|
close: async () => {
|
|
115
138
|
if (closed) return;
|
|
116
139
|
closed = true;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { spawn, type ChildProcessByStdio } from "node:child_process";
|
|
2
|
+
import type { Readable } from "node:stream";
|
|
3
|
+
|
|
4
|
+
import { rgPath } from "@vscode/ripgrep";
|
|
5
|
+
|
|
6
|
+
import { spawnDirectoryAuthorityCommand } from "../../../src/shared/directory-authority/index.js";
|
|
7
|
+
|
|
8
|
+
export type SearchDirectoryAuthority = Readonly<{
|
|
9
|
+
directoryPath: string;
|
|
10
|
+
directoryFd: number;
|
|
11
|
+
}>;
|
|
12
|
+
|
|
13
|
+
function controlledRipgrepEnvironment(): NodeJS.ProcessEnv {
|
|
14
|
+
const environment = { ...process.env };
|
|
15
|
+
for (const name of Object.keys(environment)) {
|
|
16
|
+
if (name.toUpperCase() === "RIPGREP_CONFIG_PATH") {
|
|
17
|
+
delete environment[name];
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return environment;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function terminateAuthorityGroup(
|
|
24
|
+
child: ChildProcessByStdio<null, Readable, Readable>,
|
|
25
|
+
): void {
|
|
26
|
+
if (!child.pid) {
|
|
27
|
+
child.kill("SIGKILL");
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
try {
|
|
31
|
+
process.kill(-child.pid, "SIGKILL");
|
|
32
|
+
} catch (error) {
|
|
33
|
+
const groupAlreadyExited =
|
|
34
|
+
(error as NodeJS.ErrnoException).code === "ESRCH";
|
|
35
|
+
if (groupAlreadyExited) return;
|
|
36
|
+
throw error;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function spawnRipgrepProcess(
|
|
41
|
+
args: readonly string[],
|
|
42
|
+
cwd: string,
|
|
43
|
+
stdinFd?: number,
|
|
44
|
+
directoryAuthority?: SearchDirectoryAuthority,
|
|
45
|
+
) {
|
|
46
|
+
const commandArgs = ["--no-config", ...args];
|
|
47
|
+
const env = controlledRipgrepEnvironment();
|
|
48
|
+
if (directoryAuthority) {
|
|
49
|
+
const child = spawnDirectoryAuthorityCommand({
|
|
50
|
+
...directoryAuthority,
|
|
51
|
+
command: rgPath,
|
|
52
|
+
args: commandArgs,
|
|
53
|
+
env,
|
|
54
|
+
});
|
|
55
|
+
return { child, terminate: () => terminateAuthorityGroup(child) };
|
|
56
|
+
}
|
|
57
|
+
const child = spawn(rgPath, commandArgs, {
|
|
58
|
+
cwd,
|
|
59
|
+
env,
|
|
60
|
+
stdio: [stdinFd ?? "ignore", "pipe", "pipe"],
|
|
61
|
+
windowsHide: true,
|
|
62
|
+
}) as ChildProcessByStdio<null, Readable, Readable>;
|
|
63
|
+
return {
|
|
64
|
+
child,
|
|
65
|
+
terminate: () => {
|
|
66
|
+
child.kill("SIGKILL");
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { spawn, type ChildProcessByStdio } from "node:child_process";
|
|
2
|
-
import type { Readable } from "node:stream";
|
|
3
1
|
import { TextDecoder } from "node:util";
|
|
4
2
|
|
|
5
|
-
import { rgPath } from "@vscode/ripgrep";
|
|
6
|
-
|
|
7
3
|
import { LocalSearchError } from "./errors.js";
|
|
4
|
+
import {
|
|
5
|
+
spawnRipgrepProcess,
|
|
6
|
+
type SearchDirectoryAuthority,
|
|
7
|
+
} from "./ripgrep-process.js";
|
|
8
8
|
|
|
9
9
|
const SEARCH_TIMEOUT_MS = 10_000;
|
|
10
10
|
const MAX_PATH_RECORD_BYTES = 64 * 1024;
|
|
@@ -45,16 +45,6 @@ export function escapeRgGlobLiteral(value: string): string {
|
|
|
45
45
|
.join("");
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
function controlledRipgrepEnvironment(): NodeJS.ProcessEnv {
|
|
49
|
-
const environment = { ...process.env };
|
|
50
|
-
for (const name of Object.keys(environment)) {
|
|
51
|
-
if (name.toUpperCase() === "RIPGREP_CONFIG_PATH") {
|
|
52
|
-
delete environment[name];
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
return environment;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
48
|
function decodeUtf8Record(bytes: Buffer, kind: "path" | "result"): string {
|
|
59
49
|
let value: string;
|
|
60
50
|
try {
|
|
@@ -129,6 +119,7 @@ async function runRipgrepBounded<T>(
|
|
|
129
119
|
parser: RipgrepStreamParser<T>,
|
|
130
120
|
signal?: AbortSignal,
|
|
131
121
|
stdinFd?: number,
|
|
122
|
+
directoryAuthority?: SearchDirectoryAuthority,
|
|
132
123
|
): Promise<BoundedRipgrepResult<T>> {
|
|
133
124
|
if (signal?.aborted) {
|
|
134
125
|
throw new LocalSearchError(
|
|
@@ -143,12 +134,12 @@ async function runRipgrepBounded<T>(
|
|
|
143
134
|
let timedOut = false;
|
|
144
135
|
let aborted = false;
|
|
145
136
|
let parserError: unknown;
|
|
146
|
-
const child =
|
|
137
|
+
const { child, terminate } = spawnRipgrepProcess(
|
|
138
|
+
args,
|
|
147
139
|
cwd,
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
}) as ChildProcessByStdio<null, Readable, Readable>;
|
|
140
|
+
stdinFd,
|
|
141
|
+
directoryAuthority,
|
|
142
|
+
);
|
|
152
143
|
|
|
153
144
|
const cleanup = (): void => {
|
|
154
145
|
clearTimeout(timeout);
|
|
@@ -177,17 +168,17 @@ async function runRipgrepBounded<T>(
|
|
|
177
168
|
stoppedAfterExtraResult = true;
|
|
178
169
|
if (mayStopProcess) {
|
|
179
170
|
child.stdout.pause();
|
|
180
|
-
|
|
171
|
+
terminate();
|
|
181
172
|
}
|
|
182
173
|
return false;
|
|
183
174
|
};
|
|
184
175
|
const abortListener = (): void => {
|
|
185
176
|
aborted = true;
|
|
186
|
-
|
|
177
|
+
terminate();
|
|
187
178
|
};
|
|
188
179
|
const timeout = setTimeout(() => {
|
|
189
180
|
timedOut = true;
|
|
190
|
-
|
|
181
|
+
terminate();
|
|
191
182
|
}, SEARCH_TIMEOUT_MS);
|
|
192
183
|
timeout.unref?.();
|
|
193
184
|
|
|
@@ -198,7 +189,7 @@ async function runRipgrepBounded<T>(
|
|
|
198
189
|
} catch (error) {
|
|
199
190
|
parserError = error;
|
|
200
191
|
child.stdout.pause();
|
|
201
|
-
|
|
192
|
+
terminate();
|
|
202
193
|
}
|
|
203
194
|
});
|
|
204
195
|
// Always drain stderr so the child cannot block on its diagnostic pipe.
|
|
@@ -313,6 +304,7 @@ export function runRipgrepPathSearch(
|
|
|
313
304
|
cwd: string,
|
|
314
305
|
maxResults: number,
|
|
315
306
|
signal?: AbortSignal,
|
|
307
|
+
directoryAuthority?: SearchDirectoryAuthority,
|
|
316
308
|
): Promise<BoundedRipgrepResult<string>> {
|
|
317
309
|
return runRipgrepBounded(
|
|
318
310
|
args,
|
|
@@ -324,6 +316,8 @@ export function runRipgrepPathSearch(
|
|
|
324
316
|
parse: (record) => decodeUtf8Record(record, "path"),
|
|
325
317
|
}),
|
|
326
318
|
signal,
|
|
319
|
+
undefined,
|
|
320
|
+
directoryAuthority,
|
|
327
321
|
);
|
|
328
322
|
}
|
|
329
323
|
|
|
@@ -333,6 +327,7 @@ export function runRipgrepContentSearch(
|
|
|
333
327
|
maxResults: number,
|
|
334
328
|
signal?: AbortSignal,
|
|
335
329
|
stdinFd?: number,
|
|
330
|
+
directoryAuthority?: SearchDirectoryAuthority,
|
|
336
331
|
): Promise<BoundedRipgrepResult<ContentMatch>> {
|
|
337
332
|
return runRipgrepBounded(
|
|
338
333
|
args,
|
|
@@ -349,5 +344,6 @@ export function runRipgrepContentSearch(
|
|
|
349
344
|
}),
|
|
350
345
|
signal,
|
|
351
346
|
stdinFd,
|
|
347
|
+
directoryAuthority,
|
|
352
348
|
);
|
|
353
349
|
}
|