@probelabs/probe 0.6.0-rc154 → 0.6.0-rc159

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 (31) hide show
  1. package/bin/binaries/probe-v0.6.0-rc159-aarch64-apple-darwin.tar.gz +0 -0
  2. package/bin/binaries/probe-v0.6.0-rc159-aarch64-unknown-linux-musl.tar.gz +0 -0
  3. package/bin/binaries/probe-v0.6.0-rc159-x86_64-apple-darwin.tar.gz +0 -0
  4. package/bin/binaries/probe-v0.6.0-rc159-x86_64-pc-windows-msvc.zip +0 -0
  5. package/bin/binaries/probe-v0.6.0-rc159-x86_64-unknown-linux-musl.tar.gz +0 -0
  6. package/build/agent/ProbeAgent.d.ts +2 -0
  7. package/build/agent/ProbeAgent.js +20 -4
  8. package/build/agent/acp/server.js +1 -0
  9. package/build/agent/index.js +464 -216
  10. package/build/delegate.js +326 -201
  11. package/build/downloader.js +46 -17
  12. package/build/extractor.js +12 -12
  13. package/build/tools/vercel.js +55 -14
  14. package/build/utils.js +18 -9
  15. package/cjs/agent/ProbeAgent.cjs +478 -234
  16. package/cjs/index.cjs +41496 -41272
  17. package/package.json +2 -2
  18. package/src/agent/ProbeAgent.d.ts +2 -0
  19. package/src/agent/ProbeAgent.js +20 -4
  20. package/src/agent/acp/server.js +1 -0
  21. package/src/agent/index.js +8 -0
  22. package/src/delegate.js +326 -201
  23. package/src/downloader.js +46 -17
  24. package/src/extractor.js +12 -12
  25. package/src/tools/vercel.js +55 -14
  26. package/src/utils.js +18 -9
  27. package/bin/binaries/probe-v0.6.0-rc154-aarch64-apple-darwin.tar.gz +0 -0
  28. package/bin/binaries/probe-v0.6.0-rc154-aarch64-unknown-linux-gnu.tar.gz +0 -0
  29. package/bin/binaries/probe-v0.6.0-rc154-x86_64-apple-darwin.tar.gz +0 -0
  30. package/bin/binaries/probe-v0.6.0-rc154-x86_64-pc-windows-msvc.zip +0 -0
  31. package/bin/binaries/probe-v0.6.0-rc154-x86_64-unknown-linux-gnu.tar.gz +0 -0
package/src/delegate.js CHANGED
@@ -1,267 +1,392 @@
1
1
  /**
2
2
  * Delegate functionality for the probe package - used automatically by AI agents
3
+ * Uses ProbeAgent SDK directly instead of spawning processes for better performance
3
4
  * @module delegate
4
5
  */
5
6
 
6
- import { spawn } from 'child_process';
7
7
  import { randomUUID } from 'crypto';
8
- import { getBinaryPath, buildCliArgs } from './utils.js';
9
- import { createMessagePreview } from './tools/common.js';
8
+ import { ProbeAgent } from './agent/ProbeAgent.js';
9
+
10
+ /**
11
+ * DelegationManager - Simple delegation tracking with proper resource management
12
+ * Note: In single-threaded Node.js, simple counter operations are atomic within the event loop.
13
+ * No mutex/locking needed since operations are synchronous.
14
+ *
15
+ * Design notes:
16
+ * - Uses Map instead of WeakMap because sessionIds are strings (UUIDs), not objects
17
+ * - WeakMap only accepts objects as keys, so it cannot be used for string-based session IDs
18
+ * - Session entries are automatically cleaned up when their count reaches 0
19
+ * - For long-running processes, periodic cleanup of stale sessions may be needed
20
+ */
21
+ class DelegationManager {
22
+ constructor() {
23
+ this.maxConcurrent = parseInt(process.env.MAX_CONCURRENT_DELEGATIONS || '3', 10);
24
+ this.maxPerSession = parseInt(process.env.MAX_DELEGATIONS_PER_SESSION || '10', 10);
25
+
26
+ // Track delegations per session with timestamp for potential TTL cleanup
27
+ // Map<string, { count: number, lastUpdated: number }>
28
+ this.sessionDelegations = new Map();
29
+ this.globalActive = 0;
30
+
31
+ // Start periodic cleanup of stale sessions (every 5 minutes)
32
+ // Wrapped in try-catch to prevent interval errors from crashing the process
33
+ this.cleanupInterval = setInterval(() => {
34
+ try {
35
+ this.cleanupStaleSessions();
36
+ } catch (error) {
37
+ console.error('[DelegationManager] Error during cleanup:', error);
38
+ }
39
+ }, 5 * 60 * 1000);
40
+
41
+ // Allow Node.js to exit even if interval is active
42
+ if (this.cleanupInterval.unref) {
43
+ this.cleanupInterval.unref();
44
+ }
45
+ }
46
+
47
+ /**
48
+ * Check limits and increment counters (synchronous, atomic in Node.js event loop)
49
+ * @param {string|null|undefined} parentSessionId - Parent session ID for tracking
50
+ */
51
+ tryAcquire(parentSessionId) {
52
+ // Validate parentSessionId parameter
53
+ if (parentSessionId !== null && parentSessionId !== undefined && typeof parentSessionId !== 'string') {
54
+ throw new TypeError('parentSessionId must be a string, null, or undefined');
55
+ }
56
+
57
+ // Check global limit
58
+ if (this.globalActive >= this.maxConcurrent) {
59
+ throw new Error(`Maximum concurrent delegations (${this.maxConcurrent}) reached. Please wait for some delegations to complete.`);
60
+ }
61
+
62
+ // Check per-session limit
63
+ if (parentSessionId) {
64
+ const sessionData = this.sessionDelegations.get(parentSessionId);
65
+ const sessionCount = sessionData?.count || 0;
66
+
67
+ if (sessionCount >= this.maxPerSession) {
68
+ throw new Error(`Maximum delegations per session (${this.maxPerSession}) reached for session ${parentSessionId}`);
69
+ }
70
+ }
71
+
72
+ // Increment counters (atomic in single-threaded Node.js)
73
+ this.globalActive++;
74
+
75
+ if (parentSessionId) {
76
+ const sessionData = this.sessionDelegations.get(parentSessionId);
77
+ if (sessionData) {
78
+ sessionData.count++;
79
+ sessionData.lastUpdated = Date.now();
80
+ } else {
81
+ this.sessionDelegations.set(parentSessionId, {
82
+ count: 1,
83
+ lastUpdated: Date.now()
84
+ });
85
+ }
86
+ }
87
+
88
+ return true;
89
+ }
90
+
91
+ /**
92
+ * Decrement counters (synchronous, atomic in Node.js event loop)
93
+ */
94
+ release(parentSessionId, debug = false) {
95
+ this.globalActive = Math.max(0, this.globalActive - 1);
96
+
97
+ if (parentSessionId) {
98
+ const sessionData = this.sessionDelegations.get(parentSessionId);
99
+ if (sessionData) {
100
+ sessionData.count = Math.max(0, sessionData.count - 1);
101
+
102
+ // Clean up if count reaches 0
103
+ if (sessionData.count === 0) {
104
+ this.sessionDelegations.delete(parentSessionId);
105
+ }
106
+ }
107
+ }
108
+
109
+ if (debug) {
110
+ console.error(`[DELEGATE] Released. Global active: ${this.globalActive}`);
111
+ }
112
+ }
113
+
114
+ /**
115
+ * Get current stats for monitoring
116
+ */
117
+ getStats() {
118
+ return {
119
+ globalActive: this.globalActive,
120
+ maxConcurrent: this.maxConcurrent,
121
+ maxPerSession: this.maxPerSession,
122
+ sessionCount: this.sessionDelegations.size
123
+ };
124
+ }
125
+
126
+ /**
127
+ * Clean up stale sessions (sessions with count=0 that haven't been updated in 1 hour)
128
+ */
129
+ cleanupStaleSessions() {
130
+ const oneHourAgo = Date.now() - (60 * 60 * 1000);
131
+ for (const [sessionId, data] of this.sessionDelegations.entries()) {
132
+ if (data.count === 0 && data.lastUpdated < oneHourAgo) {
133
+ this.sessionDelegations.delete(sessionId);
134
+ }
135
+ }
136
+ }
137
+
138
+ /**
139
+ * Cleanup all resources (for testing or shutdown)
140
+ */
141
+ cleanup() {
142
+ if (this.cleanupInterval) {
143
+ clearInterval(this.cleanupInterval);
144
+ this.cleanupInterval = null;
145
+ }
146
+ this.sessionDelegations.clear();
147
+ this.globalActive = 0;
148
+ }
149
+ }
150
+
151
+ // Singleton instance for the module
152
+ const delegationManager = new DelegationManager();
10
153
 
11
154
  /**
12
155
  * Delegate a big distinct task to a probe subagent (used automatically by AI agents)
13
- *
156
+ *
14
157
  * This function is designed for automatic use within the agentic loop. AI agents
15
158
  * should automatically identify complex multi-part requests and break them down
16
159
  * into focused, parallel tasks using this delegation mechanism.
17
- *
18
- * Spawns a new probe agent with a clean environment that automatically:
160
+ *
161
+ * Creates a new ProbeAgent instance with a clean environment that automatically:
19
162
  * - Uses the default 'code-researcher' prompt (not inherited)
20
163
  * - Disables schema validation for simpler responses
21
164
  * - Disables mermaid validation for faster processing
165
+ * - Disables delegation to prevent recursion
22
166
  * - Limits iterations to remaining parent iterations
23
- *
167
+ *
24
168
  * @param {Object} options - Delegate options
25
169
  * @param {string} options.task - A complete, self-contained task for the subagent. Should be specific and focused on one area of expertise.
26
170
  * @param {number} [options.timeout=300] - Timeout in seconds (default: 5 minutes)
27
171
  * @param {boolean} [options.debug=false] - Enable debug logging
28
172
  * @param {number} [options.currentIteration=0] - Current tool iteration count from parent agent
29
173
  * @param {number} [options.maxIterations=30] - Maximum tool iterations allowed
174
+ * @param {string} [options.parentSessionId=null] - Parent session ID for tracking
175
+ * @param {string} [options.path] - Search directory path (inherited from parent)
176
+ * @param {string} [options.provider] - AI provider (inherited from parent)
177
+ * @param {string} [options.model] - AI model (inherited from parent)
178
+ * @param {Object} [options.tracer=null] - Telemetry tracer instance
30
179
  * @returns {Promise<string>} The response from the delegate agent
31
180
  */
32
- export async function delegate({ task, timeout = 300, debug = false, currentIteration = 0, maxIterations = 30, tracer = null }) {
181
+ export async function delegate({
182
+ task,
183
+ timeout = 300,
184
+ debug = false,
185
+ currentIteration = 0,
186
+ maxIterations = 30,
187
+ tracer = null,
188
+ parentSessionId = null,
189
+ path = null,
190
+ provider = null,
191
+ model = null
192
+ }) {
33
193
  if (!task || typeof task !== 'string') {
34
194
  throw new Error('Task parameter is required and must be a string');
35
195
  }
36
196
 
37
197
  const sessionId = randomUUID();
38
198
  const startTime = Date.now();
39
-
199
+
40
200
  // Calculate remaining iterations for subagent
41
201
  const remainingIterations = Math.max(1, maxIterations - currentIteration);
42
202
 
43
- if (debug) {
44
- console.error(`[DELEGATE] Starting delegation session ${sessionId}`);
45
- console.error(`[DELEGATE] Task: ${task}`);
46
- console.error(`[DELEGATE] Current iteration: ${currentIteration}/${maxIterations}`);
47
- console.error(`[DELEGATE] Remaining iterations for subagent: ${remainingIterations}`);
48
- console.error(`[DELEGATE] Timeout configured: ${timeout} seconds`);
49
- console.error(`[DELEGATE] Using clean agent environment with code-researcher prompt`);
50
- }
203
+ // Create delegation span for telemetry if tracer is available
204
+ const delegationSpan = tracer ? tracer.createDelegationSpan(sessionId, task) : null;
205
+
206
+ let timeoutId = null;
207
+ let acquired = false;
51
208
 
52
209
  try {
53
- // Get the probe binary path
54
- const binaryPath = await getBinaryPath();
55
-
56
- // Create the agent command with automatic subagent configuration
57
- const args = [
58
- 'agent',
59
- '--task', task,
60
- '--session-id', sessionId,
61
- '--prompt-type', 'code-researcher', // Automatically use default code researcher prompt
62
- '--no-schema-validation', // Automatically disable schema validation
63
- '--no-mermaid-validation', // Automatically disable mermaid validation
64
- '--max-iterations', remainingIterations.toString() // Automatically limit to remaining iterations
65
- ];
66
-
210
+ // Check limits and acquire delegation slot inside try block for proper cleanup
211
+ delegationManager.tryAcquire(parentSessionId);
212
+ acquired = true;
213
+
67
214
  if (debug) {
68
- args.push('--debug');
69
- console.error(`[DELEGATE] Using binary at: ${binaryPath}`);
70
- console.error(`[DELEGATE] Command args: ${args.join(' ')}`);
215
+ const stats = delegationManager.getStats();
216
+ console.error(`[DELEGATE] Starting delegation session ${sessionId}`);
217
+ console.error(`[DELEGATE] Parent session: ${parentSessionId || 'none'}`);
218
+ console.error(`[DELEGATE] Task: ${task}`);
219
+ console.error(`[DELEGATE] Current iteration: ${currentIteration}/${maxIterations}`);
220
+ console.error(`[DELEGATE] Remaining iterations for subagent: ${remainingIterations}`);
221
+ console.error(`[DELEGATE] Timeout configured: ${timeout} seconds`);
222
+ console.error(`[DELEGATE] Global active delegations: ${stats.globalActive}/${stats.maxConcurrent}`);
223
+ console.error(`[DELEGATE] Using ProbeAgent SDK with code-researcher prompt`);
71
224
  }
225
+ // Create a new ProbeAgent instance for the delegated task
226
+ const subagent = new ProbeAgent({
227
+ sessionId,
228
+ promptType: 'code-researcher', // Clean prompt, not inherited from parent
229
+ enableDelegate: false, // Explicitly disable delegation to prevent recursion
230
+ disableMermaidValidation: true, // Faster processing
231
+ disableJsonValidation: true, // Simpler responses
232
+ maxIterations: remainingIterations,
233
+ debug,
234
+ tracer,
235
+ path, // Inherit from parent
236
+ provider, // Inherit from parent
237
+ model // Inherit from parent
238
+ });
72
239
 
73
- // Spawn the delegate process
74
- return new Promise((resolve, reject) => {
75
- // Create delegation span for telemetry if tracer is available
76
- const delegationSpan = tracer ? tracer.createDelegationSpan(sessionId, task) : null;
77
-
78
- const process = spawn(binaryPath, args, {
79
- stdio: ['pipe', 'pipe', 'pipe'],
80
- timeout: timeout * 1000
81
- });
82
-
83
- let stdout = '';
84
- let stderr = '';
85
- let isResolved = false;
86
-
87
- // Collect stdout
88
- process.stdout.on('data', (data) => {
89
- const chunk = data.toString();
90
- stdout += chunk;
91
-
92
- if (debug) {
93
- const preview = createMessagePreview(chunk);
94
- console.error(`[DELEGATE] stdout chunk received (${chunk.length} chars): ${preview}`);
95
- }
96
- });
240
+ if (debug) {
241
+ console.error(`[DELEGATE] Created subagent with session ${sessionId}`);
242
+ console.error(`[DELEGATE] Subagent config: promptType=code-researcher, enableDelegate=false, maxIterations=${remainingIterations}`);
243
+ }
97
244
 
98
- // Collect stderr
99
- process.stderr.on('data', (data) => {
100
- const chunk = data.toString();
101
- stderr += chunk;
102
-
103
- if (debug) {
104
- const preview = createMessagePreview(chunk);
105
- console.error(`[DELEGATE] stderr chunk received (${chunk.length} chars): ${preview}`);
106
- }
107
- });
245
+ // Set up timeout with proper cleanup
246
+ // TODO: Implement AbortController support in ProbeAgent.answer() for proper cancellation
247
+ // Current limitation: When timeout occurs, subagent.answer() continues running in background
248
+ // This is acceptable since:
249
+ // 1. The promise will eventually resolve/reject and be garbage collected
250
+ // 2. The delegation slot is properly released on timeout
251
+ // 3. The parent receives timeout error and can handle it
252
+ // Future improvement: Add signal parameter to ProbeAgent.answer(task, [], { signal })
253
+ const timeoutPromise = new Promise((_, reject) => {
254
+ timeoutId = setTimeout(() => {
255
+ reject(new Error(`Delegation timed out after ${timeout} seconds`));
256
+ }, timeout * 1000);
257
+ });
108
258
 
109
- // Handle process completion
110
- process.on('close', (code) => {
111
- if (isResolved) return;
112
- isResolved = true;
259
+ // Execute the task with timeout
260
+ const answerPromise = subagent.answer(task);
261
+ const response = await Promise.race([answerPromise, timeoutPromise]);
113
262
 
114
- const duration = Date.now() - startTime;
263
+ // Clear timeout immediately after race completes to prevent memory leak
264
+ // Note: timeoutId is always set by this point (synchronous in Promise constructor)
265
+ // but we keep the null check for defensive programming
266
+ if (timeoutId !== null) {
267
+ clearTimeout(timeoutId);
268
+ timeoutId = null;
269
+ }
115
270
 
116
- if (debug) {
117
- console.error(`[DELEGATE] Process completed with code ${code} in ${duration}ms`);
118
- console.error(`[DELEGATE] Duration: ${(duration / 1000).toFixed(2)}s`);
119
- console.error(`[DELEGATE] Total stdout: ${stdout.length} chars`);
120
- console.error(`[DELEGATE] Total stderr: ${stderr.length} chars`);
121
- }
271
+ const duration = Date.now() - startTime;
122
272
 
123
- if (code === 0) {
124
- // Successful delegation - return the response
125
- const response = stdout.trim();
126
-
127
- if (!response) {
128
- if (debug) {
129
- console.error(`[DELEGATE] Task completed but returned empty response for session ${sessionId}`);
130
- }
131
- reject(new Error('Delegate agent returned empty response'));
132
- return;
133
- }
134
-
135
- if (debug) {
136
- console.error(`[DELEGATE] Task completed successfully for session ${sessionId}`);
137
- console.error(`[DELEGATE] Response length: ${response.length} chars`);
138
- }
139
-
140
- // Record successful completion in telemetry
141
- if (tracer) {
142
- tracer.recordDelegationEvent('completed', {
143
- 'delegation.session_id': sessionId,
144
- 'delegation.duration_ms': duration,
145
- 'delegation.response_length': response.length,
146
- 'delegation.success': true
147
- });
148
-
149
- if (delegationSpan) {
150
- delegationSpan.setAttributes({
151
- 'delegation.result.success': true,
152
- 'delegation.result.response_length': response.length,
153
- 'delegation.result.duration_ms': duration
154
- });
155
- delegationSpan.setStatus({ code: 1 }); // OK
156
- delegationSpan.end();
157
- }
158
- }
159
-
160
- resolve(response);
161
- } else {
162
- // Failed delegation
163
- const errorMessage = stderr.trim() || `Delegate process failed with exit code ${code}`;
164
- if (debug) {
165
- console.error(`[DELEGATE] Task failed for session ${sessionId} with code ${code}`);
166
- console.error(`[DELEGATE] Error message: ${errorMessage}`);
167
- }
168
-
169
- // Record failure in telemetry
170
- if (tracer) {
171
- tracer.recordDelegationEvent('failed', {
172
- 'delegation.session_id': sessionId,
173
- 'delegation.duration_ms': duration,
174
- 'delegation.exit_code': code,
175
- 'delegation.error_message': errorMessage,
176
- 'delegation.success': false
177
- });
178
-
179
- if (delegationSpan) {
180
- delegationSpan.setAttributes({
181
- 'delegation.result.success': false,
182
- 'delegation.result.exit_code': code,
183
- 'delegation.result.error': errorMessage,
184
- 'delegation.result.duration_ms': duration
185
- });
186
- delegationSpan.setStatus({ code: 2, message: errorMessage }); // ERROR
187
- delegationSpan.end();
188
- }
189
- }
190
-
191
- reject(new Error(`Delegation failed: ${errorMessage}`));
192
- }
193
- });
273
+ // Validate response (check for type first, then content)
274
+ if (typeof response !== 'string') {
275
+ throw new Error('Delegate agent returned invalid response (not a string)');
276
+ }
194
277
 
195
- // Handle process errors
196
- process.on('error', (error) => {
197
- if (isResolved) return;
198
- isResolved = true;
278
+ const trimmedResponse = response.trim();
279
+ if (trimmedResponse.length === 0) {
280
+ throw new Error('Delegate agent returned empty or whitespace-only response');
281
+ }
199
282
 
200
- const duration = Date.now() - startTime;
283
+ // Check for null bytes (edge case)
284
+ if (trimmedResponse.includes('\0')) {
285
+ throw new Error('Delegate agent returned response containing null bytes');
286
+ }
201
287
 
202
- if (debug) {
203
- console.error(`[DELEGATE] Process spawn error after ${duration}ms:`, error);
204
- console.error(`[DELEGATE] Session ${sessionId} failed during process creation`);
205
- console.error(`[DELEGATE] Error type: ${error.code || 'unknown'}`);
206
- }
288
+ if (debug) {
289
+ console.error(`[DELEGATE] Task completed successfully for session ${sessionId}`);
290
+ console.error(`[DELEGATE] Duration: ${(duration / 1000).toFixed(2)}s`);
291
+ console.error(`[DELEGATE] Response length: ${response.length} chars`);
292
+ }
207
293
 
208
- reject(new Error(`Failed to start delegate process: ${error.message}`));
294
+ // Record successful completion in telemetry
295
+ if (tracer) {
296
+ tracer.recordDelegationEvent('completed', {
297
+ 'delegation.session_id': sessionId,
298
+ 'delegation.parent_session_id': parentSessionId,
299
+ 'delegation.duration_ms': duration,
300
+ 'delegation.response_length': response.length,
301
+ 'delegation.success': true
209
302
  });
210
303
 
211
- // Handle timeout
212
- setTimeout(() => {
213
- if (isResolved) return;
214
- isResolved = true;
215
-
216
- const duration = Date.now() - startTime;
304
+ if (delegationSpan) {
305
+ delegationSpan.setAttributes({
306
+ 'delegation.result.success': true,
307
+ 'delegation.result.response_length': response.length,
308
+ 'delegation.result.duration_ms': duration
309
+ });
310
+ delegationSpan.setStatus({ code: 1 }); // OK
311
+ delegationSpan.end();
312
+ }
313
+ }
217
314
 
218
- if (debug) {
219
- console.error(`[DELEGATE] Process timeout after ${(duration / 1000).toFixed(2)}s (limit: ${timeout}s)`);
220
- console.error(`[DELEGATE] Terminating session ${sessionId} due to timeout`);
221
- console.error(`[DELEGATE] Partial stdout: ${stdout.substring(0, 500)}${stdout.length > 500 ? '...' : ''}`);
222
- console.error(`[DELEGATE] Partial stderr: ${stderr.substring(0, 500)}${stderr.length > 500 ? '...' : ''}`);
223
- }
315
+ // Release delegation slot
316
+ if (acquired) {
317
+ delegationManager.release(parentSessionId, debug);
318
+ }
224
319
 
225
- // Kill the process
226
- process.kill('SIGTERM');
227
-
228
- // Give it a moment to terminate gracefully
229
- setTimeout(() => {
230
- if (!process.killed) {
231
- if (debug) {
232
- console.error(`[DELEGATE] Force killing process ${sessionId} after graceful timeout`);
233
- }
234
- process.kill('SIGKILL');
235
- }
236
- }, 5000);
237
-
238
- reject(new Error(`Delegation timed out after ${timeout} seconds`));
239
- }, timeout * 1000);
240
- });
320
+ return response;
241
321
 
242
322
  } catch (error) {
323
+ // Clear timeout if still active
324
+ if (timeoutId !== null) {
325
+ clearTimeout(timeoutId);
326
+ timeoutId = null;
327
+ }
328
+
243
329
  const duration = Date.now() - startTime;
244
330
 
331
+ // Release delegation slot on error (only if it was acquired)
332
+ if (acquired) {
333
+ delegationManager.release(parentSessionId, debug);
334
+ }
335
+
245
336
  if (debug) {
246
- console.error(`[DELEGATE] Error in delegate function after ${duration}ms:`, error);
247
- console.error(`[DELEGATE] Session ${sessionId} failed during setup`);
248
- console.error(`[DELEGATE] Error stack: ${error.stack}`);
337
+ console.error(`[DELEGATE] Task failed for session ${sessionId} after ${duration}ms`);
338
+ console.error(`[DELEGATE] Error: ${error.message}`);
339
+ console.error(`[DELEGATE] Stack: ${error.stack}`);
249
340
  }
250
- throw new Error(`Delegation setup failed: ${error.message}`);
341
+
342
+ // Record failure in telemetry
343
+ if (tracer) {
344
+ tracer.recordDelegationEvent('failed', {
345
+ 'delegation.session_id': sessionId,
346
+ 'delegation.parent_session_id': parentSessionId,
347
+ 'delegation.duration_ms': duration,
348
+ 'delegation.error_message': error.message,
349
+ 'delegation.success': false
350
+ });
351
+
352
+ if (delegationSpan) {
353
+ delegationSpan.setAttributes({
354
+ 'delegation.result.success': false,
355
+ 'delegation.result.error': error.message,
356
+ 'delegation.result.duration_ms': duration
357
+ });
358
+ delegationSpan.setStatus({ code: 2, message: error.message }); // ERROR
359
+ delegationSpan.end();
360
+ }
361
+ }
362
+
363
+ throw new Error(`Delegation failed: ${error.message}`);
251
364
  }
252
365
  }
253
366
 
254
367
 
255
368
  /**
256
369
  * Check if delegate functionality is available
257
- *
370
+ *
258
371
  * @returns {Promise<boolean>} True if delegate is available
259
372
  */
260
373
  export async function isDelegateAvailable() {
261
- try {
262
- const binaryPath = await getBinaryPath();
263
- return !!binaryPath;
264
- } catch (error) {
265
- return false;
266
- }
267
- }
374
+ // Delegate is always available when using SDK-based approach
375
+ return true;
376
+ }
377
+
378
+ /**
379
+ * Get delegation statistics (for monitoring/debugging)
380
+ *
381
+ * @returns {Object} Current delegation stats
382
+ */
383
+ export function getDelegationStats() {
384
+ return delegationManager.getStats();
385
+ }
386
+
387
+ /**
388
+ * Cleanup delegation manager (for testing or shutdown)
389
+ */
390
+ export async function cleanupDelegationManager() {
391
+ return delegationManager.cleanup();
392
+ }