@probelabs/probe 0.6.0-rc304 → 0.6.0-rc306

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.
@@ -882,6 +882,18 @@ export class ProbeAgent {
882
882
  outputBuffer: this._outputBuffer,
883
883
  concurrencyLimiter: this.concurrencyLimiter, // Global AI concurrency limiter
884
884
  isToolAllowed,
885
+ // MCP config for delegate subagents — these are set in constructor before tools init,
886
+ // so they're available here. The delegate tool closure needs them to pass to subagents
887
+ // so they can create their own MCPXmlBridge instances.
888
+ enableMcp: this.enableMcp,
889
+ mcpConfig: this.mcpConfig,
890
+ mcpConfigPath: this.mcpConfigPath,
891
+ // Pass parent's prompt settings so delegate subagents inherit the same persona/capabilities.
892
+ // Without promptType, delegate() defaulted to 'code-researcher' which doesn't exist,
893
+ // causing fallback to the read-only 'code-explorer' prompt.
894
+ promptType: this.promptType,
895
+ customPrompt: this.customPrompt,
896
+ completionPrompt: this.completionPrompt,
885
897
  // Lazy MCP getters — MCP is initialized after tools are created, so we use
886
898
  // getter functions that resolve at call-time to get the current MCP state
887
899
  getMcpBridge: () => this.mcpBridge,
@@ -63,7 +63,11 @@ export class TaskManager {
63
63
  * @throws {Error} If dependencies are invalid or create a cycle
64
64
  */
65
65
  createTask(taskData) {
66
- const id = this._generateId();
66
+ const id = taskData.id || this._generateId();
67
+ // If a user-provided ID collides with an existing task, throw
68
+ if (taskData.id && this.tasks.has(taskData.id)) {
69
+ throw new Error(`Task ID "${taskData.id}" already exists. Choose a different ID. Available tasks: ${this._getAvailableTaskIds()}`);
70
+ }
67
71
  const now = this._now();
68
72
 
69
73
  // Validate dependencies exist
@@ -148,46 +152,42 @@ export class TaskManager {
148
152
  }
149
153
  }
150
154
 
151
- // Build a mapping of user-provided IDs to future auto-generated IDs
152
- const idMap = new Map();
153
- const batchAutoIds = new Set();
154
- let nextCounter = this.taskCounter;
155
+ // Collect all IDs that will exist after this batch (for dependency validation)
156
+ const batchIds = new Set();
155
157
  for (const taskData of tasksData) {
156
- nextCounter++;
157
- const autoId = `task-${nextCounter}`;
158
- batchAutoIds.add(autoId);
159
158
  if (taskData.id) {
160
- idMap.set(taskData.id, autoId);
159
+ if (this.tasks.has(taskData.id)) {
160
+ throw new Error(`Task ID "${taskData.id}" already exists. Choose a different ID. Available tasks: ${this._getAvailableTaskIds()}`);
161
+ }
162
+ if (batchIds.has(taskData.id)) {
163
+ throw new Error(`Duplicate task ID "${taskData.id}" in batch. Each task must have a unique ID.`);
164
+ }
165
+ batchIds.add(taskData.id);
161
166
  }
162
167
  }
163
168
 
164
- // Resolve dependencies and "after" references, then validate before creating anything
169
+ // Validate dependencies and "after" references before creating anything
165
170
  const resolvedTasksData = tasksData.map(taskData => {
166
171
  const resolved = { ...taskData };
167
- delete resolved.id; // Don't pass user ID to createTask
168
172
 
169
173
  if (resolved.dependencies) {
170
174
  resolved.dependencies = resolved.dependencies.map(depId => {
171
- if (idMap.has(depId)) return idMap.get(depId);
172
- // Check if it's already an existing task ID or a batch auto-generated ID
173
- if (this.tasks.has(depId) || batchAutoIds.has(depId)) return depId;
174
- const batchIds = idMap.size > 0 ? Array.from(idMap.keys()).join(', ') : '(none provided)';
175
- throw new Error(`Dependency "${depId}" does not exist. Each task in the batch must have an "id" field, and dependencies must reference those IDs. Current batch IDs: ${batchIds}. Existing tasks: ${this._getAvailableTaskIds()}`);
175
+ if (batchIds.has(depId) || this.tasks.has(depId)) return depId;
176
+ const knownIds = batchIds.size > 0 ? Array.from(batchIds).join(', ') : '(none provided)';
177
+ throw new Error(`Dependency "${depId}" does not exist. Each task in the batch must have an "id" field, and dependencies must reference those IDs. Current batch IDs: ${knownIds}. Existing tasks: ${this._getAvailableTaskIds()}`);
176
178
  });
177
179
  }
178
180
 
179
181
  if (resolved.after) {
180
- if (idMap.has(resolved.after)) {
181
- resolved.after = idMap.get(resolved.after);
182
- } else if (!this.tasks.has(resolved.after) && !batchAutoIds.has(resolved.after)) {
183
- throw new Error(`Task "${resolved.after}" does not exist. Cannot insert after non-existent task. Available tasks: ${this._getAvailableTaskIds()}${idMap.size > 0 ? `, batch IDs: ${Array.from(idMap.keys()).join(', ')}` : ''}`);
182
+ if (!batchIds.has(resolved.after) && !this.tasks.has(resolved.after)) {
183
+ throw new Error(`Task "${resolved.after}" does not exist. Cannot insert after non-existent task. Available tasks: ${this._getAvailableTaskIds()}${batchIds.size > 0 ? `, batch IDs: ${Array.from(batchIds).join(', ')}` : ''}`);
184
184
  }
185
185
  }
186
186
 
187
187
  return resolved;
188
188
  });
189
189
 
190
- // All validation passed — create tasks
190
+ // All validation passed — create tasks (user-provided IDs are preserved by createTask)
191
191
  const createdTasks = [];
192
192
  for (const taskData of resolvedTasksData) {
193
193
  const task = this.createTask(taskData);
package/build/delegate.js CHANGED
@@ -387,7 +387,9 @@ export async function delegate({
387
387
  bashConfig = null,
388
388
  allowEdit = false,
389
389
  architectureFileName = null,
390
- promptType = 'code-researcher',
390
+ promptType = undefined,
391
+ customPrompt = null,
392
+ completionPrompt = null,
391
393
  allowedTools = null,
392
394
  disableTools = false,
393
395
  searchDelegate = undefined,
@@ -474,7 +476,9 @@ export async function delegate({
474
476
  // Tasks do not propagate back to the parent - each subagent has its own scope.
475
477
  const subagent = new ProbeAgent({
476
478
  sessionId,
477
- promptType, // Clean prompt, not inherited from parent
479
+ promptType, // Inherit from parent (or use parent's default)
480
+ customPrompt, // Inherit custom system prompt from parent
481
+ completionPrompt, // Inherit completion prompt from parent
478
482
  enableDelegate: false, // Explicitly disable delegation to prevent recursion
479
483
  disableMermaidValidation: true, // Faster processing
480
484
  disableJsonValidation: true, // Simpler responses
@@ -1135,7 +1135,7 @@ export const extractTool = (options = {}) => {
1135
1135
  * @returns {Object} Configured delegate tool
1136
1136
  */
1137
1137
  export const delegateTool = (options = {}) => {
1138
- const { debug = false, timeout = 300, cwd, allowedFolders, workspaceRoot, enableBash = false, bashConfig, allowEdit = false, architectureFileName, enableMcp = false, mcpConfig = null, mcpConfigPath = null, delegationManager = null,
1138
+ const { debug = false, timeout = 300, cwd, allowedFolders, workspaceRoot, enableBash = false, bashConfig, allowEdit = false, architectureFileName, enableMcp = false, mcpConfig = null, mcpConfigPath = null, promptType: parentPromptType, customPrompt: parentCustomPrompt = null, completionPrompt: parentCompletionPrompt = null, delegationManager = null,
1139
1139
  // Timeout settings inherited from parent agent
1140
1140
  timeoutBehavior, maxOperationTimeout, requestTimeout, gracefulTimeoutBonusSteps,
1141
1141
  negotiatedTimeoutBudget, negotiatedTimeoutMaxRequests, negotiatedTimeoutMaxPerRequest,
@@ -1257,6 +1257,9 @@ export const delegateTool = (options = {}) => {
1257
1257
  allowEdit,
1258
1258
  bashConfig,
1259
1259
  architectureFileName,
1260
+ promptType: parentPromptType, // Inherit parent's prompt type
1261
+ customPrompt: parentCustomPrompt, // Inherit parent's custom system prompt
1262
+ completionPrompt: parentCompletionPrompt, // Inherit parent's completion prompt
1260
1263
  searchDelegate,
1261
1264
  enableMcp,
1262
1265
  mcpConfig,
@@ -26757,7 +26757,9 @@ async function delegate({
26757
26757
  bashConfig = null,
26758
26758
  allowEdit = false,
26759
26759
  architectureFileName = null,
26760
- promptType = "code-researcher",
26760
+ promptType = void 0,
26761
+ customPrompt = null,
26762
+ completionPrompt = null,
26761
26763
  allowedTools = null,
26762
26764
  disableTools = false,
26763
26765
  searchDelegate = void 0,
@@ -26823,7 +26825,11 @@ async function delegate({
26823
26825
  const subagent = new ProbeAgent({
26824
26826
  sessionId,
26825
26827
  promptType,
26826
- // Clean prompt, not inherited from parent
26828
+ // Inherit from parent (or use parent's default)
26829
+ customPrompt,
26830
+ // Inherit custom system prompt from parent
26831
+ completionPrompt,
26832
+ // Inherit completion prompt from parent
26827
26833
  enableDelegate: false,
26828
26834
  // Explicitly disable delegation to prevent recursion
26829
26835
  disableMermaidValidation: true,
@@ -28930,6 +28936,9 @@ Do NOT search for analogies or loosely related concepts. If the feature does not
28930
28936
  enableMcp = false,
28931
28937
  mcpConfig = null,
28932
28938
  mcpConfigPath = null,
28939
+ promptType: parentPromptType,
28940
+ customPrompt: parentCustomPrompt = null,
28941
+ completionPrompt: parentCompletionPrompt = null,
28933
28942
  delegationManager = null,
28934
28943
  // Timeout settings inherited from parent agent
28935
28944
  timeoutBehavior,
@@ -29023,6 +29032,12 @@ Do NOT search for analogies or loosely related concepts. If the feature does not
29023
29032
  allowEdit,
29024
29033
  bashConfig,
29025
29034
  architectureFileName,
29035
+ promptType: parentPromptType,
29036
+ // Inherit parent's prompt type
29037
+ customPrompt: parentCustomPrompt,
29038
+ // Inherit parent's custom system prompt
29039
+ completionPrompt: parentCompletionPrompt,
29040
+ // Inherit parent's completion prompt
29026
29041
  searchDelegate,
29027
29042
  enableMcp,
29028
29043
  mcpConfig,
@@ -50389,7 +50404,10 @@ var init_TaskManager = __esm({
50389
50404
  * @throws {Error} If dependencies are invalid or create a cycle
50390
50405
  */
50391
50406
  createTask(taskData) {
50392
- const id = this._generateId();
50407
+ const id = taskData.id || this._generateId();
50408
+ if (taskData.id && this.tasks.has(taskData.id)) {
50409
+ throw new Error(`Task ID "${taskData.id}" already exists. Choose a different ID. Available tasks: ${this._getAvailableTaskIds()}`);
50410
+ }
50393
50411
  const now = this._now();
50394
50412
  const dependencies = taskData.dependencies || [];
50395
50413
  for (const depId of dependencies) {
@@ -50457,33 +50475,30 @@ var init_TaskManager = __esm({
50457
50475
  }
50458
50476
  }
50459
50477
  }
50460
- const idMap = /* @__PURE__ */ new Map();
50461
- const batchAutoIds = /* @__PURE__ */ new Set();
50462
- let nextCounter = this.taskCounter;
50478
+ const batchIds = /* @__PURE__ */ new Set();
50463
50479
  for (const taskData of tasksData) {
50464
- nextCounter++;
50465
- const autoId = `task-${nextCounter}`;
50466
- batchAutoIds.add(autoId);
50467
50480
  if (taskData.id) {
50468
- idMap.set(taskData.id, autoId);
50481
+ if (this.tasks.has(taskData.id)) {
50482
+ throw new Error(`Task ID "${taskData.id}" already exists. Choose a different ID. Available tasks: ${this._getAvailableTaskIds()}`);
50483
+ }
50484
+ if (batchIds.has(taskData.id)) {
50485
+ throw new Error(`Duplicate task ID "${taskData.id}" in batch. Each task must have a unique ID.`);
50486
+ }
50487
+ batchIds.add(taskData.id);
50469
50488
  }
50470
50489
  }
50471
50490
  const resolvedTasksData = tasksData.map((taskData) => {
50472
50491
  const resolved = { ...taskData };
50473
- delete resolved.id;
50474
50492
  if (resolved.dependencies) {
50475
50493
  resolved.dependencies = resolved.dependencies.map((depId) => {
50476
- if (idMap.has(depId)) return idMap.get(depId);
50477
- if (this.tasks.has(depId) || batchAutoIds.has(depId)) return depId;
50478
- const batchIds = idMap.size > 0 ? Array.from(idMap.keys()).join(", ") : "(none provided)";
50479
- throw new Error(`Dependency "${depId}" does not exist. Each task in the batch must have an "id" field, and dependencies must reference those IDs. Current batch IDs: ${batchIds}. Existing tasks: ${this._getAvailableTaskIds()}`);
50494
+ if (batchIds.has(depId) || this.tasks.has(depId)) return depId;
50495
+ const knownIds = batchIds.size > 0 ? Array.from(batchIds).join(", ") : "(none provided)";
50496
+ throw new Error(`Dependency "${depId}" does not exist. Each task in the batch must have an "id" field, and dependencies must reference those IDs. Current batch IDs: ${knownIds}. Existing tasks: ${this._getAvailableTaskIds()}`);
50480
50497
  });
50481
50498
  }
50482
50499
  if (resolved.after) {
50483
- if (idMap.has(resolved.after)) {
50484
- resolved.after = idMap.get(resolved.after);
50485
- } else if (!this.tasks.has(resolved.after) && !batchAutoIds.has(resolved.after)) {
50486
- throw new Error(`Task "${resolved.after}" does not exist. Cannot insert after non-existent task. Available tasks: ${this._getAvailableTaskIds()}${idMap.size > 0 ? `, batch IDs: ${Array.from(idMap.keys()).join(", ")}` : ""}`);
50500
+ if (!batchIds.has(resolved.after) && !this.tasks.has(resolved.after)) {
50501
+ throw new Error(`Task "${resolved.after}" does not exist. Cannot insert after non-existent task. Available tasks: ${this._getAvailableTaskIds()}${batchIds.size > 0 ? `, batch IDs: ${Array.from(batchIds).join(", ")}` : ""}`);
50487
50502
  }
50488
50503
  }
50489
50504
  return resolved;
@@ -101512,6 +101527,18 @@ var init_ProbeAgent = __esm({
101512
101527
  concurrencyLimiter: this.concurrencyLimiter,
101513
101528
  // Global AI concurrency limiter
101514
101529
  isToolAllowed,
101530
+ // MCP config for delegate subagents — these are set in constructor before tools init,
101531
+ // so they're available here. The delegate tool closure needs them to pass to subagents
101532
+ // so they can create their own MCPXmlBridge instances.
101533
+ enableMcp: this.enableMcp,
101534
+ mcpConfig: this.mcpConfig,
101535
+ mcpConfigPath: this.mcpConfigPath,
101536
+ // Pass parent's prompt settings so delegate subagents inherit the same persona/capabilities.
101537
+ // Without promptType, delegate() defaulted to 'code-researcher' which doesn't exist,
101538
+ // causing fallback to the read-only 'code-explorer' prompt.
101539
+ promptType: this.promptType,
101540
+ customPrompt: this.customPrompt,
101541
+ completionPrompt: this.completionPrompt,
101515
101542
  // Lazy MCP getters — MCP is initialized after tools are created, so we use
101516
101543
  // getter functions that resolve at call-time to get the current MCP state
101517
101544
  getMcpBridge: () => this.mcpBridge,
package/cjs/index.cjs CHANGED
@@ -26847,7 +26847,10 @@ var init_TaskManager = __esm({
26847
26847
  * @throws {Error} If dependencies are invalid or create a cycle
26848
26848
  */
26849
26849
  createTask(taskData) {
26850
- const id = this._generateId();
26850
+ const id = taskData.id || this._generateId();
26851
+ if (taskData.id && this.tasks.has(taskData.id)) {
26852
+ throw new Error(`Task ID "${taskData.id}" already exists. Choose a different ID. Available tasks: ${this._getAvailableTaskIds()}`);
26853
+ }
26851
26854
  const now = this._now();
26852
26855
  const dependencies = taskData.dependencies || [];
26853
26856
  for (const depId of dependencies) {
@@ -26915,33 +26918,30 @@ var init_TaskManager = __esm({
26915
26918
  }
26916
26919
  }
26917
26920
  }
26918
- const idMap = /* @__PURE__ */ new Map();
26919
- const batchAutoIds = /* @__PURE__ */ new Set();
26920
- let nextCounter = this.taskCounter;
26921
+ const batchIds = /* @__PURE__ */ new Set();
26921
26922
  for (const taskData of tasksData) {
26922
- nextCounter++;
26923
- const autoId = `task-${nextCounter}`;
26924
- batchAutoIds.add(autoId);
26925
26923
  if (taskData.id) {
26926
- idMap.set(taskData.id, autoId);
26924
+ if (this.tasks.has(taskData.id)) {
26925
+ throw new Error(`Task ID "${taskData.id}" already exists. Choose a different ID. Available tasks: ${this._getAvailableTaskIds()}`);
26926
+ }
26927
+ if (batchIds.has(taskData.id)) {
26928
+ throw new Error(`Duplicate task ID "${taskData.id}" in batch. Each task must have a unique ID.`);
26929
+ }
26930
+ batchIds.add(taskData.id);
26927
26931
  }
26928
26932
  }
26929
26933
  const resolvedTasksData = tasksData.map((taskData) => {
26930
26934
  const resolved = { ...taskData };
26931
- delete resolved.id;
26932
26935
  if (resolved.dependencies) {
26933
26936
  resolved.dependencies = resolved.dependencies.map((depId) => {
26934
- if (idMap.has(depId)) return idMap.get(depId);
26935
- if (this.tasks.has(depId) || batchAutoIds.has(depId)) return depId;
26936
- const batchIds = idMap.size > 0 ? Array.from(idMap.keys()).join(", ") : "(none provided)";
26937
- throw new Error(`Dependency "${depId}" does not exist. Each task in the batch must have an "id" field, and dependencies must reference those IDs. Current batch IDs: ${batchIds}. Existing tasks: ${this._getAvailableTaskIds()}`);
26937
+ if (batchIds.has(depId) || this.tasks.has(depId)) return depId;
26938
+ const knownIds = batchIds.size > 0 ? Array.from(batchIds).join(", ") : "(none provided)";
26939
+ throw new Error(`Dependency "${depId}" does not exist. Each task in the batch must have an "id" field, and dependencies must reference those IDs. Current batch IDs: ${knownIds}. Existing tasks: ${this._getAvailableTaskIds()}`);
26938
26940
  });
26939
26941
  }
26940
26942
  if (resolved.after) {
26941
- if (idMap.has(resolved.after)) {
26942
- resolved.after = idMap.get(resolved.after);
26943
- } else if (!this.tasks.has(resolved.after) && !batchAutoIds.has(resolved.after)) {
26944
- throw new Error(`Task "${resolved.after}" does not exist. Cannot insert after non-existent task. Available tasks: ${this._getAvailableTaskIds()}${idMap.size > 0 ? `, batch IDs: ${Array.from(idMap.keys()).join(", ")}` : ""}`);
26943
+ if (!batchIds.has(resolved.after) && !this.tasks.has(resolved.after)) {
26944
+ throw new Error(`Task "${resolved.after}" does not exist. Cannot insert after non-existent task. Available tasks: ${this._getAvailableTaskIds()}${batchIds.size > 0 ? `, batch IDs: ${Array.from(batchIds).join(", ")}` : ""}`);
26945
26945
  }
26946
26946
  }
26947
26947
  return resolved;
@@ -98107,6 +98107,18 @@ var init_ProbeAgent = __esm({
98107
98107
  concurrencyLimiter: this.concurrencyLimiter,
98108
98108
  // Global AI concurrency limiter
98109
98109
  isToolAllowed,
98110
+ // MCP config for delegate subagents — these are set in constructor before tools init,
98111
+ // so they're available here. The delegate tool closure needs them to pass to subagents
98112
+ // so they can create their own MCPXmlBridge instances.
98113
+ enableMcp: this.enableMcp,
98114
+ mcpConfig: this.mcpConfig,
98115
+ mcpConfigPath: this.mcpConfigPath,
98116
+ // Pass parent's prompt settings so delegate subagents inherit the same persona/capabilities.
98117
+ // Without promptType, delegate() defaulted to 'code-researcher' which doesn't exist,
98118
+ // causing fallback to the read-only 'code-explorer' prompt.
98119
+ promptType: this.promptType,
98120
+ customPrompt: this.customPrompt,
98121
+ completionPrompt: this.completionPrompt,
98110
98122
  // Lazy MCP getters — MCP is initialized after tools are created, so we use
98111
98123
  // getter functions that resolve at call-time to get the current MCP state
98112
98124
  getMcpBridge: () => this.mcpBridge,
@@ -101912,7 +101924,9 @@ async function delegate({
101912
101924
  bashConfig = null,
101913
101925
  allowEdit = false,
101914
101926
  architectureFileName = null,
101915
- promptType = "code-researcher",
101927
+ promptType = void 0,
101928
+ customPrompt = null,
101929
+ completionPrompt = null,
101916
101930
  allowedTools = null,
101917
101931
  disableTools = false,
101918
101932
  searchDelegate = void 0,
@@ -101978,7 +101992,11 @@ async function delegate({
101978
101992
  const subagent = new ProbeAgent({
101979
101993
  sessionId,
101980
101994
  promptType,
101981
- // Clean prompt, not inherited from parent
101995
+ // Inherit from parent (or use parent's default)
101996
+ customPrompt,
101997
+ // Inherit custom system prompt from parent
101998
+ completionPrompt,
101999
+ // Inherit completion prompt from parent
101982
102000
  enableDelegate: false,
101983
102001
  // Explicitly disable delegation to prevent recursion
101984
102002
  disableMermaidValidation: true,
@@ -103849,6 +103867,9 @@ Do NOT search for analogies or loosely related concepts. If the feature does not
103849
103867
  enableMcp = false,
103850
103868
  mcpConfig = null,
103851
103869
  mcpConfigPath = null,
103870
+ promptType: parentPromptType,
103871
+ customPrompt: parentCustomPrompt = null,
103872
+ completionPrompt: parentCompletionPrompt = null,
103852
103873
  delegationManager = null,
103853
103874
  // Timeout settings inherited from parent agent
103854
103875
  timeoutBehavior,
@@ -103942,6 +103963,12 @@ Do NOT search for analogies or loosely related concepts. If the feature does not
103942
103963
  allowEdit,
103943
103964
  bashConfig,
103944
103965
  architectureFileName,
103966
+ promptType: parentPromptType,
103967
+ // Inherit parent's prompt type
103968
+ customPrompt: parentCustomPrompt,
103969
+ // Inherit parent's custom system prompt
103970
+ completionPrompt: parentCompletionPrompt,
103971
+ // Inherit parent's completion prompt
103945
103972
  searchDelegate,
103946
103973
  enableMcp,
103947
103974
  mcpConfig,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@probelabs/probe",
3
- "version": "0.6.0-rc304",
3
+ "version": "0.6.0-rc306",
4
4
  "description": "Node.js wrapper for the probe code search tool",
5
5
  "main": "src/index.js",
6
6
  "module": "src/index.js",
@@ -882,6 +882,18 @@ export class ProbeAgent {
882
882
  outputBuffer: this._outputBuffer,
883
883
  concurrencyLimiter: this.concurrencyLimiter, // Global AI concurrency limiter
884
884
  isToolAllowed,
885
+ // MCP config for delegate subagents — these are set in constructor before tools init,
886
+ // so they're available here. The delegate tool closure needs them to pass to subagents
887
+ // so they can create their own MCPXmlBridge instances.
888
+ enableMcp: this.enableMcp,
889
+ mcpConfig: this.mcpConfig,
890
+ mcpConfigPath: this.mcpConfigPath,
891
+ // Pass parent's prompt settings so delegate subagents inherit the same persona/capabilities.
892
+ // Without promptType, delegate() defaulted to 'code-researcher' which doesn't exist,
893
+ // causing fallback to the read-only 'code-explorer' prompt.
894
+ promptType: this.promptType,
895
+ customPrompt: this.customPrompt,
896
+ completionPrompt: this.completionPrompt,
885
897
  // Lazy MCP getters — MCP is initialized after tools are created, so we use
886
898
  // getter functions that resolve at call-time to get the current MCP state
887
899
  getMcpBridge: () => this.mcpBridge,
@@ -63,7 +63,11 @@ export class TaskManager {
63
63
  * @throws {Error} If dependencies are invalid or create a cycle
64
64
  */
65
65
  createTask(taskData) {
66
- const id = this._generateId();
66
+ const id = taskData.id || this._generateId();
67
+ // If a user-provided ID collides with an existing task, throw
68
+ if (taskData.id && this.tasks.has(taskData.id)) {
69
+ throw new Error(`Task ID "${taskData.id}" already exists. Choose a different ID. Available tasks: ${this._getAvailableTaskIds()}`);
70
+ }
67
71
  const now = this._now();
68
72
 
69
73
  // Validate dependencies exist
@@ -148,46 +152,42 @@ export class TaskManager {
148
152
  }
149
153
  }
150
154
 
151
- // Build a mapping of user-provided IDs to future auto-generated IDs
152
- const idMap = new Map();
153
- const batchAutoIds = new Set();
154
- let nextCounter = this.taskCounter;
155
+ // Collect all IDs that will exist after this batch (for dependency validation)
156
+ const batchIds = new Set();
155
157
  for (const taskData of tasksData) {
156
- nextCounter++;
157
- const autoId = `task-${nextCounter}`;
158
- batchAutoIds.add(autoId);
159
158
  if (taskData.id) {
160
- idMap.set(taskData.id, autoId);
159
+ if (this.tasks.has(taskData.id)) {
160
+ throw new Error(`Task ID "${taskData.id}" already exists. Choose a different ID. Available tasks: ${this._getAvailableTaskIds()}`);
161
+ }
162
+ if (batchIds.has(taskData.id)) {
163
+ throw new Error(`Duplicate task ID "${taskData.id}" in batch. Each task must have a unique ID.`);
164
+ }
165
+ batchIds.add(taskData.id);
161
166
  }
162
167
  }
163
168
 
164
- // Resolve dependencies and "after" references, then validate before creating anything
169
+ // Validate dependencies and "after" references before creating anything
165
170
  const resolvedTasksData = tasksData.map(taskData => {
166
171
  const resolved = { ...taskData };
167
- delete resolved.id; // Don't pass user ID to createTask
168
172
 
169
173
  if (resolved.dependencies) {
170
174
  resolved.dependencies = resolved.dependencies.map(depId => {
171
- if (idMap.has(depId)) return idMap.get(depId);
172
- // Check if it's already an existing task ID or a batch auto-generated ID
173
- if (this.tasks.has(depId) || batchAutoIds.has(depId)) return depId;
174
- const batchIds = idMap.size > 0 ? Array.from(idMap.keys()).join(', ') : '(none provided)';
175
- throw new Error(`Dependency "${depId}" does not exist. Each task in the batch must have an "id" field, and dependencies must reference those IDs. Current batch IDs: ${batchIds}. Existing tasks: ${this._getAvailableTaskIds()}`);
175
+ if (batchIds.has(depId) || this.tasks.has(depId)) return depId;
176
+ const knownIds = batchIds.size > 0 ? Array.from(batchIds).join(', ') : '(none provided)';
177
+ throw new Error(`Dependency "${depId}" does not exist. Each task in the batch must have an "id" field, and dependencies must reference those IDs. Current batch IDs: ${knownIds}. Existing tasks: ${this._getAvailableTaskIds()}`);
176
178
  });
177
179
  }
178
180
 
179
181
  if (resolved.after) {
180
- if (idMap.has(resolved.after)) {
181
- resolved.after = idMap.get(resolved.after);
182
- } else if (!this.tasks.has(resolved.after) && !batchAutoIds.has(resolved.after)) {
183
- throw new Error(`Task "${resolved.after}" does not exist. Cannot insert after non-existent task. Available tasks: ${this._getAvailableTaskIds()}${idMap.size > 0 ? `, batch IDs: ${Array.from(idMap.keys()).join(', ')}` : ''}`);
182
+ if (!batchIds.has(resolved.after) && !this.tasks.has(resolved.after)) {
183
+ throw new Error(`Task "${resolved.after}" does not exist. Cannot insert after non-existent task. Available tasks: ${this._getAvailableTaskIds()}${batchIds.size > 0 ? `, batch IDs: ${Array.from(batchIds).join(', ')}` : ''}`);
184
184
  }
185
185
  }
186
186
 
187
187
  return resolved;
188
188
  });
189
189
 
190
- // All validation passed — create tasks
190
+ // All validation passed — create tasks (user-provided IDs are preserved by createTask)
191
191
  const createdTasks = [];
192
192
  for (const taskData of resolvedTasksData) {
193
193
  const task = this.createTask(taskData);
package/src/delegate.js CHANGED
@@ -387,7 +387,9 @@ export async function delegate({
387
387
  bashConfig = null,
388
388
  allowEdit = false,
389
389
  architectureFileName = null,
390
- promptType = 'code-researcher',
390
+ promptType = undefined,
391
+ customPrompt = null,
392
+ completionPrompt = null,
391
393
  allowedTools = null,
392
394
  disableTools = false,
393
395
  searchDelegate = undefined,
@@ -474,7 +476,9 @@ export async function delegate({
474
476
  // Tasks do not propagate back to the parent - each subagent has its own scope.
475
477
  const subagent = new ProbeAgent({
476
478
  sessionId,
477
- promptType, // Clean prompt, not inherited from parent
479
+ promptType, // Inherit from parent (or use parent's default)
480
+ customPrompt, // Inherit custom system prompt from parent
481
+ completionPrompt, // Inherit completion prompt from parent
478
482
  enableDelegate: false, // Explicitly disable delegation to prevent recursion
479
483
  disableMermaidValidation: true, // Faster processing
480
484
  disableJsonValidation: true, // Simpler responses
@@ -1135,7 +1135,7 @@ export const extractTool = (options = {}) => {
1135
1135
  * @returns {Object} Configured delegate tool
1136
1136
  */
1137
1137
  export const delegateTool = (options = {}) => {
1138
- const { debug = false, timeout = 300, cwd, allowedFolders, workspaceRoot, enableBash = false, bashConfig, allowEdit = false, architectureFileName, enableMcp = false, mcpConfig = null, mcpConfigPath = null, delegationManager = null,
1138
+ const { debug = false, timeout = 300, cwd, allowedFolders, workspaceRoot, enableBash = false, bashConfig, allowEdit = false, architectureFileName, enableMcp = false, mcpConfig = null, mcpConfigPath = null, promptType: parentPromptType, customPrompt: parentCustomPrompt = null, completionPrompt: parentCompletionPrompt = null, delegationManager = null,
1139
1139
  // Timeout settings inherited from parent agent
1140
1140
  timeoutBehavior, maxOperationTimeout, requestTimeout, gracefulTimeoutBonusSteps,
1141
1141
  negotiatedTimeoutBudget, negotiatedTimeoutMaxRequests, negotiatedTimeoutMaxPerRequest,
@@ -1257,6 +1257,9 @@ export const delegateTool = (options = {}) => {
1257
1257
  allowEdit,
1258
1258
  bashConfig,
1259
1259
  architectureFileName,
1260
+ promptType: parentPromptType, // Inherit parent's prompt type
1261
+ customPrompt: parentCustomPrompt, // Inherit parent's custom system prompt
1262
+ completionPrompt: parentCompletionPrompt, // Inherit parent's completion prompt
1260
1263
  searchDelegate,
1261
1264
  enableMcp,
1262
1265
  mcpConfig,