@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.
@@ -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
+ };
@@ -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
- export declare function resolveProjectFilePath(branchPath: string, virtualPath: string): {
107
+ type ResolvedWorkerFilePath = {
68
108
  absolutePath: string;
69
- virtualPath: string;
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 {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ricsam/r5d-worker",
3
- "version": "0.0.29",
3
+ "version": "0.0.31",
4
4
  "type": "module",
5
5
  "main": "./dist/cjs/main.cjs",
6
6
  "module": "./dist/mjs/main.mjs",