@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.
- package/bin/binaries/probe-v0.6.0-rc159-aarch64-apple-darwin.tar.gz +0 -0
- package/bin/binaries/probe-v0.6.0-rc159-aarch64-unknown-linux-musl.tar.gz +0 -0
- package/bin/binaries/probe-v0.6.0-rc159-x86_64-apple-darwin.tar.gz +0 -0
- package/bin/binaries/probe-v0.6.0-rc159-x86_64-pc-windows-msvc.zip +0 -0
- package/bin/binaries/probe-v0.6.0-rc159-x86_64-unknown-linux-musl.tar.gz +0 -0
- package/build/agent/ProbeAgent.d.ts +2 -0
- package/build/agent/ProbeAgent.js +20 -4
- package/build/agent/acp/server.js +1 -0
- package/build/agent/index.js +464 -216
- package/build/delegate.js +326 -201
- package/build/downloader.js +46 -17
- package/build/extractor.js +12 -12
- package/build/tools/vercel.js +55 -14
- package/build/utils.js +18 -9
- package/cjs/agent/ProbeAgent.cjs +478 -234
- package/cjs/index.cjs +41496 -41272
- package/package.json +2 -2
- package/src/agent/ProbeAgent.d.ts +2 -0
- package/src/agent/ProbeAgent.js +20 -4
- package/src/agent/acp/server.js +1 -0
- package/src/agent/index.js +8 -0
- package/src/delegate.js +326 -201
- package/src/downloader.js +46 -17
- package/src/extractor.js +12 -12
- package/src/tools/vercel.js +55 -14
- package/src/utils.js +18 -9
- package/bin/binaries/probe-v0.6.0-rc154-aarch64-apple-darwin.tar.gz +0 -0
- package/bin/binaries/probe-v0.6.0-rc154-aarch64-unknown-linux-gnu.tar.gz +0 -0
- package/bin/binaries/probe-v0.6.0-rc154-x86_64-apple-darwin.tar.gz +0 -0
- package/bin/binaries/probe-v0.6.0-rc154-x86_64-pc-windows-msvc.zip +0 -0
- 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 {
|
|
9
|
-
|
|
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
|
-
*
|
|
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({
|
|
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
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
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
|
-
//
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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
|
-
|
|
69
|
-
console.error(`[DELEGATE]
|
|
70
|
-
console.error(`[DELEGATE]
|
|
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
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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
|
-
|
|
110
|
-
|
|
111
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
124
|
-
|
|
125
|
-
|
|
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
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
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
|
-
|
|
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
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
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
|
-
|
|
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
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
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
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
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
|
-
|
|
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]
|
|
247
|
-
console.error(`[DELEGATE]
|
|
248
|
-
console.error(`[DELEGATE]
|
|
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
|
-
|
|
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
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
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
|
+
}
|