@probelabs/probe 0.6.0-rc312 → 0.6.0-rc313

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.
@@ -350,6 +350,7 @@ export class ProbeAgent {
350
350
  // Task management configuration
351
351
  this.enableTasks = !!options.enableTasks;
352
352
  this.taskManager = null; // Initialized per-request in answer()
353
+ this.delegationTask = options.delegationTask || null; // Task description when this is a subagent
353
354
 
354
355
  // Per-instance delegation manager for concurrent delegation limits
355
356
  // Each ProbeAgent instance has its own limits, not shared globally
@@ -3350,14 +3351,19 @@ Follow these instructions carefully:
3350
3351
  this.toolImplementations.task = createTaskTool({
3351
3352
  taskManager: this.taskManager,
3352
3353
  tracer: this.tracer,
3353
- debug: this.debug
3354
+ debug: this.debug,
3355
+ delegationTask: this.delegationTask
3354
3356
  });
3355
3357
  }
3356
3358
 
3357
3359
  // Record telemetry for task initialization
3358
3360
  if (this.tracer && typeof this.tracer.recordTaskEvent === 'function') {
3359
3361
  this.tracer.recordTaskEvent('session_started', {
3360
- 'task.enabled': true
3362
+ 'task.enabled': true,
3363
+ 'agent.session_id': this.tracer?.sessionId ?? null,
3364
+ 'agent.parent_session_id': this.tracer?.parentSessionId ?? null,
3365
+ 'agent.root_session_id': this.tracer?.rootSessionId ?? null,
3366
+ 'agent.kind': this.tracer?.agentKind ?? 'main',
3361
3367
  });
3362
3368
  }
3363
3369
 
@@ -136,9 +136,28 @@ export class SimpleTelemetry {
136
136
  * Simple tracer for application-level tracing
137
137
  */
138
138
  export class SimpleAppTracer {
139
- constructor(telemetry, sessionId = null) {
139
+ constructor(telemetry, sessionId = null, options = {}) {
140
140
  this.telemetry = telemetry;
141
141
  this.sessionId = sessionId || this.generateSessionId();
142
+ this.parentSessionId = options.parentSessionId || null;
143
+ this.rootSessionId = options.rootSessionId || this.sessionId;
144
+ this.agentKind = options.agentKind || 'main';
145
+ }
146
+
147
+ /**
148
+ * Create a child tracer for a delegated subagent.
149
+ * Inherits the same telemetry backend but scopes events to the child session.
150
+ * @param {string} childSessionId - The subagent's session ID
151
+ * @param {Object} [options] - Additional options
152
+ * @param {string} [options.agentKind='delegate'] - Kind of child agent
153
+ * @returns {SimpleAppTracer} A new tracer scoped to the child session
154
+ */
155
+ createChildTracer(childSessionId, options = {}) {
156
+ return new SimpleAppTracer(this.telemetry, childSessionId, {
157
+ parentSessionId: this.sessionId,
158
+ rootSessionId: this.rootSessionId,
159
+ agentKind: options.agentKind || 'delegate',
160
+ });
142
161
  }
143
162
 
144
163
  generateSessionId() {
@@ -500,13 +500,28 @@ export class TaskManager {
500
500
 
501
501
  let line = ` <task id="${this._escapeXml(task.id)}" status="${this._escapeXml(task.status)}"`;
502
502
  if (task.priority) line += ` priority="${this._escapeXml(task.priority)}"`;
503
+ if (task.dependencies.length > 0) line += ` depends_on="${this._escapeXml(task.dependencies.join(','))}"`;
503
504
  if (blockers.length > 0) line += ` blocked_by="${this._escapeXml(blockers.join(','))}"`;
504
505
  line += `>${this._escapeXml(task.title)}</task>`;
505
506
 
506
507
  return line;
507
508
  });
508
509
 
509
- return `<task_status>\n${taskLines.join('\n')}\n</task_status>`;
510
+ // Add a brief status line so the AI can quickly assess progress
511
+ let completed = 0, inProgress = 0, pending = 0, cancelled = 0;
512
+ for (const t of tasks) {
513
+ if (t.status === 'completed') completed++;
514
+ else if (t.status === 'in_progress') inProgress++;
515
+ else if (t.status === 'pending') pending++;
516
+ else if (t.status === 'cancelled') cancelled++;
517
+ }
518
+ const statusLine = ` <!-- ${completed}/${tasks.length} completed` +
519
+ (inProgress > 0 ? `, ${inProgress} in progress` : '') +
520
+ (pending > 0 ? `, ${pending} pending` : '') +
521
+ (cancelled > 0 ? `, ${cancelled} cancelled` : '') +
522
+ ` -->`;
523
+
524
+ return `<task_status>\n${statusLine}\n${taskLines.join('\n')}\n</task_status>`;
510
525
  }
511
526
 
512
527
  /**
@@ -42,50 +42,150 @@ export const taskToolDefinition = '';
42
42
  /**
43
43
  * Task system prompt addition - guidance for AI on when and how to use tasks
44
44
  */
45
- export const taskSystemPrompt = `[Task Management]
45
+ export const taskSystemPrompt = `<task_management_system>
46
46
 
47
- Use the task tool to track progress on complex requests with multiple distinct goals.
47
+ <purpose>
48
+ Track progress on requests with multiple distinct goals using the task tool.
49
+ Tasks are visible to the user in real time — keeping them accurate is critical.
50
+ </purpose>
48
51
 
49
- ## When to Use Tasks
50
-
51
- CREATE tasks when the request has **multiple separate deliverables**:
52
+ <when_to_use_tasks>
53
+ CREATE tasks when the request has multiple separate deliverables:
52
54
  - "Fix bug A AND add feature B" → two tasks
53
55
  - "Investigate auth, payments, AND notifications" → three tasks
54
- - "Implement X, then add tests, then update docs" → three sequential tasks
56
+ - "Implement X, then add tests, then update docs" → three sequential tasks with dependencies
55
57
 
56
- SKIP tasks for single-goal requests, even complex ones:
57
- - "How does ranking work?" just investigate and answer
58
- - "Explain the authentication flow" just trace and explain
58
+ DO NOT create tasks for single-goal requests, even complex ones:
59
+ - "How does ranking work?" just investigate and answer
60
+ - "Explain the authentication flow" just trace and explain
59
61
  Multiple internal steps (search, read, analyze) for one goal ≠ multiple tasks.
60
62
 
61
- ## Granularity
62
-
63
- Tasks = logical units of work, not files or steps.
63
+ Granularity: tasks = logical units of work, not individual files or steps.
64
64
  - "Fix 8 similar test files" → ONE task (same fix repeated)
65
65
  - "Update API + tests + docs" → THREE tasks (different work types)
66
- - Max 3–4 tasks. More means you're too granular.
67
-
68
- ## Workflow
69
-
70
- 1. **Plan**: Call task tool with action="create" and a tasks array up front
71
- 2. **Execute**: Update status to "in_progress" / "completed" as you work. Add, split, or cancel tasks as you learn more.
72
- 3. **Finish**: All tasks must be "completed" or "cancelled" before providing your final answer.
73
-
74
- ## Rules
75
-
76
- - Dependencies are enforced: a task cannot start until its dependencies are completed
66
+ </when_to_use_tasks>
67
+
68
+ <dependency_chains>
69
+ Use dependencies to enforce ordering. A task CANNOT start until ALL its dependencies are completed.
70
+
71
+ <pattern name="sequential_chain" description="Tasks that must happen in strict order">
72
+ tasks: [
73
+ { id: "design", title: "Design the API schema" },
74
+ { id: "implement", title: "Implement endpoints", dependencies: ["design"] },
75
+ { id: "test", title: "Write integration tests", dependencies: ["implement"] }
76
+ ]
77
+ Result: design → implement → test. "implement" is blocked until "design" is completed.
78
+ </pattern>
79
+
80
+ <pattern name="fan_out" description="Multiple tasks unlock after one prerequisite">
81
+ tasks: [
82
+ { id: "setup", title: "Set up database" },
83
+ { id: "auth", title: "Add auth module", dependencies: ["setup"] },
84
+ { id: "api", title: "Add API routes", dependencies: ["setup"] }
85
+ ]
86
+ Result: both "auth" and "api" become ready once "setup" is completed.
87
+ </pattern>
88
+
89
+ <pattern name="fan_in" description="One task waits for multiple prerequisites">
90
+ tasks: [
91
+ { id: "auth", title: "Auth module" },
92
+ { id: "api", title: "API routes" },
93
+ { id: "e2e", title: "End-to-end tests", dependencies: ["auth", "api"] }
94
+ ]
95
+ Result: "e2e" is blocked until both "auth" and "api" are completed.
96
+ </pattern>
97
+ </dependency_chains>
98
+
99
+ <modifying_tasks_mid_work>
100
+ When new requirements emerge during execution, modify the task plan dynamically.
101
+
102
+ <technique name="add_subtask">
103
+ Add a new task after an existing one, with a dependency to enforce order:
104
+ action: "create", id: "fix-edge-case", title: "Handle null input edge case",
105
+ dependencies: ["implement"], after: "implement"
106
+ </technique>
107
+
108
+ <technique name="split_task">
109
+ If a task is bigger than expected, create subtasks with dependencies on it, then complete or cancel the original.
110
+ </technique>
111
+
112
+ <technique name="insert_into_chain">
113
+ Insert a new task between two existing tasks in a chain:
114
+ Step 1 — Create the new task depending on the predecessor:
115
+ action: "create", id: "review", title: "Code review", dependencies: ["implement"], after: "implement"
116
+ Step 2 — Update the successor to depend on the new task:
117
+ action: "update", id: "test", dependencies: ["review"]
118
+ Original: design → implement → test
119
+ Result: design → implement → review → test
120
+ </technique>
121
+ </modifying_tasks_mid_work>
122
+
123
+ <workflow_rules>
124
+ These rules are MANDATORY. Violating them produces incorrect progress tracking.
125
+
126
+ <rule id="1" name="plan_first">
127
+ Call task tool with action="create" and a tasks array BEFORE starting any work.
128
+ Use dependencies to express ordering constraints between tasks.
129
+ </rule>
130
+
131
+ <rule id="2" name="mark_in_progress">
132
+ Set the current task to status="in_progress" BEFORE you begin working on it.
133
+ Only one task should be in_progress at a time.
134
+ </rule>
135
+
136
+ <rule id="3" name="complete_immediately" priority="critical">
137
+ The MOMENT you finish a task's work, call the task tool with action="complete" for that task.
138
+ Do this in the SAME response — not in the next step, not at the end, not batched with other completions.
139
+ Every task must be completed the instant its work is verified done.
140
+ </rule>
141
+
142
+ <rule id="4" name="verify_before_completing">
143
+ Do NOT mark a task completed unless you have verified the result:
144
+ - Code compiles without errors
145
+ - Tests pass
146
+ - Output is correct and complete
147
+ "I wrote the code" is NOT sufficient — you must confirm it works.
148
+ </rule>
149
+
150
+ <rule id="5" name="respect_dependencies">
151
+ NEVER work on a task whose dependencies are not yet completed.
152
+ The task list shows blocked_by attributes — check them. If a task is blocked, complete its blockers first.
153
+ </rule>
154
+
155
+ <rule id="6" name="adapt_the_plan">
156
+ If you discover new work during execution, create new tasks with proper dependencies.
157
+ Do NOT silently do extra work without tracking it in the task list.
158
+ If a task needs subtasks, create them as new tasks depending on the current one.
159
+ </rule>
160
+
161
+ <rule id="7" name="finish_clean">
162
+ ALL tasks must be "completed" or "cancelled" before you provide your final answer.
163
+ You CANNOT finish with pending or in_progress tasks. The system will block you.
164
+ </rule>
165
+ </workflow_rules>
166
+
167
+ <system_enforcement>
168
+ - Dependencies are strictly enforced: blocked tasks cannot be started
77
169
  - Circular dependencies are rejected
78
- - Completion is blocked while tasks remain unresolved
170
+ - You will be blocked from finishing while tasks remain unresolved
171
+ - Task status is visible to the user in real time — stale status is misleading
172
+ </system_enforcement>
173
+
174
+ </task_management_system>
79
175
  `;
80
176
 
81
177
  /**
82
178
  * Task guidance to inject at start of request
83
179
  */
84
- export const taskGuidancePrompt = `Does this request have MULTIPLE DISTINCT GOALS?
180
+ export const taskGuidancePrompt = `<task_decision>
181
+ Does this request have MULTIPLE DISTINCT GOALS?
85
182
  - "Do A AND B AND C" (multiple goals) → Create tasks for each goal
183
+ - "Do X, then Y, then Z" (sequential goals) → Create tasks with dependencies: Y depends on X, Z depends on Y
86
184
  - "Investigate/explain/find X" (single goal) → Skip tasks, just answer directly
87
185
  Multiple internal steps for ONE goal = NO tasks needed.
88
- If creating tasks, use the task tool with action="create" first.`;
186
+ If creating tasks: call the task tool with action="create" and a tasks array FIRST, using dependencies for ordering.
187
+ CRITICAL: Complete each task IMMEDIATELY when its work is done — do not defer completions.
188
+ </task_decision>`;
89
189
 
90
190
  /**
91
191
  * Create task completion blocked message
@@ -93,40 +193,129 @@ If creating tasks, use the task tool with action="create" first.`;
93
193
  * @returns {string} Formatted message
94
194
  */
95
195
  export function createTaskCompletionBlockedMessage(taskSummary) {
96
- return `You cannot complete yet. The following tasks are still unresolved:
196
+ return `<task_completion_blocked>
197
+ You CANNOT provide a final answer yet. There are unresolved tasks:
97
198
 
98
199
  ${taskSummary}
99
200
 
100
- For each pending/in_progress task, either:
101
- - Complete it: call task tool with action="complete", id="task-X"
102
- - Cancel it: call task tool with action="update", id="task-X", status="cancelled"
201
+ <required_actions>
202
+ For EACH unresolved task, do ONE of the following RIGHT NOW:
203
+ - Work is DONE action="complete", id="<task-id>"
204
+ - Work is NOT needed → action="update", id="<task-id>", status="cancelled"
205
+ - Work is BLOCKED → complete its dependencies first, then return to it
206
+ - Work is NOT started → set to "in_progress" and do the work, then complete it
207
+ </required_actions>
208
+
209
+ You will continue to be blocked until every task is completed or cancelled.
210
+ </task_completion_blocked>`;
211
+ }
212
+
213
+ /**
214
+ * Monotonic event sequence counter for deterministic replay ordering.
215
+ * Shared across all task tool instances within the same process.
216
+ * Node.js is single-threaded so no race condition is possible.
217
+ * Resets at Number.MAX_SAFE_INTEGER to prevent overflow.
218
+ */
219
+ let _globalSequence = 0;
220
+
221
+ /** @internal Reset the global sequence counter (for testing only). */
222
+ export function _resetSequence() { _globalSequence = 0; }
103
223
 
104
- After all tasks are resolved, provide your final answer.`;
224
+ /**
225
+ * Safe JSON.stringify wrapper that returns '[]' on failure.
226
+ * @param {*} value - Value to serialize
227
+ * @returns {string} JSON string or fallback
228
+ */
229
+ function safeStringify(value) {
230
+ try {
231
+ return JSON.stringify(value);
232
+ } catch {
233
+ return '[]';
234
+ }
235
+ }
236
+
237
+ /**
238
+ * Serialize a task object into a flat telemetry-friendly payload.
239
+ * @param {Object} task - Task from TaskManager
240
+ * @param {number} index - Position in the task list (0-based)
241
+ * @returns {Object} Flat task payload
242
+ */
243
+ function serializeTask(task, index) {
244
+ return {
245
+ id: task.id,
246
+ title: task.title,
247
+ status: task.status,
248
+ priority: task.priority || null,
249
+ dependencies: task.dependencies || [],
250
+ after: null, // 'after' is an insertion hint, not stored on the task
251
+ order: index,
252
+ };
105
253
  }
106
254
 
107
255
  /**
108
256
  * Create task tool instance
109
257
  * @param {Object} options - Configuration options
110
258
  * @param {import('./TaskManager.js').TaskManager} options.taskManager - TaskManager instance
111
- * @param {Object} [options.tracer] - Optional tracer for telemetry
259
+ * @param {Object} [options.tracer] - Optional tracer for telemetry (SimpleAppTracer with session hierarchy)
112
260
  * @param {boolean} [options.debug=false] - Enable debug logging
261
+ * @param {string} [options.delegationTask] - Description of the delegated task (if this is a subagent)
113
262
  * @returns {Object} Tool instance with execute function
114
263
  */
115
264
  export function createTaskTool(options = {}) {
116
- const { taskManager, tracer, debug = false } = options;
265
+ const { taskManager, tracer, debug = false, delegationTask = null } = options;
117
266
 
118
267
  if (!taskManager) {
119
268
  throw new Error('TaskManager instance is required');
120
269
  }
121
270
 
122
271
  /**
123
- * Record task telemetry event
272
+ * Build the agent scope fields from the tracer's session hierarchy.
273
+ * These fields are included in every emitted task event so consumers
274
+ * can group events by agent/subagent without relying on span ancestry.
275
+ * @returns {Object} Agent scope attributes
276
+ */
277
+ const getAgentScope = () => {
278
+ if (!tracer) return {};
279
+ return {
280
+ 'agent.session_id': tracer.sessionId || null,
281
+ 'agent.parent_session_id': tracer.parentSessionId || null,
282
+ 'agent.root_session_id': tracer.rootSessionId || null,
283
+ 'agent.kind': tracer.agentKind || 'main',
284
+ ...(delegationTask ? { 'delegation.task': delegationTask } : {}),
285
+ };
286
+ };
287
+
288
+ /**
289
+ * Build global task-list context fields (total count, incomplete remaining).
290
+ * @param {Array} [allTasks] - Pre-fetched task list to avoid redundant calls
291
+ * @returns {Object}
292
+ */
293
+ const getListContext = (allTasks) => {
294
+ const all = allTasks || taskManager.listTasks();
295
+ const incompleteCount = all.filter(t => t.status !== 'completed' && t.status !== 'cancelled').length;
296
+ return {
297
+ 'task.total_count': all.length,
298
+ 'task.incomplete_remaining': incompleteCount,
299
+ };
300
+ };
301
+
302
+ /**
303
+ * Record task telemetry event with agent scope and monotonic sequence.
124
304
  * @param {string} eventType - Event type (created, updated, completed, deleted, listed, error)
125
305
  * @param {Object} data - Event data
126
306
  */
127
307
  const recordTaskEvent = (eventType, data = {}) => {
128
308
  if (tracer && typeof tracer.recordTaskEvent === 'function') {
129
- tracer.recordTaskEvent(eventType, data);
309
+ if (_globalSequence >= Number.MAX_SAFE_INTEGER) _globalSequence = 0;
310
+ try {
311
+ tracer.recordTaskEvent(eventType, {
312
+ 'task.sequence': ++_globalSequence,
313
+ ...getAgentScope(),
314
+ ...data
315
+ });
316
+ } catch (err) {
317
+ if (debug) console.error('[TaskTool] Failed to record telemetry event:', err);
318
+ }
130
319
  }
131
320
  };
132
321
 
@@ -167,25 +356,30 @@ export function createTaskTool(options = {}) {
167
356
  if (tasks && Array.isArray(tasks)) {
168
357
  // Batch create
169
358
  const created = taskManager.createTasks(tasks);
170
- const ids = created.map(t => t.id).join(', ');
359
+ const allTasks = taskManager.listTasks();
360
+ const taskIndex = new Map(allTasks.map((t, i) => [t.id, i]));
171
361
  recordTaskEvent('batch_created', {
172
362
  'task.action': 'create',
173
363
  'task.count': created.length,
174
- 'task.ids': ids,
175
- 'task.total_count': taskManager.listTasks().length
364
+ 'task.items_json': safeStringify(created.map(t => serializeTask(t, taskIndex.get(t.id) ?? 0))),
365
+ ...getListContext(allTasks)
176
366
  });
177
- return `Created ${created.length} tasks: ${ids}\n\n${taskManager.formatTasksForPrompt()}`;
367
+ return `Created ${created.length} tasks: ${created.map(t => t.id).join(', ')}\n\n${taskManager.formatTasksForPrompt()}`;
178
368
  } else if (title) {
179
369
  // Single create
180
370
  const task = taskManager.createTask({ title, description, priority, dependencies, after });
371
+ const allTasks = taskManager.listTasks();
372
+ const order = allTasks.findIndex(t => t.id === task.id);
181
373
  recordTaskEvent('created', {
182
374
  'task.action': 'create',
183
375
  'task.id': task.id,
184
- 'task.title': title,
185
- 'task.priority': priority || 'none',
186
- 'task.has_dependencies': dependencies && dependencies.length > 0,
187
- 'task.after': after || 'none',
188
- 'task.total_count': taskManager.listTasks().length
376
+ 'task.title': task.title,
377
+ 'task.status': task.status,
378
+ 'task.priority': task.priority || null,
379
+ 'task.dependencies': safeStringify(task.dependencies || []),
380
+ 'task.after': after || null,
381
+ 'task.order': order,
382
+ ...getListContext(allTasks)
189
383
  });
190
384
  return `Created task ${task.id}: ${task.title}\n\n${taskManager.formatTasksForPrompt()}`;
191
385
  } else {
@@ -197,13 +391,15 @@ export function createTaskTool(options = {}) {
197
391
  if (tasks && Array.isArray(tasks)) {
198
392
  // Batch update
199
393
  const updated = taskManager.updateTasks(tasks);
200
- const ids = updated.map(t => t.id).join(', ');
394
+ const allTasks = taskManager.listTasks();
395
+ const taskIndex = new Map(allTasks.map((t, i) => [t.id, i]));
201
396
  recordTaskEvent('batch_updated', {
202
397
  'task.action': 'update',
203
398
  'task.count': updated.length,
204
- 'task.ids': ids
399
+ 'task.items_json': safeStringify(updated.map(t => serializeTask(t, taskIndex.get(t.id) ?? 0))),
400
+ ...getListContext(allTasks)
205
401
  });
206
- return `Updated ${updated.length} tasks: ${ids}\n\n${taskManager.formatTasksForPrompt()}`;
402
+ return `Updated ${updated.length} tasks: ${updated.map(t => t.id).join(', ')}\n\n${taskManager.formatTasksForPrompt()}`;
207
403
  } else if (id) {
208
404
  // Single update
209
405
  const updates = {};
@@ -214,11 +410,18 @@ export function createTaskTool(options = {}) {
214
410
  if (dependencies) updates.dependencies = dependencies;
215
411
 
216
412
  const task = taskManager.updateTask(id, updates);
413
+ const allTasks = taskManager.listTasks();
414
+ const order = allTasks.findIndex(t => t.id === task.id);
217
415
  recordTaskEvent('updated', {
218
416
  'task.action': 'update',
219
- 'task.id': id,
220
- 'task.new_status': status || 'unchanged',
221
- 'task.fields_updated': Object.keys(updates).join(', ')
417
+ 'task.id': task.id,
418
+ 'task.title': task.title,
419
+ 'task.status': task.status,
420
+ 'task.priority': task.priority || null,
421
+ 'task.dependencies': safeStringify(task.dependencies || []),
422
+ 'task.order': order,
423
+ 'task.fields_updated': Object.keys(updates).join(', '),
424
+ ...getListContext(allTasks)
222
425
  });
223
426
  return `Updated task ${task.id}\n\n${taskManager.formatTasksForPrompt()}`;
224
427
  } else {
@@ -235,21 +438,29 @@ export function createTaskTool(options = {}) {
235
438
  throw new Error(`Invalid task item at index ${index}: must be a string ID or object with 'id' property`);
236
439
  });
237
440
  const completed = taskManager.completeTasks(ids);
441
+ const allTasks = taskManager.listTasks();
442
+ const taskIndex = new Map(allTasks.map((t, i) => [t.id, i]));
238
443
  recordTaskEvent('batch_completed', {
239
444
  'task.action': 'complete',
240
445
  'task.count': completed.length,
241
- 'task.ids': ids.join(', '),
242
- 'task.incomplete_remaining': taskManager.getIncompleteTasks().length
446
+ 'task.items_json': safeStringify(completed.map(t => serializeTask(t, taskIndex.get(t.id) ?? 0))),
447
+ ...getListContext(allTasks)
243
448
  });
244
449
  return `Completed ${completed.length} tasks\n\n${taskManager.formatTasksForPrompt()}`;
245
450
  } else if (id) {
246
451
  // Single complete
247
452
  const task = taskManager.completeTask(id);
453
+ const allTasks = taskManager.listTasks();
454
+ const order = allTasks.findIndex(t => t.id === task.id);
248
455
  recordTaskEvent('completed', {
249
456
  'task.action': 'complete',
250
- 'task.id': id,
457
+ 'task.id': task.id,
251
458
  'task.title': task.title,
252
- 'task.incomplete_remaining': taskManager.getIncompleteTasks().length
459
+ 'task.status': task.status,
460
+ 'task.priority': task.priority || null,
461
+ 'task.dependencies': safeStringify(task.dependencies || []),
462
+ 'task.order': order,
463
+ ...getListContext(allTasks)
253
464
  });
254
465
  return `Completed task ${task.id}: ${task.title}\n\n${taskManager.formatTasksForPrompt()}`;
255
466
  } else {
@@ -265,21 +476,26 @@ export function createTaskTool(options = {}) {
265
476
  if (t && typeof t.id === 'string') return t.id;
266
477
  throw new Error(`Invalid task item at index ${index}: must be a string ID or object with 'id' property`);
267
478
  });
479
+ // Capture task data before deletion for the event
480
+ const tasksBefore = ids.map(tid => taskManager.getTask(tid)).filter(Boolean);
268
481
  const deleted = taskManager.deleteTasks(ids);
269
482
  recordTaskEvent('batch_deleted', {
270
483
  'task.action': 'delete',
271
484
  'task.count': deleted.length,
272
- 'task.ids': deleted.join(', '),
273
- 'task.total_count': taskManager.listTasks().length
485
+ 'task.items_json': safeStringify(tasksBefore.map((t, i) => ({ id: t.id, title: t.title, status: t.status }))),
486
+ ...getListContext()
274
487
  });
275
488
  return `Deleted ${deleted.length} tasks: ${deleted.join(', ')}\n\n${taskManager.formatTasksForPrompt()}`;
276
489
  } else if (id) {
277
- // Single delete
490
+ // Capture task data before deletion
491
+ const taskBefore = taskManager.getTask(id);
278
492
  taskManager.deleteTask(id);
279
493
  recordTaskEvent('deleted', {
280
494
  'task.action': 'delete',
281
495
  'task.id': id,
282
- 'task.total_count': taskManager.listTasks().length
496
+ 'task.title': taskBefore?.title || null,
497
+ 'task.status': taskBefore?.status || null,
498
+ ...getListContext()
283
499
  });
284
500
  return `Deleted task ${id}\n\n${taskManager.formatTasksForPrompt()}`;
285
501
  } else {
@@ -289,12 +505,13 @@ export function createTaskTool(options = {}) {
289
505
 
290
506
  case 'list': {
291
507
  const allTasks = taskManager.listTasks();
292
- const incomplete = taskManager.getIncompleteTasks();
508
+ const ctx = getListContext(allTasks);
293
509
  recordTaskEvent('listed', {
294
510
  'task.action': 'list',
295
- 'task.total_count': allTasks.length,
296
- 'task.incomplete_count': incomplete.length,
297
- 'task.completed_count': allTasks.length - incomplete.length
511
+ 'task.total_count': ctx['task.total_count'],
512
+ 'task.incomplete_count': ctx['task.incomplete_remaining'],
513
+ 'task.completed_count': ctx['task.total_count'] - ctx['task.incomplete_remaining'],
514
+ 'task.items_json': safeStringify(allTasks.map((t, i) => serializeTask(t, i)))
298
515
  });
299
516
  return taskManager.formatTasksForPrompt();
300
517
  }
package/build/delegate.js CHANGED
@@ -484,7 +484,10 @@ export async function delegate({
484
484
  disableJsonValidation: true, // Simpler responses
485
485
  maxIterations: remainingIterations,
486
486
  debug,
487
- tracer,
487
+ tracer: tracer && typeof tracer.createChildTracer === 'function'
488
+ ? tracer.createChildTracer(sessionId, { agentKind: 'delegate' })
489
+ : (tracer && debug && console.warn('[delegate] createChildTracer not available, using parent tracer — events will not be scoped to subagent'), tracer),
490
+ delegationTask: task,
488
491
  path, // Workspace root (from delegateTool)
489
492
  allowedFolders, // Inherit allowed folders to keep architecture context root consistent
490
493
  cwd: path, // Explicitly set cwd to workspace root to prevent path doubling