@sudobility/testomniac_runner 0.0.132 → 0.0.134
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/package.json +1 -1
- package/src/index.ts +60 -3
- package/src/orchestrator.ts +13 -2
- package/src/runner-manager.ts +23 -1
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -15,6 +15,10 @@ const config = loadConfig();
|
|
|
15
15
|
const pollIntervalMs = Number(process.env.SCAN_POLL_INTERVAL_MS ?? 10_000);
|
|
16
16
|
const maxConcurrentRunners = config.maxConcurrentRunners;
|
|
17
17
|
const runnerManager = new RunnerManager(pollIntervalMs, maxConcurrentRunners);
|
|
18
|
+
const runnerInstanceId =
|
|
19
|
+
process.env.TESTOMNIAC_RUNNER_INSTANCE_ID ??
|
|
20
|
+
process.env.TESTOMNIAC_RUNNER_PROCESS_INSTANCE_ID;
|
|
21
|
+
const runnerInstanceName = process.env.TESTOMNIAC_RUNNER_INSTANCE_NAME;
|
|
18
22
|
|
|
19
23
|
const app = new Hono();
|
|
20
24
|
|
|
@@ -66,8 +70,8 @@ if (import.meta.main) {
|
|
|
66
70
|
baseUrl,
|
|
67
71
|
sizeClass,
|
|
68
72
|
scanMode,
|
|
69
|
-
runnerInstanceId: crypto.randomUUID(),
|
|
70
|
-
runnerInstanceName: "mcp-runner",
|
|
73
|
+
runnerInstanceId: runnerInstanceId ?? crypto.randomUUID(),
|
|
74
|
+
runnerInstanceName: runnerInstanceName ?? "mcp-runner",
|
|
71
75
|
});
|
|
72
76
|
logger.info({ scanId }, "one-shot run completed");
|
|
73
77
|
process.exit(0);
|
|
@@ -94,12 +98,65 @@ if (import.meta.main) {
|
|
|
94
98
|
}
|
|
95
99
|
} else {
|
|
96
100
|
// Default: polling mode
|
|
97
|
-
setInterval(() => {
|
|
101
|
+
const pollInterval = setInterval(() => {
|
|
98
102
|
void runnerManager.tick();
|
|
99
103
|
}, pollIntervalMs);
|
|
100
104
|
|
|
101
105
|
void runnerManager.tick();
|
|
102
106
|
|
|
107
|
+
// IPC listener: parent process can send JSON messages on stdin
|
|
108
|
+
if (process.stdin.isTTY === false || process.stdin.readable) {
|
|
109
|
+
process.stdin.setEncoding("utf-8");
|
|
110
|
+
let ipcBuffer = "";
|
|
111
|
+
process.stdin.on("data", (chunk: string) => {
|
|
112
|
+
ipcBuffer += chunk;
|
|
113
|
+
let newlineIdx;
|
|
114
|
+
while ((newlineIdx = ipcBuffer.indexOf("\n")) !== -1) {
|
|
115
|
+
const line = ipcBuffer.slice(0, newlineIdx).trim();
|
|
116
|
+
ipcBuffer = ipcBuffer.slice(newlineIdx + 1);
|
|
117
|
+
if (!line) continue;
|
|
118
|
+
try {
|
|
119
|
+
const msg = JSON.parse(line);
|
|
120
|
+
if (msg.type === "stop_run" && typeof msg.runId === "number") {
|
|
121
|
+
logger.info({ runId: msg.runId }, "IPC: stop_run received");
|
|
122
|
+
const stopped = runnerManager.stopRun(msg.runId);
|
|
123
|
+
logger.info(
|
|
124
|
+
{ runId: msg.runId, stopped },
|
|
125
|
+
"IPC: stop_run result"
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
} catch {
|
|
129
|
+
logger.warn({ line }, "IPC: ignoring malformed message");
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
process.stdin.on("end", () => {
|
|
134
|
+
logger.info("IPC: stdin closed");
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Graceful shutdown
|
|
139
|
+
let shuttingDown = false;
|
|
140
|
+
for (const sig of ["SIGTERM", "SIGINT"] as const) {
|
|
141
|
+
process.on(sig, () => {
|
|
142
|
+
if (shuttingDown) return;
|
|
143
|
+
shuttingDown = true;
|
|
144
|
+
logger.info({ signal: sig }, "graceful shutdown initiated");
|
|
145
|
+
clearInterval(pollInterval);
|
|
146
|
+
runnerManager.stopAllRuns();
|
|
147
|
+
const check = setInterval(() => {
|
|
148
|
+
if (runnerManager.getActiveRunCount() === 0) {
|
|
149
|
+
clearInterval(check);
|
|
150
|
+
process.exit(0);
|
|
151
|
+
}
|
|
152
|
+
}, 500);
|
|
153
|
+
setTimeout(() => {
|
|
154
|
+
logger.warn("graceful shutdown timed out, forcing exit");
|
|
155
|
+
process.exit(1);
|
|
156
|
+
}, 30_000);
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
|
|
103
160
|
logger.warn(
|
|
104
161
|
{ port, pollIntervalMs, maxConcurrentRunners },
|
|
105
162
|
"starting scanner service"
|
package/src/orchestrator.ts
CHANGED
|
@@ -41,6 +41,7 @@ export interface RunOptions {
|
|
|
41
41
|
entityCredentialId?: number;
|
|
42
42
|
quickScan?: boolean;
|
|
43
43
|
scanMode?: "full" | "partial" | "minimum";
|
|
44
|
+
signal?: AbortSignal;
|
|
44
45
|
}
|
|
45
46
|
|
|
46
47
|
export async function runFullScan(options: RunOptions): Promise<void> {
|
|
@@ -121,6 +122,7 @@ export async function runFullScan(options: RunOptions): Promise<void> {
|
|
|
121
122
|
credentials: options.credentials,
|
|
122
123
|
quickScan: options.quickScan,
|
|
123
124
|
scanMode: options.scanMode,
|
|
125
|
+
signal: options.signal,
|
|
124
126
|
},
|
|
125
127
|
api,
|
|
126
128
|
expertises,
|
|
@@ -163,6 +165,8 @@ export interface SequenceRunOptions {
|
|
|
163
165
|
sequenceRunId: number;
|
|
164
166
|
runnerId: number;
|
|
165
167
|
sizeClass?: string;
|
|
168
|
+
runnerInstanceId?: string;
|
|
169
|
+
runnerInstanceName?: string;
|
|
166
170
|
}
|
|
167
171
|
|
|
168
172
|
export async function runSequenceScan(
|
|
@@ -211,8 +215,15 @@ export async function runSequenceScan(
|
|
|
211
215
|
sequenceRunId: options.sequenceRunId,
|
|
212
216
|
runnerId: options.runnerId,
|
|
213
217
|
sizeClass,
|
|
214
|
-
runnerInstanceId:
|
|
215
|
-
|
|
218
|
+
runnerInstanceId:
|
|
219
|
+
options.runnerInstanceId ??
|
|
220
|
+
process.env.TESTOMNIAC_RUNNER_INSTANCE_ID ??
|
|
221
|
+
process.env.TESTOMNIAC_RUNNER_PROCESS_INSTANCE_ID ??
|
|
222
|
+
crypto.randomUUID(),
|
|
223
|
+
runnerInstanceName:
|
|
224
|
+
options.runnerInstanceName ??
|
|
225
|
+
process.env.TESTOMNIAC_RUNNER_INSTANCE_NAME ??
|
|
226
|
+
"mcp-runner",
|
|
216
227
|
},
|
|
217
228
|
api,
|
|
218
229
|
expertises,
|
package/src/runner-manager.ts
CHANGED
|
@@ -11,10 +11,12 @@ const logger = pino({
|
|
|
11
11
|
type ActiveRun = {
|
|
12
12
|
runId: number;
|
|
13
13
|
startedAtMs: number;
|
|
14
|
+
abortController: AbortController;
|
|
14
15
|
};
|
|
15
16
|
|
|
16
17
|
export class RunnerManager {
|
|
17
|
-
private readonly processInstanceId =
|
|
18
|
+
private readonly processInstanceId =
|
|
19
|
+
process.env.TESTOMNIAC_RUNNER_PROCESS_INSTANCE_ID ?? crypto.randomUUID();
|
|
18
20
|
private readonly activeRuns = new Map<number, ActiveRun>();
|
|
19
21
|
private tickInFlight = false;
|
|
20
22
|
|
|
@@ -27,6 +29,21 @@ export class RunnerManager {
|
|
|
27
29
|
return this.activeRuns.size;
|
|
28
30
|
}
|
|
29
31
|
|
|
32
|
+
stopRun(runId: number): boolean {
|
|
33
|
+
const run = this.activeRuns.get(runId);
|
|
34
|
+
if (!run) return false;
|
|
35
|
+
logger.info({ runId }, "stopping run via abort signal");
|
|
36
|
+
run.abortController.abort();
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
stopAllRuns(): void {
|
|
41
|
+
for (const [runId, run] of this.activeRuns) {
|
|
42
|
+
logger.info({ runId }, "stopping run (shutdown)");
|
|
43
|
+
run.abortController.abort();
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
30
47
|
async tick(): Promise<void> {
|
|
31
48
|
if (this.tickInFlight) return;
|
|
32
49
|
|
|
@@ -106,9 +123,11 @@ export class RunnerManager {
|
|
|
106
123
|
continue;
|
|
107
124
|
}
|
|
108
125
|
|
|
126
|
+
const abortController = new AbortController();
|
|
109
127
|
this.activeRuns.set(pendingRun.id, {
|
|
110
128
|
runId: pendingRun.id,
|
|
111
129
|
startedAtMs: Date.now(),
|
|
130
|
+
abortController,
|
|
112
131
|
});
|
|
113
132
|
|
|
114
133
|
logger.info(
|
|
@@ -131,6 +150,7 @@ export class RunnerManager {
|
|
|
131
150
|
runnerInstanceName,
|
|
132
151
|
quickScan: pendingRun.quickScan ?? false,
|
|
133
152
|
scanMode: readScanMode(pendingRun),
|
|
153
|
+
signal: abortController.signal,
|
|
134
154
|
});
|
|
135
155
|
}
|
|
136
156
|
} catch (err) {
|
|
@@ -150,6 +170,7 @@ export class RunnerManager {
|
|
|
150
170
|
runnerInstanceName: string;
|
|
151
171
|
quickScan: boolean;
|
|
152
172
|
scanMode?: "full" | "partial" | "minimum";
|
|
173
|
+
signal: AbortSignal;
|
|
153
174
|
}): Promise<void> {
|
|
154
175
|
const config = loadConfig();
|
|
155
176
|
const api = getApiClient(
|
|
@@ -169,6 +190,7 @@ export class RunnerManager {
|
|
|
169
190
|
runnerInstanceName: params.runnerInstanceName,
|
|
170
191
|
quickScan: params.quickScan,
|
|
171
192
|
scanMode: params.scanMode,
|
|
193
|
+
signal: params.signal,
|
|
172
194
|
});
|
|
173
195
|
|
|
174
196
|
logger.info({ runId: params.runId }, "run completed successfully");
|