@probelabs/probe 0.6.0-rc230 → 0.6.0-rc232
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-rc232-aarch64-apple-darwin.tar.gz +0 -0
- package/bin/binaries/probe-v0.6.0-rc232-aarch64-unknown-linux-musl.tar.gz +0 -0
- package/bin/binaries/probe-v0.6.0-rc232-x86_64-apple-darwin.tar.gz +0 -0
- package/bin/binaries/probe-v0.6.0-rc232-x86_64-pc-windows-msvc.zip +0 -0
- package/bin/binaries/probe-v0.6.0-rc232-x86_64-unknown-linux-musl.tar.gz +0 -0
- package/build/agent/ProbeAgent.js +59 -11
- package/build/agent/index.js +97 -19
- package/build/agent/tasks/taskTool.js +32 -2
- package/build/delegate.js +12 -6
- package/cjs/agent/ProbeAgent.cjs +97 -19
- package/cjs/index.cjs +97 -19
- package/package.json +2 -2
- package/src/agent/ProbeAgent.js +59 -11
- package/src/agent/tasks/taskTool.js +32 -2
- package/src/delegate.js +12 -6
- package/bin/binaries/probe-v0.6.0-rc230-aarch64-apple-darwin.tar.gz +0 -0
- package/bin/binaries/probe-v0.6.0-rc230-aarch64-unknown-linux-musl.tar.gz +0 -0
- package/bin/binaries/probe-v0.6.0-rc230-x86_64-apple-darwin.tar.gz +0 -0
- package/bin/binaries/probe-v0.6.0-rc230-x86_64-pc-windows-msvc.zip +0 -0
- package/bin/binaries/probe-v0.6.0-rc230-x86_64-unknown-linux-musl.tar.gz +0 -0
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -357,6 +357,10 @@ export class ProbeAgent {
|
|
|
357
357
|
// Each ProbeAgent instance has its own limits, not shared globally
|
|
358
358
|
this.delegationManager = new DelegationManager();
|
|
359
359
|
|
|
360
|
+
// Optional global concurrency limiter shared across all ProbeAgent instances.
|
|
361
|
+
// When set, every AI API call acquires a slot before calling the provider.
|
|
362
|
+
this.concurrencyLimiter = options.concurrencyLimiter || null;
|
|
363
|
+
|
|
360
364
|
// Request timeout configuration (default 2 minutes)
|
|
361
365
|
// Validates env var to prevent NaN or unreasonable values
|
|
362
366
|
this.requestTimeout = options.requestTimeout ?? (() => {
|
|
@@ -824,6 +828,7 @@ export class ProbeAgent {
|
|
|
824
828
|
provider: this.clientApiProvider,
|
|
825
829
|
model: this.clientApiModel,
|
|
826
830
|
delegationManager: this.delegationManager, // Per-instance delegation limits
|
|
831
|
+
concurrencyLimiter: this.concurrencyLimiter, // Global AI concurrency limiter
|
|
827
832
|
isToolAllowed
|
|
828
833
|
};
|
|
829
834
|
|
|
@@ -1363,6 +1368,16 @@ export class ProbeAgent {
|
|
|
1363
1368
|
* @private
|
|
1364
1369
|
*/
|
|
1365
1370
|
async streamTextWithRetryAndFallback(options) {
|
|
1371
|
+
// Acquire global concurrency slot if limiter is configured
|
|
1372
|
+
const limiter = this.concurrencyLimiter;
|
|
1373
|
+
if (limiter) {
|
|
1374
|
+
await limiter.acquire(null);
|
|
1375
|
+
if (this.debug) {
|
|
1376
|
+
const stats = limiter.getStats();
|
|
1377
|
+
console.log(`[DEBUG] Acquired global AI concurrency slot (${stats.globalActive}/${stats.maxConcurrent}, queue: ${stats.queueSize})`);
|
|
1378
|
+
}
|
|
1379
|
+
}
|
|
1380
|
+
|
|
1366
1381
|
// Create AbortController for overall operation timeout
|
|
1367
1382
|
const controller = new AbortController();
|
|
1368
1383
|
const timeoutState = { timeoutId: null };
|
|
@@ -1382,12 +1397,10 @@ export class ProbeAgent {
|
|
|
1382
1397
|
const useClaudeCode = this.clientApiProvider === 'claude-code' || process.env.USE_CLAUDE_CODE === 'true';
|
|
1383
1398
|
const useCodex = this.clientApiProvider === 'codex' || process.env.USE_CODEX === 'true';
|
|
1384
1399
|
|
|
1400
|
+
let result;
|
|
1385
1401
|
if (useClaudeCode || useCodex) {
|
|
1386
1402
|
try {
|
|
1387
|
-
|
|
1388
|
-
if (result) {
|
|
1389
|
-
return result;
|
|
1390
|
-
}
|
|
1403
|
+
result = await this._tryEngineStreamPath(options, controller, timeoutState);
|
|
1391
1404
|
} catch (error) {
|
|
1392
1405
|
if (this.debug) {
|
|
1393
1406
|
const engineType = useClaudeCode ? 'Claude Code' : 'Codex';
|
|
@@ -1397,8 +1410,43 @@ export class ProbeAgent {
|
|
|
1397
1410
|
}
|
|
1398
1411
|
}
|
|
1399
1412
|
|
|
1400
|
-
|
|
1401
|
-
|
|
1413
|
+
if (!result) {
|
|
1414
|
+
// Use Vercel AI SDK with retry/fallback
|
|
1415
|
+
result = await this._executeWithVercelProvider(options, controller);
|
|
1416
|
+
}
|
|
1417
|
+
|
|
1418
|
+
// Wrap textStream so limiter slot is held until stream completes
|
|
1419
|
+
if (limiter && result.textStream) {
|
|
1420
|
+
const originalStream = result.textStream;
|
|
1421
|
+
const debug = this.debug;
|
|
1422
|
+
result.textStream = (async function* () {
|
|
1423
|
+
try {
|
|
1424
|
+
for await (const chunk of originalStream) {
|
|
1425
|
+
yield chunk;
|
|
1426
|
+
}
|
|
1427
|
+
} finally {
|
|
1428
|
+
limiter.release(null);
|
|
1429
|
+
if (debug) {
|
|
1430
|
+
const stats = limiter.getStats();
|
|
1431
|
+
console.log(`[DEBUG] Released global AI concurrency slot (${stats.globalActive}/${stats.maxConcurrent}, queue: ${stats.queueSize})`);
|
|
1432
|
+
}
|
|
1433
|
+
}
|
|
1434
|
+
})();
|
|
1435
|
+
} else if (limiter) {
|
|
1436
|
+
// No textStream (shouldn't happen, but release just in case)
|
|
1437
|
+
limiter.release(null);
|
|
1438
|
+
}
|
|
1439
|
+
|
|
1440
|
+
return result;
|
|
1441
|
+
} catch (error) {
|
|
1442
|
+
// Release on error if limiter was acquired
|
|
1443
|
+
if (limiter) {
|
|
1444
|
+
limiter.release(null);
|
|
1445
|
+
if (this.debug) {
|
|
1446
|
+
console.log(`[DEBUG] Released global AI concurrency slot on error`);
|
|
1447
|
+
}
|
|
1448
|
+
}
|
|
1449
|
+
throw error;
|
|
1402
1450
|
} finally {
|
|
1403
1451
|
// Clean up timeout (for non-engine paths; engine paths clean up in the generator)
|
|
1404
1452
|
if (timeoutState.timeoutId) {
|
|
@@ -2496,10 +2544,9 @@ ${extractGuidance}
|
|
|
2496
2544
|
toolDefinitions += `${taskToolDefinition}\n`;
|
|
2497
2545
|
}
|
|
2498
2546
|
|
|
2499
|
-
// Always include attempt_completion
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
}
|
|
2547
|
+
// Always include attempt_completion unconditionally - it's a completion signal, not a tool
|
|
2548
|
+
// This ensures agents can always complete their work, regardless of tool restrictions
|
|
2549
|
+
toolDefinitions += `${attemptCompletionToolDefinition}\n`;
|
|
2503
2550
|
|
|
2504
2551
|
// Delegate tool (require both enableDelegate flag AND allowedTools permission)
|
|
2505
2552
|
// Place after attempt_completion as it's an optional tool
|
|
@@ -3304,8 +3351,9 @@ Follow these instructions carefully:
|
|
|
3304
3351
|
if (this.enableSkills && this.allowedTools.isEnabled('listSkills')) validTools.push('listSkills');
|
|
3305
3352
|
if (this.enableSkills && this.allowedTools.isEnabled('useSkill')) validTools.push('useSkill');
|
|
3306
3353
|
if (this.allowedTools.isEnabled('readImage')) validTools.push('readImage');
|
|
3307
|
-
// Always allow attempt_completion - it's a completion signal, not a tool
|
|
3354
|
+
// Always allow attempt_completion in validTools - it's a completion signal, not a tool
|
|
3308
3355
|
// This ensures agents can complete even when disableTools: true is set (fixes #333)
|
|
3356
|
+
// The tool DEFINITION may be hidden in raw AI mode, but we still need to recognize it
|
|
3309
3357
|
validTools.push('attempt_completion');
|
|
3310
3358
|
|
|
3311
3359
|
// Edit tools (require both allowEdit flag AND allowedTools permission)
|
package/build/agent/index.js
CHANGED
|
@@ -3859,8 +3859,10 @@ async function delegate({
|
|
|
3859
3859
|
enableMcp = false,
|
|
3860
3860
|
mcpConfig = null,
|
|
3861
3861
|
mcpConfigPath = null,
|
|
3862
|
-
delegationManager = null
|
|
3862
|
+
delegationManager = null,
|
|
3863
3863
|
// Optional per-instance manager, falls back to default singleton
|
|
3864
|
+
concurrencyLimiter = null
|
|
3865
|
+
// Optional global AI concurrency limiter
|
|
3864
3866
|
}) {
|
|
3865
3867
|
if (!task || typeof task !== "string") {
|
|
3866
3868
|
throw new Error("Task parameter is required and must be a string");
|
|
@@ -3936,8 +3938,10 @@ async function delegate({
|
|
|
3936
3938
|
// Inherit from parent (subagent creates own MCPXmlBridge)
|
|
3937
3939
|
mcpConfig,
|
|
3938
3940
|
// Inherit from parent
|
|
3939
|
-
mcpConfigPath
|
|
3941
|
+
mcpConfigPath,
|
|
3940
3942
|
// Inherit from parent
|
|
3943
|
+
concurrencyLimiter
|
|
3944
|
+
// Inherit global AI concurrency limiter
|
|
3941
3945
|
});
|
|
3942
3946
|
if (debug) {
|
|
3943
3947
|
console.error(`[DELEGATE] Created subagent with session ${sessionId}`);
|
|
@@ -4034,10 +4038,10 @@ var init_delegate = __esm({
|
|
|
4034
4038
|
"use strict";
|
|
4035
4039
|
init_ProbeAgent();
|
|
4036
4040
|
DelegationManager = class {
|
|
4037
|
-
constructor() {
|
|
4038
|
-
this.maxConcurrent = parseInt(process.env.MAX_CONCURRENT_DELEGATIONS || "3", 10);
|
|
4039
|
-
this.maxPerSession = parseInt(process.env.MAX_DELEGATIONS_PER_SESSION || "10", 10);
|
|
4040
|
-
this.defaultQueueTimeout = parseInt(process.env.DELEGATION_QUEUE_TIMEOUT || "60000", 10);
|
|
4041
|
+
constructor(options = {}) {
|
|
4042
|
+
this.maxConcurrent = options.maxConcurrent ?? parseInt(process.env.MAX_CONCURRENT_DELEGATIONS || "3", 10);
|
|
4043
|
+
this.maxPerSession = options.maxPerSession ?? parseInt(process.env.MAX_DELEGATIONS_PER_SESSION || "10", 10);
|
|
4044
|
+
this.defaultQueueTimeout = options.queueTimeout ?? parseInt(process.env.DELEGATION_QUEUE_TIMEOUT || "60000", 10);
|
|
4041
4045
|
this.sessionDelegations = /* @__PURE__ */ new Map();
|
|
4042
4046
|
this.globalActive = 0;
|
|
4043
4047
|
this.waitQueue = [];
|
|
@@ -9199,7 +9203,15 @@ function createTaskTool(options = {}) {
|
|
|
9199
9203
|
});
|
|
9200
9204
|
return `Error: Invalid task parameters - ${validation.error.message}`;
|
|
9201
9205
|
}
|
|
9202
|
-
const { action, tasks, id, title, description, status, priority, dependencies, after } = validation.data;
|
|
9206
|
+
const { action, tasks: rawTasks, id, title, description, status, priority, dependencies, after } = validation.data;
|
|
9207
|
+
let tasks = rawTasks;
|
|
9208
|
+
if (typeof rawTasks === "string") {
|
|
9209
|
+
try {
|
|
9210
|
+
tasks = JSON.parse(rawTasks);
|
|
9211
|
+
} catch (e) {
|
|
9212
|
+
return `Error: Invalid tasks JSON - ${e.message}`;
|
|
9213
|
+
}
|
|
9214
|
+
}
|
|
9203
9215
|
switch (action) {
|
|
9204
9216
|
case "create": {
|
|
9205
9217
|
if (tasks && Array.isArray(tasks)) {
|
|
@@ -9374,7 +9386,8 @@ var init_taskTool = __esm({
|
|
|
9374
9386
|
});
|
|
9375
9387
|
taskSchema = external_exports.object({
|
|
9376
9388
|
action: external_exports.enum(["create", "update", "complete", "delete", "list"]),
|
|
9377
|
-
|
|
9389
|
+
// Accept both array and JSON string (AI models sometimes serialize as string)
|
|
9390
|
+
tasks: external_exports.union([external_exports.array(external_exports.union([external_exports.string(), taskItemSchema])), external_exports.string()]).optional(),
|
|
9378
9391
|
id: external_exports.string().optional(),
|
|
9379
9392
|
title: external_exports.string().optional(),
|
|
9380
9393
|
description: external_exports.string().optional(),
|
|
@@ -9485,6 +9498,25 @@ SKIP TASKS for single-goal requests, even if they require multiple searches:
|
|
|
9485
9498
|
**Key insight**: Multiple *internal steps* (search, read, analyze) are NOT the same as multiple *goals*.
|
|
9486
9499
|
A single investigation with many steps is still ONE task, not many.
|
|
9487
9500
|
|
|
9501
|
+
## Task Granularity
|
|
9502
|
+
|
|
9503
|
+
Tasks represent LOGICAL UNITS OF WORK, not individual files or steps:
|
|
9504
|
+
- "Fix 8 similar test files" \u2192 ONE task (same type of fix across files)
|
|
9505
|
+
- "Update API + tests + docs" \u2192 THREE tasks (different types of work)
|
|
9506
|
+
- "Implement feature in 5 files" \u2192 ONE task (single feature)
|
|
9507
|
+
|
|
9508
|
+
**Rule of thumb**: If you're creating more than 3-4 tasks, you're probably too granular.
|
|
9509
|
+
|
|
9510
|
+
**Anti-patterns to avoid**:
|
|
9511
|
+
- One task per file \u274C
|
|
9512
|
+
- One task per function \u274C
|
|
9513
|
+
- One task per repository (when same type of work) \u274C
|
|
9514
|
+
|
|
9515
|
+
**Good patterns**:
|
|
9516
|
+
- One task per distinct deliverable \u2713
|
|
9517
|
+
- One task per phase (implement, test, document) \u2713
|
|
9518
|
+
- One task per different type of work \u2713
|
|
9519
|
+
|
|
9488
9520
|
MODIFY TASKS when (during execution):
|
|
9489
9521
|
- You discover the problem is more complex than expected \u2192 Add new tasks
|
|
9490
9522
|
- A single task covers too much scope \u2192 Split into smaller tasks
|
|
@@ -55823,6 +55855,7 @@ var require_pattern = __commonJS({
|
|
|
55823
55855
|
"use strict";
|
|
55824
55856
|
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
55825
55857
|
var code_1 = require_code2();
|
|
55858
|
+
var util_1 = require_util3();
|
|
55826
55859
|
var codegen_1 = require_codegen();
|
|
55827
55860
|
var error = {
|
|
55828
55861
|
message: ({ schemaCode }) => (0, codegen_1.str)`must match pattern "${schemaCode}"`,
|
|
@@ -55835,10 +55868,18 @@ var require_pattern = __commonJS({
|
|
|
55835
55868
|
$data: true,
|
|
55836
55869
|
error,
|
|
55837
55870
|
code(cxt) {
|
|
55838
|
-
const { data, $data, schema, schemaCode, it } = cxt;
|
|
55871
|
+
const { gen, data, $data, schema, schemaCode, it } = cxt;
|
|
55839
55872
|
const u = it.opts.unicodeRegExp ? "u" : "";
|
|
55840
|
-
|
|
55841
|
-
|
|
55873
|
+
if ($data) {
|
|
55874
|
+
const { regExp } = it.opts.code;
|
|
55875
|
+
const regExpCode = regExp.code === "new RegExp" ? (0, codegen_1._)`new RegExp` : (0, util_1.useFunc)(gen, regExp);
|
|
55876
|
+
const valid = gen.let("valid");
|
|
55877
|
+
gen.try(() => gen.assign(valid, (0, codegen_1._)`${regExpCode}(${schemaCode}, ${u}).test(${data})`), () => gen.assign(valid, false));
|
|
55878
|
+
cxt.fail$data((0, codegen_1._)`!${valid}`);
|
|
55879
|
+
} else {
|
|
55880
|
+
const regExp = (0, code_1.usePattern)(cxt, schema);
|
|
55881
|
+
cxt.fail$data((0, codegen_1._)`!${regExp}.test(${data})`);
|
|
55882
|
+
}
|
|
55842
55883
|
}
|
|
55843
55884
|
};
|
|
55844
55885
|
exports2.default = def;
|
|
@@ -70535,6 +70576,7 @@ var init_ProbeAgent = __esm({
|
|
|
70535
70576
|
this.enableTasks = !!options.enableTasks;
|
|
70536
70577
|
this.taskManager = null;
|
|
70537
70578
|
this.delegationManager = new DelegationManager();
|
|
70579
|
+
this.concurrencyLimiter = options.concurrencyLimiter || null;
|
|
70538
70580
|
this.requestTimeout = options.requestTimeout ?? (() => {
|
|
70539
70581
|
if (process.env.REQUEST_TIMEOUT) {
|
|
70540
70582
|
const parsed = parseInt(process.env.REQUEST_TIMEOUT, 10);
|
|
@@ -70911,6 +70953,8 @@ var init_ProbeAgent = __esm({
|
|
|
70911
70953
|
model: this.clientApiModel,
|
|
70912
70954
|
delegationManager: this.delegationManager,
|
|
70913
70955
|
// Per-instance delegation limits
|
|
70956
|
+
concurrencyLimiter: this.concurrencyLimiter,
|
|
70957
|
+
// Global AI concurrency limiter
|
|
70914
70958
|
isToolAllowed
|
|
70915
70959
|
};
|
|
70916
70960
|
const baseTools = createTools(configOptions);
|
|
@@ -71332,6 +71376,14 @@ var init_ProbeAgent = __esm({
|
|
|
71332
71376
|
* @private
|
|
71333
71377
|
*/
|
|
71334
71378
|
async streamTextWithRetryAndFallback(options) {
|
|
71379
|
+
const limiter = this.concurrencyLimiter;
|
|
71380
|
+
if (limiter) {
|
|
71381
|
+
await limiter.acquire(null);
|
|
71382
|
+
if (this.debug) {
|
|
71383
|
+
const stats = limiter.getStats();
|
|
71384
|
+
console.log(`[DEBUG] Acquired global AI concurrency slot (${stats.globalActive}/${stats.maxConcurrent}, queue: ${stats.queueSize})`);
|
|
71385
|
+
}
|
|
71386
|
+
}
|
|
71335
71387
|
const controller = new AbortController();
|
|
71336
71388
|
const timeoutState = { timeoutId: null };
|
|
71337
71389
|
if (this.maxOperationTimeout && this.maxOperationTimeout > 0) {
|
|
@@ -71345,12 +71397,10 @@ var init_ProbeAgent = __esm({
|
|
|
71345
71397
|
try {
|
|
71346
71398
|
const useClaudeCode = this.clientApiProvider === "claude-code" || process.env.USE_CLAUDE_CODE === "true";
|
|
71347
71399
|
const useCodex = this.clientApiProvider === "codex" || process.env.USE_CODEX === "true";
|
|
71400
|
+
let result;
|
|
71348
71401
|
if (useClaudeCode || useCodex) {
|
|
71349
71402
|
try {
|
|
71350
|
-
|
|
71351
|
-
if (result) {
|
|
71352
|
-
return result;
|
|
71353
|
-
}
|
|
71403
|
+
result = await this._tryEngineStreamPath(options, controller, timeoutState);
|
|
71354
71404
|
} catch (error) {
|
|
71355
71405
|
if (this.debug) {
|
|
71356
71406
|
const engineType = useClaudeCode ? "Claude Code" : "Codex";
|
|
@@ -71358,7 +71408,37 @@ var init_ProbeAgent = __esm({
|
|
|
71358
71408
|
}
|
|
71359
71409
|
}
|
|
71360
71410
|
}
|
|
71361
|
-
|
|
71411
|
+
if (!result) {
|
|
71412
|
+
result = await this._executeWithVercelProvider(options, controller);
|
|
71413
|
+
}
|
|
71414
|
+
if (limiter && result.textStream) {
|
|
71415
|
+
const originalStream = result.textStream;
|
|
71416
|
+
const debug = this.debug;
|
|
71417
|
+
result.textStream = (async function* () {
|
|
71418
|
+
try {
|
|
71419
|
+
for await (const chunk of originalStream) {
|
|
71420
|
+
yield chunk;
|
|
71421
|
+
}
|
|
71422
|
+
} finally {
|
|
71423
|
+
limiter.release(null);
|
|
71424
|
+
if (debug) {
|
|
71425
|
+
const stats = limiter.getStats();
|
|
71426
|
+
console.log(`[DEBUG] Released global AI concurrency slot (${stats.globalActive}/${stats.maxConcurrent}, queue: ${stats.queueSize})`);
|
|
71427
|
+
}
|
|
71428
|
+
}
|
|
71429
|
+
})();
|
|
71430
|
+
} else if (limiter) {
|
|
71431
|
+
limiter.release(null);
|
|
71432
|
+
}
|
|
71433
|
+
return result;
|
|
71434
|
+
} catch (error) {
|
|
71435
|
+
if (limiter) {
|
|
71436
|
+
limiter.release(null);
|
|
71437
|
+
if (this.debug) {
|
|
71438
|
+
console.log(`[DEBUG] Released global AI concurrency slot on error`);
|
|
71439
|
+
}
|
|
71440
|
+
}
|
|
71441
|
+
throw error;
|
|
71362
71442
|
} finally {
|
|
71363
71443
|
if (timeoutState.timeoutId) {
|
|
71364
71444
|
clearTimeout(timeoutState.timeoutId);
|
|
@@ -72250,10 +72330,8 @@ Workspace: ${this.allowedFolders.join(", ")}`;
|
|
|
72250
72330
|
toolDefinitions += `${taskToolDefinition}
|
|
72251
72331
|
`;
|
|
72252
72332
|
}
|
|
72253
|
-
|
|
72254
|
-
toolDefinitions += `${attemptCompletionToolDefinition}
|
|
72333
|
+
toolDefinitions += `${attemptCompletionToolDefinition}
|
|
72255
72334
|
`;
|
|
72256
|
-
}
|
|
72257
72335
|
if (this.enableDelegate && isToolAllowed("delegate")) {
|
|
72258
72336
|
toolDefinitions += `${delegateToolDefinition}
|
|
72259
72337
|
`;
|
|
@@ -23,7 +23,8 @@ export const taskItemSchema = z.object({
|
|
|
23
23
|
*/
|
|
24
24
|
export const taskSchema = z.object({
|
|
25
25
|
action: z.enum(['create', 'update', 'complete', 'delete', 'list']),
|
|
26
|
-
|
|
26
|
+
// Accept both array and JSON string (AI models sometimes serialize as string)
|
|
27
|
+
tasks: z.union([z.array(z.union([z.string(), taskItemSchema])), z.string()]).optional(),
|
|
27
28
|
id: z.string().optional(),
|
|
28
29
|
title: z.string().optional(),
|
|
29
30
|
description: z.string().optional(),
|
|
@@ -142,6 +143,25 @@ SKIP TASKS for single-goal requests, even if they require multiple searches:
|
|
|
142
143
|
**Key insight**: Multiple *internal steps* (search, read, analyze) are NOT the same as multiple *goals*.
|
|
143
144
|
A single investigation with many steps is still ONE task, not many.
|
|
144
145
|
|
|
146
|
+
## Task Granularity
|
|
147
|
+
|
|
148
|
+
Tasks represent LOGICAL UNITS OF WORK, not individual files or steps:
|
|
149
|
+
- "Fix 8 similar test files" → ONE task (same type of fix across files)
|
|
150
|
+
- "Update API + tests + docs" → THREE tasks (different types of work)
|
|
151
|
+
- "Implement feature in 5 files" → ONE task (single feature)
|
|
152
|
+
|
|
153
|
+
**Rule of thumb**: If you're creating more than 3-4 tasks, you're probably too granular.
|
|
154
|
+
|
|
155
|
+
**Anti-patterns to avoid**:
|
|
156
|
+
- One task per file ❌
|
|
157
|
+
- One task per function ❌
|
|
158
|
+
- One task per repository (when same type of work) ❌
|
|
159
|
+
|
|
160
|
+
**Good patterns**:
|
|
161
|
+
- One task per distinct deliverable ✓
|
|
162
|
+
- One task per phase (implement, test, document) ✓
|
|
163
|
+
- One task per different type of work ✓
|
|
164
|
+
|
|
145
165
|
MODIFY TASKS when (during execution):
|
|
146
166
|
- You discover the problem is more complex than expected → Add new tasks
|
|
147
167
|
- A single task covers too much scope → Split into smaller tasks
|
|
@@ -314,7 +334,17 @@ export function createTaskTool(options = {}) {
|
|
|
314
334
|
return `Error: Invalid task parameters - ${validation.error.message}`;
|
|
315
335
|
}
|
|
316
336
|
|
|
317
|
-
const { action, tasks, id, title, description, status, priority, dependencies, after } = validation.data;
|
|
337
|
+
const { action, tasks: rawTasks, id, title, description, status, priority, dependencies, after } = validation.data;
|
|
338
|
+
|
|
339
|
+
// Parse tasks if passed as JSON string (common AI model behavior)
|
|
340
|
+
let tasks = rawTasks;
|
|
341
|
+
if (typeof rawTasks === 'string') {
|
|
342
|
+
try {
|
|
343
|
+
tasks = JSON.parse(rawTasks);
|
|
344
|
+
} catch (e) {
|
|
345
|
+
return `Error: Invalid tasks JSON - ${e.message}`;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
318
348
|
|
|
319
349
|
switch (action) {
|
|
320
350
|
case 'create': {
|
package/build/delegate.js
CHANGED
|
@@ -19,11 +19,14 @@ import { ProbeAgent } from './agent/ProbeAgent.js';
|
|
|
19
19
|
* - For long-running processes, periodic cleanup of stale sessions may be needed
|
|
20
20
|
*/
|
|
21
21
|
class DelegationManager {
|
|
22
|
-
constructor() {
|
|
23
|
-
this.maxConcurrent =
|
|
24
|
-
|
|
22
|
+
constructor(options = {}) {
|
|
23
|
+
this.maxConcurrent = options.maxConcurrent
|
|
24
|
+
?? parseInt(process.env.MAX_CONCURRENT_DELEGATIONS || '3', 10);
|
|
25
|
+
this.maxPerSession = options.maxPerSession
|
|
26
|
+
?? parseInt(process.env.MAX_DELEGATIONS_PER_SESSION || '10', 10);
|
|
25
27
|
// Default queue timeout: 60 seconds. Set DELEGATION_QUEUE_TIMEOUT=0 to disable.
|
|
26
|
-
this.defaultQueueTimeout =
|
|
28
|
+
this.defaultQueueTimeout = options.queueTimeout
|
|
29
|
+
?? parseInt(process.env.DELEGATION_QUEUE_TIMEOUT || '60000', 10);
|
|
27
30
|
|
|
28
31
|
// Track delegations per session with timestamp for potential TTL cleanup
|
|
29
32
|
// Map<string, { count: number, lastUpdated: number }>
|
|
@@ -353,6 +356,7 @@ const DEFAULT_DELEGATE_TIMEOUT = parseInt(process.env.DELEGATE_TIMEOUT, 10) || 3
|
|
|
353
356
|
* @param {boolean} [options.enableMcp=false] - Enable MCP tool integration (inherited from parent)
|
|
354
357
|
* @param {Object} [options.mcpConfig] - MCP configuration object (inherited from parent)
|
|
355
358
|
* @param {string} [options.mcpConfigPath] - Path to MCP configuration file (inherited from parent)
|
|
359
|
+
* @param {Object} [options.concurrencyLimiter=null] - Global AI concurrency limiter (DelegationManager instance)
|
|
356
360
|
* @returns {Promise<string>} The response from the delegate agent
|
|
357
361
|
*/
|
|
358
362
|
export async function delegate({
|
|
@@ -379,7 +383,8 @@ export async function delegate({
|
|
|
379
383
|
enableMcp = false,
|
|
380
384
|
mcpConfig = null,
|
|
381
385
|
mcpConfigPath = null,
|
|
382
|
-
delegationManager = null // Optional per-instance manager, falls back to default singleton
|
|
386
|
+
delegationManager = null, // Optional per-instance manager, falls back to default singleton
|
|
387
|
+
concurrencyLimiter = null // Optional global AI concurrency limiter
|
|
383
388
|
}) {
|
|
384
389
|
if (!task || typeof task !== 'string') {
|
|
385
390
|
throw new Error('Task parameter is required and must be a string');
|
|
@@ -464,7 +469,8 @@ export async function delegate({
|
|
|
464
469
|
enableTasks, // Inherit from parent (subagent gets isolated TaskManager)
|
|
465
470
|
enableMcp, // Inherit from parent (subagent creates own MCPXmlBridge)
|
|
466
471
|
mcpConfig, // Inherit from parent
|
|
467
|
-
mcpConfigPath // Inherit from parent
|
|
472
|
+
mcpConfigPath, // Inherit from parent
|
|
473
|
+
concurrencyLimiter // Inherit global AI concurrency limiter
|
|
468
474
|
});
|
|
469
475
|
|
|
470
476
|
if (debug) {
|
package/cjs/agent/ProbeAgent.cjs
CHANGED
|
@@ -30969,8 +30969,10 @@ async function delegate({
|
|
|
30969
30969
|
enableMcp = false,
|
|
30970
30970
|
mcpConfig = null,
|
|
30971
30971
|
mcpConfigPath = null,
|
|
30972
|
-
delegationManager = null
|
|
30972
|
+
delegationManager = null,
|
|
30973
30973
|
// Optional per-instance manager, falls back to default singleton
|
|
30974
|
+
concurrencyLimiter = null
|
|
30975
|
+
// Optional global AI concurrency limiter
|
|
30974
30976
|
}) {
|
|
30975
30977
|
if (!task || typeof task !== "string") {
|
|
30976
30978
|
throw new Error("Task parameter is required and must be a string");
|
|
@@ -31046,8 +31048,10 @@ async function delegate({
|
|
|
31046
31048
|
// Inherit from parent (subagent creates own MCPXmlBridge)
|
|
31047
31049
|
mcpConfig,
|
|
31048
31050
|
// Inherit from parent
|
|
31049
|
-
mcpConfigPath
|
|
31051
|
+
mcpConfigPath,
|
|
31050
31052
|
// Inherit from parent
|
|
31053
|
+
concurrencyLimiter
|
|
31054
|
+
// Inherit global AI concurrency limiter
|
|
31051
31055
|
});
|
|
31052
31056
|
if (debug) {
|
|
31053
31057
|
console.error(`[DELEGATE] Created subagent with session ${sessionId}`);
|
|
@@ -31145,10 +31149,10 @@ var init_delegate = __esm({
|
|
|
31145
31149
|
import_crypto2 = require("crypto");
|
|
31146
31150
|
init_ProbeAgent();
|
|
31147
31151
|
DelegationManager = class {
|
|
31148
|
-
constructor() {
|
|
31149
|
-
this.maxConcurrent = parseInt(process.env.MAX_CONCURRENT_DELEGATIONS || "3", 10);
|
|
31150
|
-
this.maxPerSession = parseInt(process.env.MAX_DELEGATIONS_PER_SESSION || "10", 10);
|
|
31151
|
-
this.defaultQueueTimeout = parseInt(process.env.DELEGATION_QUEUE_TIMEOUT || "60000", 10);
|
|
31152
|
+
constructor(options = {}) {
|
|
31153
|
+
this.maxConcurrent = options.maxConcurrent ?? parseInt(process.env.MAX_CONCURRENT_DELEGATIONS || "3", 10);
|
|
31154
|
+
this.maxPerSession = options.maxPerSession ?? parseInt(process.env.MAX_DELEGATIONS_PER_SESSION || "10", 10);
|
|
31155
|
+
this.defaultQueueTimeout = options.queueTimeout ?? parseInt(process.env.DELEGATION_QUEUE_TIMEOUT || "60000", 10);
|
|
31152
31156
|
this.sessionDelegations = /* @__PURE__ */ new Map();
|
|
31153
31157
|
this.globalActive = 0;
|
|
31154
31158
|
this.waitQueue = [];
|
|
@@ -36310,7 +36314,15 @@ function createTaskTool(options = {}) {
|
|
|
36310
36314
|
});
|
|
36311
36315
|
return `Error: Invalid task parameters - ${validation.error.message}`;
|
|
36312
36316
|
}
|
|
36313
|
-
const { action, tasks, id, title, description, status, priority, dependencies, after } = validation.data;
|
|
36317
|
+
const { action, tasks: rawTasks, id, title, description, status, priority, dependencies, after } = validation.data;
|
|
36318
|
+
let tasks = rawTasks;
|
|
36319
|
+
if (typeof rawTasks === "string") {
|
|
36320
|
+
try {
|
|
36321
|
+
tasks = JSON.parse(rawTasks);
|
|
36322
|
+
} catch (e4) {
|
|
36323
|
+
return `Error: Invalid tasks JSON - ${e4.message}`;
|
|
36324
|
+
}
|
|
36325
|
+
}
|
|
36314
36326
|
switch (action) {
|
|
36315
36327
|
case "create": {
|
|
36316
36328
|
if (tasks && Array.isArray(tasks)) {
|
|
@@ -36485,7 +36497,8 @@ var init_taskTool = __esm({
|
|
|
36485
36497
|
});
|
|
36486
36498
|
taskSchema = external_exports.object({
|
|
36487
36499
|
action: external_exports.enum(["create", "update", "complete", "delete", "list"]),
|
|
36488
|
-
|
|
36500
|
+
// Accept both array and JSON string (AI models sometimes serialize as string)
|
|
36501
|
+
tasks: external_exports.union([external_exports.array(external_exports.union([external_exports.string(), taskItemSchema])), external_exports.string()]).optional(),
|
|
36489
36502
|
id: external_exports.string().optional(),
|
|
36490
36503
|
title: external_exports.string().optional(),
|
|
36491
36504
|
description: external_exports.string().optional(),
|
|
@@ -36596,6 +36609,25 @@ SKIP TASKS for single-goal requests, even if they require multiple searches:
|
|
|
36596
36609
|
**Key insight**: Multiple *internal steps* (search, read, analyze) are NOT the same as multiple *goals*.
|
|
36597
36610
|
A single investigation with many steps is still ONE task, not many.
|
|
36598
36611
|
|
|
36612
|
+
## Task Granularity
|
|
36613
|
+
|
|
36614
|
+
Tasks represent LOGICAL UNITS OF WORK, not individual files or steps:
|
|
36615
|
+
- "Fix 8 similar test files" \u2192 ONE task (same type of fix across files)
|
|
36616
|
+
- "Update API + tests + docs" \u2192 THREE tasks (different types of work)
|
|
36617
|
+
- "Implement feature in 5 files" \u2192 ONE task (single feature)
|
|
36618
|
+
|
|
36619
|
+
**Rule of thumb**: If you're creating more than 3-4 tasks, you're probably too granular.
|
|
36620
|
+
|
|
36621
|
+
**Anti-patterns to avoid**:
|
|
36622
|
+
- One task per file \u274C
|
|
36623
|
+
- One task per function \u274C
|
|
36624
|
+
- One task per repository (when same type of work) \u274C
|
|
36625
|
+
|
|
36626
|
+
**Good patterns**:
|
|
36627
|
+
- One task per distinct deliverable \u2713
|
|
36628
|
+
- One task per phase (implement, test, document) \u2713
|
|
36629
|
+
- One task per different type of work \u2713
|
|
36630
|
+
|
|
36599
36631
|
MODIFY TASKS when (during execution):
|
|
36600
36632
|
- You discover the problem is more complex than expected \u2192 Add new tasks
|
|
36601
36633
|
- A single task covers too much scope \u2192 Split into smaller tasks
|
|
@@ -82501,6 +82533,7 @@ var require_pattern = __commonJS({
|
|
|
82501
82533
|
"use strict";
|
|
82502
82534
|
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
82503
82535
|
var code_1 = require_code2();
|
|
82536
|
+
var util_1 = require_util3();
|
|
82504
82537
|
var codegen_1 = require_codegen();
|
|
82505
82538
|
var error2 = {
|
|
82506
82539
|
message: ({ schemaCode }) => (0, codegen_1.str)`must match pattern "${schemaCode}"`,
|
|
@@ -82513,10 +82546,18 @@ var require_pattern = __commonJS({
|
|
|
82513
82546
|
$data: true,
|
|
82514
82547
|
error: error2,
|
|
82515
82548
|
code(cxt) {
|
|
82516
|
-
const { data: data2, $data, schema, schemaCode, it } = cxt;
|
|
82549
|
+
const { gen, data: data2, $data, schema, schemaCode, it } = cxt;
|
|
82517
82550
|
const u4 = it.opts.unicodeRegExp ? "u" : "";
|
|
82518
|
-
|
|
82519
|
-
|
|
82551
|
+
if ($data) {
|
|
82552
|
+
const { regExp } = it.opts.code;
|
|
82553
|
+
const regExpCode = regExp.code === "new RegExp" ? (0, codegen_1._)`new RegExp` : (0, util_1.useFunc)(gen, regExp);
|
|
82554
|
+
const valid = gen.let("valid");
|
|
82555
|
+
gen.try(() => gen.assign(valid, (0, codegen_1._)`${regExpCode}(${schemaCode}, ${u4}).test(${data2})`), () => gen.assign(valid, false));
|
|
82556
|
+
cxt.fail$data((0, codegen_1._)`!${valid}`);
|
|
82557
|
+
} else {
|
|
82558
|
+
const regExp = (0, code_1.usePattern)(cxt, schema);
|
|
82559
|
+
cxt.fail$data((0, codegen_1._)`!${regExp}.test(${data2})`);
|
|
82560
|
+
}
|
|
82520
82561
|
}
|
|
82521
82562
|
};
|
|
82522
82563
|
exports2.default = def;
|
|
@@ -97212,6 +97253,7 @@ var init_ProbeAgent = __esm({
|
|
|
97212
97253
|
this.enableTasks = !!options.enableTasks;
|
|
97213
97254
|
this.taskManager = null;
|
|
97214
97255
|
this.delegationManager = new DelegationManager();
|
|
97256
|
+
this.concurrencyLimiter = options.concurrencyLimiter || null;
|
|
97215
97257
|
this.requestTimeout = options.requestTimeout ?? (() => {
|
|
97216
97258
|
if (process.env.REQUEST_TIMEOUT) {
|
|
97217
97259
|
const parsed = parseInt(process.env.REQUEST_TIMEOUT, 10);
|
|
@@ -97588,6 +97630,8 @@ var init_ProbeAgent = __esm({
|
|
|
97588
97630
|
model: this.clientApiModel,
|
|
97589
97631
|
delegationManager: this.delegationManager,
|
|
97590
97632
|
// Per-instance delegation limits
|
|
97633
|
+
concurrencyLimiter: this.concurrencyLimiter,
|
|
97634
|
+
// Global AI concurrency limiter
|
|
97591
97635
|
isToolAllowed
|
|
97592
97636
|
};
|
|
97593
97637
|
const baseTools = createTools(configOptions);
|
|
@@ -98009,6 +98053,14 @@ var init_ProbeAgent = __esm({
|
|
|
98009
98053
|
* @private
|
|
98010
98054
|
*/
|
|
98011
98055
|
async streamTextWithRetryAndFallback(options) {
|
|
98056
|
+
const limiter = this.concurrencyLimiter;
|
|
98057
|
+
if (limiter) {
|
|
98058
|
+
await limiter.acquire(null);
|
|
98059
|
+
if (this.debug) {
|
|
98060
|
+
const stats = limiter.getStats();
|
|
98061
|
+
console.log(`[DEBUG] Acquired global AI concurrency slot (${stats.globalActive}/${stats.maxConcurrent}, queue: ${stats.queueSize})`);
|
|
98062
|
+
}
|
|
98063
|
+
}
|
|
98012
98064
|
const controller = new AbortController();
|
|
98013
98065
|
const timeoutState = { timeoutId: null };
|
|
98014
98066
|
if (this.maxOperationTimeout && this.maxOperationTimeout > 0) {
|
|
@@ -98022,12 +98074,10 @@ var init_ProbeAgent = __esm({
|
|
|
98022
98074
|
try {
|
|
98023
98075
|
const useClaudeCode = this.clientApiProvider === "claude-code" || process.env.USE_CLAUDE_CODE === "true";
|
|
98024
98076
|
const useCodex = this.clientApiProvider === "codex" || process.env.USE_CODEX === "true";
|
|
98077
|
+
let result;
|
|
98025
98078
|
if (useClaudeCode || useCodex) {
|
|
98026
98079
|
try {
|
|
98027
|
-
|
|
98028
|
-
if (result) {
|
|
98029
|
-
return result;
|
|
98030
|
-
}
|
|
98080
|
+
result = await this._tryEngineStreamPath(options, controller, timeoutState);
|
|
98031
98081
|
} catch (error2) {
|
|
98032
98082
|
if (this.debug) {
|
|
98033
98083
|
const engineType = useClaudeCode ? "Claude Code" : "Codex";
|
|
@@ -98035,7 +98085,37 @@ var init_ProbeAgent = __esm({
|
|
|
98035
98085
|
}
|
|
98036
98086
|
}
|
|
98037
98087
|
}
|
|
98038
|
-
|
|
98088
|
+
if (!result) {
|
|
98089
|
+
result = await this._executeWithVercelProvider(options, controller);
|
|
98090
|
+
}
|
|
98091
|
+
if (limiter && result.textStream) {
|
|
98092
|
+
const originalStream = result.textStream;
|
|
98093
|
+
const debug = this.debug;
|
|
98094
|
+
result.textStream = (async function* () {
|
|
98095
|
+
try {
|
|
98096
|
+
for await (const chunk of originalStream) {
|
|
98097
|
+
yield chunk;
|
|
98098
|
+
}
|
|
98099
|
+
} finally {
|
|
98100
|
+
limiter.release(null);
|
|
98101
|
+
if (debug) {
|
|
98102
|
+
const stats = limiter.getStats();
|
|
98103
|
+
console.log(`[DEBUG] Released global AI concurrency slot (${stats.globalActive}/${stats.maxConcurrent}, queue: ${stats.queueSize})`);
|
|
98104
|
+
}
|
|
98105
|
+
}
|
|
98106
|
+
})();
|
|
98107
|
+
} else if (limiter) {
|
|
98108
|
+
limiter.release(null);
|
|
98109
|
+
}
|
|
98110
|
+
return result;
|
|
98111
|
+
} catch (error2) {
|
|
98112
|
+
if (limiter) {
|
|
98113
|
+
limiter.release(null);
|
|
98114
|
+
if (this.debug) {
|
|
98115
|
+
console.log(`[DEBUG] Released global AI concurrency slot on error`);
|
|
98116
|
+
}
|
|
98117
|
+
}
|
|
98118
|
+
throw error2;
|
|
98039
98119
|
} finally {
|
|
98040
98120
|
if (timeoutState.timeoutId) {
|
|
98041
98121
|
clearTimeout(timeoutState.timeoutId);
|
|
@@ -98927,10 +99007,8 @@ Workspace: ${this.allowedFolders.join(", ")}`;
|
|
|
98927
99007
|
toolDefinitions += `${taskToolDefinition}
|
|
98928
99008
|
`;
|
|
98929
99009
|
}
|
|
98930
|
-
|
|
98931
|
-
toolDefinitions += `${attemptCompletionToolDefinition}
|
|
99010
|
+
toolDefinitions += `${attemptCompletionToolDefinition}
|
|
98932
99011
|
`;
|
|
98933
|
-
}
|
|
98934
99012
|
if (this.enableDelegate && isToolAllowed("delegate")) {
|
|
98935
99013
|
toolDefinitions += `${delegateToolDefinition}
|
|
98936
99014
|
`;
|
package/cjs/index.cjs
CHANGED
|
@@ -35480,7 +35480,15 @@ function createTaskTool(options = {}) {
|
|
|
35480
35480
|
});
|
|
35481
35481
|
return `Error: Invalid task parameters - ${validation.error.message}`;
|
|
35482
35482
|
}
|
|
35483
|
-
const { action, tasks, id, title, description, status, priority, dependencies, after } = validation.data;
|
|
35483
|
+
const { action, tasks: rawTasks, id, title, description, status, priority, dependencies, after } = validation.data;
|
|
35484
|
+
let tasks = rawTasks;
|
|
35485
|
+
if (typeof rawTasks === "string") {
|
|
35486
|
+
try {
|
|
35487
|
+
tasks = JSON.parse(rawTasks);
|
|
35488
|
+
} catch (e4) {
|
|
35489
|
+
return `Error: Invalid tasks JSON - ${e4.message}`;
|
|
35490
|
+
}
|
|
35491
|
+
}
|
|
35484
35492
|
switch (action) {
|
|
35485
35493
|
case "create": {
|
|
35486
35494
|
if (tasks && Array.isArray(tasks)) {
|
|
@@ -35655,7 +35663,8 @@ var init_taskTool = __esm({
|
|
|
35655
35663
|
});
|
|
35656
35664
|
taskSchema = external_exports.object({
|
|
35657
35665
|
action: external_exports.enum(["create", "update", "complete", "delete", "list"]),
|
|
35658
|
-
|
|
35666
|
+
// Accept both array and JSON string (AI models sometimes serialize as string)
|
|
35667
|
+
tasks: external_exports.union([external_exports.array(external_exports.union([external_exports.string(), taskItemSchema])), external_exports.string()]).optional(),
|
|
35659
35668
|
id: external_exports.string().optional(),
|
|
35660
35669
|
title: external_exports.string().optional(),
|
|
35661
35670
|
description: external_exports.string().optional(),
|
|
@@ -35766,6 +35775,25 @@ SKIP TASKS for single-goal requests, even if they require multiple searches:
|
|
|
35766
35775
|
**Key insight**: Multiple *internal steps* (search, read, analyze) are NOT the same as multiple *goals*.
|
|
35767
35776
|
A single investigation with many steps is still ONE task, not many.
|
|
35768
35777
|
|
|
35778
|
+
## Task Granularity
|
|
35779
|
+
|
|
35780
|
+
Tasks represent LOGICAL UNITS OF WORK, not individual files or steps:
|
|
35781
|
+
- "Fix 8 similar test files" \u2192 ONE task (same type of fix across files)
|
|
35782
|
+
- "Update API + tests + docs" \u2192 THREE tasks (different types of work)
|
|
35783
|
+
- "Implement feature in 5 files" \u2192 ONE task (single feature)
|
|
35784
|
+
|
|
35785
|
+
**Rule of thumb**: If you're creating more than 3-4 tasks, you're probably too granular.
|
|
35786
|
+
|
|
35787
|
+
**Anti-patterns to avoid**:
|
|
35788
|
+
- One task per file \u274C
|
|
35789
|
+
- One task per function \u274C
|
|
35790
|
+
- One task per repository (when same type of work) \u274C
|
|
35791
|
+
|
|
35792
|
+
**Good patterns**:
|
|
35793
|
+
- One task per distinct deliverable \u2713
|
|
35794
|
+
- One task per phase (implement, test, document) \u2713
|
|
35795
|
+
- One task per different type of work \u2713
|
|
35796
|
+
|
|
35769
35797
|
MODIFY TASKS when (during execution):
|
|
35770
35798
|
- You discover the problem is more complex than expected \u2192 Add new tasks
|
|
35771
35799
|
- A single task covers too much scope \u2192 Split into smaller tasks
|
|
@@ -79234,6 +79262,7 @@ var require_pattern = __commonJS({
|
|
|
79234
79262
|
"use strict";
|
|
79235
79263
|
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
79236
79264
|
var code_1 = require_code2();
|
|
79265
|
+
var util_1 = require_util3();
|
|
79237
79266
|
var codegen_1 = require_codegen();
|
|
79238
79267
|
var error2 = {
|
|
79239
79268
|
message: ({ schemaCode }) => (0, codegen_1.str)`must match pattern "${schemaCode}"`,
|
|
@@ -79246,10 +79275,18 @@ var require_pattern = __commonJS({
|
|
|
79246
79275
|
$data: true,
|
|
79247
79276
|
error: error2,
|
|
79248
79277
|
code(cxt) {
|
|
79249
|
-
const { data: data2, $data, schema, schemaCode, it } = cxt;
|
|
79278
|
+
const { gen, data: data2, $data, schema, schemaCode, it } = cxt;
|
|
79250
79279
|
const u4 = it.opts.unicodeRegExp ? "u" : "";
|
|
79251
|
-
|
|
79252
|
-
|
|
79280
|
+
if ($data) {
|
|
79281
|
+
const { regExp } = it.opts.code;
|
|
79282
|
+
const regExpCode = regExp.code === "new RegExp" ? (0, codegen_1._)`new RegExp` : (0, util_1.useFunc)(gen, regExp);
|
|
79283
|
+
const valid = gen.let("valid");
|
|
79284
|
+
gen.try(() => gen.assign(valid, (0, codegen_1._)`${regExpCode}(${schemaCode}, ${u4}).test(${data2})`), () => gen.assign(valid, false));
|
|
79285
|
+
cxt.fail$data((0, codegen_1._)`!${valid}`);
|
|
79286
|
+
} else {
|
|
79287
|
+
const regExp = (0, code_1.usePattern)(cxt, schema);
|
|
79288
|
+
cxt.fail$data((0, codegen_1._)`!${regExp}.test(${data2})`);
|
|
79289
|
+
}
|
|
79253
79290
|
}
|
|
79254
79291
|
};
|
|
79255
79292
|
exports2.default = def;
|
|
@@ -93945,6 +93982,7 @@ var init_ProbeAgent = __esm({
|
|
|
93945
93982
|
this.enableTasks = !!options.enableTasks;
|
|
93946
93983
|
this.taskManager = null;
|
|
93947
93984
|
this.delegationManager = new DelegationManager();
|
|
93985
|
+
this.concurrencyLimiter = options.concurrencyLimiter || null;
|
|
93948
93986
|
this.requestTimeout = options.requestTimeout ?? (() => {
|
|
93949
93987
|
if (process.env.REQUEST_TIMEOUT) {
|
|
93950
93988
|
const parsed = parseInt(process.env.REQUEST_TIMEOUT, 10);
|
|
@@ -94321,6 +94359,8 @@ var init_ProbeAgent = __esm({
|
|
|
94321
94359
|
model: this.clientApiModel,
|
|
94322
94360
|
delegationManager: this.delegationManager,
|
|
94323
94361
|
// Per-instance delegation limits
|
|
94362
|
+
concurrencyLimiter: this.concurrencyLimiter,
|
|
94363
|
+
// Global AI concurrency limiter
|
|
94324
94364
|
isToolAllowed
|
|
94325
94365
|
};
|
|
94326
94366
|
const baseTools = createTools(configOptions);
|
|
@@ -94742,6 +94782,14 @@ var init_ProbeAgent = __esm({
|
|
|
94742
94782
|
* @private
|
|
94743
94783
|
*/
|
|
94744
94784
|
async streamTextWithRetryAndFallback(options) {
|
|
94785
|
+
const limiter = this.concurrencyLimiter;
|
|
94786
|
+
if (limiter) {
|
|
94787
|
+
await limiter.acquire(null);
|
|
94788
|
+
if (this.debug) {
|
|
94789
|
+
const stats = limiter.getStats();
|
|
94790
|
+
console.log(`[DEBUG] Acquired global AI concurrency slot (${stats.globalActive}/${stats.maxConcurrent}, queue: ${stats.queueSize})`);
|
|
94791
|
+
}
|
|
94792
|
+
}
|
|
94745
94793
|
const controller = new AbortController();
|
|
94746
94794
|
const timeoutState = { timeoutId: null };
|
|
94747
94795
|
if (this.maxOperationTimeout && this.maxOperationTimeout > 0) {
|
|
@@ -94755,12 +94803,10 @@ var init_ProbeAgent = __esm({
|
|
|
94755
94803
|
try {
|
|
94756
94804
|
const useClaudeCode = this.clientApiProvider === "claude-code" || process.env.USE_CLAUDE_CODE === "true";
|
|
94757
94805
|
const useCodex = this.clientApiProvider === "codex" || process.env.USE_CODEX === "true";
|
|
94806
|
+
let result;
|
|
94758
94807
|
if (useClaudeCode || useCodex) {
|
|
94759
94808
|
try {
|
|
94760
|
-
|
|
94761
|
-
if (result) {
|
|
94762
|
-
return result;
|
|
94763
|
-
}
|
|
94809
|
+
result = await this._tryEngineStreamPath(options, controller, timeoutState);
|
|
94764
94810
|
} catch (error2) {
|
|
94765
94811
|
if (this.debug) {
|
|
94766
94812
|
const engineType = useClaudeCode ? "Claude Code" : "Codex";
|
|
@@ -94768,7 +94814,37 @@ var init_ProbeAgent = __esm({
|
|
|
94768
94814
|
}
|
|
94769
94815
|
}
|
|
94770
94816
|
}
|
|
94771
|
-
|
|
94817
|
+
if (!result) {
|
|
94818
|
+
result = await this._executeWithVercelProvider(options, controller);
|
|
94819
|
+
}
|
|
94820
|
+
if (limiter && result.textStream) {
|
|
94821
|
+
const originalStream = result.textStream;
|
|
94822
|
+
const debug = this.debug;
|
|
94823
|
+
result.textStream = (async function* () {
|
|
94824
|
+
try {
|
|
94825
|
+
for await (const chunk of originalStream) {
|
|
94826
|
+
yield chunk;
|
|
94827
|
+
}
|
|
94828
|
+
} finally {
|
|
94829
|
+
limiter.release(null);
|
|
94830
|
+
if (debug) {
|
|
94831
|
+
const stats = limiter.getStats();
|
|
94832
|
+
console.log(`[DEBUG] Released global AI concurrency slot (${stats.globalActive}/${stats.maxConcurrent}, queue: ${stats.queueSize})`);
|
|
94833
|
+
}
|
|
94834
|
+
}
|
|
94835
|
+
})();
|
|
94836
|
+
} else if (limiter) {
|
|
94837
|
+
limiter.release(null);
|
|
94838
|
+
}
|
|
94839
|
+
return result;
|
|
94840
|
+
} catch (error2) {
|
|
94841
|
+
if (limiter) {
|
|
94842
|
+
limiter.release(null);
|
|
94843
|
+
if (this.debug) {
|
|
94844
|
+
console.log(`[DEBUG] Released global AI concurrency slot on error`);
|
|
94845
|
+
}
|
|
94846
|
+
}
|
|
94847
|
+
throw error2;
|
|
94772
94848
|
} finally {
|
|
94773
94849
|
if (timeoutState.timeoutId) {
|
|
94774
94850
|
clearTimeout(timeoutState.timeoutId);
|
|
@@ -95660,10 +95736,8 @@ Workspace: ${this.allowedFolders.join(", ")}`;
|
|
|
95660
95736
|
toolDefinitions += `${taskToolDefinition}
|
|
95661
95737
|
`;
|
|
95662
95738
|
}
|
|
95663
|
-
|
|
95664
|
-
toolDefinitions += `${attemptCompletionToolDefinition}
|
|
95739
|
+
toolDefinitions += `${attemptCompletionToolDefinition}
|
|
95665
95740
|
`;
|
|
95666
|
-
}
|
|
95667
95741
|
if (this.enableDelegate && isToolAllowed("delegate")) {
|
|
95668
95742
|
toolDefinitions += `${delegateToolDefinition}
|
|
95669
95743
|
`;
|
|
@@ -97604,8 +97678,10 @@ async function delegate({
|
|
|
97604
97678
|
enableMcp = false,
|
|
97605
97679
|
mcpConfig = null,
|
|
97606
97680
|
mcpConfigPath = null,
|
|
97607
|
-
delegationManager = null
|
|
97681
|
+
delegationManager = null,
|
|
97608
97682
|
// Optional per-instance manager, falls back to default singleton
|
|
97683
|
+
concurrencyLimiter = null
|
|
97684
|
+
// Optional global AI concurrency limiter
|
|
97609
97685
|
}) {
|
|
97610
97686
|
if (!task || typeof task !== "string") {
|
|
97611
97687
|
throw new Error("Task parameter is required and must be a string");
|
|
@@ -97681,8 +97757,10 @@ async function delegate({
|
|
|
97681
97757
|
// Inherit from parent (subagent creates own MCPXmlBridge)
|
|
97682
97758
|
mcpConfig,
|
|
97683
97759
|
// Inherit from parent
|
|
97684
|
-
mcpConfigPath
|
|
97760
|
+
mcpConfigPath,
|
|
97685
97761
|
// Inherit from parent
|
|
97762
|
+
concurrencyLimiter
|
|
97763
|
+
// Inherit global AI concurrency limiter
|
|
97686
97764
|
});
|
|
97687
97765
|
if (debug) {
|
|
97688
97766
|
console.error(`[DELEGATE] Created subagent with session ${sessionId}`);
|
|
@@ -97780,10 +97858,10 @@ var init_delegate = __esm({
|
|
|
97780
97858
|
import_crypto9 = require("crypto");
|
|
97781
97859
|
init_ProbeAgent();
|
|
97782
97860
|
DelegationManager = class {
|
|
97783
|
-
constructor() {
|
|
97784
|
-
this.maxConcurrent = parseInt(process.env.MAX_CONCURRENT_DELEGATIONS || "3", 10);
|
|
97785
|
-
this.maxPerSession = parseInt(process.env.MAX_DELEGATIONS_PER_SESSION || "10", 10);
|
|
97786
|
-
this.defaultQueueTimeout = parseInt(process.env.DELEGATION_QUEUE_TIMEOUT || "60000", 10);
|
|
97861
|
+
constructor(options = {}) {
|
|
97862
|
+
this.maxConcurrent = options.maxConcurrent ?? parseInt(process.env.MAX_CONCURRENT_DELEGATIONS || "3", 10);
|
|
97863
|
+
this.maxPerSession = options.maxPerSession ?? parseInt(process.env.MAX_DELEGATIONS_PER_SESSION || "10", 10);
|
|
97864
|
+
this.defaultQueueTimeout = options.queueTimeout ?? parseInt(process.env.DELEGATION_QUEUE_TIMEOUT || "60000", 10);
|
|
97787
97865
|
this.sessionDelegations = /* @__PURE__ */ new Map();
|
|
97788
97866
|
this.globalActive = 0;
|
|
97789
97867
|
this.waitQueue = [];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@probelabs/probe",
|
|
3
|
-
"version": "0.6.0-
|
|
3
|
+
"version": "0.6.0-rc232",
|
|
4
4
|
"description": "Node.js wrapper for the probe code search tool",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"module": "src/index.js",
|
|
@@ -79,7 +79,7 @@
|
|
|
79
79
|
"@ai-sdk/openai": "^2.0.10",
|
|
80
80
|
"@anthropic-ai/claude-agent-sdk": "^0.1.46",
|
|
81
81
|
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
82
|
-
"@probelabs/maid": "^0.0.
|
|
82
|
+
"@probelabs/maid": "^0.0.24",
|
|
83
83
|
"adm-zip": "^0.5.16",
|
|
84
84
|
"ai": "^5.0.0",
|
|
85
85
|
"ajv": "^8.17.1",
|
package/src/agent/ProbeAgent.js
CHANGED
|
@@ -357,6 +357,10 @@ export class ProbeAgent {
|
|
|
357
357
|
// Each ProbeAgent instance has its own limits, not shared globally
|
|
358
358
|
this.delegationManager = new DelegationManager();
|
|
359
359
|
|
|
360
|
+
// Optional global concurrency limiter shared across all ProbeAgent instances.
|
|
361
|
+
// When set, every AI API call acquires a slot before calling the provider.
|
|
362
|
+
this.concurrencyLimiter = options.concurrencyLimiter || null;
|
|
363
|
+
|
|
360
364
|
// Request timeout configuration (default 2 minutes)
|
|
361
365
|
// Validates env var to prevent NaN or unreasonable values
|
|
362
366
|
this.requestTimeout = options.requestTimeout ?? (() => {
|
|
@@ -824,6 +828,7 @@ export class ProbeAgent {
|
|
|
824
828
|
provider: this.clientApiProvider,
|
|
825
829
|
model: this.clientApiModel,
|
|
826
830
|
delegationManager: this.delegationManager, // Per-instance delegation limits
|
|
831
|
+
concurrencyLimiter: this.concurrencyLimiter, // Global AI concurrency limiter
|
|
827
832
|
isToolAllowed
|
|
828
833
|
};
|
|
829
834
|
|
|
@@ -1363,6 +1368,16 @@ export class ProbeAgent {
|
|
|
1363
1368
|
* @private
|
|
1364
1369
|
*/
|
|
1365
1370
|
async streamTextWithRetryAndFallback(options) {
|
|
1371
|
+
// Acquire global concurrency slot if limiter is configured
|
|
1372
|
+
const limiter = this.concurrencyLimiter;
|
|
1373
|
+
if (limiter) {
|
|
1374
|
+
await limiter.acquire(null);
|
|
1375
|
+
if (this.debug) {
|
|
1376
|
+
const stats = limiter.getStats();
|
|
1377
|
+
console.log(`[DEBUG] Acquired global AI concurrency slot (${stats.globalActive}/${stats.maxConcurrent}, queue: ${stats.queueSize})`);
|
|
1378
|
+
}
|
|
1379
|
+
}
|
|
1380
|
+
|
|
1366
1381
|
// Create AbortController for overall operation timeout
|
|
1367
1382
|
const controller = new AbortController();
|
|
1368
1383
|
const timeoutState = { timeoutId: null };
|
|
@@ -1382,12 +1397,10 @@ export class ProbeAgent {
|
|
|
1382
1397
|
const useClaudeCode = this.clientApiProvider === 'claude-code' || process.env.USE_CLAUDE_CODE === 'true';
|
|
1383
1398
|
const useCodex = this.clientApiProvider === 'codex' || process.env.USE_CODEX === 'true';
|
|
1384
1399
|
|
|
1400
|
+
let result;
|
|
1385
1401
|
if (useClaudeCode || useCodex) {
|
|
1386
1402
|
try {
|
|
1387
|
-
|
|
1388
|
-
if (result) {
|
|
1389
|
-
return result;
|
|
1390
|
-
}
|
|
1403
|
+
result = await this._tryEngineStreamPath(options, controller, timeoutState);
|
|
1391
1404
|
} catch (error) {
|
|
1392
1405
|
if (this.debug) {
|
|
1393
1406
|
const engineType = useClaudeCode ? 'Claude Code' : 'Codex';
|
|
@@ -1397,8 +1410,43 @@ export class ProbeAgent {
|
|
|
1397
1410
|
}
|
|
1398
1411
|
}
|
|
1399
1412
|
|
|
1400
|
-
|
|
1401
|
-
|
|
1413
|
+
if (!result) {
|
|
1414
|
+
// Use Vercel AI SDK with retry/fallback
|
|
1415
|
+
result = await this._executeWithVercelProvider(options, controller);
|
|
1416
|
+
}
|
|
1417
|
+
|
|
1418
|
+
// Wrap textStream so limiter slot is held until stream completes
|
|
1419
|
+
if (limiter && result.textStream) {
|
|
1420
|
+
const originalStream = result.textStream;
|
|
1421
|
+
const debug = this.debug;
|
|
1422
|
+
result.textStream = (async function* () {
|
|
1423
|
+
try {
|
|
1424
|
+
for await (const chunk of originalStream) {
|
|
1425
|
+
yield chunk;
|
|
1426
|
+
}
|
|
1427
|
+
} finally {
|
|
1428
|
+
limiter.release(null);
|
|
1429
|
+
if (debug) {
|
|
1430
|
+
const stats = limiter.getStats();
|
|
1431
|
+
console.log(`[DEBUG] Released global AI concurrency slot (${stats.globalActive}/${stats.maxConcurrent}, queue: ${stats.queueSize})`);
|
|
1432
|
+
}
|
|
1433
|
+
}
|
|
1434
|
+
})();
|
|
1435
|
+
} else if (limiter) {
|
|
1436
|
+
// No textStream (shouldn't happen, but release just in case)
|
|
1437
|
+
limiter.release(null);
|
|
1438
|
+
}
|
|
1439
|
+
|
|
1440
|
+
return result;
|
|
1441
|
+
} catch (error) {
|
|
1442
|
+
// Release on error if limiter was acquired
|
|
1443
|
+
if (limiter) {
|
|
1444
|
+
limiter.release(null);
|
|
1445
|
+
if (this.debug) {
|
|
1446
|
+
console.log(`[DEBUG] Released global AI concurrency slot on error`);
|
|
1447
|
+
}
|
|
1448
|
+
}
|
|
1449
|
+
throw error;
|
|
1402
1450
|
} finally {
|
|
1403
1451
|
// Clean up timeout (for non-engine paths; engine paths clean up in the generator)
|
|
1404
1452
|
if (timeoutState.timeoutId) {
|
|
@@ -2496,10 +2544,9 @@ ${extractGuidance}
|
|
|
2496
2544
|
toolDefinitions += `${taskToolDefinition}\n`;
|
|
2497
2545
|
}
|
|
2498
2546
|
|
|
2499
|
-
// Always include attempt_completion
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
}
|
|
2547
|
+
// Always include attempt_completion unconditionally - it's a completion signal, not a tool
|
|
2548
|
+
// This ensures agents can always complete their work, regardless of tool restrictions
|
|
2549
|
+
toolDefinitions += `${attemptCompletionToolDefinition}\n`;
|
|
2503
2550
|
|
|
2504
2551
|
// Delegate tool (require both enableDelegate flag AND allowedTools permission)
|
|
2505
2552
|
// Place after attempt_completion as it's an optional tool
|
|
@@ -3304,8 +3351,9 @@ Follow these instructions carefully:
|
|
|
3304
3351
|
if (this.enableSkills && this.allowedTools.isEnabled('listSkills')) validTools.push('listSkills');
|
|
3305
3352
|
if (this.enableSkills && this.allowedTools.isEnabled('useSkill')) validTools.push('useSkill');
|
|
3306
3353
|
if (this.allowedTools.isEnabled('readImage')) validTools.push('readImage');
|
|
3307
|
-
// Always allow attempt_completion - it's a completion signal, not a tool
|
|
3354
|
+
// Always allow attempt_completion in validTools - it's a completion signal, not a tool
|
|
3308
3355
|
// This ensures agents can complete even when disableTools: true is set (fixes #333)
|
|
3356
|
+
// The tool DEFINITION may be hidden in raw AI mode, but we still need to recognize it
|
|
3309
3357
|
validTools.push('attempt_completion');
|
|
3310
3358
|
|
|
3311
3359
|
// Edit tools (require both allowEdit flag AND allowedTools permission)
|
|
@@ -23,7 +23,8 @@ export const taskItemSchema = z.object({
|
|
|
23
23
|
*/
|
|
24
24
|
export const taskSchema = z.object({
|
|
25
25
|
action: z.enum(['create', 'update', 'complete', 'delete', 'list']),
|
|
26
|
-
|
|
26
|
+
// Accept both array and JSON string (AI models sometimes serialize as string)
|
|
27
|
+
tasks: z.union([z.array(z.union([z.string(), taskItemSchema])), z.string()]).optional(),
|
|
27
28
|
id: z.string().optional(),
|
|
28
29
|
title: z.string().optional(),
|
|
29
30
|
description: z.string().optional(),
|
|
@@ -142,6 +143,25 @@ SKIP TASKS for single-goal requests, even if they require multiple searches:
|
|
|
142
143
|
**Key insight**: Multiple *internal steps* (search, read, analyze) are NOT the same as multiple *goals*.
|
|
143
144
|
A single investigation with many steps is still ONE task, not many.
|
|
144
145
|
|
|
146
|
+
## Task Granularity
|
|
147
|
+
|
|
148
|
+
Tasks represent LOGICAL UNITS OF WORK, not individual files or steps:
|
|
149
|
+
- "Fix 8 similar test files" → ONE task (same type of fix across files)
|
|
150
|
+
- "Update API + tests + docs" → THREE tasks (different types of work)
|
|
151
|
+
- "Implement feature in 5 files" → ONE task (single feature)
|
|
152
|
+
|
|
153
|
+
**Rule of thumb**: If you're creating more than 3-4 tasks, you're probably too granular.
|
|
154
|
+
|
|
155
|
+
**Anti-patterns to avoid**:
|
|
156
|
+
- One task per file ❌
|
|
157
|
+
- One task per function ❌
|
|
158
|
+
- One task per repository (when same type of work) ❌
|
|
159
|
+
|
|
160
|
+
**Good patterns**:
|
|
161
|
+
- One task per distinct deliverable ✓
|
|
162
|
+
- One task per phase (implement, test, document) ✓
|
|
163
|
+
- One task per different type of work ✓
|
|
164
|
+
|
|
145
165
|
MODIFY TASKS when (during execution):
|
|
146
166
|
- You discover the problem is more complex than expected → Add new tasks
|
|
147
167
|
- A single task covers too much scope → Split into smaller tasks
|
|
@@ -314,7 +334,17 @@ export function createTaskTool(options = {}) {
|
|
|
314
334
|
return `Error: Invalid task parameters - ${validation.error.message}`;
|
|
315
335
|
}
|
|
316
336
|
|
|
317
|
-
const { action, tasks, id, title, description, status, priority, dependencies, after } = validation.data;
|
|
337
|
+
const { action, tasks: rawTasks, id, title, description, status, priority, dependencies, after } = validation.data;
|
|
338
|
+
|
|
339
|
+
// Parse tasks if passed as JSON string (common AI model behavior)
|
|
340
|
+
let tasks = rawTasks;
|
|
341
|
+
if (typeof rawTasks === 'string') {
|
|
342
|
+
try {
|
|
343
|
+
tasks = JSON.parse(rawTasks);
|
|
344
|
+
} catch (e) {
|
|
345
|
+
return `Error: Invalid tasks JSON - ${e.message}`;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
318
348
|
|
|
319
349
|
switch (action) {
|
|
320
350
|
case 'create': {
|
package/src/delegate.js
CHANGED
|
@@ -19,11 +19,14 @@ import { ProbeAgent } from './agent/ProbeAgent.js';
|
|
|
19
19
|
* - For long-running processes, periodic cleanup of stale sessions may be needed
|
|
20
20
|
*/
|
|
21
21
|
class DelegationManager {
|
|
22
|
-
constructor() {
|
|
23
|
-
this.maxConcurrent =
|
|
24
|
-
|
|
22
|
+
constructor(options = {}) {
|
|
23
|
+
this.maxConcurrent = options.maxConcurrent
|
|
24
|
+
?? parseInt(process.env.MAX_CONCURRENT_DELEGATIONS || '3', 10);
|
|
25
|
+
this.maxPerSession = options.maxPerSession
|
|
26
|
+
?? parseInt(process.env.MAX_DELEGATIONS_PER_SESSION || '10', 10);
|
|
25
27
|
// Default queue timeout: 60 seconds. Set DELEGATION_QUEUE_TIMEOUT=0 to disable.
|
|
26
|
-
this.defaultQueueTimeout =
|
|
28
|
+
this.defaultQueueTimeout = options.queueTimeout
|
|
29
|
+
?? parseInt(process.env.DELEGATION_QUEUE_TIMEOUT || '60000', 10);
|
|
27
30
|
|
|
28
31
|
// Track delegations per session with timestamp for potential TTL cleanup
|
|
29
32
|
// Map<string, { count: number, lastUpdated: number }>
|
|
@@ -353,6 +356,7 @@ const DEFAULT_DELEGATE_TIMEOUT = parseInt(process.env.DELEGATE_TIMEOUT, 10) || 3
|
|
|
353
356
|
* @param {boolean} [options.enableMcp=false] - Enable MCP tool integration (inherited from parent)
|
|
354
357
|
* @param {Object} [options.mcpConfig] - MCP configuration object (inherited from parent)
|
|
355
358
|
* @param {string} [options.mcpConfigPath] - Path to MCP configuration file (inherited from parent)
|
|
359
|
+
* @param {Object} [options.concurrencyLimiter=null] - Global AI concurrency limiter (DelegationManager instance)
|
|
356
360
|
* @returns {Promise<string>} The response from the delegate agent
|
|
357
361
|
*/
|
|
358
362
|
export async function delegate({
|
|
@@ -379,7 +383,8 @@ export async function delegate({
|
|
|
379
383
|
enableMcp = false,
|
|
380
384
|
mcpConfig = null,
|
|
381
385
|
mcpConfigPath = null,
|
|
382
|
-
delegationManager = null // Optional per-instance manager, falls back to default singleton
|
|
386
|
+
delegationManager = null, // Optional per-instance manager, falls back to default singleton
|
|
387
|
+
concurrencyLimiter = null // Optional global AI concurrency limiter
|
|
383
388
|
}) {
|
|
384
389
|
if (!task || typeof task !== 'string') {
|
|
385
390
|
throw new Error('Task parameter is required and must be a string');
|
|
@@ -464,7 +469,8 @@ export async function delegate({
|
|
|
464
469
|
enableTasks, // Inherit from parent (subagent gets isolated TaskManager)
|
|
465
470
|
enableMcp, // Inherit from parent (subagent creates own MCPXmlBridge)
|
|
466
471
|
mcpConfig, // Inherit from parent
|
|
467
|
-
mcpConfigPath // Inherit from parent
|
|
472
|
+
mcpConfigPath, // Inherit from parent
|
|
473
|
+
concurrencyLimiter // Inherit global AI concurrency limiter
|
|
468
474
|
});
|
|
469
475
|
|
|
470
476
|
if (debug) {
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|