@ai-setting/roy-agent-core 1.6.16 → 1.6.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (28) hide show
  1. package/dist/env/agent/index.js +2 -2
  2. package/dist/env/event-source/index.js +3 -3
  3. package/dist/env/index.js +12 -12
  4. package/dist/env/prompt/index.js +2 -2
  5. package/dist/env/task/delegate/index.js +3 -3
  6. package/dist/env/task/index.js +5 -5
  7. package/dist/env/task/plugins/index.js +2 -2
  8. package/dist/env/task/tools/index.js +1 -1
  9. package/dist/env/tool/built-in/index.js +2 -2
  10. package/dist/env/tool/index.js +3 -3
  11. package/dist/env/workflow/index.js +2 -2
  12. package/dist/env/workflow/tools/index.js +1 -1
  13. package/dist/index.js +14 -14
  14. package/dist/shared/@ai-setting/{roy-agent-core-eaj0b1hm.js → roy-agent-core-44g3synv.js} +16 -8
  15. package/dist/shared/@ai-setting/{roy-agent-core-pq1q854s.js → roy-agent-core-65vpkh7x.js} +8 -0
  16. package/dist/shared/@ai-setting/{roy-agent-core-bsxgrqzq.js → roy-agent-core-6mz26khm.js} +1 -1
  17. package/dist/shared/@ai-setting/{roy-agent-core-r0t34bbh.js → roy-agent-core-c7e3htaq.js} +146 -8
  18. package/dist/shared/@ai-setting/{roy-agent-core-vkesx61c.js → roy-agent-core-e9jf4w3z.js} +1 -1
  19. package/dist/shared/@ai-setting/{roy-agent-core-x36fsm47.js → roy-agent-core-f6x6ksz3.js} +1 -1
  20. package/dist/shared/@ai-setting/{roy-agent-core-cfqm5w9b.js → roy-agent-core-h07kk80k.js} +1 -1
  21. package/dist/shared/@ai-setting/{roy-agent-core-4z7dzcgb.js → roy-agent-core-kkt2ndpf.js} +168 -7
  22. package/dist/shared/@ai-setting/{roy-agent-core-25ngkkpb.js → roy-agent-core-mvmgy9t0.js} +2 -2
  23. package/dist/shared/@ai-setting/{roy-agent-core-8v0hzw96.js → roy-agent-core-n1fx2fx4.js} +680 -33
  24. package/dist/shared/@ai-setting/{roy-agent-core-yqxrekhg.js → roy-agent-core-nreqmmtz.js} +1 -1
  25. package/dist/shared/@ai-setting/{roy-agent-core-ymz9dgw0.js → roy-agent-core-qyb2z42s.js} +28 -2
  26. package/dist/shared/@ai-setting/{roy-agent-core-dnns0v64.js → roy-agent-core-vc8e968b.js} +1 -1
  27. package/package.json +1 -1
  28. /package/dist/shared/@ai-setting/{roy-agent-core-b8wn8yhk.js → roy-agent-core-eskgj8nc.js} +0 -0
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  ProcessRegistry,
3
3
  runWithProcessRegistryAsync
4
- } from "./roy-agent-core-eaj0b1hm.js";
4
+ } from "./roy-agent-core-44g3synv.js";
5
5
  import {
6
6
  builtInPrompts
7
- } from "./roy-agent-core-pq1q854s.js";
7
+ } from "./roy-agent-core-65vpkh7x.js";
8
8
  import {
9
9
  runWithBgTaskIdAsync
10
10
  } from "./roy-agent-core-c1v263jn.js";
@@ -108,7 +108,631 @@ function listKnownSubagentDescriptions(registry) {
108
108
 
109
109
  // src/env/task/delegate/delegate-tool.ts
110
110
  init_global_hook_manager();
111
- var logger = createLogger("task:delegate");
111
+
112
+ // src/env/task/task-creation-registry.ts
113
+ var TOKEN_PATTERNS = [
114
+ /\bsk-(?:proj-)?[A-Za-z0-9_-]{16,}\b/g,
115
+ /\bxox[abprs]-[A-Za-z0-9-]{8,}\b/g,
116
+ /\bghp_[A-Za-z0-9]{16,}\b/g,
117
+ /\bgithub_pat_[A-Za-z0-9_]{16,}\b/g,
118
+ /\bBearer\s+[A-Za-z0-9._\-+/=]{12,}\b/g,
119
+ /\bAKIA[0-9A-Z]{16}\b/g
120
+ ];
121
+ function redactTokens(s) {
122
+ let out = s;
123
+ for (const pat of TOKEN_PATTERNS) {
124
+ out = out.replace(pat, "<redacted>");
125
+ }
126
+ return out;
127
+ }
128
+ function truncate(s, max) {
129
+ if (s.length <= max)
130
+ return s;
131
+ if (max <= 3)
132
+ return s.slice(0, max);
133
+ return s.slice(0, max - 1) + "…";
134
+ }
135
+ function sanitize(s, max) {
136
+ return truncate(redactTokens(s), max);
137
+ }
138
+
139
+ class TaskCreationRegistry {
140
+ maxEntries;
141
+ ttlMs;
142
+ maxDescriptionChars;
143
+ records = new Map;
144
+ childrenIndex = new Map;
145
+ constructor(config = {}) {
146
+ this.maxEntries = config.maxEntries ?? 500;
147
+ this.ttlMs = config.ttlMs ?? 24 * 60 * 60 * 1000;
148
+ this.maxDescriptionChars = config.maxDescriptionChars ?? 240;
149
+ }
150
+ record(task, source = {}) {
151
+ const now = new Date().toISOString();
152
+ const record = this.toRecord(task, source, now);
153
+ this.upsert(record);
154
+ this.indexParentChild(record);
155
+ this.evictIfNeeded();
156
+ return record;
157
+ }
158
+ update(task, source) {
159
+ const existing = this.records.get(task.id);
160
+ if (!existing)
161
+ return;
162
+ const now = new Date().toISOString();
163
+ this.records.delete(task.id);
164
+ const merged = {
165
+ ...existing,
166
+ ...this.toRecord(task, source ?? existing.source, now),
167
+ createdAt: existing.createdAt,
168
+ source: source ?? existing.source
169
+ };
170
+ this.records.set(task.id, merged);
171
+ this.childrenIndex.delete(merged.taskId);
172
+ this.indexParentChild(merged);
173
+ this.evictIfNeeded();
174
+ return merged;
175
+ }
176
+ remove(id) {
177
+ const rec = this.records.get(id);
178
+ if (!rec)
179
+ return false;
180
+ this.records.delete(id);
181
+ if (rec.parentTaskId !== undefined) {
182
+ const siblings = this.childrenIndex.get(rec.parentTaskId);
183
+ siblings?.delete(id);
184
+ if (siblings && siblings.size === 0) {
185
+ this.childrenIndex.delete(rec.parentTaskId);
186
+ }
187
+ }
188
+ return true;
189
+ }
190
+ getById(id) {
191
+ const rec = this.records.get(id);
192
+ if (!rec)
193
+ return;
194
+ if (Date.now() - Date.parse(rec.updatedAt) > this.ttlMs) {
195
+ this.remove(id);
196
+ return;
197
+ }
198
+ return rec;
199
+ }
200
+ peekById(id) {
201
+ return this.records.get(id);
202
+ }
203
+ clear() {
204
+ this.records.clear();
205
+ this.childrenIndex.clear();
206
+ }
207
+ size() {
208
+ return this.records.size;
209
+ }
210
+ recent(options = {}) {
211
+ const limit = options.limit ?? 10;
212
+ const statuses = options.statuses;
213
+ const excludeIds = new Set(options.excludeIds ?? []);
214
+ const excludeAncestorId = options.excludeAncestorOf ?? options.excludeSelfOrDescendant;
215
+ const excludeAncestorIds = excludeAncestorId ? this.collectSubtree(excludeAncestorId) : new Set;
216
+ const sinceMs = options.sinceHours ? Date.now() - options.sinceHours * 60 * 60 * 1000 : 0;
217
+ const now = Date.now();
218
+ const excludeAll = new Set([...excludeIds, ...excludeAncestorIds]);
219
+ const all = [];
220
+ for (const rec of this.records.values()) {
221
+ if (now - Date.parse(rec.updatedAt) > this.ttlMs)
222
+ continue;
223
+ if (sinceMs > 0 && Date.parse(rec.updatedAt) < sinceMs)
224
+ continue;
225
+ if (excludeAll.has(rec.taskId))
226
+ continue;
227
+ if (options.scope) {
228
+ if (options.scope.sessionId !== undefined && rec.source.sessionId !== options.scope.sessionId)
229
+ continue;
230
+ if (options.scope.parentSessionId !== undefined && rec.source.parentSessionId !== options.scope.parentSessionId)
231
+ continue;
232
+ if (options.scope.subagentType !== undefined && rec.source.subagentType !== options.scope.subagentType)
233
+ continue;
234
+ }
235
+ if (statuses) {
236
+ if (!statuses.includes(rec.status))
237
+ continue;
238
+ } else {
239
+ if (rec.status === "archived")
240
+ continue;
241
+ }
242
+ all.push(rec);
243
+ }
244
+ if (options.activeChildFirst) {
245
+ const activeChildBoost = new Map;
246
+ for (const rec of all) {
247
+ activeChildBoost.set(rec.taskId, this.hasActiveChild(rec.taskId));
248
+ }
249
+ all.sort((a, b) => {
250
+ const aBoost = activeChildBoost.get(a.taskId) ? 1 : 0;
251
+ const bBoost = activeChildBoost.get(b.taskId) ? 1 : 0;
252
+ if (aBoost !== bBoost)
253
+ return bBoost - aBoost;
254
+ const cmp = Date.parse(b.updatedAt) - Date.parse(a.updatedAt);
255
+ if (cmp !== 0)
256
+ return cmp;
257
+ return b.taskId - a.taskId;
258
+ });
259
+ } else {
260
+ all.sort((a, b) => {
261
+ const cmp = Date.parse(b.updatedAt) - Date.parse(a.updatedAt);
262
+ if (cmp !== 0)
263
+ return cmp;
264
+ return b.taskId - a.taskId;
265
+ });
266
+ }
267
+ return all.slice(0, limit);
268
+ }
269
+ getSubtreeSummary(parentId, options = {}) {
270
+ const includeArchived = options.includeArchived === true;
271
+ const summary = {
272
+ descendantCount: 0,
273
+ activeCount: 0,
274
+ completedCount: 0,
275
+ maxDepth: 0
276
+ };
277
+ if (!this.records.has(parentId) && !this.childrenIndex.has(parentId)) {
278
+ return summary;
279
+ }
280
+ const directChildren = this.childrenIndex.get(parentId);
281
+ const currentLevel = directChildren ? Array.from(directChildren) : [];
282
+ let depth = currentLevel.length > 0 ? 1 : 0;
283
+ summary.maxDepth = depth;
284
+ let levelIds = currentLevel;
285
+ while (levelIds.length > 0) {
286
+ const nextLevel = [];
287
+ for (const id of levelIds) {
288
+ const rec = this.records.get(id);
289
+ if (!rec)
290
+ continue;
291
+ if (!includeArchived && rec.status === "archived")
292
+ continue;
293
+ summary.descendantCount++;
294
+ if (rec.status === "active") {
295
+ summary.activeCount++;
296
+ } else if (rec.status === "completed") {
297
+ summary.completedCount++;
298
+ }
299
+ const grandchildren = this.childrenIndex.get(id);
300
+ if (grandchildren && grandchildren.size > 0) {
301
+ for (const gc of grandchildren)
302
+ nextLevel.push(gc);
303
+ }
304
+ }
305
+ if (nextLevel.length > 0) {
306
+ depth++;
307
+ summary.maxDepth = depth;
308
+ }
309
+ levelIds = nextLevel;
310
+ }
311
+ return summary;
312
+ }
313
+ hasActiveChild(parentId) {
314
+ const children = this.childrenIndex.get(parentId);
315
+ if (!children)
316
+ return false;
317
+ for (const childId of children) {
318
+ const rec = this.records.get(childId);
319
+ if (!rec)
320
+ continue;
321
+ if (rec.status === "active" || rec.status === "todo")
322
+ return true;
323
+ }
324
+ return false;
325
+ }
326
+ toRecord(task, source, isoNow) {
327
+ const parsedContext = this.safeParseContext(task.context);
328
+ const safeContext = parsedContext ? this.stripSensitiveKeys(parsedContext) : undefined;
329
+ return {
330
+ taskId: task.id,
331
+ title: task.title,
332
+ description: sanitize(task.description || "", this.maxDescriptionChars),
333
+ goals: sanitize(task.goals_and_expected_deliverables || "", this.maxDescriptionChars),
334
+ tags: task.tags ?? [],
335
+ status: task.status,
336
+ priority: task.priority,
337
+ type: task.type,
338
+ parentTaskId: task.parent_task_id,
339
+ source,
340
+ createdAt: task.createdAt || isoNow,
341
+ updatedAt: task.updatedAt || isoNow,
342
+ operationCount: 0,
343
+ project_path: task.project_path,
344
+ branch: this.pickString(safeContext, "branch"),
345
+ worktree_path: this.pickString(safeContext, "worktree_path"),
346
+ parentBranch: this.pickString(safeContext, "parent_branch"),
347
+ context: safeContext
348
+ };
349
+ }
350
+ safeParseContext(raw) {
351
+ if (!raw || !raw.trim())
352
+ return;
353
+ try {
354
+ const parsed = JSON.parse(raw);
355
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
356
+ return parsed;
357
+ }
358
+ return;
359
+ } catch {
360
+ return;
361
+ }
362
+ }
363
+ pickString(obj, key) {
364
+ if (!obj)
365
+ return;
366
+ const v = obj[key];
367
+ return typeof v === "string" ? v : undefined;
368
+ }
369
+ static STRIPPED_KEYS = ["commit", "token", "secret", "password", "api_key"];
370
+ stripSensitiveKeys(obj) {
371
+ if (obj === null || obj === undefined)
372
+ return obj;
373
+ if (Array.isArray(obj))
374
+ return obj.map((v) => this.stripSensitiveKeys(v));
375
+ if (typeof obj !== "object")
376
+ return obj;
377
+ const out = {};
378
+ const strippedKeys = TaskCreationRegistry.STRIPPED_KEYS;
379
+ for (const [k, v] of Object.entries(obj)) {
380
+ const lower = k.toLowerCase();
381
+ if (strippedKeys.some((s) => lower.includes(s)))
382
+ continue;
383
+ out[k] = this.stripSensitiveKeys(v);
384
+ }
385
+ return out;
386
+ }
387
+ upsert(record) {
388
+ this.records.set(record.taskId, record);
389
+ }
390
+ indexParentChild(record) {
391
+ if (record.parentTaskId === undefined || record.parentTaskId === null)
392
+ return;
393
+ const parentId = record.parentTaskId;
394
+ let set = this.childrenIndex.get(parentId);
395
+ if (!set) {
396
+ set = new Set;
397
+ this.childrenIndex.set(parentId, set);
398
+ }
399
+ set.add(record.taskId);
400
+ }
401
+ collectSubtree(rootId) {
402
+ const out = new Set([rootId]);
403
+ const queue = [rootId];
404
+ while (queue.length > 0) {
405
+ const id = queue.shift();
406
+ const children = this.childrenIndex.get(id);
407
+ if (!children)
408
+ continue;
409
+ for (const child of children) {
410
+ if (!out.has(child)) {
411
+ out.add(child);
412
+ queue.push(child);
413
+ }
414
+ }
415
+ }
416
+ return out;
417
+ }
418
+ evictIfNeeded() {
419
+ if (this.records.size <= this.maxEntries)
420
+ return;
421
+ const overflow = this.records.size - this.maxEntries;
422
+ const keys = Array.from(this.records.keys()).slice(0, overflow);
423
+ for (const k of keys) {
424
+ this.remove(k);
425
+ }
426
+ }
427
+ }
428
+
429
+ // src/env/task/registry-hook-installer.ts
430
+ init_logger();
431
+ var logger = createLogger("registry-hook-installer");
432
+ function installTaskCreationRegistryHooks(taskComponent, registry, options = {}) {
433
+ const uniqueSuffix = `${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
434
+ const hookName = options.hookName ? `${options.hookName}:${uniqueSuffix}` : `registry-hook-installer:${uniqueSuffix}`;
435
+ const priority = options.priority ?? 100;
436
+ let uninstalled = false;
437
+ const safeRecord = (task, source) => {
438
+ try {
439
+ registry.record(task, source);
440
+ } catch (err) {
441
+ logger.warn(`[registry-hook-installer] record failed for task #${task.id}: ${err instanceof Error ? err.message : String(err)}`);
442
+ }
443
+ };
444
+ const safeUpdate = (task, source) => {
445
+ try {
446
+ registry.update(task, source);
447
+ } catch (err) {
448
+ logger.warn(`[registry-hook-installer] update failed for task #${task.id}: ${err instanceof Error ? err.message : String(err)}`);
449
+ }
450
+ };
451
+ const safeRemove = (id) => {
452
+ try {
453
+ registry.remove(id);
454
+ } catch (err) {
455
+ logger.warn(`[registry-hook-installer] remove failed for task #${id}: ${err instanceof Error ? err.message : String(err)}`);
456
+ }
457
+ };
458
+ const buildSource = () => ({
459
+ sessionId: options.sessionIdProvider?.(),
460
+ ...options.staticSource ?? {}
461
+ });
462
+ const afterCreate = {
463
+ name: `${hookName}:after-create`,
464
+ priority,
465
+ execute: async (ctx) => {
466
+ const data = ctx.data;
467
+ if (data?.task)
468
+ safeRecord(data.task, buildSource());
469
+ }
470
+ };
471
+ const afterUpdate = {
472
+ name: `${hookName}:after-update`,
473
+ priority,
474
+ execute: async (ctx) => {
475
+ const data = ctx.data;
476
+ if (!data?.task)
477
+ return;
478
+ const exists = registry.peekById(data.task.id);
479
+ if (exists) {
480
+ safeUpdate(data.task, buildSource());
481
+ } else {
482
+ safeRecord(data.task, buildSource());
483
+ }
484
+ }
485
+ };
486
+ const afterComplete = {
487
+ name: `${hookName}:after-complete`,
488
+ priority,
489
+ execute: async (ctx) => {
490
+ const data = ctx.data;
491
+ if (data?.task)
492
+ safeUpdate(data.task, buildSource());
493
+ }
494
+ };
495
+ const afterDelete = {
496
+ name: `${hookName}:after-delete`,
497
+ priority,
498
+ execute: async (ctx) => {
499
+ const data = ctx.data;
500
+ if (typeof data?.id === "number")
501
+ safeRemove(data.id);
502
+ }
503
+ };
504
+ taskComponent.registerHook(TaskHookPoints.AFTER_CREATE, afterCreate);
505
+ taskComponent.registerHook(TaskHookPoints.AFTER_UPDATE, afterUpdate);
506
+ taskComponent.registerHook(TaskHookPoints.AFTER_COMPLETE, afterComplete);
507
+ taskComponent.registerHook(TaskHookPoints.AFTER_DELETE, afterDelete);
508
+ return {
509
+ uninstall() {
510
+ if (uninstalled)
511
+ return;
512
+ uninstalled = true;
513
+ taskComponent.unregisterHook(TaskHookPoints.AFTER_CREATE, afterCreate.name);
514
+ taskComponent.unregisterHook(TaskHookPoints.AFTER_UPDATE, afterUpdate.name);
515
+ taskComponent.unregisterHook(TaskHookPoints.AFTER_COMPLETE, afterComplete.name);
516
+ taskComponent.unregisterHook(TaskHookPoints.AFTER_DELETE, afterDelete.name);
517
+ logger.debug(`[registry-hook-installer] uninstalled hooks (name=${hookName})`);
518
+ }
519
+ };
520
+ }
521
+
522
+ // src/env/task/task-reuse-registry.ts
523
+ init_logger();
524
+ var logger2 = createLogger("task-reuse-registry");
525
+ var DEFAULT_CONFIG = {
526
+ maxEntries: 500,
527
+ ttlMs: 24 * 60 * 60 * 1000,
528
+ maxDescriptionChars: 240
529
+ };
530
+ var registries = new WeakMap;
531
+ function getOrCreateTaskReuseRegistry(env) {
532
+ const existing = registries.get(env);
533
+ if (existing)
534
+ return existing.registry;
535
+ const registry = new TaskCreationRegistry(DEFAULT_CONFIG);
536
+ let installHandle = { uninstall: () => {} };
537
+ try {
538
+ const taskComponent = env.getComponent?.("task");
539
+ if (taskComponent?.registerHook) {
540
+ installHandle = installTaskCreationRegistryHooks(taskComponent, registry, {
541
+ sessionIdProvider: () => {
542
+ try {
543
+ return env.currentSessionId ?? undefined;
544
+ } catch {
545
+ return;
546
+ }
547
+ },
548
+ staticSource: {
549
+ processId: typeof process !== "undefined" ? process.pid : undefined
550
+ }
551
+ });
552
+ logger2.debug("[task-reuse-registry] hook installed");
553
+ } else {
554
+ logger2.debug("[task-reuse-registry] no TaskComponent in env; registry stays empty");
555
+ }
556
+ } catch (err) {
557
+ logger2.warn(`[task-reuse-registry] hook install failed: ${err instanceof Error ? err.message : String(err)}`);
558
+ }
559
+ const entry = { registry, installHandle };
560
+ registries.set(env, entry);
561
+ return registry;
562
+ }
563
+
564
+ // src/env/task/task-reuse-context.ts
565
+ var TOKEN_PATTERNS2 = [
566
+ /\bsk-(?:proj-)?[A-Za-z0-9_-]{16,}\b/g,
567
+ /\bxox[abprs]-[A-Za-z0-9-]{8,}\b/g,
568
+ /\bghp_[A-Za-z0-9]{16,}\b/g,
569
+ /\bgithub_pat_[A-Za-z0-9_]{16,}\b/g,
570
+ /\bBearer\s+[A-Za-z0-9._\-+/=]{12,}\b/g,
571
+ /\bAKIA[0-9A-Z]{16}\b/g
572
+ ];
573
+ function redact(s) {
574
+ let out = s;
575
+ for (const pat of TOKEN_PATTERNS2)
576
+ out = out.replace(pat, "<redacted>");
577
+ return out;
578
+ }
579
+ function truncate2(s, max) {
580
+ if (s.length <= max)
581
+ return s;
582
+ if (max <= 1)
583
+ return s.slice(0, max);
584
+ return s.slice(0, max - 1) + "…";
585
+ }
586
+ function buildReuseCandidateSection(options) {
587
+ const limit = options.limit ?? 5;
588
+ const maxChars = options.maxChars ?? 2000;
589
+ const maxDescChars = options.maxDescriptionChars ?? 240;
590
+ const heading = options.heading ?? "Recent task context (reuse candidates)";
591
+ const recent = options.registry.recent({
592
+ scope: options.scope,
593
+ limit,
594
+ statuses: options.statuses,
595
+ excludeIds: options.similarTaskIds ?? [],
596
+ excludeAncestorOf: options.excludeAncestorOf
597
+ });
598
+ if (recent.length === 0) {
599
+ return { markdown: "", candidateIds: [], isEmpty: true };
600
+ }
601
+ const lines = [];
602
+ lines.push(`## ${heading}`);
603
+ lines.push("");
604
+ lines.push("The following tasks were recently created in this process / session and may be related to your current work. " + "Before creating a brand-new task, check whether one of these is the same work continuing. " + "The list is a *suggestion only* — confirm with `task_get <id>` before reusing.");
605
+ lines.push("");
606
+ for (const rec of recent) {
607
+ lines.push(`### Task #${rec.taskId}: ${rec.title}`);
608
+ lines.push(`- Status: ${rec.status}`);
609
+ if (rec.parentTaskId !== undefined && rec.parentTaskId !== null) {
610
+ lines.push(`- Parent: #${rec.parentTaskId}`);
611
+ }
612
+ if (rec.source.subagentType) {
613
+ lines.push(`- Created by: ${rec.source.subagentType}`);
614
+ }
615
+ lines.push(`- Updated: ${rec.updatedAt}`);
616
+ if (rec.project_path) {
617
+ lines.push(`- Project: ${rec.project_path}`);
618
+ }
619
+ if (rec.worktree_path) {
620
+ lines.push(`- Worktree: ${rec.worktree_path}`);
621
+ }
622
+ if (rec.branch) {
623
+ lines.push(`- Branch: ${rec.branch}`);
624
+ }
625
+ if (rec.parentBranch) {
626
+ lines.push(`- Parent branch: ${rec.parentBranch}`);
627
+ }
628
+ let summary;
629
+ try {
630
+ summary = options.registry.getSubtreeSummary(rec.taskId);
631
+ } catch {
632
+ summary = undefined;
633
+ }
634
+ if (summary && summary.descendantCount > 0) {
635
+ lines.push(`- Children: ${summary.descendantCount} ` + `(active: ${summary.activeCount}, completed: ${summary.completedCount}, max depth: ${summary.maxDepth})`);
636
+ }
637
+ const hasActiveChild = summary !== undefined && summary.activeCount > 0;
638
+ if (rec.status === "completed" && hasActiveChild) {
639
+ lines.push(`- ⚠ Has active child — reopening this parent will conflict with running work; prefer creating a child task.`);
640
+ }
641
+ const safeDesc = redact(truncate2(rec.description, maxDescChars));
642
+ if (safeDesc) {
643
+ lines.push(`- Description: ${safeDesc}`);
644
+ }
645
+ if (rec.tags && rec.tags.length > 0) {
646
+ lines.push(`- Tags: ${rec.tags.join(", ")}`);
647
+ }
648
+ lines.push("");
649
+ }
650
+ lines.push("---");
651
+ lines.push("");
652
+ lines.push("### Reuse instructions");
653
+ lines.push("");
654
+ lines.push("1. **First** call `task_get <id>` on the most relevant candidate to read its description, goals, and recent operations.");
655
+ lines.push("2. **If it is the same work continuing** — prefer `task_update` over `task_create`:");
656
+ lines.push(" - Set `status` back to `active` (or `todo`) to reopen a completed task.");
657
+ lines.push(" - Append new context to `description` and `goals_and_expected_deliverables` (do not replace).");
658
+ lines.push(' - Update `current_status` to reflect the new scope, then call `task_operation_create` with `milestone_type: "decision"` to record the continuation.');
659
+ lines.push("3. **Only** if the candidate is clearly a different task, call `task_create` with a distinguishing title and `parent_task_id` if appropriate.");
660
+ lines.push("4. Do **not** create a duplicate task merely because the existing one is already `completed` — reopen it instead.");
661
+ lines.push("5. Do **not** reuse an unrelated candidate just to fill this list. Empty reuse is a valid outcome.");
662
+ lines.push("");
663
+ lines.push("### Decision matrix — three legal paths");
664
+ lines.push("");
665
+ lines.push("Pick exactly **one** of these paths before calling `task_create`. Reopen-first");
666
+ lines.push("remains the default, but the matrix adds two new paths for independent work.");
667
+ lines.push("");
668
+ lines.push("**Path A — Continuation (reopen existing task).** The new work is the SAME");
669
+ lines.push('logical task. Use `task_update(task_id=<id>, { status: "active", ... })` and');
670
+ lines.push("append description/goals. Do **not** create a new task id.");
671
+ lines.push("");
672
+ lines.push("```");
673
+ lines.push('task_update(task_id=<parent_id>, { status: "active", current_status: "..." })');
674
+ lines.push('task_operation_create({ task_id=<parent_id>, milestone_type: "decision",');
675
+ lines.push(' milestone_title: "Reopened for <new-scope>",');
676
+ lines.push(' milestone_description: "<why this is the same work>" })');
677
+ lines.push("```");
678
+ lines.push("");
679
+ lines.push("**Path B — Child task (parent_task_id=<id>).** The new work is related but");
680
+ lines.push("distinct: an independent bug fix, optimization, parallel delivery or");
681
+ lines.push("independent acceptance item under the same feature. Create a child task so");
682
+ lines.push("the audit trail is preserved. Always pair with an audit operation.");
683
+ lines.push("");
684
+ lines.push("```");
685
+ for (const rec of recent) {
686
+ const safeProj = rec.project_path ?? "<project_path>";
687
+ lines.push(`task_create(parent_task_id=${rec.taskId}, title="<distinguishing title>", ` + `project_path="${safeProj}", context=<inherit or override>)`);
688
+ break;
689
+ }
690
+ lines.push('task_operation_create({ task_id=<new_id>, milestone_type: "milestone",');
691
+ lines.push(' milestone_title: "Child of #<parent_id>: <new-scope>",');
692
+ lines.push(' milestone_description: "<why this is a sibling/child, not a reopen>" })');
693
+ lines.push("```");
694
+ lines.push("");
695
+ lines.push("**Path C — New root (no parent_task_id).** The new work is unrelated to any");
696
+ lines.push("existing task. Create a brand-new root task. Use this sparingly — root count");
697
+ lines.push("should stay ≤ ~10 to keep the tree readable.");
698
+ lines.push("");
699
+ lines.push("```");
700
+ lines.push('task_create(parent_task_id=undefined, title="<distinct topic>", project_path="<project_path>", context=<json>)');
701
+ lines.push('task_operation_create({ task_id=<new_id>, milestone_type: "create",');
702
+ lines.push(' milestone_title: "New root: <topic>",');
703
+ lines.push(' milestone_description: "<why this is not a child of any existing task>" })');
704
+ lines.push("```");
705
+ lines.push("");
706
+ lines.push("**Operation audit templates.** Every `task_create`/`task_update` call MUST be");
707
+ lines.push("followed by a `task_operation_create` so the next agent sees *why* the path was");
708
+ lines.push("chosen. Use the appropriate `milestone_type`:");
709
+ lines.push(" - continuation → `decision` (and `progress` once the new scope makes headway)");
710
+ lines.push(" - child task → `milestone` (then `progress` as the child advances)");
711
+ lines.push(" - new root → `create` (then `progress` per phase)");
712
+ lines.push("");
713
+ lines.push("**Project + context inheritance.** For Path B/C, prefer inheriting the");
714
+ lines.push("candidate's `project_path`, `context.worktree_path`, `context.branch` and");
715
+ lines.push("`context.parent_branch` from the parent task. Sensitive keys (commits,");
716
+ lines.push("tokens, secrets) are stripped automatically. Use the helper:");
717
+ lines.push("");
718
+ lines.push("```");
719
+ lines.push("inheritChildContext({ parent: { taskId, projectPath, contextJson, tags }, overrides: {...} })");
720
+ lines.push("```");
721
+ lines.push("");
722
+ let markdown = lines.join(`
723
+ `);
724
+ if (markdown.length > maxChars) {
725
+ markdown = truncate2(markdown, maxChars);
726
+ }
727
+ return {
728
+ markdown,
729
+ candidateIds: recent.map((r) => r.taskId),
730
+ isEmpty: false
731
+ };
732
+ }
733
+
734
+ // src/env/task/delegate/delegate-tool.ts
735
+ var logger3 = createLogger("task:delegate");
112
736
  var BackgroundTaskEventTypes = {
113
737
  STARTED: "task.background.started",
114
738
  PROGRESS: "task.background.progress",
@@ -202,7 +826,7 @@ function ensureSubAgentRegistered(agentComponent, subagentType, basePrompt, subA
202
826
  if (registry) {
203
827
  const agentDef = registry.get(subagentType);
204
828
  if (agentDef?.type === "workflow") {
205
- logger.debug(`[delegate] Skipping registration for workflow agent: ${subagentType}`);
829
+ logger3.debug(`[delegate] Skipping registration for workflow agent: ${subagentType}`);
206
830
  return null;
207
831
  }
208
832
  }
@@ -219,7 +843,7 @@ function ensureSubAgentRegistered(agentComponent, subagentType, basePrompt, subA
219
843
  agentConfig.deniedTools = deniedTools;
220
844
  }
221
845
  agentInstance = agentComponent.registerAgent(subagentType, agentConfig);
222
- logger.debug(`[delegate] Registered subagent: ${subagentType}`);
846
+ logger3.debug(`[delegate] Registered subagent: ${subagentType}`);
223
847
  }
224
848
  return agentInstance;
225
849
  }
@@ -316,11 +940,11 @@ class BackgroundTaskManager {
316
940
  return { runIds: [], stopResults: [] };
317
941
  }
318
942
  const runIds = Array.from(set);
319
- logger.info(`[BackgroundTaskManager] propagateStopToWorkflowRuns count=${runIds.length} bgTaskId=${bgTaskId}`);
943
+ logger3.info(`[BackgroundTaskManager] propagateStopToWorkflowRuns count=${runIds.length} bgTaskId=${bgTaskId}`);
320
944
  const stopResults = [];
321
945
  for (const runId of runIds) {
322
946
  if (typeof this.workflowService?.stopRun !== "function") {
323
- logger.warn(`[BackgroundTaskManager] workflowService not injected; cannot stop ${runId}`);
947
+ logger3.warn(`[BackgroundTaskManager] workflowService not injected; cannot stop ${runId}`);
324
948
  stopResults.push({
325
949
  runId,
326
950
  success: false,
@@ -333,7 +957,7 @@ class BackgroundTaskManager {
333
957
  stopResults.push({ runId, success: true });
334
958
  } catch (err) {
335
959
  const errMsg = err instanceof Error ? err.message : String(err);
336
- logger.warn(`[BackgroundTaskManager] workflowService.stopRun(${runId}) failed (likely already terminal): ${errMsg}`);
960
+ logger3.warn(`[BackgroundTaskManager] workflowService.stopRun(${runId}) failed (likely already terminal): ${errMsg}`);
337
961
  stopResults.push({ runId, success: false, error: errMsg });
338
962
  }
339
963
  }
@@ -344,7 +968,7 @@ class BackgroundTaskManager {
344
968
  resolveEntryAgent(subagentType) {
345
969
  try {
346
970
  if (BackgroundTaskManager.detectWorkflowShortcutCompat(subagentType)) {
347
- logger.info(`[BackgroundTaskManager] Compat rewrite: '${subagentType}' → '${BackgroundTaskManager.DEFAULT_WORKFLOW_ENTRY_AGENT}'`);
971
+ logger3.info(`[BackgroundTaskManager] Compat rewrite: '${subagentType}' → '${BackgroundTaskManager.DEFAULT_WORKFLOW_ENTRY_AGENT}'`);
348
972
  return BackgroundTaskManager.DEFAULT_WORKFLOW_ENTRY_AGENT;
349
973
  }
350
974
  const agentComponent = this.env?.getComponent?.("agent");
@@ -355,7 +979,7 @@ class BackgroundTaskManager {
355
979
  }
356
980
  return agent.entryAgent ?? BackgroundTaskManager.DEFAULT_WORKFLOW_ENTRY_AGENT;
357
981
  } catch (err) {
358
- logger.warn(`[BackgroundTaskManager] resolveEntryAgent failed for ${subagentType}: ${err}`);
982
+ logger3.warn(`[BackgroundTaskManager] resolveEntryAgent failed for ${subagentType}: ${err}`);
359
983
  return;
360
984
  }
361
985
  }
@@ -376,7 +1000,7 @@ class BackgroundTaskManager {
376
1000
  trigger_session_id: parentSessionId
377
1001
  }
378
1002
  });
379
- logger.info(`[BackgroundTaskManager] Event published: ${type}`);
1003
+ logger3.info(`[BackgroundTaskManager] Event published: ${type}`);
380
1004
  }
381
1005
  }
382
1006
  async createTask(options) {
@@ -398,7 +1022,7 @@ class BackgroundTaskManager {
398
1022
  }
399
1023
  const createdBy = reusedSession?.info?.metadata?.created_by;
400
1024
  if (createdBy && createdBy !== "subagent") {
401
- logger.warn(`[BackgroundTaskManager] Session ${existingSubSessionId} was not created by a sub-agent (created_by=${createdBy}); reusing anyway`);
1025
+ logger3.warn(`[BackgroundTaskManager] Session ${existingSubSessionId} was not created by a sub-agent (created_by=${createdBy}); reusing anyway`);
402
1026
  }
403
1027
  subSession = reusedSession;
404
1028
  try {
@@ -411,9 +1035,9 @@ class BackgroundTaskManager {
411
1035
  }
412
1036
  });
413
1037
  } catch (err) {
414
- logger.warn(`[BackgroundTaskManager] Failed to update reused session metadata`, { existingSubSessionId, error: err instanceof Error ? err.message : String(err) });
1038
+ logger3.warn(`[BackgroundTaskManager] Failed to update reused session metadata`, { existingSubSessionId, error: err instanceof Error ? err.message : String(err) });
415
1039
  }
416
- logger.info(`[BackgroundTaskManager] Reusing sub session: ${existingSubSessionId} for task ${taskIdGen}`);
1040
+ logger3.info(`[BackgroundTaskManager] Reusing sub session: ${existingSubSessionId} for task ${taskIdGen}`);
417
1041
  } else {
418
1042
  const metadata = {
419
1043
  subagent_type: subagentType,
@@ -476,7 +1100,7 @@ class BackgroundTaskManager {
476
1100
  };
477
1101
  this.publishBackgroundEvent(BackgroundTaskEventTypes.STARTED, startedPayload, parentSessionId);
478
1102
  this.executeTask(taskIdGen, prompt, timeout, parentSessionId).catch((err) => {
479
- logger.error(`[BackgroundTaskManager] executeTask unhandled rejection`, {
1103
+ logger3.error(`[BackgroundTaskManager] executeTask unhandled rejection`, {
480
1104
  taskId: taskIdGen,
481
1105
  error: err instanceof Error ? err.message : String(err)
482
1106
  });
@@ -486,7 +1110,7 @@ class BackgroundTaskManager {
486
1110
  async executeTask(taskId, prompt, timeout, parentSessionId) {
487
1111
  const task = this.tasks.get(taskId);
488
1112
  if (!task) {
489
- logger.warn(`[BackgroundTaskManager] executeTask: Task not found`, { taskId });
1113
+ logger3.warn(`[BackgroundTaskManager] executeTask: Task not found`, { taskId });
490
1114
  return;
491
1115
  }
492
1116
  const startedAt = Date.now();
@@ -505,7 +1129,7 @@ class BackgroundTaskManager {
505
1129
  const taskRegistry = this.processRegistries.get(taskId);
506
1130
  const result = await this.executeWithAbort(subSession, prompt, timeoutMs, taskId, abortController?.signal, task.similarTaskIds, taskRegistry);
507
1131
  if (abortController?.signal.aborted) {
508
- logger.info(`[BackgroundTaskManager] Task was aborted`, { taskId });
1132
+ logger3.info(`[BackgroundTaskManager] Task was aborted`, { taskId });
509
1133
  return;
510
1134
  }
511
1135
  const completedAt = Date.now();
@@ -532,7 +1156,7 @@ class BackgroundTaskManager {
532
1156
  associatedTaskId: task.associatedTaskId
533
1157
  };
534
1158
  this.publishBackgroundEvent(BackgroundTaskEventTypes.COMPLETED, completedPayload, parentSessionId || task.parentSessionId);
535
- logger.info(`[BackgroundTaskManager] Task completed successfully`, {
1159
+ logger3.info(`[BackgroundTaskManager] Task completed successfully`, {
536
1160
  taskId,
537
1161
  executionTimeMs
538
1162
  });
@@ -584,7 +1208,7 @@ class BackgroundTaskManager {
584
1208
  this.publishBackgroundEvent(BackgroundTaskEventTypes.FAILED, failedPayload, parentSessionId || task.parentSessionId);
585
1209
  }
586
1210
  task.error = errorMessage;
587
- logger.error(`[BackgroundTaskManager] Task execution error`, {
1211
+ logger3.error(`[BackgroundTaskManager] Task execution error`, {
588
1212
  taskId,
589
1213
  status: task.status,
590
1214
  error: errorMessage
@@ -667,7 +1291,7 @@ ${prompt}
667
1291
  try {
668
1292
  const similarTask = await taskComponent.getTask(similarId);
669
1293
  if (!similarTask) {
670
- logger.warn(`[delegate] Similar task #${similarId} not found, skipping`);
1294
+ logger3.warn(`[delegate] Similar task #${similarId} not found, skipping`);
671
1295
  continue;
672
1296
  }
673
1297
  const operations = await taskComponent.listOperations({ taskId: similarId, limit: 10 });
@@ -693,7 +1317,7 @@ ${prompt}
693
1317
  }
694
1318
  similarTasksSections.push(section);
695
1319
  } catch (err) {
696
- logger.warn(`[delegate] Failed to fetch similar task #${similarId}`, { error: err instanceof Error ? err.message : String(err) });
1320
+ logger3.warn(`[delegate] Failed to fetch similar task #${similarId}`, { error: err instanceof Error ? err.message : String(err) });
697
1321
  }
698
1322
  }
699
1323
  if (similarTasksSections.length > 0) {
@@ -716,12 +1340,35 @@ Use these as guidance to improve task execution efficiency.`;
716
1340
  }
717
1341
  }
718
1342
  } catch (err) {
719
- logger.warn(`[delegate] Failed to inject similar tasks reference`, { error: err instanceof Error ? err.message : String(err) });
1343
+ logger3.warn(`[delegate] Failed to inject similar tasks reference`, { error: err instanceof Error ? err.message : String(err) });
720
1344
  }
721
1345
  }
722
1346
  if (!agentComponent) {
723
1347
  throw new Error("AgentComponent not found");
724
1348
  }
1349
+ try {
1350
+ if (this.env) {
1351
+ const reuseRegistry = getOrCreateTaskReuseRegistry(this.env);
1352
+ const reuseSection = buildReuseCandidateSection({
1353
+ registry: reuseRegistry,
1354
+ similarTaskIds: similarTaskIds ?? [],
1355
+ limit: 5,
1356
+ maxChars: 2000,
1357
+ maxDescriptionChars: 240
1358
+ });
1359
+ if (!reuseSection.isEmpty) {
1360
+ fullPrompt += `
1361
+
1362
+ ---
1363
+
1364
+ ${reuseSection.markdown}`;
1365
+ }
1366
+ }
1367
+ } catch (err) {
1368
+ logger3.warn(`[delegate] Failed to inject reuse candidate section`, {
1369
+ error: err instanceof Error ? err.message : String(err)
1370
+ });
1371
+ }
725
1372
  const subAgentForRegister = subAgent ? { ...subAgent, allowedTools } : allowedTools ? {
726
1373
  id: subagentType,
727
1374
  name: subagentType,
@@ -738,7 +1385,7 @@ Use these as guidance to improve task execution efficiency.`;
738
1385
  try {
739
1386
  await processRegistry?.killAll();
740
1387
  } catch (err) {
741
- logger.warn(`[delegate] ProcessRegistry.killAll failed`, {
1388
+ logger3.warn(`[delegate] ProcessRegistry.killAll failed`, {
742
1389
  error: err instanceof Error ? err.message : String(err)
743
1390
  });
744
1391
  }
@@ -798,7 +1445,7 @@ Use these as guidance to improve task execution efficiency.`;
798
1445
  try {
799
1446
  await this.processRegistries.get(taskId)?.killAll();
800
1447
  } catch (err) {
801
- logger.warn(`[delegate] stopTask killAll failed`, {
1448
+ logger3.warn(`[delegate] stopTask killAll failed`, {
802
1449
  taskId,
803
1450
  error: err instanceof Error ? err.message : String(err)
804
1451
  });
@@ -852,7 +1499,7 @@ Use these as guidance to improve task execution efficiency.`;
852
1499
  stoppedTaskIds.push(taskId);
853
1500
  }
854
1501
  } catch (err) {
855
- logger.warn(`[BackgroundTaskManager] stopAllForParent failed for ${taskId}: ${err instanceof Error ? err.message : String(err)}`);
1502
+ logger3.warn(`[BackgroundTaskManager] stopAllForParent failed for ${taskId}: ${err instanceof Error ? err.message : String(err)}`);
856
1503
  }
857
1504
  }
858
1505
  }
@@ -863,12 +1510,12 @@ Use these as guidance to improve task execution efficiency.`;
863
1510
  try {
864
1511
  await this.processRegistries.get(taskId)?.killAll();
865
1512
  } catch (err) {
866
- logger.warn(`[BackgroundTaskManager] forceStopAllAcrossParents killAll failed for ${taskId}: ${err instanceof Error ? err.message : String(err)}`);
1513
+ logger3.warn(`[BackgroundTaskManager] forceStopAllAcrossParents killAll failed for ${taskId}: ${err instanceof Error ? err.message : String(err)}`);
867
1514
  }
868
1515
  try {
869
1516
  await this.propagateStopToWorkflowRuns(taskId, "force stop all");
870
1517
  } catch (err) {
871
- logger.warn(`[BackgroundTaskManager] forceStopAllAcrossParents propagateStopToWorkflowRuns failed for ${taskId}: ${err instanceof Error ? err.message : String(err)}`);
1518
+ logger3.warn(`[BackgroundTaskManager] forceStopAllAcrossParents propagateStopToWorkflowRuns failed for ${taskId}: ${err instanceof Error ? err.message : String(err)}`);
872
1519
  }
873
1520
  let justMarkedStopped = false;
874
1521
  if (task.status === "running" || task.status === "pending") {
@@ -879,7 +1526,7 @@ Use these as guidance to improve task execution efficiency.`;
879
1526
  task.abortController?.abort();
880
1527
  justMarkedStopped = true;
881
1528
  } catch (err) {
882
- logger.warn(`[BackgroundTaskManager] forceStopAllAcrossParents mark stopped failed for ${taskId}: ${err instanceof Error ? err.message : String(err)}`);
1529
+ logger3.warn(`[BackgroundTaskManager] forceStopAllAcrossParents mark stopped failed for ${taskId}: ${err instanceof Error ? err.message : String(err)}`);
883
1530
  }
884
1531
  }
885
1532
  if (justMarkedStopped) {
@@ -903,14 +1550,14 @@ Use these as guidance to improve task execution efficiency.`;
903
1550
  async dispose() {
904
1551
  for (const [taskId, controller] of this.abortControllers) {
905
1552
  controller.abort();
906
- logger.debug(`[BackgroundTaskManager] Aborted task: ${taskId}`);
1553
+ logger3.debug(`[BackgroundTaskManager] Aborted task: ${taskId}`);
907
1554
  }
908
1555
  this.abortControllers.clear();
909
1556
  for (const [, registry] of this.processRegistries) {
910
1557
  try {
911
1558
  await registry.killAll();
912
1559
  } catch (err) {
913
- logger.warn(`[BackgroundTaskManager] dispose registry.killAll failed`, {
1560
+ logger3.warn(`[BackgroundTaskManager] dispose registry.killAll failed`, {
914
1561
  error: err instanceof Error ? err.message : String(err)
915
1562
  });
916
1563
  }
@@ -921,7 +1568,7 @@ Use these as guidance to improve task execution efficiency.`;
921
1568
  }
922
1569
  this.workflowRunRegistries.clear();
923
1570
  this.tasks.clear();
924
- logger.info(`[BackgroundTaskManager] Disposed`);
1571
+ logger3.info(`[BackgroundTaskManager] Disposed`);
925
1572
  }
926
1573
  }
927
1574
  __legacyDecorateClassTS([
@@ -1035,7 +1682,7 @@ No need to poll — just wait for the notification and use \`task_get\` with the
1035
1682
  const params = DelegateToolParameters.parse(args);
1036
1683
  const { description, prompt, subagent_type = "general", timeout, reason, similar_task_ids } = params;
1037
1684
  const parentSessionId = ctx.session_id || "default";
1038
- logger.info(`[delegate_task] Called: description=${description}, subagent_type=${subagent_type}`);
1685
+ logger3.info(`[delegate_task] Called: description=${description}, subagent_type=${subagent_type}`);
1039
1686
  const registry2 = getAgentRegistry(taskComponent);
1040
1687
  if (!isKnownSubagentType(subagent_type, registry2)) {
1041
1688
  return {
@@ -1083,7 +1730,7 @@ async function handleBackgroundTask(backgroundTaskManager, parentSessionId, desc
1083
1730
  originalPrompt: prompt,
1084
1731
  description
1085
1732
  });
1086
- logger.info(`[delegate_task] Routing workflow subagent '${subagentType}' → entry-agent '${entryAgent}'`);
1733
+ logger3.info(`[delegate_task] Routing workflow subagent '${subagentType}' → entry-agent '${entryAgent}'`);
1087
1734
  }
1088
1735
  const { taskId: bgProcessId, subSessionId } = await backgroundTaskManager.createTask({
1089
1736
  parentSessionId,