@telora/daemon-core 0.2.5

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.
Files changed (81) hide show
  1. package/dist/activity-tracker.d.ts +69 -0
  2. package/dist/activity-tracker.d.ts.map +1 -0
  3. package/dist/activity-tracker.js +155 -0
  4. package/dist/activity-tracker.js.map +1 -0
  5. package/dist/api-client.d.ts +94 -0
  6. package/dist/api-client.d.ts.map +1 -0
  7. package/dist/api-client.js +145 -0
  8. package/dist/api-client.js.map +1 -0
  9. package/dist/config.d.ts +117 -0
  10. package/dist/config.d.ts.map +1 -0
  11. package/dist/config.js +348 -0
  12. package/dist/config.js.map +1 -0
  13. package/dist/engine.d.ts +120 -0
  14. package/dist/engine.d.ts.map +1 -0
  15. package/dist/engine.js +18 -0
  16. package/dist/engine.js.map +1 -0
  17. package/dist/escalation-types.d.ts +31 -0
  18. package/dist/escalation-types.d.ts.map +1 -0
  19. package/dist/escalation-types.js +24 -0
  20. package/dist/escalation-types.js.map +1 -0
  21. package/dist/event-logger.d.ts +13 -0
  22. package/dist/event-logger.d.ts.map +1 -0
  23. package/dist/event-logger.js +82 -0
  24. package/dist/event-logger.js.map +1 -0
  25. package/dist/git.d.ts +39 -0
  26. package/dist/git.d.ts.map +1 -0
  27. package/dist/git.js +72 -0
  28. package/dist/git.js.map +1 -0
  29. package/dist/index.d.ts +20 -0
  30. package/dist/index.d.ts.map +1 -0
  31. package/dist/index.js +36 -0
  32. package/dist/index.js.map +1 -0
  33. package/dist/lifecycle.d.ts +104 -0
  34. package/dist/lifecycle.d.ts.map +1 -0
  35. package/dist/lifecycle.js +192 -0
  36. package/dist/lifecycle.js.map +1 -0
  37. package/dist/log-manager.d.ts +83 -0
  38. package/dist/log-manager.d.ts.map +1 -0
  39. package/dist/log-manager.js +217 -0
  40. package/dist/log-manager.js.map +1 -0
  41. package/dist/otel-env.d.ts +29 -0
  42. package/dist/otel-env.d.ts.map +1 -0
  43. package/dist/otel-env.js +44 -0
  44. package/dist/otel-env.js.map +1 -0
  45. package/dist/resilience.d.ts +127 -0
  46. package/dist/resilience.d.ts.map +1 -0
  47. package/dist/resilience.js +300 -0
  48. package/dist/resilience.js.map +1 -0
  49. package/dist/resource-governor.d.ts +83 -0
  50. package/dist/resource-governor.d.ts.map +1 -0
  51. package/dist/resource-governor.js +184 -0
  52. package/dist/resource-governor.js.map +1 -0
  53. package/dist/spawn.d.ts +72 -0
  54. package/dist/spawn.d.ts.map +1 -0
  55. package/dist/spawn.js +82 -0
  56. package/dist/spawn.js.map +1 -0
  57. package/dist/stream-json.d.ts +885 -0
  58. package/dist/stream-json.d.ts.map +1 -0
  59. package/dist/stream-json.js +298 -0
  60. package/dist/stream-json.js.map +1 -0
  61. package/dist/token-usage.d.ts +67 -0
  62. package/dist/token-usage.d.ts.map +1 -0
  63. package/dist/token-usage.js +150 -0
  64. package/dist/token-usage.js.map +1 -0
  65. package/dist/transforms.d.ts +64 -0
  66. package/dist/transforms.d.ts.map +1 -0
  67. package/dist/transforms.js +78 -0
  68. package/dist/transforms.js.map +1 -0
  69. package/dist/unified-config.d.ts +62 -0
  70. package/dist/unified-config.d.ts.map +1 -0
  71. package/dist/unified-config.js +155 -0
  72. package/dist/unified-config.js.map +1 -0
  73. package/dist/workflow-types.d.ts +202 -0
  74. package/dist/workflow-types.d.ts.map +1 -0
  75. package/dist/workflow-types.js +15 -0
  76. package/dist/workflow-types.js.map +1 -0
  77. package/dist/worktree.d.ts +92 -0
  78. package/dist/worktree.d.ts.map +1 -0
  79. package/dist/worktree.js +221 -0
  80. package/dist/worktree.js.map +1 -0
  81. package/package.json +57 -0
@@ -0,0 +1,300 @@
1
+ /**
2
+ * Resilience utilities for Telora daemon and factory.
3
+ *
4
+ * Provides retry with exponential backoff and circuit breaker patterns
5
+ * to make API calls and other operations robust against transient failures.
6
+ */
7
+ // ============================================
8
+ // Retry with Exponential Backoff
9
+ // ============================================
10
+ /** Error codes that indicate transient network failures worth retrying. */
11
+ const RETRYABLE_ERROR_CODES = new Set([
12
+ 'ECONNRESET',
13
+ 'ECONNREFUSED',
14
+ 'ETIMEDOUT',
15
+ 'ENOTFOUND',
16
+ 'EAI_AGAIN',
17
+ 'UND_ERR_SOCKET',
18
+ 'UND_ERR_CONNECT_TIMEOUT',
19
+ 'UND_ERR_HEADERS_TIMEOUT',
20
+ 'UND_ERR_BODY_TIMEOUT',
21
+ ]);
22
+ /** HTTP status codes that are retryable. */
23
+ const RETRYABLE_HTTP_STATUSES = new Set([429, 502, 503]);
24
+ /** HTTP status codes that should never be retried. */
25
+ const NON_RETRYABLE_HTTP_STATUSES = new Set([400, 401, 403, 404, 422]);
26
+ /**
27
+ * Custom error class that carries HTTP status information.
28
+ */
29
+ export class ApiError extends Error {
30
+ statusCode;
31
+ isRetryable;
32
+ constructor(message, statusCode, isRetryable = false) {
33
+ super(message);
34
+ this.statusCode = statusCode;
35
+ this.isRetryable = isRetryable;
36
+ this.name = 'ApiError';
37
+ }
38
+ }
39
+ /**
40
+ * Determine if an error is retryable.
41
+ */
42
+ export function isRetryableError(error) {
43
+ if (error instanceof ApiError) {
44
+ return error.isRetryable;
45
+ }
46
+ // Check for network error codes
47
+ const err = error;
48
+ const code = err.code || err.cause?.code;
49
+ if (code && RETRYABLE_ERROR_CODES.has(code)) {
50
+ return true;
51
+ }
52
+ // Check error message for common transient patterns
53
+ const message = error.message || '';
54
+ if (message.includes('UND_ERR_SOCKET') ||
55
+ message.includes('UND_ERR_CONNECT_TIMEOUT') ||
56
+ message.includes('ConnectTimeoutError') ||
57
+ message.includes('fetch failed')) {
58
+ return true;
59
+ }
60
+ return false;
61
+ }
62
+ /**
63
+ * Classify an HTTP response status code for retry decisions.
64
+ */
65
+ export function classifyHttpStatus(status) {
66
+ if (RETRYABLE_HTTP_STATUSES.has(status))
67
+ return 'retryable';
68
+ if (NON_RETRYABLE_HTTP_STATUSES.has(status))
69
+ return 'non-retryable';
70
+ if (status >= 500)
71
+ return 'retryable'; // Other 5xx errors are generally retryable
72
+ return 'non-retryable';
73
+ }
74
+ /**
75
+ * Execute an async function with retry and exponential backoff.
76
+ *
77
+ * Only retries on errors classified as retryable. Non-retryable errors
78
+ * are thrown immediately.
79
+ */
80
+ export async function withRetry(fn, options = {}) {
81
+ const { maxAttempts = 3, baseDelayMs = 1000, label = 'operation' } = options;
82
+ let lastError;
83
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
84
+ try {
85
+ return await fn();
86
+ }
87
+ catch (error) {
88
+ lastError = error;
89
+ if (!isRetryableError(error) || attempt === maxAttempts) {
90
+ throw error;
91
+ }
92
+ const delayMs = baseDelayMs * Math.pow(2, attempt - 1);
93
+ console.warn(`[retry] ${label} failed (attempt ${attempt}/${maxAttempts}): ${error.message}. Retrying in ${delayMs}ms...`);
94
+ await sleep(delayMs);
95
+ }
96
+ }
97
+ // Should not be reached, but satisfy TypeScript
98
+ throw lastError;
99
+ }
100
+ /** Global registry of all circuit breaker instances for force-close support. */
101
+ const circuitBreakerRegistry = [];
102
+ /**
103
+ * Get all registered circuit breaker instances.
104
+ */
105
+ export function getCircuitBreakers() {
106
+ return circuitBreakerRegistry;
107
+ }
108
+ /**
109
+ * Force-close all registered circuit breakers. Used by SIGUSR2 handler.
110
+ */
111
+ export function forceCloseAllCircuitBreakers() {
112
+ for (const cb of circuitBreakerRegistry) {
113
+ cb.forceClose();
114
+ }
115
+ console.log(`All circuit breakers force-closed via operator override (${circuitBreakerRegistry.length} instance(s))`);
116
+ }
117
+ /**
118
+ * Circuit breaker to prevent hammering a failing endpoint.
119
+ *
120
+ * States:
121
+ * - closed: Normal operation, requests pass through
122
+ * - open: Requests fail immediately without hitting the network
123
+ * - half-open: Probe requests are allowed; N consecutive successes required to close
124
+ *
125
+ * Features:
126
+ * - Graduated recovery: half-open requires N consecutive successes (default 2)
127
+ * - Exponential cooldown backoff on repeated open cycles
128
+ * - State transition logging with endpoint label and reason
129
+ * - Force-close for operator override (SIGUSR2)
130
+ */
131
+ export class CircuitBreaker {
132
+ state = 'closed';
133
+ consecutiveFailures = 0;
134
+ lastFailureTime = 0;
135
+ halfOpenSuccesses = 0;
136
+ consecutiveOpenCycles = 0;
137
+ /** Tracks whether we already logged the open-state transition to suppress repeat logs. */
138
+ openStateLogged = false;
139
+ /** Count of suppressed CircuitOpenError throws since the last state-change log. */
140
+ suppressedCallCount = 0;
141
+ failureThreshold;
142
+ baseCooldownMs;
143
+ maxCooldownMs;
144
+ halfOpenSuccessThreshold;
145
+ label;
146
+ constructor(options = {}) {
147
+ this.failureThreshold = options.failureThreshold ?? 5;
148
+ this.baseCooldownMs = options.cooldownMs ?? 60000;
149
+ this.maxCooldownMs = options.maxCooldownMs ?? 300000;
150
+ this.halfOpenSuccessThreshold = options.halfOpenSuccessThreshold ?? 2;
151
+ this.label = options.label ?? 'circuit';
152
+ circuitBreakerRegistry.push(this);
153
+ }
154
+ /** Current effective cooldown with exponential backoff. */
155
+ getEffectiveCooldownMs() {
156
+ const cooldown = this.baseCooldownMs * Math.pow(2, this.consecutiveOpenCycles);
157
+ return Math.min(cooldown, this.maxCooldownMs);
158
+ }
159
+ /** Log a state transition. */
160
+ logTransition(from, to, reason) {
161
+ console.log(`[circuit:${this.label}] ${from} -> ${to} (reason: ${reason})`);
162
+ }
163
+ /**
164
+ * Execute a function through the circuit breaker.
165
+ * Throws CircuitOpenError if the circuit is open and cooldown hasn't elapsed.
166
+ */
167
+ async execute(fn) {
168
+ if (this.state === 'open') {
169
+ const effectiveCooldown = this.getEffectiveCooldownMs();
170
+ const elapsed = Date.now() - this.lastFailureTime;
171
+ if (elapsed < effectiveCooldown) {
172
+ // Suppress repeated logs: only count, don't log each blocked call
173
+ this.suppressedCallCount++;
174
+ throw new CircuitOpenError(`[${this.label}] Circuit is open. ${Math.ceil((effectiveCooldown - elapsed) / 1000)}s until next probe.`);
175
+ }
176
+ // Cooldown elapsed, transition to half-open for probe
177
+ this.transitionToHalfOpen();
178
+ }
179
+ try {
180
+ const result = await fn();
181
+ this.onSuccess();
182
+ return result;
183
+ }
184
+ catch (error) {
185
+ this.onFailure();
186
+ throw error;
187
+ }
188
+ }
189
+ /** Transition from open to half-open, logging suppressed call count. */
190
+ transitionToHalfOpen() {
191
+ const prevState = this.state;
192
+ this.state = 'half-open';
193
+ this.halfOpenSuccesses = 0;
194
+ const suppressedNote = this.suppressedCallCount > 0
195
+ ? `, ${this.suppressedCallCount} calls suppressed while open`
196
+ : '';
197
+ this.logTransition(prevState, 'half-open', `cooldown elapsed, allowing probe${suppressedNote}`);
198
+ this.suppressedCallCount = 0;
199
+ this.openStateLogged = false;
200
+ }
201
+ onSuccess() {
202
+ if (this.state === 'half-open') {
203
+ this.halfOpenSuccesses++;
204
+ if (this.halfOpenSuccesses >= this.halfOpenSuccessThreshold) {
205
+ this.logTransition('half-open', 'closed', `${this.halfOpenSuccesses} consecutive probe successes`);
206
+ this.state = 'closed';
207
+ this.consecutiveFailures = 0;
208
+ this.halfOpenSuccesses = 0;
209
+ this.consecutiveOpenCycles = 0; // Reset backoff on full recovery
210
+ this.openStateLogged = false;
211
+ this.suppressedCallCount = 0;
212
+ }
213
+ return;
214
+ }
215
+ // In closed state, just reset failure count
216
+ this.consecutiveFailures = 0;
217
+ }
218
+ onFailure() {
219
+ this.consecutiveFailures++;
220
+ this.lastFailureTime = Date.now();
221
+ if (this.state === 'half-open') {
222
+ this.consecutiveOpenCycles++;
223
+ const effectiveCooldown = this.getEffectiveCooldownMs();
224
+ this.logTransition('half-open', 'open', `probe failed, backoff cycle ${this.consecutiveOpenCycles}, cooldown ${(effectiveCooldown / 1000).toFixed(0)}s`);
225
+ this.state = 'open';
226
+ this.halfOpenSuccesses = 0;
227
+ this.openStateLogged = true;
228
+ this.suppressedCallCount = 0;
229
+ return;
230
+ }
231
+ if (this.consecutiveFailures >= this.failureThreshold) {
232
+ this.logTransition('closed', 'open', `${this.consecutiveFailures} consecutive failures`);
233
+ this.state = 'open';
234
+ this.openStateLogged = true;
235
+ this.suppressedCallCount = 0;
236
+ }
237
+ }
238
+ /** Get current circuit state for diagnostics. */
239
+ getState() {
240
+ return this.state;
241
+ }
242
+ /** Get the number of consecutive open cycles (for diagnostics). */
243
+ getConsecutiveOpenCycles() {
244
+ return this.consecutiveOpenCycles;
245
+ }
246
+ /** Reset the circuit breaker to closed state. */
247
+ reset() {
248
+ this.state = 'closed';
249
+ this.consecutiveFailures = 0;
250
+ this.lastFailureTime = 0;
251
+ this.halfOpenSuccesses = 0;
252
+ this.consecutiveOpenCycles = 0;
253
+ this.openStateLogged = false;
254
+ this.suppressedCallCount = 0;
255
+ }
256
+ /**
257
+ * Force-close the circuit breaker. Used for operator override.
258
+ * Resets all state including backoff counters.
259
+ */
260
+ forceClose() {
261
+ const prevState = this.state;
262
+ this.reset();
263
+ this.logTransition(prevState, 'closed', 'operator force-close');
264
+ }
265
+ }
266
+ /**
267
+ * Error thrown when the circuit breaker is open.
268
+ */
269
+ export class CircuitOpenError extends Error {
270
+ constructor(message) {
271
+ super(message);
272
+ this.name = 'CircuitOpenError';
273
+ }
274
+ }
275
+ // ============================================
276
+ // Safe Interval
277
+ // ============================================
278
+ /**
279
+ * Create a setInterval that wraps the callback in try/catch.
280
+ * Prevents unhandled exceptions from crashing the process.
281
+ *
282
+ * @returns The interval handle for clearing with clearInterval()
283
+ */
284
+ export function safeInterval(callback, intervalMs, label) {
285
+ return setInterval(async () => {
286
+ try {
287
+ await callback();
288
+ }
289
+ catch (error) {
290
+ console.error(`[${label}] Unhandled error in interval callback:`, error.message);
291
+ }
292
+ }, intervalMs);
293
+ }
294
+ // ============================================
295
+ // Helpers
296
+ // ============================================
297
+ function sleep(ms) {
298
+ return new Promise((resolve) => setTimeout(resolve, ms));
299
+ }
300
+ //# sourceMappingURL=resilience.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resilience.js","sourceRoot":"","sources":["../src/resilience.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Resource Governor -- global concurrency limiter for Claude Code processes.
3
+ *
4
+ * Enforces both per-engine and global limits on the number of concurrent
5
+ * Claude Code processes. Uses a semaphore pattern with FIFO queuing:
6
+ *
7
+ * - Each engine has its own max (e.g. strategy.maxSessions = 4)
8
+ * - A global max caps the total across all engines (e.g. maxGlobalSessions = 8)
9
+ * - If one engine is idle, the other can use slots up to the global max
10
+ * - When at capacity, acquireSlot() queues and resolves FIFO when slots free up
11
+ */
12
+ /** Configuration for the resource governor. */
13
+ export interface ResourceGovernorConfig {
14
+ /** Maximum total Claude Code processes across all engines. */
15
+ maxGlobalSessions: number;
16
+ /** Per-engine limits. Key = engine name, value = max concurrent sessions. */
17
+ engineLimits: Record<string, number>;
18
+ }
19
+ /** Utilization snapshot returned by getUtilization(). */
20
+ export interface ResourceUtilization {
21
+ /** Total slots currently in use across all engines. */
22
+ total: number;
23
+ /** Slots in use per engine. */
24
+ perEngine: Record<string, number>;
25
+ /** Global maximum. */
26
+ max: number;
27
+ /** Number of queued requests waiting for slots. */
28
+ queueDepth: number;
29
+ /** Queued requests per engine. */
30
+ queueDepthPerEngine: Record<string, number>;
31
+ }
32
+ export declare class ResourceGovernor {
33
+ private readonly maxGlobal;
34
+ private readonly engineLimits;
35
+ /** Current slot count per engine. */
36
+ private readonly activePerEngine;
37
+ /** FIFO queue of pending acquire requests. */
38
+ private readonly queue;
39
+ /** Whether the governor has been shut down (rejects new acquires). */
40
+ private shuttingDown;
41
+ constructor(config: ResourceGovernorConfig);
42
+ /**
43
+ * Acquire a slot for the given engine. Blocks (returns a Promise) until
44
+ * a slot is available within both the engine limit and global limit.
45
+ *
46
+ * Rejects if the governor is shutting down.
47
+ */
48
+ acquireSlot(engine: string): Promise<void>;
49
+ /**
50
+ * Try to acquire a slot without blocking. Returns true if successful,
51
+ * false if at capacity.
52
+ */
53
+ tryAcquireSlot(engine: string): boolean;
54
+ /**
55
+ * Release a slot for the given engine. Processes the next queued
56
+ * request if one is waiting.
57
+ */
58
+ releaseSlot(engine: string): void;
59
+ /** Return a snapshot of current utilization. */
60
+ getUtilization(): ResourceUtilization;
61
+ /**
62
+ * Shut down the governor. Rejects all pending requests and prevents
63
+ * new acquisitions.
64
+ */
65
+ shutdown(): void;
66
+ /**
67
+ * Reset the governor (for testing). Clears all state and re-enables
68
+ * slot acquisition.
69
+ */
70
+ reset(): void;
71
+ /** Check if a slot can be acquired for the given engine right now. */
72
+ private canAcquire;
73
+ /** Increment the active slot count for an engine. */
74
+ private incrementSlot;
75
+ /** Get the total active slots across all engines. */
76
+ private getGlobalActive;
77
+ /**
78
+ * Process the FIFO queue, granting slots to waiting requests
79
+ * that can now be satisfied.
80
+ */
81
+ private processQueue;
82
+ }
83
+ //# sourceMappingURL=resource-governor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resource-governor.d.ts","sourceRoot":"","sources":["../src/resource-governor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAMH,+CAA+C;AAC/C,MAAM,WAAW,sBAAsB;IACrC,8DAA8D;IAC9D,iBAAiB,EAAE,MAAM,CAAC;IAC1B,6EAA6E;IAC7E,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACtC;AAED,yDAAyD;AACzD,MAAM,WAAW,mBAAmB;IAClC,uDAAuD;IACvD,KAAK,EAAE,MAAM,CAAC;IACd,+BAA+B;IAC/B,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAClC,sBAAsB;IACtB,GAAG,EAAE,MAAM,CAAC;IACZ,mDAAmD;IACnD,UAAU,EAAE,MAAM,CAAC;IACnB,kCAAkC;IAClC,mBAAmB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC7C;AAaD,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAsB;IAEnD,qCAAqC;IACrC,OAAO,CAAC,QAAQ,CAAC,eAAe,CAA6B;IAE7D,8CAA8C;IAC9C,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAwB;IAE9C,sEAAsE;IACtE,OAAO,CAAC,YAAY,CAAS;gBAEjB,MAAM,EAAE,sBAAsB;IAe1C;;;;;OAKG;IACH,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAiB1C;;;OAGG;IACH,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO;IAOvC;;;OAGG;IACH,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAUjC,gDAAgD;IAChD,cAAc,IAAI,mBAAmB;IA+BrC;;;OAGG;IACH,QAAQ,IAAI,IAAI;IAShB;;;OAGG;IACH,KAAK,IAAI,IAAI;IAab,sEAAsE;IACtE,OAAO,CAAC,UAAU;IAelB,qDAAqD;IACrD,OAAO,CAAC,aAAa;IAKrB,qDAAqD;IACrD,OAAO,CAAC,eAAe;IAQvB;;;OAGG;IACH,OAAO,CAAC,YAAY;CAcrB"}
@@ -0,0 +1,184 @@
1
+ /**
2
+ * Resource Governor -- global concurrency limiter for Claude Code processes.
3
+ *
4
+ * Enforces both per-engine and global limits on the number of concurrent
5
+ * Claude Code processes. Uses a semaphore pattern with FIFO queuing:
6
+ *
7
+ * - Each engine has its own max (e.g. strategy.maxSessions = 4)
8
+ * - A global max caps the total across all engines (e.g. maxGlobalSessions = 8)
9
+ * - If one engine is idle, the other can use slots up to the global max
10
+ * - When at capacity, acquireSlot() queues and resolves FIFO when slots free up
11
+ */
12
+ // ---------------------------------------------------------------------------
13
+ // ResourceGovernor
14
+ // ---------------------------------------------------------------------------
15
+ export class ResourceGovernor {
16
+ maxGlobal;
17
+ engineLimits;
18
+ /** Current slot count per engine. */
19
+ activePerEngine = new Map();
20
+ /** FIFO queue of pending acquire requests. */
21
+ queue = [];
22
+ /** Whether the governor has been shut down (rejects new acquires). */
23
+ shuttingDown = false;
24
+ constructor(config) {
25
+ if (config.maxGlobalSessions < 1) {
26
+ throw new Error('maxGlobalSessions must be >= 1');
27
+ }
28
+ this.maxGlobal = config.maxGlobalSessions;
29
+ this.engineLimits = new Map(Object.entries(config.engineLimits));
30
+ // Validate per-engine limits
31
+ for (const [engine, limit] of this.engineLimits) {
32
+ if (limit < 1) {
33
+ throw new Error(`Engine limit for '${engine}' must be >= 1, got ${limit}`);
34
+ }
35
+ }
36
+ }
37
+ /**
38
+ * Acquire a slot for the given engine. Blocks (returns a Promise) until
39
+ * a slot is available within both the engine limit and global limit.
40
+ *
41
+ * Rejects if the governor is shutting down.
42
+ */
43
+ acquireSlot(engine) {
44
+ if (this.shuttingDown) {
45
+ return Promise.reject(new Error('ResourceGovernor is shutting down'));
46
+ }
47
+ // Try immediate acquisition
48
+ if (this.canAcquire(engine)) {
49
+ this.incrementSlot(engine);
50
+ return Promise.resolve();
51
+ }
52
+ // Queue the request
53
+ return new Promise((resolve, reject) => {
54
+ this.queue.push({ engine, resolve, reject });
55
+ });
56
+ }
57
+ /**
58
+ * Try to acquire a slot without blocking. Returns true if successful,
59
+ * false if at capacity.
60
+ */
61
+ tryAcquireSlot(engine) {
62
+ if (this.shuttingDown)
63
+ return false;
64
+ if (!this.canAcquire(engine))
65
+ return false;
66
+ this.incrementSlot(engine);
67
+ return true;
68
+ }
69
+ /**
70
+ * Release a slot for the given engine. Processes the next queued
71
+ * request if one is waiting.
72
+ */
73
+ releaseSlot(engine) {
74
+ const current = this.activePerEngine.get(engine) ?? 0;
75
+ if (current <= 0) {
76
+ console.warn(`ResourceGovernor: releaseSlot('${engine}') called with no active slots`);
77
+ return;
78
+ }
79
+ this.activePerEngine.set(engine, current - 1);
80
+ this.processQueue();
81
+ }
82
+ /** Return a snapshot of current utilization. */
83
+ getUtilization() {
84
+ let total = 0;
85
+ const perEngine = {};
86
+ for (const [engine, count] of this.activePerEngine) {
87
+ perEngine[engine] = count;
88
+ total += count;
89
+ }
90
+ // Include engines that have limits but no active slots
91
+ for (const engine of this.engineLimits.keys()) {
92
+ if (!(engine in perEngine)) {
93
+ perEngine[engine] = 0;
94
+ }
95
+ }
96
+ // Queue depth per engine
97
+ const queueDepthPerEngine = {};
98
+ for (const req of this.queue) {
99
+ queueDepthPerEngine[req.engine] = (queueDepthPerEngine[req.engine] ?? 0) + 1;
100
+ }
101
+ return {
102
+ total,
103
+ perEngine,
104
+ max: this.maxGlobal,
105
+ queueDepth: this.queue.length,
106
+ queueDepthPerEngine,
107
+ };
108
+ }
109
+ /**
110
+ * Shut down the governor. Rejects all pending requests and prevents
111
+ * new acquisitions.
112
+ */
113
+ shutdown() {
114
+ this.shuttingDown = true;
115
+ // Reject all queued requests
116
+ while (this.queue.length > 0) {
117
+ const req = this.queue.shift();
118
+ req.reject(new Error('ResourceGovernor is shutting down'));
119
+ }
120
+ }
121
+ /**
122
+ * Reset the governor (for testing). Clears all state and re-enables
123
+ * slot acquisition.
124
+ */
125
+ reset() {
126
+ this.shuttingDown = false;
127
+ this.activePerEngine.clear();
128
+ while (this.queue.length > 0) {
129
+ const req = this.queue.shift();
130
+ req.reject(new Error('ResourceGovernor reset'));
131
+ }
132
+ }
133
+ // -------------------------------------------------------------------------
134
+ // Private
135
+ // -------------------------------------------------------------------------
136
+ /** Check if a slot can be acquired for the given engine right now. */
137
+ canAcquire(engine) {
138
+ // Check global limit
139
+ const globalCurrent = this.getGlobalActive();
140
+ if (globalCurrent >= this.maxGlobal)
141
+ return false;
142
+ // Check per-engine limit (if configured)
143
+ const engineLimit = this.engineLimits.get(engine);
144
+ if (engineLimit !== undefined) {
145
+ const engineCurrent = this.activePerEngine.get(engine) ?? 0;
146
+ if (engineCurrent >= engineLimit)
147
+ return false;
148
+ }
149
+ return true;
150
+ }
151
+ /** Increment the active slot count for an engine. */
152
+ incrementSlot(engine) {
153
+ const current = this.activePerEngine.get(engine) ?? 0;
154
+ this.activePerEngine.set(engine, current + 1);
155
+ }
156
+ /** Get the total active slots across all engines. */
157
+ getGlobalActive() {
158
+ let total = 0;
159
+ for (const count of this.activePerEngine.values()) {
160
+ total += count;
161
+ }
162
+ return total;
163
+ }
164
+ /**
165
+ * Process the FIFO queue, granting slots to waiting requests
166
+ * that can now be satisfied.
167
+ */
168
+ processQueue() {
169
+ let i = 0;
170
+ while (i < this.queue.length) {
171
+ const req = this.queue[i];
172
+ if (this.canAcquire(req.engine)) {
173
+ this.queue.splice(i, 1);
174
+ this.incrementSlot(req.engine);
175
+ req.resolve();
176
+ // Don't increment i -- array shifted
177
+ }
178
+ else {
179
+ i++;
180
+ }
181
+ }
182
+ }
183
+ }
184
+ //# sourceMappingURL=resource-governor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resource-governor.js","sourceRoot":"","sources":["../src/resource-governor.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Shared Claude Code process spawning utilities.
3
+ *
4
+ * Provides common building blocks for spawning Claude Code CLI processes:
5
+ * - CLI argument construction for stream-json mode
6
+ * - Environment variable stripping (remove inherited Claude Code session vars)
7
+ * - Log file creation with restrictive permissions
8
+ *
9
+ * Both the Telora daemon and factory engines use these primitives,
10
+ * appending their own engine-specific options on top.
11
+ */
12
+ import { type WriteStream } from 'node:fs';
13
+ /**
14
+ * Options for building stream-json CLI arguments.
15
+ *
16
+ * Only the common, cross-engine flags are included here. Engine-specific
17
+ * flags (e.g. --teammate-mode, --setting-sources) should be appended by
18
+ * the caller after calling `buildStreamJsonArgs`.
19
+ */
20
+ export interface StreamJsonArgsOptions {
21
+ /** Path to an MCP config file. Resolved to an absolute path. Null/undefined means no MCP config. */
22
+ mcpConfigPath?: string | null;
23
+ }
24
+ /**
25
+ * Options controlling which env var patterns are stripped.
26
+ */
27
+ export interface StripEnvOptions {
28
+ /**
29
+ * Env var keys to preserve even if they match a strip pattern.
30
+ * For example, the daemon preserves `CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS`.
31
+ */
32
+ preserve?: string[];
33
+ }
34
+ /**
35
+ * Build the common Claude Code CLI arguments for stream-json I/O.
36
+ *
37
+ * Returns the base set of flags shared across all engines:
38
+ * - `--dangerously-skip-permissions`
39
+ * - `--input-format stream-json`
40
+ * - `--output-format stream-json`
41
+ * - `--verbose`
42
+ * - `--mcp-config <absolute-path>` (when mcpConfigPath is provided)
43
+ *
44
+ * Callers append engine-specific flags (e.g. `--teammate-mode`, `--setting-sources`).
45
+ */
46
+ export declare function buildStreamJsonArgs(options?: StreamJsonArgsOptions): string[];
47
+ /**
48
+ * Strip inherited Claude Code session variables from an environment object.
49
+ *
50
+ * When a Claude Code process is spawned from within another Claude Code session,
51
+ * inherited env vars (CLAUDECODE, CLAUDE_CODE_*, CLAUDECODE_*) cause the child
52
+ * to detect a parent session and hang indefinitely. This function removes those
53
+ * markers while allowing specific keys to be preserved.
54
+ *
55
+ * Patterns stripped:
56
+ * - `CLAUDECODE` (exact match)
57
+ * - `CLAUDE_CODE_*` (prefix match)
58
+ * - `CLAUDECODE_*` (prefix match -- used by some versions)
59
+ *
60
+ * @param env - The environment object to clean (not mutated; a copy is returned).
61
+ * @param options - Optional settings (e.g. keys to preserve).
62
+ * @returns A new environment object with matching keys removed.
63
+ */
64
+ export declare function stripClaudeCodeEnvVars(env: Record<string, string | undefined>, options?: StripEnvOptions): Record<string, string | undefined>;
65
+ /**
66
+ * Create a writable log stream with restrictive permissions (owner-only read/write).
67
+ *
68
+ * Used for stdout/stderr/jsonl log files for spawned agent processes.
69
+ * The file is created with mode 0o600 (rw-------).
70
+ */
71
+ export declare function createLogStream(path: string): WriteStream;
72
+ //# sourceMappingURL=spawn.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spawn.d.ts","sourceRoot":"","sources":["../src/spawn.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAqB,KAAK,WAAW,EAAE,MAAM,SAAS,CAAC;AAK9D;;;;;;GAMG;AACH,MAAM,WAAW,qBAAqB;IACpC,oGAAoG;IACpG,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC/B;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;CACrB;AAID;;;;;;;;;;;GAWG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,GAAE,qBAA0B,GAAG,MAAM,EAAE,CAcjF;AAID;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,sBAAsB,CACpC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,EACvC,OAAO,GAAE,eAAoB,GAC5B,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAiBpC;AAID;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,CAEzD"}