@northflare/runner 0.0.1
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/DEBUG_LOGGING.md +60 -0
- package/LICENSE +21 -0
- package/MIGRATION_PLAN.md +52 -0
- package/README.md +220 -0
- package/SDK_IMPLEMENTATION_GUIDE.md +1036 -0
- package/bin/northflare-runner +367 -0
- package/coverage/base.css +224 -0
- package/coverage/block-navigation.js +87 -0
- package/coverage/coverage-final.json +12 -0
- package/coverage/favicon.png +0 -0
- package/coverage/index.html +176 -0
- package/coverage/lib/index.html +116 -0
- package/coverage/lib/preload-script.js.html +964 -0
- package/coverage/prettify.css +1 -0
- package/coverage/prettify.js +2 -0
- package/coverage/sort-arrow-sprite.png +0 -0
- package/coverage/sorter.js +196 -0
- package/coverage/src/collections/index.html +116 -0
- package/coverage/src/collections/runner-messages.ts.html +312 -0
- package/coverage/src/components/claude-manager.ts.html +1290 -0
- package/coverage/src/components/index.html +146 -0
- package/coverage/src/components/message-handler.ts.html +730 -0
- package/coverage/src/components/repository-manager.ts.html +841 -0
- package/coverage/src/index.html +131 -0
- package/coverage/src/index.ts.html +448 -0
- package/coverage/src/runner.ts.html +1239 -0
- package/coverage/src/utils/config.ts.html +780 -0
- package/coverage/src/utils/console.ts.html +121 -0
- package/coverage/src/utils/index.html +161 -0
- package/coverage/src/utils/logger.ts.html +475 -0
- package/coverage/src/utils/status-line.ts.html +445 -0
- package/dist/collections/runner-messages.d.ts +52 -0
- package/dist/collections/runner-messages.d.ts.map +1 -0
- package/dist/collections/runner-messages.js +161 -0
- package/dist/collections/runner-messages.js.map +1 -0
- package/dist/components/claude-manager.d.ts +39 -0
- package/dist/components/claude-manager.d.ts.map +1 -0
- package/dist/components/claude-manager.js +783 -0
- package/dist/components/claude-manager.js.map +1 -0
- package/dist/components/claude-sdk-manager.d.ts +47 -0
- package/dist/components/claude-sdk-manager.d.ts.map +1 -0
- package/dist/components/claude-sdk-manager.js +1088 -0
- package/dist/components/claude-sdk-manager.js.map +1 -0
- package/dist/components/enhanced-repository-manager.d.ts +134 -0
- package/dist/components/enhanced-repository-manager.d.ts.map +1 -0
- package/dist/components/enhanced-repository-manager.js +602 -0
- package/dist/components/enhanced-repository-manager.js.map +1 -0
- package/dist/components/message-handler-sse.d.ts +46 -0
- package/dist/components/message-handler-sse.d.ts.map +1 -0
- package/dist/components/message-handler-sse.js +734 -0
- package/dist/components/message-handler-sse.js.map +1 -0
- package/dist/components/message-handler.d.ts +35 -0
- package/dist/components/message-handler.d.ts.map +1 -0
- package/dist/components/message-handler.js +689 -0
- package/dist/components/message-handler.js.map +1 -0
- package/dist/components/repository-manager.d.ts +51 -0
- package/dist/components/repository-manager.d.ts.map +1 -0
- package/dist/components/repository-manager.js +295 -0
- package/dist/components/repository-manager.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +166 -0
- package/dist/index.js.map +1 -0
- package/dist/runner-sse.d.ts +57 -0
- package/dist/runner-sse.d.ts.map +1 -0
- package/dist/runner-sse.js +698 -0
- package/dist/runner-sse.js.map +1 -0
- package/dist/runner.d.ts +51 -0
- package/dist/runner.d.ts.map +1 -0
- package/dist/runner.js +530 -0
- package/dist/runner.js.map +1 -0
- package/dist/services/RunnerAPIClient.d.ts +30 -0
- package/dist/services/RunnerAPIClient.d.ts.map +1 -0
- package/dist/services/RunnerAPIClient.js +112 -0
- package/dist/services/RunnerAPIClient.js.map +1 -0
- package/dist/services/SSEClient.d.ts +60 -0
- package/dist/services/SSEClient.d.ts.map +1 -0
- package/dist/services/SSEClient.js +204 -0
- package/dist/services/SSEClient.js.map +1 -0
- package/dist/types/claude.d.ts +45 -0
- package/dist/types/claude.d.ts.map +1 -0
- package/dist/types/claude.js +6 -0
- package/dist/types/claude.js.map +1 -0
- package/dist/types/index.d.ts +47 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +23 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/messages.d.ts +31 -0
- package/dist/types/messages.d.ts.map +1 -0
- package/dist/types/messages.js +6 -0
- package/dist/types/messages.js.map +1 -0
- package/dist/types/runner-interface.d.ts +24 -0
- package/dist/types/runner-interface.d.ts.map +1 -0
- package/dist/types/runner-interface.js +6 -0
- package/dist/types/runner-interface.js.map +1 -0
- package/dist/utils/StateManager.d.ts +52 -0
- package/dist/utils/StateManager.d.ts.map +1 -0
- package/dist/utils/StateManager.js +162 -0
- package/dist/utils/StateManager.js.map +1 -0
- package/dist/utils/config.d.ts +41 -0
- package/dist/utils/config.d.ts.map +1 -0
- package/dist/utils/config.js +250 -0
- package/dist/utils/config.js.map +1 -0
- package/dist/utils/console.d.ts +11 -0
- package/dist/utils/console.d.ts.map +1 -0
- package/dist/utils/console.js +15 -0
- package/dist/utils/console.js.map +1 -0
- package/dist/utils/expand-env.d.ts +2 -0
- package/dist/utils/expand-env.d.ts.map +1 -0
- package/dist/utils/expand-env.js +20 -0
- package/dist/utils/expand-env.js.map +1 -0
- package/dist/utils/logger.d.ts +9 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +108 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/status-line.d.ts +37 -0
- package/dist/utils/status-line.d.ts.map +1 -0
- package/dist/utils/status-line.js +113 -0
- package/dist/utils/status-line.js.map +1 -0
- package/docs/claude-manager.md +91 -0
- package/exceptions.log +22 -0
- package/lib/preload-script.js +293 -0
- package/package.json +55 -0
- package/rejections.log +63 -0
- package/runner.log +488 -0
- package/src/components/claude-sdk-manager.ts +1354 -0
- package/src/components/enhanced-repository-manager.ts +823 -0
- package/src/components/message-handler-sse.ts +1011 -0
- package/src/components/repository-manager.ts +337 -0
- package/src/index.ts +166 -0
- package/src/runner-sse.ts +847 -0
- package/src/services/RunnerAPIClient.ts +135 -0
- package/src/services/SSEClient.ts +258 -0
- package/src/types/claude.ts +55 -0
- package/src/types/computer-name.d.ts +4 -0
- package/src/types/index.ts +63 -0
- package/src/types/messages.ts +39 -0
- package/src/types/runner-interface.ts +34 -0
- package/src/utils/StateManager.ts +187 -0
- package/src/utils/codex-sdk.js +448 -0
- package/src/utils/config.ts +315 -0
- package/src/utils/console.ts +13 -0
- package/src/utils/expand-env.ts +22 -0
- package/src/utils/logger.ts +131 -0
- package/src/utils/sdk-demo.js +34 -0
- package/src/utils/status-line.ts +121 -0
- package/test-debug.sh +26 -0
- package/tests/retry-strategies.test.ts +410 -0
- package/tests/sdk-integration.test.ts +329 -0
- package/tests/sdk-streaming.test.ts +1180 -0
- package/tests/setup.ts +5 -0
- package/tests/test-claude-manager.ts +120 -0
- package/tsconfig.json +36 -0
- package/vitest.config.ts +27 -0
|
@@ -0,0 +1,315 @@
|
|
|
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.ai";
|
|
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
|
+
DEBUG: process.env["DEBUG"],
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Save configuration file with updated data
|
|
303
|
+
*/
|
|
304
|
+
static async saveConfigFile(configPath: string, config: any): Promise<void> {
|
|
305
|
+
try {
|
|
306
|
+
const absolutePath = path.resolve(configPath);
|
|
307
|
+
const content = JSON.stringify(config, null, 2);
|
|
308
|
+
await fs.writeFile(absolutePath, content, "utf-8");
|
|
309
|
+
logger.info(`Updated configuration file: ${absolutePath}`);
|
|
310
|
+
} catch (error) {
|
|
311
|
+
logger.error("Failed to save configuration file", error);
|
|
312
|
+
throw error;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Console wrapper that suppresses output when not in debug mode
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const isDebug = process.env["DEBUG"] === "true";
|
|
6
|
+
|
|
7
|
+
export const console = {
|
|
8
|
+
log: isDebug ? global.console.log.bind(global.console) : () => {},
|
|
9
|
+
warn: isDebug ? global.console.warn.bind(global.console) : () => {},
|
|
10
|
+
error: global.console.error.bind(global.console), // Always show errors
|
|
11
|
+
info: isDebug ? global.console.info.bind(global.console) : () => {},
|
|
12
|
+
debug: isDebug ? global.console.debug.bind(global.console) : () => {},
|
|
13
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Logger configuration using Winston
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import winston from "winston";
|
|
6
|
+
import path from "path";
|
|
7
|
+
|
|
8
|
+
// Custom log levels
|
|
9
|
+
const levels = {
|
|
10
|
+
error: 0,
|
|
11
|
+
warn: 1,
|
|
12
|
+
info: 2,
|
|
13
|
+
http: 3,
|
|
14
|
+
verbose: 4,
|
|
15
|
+
debug: 5,
|
|
16
|
+
silly: 6,
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
// Log colors
|
|
20
|
+
const colors = {
|
|
21
|
+
error: "red",
|
|
22
|
+
warn: "yellow",
|
|
23
|
+
info: "green",
|
|
24
|
+
http: "magenta",
|
|
25
|
+
verbose: "cyan",
|
|
26
|
+
debug: "blue",
|
|
27
|
+
silly: "gray",
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
winston.addColors(colors);
|
|
31
|
+
|
|
32
|
+
// Format for console output
|
|
33
|
+
const consoleFormat = winston.format.combine(
|
|
34
|
+
winston.format.colorize({ all: true }),
|
|
35
|
+
winston.format.timestamp({ format: "YYYY-MM-DD HH:mm:ss" }),
|
|
36
|
+
winston.format.printf(({ timestamp, level, message, stack, ...metadata }) => {
|
|
37
|
+
let msg = `${timestamp} [${level}]: ${message}`;
|
|
38
|
+
// Only show stack traces and metadata in debug mode
|
|
39
|
+
if (process.env["DEBUG"] === "true") {
|
|
40
|
+
if (stack) {
|
|
41
|
+
msg += `\n${stack}`;
|
|
42
|
+
}
|
|
43
|
+
const filteredMetadata = { ...metadata };
|
|
44
|
+
delete filteredMetadata["component"]; // Keep component info
|
|
45
|
+
if (Object.keys(filteredMetadata).length > 0) {
|
|
46
|
+
msg += ` ${JSON.stringify(filteredMetadata)}`;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return msg;
|
|
50
|
+
})
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
// Format for file output
|
|
54
|
+
const fileFormat = winston.format.combine(
|
|
55
|
+
winston.format.timestamp({ format: "YYYY-MM-DD HH:mm:ss" }),
|
|
56
|
+
winston.format.errors({ stack: true }),
|
|
57
|
+
winston.format.json()
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
// Create logger instance
|
|
61
|
+
export const logger = winston.createLogger({
|
|
62
|
+
level: process.env["DEBUG"] === "true" ? "debug" : "info",
|
|
63
|
+
levels,
|
|
64
|
+
transports: [
|
|
65
|
+
// Always include console transport for errors
|
|
66
|
+
new winston.transports.Console({
|
|
67
|
+
format: consoleFormat,
|
|
68
|
+
level: process.env["DEBUG"] === "true" ? "debug" : "error",
|
|
69
|
+
}),
|
|
70
|
+
],
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
// Add file transports if log directory is configured
|
|
74
|
+
export function configureFileLogging(logDir: string): void {
|
|
75
|
+
// Error log file
|
|
76
|
+
logger.add(
|
|
77
|
+
new winston.transports.File({
|
|
78
|
+
filename: path.join(logDir, "error.log"),
|
|
79
|
+
level: "error",
|
|
80
|
+
format: fileFormat,
|
|
81
|
+
maxsize: 10 * 1024 * 1024, // 10MB
|
|
82
|
+
maxFiles: 5,
|
|
83
|
+
})
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
// Combined log file
|
|
87
|
+
logger.add(
|
|
88
|
+
new winston.transports.File({
|
|
89
|
+
filename: path.join(logDir, "combined.log"),
|
|
90
|
+
format: fileFormat,
|
|
91
|
+
maxsize: 50 * 1024 * 1024, // 50MB
|
|
92
|
+
maxFiles: 10,
|
|
93
|
+
})
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
// Debug log file (only in debug mode)
|
|
97
|
+
if (process.env["DEBUG"] === "true") {
|
|
98
|
+
logger.add(
|
|
99
|
+
new winston.transports.File({
|
|
100
|
+
filename: path.join(logDir, "debug.log"),
|
|
101
|
+
level: "debug",
|
|
102
|
+
format: fileFormat,
|
|
103
|
+
maxsize: 100 * 1024 * 1024, // 100MB
|
|
104
|
+
maxFiles: 3,
|
|
105
|
+
})
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Create child logger for specific components
|
|
111
|
+
export function createLogger(component: string): winston.Logger {
|
|
112
|
+
return logger.child({ component });
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Log unhandled errors
|
|
116
|
+
logger.exceptions.handle(
|
|
117
|
+
new winston.transports.File({
|
|
118
|
+
filename: "exceptions.log",
|
|
119
|
+
format: fileFormat,
|
|
120
|
+
})
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
logger.rejections.handle(
|
|
124
|
+
new winston.transports.File({
|
|
125
|
+
filename: "rejections.log",
|
|
126
|
+
format: fileFormat,
|
|
127
|
+
})
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
// Export default logger
|
|
131
|
+
export default logger;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { query } from "./codex-sdk.js";
|
|
2
|
+
|
|
3
|
+
export default async function getAllOutput(prompt = "Hello!", options = {}) {
|
|
4
|
+
const defaultOptions = {
|
|
5
|
+
cwd: "/Users/toby/Code/hypervisual",
|
|
6
|
+
configOverrides: {
|
|
7
|
+
model: "gpt-5-codex",
|
|
8
|
+
model_reasoning_effort: "high",
|
|
9
|
+
sandbox_mode: "danger-full-access",
|
|
10
|
+
},
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const q = query({
|
|
14
|
+
prompt,
|
|
15
|
+
options: { ...defaultOptions, ...options },
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
const messages = [];
|
|
19
|
+
for await (const message of q) {
|
|
20
|
+
messages.push(message);
|
|
21
|
+
console.log(message);
|
|
22
|
+
|
|
23
|
+
// Check if we've received a task completion
|
|
24
|
+
if (message.msg?.type === "task_complete") {
|
|
25
|
+
break;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Send shutdown command and close
|
|
30
|
+
await q.shutdown();
|
|
31
|
+
await q.close();
|
|
32
|
+
|
|
33
|
+
return messages;
|
|
34
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* StatusLineManager - Manages persistent status line output for active Claude Code processes
|
|
3
|
+
*
|
|
4
|
+
* This component maintains a single-line status output that updates every minute to show
|
|
5
|
+
* the count of active Claude Code processes. It prevents the machine from entering idle
|
|
6
|
+
* state while processes are running and provides clear visual feedback without cluttering
|
|
7
|
+
* the terminal with multiple log lines.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export class StatusLineManager {
|
|
11
|
+
private intervalId?: NodeJS.Timeout;
|
|
12
|
+
private activeCount: number = 0;
|
|
13
|
+
private lastLine: string = "";
|
|
14
|
+
private isEnabled: boolean = true;
|
|
15
|
+
|
|
16
|
+
constructor() {
|
|
17
|
+
// Only enable status line when not in debug mode
|
|
18
|
+
this.isEnabled = process.env["DEBUG"] !== "true";
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Updates the count of active conversations
|
|
23
|
+
*/
|
|
24
|
+
updateActiveCount(count: number): void {
|
|
25
|
+
const wasZero = this.activeCount === 0;
|
|
26
|
+
const isZero = count === 0;
|
|
27
|
+
this.activeCount = count;
|
|
28
|
+
|
|
29
|
+
// Start interval when transitioning from 0 to active
|
|
30
|
+
if (wasZero && !isZero) {
|
|
31
|
+
this.startInterval();
|
|
32
|
+
}
|
|
33
|
+
// Stop interval when transitioning to 0
|
|
34
|
+
else if (!wasZero && isZero) {
|
|
35
|
+
this.stopInterval();
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Starts the status line interval
|
|
41
|
+
*/
|
|
42
|
+
private startInterval(): void {
|
|
43
|
+
if (!this.isEnabled || this.intervalId) return;
|
|
44
|
+
|
|
45
|
+
// Show initial status immediately
|
|
46
|
+
this.showStatus();
|
|
47
|
+
|
|
48
|
+
// Update every minute on the minute
|
|
49
|
+
const now = new Date();
|
|
50
|
+
const msUntilNextMinute = (60 - now.getSeconds()) * 1000 - now.getMilliseconds();
|
|
51
|
+
|
|
52
|
+
// First timeout to align with the minute
|
|
53
|
+
setTimeout(() => {
|
|
54
|
+
this.showStatus();
|
|
55
|
+
|
|
56
|
+
// Then set up regular interval
|
|
57
|
+
this.intervalId = setInterval(() => {
|
|
58
|
+
this.showStatus();
|
|
59
|
+
}, 60000); // Every 60 seconds
|
|
60
|
+
}, msUntilNextMinute);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Stops the status line interval
|
|
65
|
+
*/
|
|
66
|
+
private stopInterval(): void {
|
|
67
|
+
if (this.intervalId) {
|
|
68
|
+
clearInterval(this.intervalId);
|
|
69
|
+
this.intervalId = undefined;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Show final "No active" message
|
|
73
|
+
this.showStatus();
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Shows the current status
|
|
78
|
+
*/
|
|
79
|
+
private showStatus(): void {
|
|
80
|
+
if (!this.isEnabled) return;
|
|
81
|
+
|
|
82
|
+
const now = new Date();
|
|
83
|
+
const timeStr = now.toTimeString().slice(0, 5); // HH:MM format
|
|
84
|
+
|
|
85
|
+
let newLine: string;
|
|
86
|
+
if (this.activeCount === 0) {
|
|
87
|
+
newLine = "No active Claude Code processes";
|
|
88
|
+
} else if (this.activeCount === 1) {
|
|
89
|
+
newLine = `${timeStr} 1 active Claude Code process`;
|
|
90
|
+
} else {
|
|
91
|
+
newLine = `${timeStr} ${this.activeCount} active Claude Code processes`;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// If this is the first status line, position it
|
|
95
|
+
if (!this.lastLine) {
|
|
96
|
+
// Move to start of line and save cursor position
|
|
97
|
+
process.stdout.write('\r');
|
|
98
|
+
} else {
|
|
99
|
+
// Clear the current line
|
|
100
|
+
process.stdout.write('\r' + ' '.repeat(this.lastLine.length) + '\r');
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Write the new status without newline
|
|
104
|
+
process.stdout.write(newLine);
|
|
105
|
+
this.lastLine = newLine;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Cleans up the status line manager
|
|
110
|
+
*/
|
|
111
|
+
dispose(): void {
|
|
112
|
+
this.stopInterval();
|
|
113
|
+
// Add a newline to ensure next output starts on a new line
|
|
114
|
+
if (this.lastLine) {
|
|
115
|
+
process.stdout.write('\n');
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Singleton instance
|
|
121
|
+
export const statusLineManager = new StatusLineManager();
|
package/test-debug.sh
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# Test script to demonstrate debug logging in the runner
|
|
4
|
+
|
|
5
|
+
echo "Starting runner in debug mode to demonstrate enhanced logging..."
|
|
6
|
+
echo "This will show:"
|
|
7
|
+
echo " - runnerUid on initialization"
|
|
8
|
+
echo " - lastProcessedAt state for ElectricSQL connection"
|
|
9
|
+
echo " - ElectricSQL connection details"
|
|
10
|
+
echo " - Message processing decisions with ownership info"
|
|
11
|
+
echo ""
|
|
12
|
+
echo "Set DEBUG=true to enable verbose logging"
|
|
13
|
+
echo ""
|
|
14
|
+
|
|
15
|
+
# Run the runner with debug mode enabled
|
|
16
|
+
DEBUG=true npm run start -- --config ./config.json 2>&1 | head -100
|
|
17
|
+
|
|
18
|
+
echo ""
|
|
19
|
+
echo "Note: The runner will display debug logs including:"
|
|
20
|
+
echo " - Runner UID and ownership details during registration"
|
|
21
|
+
echo " - LastProcessedAt watermark for message filtering"
|
|
22
|
+
echo " - ElectricSQL collection creation with connection details"
|
|
23
|
+
echo " - Message processing decisions with reasons"
|
|
24
|
+
echo ""
|
|
25
|
+
echo "To run the runner with debug logging enabled, use:"
|
|
26
|
+
echo " DEBUG=true npm run start"
|