@northflare/runner 0.0.12 → 0.0.13
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 +2 -3
- package/coverage/base.css +0 -224
- package/coverage/block-navigation.js +0 -87
- package/coverage/coverage-final.json +0 -12
- package/coverage/favicon.png +0 -0
- package/coverage/index.html +0 -176
- package/coverage/lib/index.html +0 -116
- package/coverage/lib/preload-script.js.html +0 -964
- package/coverage/prettify.css +0 -1
- package/coverage/prettify.js +0 -2
- package/coverage/sort-arrow-sprite.png +0 -0
- package/coverage/sorter.js +0 -196
- package/coverage/src/collections/index.html +0 -116
- package/coverage/src/collections/runner-messages.ts.html +0 -312
- package/coverage/src/components/claude-manager.ts.html +0 -1290
- package/coverage/src/components/index.html +0 -146
- package/coverage/src/components/message-handler.ts.html +0 -730
- package/coverage/src/components/repository-manager.ts.html +0 -841
- package/coverage/src/index.html +0 -131
- package/coverage/src/index.ts.html +0 -448
- package/coverage/src/runner.ts.html +0 -1239
- package/coverage/src/utils/config.ts.html +0 -780
- package/coverage/src/utils/console.ts.html +0 -121
- package/coverage/src/utils/index.html +0 -161
- package/coverage/src/utils/logger.ts.html +0 -475
- package/coverage/src/utils/status-line.ts.html +0 -445
- package/exceptions.log +0 -24
- package/lib/codex-sdk/src/codex.ts +0 -38
- package/lib/codex-sdk/src/codexOptions.ts +0 -10
- package/lib/codex-sdk/src/events.ts +0 -80
- package/lib/codex-sdk/src/exec.ts +0 -336
- package/lib/codex-sdk/src/index.ts +0 -39
- package/lib/codex-sdk/src/items.ts +0 -127
- package/lib/codex-sdk/src/outputSchemaFile.ts +0 -40
- package/lib/codex-sdk/src/thread.ts +0 -155
- package/lib/codex-sdk/src/threadOptions.ts +0 -18
- package/lib/codex-sdk/src/turnOptions.ts +0 -6
- package/lib/codex-sdk/tests/abort.test.ts +0 -165
- package/lib/codex-sdk/tests/codexExecSpy.ts +0 -37
- package/lib/codex-sdk/tests/responsesProxy.ts +0 -225
- package/lib/codex-sdk/tests/run.test.ts +0 -687
- package/lib/codex-sdk/tests/runStreamed.test.ts +0 -211
- package/lib/codex-sdk/tsconfig.json +0 -24
- package/rejections.log +0 -68
- package/runner.log +0 -488
- package/src/components/claude-sdk-manager.ts +0 -1425
- package/src/components/codex-sdk-manager.ts +0 -1358
- package/src/components/enhanced-repository-manager.ts +0 -823
- package/src/components/message-handler-sse.ts +0 -1097
- package/src/components/repository-manager.ts +0 -337
- package/src/index.ts +0 -168
- package/src/runner-sse.ts +0 -917
- package/src/services/RunnerAPIClient.ts +0 -175
- package/src/services/SSEClient.ts +0 -258
- package/src/types/claude.ts +0 -66
- package/src/types/computer-name.d.ts +0 -4
- package/src/types/index.ts +0 -64
- package/src/types/messages.ts +0 -39
- package/src/types/runner-interface.ts +0 -36
- package/src/utils/StateManager.ts +0 -187
- package/src/utils/config.ts +0 -327
- package/src/utils/console.ts +0 -15
- package/src/utils/debug.ts +0 -18
- package/src/utils/expand-env.ts +0 -22
- package/src/utils/logger.ts +0 -134
- package/src/utils/model.ts +0 -29
- package/src/utils/status-line.ts +0 -122
- package/src/utils/tool-response-sanitizer.ts +0 -160
- package/test-debug.sh +0 -26
- package/tests/retry-strategies.test.ts +0 -410
- package/tests/sdk-integration.test.ts +0 -329
- package/tests/sdk-streaming.test.ts +0 -1180
- package/tests/setup.ts +0 -5
- package/tests/test-claude-manager.ts +0 -120
- package/tests/tool-response-sanitizer.test.ts +0 -63
- package/tsconfig.json +0 -36
- package/vitest.config.ts +0 -27
|
@@ -1,337 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* RepositoryManager - Manages Git repository checkouts and workspace isolation
|
|
3
|
-
*
|
|
4
|
-
* This component handles all Git operations for the runner, including:
|
|
5
|
-
* - Cloning repositories with authentication
|
|
6
|
-
* - Updating repositories to latest state
|
|
7
|
-
* - Workspace isolation via separate directories
|
|
8
|
-
* - Cleanup of old repositories
|
|
9
|
-
* - Error handling for Git operations
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
import { IRunnerApp } from "../types/runner-interface";
|
|
13
|
-
import { WorkspaceRepository } from "../types";
|
|
14
|
-
import path from "path";
|
|
15
|
-
import fs from "fs/promises";
|
|
16
|
-
import { mkdirSync } from "fs";
|
|
17
|
-
import simpleGit, { SimpleGit, GitError } from "simple-git";
|
|
18
|
-
import { console } from "../utils/console";
|
|
19
|
-
|
|
20
|
-
export class RepositoryManager {
|
|
21
|
-
private repositories: Map<string, WorkspaceRepository>;
|
|
22
|
-
protected repoBasePath: string;
|
|
23
|
-
private runner: IRunnerApp;
|
|
24
|
-
private git: SimpleGit;
|
|
25
|
-
|
|
26
|
-
constructor(runner: IRunnerApp) {
|
|
27
|
-
this.runner = runner;
|
|
28
|
-
this.repositories = new Map();
|
|
29
|
-
|
|
30
|
-
// Use workspaceDir from config if available, otherwise use environment variable
|
|
31
|
-
this.repoBasePath =
|
|
32
|
-
runner.config_.workspaceDir ||
|
|
33
|
-
process.env["NORTHFLARE_WORKSPACE_DIR"] ||
|
|
34
|
-
"/tmp/northflare-workspace";
|
|
35
|
-
if (!this.repoBasePath) {
|
|
36
|
-
throw new Error(
|
|
37
|
-
"NORTHFLARE_WORKSPACE_DIR environment variable must be set"
|
|
38
|
-
);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
// Ensure workspace directory exists before initializing simple-git
|
|
42
|
-
try {
|
|
43
|
-
mkdirSync(this.repoBasePath, { recursive: true });
|
|
44
|
-
} catch (error) {
|
|
45
|
-
console.error(
|
|
46
|
-
`Failed to create workspace directory ${this.repoBasePath}:`,
|
|
47
|
-
error
|
|
48
|
-
);
|
|
49
|
-
throw error;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// Initialize simple-git
|
|
53
|
-
this.git = simpleGit({
|
|
54
|
-
baseDir: this.repoBasePath,
|
|
55
|
-
binary: "git",
|
|
56
|
-
maxConcurrentProcesses: 3,
|
|
57
|
-
config: ["user.name=Northflare", "user.email=runner@northflare.ai"],
|
|
58
|
-
});
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
async checkoutRepository(
|
|
62
|
-
workspaceId: string,
|
|
63
|
-
repoUrl: string,
|
|
64
|
-
branch: string,
|
|
65
|
-
githubToken?: string
|
|
66
|
-
): Promise<string> {
|
|
67
|
-
const existing = this.repositories.get(workspaceId);
|
|
68
|
-
if (
|
|
69
|
-
existing &&
|
|
70
|
-
existing.repoUrl === repoUrl &&
|
|
71
|
-
existing.branch === branch
|
|
72
|
-
) {
|
|
73
|
-
await this.updateRepository(existing, githubToken);
|
|
74
|
-
return existing.localPath;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
const localPath = path.join(this.repoBasePath, workspaceId);
|
|
78
|
-
|
|
79
|
-
// Clean up existing directory if it exists
|
|
80
|
-
await this.cleanupDirectory(localPath);
|
|
81
|
-
|
|
82
|
-
// Clone with authentication
|
|
83
|
-
const authUrl = this.getAuthenticatedUrl(repoUrl, githubToken);
|
|
84
|
-
|
|
85
|
-
try {
|
|
86
|
-
console.log(`Cloning repository for workspace ${workspaceId}...`);
|
|
87
|
-
await this.executeGit(["clone", "--branch", branch, authUrl, localPath]);
|
|
88
|
-
|
|
89
|
-
// Configure the repository
|
|
90
|
-
const repoGit = simpleGit(localPath);
|
|
91
|
-
await repoGit.addConfig("credential.helper", "store");
|
|
92
|
-
|
|
93
|
-
const repo: WorkspaceRepository = {
|
|
94
|
-
workspaceId,
|
|
95
|
-
repoUrl,
|
|
96
|
-
branch,
|
|
97
|
-
localPath,
|
|
98
|
-
lastAccessed: new Date(),
|
|
99
|
-
};
|
|
100
|
-
|
|
101
|
-
this.repositories.set(workspaceId, repo);
|
|
102
|
-
console.log(
|
|
103
|
-
`Successfully cloned repository for workspace ${workspaceId}`
|
|
104
|
-
);
|
|
105
|
-
return localPath;
|
|
106
|
-
} catch (error) {
|
|
107
|
-
console.error(
|
|
108
|
-
`Failed to clone repository for workspace ${workspaceId}:`,
|
|
109
|
-
error
|
|
110
|
-
);
|
|
111
|
-
await this.cleanupDirectory(localPath);
|
|
112
|
-
throw error;
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* Checkout a local repository (bypasses Git operations)
|
|
118
|
-
* Used for local workspaces that point to existing directories on the filesystem
|
|
119
|
-
*/
|
|
120
|
-
async checkoutLocalRepository(
|
|
121
|
-
workspaceId: string,
|
|
122
|
-
localRepoPath: string
|
|
123
|
-
): Promise<string> {
|
|
124
|
-
console.log(
|
|
125
|
-
`Using local repository for workspace ${workspaceId}: ${localRepoPath}`
|
|
126
|
-
);
|
|
127
|
-
|
|
128
|
-
// Verify the path exists
|
|
129
|
-
try {
|
|
130
|
-
const stats = await fs.stat(localRepoPath);
|
|
131
|
-
if (!stats.isDirectory()) {
|
|
132
|
-
throw new Error(`Path is not a directory: ${localRepoPath}`);
|
|
133
|
-
}
|
|
134
|
-
} catch (error) {
|
|
135
|
-
console.error(`Local repository path not found: ${localRepoPath}`);
|
|
136
|
-
throw new Error(`Local repository path not found: ${localRepoPath}`);
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
// Store in repositories map for consistency
|
|
140
|
-
const repo: WorkspaceRepository = {
|
|
141
|
-
workspaceId,
|
|
142
|
-
repoUrl: `file://${localRepoPath}`, // Use file:// protocol to indicate local
|
|
143
|
-
branch: "local", // Local repos don't have branches in the same sense
|
|
144
|
-
localPath: localRepoPath,
|
|
145
|
-
lastAccessed: new Date(),
|
|
146
|
-
};
|
|
147
|
-
|
|
148
|
-
this.repositories.set(workspaceId, repo);
|
|
149
|
-
console.log(
|
|
150
|
-
`Successfully set up local repository for workspace ${workspaceId}`
|
|
151
|
-
);
|
|
152
|
-
return localRepoPath;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
async getWorkspacePath(workspaceId: string): Promise<string> {
|
|
156
|
-
const localPath = path.join(this.repoBasePath, workspaceId);
|
|
157
|
-
await this.ensureDirectory(localPath);
|
|
158
|
-
return localPath;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
private async updateRepository(
|
|
162
|
-
repo: WorkspaceRepository,
|
|
163
|
-
githubToken?: string
|
|
164
|
-
): Promise<void> {
|
|
165
|
-
repo.lastAccessed = new Date();
|
|
166
|
-
|
|
167
|
-
// Skip Git operations for local repositories
|
|
168
|
-
if (repo.repoUrl.startsWith("file://")) {
|
|
169
|
-
console.log(
|
|
170
|
-
`Local repository for workspace ${repo.workspaceId} - no update needed`
|
|
171
|
-
);
|
|
172
|
-
return;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
const repoGit = simpleGit(repo.localPath);
|
|
176
|
-
|
|
177
|
-
try {
|
|
178
|
-
console.log(`Updating repository for workspace ${repo.workspaceId}...`);
|
|
179
|
-
|
|
180
|
-
// Update remote URL if token has changed
|
|
181
|
-
if (githubToken) {
|
|
182
|
-
const authUrl = this.getAuthenticatedUrl(repo.repoUrl, githubToken);
|
|
183
|
-
await repoGit.remote(["set-url", "origin", authUrl]);
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
// Fetch latest changes
|
|
187
|
-
await repoGit.fetch("origin");
|
|
188
|
-
|
|
189
|
-
// Reset to latest branch state
|
|
190
|
-
await repoGit.reset(["--hard", `origin/${repo.branch}`]);
|
|
191
|
-
|
|
192
|
-
// Clean untracked files
|
|
193
|
-
await repoGit.clean("f", ["-d"]);
|
|
194
|
-
|
|
195
|
-
console.log(
|
|
196
|
-
`Successfully updated repository for workspace ${repo.workspaceId}`
|
|
197
|
-
);
|
|
198
|
-
} catch (error) {
|
|
199
|
-
console.error(
|
|
200
|
-
`Failed to update repository for workspace ${repo.workspaceId}:`,
|
|
201
|
-
error
|
|
202
|
-
);
|
|
203
|
-
throw error;
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
async cleanupRepository(workspaceId: string): Promise<void> {
|
|
208
|
-
const repo = this.repositories.get(workspaceId);
|
|
209
|
-
if (!repo) return;
|
|
210
|
-
|
|
211
|
-
console.log(`Cleaning up repository for workspace ${workspaceId}...`);
|
|
212
|
-
|
|
213
|
-
// For local repositories, only remove from tracking, don't delete the directory
|
|
214
|
-
if (repo.repoUrl.startsWith("file://")) {
|
|
215
|
-
console.log(`Local repository - removing from tracking only`);
|
|
216
|
-
this.repositories.delete(workspaceId);
|
|
217
|
-
return;
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
// Remove local directory for cloned repositories
|
|
221
|
-
await this.cleanupDirectory(repo.localPath);
|
|
222
|
-
|
|
223
|
-
this.repositories.delete(workspaceId);
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
async cleanupOldRepositories(
|
|
227
|
-
maxAge: number = 7 * 24 * 60 * 60 * 1000
|
|
228
|
-
): Promise<void> {
|
|
229
|
-
const now = new Date();
|
|
230
|
-
const toCleanup: string[] = [];
|
|
231
|
-
|
|
232
|
-
for (const [workspaceId, repo] of this.repositories.entries()) {
|
|
233
|
-
const age = now.getTime() - repo.lastAccessed.getTime();
|
|
234
|
-
if (age > maxAge) {
|
|
235
|
-
toCleanup.push(workspaceId);
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
for (const workspaceId of toCleanup) {
|
|
240
|
-
await this.cleanupRepository(workspaceId);
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
console.log(`Cleaned up ${toCleanup.length} old repositories`);
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
protected async executeGit(args: string[], cwd?: string): Promise<void> {
|
|
247
|
-
const git = cwd ? simpleGit(cwd) : this.git;
|
|
248
|
-
try {
|
|
249
|
-
await git.raw(args);
|
|
250
|
-
} catch (error) {
|
|
251
|
-
if (error instanceof GitError) {
|
|
252
|
-
throw new Error(`Git operation failed: ${error.message}`);
|
|
253
|
-
}
|
|
254
|
-
throw error;
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
protected getAuthenticatedUrl(repoUrl: string, githubToken?: string): string {
|
|
259
|
-
if (!githubToken) {
|
|
260
|
-
return repoUrl;
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
// Handle different URL formats
|
|
264
|
-
if (repoUrl.startsWith("https://")) {
|
|
265
|
-
// HTTPS URL
|
|
266
|
-
return repoUrl.replace("https://", `https://${githubToken}@`);
|
|
267
|
-
} else if (repoUrl.startsWith("git@")) {
|
|
268
|
-
// SSH URL - convert to HTTPS with token
|
|
269
|
-
const httpsUrl = repoUrl
|
|
270
|
-
.replace("git@github.com:", "https://github.com/")
|
|
271
|
-
.replace(".git", "");
|
|
272
|
-
return `https://${githubToken}@${httpsUrl.replace("https://", "")}.git`;
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
return repoUrl;
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
protected async ensureDirectory(dirPath: string): Promise<void> {
|
|
279
|
-
try {
|
|
280
|
-
await fs.mkdir(dirPath, { recursive: true });
|
|
281
|
-
} catch (error) {
|
|
282
|
-
console.error(`Failed to create directory ${dirPath}:`, error);
|
|
283
|
-
throw error;
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
protected async cleanupDirectory(dirPath: string): Promise<void> {
|
|
288
|
-
try {
|
|
289
|
-
const stats = await fs.stat(dirPath).catch(() => null);
|
|
290
|
-
if (stats) {
|
|
291
|
-
await fs.rm(dirPath, { recursive: true, force: true });
|
|
292
|
-
}
|
|
293
|
-
} catch (error) {
|
|
294
|
-
console.error(`Failed to cleanup directory ${dirPath}:`, error);
|
|
295
|
-
// Don't throw - cleanup errors are not critical
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
/**
|
|
300
|
-
* Get statistics about managed repositories
|
|
301
|
-
*/
|
|
302
|
-
getRepositoryStats(): {
|
|
303
|
-
total: number;
|
|
304
|
-
workspaceIds: string[];
|
|
305
|
-
oldestAccess: Date | null;
|
|
306
|
-
newestAccess: Date | null;
|
|
307
|
-
} {
|
|
308
|
-
const repos = Array.from(this.repositories.values());
|
|
309
|
-
|
|
310
|
-
return {
|
|
311
|
-
total: repos.length,
|
|
312
|
-
workspaceIds: Array.from(this.repositories.keys()),
|
|
313
|
-
oldestAccess:
|
|
314
|
-
repos.length > 0
|
|
315
|
-
? new Date(Math.min(...repos.map((r) => r.lastAccessed.getTime())))
|
|
316
|
-
: null,
|
|
317
|
-
newestAccess:
|
|
318
|
-
repos.length > 0
|
|
319
|
-
? new Date(Math.max(...repos.map((r) => r.lastAccessed.getTime())))
|
|
320
|
-
: null,
|
|
321
|
-
};
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
/**
|
|
325
|
-
* Check if a workspace has an existing repository
|
|
326
|
-
*/
|
|
327
|
-
hasRepository(workspaceId: string): boolean {
|
|
328
|
-
return this.repositories.has(workspaceId);
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
/**
|
|
332
|
-
* Get repository info for a workspace
|
|
333
|
-
*/
|
|
334
|
-
getRepository(workspaceId: string): WorkspaceRepository | undefined {
|
|
335
|
-
return this.repositories.get(workspaceId);
|
|
336
|
-
}
|
|
337
|
-
}
|
package/src/index.ts
DELETED
|
@@ -1,168 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Main entry point for the Northflare Runner App
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { RunnerApp } from "./runner-sse";
|
|
6
|
-
import { ConfigManager } from "./utils/config";
|
|
7
|
-
import { logger, configureFileLogging } from "./utils/logger";
|
|
8
|
-
import { isRunnerDebugEnabled } from "./utils/debug";
|
|
9
|
-
import path from "path";
|
|
10
|
-
import fs from "fs/promises";
|
|
11
|
-
|
|
12
|
-
let runner: RunnerApp | null = null;
|
|
13
|
-
|
|
14
|
-
async function main() {
|
|
15
|
-
try {
|
|
16
|
-
logger.info("Starting Northflare Runner...");
|
|
17
|
-
const debugEnabled = isRunnerDebugEnabled();
|
|
18
|
-
|
|
19
|
-
// Load configuration (args already parsed by CLI)
|
|
20
|
-
let configPath = process.argv[2]; // This is set by the CLI if --config was provided
|
|
21
|
-
|
|
22
|
-
// If no config path provided, check for default location
|
|
23
|
-
if (!configPath) {
|
|
24
|
-
try {
|
|
25
|
-
const envPaths = require("env-paths").default || require("env-paths");
|
|
26
|
-
const paths = envPaths("northflare-runner", { suffix: "" });
|
|
27
|
-
const defaultConfigPath = path.join(paths.config, "config.json");
|
|
28
|
-
|
|
29
|
-
// Check if default config exists
|
|
30
|
-
if (
|
|
31
|
-
await fs
|
|
32
|
-
.access(defaultConfigPath)
|
|
33
|
-
.then(() => true)
|
|
34
|
-
.catch(() => false)
|
|
35
|
-
) {
|
|
36
|
-
configPath = defaultConfigPath;
|
|
37
|
-
}
|
|
38
|
-
} catch (error) {
|
|
39
|
-
// env-paths not available or error accessing default location
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
const config = await ConfigManager.loadConfig(configPath);
|
|
44
|
-
|
|
45
|
-
// Set up file logging
|
|
46
|
-
const logDir = path.join(config.dataDir, "logs");
|
|
47
|
-
await fs.mkdir(logDir, { recursive: true });
|
|
48
|
-
configureFileLogging(logDir);
|
|
49
|
-
|
|
50
|
-
logger.info("Configuration loaded", {
|
|
51
|
-
orchestratorUrl: config.orchestratorUrl,
|
|
52
|
-
retryStrategy: config.retryStrategy,
|
|
53
|
-
retryIntervalSecs: config.retryIntervalSecs,
|
|
54
|
-
retryDurationSecs: config.retryDurationSecs,
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
// Additional debug logging
|
|
58
|
-
if (debugEnabled) {
|
|
59
|
-
logger.debug("Debug mode enabled - verbose logging active", {
|
|
60
|
-
dataDir: config.dataDir,
|
|
61
|
-
heartbeatInterval: config.heartbeatInterval,
|
|
62
|
-
nodeVersion: process.version,
|
|
63
|
-
platform: process.platform,
|
|
64
|
-
pid: process.pid,
|
|
65
|
-
});
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// Create and start runner - pass the resolved config path
|
|
69
|
-
runner = new RunnerApp(config, configPath);
|
|
70
|
-
await runner.start();
|
|
71
|
-
|
|
72
|
-
// Set up graceful shutdown handlers
|
|
73
|
-
setupShutdownHandlers();
|
|
74
|
-
|
|
75
|
-
logger.info("Northflare Runner is running", {
|
|
76
|
-
runnerId: runner.getRunnerId() || "pending registration",
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
// Log additional details in debug mode
|
|
80
|
-
if (debugEnabled && runner) {
|
|
81
|
-
logger.debug("Runner started with full details", {
|
|
82
|
-
runnerId: runner.getRunnerId(),
|
|
83
|
-
runnerUid: runner.getRunnerUid(),
|
|
84
|
-
lastProcessedAt: runner.getLastProcessedAt()?.toISOString() || "null",
|
|
85
|
-
isActiveRunner: runner.getIsActiveRunner(),
|
|
86
|
-
uptime: process.uptime(),
|
|
87
|
-
memoryUsage: process.memoryUsage(),
|
|
88
|
-
});
|
|
89
|
-
}
|
|
90
|
-
} catch (error) {
|
|
91
|
-
logger.error("Failed to start runner:", error);
|
|
92
|
-
process.exit(1);
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
function setupShutdownHandlers() {
|
|
97
|
-
let isShuttingDown = false;
|
|
98
|
-
|
|
99
|
-
// Handle various shutdown signals
|
|
100
|
-
const signals: NodeJS.Signals[] = ["SIGTERM", "SIGINT", "SIGHUP"];
|
|
101
|
-
|
|
102
|
-
for (const signal of signals) {
|
|
103
|
-
process.on(signal, async () => {
|
|
104
|
-
if (isShuttingDown) {
|
|
105
|
-
logger.info(`Already shutting down, ignoring ${signal}`);
|
|
106
|
-
return;
|
|
107
|
-
}
|
|
108
|
-
isShuttingDown = true;
|
|
109
|
-
logger.info(`Received ${signal}, shutting down gracefully...`);
|
|
110
|
-
await shutdown();
|
|
111
|
-
});
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
// Handle uncaught exceptions
|
|
115
|
-
process.on("uncaughtException", async (error) => {
|
|
116
|
-
logger.error("Uncaught exception:", error);
|
|
117
|
-
if (!isShuttingDown) {
|
|
118
|
-
isShuttingDown = true;
|
|
119
|
-
await shutdown(1);
|
|
120
|
-
}
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
// Handle unhandled promise rejections
|
|
124
|
-
process.on("unhandledRejection", async (reason, promise) => {
|
|
125
|
-
logger.error("Unhandled rejection", { promise, reason });
|
|
126
|
-
if (!isShuttingDown) {
|
|
127
|
-
isShuttingDown = true;
|
|
128
|
-
await shutdown(1);
|
|
129
|
-
}
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
// Handle process warnings
|
|
133
|
-
process.on("warning", (warning) => {
|
|
134
|
-
logger.warn("Process warning", {
|
|
135
|
-
name: warning.name,
|
|
136
|
-
message: warning.message,
|
|
137
|
-
stack: warning.stack,
|
|
138
|
-
});
|
|
139
|
-
});
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
async function shutdown(exitCode: number = 0) {
|
|
143
|
-
if (runner) {
|
|
144
|
-
try {
|
|
145
|
-
await runner.stop();
|
|
146
|
-
logger.info("Runner stopped successfully");
|
|
147
|
-
} catch (error) {
|
|
148
|
-
logger.error("Error during shutdown:", error);
|
|
149
|
-
exitCode = 1;
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
process.exit(exitCode);
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
// Start the runner if this is the main module
|
|
157
|
-
if (require.main === module) {
|
|
158
|
-
main().catch((error) => {
|
|
159
|
-
logger.error("Fatal error:", error);
|
|
160
|
-
process.exit(1);
|
|
161
|
-
});
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
// Export for programmatic usage
|
|
165
|
-
export { RunnerApp } from "./runner-sse";
|
|
166
|
-
export { ConfigManager } from "./utils/config";
|
|
167
|
-
export * from "./types";
|
|
168
|
-
export { main };
|