@synergenius/flow-weaver-pack-weaver 0.9.152 → 0.9.154

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 (104) hide show
  1. package/dist/ai-chat-provider.js +4 -4
  2. package/dist/ai-chat-provider.js.map +1 -1
  3. package/dist/bot/ai-client.d.ts +30 -0
  4. package/dist/bot/ai-client.d.ts.map +1 -1
  5. package/dist/bot/ai-client.js +37 -0
  6. package/dist/bot/ai-client.js.map +1 -1
  7. package/dist/bot/behavior-defaults.d.ts.map +1 -1
  8. package/dist/bot/behavior-defaults.js +7 -2
  9. package/dist/bot/behavior-defaults.js.map +1 -1
  10. package/dist/bot/capability-registry.d.ts.map +1 -1
  11. package/dist/bot/capability-registry.js +46 -33
  12. package/dist/bot/capability-registry.js.map +1 -1
  13. package/dist/bot/file-validator.d.ts +7 -0
  14. package/dist/bot/file-validator.d.ts.map +1 -1
  15. package/dist/bot/file-validator.js +76 -0
  16. package/dist/bot/file-validator.js.map +1 -1
  17. package/dist/bot/instance-manager.d.ts +22 -7
  18. package/dist/bot/instance-manager.d.ts.map +1 -1
  19. package/dist/bot/instance-manager.js +69 -7
  20. package/dist/bot/instance-manager.js.map +1 -1
  21. package/dist/bot/orchestrator.d.ts +11 -9
  22. package/dist/bot/orchestrator.d.ts.map +1 -1
  23. package/dist/bot/orchestrator.js +56 -107
  24. package/dist/bot/orchestrator.js.map +1 -1
  25. package/dist/bot/runner.d.ts +29 -0
  26. package/dist/bot/runner.d.ts.map +1 -1
  27. package/dist/bot/runner.js +114 -73
  28. package/dist/bot/runner.js.map +1 -1
  29. package/dist/bot/step-executor.d.ts.map +1 -1
  30. package/dist/bot/step-executor.js +28 -9
  31. package/dist/bot/step-executor.js.map +1 -1
  32. package/dist/bot/swarm-controller.d.ts +7 -6
  33. package/dist/bot/swarm-controller.d.ts.map +1 -1
  34. package/dist/bot/swarm-controller.js +64 -74
  35. package/dist/bot/swarm-controller.js.map +1 -1
  36. package/dist/bot/system-prompt.d.ts.map +1 -1
  37. package/dist/bot/system-prompt.js +2 -0
  38. package/dist/bot/system-prompt.js.map +1 -1
  39. package/dist/bot/task-types.d.ts +1 -0
  40. package/dist/bot/task-types.d.ts.map +1 -1
  41. package/dist/bot/weaver-tools.d.ts +1 -1
  42. package/dist/bot/weaver-tools.d.ts.map +1 -1
  43. package/dist/bot/weaver-tools.js +12 -1
  44. package/dist/bot/weaver-tools.js.map +1 -1
  45. package/dist/node-types/agent-execute.js +2 -2
  46. package/dist/node-types/agent-execute.js.map +1 -1
  47. package/dist/node-types/bot-report.d.ts.map +1 -1
  48. package/dist/node-types/bot-report.js +5 -2
  49. package/dist/node-types/bot-report.js.map +1 -1
  50. package/dist/node-types/build-context.d.ts.map +1 -1
  51. package/dist/node-types/build-context.js +13 -1
  52. package/dist/node-types/build-context.js.map +1 -1
  53. package/dist/node-types/exec-validate-retry.d.ts +3 -3
  54. package/dist/node-types/exec-validate-retry.d.ts.map +1 -1
  55. package/dist/node-types/exec-validate-retry.js +13 -184
  56. package/dist/node-types/exec-validate-retry.js.map +1 -1
  57. package/dist/node-types/load-config.d.ts +1 -0
  58. package/dist/node-types/load-config.d.ts.map +1 -1
  59. package/dist/node-types/load-config.js +1 -0
  60. package/dist/node-types/load-config.js.map +1 -1
  61. package/dist/node-types/plan-task.d.ts +7 -5
  62. package/dist/node-types/plan-task.d.ts.map +1 -1
  63. package/dist/node-types/plan-task.js +282 -83
  64. package/dist/node-types/plan-task.js.map +1 -1
  65. package/dist/ui/bot-panel.js +1 -1
  66. package/dist/ui/capability-editor.js +46 -33
  67. package/dist/ui/chat-task-result.js +7 -7
  68. package/dist/ui/profile-editor.js +44 -31
  69. package/dist/ui/swarm-dashboard.js +80 -47
  70. package/dist/ui/task-detail-view.js +31 -11
  71. package/dist/ui/task-editor.js +1 -1
  72. package/dist/ui/task-pool-list.js +1 -1
  73. package/dist/workflows/weaver-bot.d.ts +5 -4
  74. package/dist/workflows/weaver-bot.d.ts.map +1 -1
  75. package/dist/workflows/weaver-bot.js +8 -7
  76. package/dist/workflows/weaver-bot.js.map +1 -1
  77. package/flowweaver.manifest.json +1 -1
  78. package/package.json +1 -1
  79. package/src/ai-chat-provider.ts +4 -4
  80. package/src/bot/ai-client.ts +65 -0
  81. package/src/bot/behavior-defaults.ts +5 -2
  82. package/src/bot/capability-registry.ts +46 -33
  83. package/src/bot/file-validator.ts +97 -0
  84. package/src/bot/instance-manager.ts +77 -7
  85. package/src/bot/orchestrator.ts +63 -126
  86. package/src/bot/runner.ts +124 -70
  87. package/src/bot/step-executor.ts +30 -9
  88. package/src/bot/swarm-controller.ts +65 -76
  89. package/src/bot/system-prompt.ts +2 -0
  90. package/src/bot/task-types.ts +1 -0
  91. package/src/bot/weaver-tools.ts +14 -1
  92. package/src/node-types/agent-execute.ts +2 -2
  93. package/src/node-types/bot-report.ts +5 -2
  94. package/src/node-types/build-context.ts +13 -1
  95. package/src/node-types/exec-validate-retry.ts +14 -203
  96. package/src/node-types/load-config.ts +1 -0
  97. package/src/node-types/plan-task.ts +313 -88
  98. package/src/ui/bot-panel.tsx +1 -1
  99. package/src/ui/chat-task-result.tsx +10 -8
  100. package/src/ui/swarm-dashboard.tsx +4 -4
  101. package/src/ui/task-detail-view.tsx +35 -12
  102. package/src/ui/task-editor.tsx +2 -2
  103. package/src/ui/task-pool-list.tsx +2 -2
  104. package/src/workflows/weaver-bot.ts +8 -7
@@ -1,9 +1,10 @@
1
1
  /**
2
2
  * SwarmController — multi-bot orchestrator for the Weaver Swarm.
3
3
  *
4
- * Uses a centralized orchestrator dispatch loop instead of per-bot pull loops.
5
- * The Orchestrator routes pending tasks to the best available bot instance,
6
- * the InstanceManager handles scaling, and the ProfileStore persists profiles.
4
+ * Uses a dynamic worker pool: N generic worker slots (controlled by maxConcurrent).
5
+ * Any worker can run any profile's task. The profile is loaded per-task, not per-slot.
6
+ *
7
+ * Worker naming: `worker-0`, `worker-1`, `worker-2` (no per-profile instances).
7
8
  *
8
9
  * Singleton per workspace via `SwarmController.getInstance(projectDir)`.
9
10
  */
@@ -42,7 +43,7 @@ export interface SwarmState {
42
43
  status: 'idle' | 'running' | 'paused' | 'stopping';
43
44
  startedAt?: string;
44
45
 
45
- /** Active bot instances managed by InstanceManager. */
46
+ /** Active worker slots (generic pool). Keyed by instanceId (worker-0, worker-1, ...). */
46
47
  instances: Record<string, BotInstance>;
47
48
  maxConcurrent: number;
48
49
 
@@ -105,7 +106,7 @@ export class SwarmController {
105
106
  private dispatchAbort: AbortController | null = null;
106
107
  /** Promise for the dispatch loop (resolved when the loop exits). */
107
108
  private dispatchLoopPromise: Promise<void> | null = null;
108
- /** Promises for currently executing tasks (instanceId -> Promise). */
109
+ /** Promises for currently executing tasks (workerId -> Promise). */
109
110
  private executionPromises = new Map<string, Promise<void>>();
110
111
  /** Track which promises have settled (resolved or rejected). */
111
112
  private settledPromises = new Set<string>();
@@ -189,23 +190,11 @@ export class SwarmController {
189
190
  if (bot) this.profileBotMap.set(profile.id, bot);
190
191
  }
191
192
 
192
- // Stop any existing instances
193
+ // Stop any existing workers
193
194
  this.instanceManager.stopAll();
194
195
 
195
- // Spawn initial instances per profile (up to maxConcurrent total)
196
- let totalSpawned = 0;
197
- for (const profile of profiles) {
198
- if (totalSpawned >= this.state.maxConcurrent) break;
199
-
200
- // Spawn at least minInstances, or 1 if minInstances is 0
201
- const toSpawn = Math.max(profile.minInstances, 1);
202
- const clamped = Math.min(toSpawn, this.state.maxConcurrent - totalSpawned);
203
-
204
- for (let i = 0; i < clamped; i++) {
205
- this.instanceManager.spawn(profile);
206
- totalSpawned++;
207
- }
208
- }
196
+ // Spawn generic worker pool based on maxConcurrent
197
+ this.instanceManager.spawnPool(this.state.maxConcurrent);
209
198
 
210
199
  // Populate state fields
211
200
  this._syncInstancesState();
@@ -259,7 +248,7 @@ export class SwarmController {
259
248
  const execPromises = Array.from(this.executionPromises.values());
260
249
  await Promise.allSettled(execPromises);
261
250
 
262
- // Stop all instances
251
+ // Stop all workers
263
252
  this.instanceManager.stopAll();
264
253
 
265
254
  // Update state
@@ -277,7 +266,7 @@ export class SwarmController {
277
266
  }
278
267
 
279
268
  getStatus(): SwarmState {
280
- // Sync instances from InstanceManager before returning
269
+ // Sync workers from InstanceManager before returning
281
270
  this._syncInstancesState();
282
271
  return { ...this.state, instances: { ...this.state.instances } };
283
272
  }
@@ -298,11 +287,11 @@ export class SwarmController {
298
287
  // -----------------------------------------------------------------------
299
288
 
300
289
  async recover(): Promise<void> {
301
- // Reset all instances to idle
302
- const allInstances = this.instanceManager.listAll();
303
- for (const inst of allInstances) {
304
- if (inst.status === 'executing') {
305
- this.instanceManager.markIdle(inst.instanceId, false);
290
+ // Reset all workers to idle
291
+ const allWorkers = this.instanceManager.listAll();
292
+ for (const w of allWorkers) {
293
+ if (w.status === 'executing') {
294
+ this.instanceManager.markIdle(w.instanceId, false);
306
295
  }
307
296
  }
308
297
 
@@ -332,11 +321,11 @@ export class SwarmController {
332
321
  // Token/cost recording
333
322
  // -----------------------------------------------------------------------
334
323
 
335
- recordTokenUsage(instanceId: string, _taskId: string, tokensUsed: number, cost: number): void {
336
- // Update instance in InstanceManager
324
+ recordTokenUsage(workerId: string, _taskId: string, tokensUsed: number, cost: number): void {
325
+ // Update worker in InstanceManager
337
326
  try {
338
- this.instanceManager.recordUsage(instanceId, tokensUsed, cost);
339
- } catch { /* instance may not exist if stopped */ }
327
+ this.instanceManager.recordUsage(workerId, tokensUsed, cost);
328
+ } catch { /* worker may not exist if stopped */ }
340
329
 
341
330
  // Update session budgets
342
331
  this.state.budgets.session.usedTokens += tokensUsed;
@@ -355,7 +344,7 @@ export class SwarmController {
355
344
  this.eventLog.emit({
356
345
  type: 'budget-updated',
357
346
  timestamp: Date.now(),
358
- data: { botId: instanceId, tokensUsed, cost },
347
+ data: { botId: workerId, tokensUsed, cost },
359
348
  });
360
349
  }
361
350
 
@@ -423,10 +412,10 @@ export class SwarmController {
423
412
  break;
424
413
  }
425
414
 
426
- // Check instance-level steering
427
- const allInstances = this.instanceManager.listAll();
428
- for (const inst of allInstances) {
429
- await this._checkSteering(inst.instanceId);
415
+ // Check worker-level steering
416
+ const allWorkers = this.instanceManager.listAll();
417
+ for (const w of allWorkers) {
418
+ await this._checkSteering(w.instanceId);
430
419
  }
431
420
 
432
421
  // Route unassigned tasks: top-level → orchestrator, subtasks → developer (fallback)
@@ -478,7 +467,7 @@ export class SwarmController {
478
467
 
479
468
  // Build orchestrator input
480
469
  const profiles = this.profileStore.list();
481
- const instances = this.instanceManager.listAll();
470
+ const workers = this.instanceManager.listAll();
482
471
  const { workspace, session } = this.state.budgets;
483
472
 
484
473
  const budgetRemaining = {
@@ -502,11 +491,11 @@ export class SwarmController {
502
491
  },
503
492
  })),
504
493
  profiles,
505
- instances,
494
+ instances: workers,
506
495
  budgetRemaining,
507
496
  };
508
497
 
509
- // Route via orchestrator (node functions called directly to avoid circular import)
498
+ // Route via orchestrator
510
499
  const output = await this.orchestrator.route(input);
511
500
 
512
501
  // Log routing decisions to event log for dashboard visibility
@@ -518,20 +507,14 @@ export class SwarmController {
518
507
  });
519
508
  }
520
509
 
521
- // Apply scale actions
522
- for (const sa of output.scaleActions) {
523
- const profile = profiles.find((p) => p.id === sa.profileId);
524
- if (profile) {
525
- this.instanceManager.scaleTo(profile, sa.targetInstances);
526
- }
527
- }
510
+ // No scale actions in pool model — pool size is fixed at maxConcurrent
528
511
 
529
512
  // Build file conflict map: which files are being modified by currently executing tasks
530
513
  const activeFiles = new Map<string, string>(); // filepath → taskId
531
- for (const [instId] of this.executionPromises) {
532
- const inst = this.instanceManager.listAll().find(i => i.instanceId === instId);
533
- if (inst?.currentTaskId) {
534
- const runningTask = await this.taskStore.get(inst.currentTaskId);
514
+ for (const [workerId] of this.executionPromises) {
515
+ const w = this.instanceManager.listAll().find(i => i.instanceId === workerId);
516
+ if (w?.currentTaskId) {
517
+ const runningTask = await this.taskStore.get(w.currentTaskId);
535
518
  if (runningTask?.context.files) {
536
519
  for (const f of runningTask.context.files) {
537
520
  activeFiles.set(f, runningTask.id);
@@ -564,9 +547,9 @@ export class SwarmController {
564
547
  // Assign task in TaskStore
565
548
  await this.taskStore.assignToInstance(assignment.taskId, assignment.instanceId, assignment.profileId);
566
549
 
567
- // Mark instance as executing and store runId on task for live streaming
550
+ // Mark worker as executing with the task's profileId
568
551
  const runId = RunStore.newId();
569
- this.instanceManager.markExecuting(assignment.instanceId, assignment.taskId, runId);
552
+ this.instanceManager.markExecuting(assignment.instanceId, assignment.taskId, runId, assignment.profileId);
570
553
  await this.taskStore.update(assignment.taskId, { currentRunId: runId });
571
554
 
572
555
  // Sync state for dashboard
@@ -581,10 +564,10 @@ export class SwarmController {
581
564
 
582
565
  // Spawn execution (non-blocking).
583
566
  // Track settlement so the cleanup loop can remove finished promises.
584
- const instanceIdForClosure = assignment.instanceId;
567
+ const workerIdForClosure = assignment.instanceId;
585
568
  const execPromise = this._executeTask(assignment.instanceId, assignment.taskId, runId, bot, profile)
586
569
  .catch(() => { /* errors handled inside _executeTask */ })
587
- .finally(() => { this.settledPromises.add(instanceIdForClosure); });
570
+ .finally(() => { this.settledPromises.add(workerIdForClosure); });
588
571
  this.executionPromises.set(assignment.instanceId, execPromise);
589
572
  } catch {
590
573
  // Assignment failed (task may have been grabbed by another cycle) — skip
@@ -605,10 +588,10 @@ export class SwarmController {
605
588
  // Clean up completed execution promises.
606
589
  // Use a status-tracking wrapper instead of Promise.race with an
607
590
  // immediately-resolved promise (which always returns 'pending').
608
- for (const [instanceId] of this.executionPromises) {
609
- if (this.settledPromises.has(instanceId)) {
610
- this.executionPromises.delete(instanceId);
611
- this.settledPromises.delete(instanceId);
591
+ for (const [workerId] of this.executionPromises) {
592
+ if (this.settledPromises.has(workerId)) {
593
+ this.executionPromises.delete(workerId);
594
+ this.settledPromises.delete(workerId);
612
595
  }
613
596
  }
614
597
 
@@ -634,7 +617,7 @@ export class SwarmController {
634
617
  // -----------------------------------------------------------------------
635
618
 
636
619
  private async _executeTask(
637
- instanceId: string,
620
+ workerId: string,
638
621
  taskId: string,
639
622
  runId: string,
640
623
  bot: BotRegistration,
@@ -655,10 +638,15 @@ export class SwarmController {
655
638
  // Wrap in the JSON envelope that receive-task and downstream nodes expect.
656
639
  // autoApprove: true because the user consented by starting the swarm.
657
640
  const taskJson = JSON.stringify({
641
+ id: task.id,
642
+ parentId: task.parentId,
658
643
  instruction: prompt,
659
644
  mode: task.context.files.length > 0 ? 'modify' : 'create',
660
645
  targets: task.context.files.length > 0 ? task.context.files : undefined,
661
646
  options: { autoApprove: true },
647
+ attempt: task.attempt,
648
+ lastError: task.context.lastError,
649
+ runSummaries: task.context.runSummaries,
662
650
  });
663
651
 
664
652
  // Build behavior config from profile preferences, adjusted for task complexity.
@@ -683,7 +671,7 @@ export class SwarmController {
683
671
  runWorkflow(workflowPath, {
684
672
  runId,
685
673
  taskId,
686
- botId: instanceId,
674
+ botId: workerId,
687
675
  config: { provider: 'auto' },
688
676
  params: { taskJson, projectDir: this.projectDir, behaviorJson },
689
677
  eventLog: runEventLog,
@@ -708,9 +696,10 @@ export class SwarmController {
708
696
 
709
697
  const runSummary: RunSummary = {
710
698
  runId,
711
- botId: instanceId,
699
+ botId: workerId,
712
700
  outcome: result.success ? 'success' : 'failed',
713
701
  summary: result.summary || (result.success ? 'Completed' : 'Failed'),
702
+ report: result.report,
714
703
  filesModified: [],
715
704
  error: result.success ? undefined : (result.summary || 'Unknown error'),
716
705
  durationMs,
@@ -723,7 +712,7 @@ export class SwarmController {
723
712
  await this.taskStore.release(taskId, releaseStatus, runSummary);
724
713
 
725
714
  // Record token usage
726
- this.recordTokenUsage(instanceId, taskId, tokensUsed, costUsed);
715
+ this.recordTokenUsage(workerId, taskId, tokensUsed, costUsed);
727
716
 
728
717
  // Update stats
729
718
  if (result.success) {
@@ -732,16 +721,16 @@ export class SwarmController {
732
721
  this.state.tasksFailed += 1;
733
722
  }
734
723
 
735
- // Mark instance as idle
724
+ // Mark worker as idle (clears profile association)
736
725
  try {
737
- this.instanceManager.markIdle(instanceId, result.success);
738
- } catch { /* instance may have been stopped */ }
726
+ this.instanceManager.markIdle(workerId, result.success);
727
+ } catch { /* worker may have been stopped */ }
739
728
 
740
729
  // Emit swarm-level task event
741
730
  this.eventLog.emit({
742
731
  type: result.success ? 'task-done' : 'task-failed',
743
732
  timestamp: Date.now(),
744
- data: { botId: instanceId, taskId, outcome: runSummary.outcome },
733
+ data: { botId: workerId, taskId, outcome: runSummary.outcome },
745
734
  });
746
735
 
747
736
  // Emit hierarchy-scoped event so sibling tasks can see what happened
@@ -755,7 +744,7 @@ export class SwarmController {
755
744
  data: {
756
745
  summary: runSummary.summary,
757
746
  filesModified: runSummary.filesModified,
758
- botId: instanceId,
747
+ botId: workerId,
759
748
  attempt: task.attempt,
760
749
  },
761
750
  });
@@ -802,8 +791,8 @@ export class SwarmController {
802
791
  // Steering
803
792
  // -----------------------------------------------------------------------
804
793
 
805
- private async _checkSteering(instanceId: string): Promise<void> {
806
- const steerPath = path.join(this.weaverDir, `steer-${instanceId}.json`);
794
+ private async _checkSteering(workerId: string): Promise<void> {
795
+ const steerPath = path.join(this.weaverDir, `steer-${workerId}.json`);
807
796
  try {
808
797
  if (!fs.existsSync(steerPath)) return;
809
798
  const raw = fs.readFileSync(steerPath, 'utf-8');
@@ -812,28 +801,28 @@ export class SwarmController {
812
801
 
813
802
  switch (command.command) {
814
803
  case 'pause':
815
- this.eventLog.emit({ type: 'bot-paused', timestamp: Date.now(), data: { botId: instanceId } });
804
+ this.eventLog.emit({ type: 'bot-paused', timestamp: Date.now(), data: { botId: workerId } });
816
805
  break;
817
806
  case 'resume':
818
807
  break;
819
808
  case 'cancel':
820
- this.instanceManager.stop(instanceId);
809
+ this.instanceManager.stop(workerId);
821
810
  break;
822
811
  }
823
812
  } catch { /* steer file read error — skip */ }
824
813
  }
825
814
 
826
815
  // -----------------------------------------------------------------------
827
- // State sync — instances field
816
+ // State sync — instances field (workers mapped to instances for API compat)
828
817
  // -----------------------------------------------------------------------
829
818
 
830
819
  private _syncInstancesState(): void {
831
- const allInstances = this.instanceManager.listAll();
820
+ const allWorkers = this.instanceManager.listAll();
832
821
 
833
- // Build instances record
822
+ // Build instances record from worker pool
834
823
  this.state.instances = {};
835
- for (const inst of allInstances) {
836
- this.state.instances[inst.instanceId] = inst;
824
+ for (const w of allWorkers) {
825
+ this.state.instances[w.instanceId] = w;
837
826
  }
838
827
  }
839
828
 
@@ -208,6 +208,8 @@ USE TOOLS to complete tasks. Do NOT describe what you would do — actually do i
208
208
 
209
209
  ### Knowledge tools
210
210
  - learn(key, value), recall(query)
211
+ - When you discover project facts (test runner, build tool, conventions, common patterns), use learn() to persist them for future runs
212
+ - Previous knowledge is auto-loaded into your context — avoid re-discovering what's already known
211
213
 
212
214
  ### Web & interaction tools
213
215
  - web_fetch(url), ask_user(question)
@@ -8,6 +8,7 @@ export interface RunSummary {
8
8
  botId: string;
9
9
  outcome: 'success' | 'failed' | 'error';
10
10
  summary: string;
11
+ report?: string;
11
12
  filesModified: string[];
12
13
  error?: string;
13
14
  durationMs: number;
@@ -30,9 +30,15 @@ const OPERATION_MAP: Record<string, string> = {
30
30
  */
31
31
  export function createWeaverExecutor(projectDir: string) {
32
32
  return async (
33
- name: string,
33
+ rawName: string,
34
34
  args: Record<string, unknown>,
35
35
  ): Promise<{ result: string; isError: boolean }> => {
36
+ // Strip MCP server prefix from tool names.
37
+ // The Claude CLI registers MCP tools as `mcp__<server>__<tool>` and the
38
+ // API streams back tool_use events with the prefixed name. The agent loop
39
+ // passes this name to the executor, but our switch cases use unprefixed names.
40
+ const name = rawName.replace(/^mcp__[a-zA-Z0-9_-]+__/, '');
41
+
36
42
  // Handle new tools that bypass step-executor
37
43
  switch (name) {
38
44
  case 'web_fetch': {
@@ -179,6 +185,13 @@ export function createWeaverExecutor(projectDir: string) {
179
185
  break;
180
186
  }
181
187
 
188
+ // Ignore Claude Code built-in tools that leak through when disallowed list is incomplete.
189
+ // These are no-ops — the bot should use the MCP bridge tools instead.
190
+ const IGNORED_BUILTINS = new Set(['TodoWrite', 'TodoRead', 'Agent', 'WebFetch', 'WebSearch', 'MultiTool', 'Skill']);
191
+ if (IGNORED_BUILTINS.has(name) || IGNORED_BUILTINS.has(rawName)) {
192
+ return { result: `Tool "${name}" is not available. Use the provided tools instead.`, isError: true };
193
+ }
194
+
182
195
  // Existing step-executor-based tools
183
196
  const operation = OPERATION_MAP[name];
184
197
  if (!operation) {
@@ -153,8 +153,8 @@ export async function weaverAgentExecute(
153
153
  }
154
154
 
155
155
  const taskPrompt = task.instruction.startsWith('## Task:')
156
- ? `${task.instruction}\n\nProject directory: ${projectDir}`
157
- : `Task: ${task.instruction}\nProject directory: ${projectDir}\n${task.targets ? 'Target files: ' + task.targets.join(', ') : ''}`;
156
+ ? `${task.instruction}`
157
+ : `Task: ${task.instruction}\n${task.targets ? 'Target files: ' + task.targets.join(', ') : ''}`;
158
158
 
159
159
  // Create renderer — single source of all terminal output
160
160
  const { TerminalRenderer } = await import('../bot/terminal-renderer.js');
@@ -27,7 +27,10 @@ export async function weaverBotReport(
27
27
  abortCtx?: string,
28
28
  failCtx?: string,
29
29
  ): Promise<{ onSuccess: boolean; onFailure: boolean; summary: string; report: string; reportJson: string }> {
30
- const ctxStr = mainCtx ?? readCtx ?? abortCtx ?? failCtx;
30
+ // Pick the first ctx with meaningful content (not just '{}').
31
+ // An empty merge result '{}' must not shadow a real ctx from failCtx/abortCtx.
32
+ const candidates = [mainCtx, readCtx, abortCtx, failCtx];
33
+ const ctxStr = candidates.find(c => c != null && c !== '{}' && c.length > 2) ?? candidates.find(c => c != null);
31
34
 
32
35
  if (!execute) {
33
36
  const reportObj = { task: {}, path: 'unknown', result: null, filesModified: [], gitResult: null, timestamp: Date.now() };
@@ -96,7 +99,7 @@ export async function weaverBotReport(
96
99
  } catch { /* ignore parse errors */ }
97
100
  }
98
101
 
99
- const success = result?.success !== false && pathName !== 'abort' && reviewPassed;
102
+ const success = result?.success !== false && pathName !== 'abort' && pathName !== 'failed' && context.allValid !== false && reviewPassed;
100
103
 
101
104
  if (result) {
102
105
  parts.push(`Outcome: ${result.outcome ?? (success ? 'completed' : 'failed')}`);
@@ -45,6 +45,17 @@ export function weaverBuildContext(ctx: string): { ctx: string } {
45
45
  }
46
46
  } catch { /* non-fatal — memory is best-effort */ }
47
47
 
48
+ // Auto-recall learned knowledge from previous bot runs
49
+ try {
50
+ const { KnowledgeStore } = require('../bot/knowledge-store.js') as typeof import('../bot/knowledge-store.js');
51
+ const knowledge = new KnowledgeStore(projectDir);
52
+ const entries = knowledge.list();
53
+ if (entries.length > 0) {
54
+ const knowledgeLines = entries.map((e: { key: string; value: string }) => `- **${e.key}**: ${e.value}`);
55
+ sections.push(`## Learned Knowledge\n\nFacts discovered by previous runs — use these instead of re-discovering:\n${knowledgeLines.join('\n')}`);
56
+ }
57
+ } catch { /* non-fatal — knowledge recall is best-effort */ }
58
+
48
59
  // Include sibling context from hierarchy events (previous task completions in same hierarchy)
49
60
  try {
50
61
  const parsedTask = JSON.parse(context.taskJson!) as { parentId?: string };
@@ -92,10 +103,11 @@ function buildModifyContext(projectDir: string, targets: string[]): string[] {
92
103
  const includedFiles = new Set<string>();
93
104
  for (const target of targets) {
94
105
  const filePath = path.isAbsolute(target) ? target : path.resolve(projectDir, target);
106
+ const displayTarget = path.isAbsolute(target) ? path.relative(projectDir, target) : target;
95
107
  try {
96
108
  if (!fs.existsSync(filePath)) continue;
97
109
  const source = fs.readFileSync(filePath, 'utf-8');
98
- sections.push(`## Target: ${target}\n\n\`\`\`typescript\n${source}\n\`\`\``);
110
+ sections.push(`## Target: ${displayTarget}\n\n\`\`\`typescript\n${source}\n\`\`\``);
99
111
  includedFiles.add(filePath);
100
112
 
101
113
  // Extract import paths to find referenced node type files