@supaku/agentfactory-server 0.1.2 → 0.2.0

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,65 @@
1
+ /**
2
+ * Environment Variable Validation
3
+ *
4
+ * Validates required environment variables on startup.
5
+ * Fails fast if critical security variables are missing in production.
6
+ */
7
+ /**
8
+ * Configuration for environment validation
9
+ */
10
+ export interface EnvValidationConfig {
11
+ /** Variable names required in production */
12
+ requiredVars?: string[];
13
+ /** Variables that need minimum length validation */
14
+ minLengthVars?: Array<{
15
+ name: string;
16
+ minLength: number;
17
+ }>;
18
+ }
19
+ /**
20
+ * Validation result
21
+ */
22
+ export interface EnvValidationResult {
23
+ valid: boolean;
24
+ missing: string[];
25
+ warnings: string[];
26
+ }
27
+ /**
28
+ * Validate environment variables
29
+ *
30
+ * In production: All required vars must be present
31
+ * In development: Log warnings for missing vars but don't fail
32
+ *
33
+ * @param config - Optional configuration to override defaults
34
+ * @returns Validation result with missing vars
35
+ */
36
+ export declare function validateEnv(config?: EnvValidationConfig): EnvValidationResult;
37
+ /**
38
+ * Validate and fail fast if critical vars are missing
39
+ *
40
+ * Call this at application startup to ensure required
41
+ * environment variables are configured.
42
+ *
43
+ * @param config - Optional configuration to override defaults
44
+ * @throws Error if required vars are missing in production
45
+ */
46
+ export declare function validateEnvOrThrow(config?: EnvValidationConfig): void;
47
+ /**
48
+ * Check if webhook signature verification is configured
49
+ */
50
+ export declare function isWebhookSecretConfigured(): boolean;
51
+ /**
52
+ * Check if cron authentication is configured
53
+ */
54
+ export declare function isCronSecretConfigured(): boolean;
55
+ /**
56
+ * Check if session hashing is configured
57
+ */
58
+ export declare function isSessionHashConfigured(): boolean;
59
+ /**
60
+ * Get session hash salt
61
+ * @param saltEnvVar - Environment variable name for the salt (default: SESSION_HASH_SALT)
62
+ * @throws Error if not configured
63
+ */
64
+ export declare function getSessionHashSalt(saltEnvVar?: string): string;
65
+ //# sourceMappingURL=env-validation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"env-validation.d.ts","sourceRoot":"","sources":["../../src/env-validation.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAMH;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,4CAA4C;IAC5C,YAAY,CAAC,EAAE,MAAM,EAAE,CAAA;IACvB,oDAAoD;IACpD,aAAa,CAAC,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CAC3D;AA8BD;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,OAAO,CAAA;IACd,OAAO,EAAE,MAAM,EAAE,CAAA;IACjB,QAAQ,EAAE,MAAM,EAAE,CAAA;CACnB;AAED;;;;;;;;GAQG;AACH,wBAAgB,WAAW,CAAC,MAAM,CAAC,EAAE,mBAAmB,GAAG,mBAAmB,CAkC7E;AAED;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,CAAC,EAAE,mBAAmB,GAAG,IAAI,CAsBrE;AAED;;GAEG;AACH,wBAAgB,yBAAyB,IAAI,OAAO,CAEnD;AAED;;GAEG;AACH,wBAAgB,sBAAsB,IAAI,OAAO,CAEhD;AAED;;GAEG;AACH,wBAAgB,uBAAuB,IAAI,OAAO,CAGjD;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,UAAU,SAAsB,GAAG,MAAM,CAM3E"}
@@ -0,0 +1,134 @@
1
+ /**
2
+ * Environment Variable Validation
3
+ *
4
+ * Validates required environment variables on startup.
5
+ * Fails fast if critical security variables are missing in production.
6
+ */
7
+ import { createLogger } from './logger';
8
+ const log = createLogger('env-validation');
9
+ /**
10
+ * Default required environment variables for production
11
+ * These are critical for security and must be present
12
+ */
13
+ const DEFAULT_REQUIRED_VARS = [
14
+ 'LINEAR_WEBHOOK_SECRET',
15
+ 'CRON_SECRET',
16
+ 'WORKER_API_KEY',
17
+ 'SESSION_HASH_SALT',
18
+ ];
19
+ /**
20
+ * Default minimum length validations
21
+ */
22
+ const DEFAULT_MIN_LENGTH_VARS = [
23
+ { name: 'SESSION_HASH_SALT', minLength: 32 },
24
+ ];
25
+ /**
26
+ * Check if running in production environment
27
+ */
28
+ function isProduction() {
29
+ return (process.env.NODE_ENV === 'production' ||
30
+ process.env.VERCEL_ENV === 'production');
31
+ }
32
+ /**
33
+ * Validate environment variables
34
+ *
35
+ * In production: All required vars must be present
36
+ * In development: Log warnings for missing vars but don't fail
37
+ *
38
+ * @param config - Optional configuration to override defaults
39
+ * @returns Validation result with missing vars
40
+ */
41
+ export function validateEnv(config) {
42
+ const missing = [];
43
+ const warnings = [];
44
+ const requiredVars = config?.requiredVars ?? DEFAULT_REQUIRED_VARS;
45
+ const minLengthVars = config?.minLengthVars ?? DEFAULT_MIN_LENGTH_VARS;
46
+ for (const varName of requiredVars) {
47
+ if (!process.env[varName]) {
48
+ if (isProduction()) {
49
+ missing.push(varName);
50
+ }
51
+ else {
52
+ warnings.push(varName);
53
+ }
54
+ }
55
+ }
56
+ // Additional validation for minimum length variables
57
+ for (const { name, minLength } of minLengthVars) {
58
+ const value = process.env[name];
59
+ if (value && value.length < minLength) {
60
+ const msg = `${name} should be at least ${minLength} characters for security`;
61
+ if (isProduction()) {
62
+ missing.push(`${name} (too short)`);
63
+ }
64
+ else {
65
+ warnings.push(msg);
66
+ }
67
+ }
68
+ }
69
+ return {
70
+ valid: missing.length === 0,
71
+ missing,
72
+ warnings,
73
+ };
74
+ }
75
+ /**
76
+ * Validate and fail fast if critical vars are missing
77
+ *
78
+ * Call this at application startup to ensure required
79
+ * environment variables are configured.
80
+ *
81
+ * @param config - Optional configuration to override defaults
82
+ * @throws Error if required vars are missing in production
83
+ */
84
+ export function validateEnvOrThrow(config) {
85
+ const result = validateEnv(config);
86
+ // Log warnings for development
87
+ if (result.warnings.length > 0) {
88
+ log.warn('Missing environment variables (development mode)', {
89
+ variables: result.warnings,
90
+ hint: 'These are required in production',
91
+ });
92
+ }
93
+ // Fail in production if required vars are missing
94
+ if (!result.valid) {
95
+ const errorMsg = `Missing required environment variables: ${result.missing.join(', ')}`;
96
+ log.error(errorMsg, {
97
+ missing: result.missing,
98
+ environment: process.env.NODE_ENV,
99
+ });
100
+ throw new Error(errorMsg);
101
+ }
102
+ log.debug('Environment validation passed');
103
+ }
104
+ /**
105
+ * Check if webhook signature verification is configured
106
+ */
107
+ export function isWebhookSecretConfigured() {
108
+ return !!process.env.LINEAR_WEBHOOK_SECRET;
109
+ }
110
+ /**
111
+ * Check if cron authentication is configured
112
+ */
113
+ export function isCronSecretConfigured() {
114
+ return !!process.env.CRON_SECRET;
115
+ }
116
+ /**
117
+ * Check if session hashing is configured
118
+ */
119
+ export function isSessionHashConfigured() {
120
+ const salt = process.env.SESSION_HASH_SALT;
121
+ return !!salt && salt.length >= 32;
122
+ }
123
+ /**
124
+ * Get session hash salt
125
+ * @param saltEnvVar - Environment variable name for the salt (default: SESSION_HASH_SALT)
126
+ * @throws Error if not configured
127
+ */
128
+ export function getSessionHashSalt(saltEnvVar = 'SESSION_HASH_SALT') {
129
+ const salt = process.env[saltEnvVar];
130
+ if (!salt) {
131
+ throw new Error(`${saltEnvVar} not configured`);
132
+ }
133
+ return salt;
134
+ }
@@ -1,3 +1,4 @@
1
+ export * from './logger';
1
2
  export * from './types';
2
3
  export * from './redis';
3
4
  export * from './session-storage';
@@ -6,4 +7,11 @@ export * from './worker-storage';
6
7
  export * from './issue-lock';
7
8
  export * from './agent-tracking';
8
9
  export * from './webhook-idempotency';
10
+ export * from './pending-prompts';
11
+ export * from './orphan-cleanup';
12
+ export * from './worker-auth';
13
+ export * from './session-hash';
14
+ export * from './rate-limit';
15
+ export * from './token-storage';
16
+ export * from './env-validation';
9
17
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AACA,cAAc,SAAS,CAAA;AAGvB,cAAc,SAAS,CAAA;AAGvB,cAAc,mBAAmB,CAAA;AAGjC,cAAc,cAAc,CAAA;AAG5B,cAAc,kBAAkB,CAAA;AAGhC,cAAc,cAAc,CAAA;AAG5B,cAAc,kBAAkB,CAAA;AAGhC,cAAc,uBAAuB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AACA,cAAc,UAAU,CAAA;AAGxB,cAAc,SAAS,CAAA;AAGvB,cAAc,SAAS,CAAA;AAGvB,cAAc,mBAAmB,CAAA;AAGjC,cAAc,cAAc,CAAA;AAG5B,cAAc,kBAAkB,CAAA;AAGhC,cAAc,cAAc,CAAA;AAG5B,cAAc,kBAAkB,CAAA;AAGhC,cAAc,uBAAuB,CAAA;AAGrC,cAAc,mBAAmB,CAAA;AAGjC,cAAc,kBAAkB,CAAA;AAGhC,cAAc,eAAe,CAAA;AAG7B,cAAc,gBAAgB,CAAA;AAG9B,cAAc,cAAc,CAAA;AAG5B,cAAc,iBAAiB,CAAA;AAG/B,cAAc,kBAAkB,CAAA"}
package/dist/src/index.js CHANGED
@@ -1,3 +1,5 @@
1
+ // Logger
2
+ export * from './logger';
1
3
  // Types
2
4
  export * from './types';
3
5
  // Redis client
@@ -14,3 +16,17 @@ export * from './issue-lock';
14
16
  export * from './agent-tracking';
15
17
  // Webhook idempotency
16
18
  export * from './webhook-idempotency';
19
+ // Pending prompts
20
+ export * from './pending-prompts';
21
+ // Orphan cleanup
22
+ export * from './orphan-cleanup';
23
+ // Worker authentication
24
+ export * from './worker-auth';
25
+ // Session hashing
26
+ export * from './session-hash';
27
+ // Rate limiting
28
+ export * from './rate-limit';
29
+ // Token storage
30
+ export * from './token-storage';
31
+ // Environment validation
32
+ export * from './env-validation';
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Structured JSON Logger for AgentFactory Server
3
+ *
4
+ * Provides consistent, structured logging with:
5
+ * - JSON output format for log aggregation
6
+ * - Log levels (debug, info, warn, error)
7
+ * - Context fields (requestId, sessionId, issueId, etc.)
8
+ * - Automatic timestamps
9
+ */
10
+ export type LogLevel = 'debug' | 'info' | 'warn' | 'error';
11
+ export interface LogContext {
12
+ /** Unique request identifier for tracing */
13
+ requestId?: string;
14
+ /** Linear agent session ID */
15
+ sessionId?: string;
16
+ /** Linear issue ID */
17
+ issueId?: string;
18
+ /** Linear issue identifier (e.g., SUP-123) */
19
+ issueIdentifier?: string;
20
+ /** Linear workspace/organization ID */
21
+ workspaceId?: string;
22
+ /** Duration in milliseconds */
23
+ durationMs?: number;
24
+ /** Error object for error logs */
25
+ error?: Error | unknown;
26
+ /** Any additional context fields */
27
+ [key: string]: unknown;
28
+ }
29
+ /**
30
+ * Logger class for structured logging
31
+ */
32
+ declare class Logger {
33
+ private service;
34
+ private defaultContext;
35
+ private minLevel;
36
+ private jsonEnabled;
37
+ constructor(service: string, defaultContext?: LogContext);
38
+ /**
39
+ * Create a child logger with additional default context
40
+ */
41
+ child(context: LogContext): Logger;
42
+ /**
43
+ * Check if a log level should be output
44
+ */
45
+ private shouldLog;
46
+ /**
47
+ * Format and output a log entry
48
+ */
49
+ private log;
50
+ /**
51
+ * Output JSON formatted log
52
+ */
53
+ private outputJson;
54
+ /**
55
+ * Output human-readable log for development
56
+ */
57
+ private outputPretty;
58
+ debug(message: string, context?: LogContext): void;
59
+ info(message: string, context?: LogContext): void;
60
+ warn(message: string, context?: LogContext): void;
61
+ error(message: string, context?: LogContext): void;
62
+ }
63
+ /**
64
+ * Create a logger instance for a service
65
+ */
66
+ export declare function createLogger(service: string, defaultContext?: LogContext): Logger;
67
+ /**
68
+ * Generate a unique request ID
69
+ */
70
+ export declare function generateRequestId(): string;
71
+ /**
72
+ * Default logger instance for the server
73
+ */
74
+ export declare const logger: Logger;
75
+ export {};
76
+ //# sourceMappingURL=logger.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../src/logger.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,MAAM,MAAM,QAAQ,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAA;AAE1D,MAAM,WAAW,UAAU;IACzB,4CAA4C;IAC5C,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,8BAA8B;IAC9B,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,sBAAsB;IACtB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,8CAA8C;IAC9C,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,uCAAuC;IACvC,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,+BAA+B;IAC/B,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,kCAAkC;IAClC,KAAK,CAAC,EAAE,KAAK,GAAG,OAAO,CAAA;IACvB,oCAAoC;IACpC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;CACvB;AAgFD;;GAEG;AACH,cAAM,MAAM;IACV,OAAO,CAAC,OAAO,CAAQ;IACvB,OAAO,CAAC,cAAc,CAAY;IAClC,OAAO,CAAC,QAAQ,CAAU;IAC1B,OAAO,CAAC,WAAW,CAAS;gBAEhB,OAAO,EAAE,MAAM,EAAE,cAAc,GAAE,UAAe;IAO5D;;OAEG;IACH,KAAK,CAAC,OAAO,EAAE,UAAU,GAAG,MAAM;IAQlC;;OAEG;IACH,OAAO,CAAC,SAAS;IAIjB;;OAEG;IACH,OAAO,CAAC,GAAG;IAuBX;;OAEG;IACH,OAAO,CAAC,UAAU;IAclB;;OAEG;IACH,OAAO,CAAC,YAAY;IAiDpB,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,UAAU,GAAG,IAAI;IAIlD,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,UAAU,GAAG,IAAI;IAIjD,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,UAAU,GAAG,IAAI;IAIjD,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,UAAU,GAAG,IAAI;CAGnD;AAED;;GAEG;AACH,wBAAgB,YAAY,CAC1B,OAAO,EAAE,MAAM,EACf,cAAc,GAAE,UAAe,GAC9B,MAAM,CAER;AAED;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,MAAM,CAE1C;AAED;;GAEG;AACH,eAAO,MAAM,MAAM,QAAsC,CAAA"}
@@ -0,0 +1,218 @@
1
+ /**
2
+ * Structured JSON Logger for AgentFactory Server
3
+ *
4
+ * Provides consistent, structured logging with:
5
+ * - JSON output format for log aggregation
6
+ * - Log levels (debug, info, warn, error)
7
+ * - Context fields (requestId, sessionId, issueId, etc.)
8
+ * - Automatic timestamps
9
+ */
10
+ /**
11
+ * Log level priority for filtering
12
+ */
13
+ const LOG_LEVEL_PRIORITY = {
14
+ debug: 0,
15
+ info: 1,
16
+ warn: 2,
17
+ error: 3,
18
+ };
19
+ /**
20
+ * Get the minimum log level from environment
21
+ * Defaults to 'info' in production, 'debug' in development
22
+ */
23
+ function getMinLogLevel() {
24
+ const envLevel = process.env.LOG_LEVEL?.toLowerCase();
25
+ if (envLevel && envLevel in LOG_LEVEL_PRIORITY) {
26
+ return envLevel;
27
+ }
28
+ return process.env.NODE_ENV === 'production' ? 'info' : 'debug';
29
+ }
30
+ /**
31
+ * Check if JSON logging is enabled
32
+ * Defaults to true in production, false in development for readability
33
+ */
34
+ function isJsonLoggingEnabled() {
35
+ const envValue = process.env.LOG_JSON;
36
+ if (envValue !== undefined) {
37
+ return envValue === 'true' || envValue === '1';
38
+ }
39
+ return process.env.NODE_ENV === 'production';
40
+ }
41
+ /**
42
+ * Format an error for logging
43
+ */
44
+ function formatError(error) {
45
+ if (error instanceof Error) {
46
+ return {
47
+ name: error.name,
48
+ message: error.message,
49
+ stack: error.stack,
50
+ ...(error.cause ? { cause: formatError(error.cause) } : {}),
51
+ };
52
+ }
53
+ return { message: String(error) };
54
+ }
55
+ /**
56
+ * Format context for logging, handling special cases
57
+ */
58
+ function formatContext(context) {
59
+ const formatted = {};
60
+ for (const [key, value] of Object.entries(context)) {
61
+ if (value === undefined)
62
+ continue;
63
+ if (key === 'error') {
64
+ formatted.error = formatError(value);
65
+ }
66
+ else if (value instanceof Error) {
67
+ formatted[key] = formatError(value);
68
+ }
69
+ else {
70
+ formatted[key] = value;
71
+ }
72
+ }
73
+ return formatted;
74
+ }
75
+ /**
76
+ * Logger class for structured logging
77
+ */
78
+ class Logger {
79
+ service;
80
+ defaultContext;
81
+ minLevel;
82
+ jsonEnabled;
83
+ constructor(service, defaultContext = {}) {
84
+ this.service = service;
85
+ this.defaultContext = defaultContext;
86
+ this.minLevel = getMinLogLevel();
87
+ this.jsonEnabled = isJsonLoggingEnabled();
88
+ }
89
+ /**
90
+ * Create a child logger with additional default context
91
+ */
92
+ child(context) {
93
+ const child = new Logger(this.service, {
94
+ ...this.defaultContext,
95
+ ...context,
96
+ });
97
+ return child;
98
+ }
99
+ /**
100
+ * Check if a log level should be output
101
+ */
102
+ shouldLog(level) {
103
+ return LOG_LEVEL_PRIORITY[level] >= LOG_LEVEL_PRIORITY[this.minLevel];
104
+ }
105
+ /**
106
+ * Format and output a log entry
107
+ */
108
+ log(level, message, context = {}) {
109
+ if (!this.shouldLog(level))
110
+ return;
111
+ const mergedContext = { ...this.defaultContext, ...context };
112
+ const formattedContext = formatContext(mergedContext);
113
+ const entry = {
114
+ timestamp: new Date().toISOString(),
115
+ level,
116
+ message,
117
+ service: this.service,
118
+ ...(Object.keys(formattedContext).length > 0
119
+ ? { context: formattedContext }
120
+ : {}),
121
+ };
122
+ if (this.jsonEnabled) {
123
+ this.outputJson(level, entry);
124
+ }
125
+ else {
126
+ this.outputPretty(level, entry);
127
+ }
128
+ }
129
+ /**
130
+ * Output JSON formatted log
131
+ */
132
+ outputJson(level, entry) {
133
+ const output = JSON.stringify(entry);
134
+ switch (level) {
135
+ case 'error':
136
+ console.error(output);
137
+ break;
138
+ case 'warn':
139
+ console.warn(output);
140
+ break;
141
+ default:
142
+ console.log(output);
143
+ }
144
+ }
145
+ /**
146
+ * Output human-readable log for development
147
+ */
148
+ outputPretty(level, entry) {
149
+ const levelColors = {
150
+ debug: '\x1b[36m', // cyan
151
+ info: '\x1b[32m', // green
152
+ warn: '\x1b[33m', // yellow
153
+ error: '\x1b[31m', // red
154
+ };
155
+ const reset = '\x1b[0m';
156
+ const dim = '\x1b[2m';
157
+ const color = levelColors[level];
158
+ const levelStr = level.toUpperCase().padEnd(5);
159
+ const time = entry.timestamp.split('T')[1].replace('Z', '');
160
+ let output = `${dim}${time}${reset} ${color}${levelStr}${reset} [${entry.service}] ${entry.message}`;
161
+ if (entry.context && Object.keys(entry.context).length > 0) {
162
+ const contextStr = Object.entries(entry.context)
163
+ .filter(([key]) => key !== 'error')
164
+ .map(([key, value]) => `${key}=${JSON.stringify(value)}`)
165
+ .join(' ');
166
+ if (contextStr) {
167
+ output += ` ${dim}${contextStr}${reset}`;
168
+ }
169
+ // Print error details on separate lines
170
+ if (entry.context.error) {
171
+ const err = entry.context.error;
172
+ output += `\n ${color}${err.name}: ${err.message}${reset}`;
173
+ if (err.stack) {
174
+ const stackLines = err.stack.split('\n').slice(1, 4);
175
+ output += `\n${dim}${stackLines.join('\n')}${reset}`;
176
+ }
177
+ }
178
+ }
179
+ switch (level) {
180
+ case 'error':
181
+ console.error(output);
182
+ break;
183
+ case 'warn':
184
+ console.warn(output);
185
+ break;
186
+ default:
187
+ console.log(output);
188
+ }
189
+ }
190
+ debug(message, context) {
191
+ this.log('debug', message, context);
192
+ }
193
+ info(message, context) {
194
+ this.log('info', message, context);
195
+ }
196
+ warn(message, context) {
197
+ this.log('warn', message, context);
198
+ }
199
+ error(message, context) {
200
+ this.log('error', message, context);
201
+ }
202
+ }
203
+ /**
204
+ * Create a logger instance for a service
205
+ */
206
+ export function createLogger(service, defaultContext = {}) {
207
+ return new Logger(service, defaultContext);
208
+ }
209
+ /**
210
+ * Generate a unique request ID
211
+ */
212
+ export function generateRequestId() {
213
+ return `req_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
214
+ }
215
+ /**
216
+ * Default logger instance for the server
217
+ */
218
+ export const logger = createLogger('agentfactory-server');
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Orphan Cleanup Module
3
+ *
4
+ * Detects and handles orphaned sessions - sessions marked as running/claimed
5
+ * but whose worker is no longer active (heartbeat timeout).
6
+ *
7
+ * When a worker disconnects, the work is re-queued for another worker to resume.
8
+ * The Linear issue status is NOT rolled back - the issue remains in its current
9
+ * workflow state and the next worker will resume from where the previous one left off.
10
+ */
11
+ import { type AgentSessionState } from './session-storage';
12
+ /**
13
+ * Callback for when an orphaned session is re-queued
14
+ */
15
+ export interface OrphanCleanupCallbacks {
16
+ /** Called when an orphaned session is re-queued. Use to post Linear comments, etc. */
17
+ onOrphanRequeued?: (session: AgentSessionState) => Promise<void>;
18
+ /** Called when a zombie pending session is recovered. Use to post Linear comments, etc. */
19
+ onZombieRecovered?: (session: AgentSessionState) => Promise<void>;
20
+ }
21
+ export interface OrphanCleanupResult {
22
+ checked: number;
23
+ orphaned: number;
24
+ requeued: number;
25
+ failed: number;
26
+ details: Array<{
27
+ sessionId: string;
28
+ issueIdentifier: string;
29
+ action: 'requeued' | 'failed';
30
+ reason?: string;
31
+ /** Path to worktree that may need cleanup (if on worker machine) */
32
+ worktreePath?: string;
33
+ }>;
34
+ /** Worktree paths that need cleanup on worker machines */
35
+ worktreePathsToCleanup: string[];
36
+ }
37
+ /**
38
+ * Find sessions that are orphaned (running/claimed but worker is gone)
39
+ */
40
+ export declare function findOrphanedSessions(): Promise<AgentSessionState[]>;
41
+ /**
42
+ * Find zombie pending sessions — sessions stuck in `pending` status
43
+ * that have no corresponding entry in the work queue or any issue-pending queue.
44
+ *
45
+ * These arise when:
46
+ * - claimWork() removes from queue, but claimSession() fails and requeue also fails
47
+ * - Issue lock expires but promotion fails silently
48
+ */
49
+ export declare function findZombiePendingSessions(): Promise<AgentSessionState[]>;
50
+ /**
51
+ * Clean up orphaned sessions by re-queuing them
52
+ *
53
+ * @param callbacks - Optional callbacks for external integrations (e.g., posting Linear comments)
54
+ */
55
+ export declare function cleanupOrphanedSessions(callbacks?: OrphanCleanupCallbacks): Promise<OrphanCleanupResult>;
56
+ export declare function shouldRunCleanup(): boolean;
57
+ /**
58
+ * Run cleanup if enough time has passed (debounced)
59
+ * Safe to call frequently - will only actually run periodically
60
+ *
61
+ * @param callbacks - Optional callbacks for external integrations
62
+ */
63
+ export declare function maybeCleanupOrphans(callbacks?: OrphanCleanupCallbacks): Promise<OrphanCleanupResult | null>;
64
+ //# sourceMappingURL=orphan-cleanup.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"orphan-cleanup.d.ts","sourceRoot":"","sources":["../../src/orphan-cleanup.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,OAAO,EAGL,KAAK,iBAAiB,EACvB,MAAM,mBAAmB,CAAA;AAmB1B;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACrC,sFAAsF;IACtF,gBAAgB,CAAC,EAAE,CAAC,OAAO,EAAE,iBAAiB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IAChE,2FAA2F;IAC3F,iBAAiB,CAAC,EAAE,CAAC,OAAO,EAAE,iBAAiB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;CAClE;AAED,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,MAAM,CAAA;IACf,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,KAAK,CAAC;QACb,SAAS,EAAE,MAAM,CAAA;QACjB,eAAe,EAAE,MAAM,CAAA;QACvB,MAAM,EAAE,UAAU,GAAG,QAAQ,CAAA;QAC7B,MAAM,CAAC,EAAE,MAAM,CAAA;QACf,oEAAoE;QACpE,YAAY,CAAC,EAAE,MAAM,CAAA;KACtB,CAAC,CAAA;IACF,0DAA0D;IAC1D,sBAAsB,EAAE,MAAM,EAAE,CAAA;CACjC;AAED;;GAEG;AACH,wBAAsB,oBAAoB,IAAI,OAAO,CAAC,iBAAiB,EAAE,CAAC,CA4CzE;AAKD;;;;;;;GAOG;AACH,wBAAsB,yBAAyB,IAAI,OAAO,CAAC,iBAAiB,EAAE,CAAC,CAiC9E;AAED;;;;GAIG;AACH,wBAAsB,uBAAuB,CAC3C,SAAS,CAAC,EAAE,sBAAsB,GACjC,OAAO,CAAC,mBAAmB,CAAC,CA+N9B;AASD,wBAAgB,gBAAgB,IAAI,OAAO,CAO1C;AAED;;;;;GAKG;AACH,wBAAsB,mBAAmB,CACvC,SAAS,CAAC,EAAE,sBAAsB,GACjC,OAAO,CAAC,mBAAmB,GAAG,IAAI,CAAC,CAKrC"}