@northflare/runner 0.0.11 → 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/dist/utils/config.d.ts +1 -0
- package/dist/utils/config.d.ts.map +1 -1
- package/dist/utils/config.js +13 -2
- package/dist/utils/config.js.map +1 -1
- package/package.json +1 -2
- 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 -316
- 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,187 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* State Manager for persisting runner state
|
|
3
|
-
*
|
|
4
|
-
* Manages persistent state including lastProcessedAt timestamp
|
|
5
|
-
* to enable proper catch-up on restart.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import fs from 'fs/promises';
|
|
9
|
-
import path from 'path';
|
|
10
|
-
import { createLogger } from './logger';
|
|
11
|
-
|
|
12
|
-
const logger = createLogger('StateManager');
|
|
13
|
-
|
|
14
|
-
export interface RunnerState {
|
|
15
|
-
runnerId: string;
|
|
16
|
-
runnerUid: string | null;
|
|
17
|
-
lastProcessedAt: string | null; // ISO string
|
|
18
|
-
isActiveRunner: boolean;
|
|
19
|
-
updatedAt: string; // ISO string
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export class StateManager {
|
|
23
|
-
private statePath: string;
|
|
24
|
-
private state: RunnerState | null = null;
|
|
25
|
-
private saveTimer: NodeJS.Timeout | null = null;
|
|
26
|
-
|
|
27
|
-
constructor(dataDir: string, runnerId: string) {
|
|
28
|
-
// Namespace state file by runnerId (stable external ID) to separate different users
|
|
29
|
-
const stateFile = `runner-state-${runnerId}.json`;
|
|
30
|
-
this.statePath = path.join(dataDir, stateFile);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Load state from disk
|
|
35
|
-
*/
|
|
36
|
-
async loadState(): Promise<RunnerState | null> {
|
|
37
|
-
try {
|
|
38
|
-
const content = await fs.readFile(this.statePath, 'utf-8');
|
|
39
|
-
this.state = JSON.parse(content);
|
|
40
|
-
logger.info('Loaded runner state', {
|
|
41
|
-
runnerId: this.state?.runnerId,
|
|
42
|
-
runnerUid: this.state?.runnerUid,
|
|
43
|
-
lastProcessedAt: this.state?.lastProcessedAt,
|
|
44
|
-
isActiveRunner: this.state?.isActiveRunner,
|
|
45
|
-
});
|
|
46
|
-
return this.state;
|
|
47
|
-
} catch (error) {
|
|
48
|
-
if ((error as any).code === 'ENOENT') {
|
|
49
|
-
logger.debug('No state file found, starting fresh');
|
|
50
|
-
return null;
|
|
51
|
-
}
|
|
52
|
-
logger.error('Failed to load state file:', error);
|
|
53
|
-
return null;
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Save state to disk
|
|
59
|
-
*/
|
|
60
|
-
async saveState(state: RunnerState): Promise<void> {
|
|
61
|
-
this.state = state;
|
|
62
|
-
|
|
63
|
-
// Cancel any pending save
|
|
64
|
-
if (this.saveTimer) {
|
|
65
|
-
clearTimeout(this.saveTimer);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// Debounce saves to avoid excessive disk writes
|
|
69
|
-
this.saveTimer = setTimeout(async () => {
|
|
70
|
-
try {
|
|
71
|
-
// Ensure directory exists
|
|
72
|
-
const dir = path.dirname(this.statePath);
|
|
73
|
-
await fs.mkdir(dir, { recursive: true });
|
|
74
|
-
|
|
75
|
-
// Update timestamp
|
|
76
|
-
state.updatedAt = new Date().toISOString();
|
|
77
|
-
|
|
78
|
-
// Write atomically by writing to temp file first
|
|
79
|
-
const tempPath = `${this.statePath}.tmp`;
|
|
80
|
-
await fs.writeFile(tempPath, JSON.stringify(state, null, 2), 'utf-8');
|
|
81
|
-
await fs.rename(tempPath, this.statePath);
|
|
82
|
-
|
|
83
|
-
logger.debug('Saved runner state', {
|
|
84
|
-
runnerId: state.runnerId,
|
|
85
|
-
lastProcessedAt: state.lastProcessedAt,
|
|
86
|
-
isActiveRunner: state.isActiveRunner,
|
|
87
|
-
});
|
|
88
|
-
} catch (error) {
|
|
89
|
-
logger.error('Failed to save state file:', error);
|
|
90
|
-
throw error;
|
|
91
|
-
}
|
|
92
|
-
}, 1000); // 1 second debounce
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* Update lastProcessedAt timestamp
|
|
97
|
-
*/
|
|
98
|
-
async updateLastProcessedAt(timestamp: Date | string): Promise<void> {
|
|
99
|
-
if (!this.state) {
|
|
100
|
-
throw new Error('State not loaded');
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
const isoString = timestamp instanceof Date ? timestamp.toISOString() : timestamp;
|
|
104
|
-
|
|
105
|
-
const updatedState: RunnerState = {
|
|
106
|
-
...this.state,
|
|
107
|
-
lastProcessedAt: isoString,
|
|
108
|
-
};
|
|
109
|
-
|
|
110
|
-
await this.saveState(updatedState);
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
/**
|
|
114
|
-
* Update runner registration details
|
|
115
|
-
*/
|
|
116
|
-
async updateRunnerRegistration(
|
|
117
|
-
runnerId: string,
|
|
118
|
-
runnerUid: string | null,
|
|
119
|
-
lastProcessedAt: Date | null
|
|
120
|
-
): Promise<void> {
|
|
121
|
-
const state: RunnerState = this.state || {
|
|
122
|
-
runnerId: '',
|
|
123
|
-
runnerUid: null,
|
|
124
|
-
lastProcessedAt: null,
|
|
125
|
-
isActiveRunner: false,
|
|
126
|
-
updatedAt: new Date().toISOString(),
|
|
127
|
-
};
|
|
128
|
-
|
|
129
|
-
const updatedState: RunnerState = {
|
|
130
|
-
...state,
|
|
131
|
-
runnerId,
|
|
132
|
-
runnerUid,
|
|
133
|
-
lastProcessedAt: lastProcessedAt ? lastProcessedAt.toISOString() : null,
|
|
134
|
-
};
|
|
135
|
-
|
|
136
|
-
await this.saveState(updatedState);
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
/**
|
|
140
|
-
* Update active runner status
|
|
141
|
-
*/
|
|
142
|
-
async updateActiveStatus(isActive: boolean): Promise<void> {
|
|
143
|
-
if (!this.state) {
|
|
144
|
-
throw new Error('State not loaded');
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
const updatedState: RunnerState = {
|
|
148
|
-
...this.state,
|
|
149
|
-
isActiveRunner: isActive,
|
|
150
|
-
};
|
|
151
|
-
|
|
152
|
-
await this.saveState(updatedState);
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
/**
|
|
156
|
-
* Get current state
|
|
157
|
-
*/
|
|
158
|
-
getState(): RunnerState | null {
|
|
159
|
-
return this.state;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
/**
|
|
163
|
-
* Get lastProcessedAt as Date
|
|
164
|
-
*/
|
|
165
|
-
getLastProcessedAt(): Date | null {
|
|
166
|
-
if (!this.state || !this.state.lastProcessedAt) {
|
|
167
|
-
return null;
|
|
168
|
-
}
|
|
169
|
-
return new Date(this.state.lastProcessedAt);
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
/**
|
|
173
|
-
* Clear state (for testing or reset)
|
|
174
|
-
*/
|
|
175
|
-
async clearState(): Promise<void> {
|
|
176
|
-
this.state = null;
|
|
177
|
-
try {
|
|
178
|
-
await fs.unlink(this.statePath);
|
|
179
|
-
logger.info('Cleared runner state');
|
|
180
|
-
} catch (error) {
|
|
181
|
-
if ((error as any).code !== 'ENOENT') {
|
|
182
|
-
logger.error('Failed to clear state file:', error);
|
|
183
|
-
throw error;
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
}
|
package/src/utils/config.ts
DELETED
|
@@ -1,316 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Configuration management utilities
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { RunnerConfig, EnvironmentConfig, RetryStrategy } from "../types";
|
|
6
|
-
import fs from "fs/promises";
|
|
7
|
-
import path from "path";
|
|
8
|
-
import { createLogger } from "./logger";
|
|
9
|
-
import { Command } from "commander";
|
|
10
|
-
|
|
11
|
-
const logger = createLogger("ConfigManager");
|
|
12
|
-
|
|
13
|
-
export class ConfigManager {
|
|
14
|
-
private static DEFAULT_CONFIG: Partial<RunnerConfig> = {
|
|
15
|
-
dataDir: "./data",
|
|
16
|
-
heartbeatInterval: 120000, // 2 minutes
|
|
17
|
-
retryStrategy: "exponential" as RetryStrategy,
|
|
18
|
-
retryIntervalSecs: 60,
|
|
19
|
-
retryDurationSecs: 900,
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Load configuration without parsing command line arguments
|
|
24
|
-
* Used when arguments have already been parsed by the CLI
|
|
25
|
-
*/
|
|
26
|
-
static async loadConfig(configPath?: string): Promise<RunnerConfig> {
|
|
27
|
-
// Validate required environment variables
|
|
28
|
-
this.validateEnvironment();
|
|
29
|
-
|
|
30
|
-
// Start with defaults
|
|
31
|
-
let config: Partial<RunnerConfig> = { ...this.DEFAULT_CONFIG };
|
|
32
|
-
|
|
33
|
-
// Determine config path - use provided path or default location
|
|
34
|
-
let effectiveConfigPath = configPath;
|
|
35
|
-
if (!effectiveConfigPath) {
|
|
36
|
-
// Try to use default config location
|
|
37
|
-
try {
|
|
38
|
-
const envPaths = require("env-paths").default || require("env-paths");
|
|
39
|
-
const paths = envPaths("northflare-runner", { suffix: "" });
|
|
40
|
-
const defaultConfigPath = path.join(paths.config, "config.json");
|
|
41
|
-
|
|
42
|
-
// Check if default config exists
|
|
43
|
-
const fs = require("fs");
|
|
44
|
-
if (fs.existsSync(defaultConfigPath)) {
|
|
45
|
-
effectiveConfigPath = defaultConfigPath;
|
|
46
|
-
logger.info(`Using default config file: ${defaultConfigPath}`);
|
|
47
|
-
}
|
|
48
|
-
} catch (error) {
|
|
49
|
-
// env-paths not available or error accessing default location
|
|
50
|
-
logger.debug("Could not check default config location:", error);
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// Load from config file if we have a path
|
|
55
|
-
if (effectiveConfigPath) {
|
|
56
|
-
const fileConfig = await this.loadConfigFile(effectiveConfigPath);
|
|
57
|
-
config = { ...config, ...fileConfig };
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// Override with environment variables
|
|
61
|
-
const envConfig = this.loadFromEnvironment();
|
|
62
|
-
config = { ...config, ...envConfig };
|
|
63
|
-
|
|
64
|
-
// Validate final configuration
|
|
65
|
-
this.validateConfig(config);
|
|
66
|
-
|
|
67
|
-
return config as RunnerConfig;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Parse command line arguments and load configuration
|
|
72
|
-
*/
|
|
73
|
-
static async parseArgsAndLoadConfig(argv: string[]): Promise<RunnerConfig> {
|
|
74
|
-
const program = new Command();
|
|
75
|
-
|
|
76
|
-
program
|
|
77
|
-
.name("northflare-runner")
|
|
78
|
-
.description(
|
|
79
|
-
"Northflare Runner - Executes Claude agents for task processing"
|
|
80
|
-
)
|
|
81
|
-
.version("1.0.0")
|
|
82
|
-
.option("-c, --config <path>", "Path to configuration file")
|
|
83
|
-
.option(
|
|
84
|
-
"--retry-strategy <strategy>",
|
|
85
|
-
"Registration retry strategy (none, interval, exponential)",
|
|
86
|
-
"exponential"
|
|
87
|
-
)
|
|
88
|
-
.option(
|
|
89
|
-
"--retry-interval-secs <seconds>",
|
|
90
|
-
"Retry interval in seconds for interval strategy",
|
|
91
|
-
"60"
|
|
92
|
-
)
|
|
93
|
-
.option(
|
|
94
|
-
"--retry-duration-secs <seconds>",
|
|
95
|
-
"Max retry duration in seconds for exponential strategy",
|
|
96
|
-
"900"
|
|
97
|
-
)
|
|
98
|
-
.option("--data-dir <path>", "Data directory path", "./data")
|
|
99
|
-
.option(
|
|
100
|
-
"--heartbeat-interval <ms>",
|
|
101
|
-
"Heartbeat interval in milliseconds",
|
|
102
|
-
"120000"
|
|
103
|
-
);
|
|
104
|
-
|
|
105
|
-
program.parse(argv);
|
|
106
|
-
const options = program.opts();
|
|
107
|
-
|
|
108
|
-
// Validate required environment variables
|
|
109
|
-
this.validateEnvironment();
|
|
110
|
-
|
|
111
|
-
// Start with defaults
|
|
112
|
-
let config: Partial<RunnerConfig> = { ...this.DEFAULT_CONFIG };
|
|
113
|
-
|
|
114
|
-
// Load from config file if provided
|
|
115
|
-
if (options["config"]) {
|
|
116
|
-
const fileConfig = await this.loadConfigFile(options["config"]);
|
|
117
|
-
config = { ...config, ...fileConfig };
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
// Override with environment variables
|
|
121
|
-
const envConfig = this.loadFromEnvironment();
|
|
122
|
-
config = { ...config, ...envConfig };
|
|
123
|
-
|
|
124
|
-
// Override with CLI arguments (highest priority)
|
|
125
|
-
if (options["retryStrategy"]) {
|
|
126
|
-
config.retryStrategy = options["retryStrategy"] as RetryStrategy;
|
|
127
|
-
}
|
|
128
|
-
if (options["retryIntervalSecs"]) {
|
|
129
|
-
config.retryIntervalSecs = parseInt(options["retryIntervalSecs"]);
|
|
130
|
-
}
|
|
131
|
-
if (options["retryDurationSecs"]) {
|
|
132
|
-
config.retryDurationSecs = parseInt(options["retryDurationSecs"]);
|
|
133
|
-
}
|
|
134
|
-
if (options["dataDir"]) {
|
|
135
|
-
config.dataDir = options["dataDir"];
|
|
136
|
-
}
|
|
137
|
-
if (options["heartbeatInterval"]) {
|
|
138
|
-
config.heartbeatInterval = parseInt(options["heartbeatInterval"]);
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
// Validate final configuration
|
|
142
|
-
this.validateConfig(config);
|
|
143
|
-
|
|
144
|
-
return config as RunnerConfig;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
/**
|
|
148
|
-
* Validate required environment variables
|
|
149
|
-
*/
|
|
150
|
-
private static validateEnvironment(): void {
|
|
151
|
-
// Set default for NORTHFLARE_WORKSPACE_DIR if not provided
|
|
152
|
-
if (!process.env["NORTHFLARE_WORKSPACE_DIR"]) {
|
|
153
|
-
try {
|
|
154
|
-
const envPaths = require("env-paths").default || require("env-paths");
|
|
155
|
-
const paths = envPaths("northflare-runner", { suffix: "" });
|
|
156
|
-
process.env["NORTHFLARE_WORKSPACE_DIR"] = paths.data;
|
|
157
|
-
} catch (error) {
|
|
158
|
-
// Fallback to original default if env-paths is not available
|
|
159
|
-
process.env["NORTHFLARE_WORKSPACE_DIR"] = "/workspace";
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
// Set default for NORTHFLARE_ORCHESTRATOR_URL if not provided
|
|
164
|
-
if (!process.env["NORTHFLARE_ORCHESTRATOR_URL"]) {
|
|
165
|
-
process.env["NORTHFLARE_ORCHESTRATOR_URL"] = "https://api.northflare.app";
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
const required = [
|
|
169
|
-
"NORTHFLARE_RUNNER_TOKEN",
|
|
170
|
-
"NORTHFLARE_WORKSPACE_DIR",
|
|
171
|
-
"NORTHFLARE_ORCHESTRATOR_URL",
|
|
172
|
-
];
|
|
173
|
-
|
|
174
|
-
const missing = required.filter((key) => !process.env[key]);
|
|
175
|
-
|
|
176
|
-
if (missing.length > 0) {
|
|
177
|
-
throw new Error(
|
|
178
|
-
`Missing required environment variables: ${missing.join(", ")}\n` +
|
|
179
|
-
"Please set these environment variables before starting the runner."
|
|
180
|
-
);
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
/**
|
|
185
|
-
* Load configuration from environment variables
|
|
186
|
-
*/
|
|
187
|
-
private static loadFromEnvironment(): Partial<RunnerConfig> {
|
|
188
|
-
const config: Partial<RunnerConfig> = {
|
|
189
|
-
orchestratorUrl: process.env["NORTHFLARE_ORCHESTRATOR_URL"]!,
|
|
190
|
-
};
|
|
191
|
-
|
|
192
|
-
// Optional environment overrides
|
|
193
|
-
if (process.env["NORTHFLARE_DATA_DIR"]) {
|
|
194
|
-
config.dataDir = process.env["NORTHFLARE_DATA_DIR"];
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
return config;
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
/**
|
|
201
|
-
* Load configuration from file
|
|
202
|
-
*/
|
|
203
|
-
private static async loadConfigFile(
|
|
204
|
-
configPath: string
|
|
205
|
-
): Promise<Partial<RunnerConfig>> {
|
|
206
|
-
try {
|
|
207
|
-
const absolutePath = path.resolve(configPath);
|
|
208
|
-
const content = await fs.readFile(absolutePath, "utf-8");
|
|
209
|
-
|
|
210
|
-
// Support both JSON and YAML formats
|
|
211
|
-
if (configPath.endsWith(".json")) {
|
|
212
|
-
const config = JSON.parse(content);
|
|
213
|
-
|
|
214
|
-
// Log if runnerRepos are found in config
|
|
215
|
-
if (config.runnerRepos && Array.isArray(config.runnerRepos)) {
|
|
216
|
-
logger.info(
|
|
217
|
-
`Found ${config.runnerRepos.length} runner repos in config file`
|
|
218
|
-
);
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
return config;
|
|
222
|
-
} else if (configPath.endsWith(".yaml") || configPath.endsWith(".yml")) {
|
|
223
|
-
// For YAML support, we'd need to add a yaml parser dependency
|
|
224
|
-
throw new Error("YAML configuration files are not yet supported");
|
|
225
|
-
} else {
|
|
226
|
-
throw new Error("Configuration file must be .json format");
|
|
227
|
-
}
|
|
228
|
-
} catch (error) {
|
|
229
|
-
if ((error as any).code === "ENOENT") {
|
|
230
|
-
logger.warn(`Configuration file not found: ${configPath}`);
|
|
231
|
-
return {};
|
|
232
|
-
}
|
|
233
|
-
throw error;
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
/**
|
|
238
|
-
* Validate final configuration
|
|
239
|
-
*/
|
|
240
|
-
private static validateConfig(config: Partial<RunnerConfig>): void {
|
|
241
|
-
// Note: Runner ID will be generated by server during registration
|
|
242
|
-
|
|
243
|
-
if (!config.orchestratorUrl) {
|
|
244
|
-
throw new Error("orchestratorUrl is required");
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
if (!config.dataDir) {
|
|
248
|
-
throw new Error("dataDir is required");
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
if (!config.heartbeatInterval || config.heartbeatInterval < 1000) {
|
|
252
|
-
throw new Error("heartbeatInterval must be at least 1000ms");
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
// Validate retry strategy
|
|
256
|
-
const validStrategies: RetryStrategy[] = [
|
|
257
|
-
"none",
|
|
258
|
-
"interval",
|
|
259
|
-
"exponential",
|
|
260
|
-
];
|
|
261
|
-
if (
|
|
262
|
-
!config.retryStrategy ||
|
|
263
|
-
!validStrategies.includes(config.retryStrategy)
|
|
264
|
-
) {
|
|
265
|
-
throw new Error(
|
|
266
|
-
`retryStrategy must be one of: ${validStrategies.join(", ")}`
|
|
267
|
-
);
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
if (
|
|
271
|
-
config.retryStrategy === "interval" &&
|
|
272
|
-
(!config.retryIntervalSecs || config.retryIntervalSecs < 1)
|
|
273
|
-
) {
|
|
274
|
-
throw new Error(
|
|
275
|
-
"retryIntervalSecs must be at least 1 when using interval strategy"
|
|
276
|
-
);
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
if (
|
|
280
|
-
config.retryStrategy === "exponential" &&
|
|
281
|
-
(!config.retryDurationSecs || config.retryDurationSecs < 1)
|
|
282
|
-
) {
|
|
283
|
-
throw new Error(
|
|
284
|
-
"retryDurationSecs must be at least 1 when using exponential strategy"
|
|
285
|
-
);
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
/**
|
|
290
|
-
* Get all environment configuration
|
|
291
|
-
*/
|
|
292
|
-
static getEnvironmentConfig(): EnvironmentConfig {
|
|
293
|
-
return {
|
|
294
|
-
NORTHFLARE_RUNNER_TOKEN: process.env["NORTHFLARE_RUNNER_TOKEN"]!,
|
|
295
|
-
NORTHFLARE_WORKSPACE_DIR: process.env["NORTHFLARE_WORKSPACE_DIR"]!,
|
|
296
|
-
NORTHFLARE_ORCHESTRATOR_URL: process.env["NORTHFLARE_ORCHESTRATOR_URL"]!,
|
|
297
|
-
NORTHFLARE_RUNNER_DEBUG: process.env["NORTHFLARE_RUNNER_DEBUG"],
|
|
298
|
-
DEBUG: process.env["DEBUG"],
|
|
299
|
-
};
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
/**
|
|
303
|
-
* Save configuration file with updated data
|
|
304
|
-
*/
|
|
305
|
-
static async saveConfigFile(configPath: string, config: any): Promise<void> {
|
|
306
|
-
try {
|
|
307
|
-
const absolutePath = path.resolve(configPath);
|
|
308
|
-
const content = JSON.stringify(config, null, 2);
|
|
309
|
-
await fs.writeFile(absolutePath, content, "utf-8");
|
|
310
|
-
logger.info(`Updated configuration file: ${absolutePath}`);
|
|
311
|
-
} catch (error) {
|
|
312
|
-
logger.error("Failed to save configuration file", error);
|
|
313
|
-
throw error;
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
}
|
package/src/utils/console.ts
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Console wrapper that suppresses output when not in debug mode
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { isRunnerDebugEnabled } from "./debug";
|
|
6
|
-
|
|
7
|
-
const isDebug = isRunnerDebugEnabled();
|
|
8
|
-
|
|
9
|
-
export const console = {
|
|
10
|
-
log: isDebug ? global.console.log.bind(global.console) : () => {},
|
|
11
|
-
warn: isDebug ? global.console.warn.bind(global.console) : () => {},
|
|
12
|
-
error: global.console.error.bind(global.console), // Always show errors
|
|
13
|
-
info: isDebug ? global.console.info.bind(global.console) : () => {},
|
|
14
|
-
debug: isDebug ? global.console.debug.bind(global.console) : () => {},
|
|
15
|
-
};
|
package/src/utils/debug.ts
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
export function isRunnerDebugEnabled(): boolean {
|
|
2
|
-
const explicitFlag = process.env["NORTHFLARE_RUNNER_DEBUG"];
|
|
3
|
-
if (typeof explicitFlag === "string") {
|
|
4
|
-
return isTruthy(explicitFlag);
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
const legacyFlag = process.env["DEBUG"];
|
|
8
|
-
if (typeof legacyFlag === "string") {
|
|
9
|
-
return isTruthy(legacyFlag);
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
return false;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
function isTruthy(value: string): boolean {
|
|
16
|
-
const normalized = value.trim().toLowerCase();
|
|
17
|
-
return normalized === "true" || normalized === "1" || normalized === "yes" || normalized === "on";
|
|
18
|
-
}
|
package/src/utils/expand-env.ts
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
export function expandEnv(
|
|
2
|
-
obj: any,
|
|
3
|
-
env: Record<string, string | undefined> = process.env
|
|
4
|
-
): any {
|
|
5
|
-
if (typeof obj === "string") {
|
|
6
|
-
return obj.replace(/\$\{([^}]+)\}/g, (match, key) => env[key] || match);
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
if (Array.isArray(obj)) {
|
|
10
|
-
return obj.map((item) => expandEnv(item, env));
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
if (obj && typeof obj === "object") {
|
|
14
|
-
const result: Record<string, any> = {};
|
|
15
|
-
for (const [key, value] of Object.entries(obj)) {
|
|
16
|
-
result[key] = expandEnv(value, env);
|
|
17
|
-
}
|
|
18
|
-
return result;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
return obj;
|
|
22
|
-
}
|
package/src/utils/logger.ts
DELETED
|
@@ -1,134 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Logger configuration using Winston
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import winston from "winston";
|
|
6
|
-
import path from "path";
|
|
7
|
-
import { isRunnerDebugEnabled } from "./debug";
|
|
8
|
-
|
|
9
|
-
// Custom log levels
|
|
10
|
-
const levels = {
|
|
11
|
-
error: 0,
|
|
12
|
-
warn: 1,
|
|
13
|
-
info: 2,
|
|
14
|
-
http: 3,
|
|
15
|
-
verbose: 4,
|
|
16
|
-
debug: 5,
|
|
17
|
-
silly: 6,
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
// Log colors
|
|
21
|
-
const colors = {
|
|
22
|
-
error: "red",
|
|
23
|
-
warn: "yellow",
|
|
24
|
-
info: "green",
|
|
25
|
-
http: "magenta",
|
|
26
|
-
verbose: "cyan",
|
|
27
|
-
debug: "blue",
|
|
28
|
-
silly: "gray",
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
winston.addColors(colors);
|
|
32
|
-
|
|
33
|
-
const debugEnabled = isRunnerDebugEnabled();
|
|
34
|
-
|
|
35
|
-
// Format for console output
|
|
36
|
-
const consoleFormat = winston.format.combine(
|
|
37
|
-
winston.format.colorize({ all: true }),
|
|
38
|
-
winston.format.timestamp({ format: "YYYY-MM-DD HH:mm:ss" }),
|
|
39
|
-
winston.format.printf(({ timestamp, level, message, stack, ...metadata }) => {
|
|
40
|
-
let msg = `${timestamp} [${level}]: ${message}`;
|
|
41
|
-
// Only show stack traces and metadata in debug mode
|
|
42
|
-
if (debugEnabled) {
|
|
43
|
-
if (stack) {
|
|
44
|
-
msg += `\n${stack}`;
|
|
45
|
-
}
|
|
46
|
-
const filteredMetadata = { ...metadata };
|
|
47
|
-
delete filteredMetadata["component"]; // Keep component info
|
|
48
|
-
if (Object.keys(filteredMetadata).length > 0) {
|
|
49
|
-
msg += ` ${JSON.stringify(filteredMetadata)}`;
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
return msg;
|
|
53
|
-
})
|
|
54
|
-
);
|
|
55
|
-
|
|
56
|
-
// Format for file output
|
|
57
|
-
const fileFormat = winston.format.combine(
|
|
58
|
-
winston.format.timestamp({ format: "YYYY-MM-DD HH:mm:ss" }),
|
|
59
|
-
winston.format.errors({ stack: true }),
|
|
60
|
-
winston.format.json()
|
|
61
|
-
);
|
|
62
|
-
|
|
63
|
-
// Create logger instance
|
|
64
|
-
export const logger = winston.createLogger({
|
|
65
|
-
level: debugEnabled ? "debug" : "info",
|
|
66
|
-
levels,
|
|
67
|
-
transports: [
|
|
68
|
-
// Always include console transport for errors
|
|
69
|
-
new winston.transports.Console({
|
|
70
|
-
format: consoleFormat,
|
|
71
|
-
level: debugEnabled ? "debug" : "error",
|
|
72
|
-
}),
|
|
73
|
-
],
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
// Add file transports if log directory is configured
|
|
77
|
-
export function configureFileLogging(logDir: string): void {
|
|
78
|
-
// Error log file
|
|
79
|
-
logger.add(
|
|
80
|
-
new winston.transports.File({
|
|
81
|
-
filename: path.join(logDir, "error.log"),
|
|
82
|
-
level: "error",
|
|
83
|
-
format: fileFormat,
|
|
84
|
-
maxsize: 10 * 1024 * 1024, // 10MB
|
|
85
|
-
maxFiles: 5,
|
|
86
|
-
})
|
|
87
|
-
);
|
|
88
|
-
|
|
89
|
-
// Combined log file
|
|
90
|
-
logger.add(
|
|
91
|
-
new winston.transports.File({
|
|
92
|
-
filename: path.join(logDir, "combined.log"),
|
|
93
|
-
format: fileFormat,
|
|
94
|
-
maxsize: 50 * 1024 * 1024, // 50MB
|
|
95
|
-
maxFiles: 10,
|
|
96
|
-
})
|
|
97
|
-
);
|
|
98
|
-
|
|
99
|
-
// Debug log file (only in debug mode)
|
|
100
|
-
if (debugEnabled) {
|
|
101
|
-
logger.add(
|
|
102
|
-
new winston.transports.File({
|
|
103
|
-
filename: path.join(logDir, "debug.log"),
|
|
104
|
-
level: "debug",
|
|
105
|
-
format: fileFormat,
|
|
106
|
-
maxsize: 100 * 1024 * 1024, // 100MB
|
|
107
|
-
maxFiles: 3,
|
|
108
|
-
})
|
|
109
|
-
);
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
// Create child logger for specific components
|
|
114
|
-
export function createLogger(component: string): winston.Logger {
|
|
115
|
-
return logger.child({ component });
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
// Log unhandled errors
|
|
119
|
-
logger.exceptions.handle(
|
|
120
|
-
new winston.transports.File({
|
|
121
|
-
filename: "exceptions.log",
|
|
122
|
-
format: fileFormat,
|
|
123
|
-
})
|
|
124
|
-
);
|
|
125
|
-
|
|
126
|
-
logger.rejections.handle(
|
|
127
|
-
new winston.transports.File({
|
|
128
|
-
filename: "rejections.log",
|
|
129
|
-
format: fileFormat,
|
|
130
|
-
})
|
|
131
|
-
);
|
|
132
|
-
|
|
133
|
-
// Export default logger
|
|
134
|
-
export default logger;
|