@ricsam/r5d-worker 0.0.29 → 0.0.31
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/cjs/main.cjs +252 -123
- package/dist/cjs/package.json +1 -1
- package/dist/cjs/process-tree.cjs +117 -0
- package/dist/mjs/main.mjs +246 -123
- package/dist/mjs/package.json +1 -1
- package/dist/mjs/process-tree.mjs +93 -0
- package/dist/types/main.d.ts +68 -2
- package/dist/types/process-tree.d.ts +16 -0
- package/package.json +1 -1
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
const DEFAULT_TERMINATION_GRACE_MS = 2e3;
|
|
2
|
+
const DEFAULT_FORCE_KILL_WAIT_MS = 2e3;
|
|
3
|
+
const PROCESS_GROUP_POLL_INTERVAL_MS = 25;
|
|
4
|
+
const processTerminations = /* @__PURE__ */ new WeakMap();
|
|
5
|
+
function errorCode(error) {
|
|
6
|
+
if (!error || typeof error !== "object" || !("code" in error)) {
|
|
7
|
+
return void 0;
|
|
8
|
+
}
|
|
9
|
+
return typeof error.code === "string" ? error.code : void 0;
|
|
10
|
+
}
|
|
11
|
+
function delay(ms) {
|
|
12
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
13
|
+
}
|
|
14
|
+
function isPosixProcessGroupRunning(processGroupId) {
|
|
15
|
+
try {
|
|
16
|
+
process.kill(-processGroupId, 0);
|
|
17
|
+
return true;
|
|
18
|
+
} catch (error) {
|
|
19
|
+
if (errorCode(error) === "ESRCH") {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
if (errorCode(error) === "EPERM") {
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
throw error;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
function signalPosixProcessGroup(processGroupId, signal) {
|
|
29
|
+
try {
|
|
30
|
+
process.kill(-processGroupId, signal);
|
|
31
|
+
} catch (error) {
|
|
32
|
+
if (errorCode(error) !== "ESRCH") {
|
|
33
|
+
throw error;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
async function waitForPosixProcessGroupExit(processGroupId, timeoutMs) {
|
|
38
|
+
const deadline = Date.now() + Math.max(0, timeoutMs);
|
|
39
|
+
while (isPosixProcessGroupRunning(processGroupId)) {
|
|
40
|
+
const remainingMs = deadline - Date.now();
|
|
41
|
+
if (remainingMs <= 0) {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
await delay(Math.min(PROCESS_GROUP_POLL_INTERVAL_MS, remainingMs));
|
|
45
|
+
}
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
async function runWindowsTaskkill(pid) {
|
|
49
|
+
const taskkill = Bun.spawn(["taskkill", "/PID", String(pid), "/T", "/F"], {
|
|
50
|
+
stdout: "ignore",
|
|
51
|
+
stderr: "pipe"
|
|
52
|
+
});
|
|
53
|
+
const [exitCode, stderr] = await Promise.all([
|
|
54
|
+
taskkill.exited,
|
|
55
|
+
taskkill.stderr ? new Response(taskkill.stderr).text() : Promise.resolve("")
|
|
56
|
+
]);
|
|
57
|
+
if (exitCode !== 0 && !/not found|no running instance/i.test(stderr)) {
|
|
58
|
+
throw new Error(`Failed to terminate process tree ${pid}: ${stderr.trim() || `taskkill exited ${exitCode}`}`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
async function terminateProcessTreeOnce(subprocess, options) {
|
|
62
|
+
if (!Number.isSafeInteger(subprocess.pid) || subprocess.pid <= 0) {
|
|
63
|
+
throw new Error(`Cannot terminate invalid subprocess PID: ${subprocess.pid}`);
|
|
64
|
+
}
|
|
65
|
+
if (process.platform === "win32") {
|
|
66
|
+
await runWindowsTaskkill(subprocess.pid);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
const graceMs = options.graceMs ?? DEFAULT_TERMINATION_GRACE_MS;
|
|
70
|
+
const forceKillWaitMs = options.forceKillWaitMs ?? DEFAULT_FORCE_KILL_WAIT_MS;
|
|
71
|
+
signalPosixProcessGroup(subprocess.pid, "SIGTERM");
|
|
72
|
+
if (await waitForPosixProcessGroupExit(subprocess.pid, graceMs)) {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
signalPosixProcessGroup(subprocess.pid, "SIGKILL");
|
|
76
|
+
if (!await waitForPosixProcessGroupExit(subprocess.pid, forceKillWaitMs)) {
|
|
77
|
+
throw new Error(`Process group ${subprocess.pid} remained active after SIGKILL`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
function terminateProcessTree(subprocess, options = {}) {
|
|
81
|
+
const pending = processTerminations.get(subprocess);
|
|
82
|
+
if (pending) {
|
|
83
|
+
return pending;
|
|
84
|
+
}
|
|
85
|
+
const termination = terminateProcessTreeOnce(subprocess, options).finally(() => {
|
|
86
|
+
processTerminations.delete(subprocess);
|
|
87
|
+
});
|
|
88
|
+
processTerminations.set(subprocess, termination);
|
|
89
|
+
return termination;
|
|
90
|
+
}
|
|
91
|
+
export {
|
|
92
|
+
terminateProcessTree
|
|
93
|
+
};
|
package/dist/types/main.d.ts
CHANGED
|
@@ -1,4 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
|
+
type WorkerReadFileResult = {
|
|
3
|
+
type: "read";
|
|
4
|
+
kind: "text";
|
|
5
|
+
file: string;
|
|
6
|
+
gitBlobHash?: string;
|
|
7
|
+
content: string;
|
|
8
|
+
totalLines: number;
|
|
9
|
+
startLine: number;
|
|
10
|
+
endLine: number;
|
|
11
|
+
truncated: boolean;
|
|
12
|
+
continuation?: string;
|
|
13
|
+
};
|
|
2
14
|
type WorkerProjectManifestEntry = {
|
|
3
15
|
projectId: string;
|
|
4
16
|
repoSlug: string;
|
|
@@ -28,6 +40,34 @@ type WorkerRepositorySyncResult = {
|
|
|
28
40
|
id: string;
|
|
29
41
|
}>;
|
|
30
42
|
};
|
|
43
|
+
type WorkerGrepResult = {
|
|
44
|
+
type: "grep";
|
|
45
|
+
content: string;
|
|
46
|
+
matchCount: number;
|
|
47
|
+
truncated: boolean;
|
|
48
|
+
};
|
|
49
|
+
type WorkerFindResult = {
|
|
50
|
+
type: "find";
|
|
51
|
+
content: string;
|
|
52
|
+
count: number;
|
|
53
|
+
truncated: boolean;
|
|
54
|
+
};
|
|
55
|
+
type WorkerLsResult = {
|
|
56
|
+
type: "ls";
|
|
57
|
+
path: string;
|
|
58
|
+
content: string;
|
|
59
|
+
count: number;
|
|
60
|
+
truncated: boolean;
|
|
61
|
+
};
|
|
62
|
+
type WorkerViewFileBytesResult = {
|
|
63
|
+
type: "view_file_bytes";
|
|
64
|
+
filePath: string;
|
|
65
|
+
mediaType: "image/png" | "image/jpeg";
|
|
66
|
+
base64: string;
|
|
67
|
+
fileSizeBytes: number;
|
|
68
|
+
width: number;
|
|
69
|
+
height: number;
|
|
70
|
+
};
|
|
31
71
|
export declare function isArtifactEnvPath(filePath: string): boolean;
|
|
32
72
|
export declare function syncSessionArtifacts(input: {
|
|
33
73
|
baseUrl: string;
|
|
@@ -64,11 +104,21 @@ type GitAuth = {
|
|
|
64
104
|
extraHeaderUrl: string;
|
|
65
105
|
header: string;
|
|
66
106
|
};
|
|
67
|
-
|
|
107
|
+
type ResolvedWorkerFilePath = {
|
|
68
108
|
absolutePath: string;
|
|
69
|
-
|
|
109
|
+
displayPath: string;
|
|
70
110
|
repoRelativePath: string;
|
|
111
|
+
scope: "project";
|
|
112
|
+
} | {
|
|
113
|
+
absolutePath: string;
|
|
114
|
+
displayPath: string;
|
|
115
|
+
repoRelativePath: null;
|
|
116
|
+
scope: "host";
|
|
71
117
|
};
|
|
118
|
+
export declare function resolveWorkerFilePath(branchPath: string, inputPath: string): ResolvedWorkerFilePath;
|
|
119
|
+
export declare function resolveProjectFilePath(branchPath: string, inputPath: string): Extract<ResolvedWorkerFilePath, {
|
|
120
|
+
scope: "project";
|
|
121
|
+
}>;
|
|
72
122
|
export declare function ensureVisibleGitCheckout(input: {
|
|
73
123
|
projectRoot: string;
|
|
74
124
|
branchName: string;
|
|
@@ -99,6 +149,22 @@ export declare function syncManifestProjectsFromInternal(input: {
|
|
|
99
149
|
manifests: WorkerProjectManifestEntry[];
|
|
100
150
|
projectIds?: string[];
|
|
101
151
|
}): Promise<WorkerRepositorySyncResult>;
|
|
152
|
+
export declare function readWorkerTextFile(branchPath: string, filePath: string, offset?: number, limit?: number): WorkerReadFileResult;
|
|
153
|
+
export declare function grepWorkerFiles(branchPath: string, input: {
|
|
154
|
+
pattern: string;
|
|
155
|
+
path?: string;
|
|
156
|
+
glob?: string;
|
|
157
|
+
caseSensitive?: boolean;
|
|
158
|
+
limit?: number;
|
|
159
|
+
}): WorkerGrepResult;
|
|
160
|
+
export declare function findWorkerFiles(branchPath: string, input: {
|
|
161
|
+
pattern?: string;
|
|
162
|
+
path?: string;
|
|
163
|
+
entryType?: string;
|
|
164
|
+
limit?: number;
|
|
165
|
+
}): WorkerFindResult;
|
|
166
|
+
export declare function listWorkerDirectory(branchPath: string, inputPath?: string, inputLimit?: number): WorkerLsResult;
|
|
167
|
+
export declare function readWorkerImageFile(branchPath: string, filePath: string): WorkerViewFileBytesResult;
|
|
102
168
|
export declare function resolveHostShell(command?: string, platform?: NodeJS.Platform): {
|
|
103
169
|
file: string;
|
|
104
170
|
args: string[];
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export type KillableSubprocess = {
|
|
2
|
+
readonly pid: number;
|
|
3
|
+
readonly exited: Promise<number>;
|
|
4
|
+
kill(signal?: number | NodeJS.Signals): void;
|
|
5
|
+
};
|
|
6
|
+
type TerminateProcessTreeOptions = {
|
|
7
|
+
graceMs?: number;
|
|
8
|
+
forceKillWaitMs?: number;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Terminates a subprocess spawned with `detached: true` and every descendant
|
|
12
|
+
* that remains in its process group. Concurrent termination requests share one
|
|
13
|
+
* cleanup attempt.
|
|
14
|
+
*/
|
|
15
|
+
export declare function terminateProcessTree(subprocess: KillableSubprocess, options?: TerminateProcessTreeOptions): Promise<void>;
|
|
16
|
+
export {};
|