@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.
- package/dist/activity-tracker.d.ts +69 -0
- package/dist/activity-tracker.d.ts.map +1 -0
- package/dist/activity-tracker.js +155 -0
- package/dist/activity-tracker.js.map +1 -0
- package/dist/api-client.d.ts +94 -0
- package/dist/api-client.d.ts.map +1 -0
- package/dist/api-client.js +145 -0
- package/dist/api-client.js.map +1 -0
- package/dist/config.d.ts +117 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +348 -0
- package/dist/config.js.map +1 -0
- package/dist/engine.d.ts +120 -0
- package/dist/engine.d.ts.map +1 -0
- package/dist/engine.js +18 -0
- package/dist/engine.js.map +1 -0
- package/dist/escalation-types.d.ts +31 -0
- package/dist/escalation-types.d.ts.map +1 -0
- package/dist/escalation-types.js +24 -0
- package/dist/escalation-types.js.map +1 -0
- package/dist/event-logger.d.ts +13 -0
- package/dist/event-logger.d.ts.map +1 -0
- package/dist/event-logger.js +82 -0
- package/dist/event-logger.js.map +1 -0
- package/dist/git.d.ts +39 -0
- package/dist/git.d.ts.map +1 -0
- package/dist/git.js +72 -0
- package/dist/git.js.map +1 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +36 -0
- package/dist/index.js.map +1 -0
- package/dist/lifecycle.d.ts +104 -0
- package/dist/lifecycle.d.ts.map +1 -0
- package/dist/lifecycle.js +192 -0
- package/dist/lifecycle.js.map +1 -0
- package/dist/log-manager.d.ts +83 -0
- package/dist/log-manager.d.ts.map +1 -0
- package/dist/log-manager.js +217 -0
- package/dist/log-manager.js.map +1 -0
- package/dist/otel-env.d.ts +29 -0
- package/dist/otel-env.d.ts.map +1 -0
- package/dist/otel-env.js +44 -0
- package/dist/otel-env.js.map +1 -0
- package/dist/resilience.d.ts +127 -0
- package/dist/resilience.d.ts.map +1 -0
- package/dist/resilience.js +300 -0
- package/dist/resilience.js.map +1 -0
- package/dist/resource-governor.d.ts +83 -0
- package/dist/resource-governor.d.ts.map +1 -0
- package/dist/resource-governor.js +184 -0
- package/dist/resource-governor.js.map +1 -0
- package/dist/spawn.d.ts +72 -0
- package/dist/spawn.d.ts.map +1 -0
- package/dist/spawn.js +82 -0
- package/dist/spawn.js.map +1 -0
- package/dist/stream-json.d.ts +885 -0
- package/dist/stream-json.d.ts.map +1 -0
- package/dist/stream-json.js +298 -0
- package/dist/stream-json.js.map +1 -0
- package/dist/token-usage.d.ts +67 -0
- package/dist/token-usage.d.ts.map +1 -0
- package/dist/token-usage.js +150 -0
- package/dist/token-usage.js.map +1 -0
- package/dist/transforms.d.ts +64 -0
- package/dist/transforms.d.ts.map +1 -0
- package/dist/transforms.js +78 -0
- package/dist/transforms.js.map +1 -0
- package/dist/unified-config.d.ts +62 -0
- package/dist/unified-config.d.ts.map +1 -0
- package/dist/unified-config.js +155 -0
- package/dist/unified-config.js.map +1 -0
- package/dist/workflow-types.d.ts +202 -0
- package/dist/workflow-types.d.ts.map +1 -0
- package/dist/workflow-types.js +15 -0
- package/dist/workflow-types.js.map +1 -0
- package/dist/worktree.d.ts +92 -0
- package/dist/worktree.d.ts.map +1 -0
- package/dist/worktree.js +221 -0
- package/dist/worktree.js.map +1 -0
- 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":"AAAA;;;;;;;;;;GAUG;AAmCH,8EAA8E;AAC9E,mBAAmB;AACnB,8EAA8E;AAE9E,MAAM,OAAO,gBAAgB;IACV,SAAS,CAAS;IAClB,YAAY,CAAsB;IAEnD,qCAAqC;IACpB,eAAe,GAAG,IAAI,GAAG,EAAkB,CAAC;IAE7D,8CAA8C;IAC7B,KAAK,GAAqB,EAAE,CAAC;IAE9C,sEAAsE;IAC9D,YAAY,GAAG,KAAK,CAAC;IAE7B,YAAY,MAA8B;QACxC,IAAI,MAAM,CAAC,iBAAiB,GAAG,CAAC,EAAE,CAAC;YACjC,MAAM,IAAI,KAAK,CAAC,gCAAgC,CAAC,CAAC;QACpD,CAAC;QACD,IAAI,CAAC,SAAS,GAAG,MAAM,CAAC,iBAAiB,CAAC;QAC1C,IAAI,CAAC,YAAY,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC,CAAC;QAEjE,6BAA6B;QAC7B,KAAK,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,IAAI,IAAI,CAAC,YAAY,EAAE,CAAC;YAChD,IAAI,KAAK,GAAG,CAAC,EAAE,CAAC;gBACd,MAAM,IAAI,KAAK,CAAC,qBAAqB,MAAM,uBAAuB,KAAK,EAAE,CAAC,CAAC;YAC7E,CAAC;QACH,CAAC;IACH,CAAC;IAED;;;;;OAKG;IACH,WAAW,CAAC,MAAc;QACxB,IAAI,IAAI,CAAC,YAAY,EAAE,CAAC;YACtB,OAAO,OAAO,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,mCAAmC,CAAC,CAAC,CAAC;QACxE,CAAC;QAED,4BAA4B;QAC5B,IAAI,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;YAC5B,IAAI,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC;YAC3B,OAAO,OAAO,CAAC,OAAO,EAAE,CAAC;QAC3B,CAAC;QAED,oBAAoB;QACpB,OAAO,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YAC3C,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC;QAC/C,CAAC,CAAC,CAAC;IACL,CAAC;IAED;;;OAGG;IACH,cAAc,CAAC,MAAc;QAC3B,IAAI,IAAI,CAAC,YAAY;YAAE,OAAO,KAAK,CAAC;QACpC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC;YAAE,OAAO,KAAK,CAAC;QAC3C,IAAI,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC;QAC3B,OAAO,IAAI,CAAC;IACd,CAAC;IAED;;;OAGG;IACH,WAAW,CAAC,MAAc;QACxB,MAAM,OAAO,GAAG,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QACtD,IAAI,OAAO,IAAI,CAAC,EAAE,CAAC;YACjB,OAAO,CAAC,IAAI,CAAC,kCAAkC,MAAM,gCAAgC,CAAC,CAAC;YACvF,OAAO;QACT,CAAC;QACD,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,MAAM,EAAE,OAAO,GAAG,CAAC,CAAC,CAAC;QAC9C,IAAI,CAAC,YAAY,EAAE,CAAC;IACtB,CAAC;IAED,gDAAgD;IAChD,cAAc;QACZ,IAAI,KAAK,GAAG,CAAC,CAAC;QACd,MAAM,SAAS,GAA2B,EAAE,CAAC;QAE7C,KAAK,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,IAAI,IAAI,CAAC,eAAe,EAAE,CAAC;YACnD,SAAS,CAAC,MAAM,CAAC,GAAG,KAAK,CAAC;YAC1B,KAAK,IAAI,KAAK,CAAC;QACjB,CAAC;QAED,uDAAuD;QACvD,KAAK,MAAM,MAAM,IAAI,IAAI,CAAC,YAAY,CAAC,IAAI,EAAE,EAAE,CAAC;YAC9C,IAAI,CAAC,CAAC,MAAM,IAAI,SAAS,CAAC,EAAE,CAAC;gBAC3B,SAAS,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YACxB,CAAC;QACH,CAAC;QAED,yBAAyB;QACzB,MAAM,mBAAmB,GAA2B,EAAE,CAAC;QACvD,KAAK,MAAM,GAAG,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;YAC7B,mBAAmB,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,mBAAmB,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC;QAC/E,CAAC;QAED,OAAO;YACL,KAAK;YACL,SAAS;YACT,GAAG,EAAE,IAAI,CAAC,SAAS;YACnB,UAAU,EAAE,IAAI,CAAC,KAAK,CAAC,MAAM;YAC7B,mBAAmB;SACpB,CAAC;IACJ,CAAC;IAED;;;OAGG;IACH,QAAQ;QACN,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC;QACzB,6BAA6B;QAC7B,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC7B,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,EAAG,CAAC;YAChC,GAAG,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,mCAAmC,CAAC,CAAC,CAAC;QAC7D,CAAC;IACH,CAAC;IAED;;;OAGG;IACH,KAAK;QACH,IAAI,CAAC,YAAY,GAAG,KAAK,CAAC;QAC1B,IAAI,CAAC,eAAe,CAAC,KAAK,EAAE,CAAC;QAC7B,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC7B,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,EAAG,CAAC;YAChC,GAAG,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,wBAAwB,CAAC,CAAC,CAAC;QAClD,CAAC;IACH,CAAC;IAED,4EAA4E;IAC5E,UAAU;IACV,4EAA4E;IAE5E,sEAAsE;IAC9D,UAAU,CAAC,MAAc;QAC/B,qBAAqB;QACrB,MAAM,aAAa,GAAG,IAAI,CAAC,eAAe,EAAE,CAAC;QAC7C,IAAI,aAAa,IAAI,IAAI,CAAC,SAAS;YAAE,OAAO,KAAK,CAAC;QAElD,yCAAyC;QACzC,MAAM,WAAW,GAAG,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QAClD,IAAI,WAAW,KAAK,SAAS,EAAE,CAAC;YAC9B,MAAM,aAAa,GAAG,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;YAC5D,IAAI,aAAa,IAAI,WAAW;gBAAE,OAAO,KAAK,CAAC;QACjD,CAAC;QAED,OAAO,IAAI,CAAC;IACd,CAAC;IAED,qDAAqD;IAC7C,aAAa,CAAC,MAAc;QAClC,MAAM,OAAO,GAAG,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QACtD,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,MAAM,EAAE,OAAO,GAAG,CAAC,CAAC,CAAC;IAChD,CAAC;IAED,qDAAqD;IAC7C,eAAe;QACrB,IAAI,KAAK,GAAG,CAAC,CAAC;QACd,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,eAAe,CAAC,MAAM,EAAE,EAAE,CAAC;YAClD,KAAK,IAAI,KAAK,CAAC;QACjB,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;IAED;;;OAGG;IACK,YAAY;QAClB,IAAI,CAAC,GAAG,CAAC,CAAC;QACV,OAAO,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC;YAC7B,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;YAC1B,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;gBAChC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;gBACxB,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;gBAC/B,GAAG,CAAC,OAAO,EAAE,CAAC;gBACd,qCAAqC;YACvC,CAAC;iBAAM,CAAC;gBACN,CAAC,EAAE,CAAC;YACN,CAAC;QACH,CAAC;IACH,CAAC;CACF"}
|
package/dist/spawn.d.ts
ADDED
|
@@ -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"}
|