@synergenius/flow-weaver-pack-weaver 0.9.62 → 0.9.78
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ai-chat-provider.d.ts +12 -0
- package/dist/ai-chat-provider.d.ts.map +1 -1
- package/dist/ai-chat-provider.js +173 -19
- package/dist/ai-chat-provider.js.map +1 -1
- package/dist/bot/agent-loop.d.ts +20 -0
- package/dist/bot/agent-loop.d.ts.map +1 -0
- package/dist/bot/agent-loop.js +331 -0
- package/dist/bot/agent-loop.js.map +1 -0
- package/dist/bot/ai-router.d.ts +19 -0
- package/dist/bot/ai-router.d.ts.map +1 -0
- package/dist/bot/ai-router.js +104 -0
- package/dist/bot/ai-router.js.map +1 -0
- package/dist/bot/bot-registry.js +2 -2
- package/dist/bot/bot-registry.js.map +1 -1
- package/dist/bot/conversation-store.d.ts +1 -0
- package/dist/bot/conversation-store.d.ts.map +1 -1
- package/dist/bot/conversation-store.js.map +1 -1
- package/dist/bot/improve-loop.js.map +1 -1
- package/dist/bot/instance-manager.d.ts +31 -0
- package/dist/bot/instance-manager.d.ts.map +1 -0
- package/dist/bot/instance-manager.js +115 -0
- package/dist/bot/instance-manager.js.map +1 -0
- package/dist/bot/orchestrator.d.ts +36 -0
- package/dist/bot/orchestrator.d.ts.map +1 -0
- package/dist/bot/orchestrator.js +176 -0
- package/dist/bot/orchestrator.js.map +1 -0
- package/dist/bot/profile-store.d.ts +36 -0
- package/dist/bot/profile-store.d.ts.map +1 -0
- package/dist/bot/profile-store.js +208 -0
- package/dist/bot/profile-store.js.map +1 -0
- package/dist/bot/profile-types.d.ts +126 -0
- package/dist/bot/profile-types.d.ts.map +1 -0
- package/dist/bot/profile-types.js +7 -0
- package/dist/bot/profile-types.js.map +1 -0
- package/dist/bot/session-state.d.ts +25 -0
- package/dist/bot/session-state.d.ts.map +1 -0
- package/dist/bot/session-state.js +110 -0
- package/dist/bot/session-state.js.map +1 -0
- package/dist/bot/swarm-controller.d.ts +37 -21
- package/dist/bot/swarm-controller.d.ts.map +1 -1
- package/dist/bot/swarm-controller.js +344 -163
- package/dist/bot/swarm-controller.js.map +1 -1
- package/dist/bot/task-prompt-builder.d.ts +2 -1
- package/dist/bot/task-prompt-builder.d.ts.map +1 -1
- package/dist/bot/task-prompt-builder.js +33 -10
- package/dist/bot/task-prompt-builder.js.map +1 -1
- package/dist/bot/task-queue.d.ts +46 -0
- package/dist/bot/task-queue.d.ts.map +1 -0
- package/dist/bot/task-queue.js +237 -0
- package/dist/bot/task-queue.js.map +1 -0
- package/dist/bot/task-store.d.ts +1 -6
- package/dist/bot/task-store.d.ts.map +1 -1
- package/dist/bot/task-store.js +27 -78
- package/dist/bot/task-store.js.map +1 -1
- package/dist/bot/task-types.d.ts +8 -4
- package/dist/bot/task-types.d.ts.map +1 -1
- package/dist/cli-handlers.d.ts.map +1 -1
- package/dist/cli-handlers.js +2 -3
- package/dist/cli-handlers.js.map +1 -1
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +749 -0
- package/dist/cli.js.map +1 -0
- package/dist/docs/docs/weaver-bot-usage.md +35 -18
- package/dist/docs/docs/weaver-config.md +20 -0
- package/dist/docs/docs/weaver-task-queue.md +31 -19
- package/dist/docs/weaver-config.md +15 -9
- package/dist/mcp-tools.d.ts +17 -0
- package/dist/mcp-tools.d.ts.map +1 -1
- package/dist/mcp-tools.js +98 -232
- package/dist/mcp-tools.js.map +1 -1
- package/dist/node-types/orchestrator-dispatch.d.ts +17 -0
- package/dist/node-types/orchestrator-dispatch.d.ts.map +1 -0
- package/dist/node-types/orchestrator-dispatch.js +63 -0
- package/dist/node-types/orchestrator-dispatch.js.map +1 -0
- package/dist/node-types/orchestrator-load-state.d.ts +16 -0
- package/dist/node-types/orchestrator-load-state.d.ts.map +1 -0
- package/dist/node-types/orchestrator-load-state.js +60 -0
- package/dist/node-types/orchestrator-load-state.js.map +1 -0
- package/dist/node-types/orchestrator-route.d.ts +16 -0
- package/dist/node-types/orchestrator-route.d.ts.map +1 -0
- package/dist/node-types/orchestrator-route.js +28 -0
- package/dist/node-types/orchestrator-route.js.map +1 -0
- package/dist/node-types/receive-task.d.ts +2 -3
- package/dist/node-types/receive-task.d.ts.map +1 -1
- package/dist/node-types/receive-task.js +3 -28
- package/dist/node-types/receive-task.js.map +1 -1
- package/dist/templates/weaver-template.d.ts +11 -0
- package/dist/templates/weaver-template.d.ts.map +1 -0
- package/dist/templates/weaver-template.js +53 -0
- package/dist/templates/weaver-template.js.map +1 -0
- package/dist/ui/bot-constants.d.ts +14 -0
- package/dist/ui/bot-constants.d.ts.map +1 -0
- package/dist/ui/bot-constants.js +189 -0
- package/dist/ui/bot-constants.js.map +1 -0
- package/dist/ui/bot-panel.js +51 -90
- package/dist/ui/bot-slot-card.js +87 -122
- package/dist/ui/budget-bar.js +5 -3
- package/dist/ui/chat-task-result.js +4 -7
- package/dist/ui/decision-log.js +136 -0
- package/dist/ui/profile-card.js +158 -0
- package/dist/ui/profile-editor.js +597 -0
- package/dist/ui/swarm-controls.js +36 -27
- package/dist/ui/swarm-dashboard.js +2034 -736
- package/dist/ui/task-create-form.js +39 -116
- package/dist/ui/task-detail-view.js +490 -239
- package/dist/ui/task-pool-list.js +69 -94
- package/dist/workflows/orchestrator.d.ts +21 -0
- package/dist/workflows/orchestrator.d.ts.map +1 -0
- package/dist/workflows/orchestrator.js +281 -0
- package/dist/workflows/orchestrator.js.map +1 -0
- package/dist/workflows/weaver-bot-session.d.ts +65 -0
- package/dist/workflows/weaver-bot-session.d.ts.map +1 -0
- package/dist/workflows/weaver-bot-session.js +68 -0
- package/dist/workflows/weaver-bot-session.js.map +1 -0
- package/dist/workflows/weaver.d.ts +24 -0
- package/dist/workflows/weaver.d.ts.map +1 -0
- package/dist/workflows/weaver.js +28 -0
- package/dist/workflows/weaver.js.map +1 -0
- package/flowweaver.manifest.json +253 -66
- package/package.json +1 -1
- package/src/ai-chat-provider.ts +184 -18
- package/src/bot/ai-router.ts +132 -0
- package/src/bot/bot-registry.ts +2 -2
- package/src/bot/conversation-store.ts +2 -1
- package/src/bot/improve-loop.ts +6 -6
- package/src/bot/instance-manager.ts +128 -0
- package/src/bot/orchestrator.ts +244 -0
- package/src/bot/profile-store.ts +225 -0
- package/src/bot/profile-types.ts +141 -0
- package/src/bot/swarm-controller.ts +385 -186
- package/src/bot/task-prompt-builder.ts +37 -6
- package/src/bot/task-store.ts +28 -89
- package/src/bot/task-types.ts +10 -4
- package/src/cli-handlers.ts +2 -3
- package/src/docs/weaver-bot-usage.md +35 -18
- package/src/docs/weaver-config.md +20 -0
- package/src/docs/weaver-task-queue.md +31 -19
- package/src/mcp-tools.ts +129 -320
- package/src/node-types/orchestrator-dispatch.ts +71 -0
- package/src/node-types/orchestrator-load-state.ts +66 -0
- package/src/node-types/orchestrator-route.ts +33 -0
- package/src/node-types/receive-task.ts +3 -26
- package/src/ui/bot-constants.ts +192 -0
- package/src/ui/bot-panel.tsx +55 -79
- package/src/ui/bot-slot-card.tsx +69 -117
- package/src/ui/budget-bar.tsx +5 -3
- package/src/ui/chat-task-result.tsx +6 -9
- package/src/ui/decision-log.tsx +148 -0
- package/src/ui/profile-card.tsx +157 -0
- package/src/ui/profile-editor.tsx +384 -0
- package/src/ui/swarm-controls.tsx +35 -31
- package/src/ui/swarm-dashboard.tsx +409 -80
- package/src/ui/task-create-form.tsx +29 -119
- package/src/ui/task-detail-view.tsx +461 -215
- package/src/ui/task-pool-list.tsx +74 -95
- package/src/workflows/orchestrator.ts +302 -0
- package/dist/docs/weaver-bot-usage.md +0 -34
- package/dist/docs/weaver-genesis.md +0 -32
- package/dist/docs/weaver-task-queue.md +0 -34
- package/src/bot/error-guide.ts +0 -4
- package/src/bot/retry-utils.ts +0 -4
package/src/ai-chat-provider.ts
CHANGED
|
@@ -26,6 +26,7 @@ import { TaskStore } from './bot/task-store.js';
|
|
|
26
26
|
import { SwarmController } from './bot/swarm-controller.js';
|
|
27
27
|
import { SwarmEventLog } from './bot/swarm-event-log.js';
|
|
28
28
|
import type { CreateTaskInput, TaskFilter } from './bot/task-types.js';
|
|
29
|
+
import type { CreateProfileInput } from './bot/profile-types.js';
|
|
29
30
|
|
|
30
31
|
interface AiChatToolContext {
|
|
31
32
|
workspacePath: string;
|
|
@@ -145,8 +146,8 @@ const toolHandlers: Record<
|
|
|
145
146
|
const state = controller.getStatus();
|
|
146
147
|
return JSON.stringify({
|
|
147
148
|
swarmStatus: state.status,
|
|
148
|
-
activeBots: Object.values(state.
|
|
149
|
-
totalBots: Object.keys(state.
|
|
149
|
+
activeBots: Object.values(state.instances).filter(i => i.status === 'executing').length,
|
|
150
|
+
totalBots: Object.keys(state.instances).length,
|
|
150
151
|
tasksCompleted: state.tasksCompleted,
|
|
151
152
|
tasksFailed: state.tasksFailed,
|
|
152
153
|
totalTokensUsed: state.totalTokensUsed,
|
|
@@ -318,7 +319,6 @@ const toolHandlers: Record<
|
|
|
318
319
|
const input: CreateTaskInput = {
|
|
319
320
|
title: args.title as string,
|
|
320
321
|
description: (args.description as string) ?? '',
|
|
321
|
-
assignedBots: args.assignedBots as string[] | undefined,
|
|
322
322
|
priority: args.priority as number | undefined,
|
|
323
323
|
parentId: args.parentId as string | undefined,
|
|
324
324
|
dependsOn: args.dependsOn as string[] | undefined,
|
|
@@ -327,6 +327,8 @@ const toolHandlers: Record<
|
|
|
327
327
|
timeoutMs: args.timeoutMs as number | undefined,
|
|
328
328
|
maxAttempts: args.maxAttempts as number | undefined,
|
|
329
329
|
createdBy: 'ai',
|
|
330
|
+
assignedProfile: args.assignedProfile as string | undefined,
|
|
331
|
+
complexity: args.complexity as CreateTaskInput['complexity'],
|
|
330
332
|
subtasks: args.subtasks as CreateTaskInput['subtasks'],
|
|
331
333
|
};
|
|
332
334
|
const task = await store.create(input);
|
|
@@ -430,8 +432,153 @@ const toolHandlers: Record<
|
|
|
430
432
|
const done = controller.getStatus().status === 'idle';
|
|
431
433
|
return JSON.stringify({ events, done });
|
|
432
434
|
},
|
|
435
|
+
|
|
436
|
+
// ---------------------------------------------------------------------------
|
|
437
|
+
// Profile CRUD tools
|
|
438
|
+
// ---------------------------------------------------------------------------
|
|
439
|
+
|
|
440
|
+
async fw_weaver_profile_list(_args: Record<string, unknown>, ctx: AiChatToolContext) {
|
|
441
|
+
const controller = SwarmController.getInstance(ctx.workspacePath);
|
|
442
|
+
const profileStore = controller.getProfileStore();
|
|
443
|
+
const instanceManager = controller.getInstanceManager();
|
|
444
|
+
const profiles = profileStore.list();
|
|
445
|
+
const enriched = profiles.map(p => ({
|
|
446
|
+
...p,
|
|
447
|
+
activeInstances: instanceManager.listByProfile(p.id).filter(i => i.status === 'executing').length,
|
|
448
|
+
idleInstances: instanceManager.findIdle(p.id).length,
|
|
449
|
+
totalInstances: instanceManager.listByProfile(p.id).length,
|
|
450
|
+
}));
|
|
451
|
+
return JSON.stringify(enriched, null, 2);
|
|
452
|
+
},
|
|
453
|
+
|
|
454
|
+
async fw_weaver_profile_create(args: Record<string, unknown>, ctx: AiChatToolContext) {
|
|
455
|
+
const controller = SwarmController.getInstance(ctx.workspacePath);
|
|
456
|
+
const profileStore = controller.getProfileStore();
|
|
457
|
+
const input: CreateProfileInput = {
|
|
458
|
+
name: args.name as string,
|
|
459
|
+
botId: args.botId as string,
|
|
460
|
+
capabilities: args.capabilities as CreateProfileInput['capabilities'],
|
|
461
|
+
preferences: {
|
|
462
|
+
costStrategy: (args.costStrategy as 'frugal' | 'balanced' | 'performance') ?? 'balanced',
|
|
463
|
+
maxCostPerRun: args.maxCostPerRun as number | undefined,
|
|
464
|
+
maxCostPerTask: args.maxCostPerTask as number | undefined,
|
|
465
|
+
requireApproval: args.requireApproval as boolean | undefined,
|
|
466
|
+
instructions: args.instructions as string | undefined,
|
|
467
|
+
},
|
|
468
|
+
maxInstances: args.maxInstances as number | undefined,
|
|
469
|
+
description: args.description as string | undefined,
|
|
470
|
+
icon: args.icon as string | undefined,
|
|
471
|
+
color: args.color as string | undefined,
|
|
472
|
+
};
|
|
473
|
+
const profile = profileStore.create(input);
|
|
474
|
+
return JSON.stringify(profile, null, 2);
|
|
475
|
+
},
|
|
476
|
+
|
|
477
|
+
async fw_weaver_profile_update(args: Record<string, unknown>, ctx: AiChatToolContext) {
|
|
478
|
+
const controller = SwarmController.getInstance(ctx.workspacePath);
|
|
479
|
+
const profileStore = controller.getProfileStore();
|
|
480
|
+
const { id, ...rest } = args;
|
|
481
|
+
|
|
482
|
+
// Build a typed patch from the remaining args
|
|
483
|
+
const patch: Record<string, unknown> = {};
|
|
484
|
+
if (rest.name !== undefined) patch.name = rest.name;
|
|
485
|
+
if (rest.description !== undefined) patch.description = rest.description;
|
|
486
|
+
if (rest.icon !== undefined) patch.icon = rest.icon;
|
|
487
|
+
if (rest.color !== undefined) patch.color = rest.color;
|
|
488
|
+
if (rest.capabilities !== undefined) patch.capabilities = rest.capabilities;
|
|
489
|
+
if (rest.maxInstances !== undefined) patch.maxInstances = rest.maxInstances;
|
|
490
|
+
if (rest.minInstances !== undefined) patch.minInstances = rest.minInstances;
|
|
491
|
+
if (rest.costStrategy !== undefined || rest.maxCostPerRun !== undefined || rest.maxCostPerTask !== undefined || rest.requireApproval !== undefined || rest.instructions !== undefined) {
|
|
492
|
+
patch.preferences = {
|
|
493
|
+
...(rest.costStrategy !== undefined && { costStrategy: rest.costStrategy }),
|
|
494
|
+
...(rest.maxCostPerRun !== undefined && { maxCostPerRun: rest.maxCostPerRun }),
|
|
495
|
+
...(rest.maxCostPerTask !== undefined && { maxCostPerTask: rest.maxCostPerTask }),
|
|
496
|
+
...(rest.requireApproval !== undefined && { requireApproval: rest.requireApproval }),
|
|
497
|
+
...(rest.instructions !== undefined && { instructions: rest.instructions }),
|
|
498
|
+
};
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
const profile = profileStore.update(id as string, patch);
|
|
502
|
+
return JSON.stringify(profile, null, 2);
|
|
503
|
+
},
|
|
504
|
+
|
|
505
|
+
async fw_weaver_profile_delete(args: Record<string, unknown>, ctx: AiChatToolContext) {
|
|
506
|
+
const controller = SwarmController.getInstance(ctx.workspacePath);
|
|
507
|
+
const profileStore = controller.getProfileStore();
|
|
508
|
+
const deleted = profileStore.delete(args.id as string);
|
|
509
|
+
return JSON.stringify({ deleted });
|
|
510
|
+
},
|
|
511
|
+
|
|
512
|
+
// ---------------------------------------------------------------------------
|
|
513
|
+
// Orchestrator tools
|
|
514
|
+
// ---------------------------------------------------------------------------
|
|
515
|
+
|
|
516
|
+
async fw_weaver_orchestrator_status(_args: Record<string, unknown>, ctx: AiChatToolContext) {
|
|
517
|
+
const controller = SwarmController.getInstance(ctx.workspacePath);
|
|
518
|
+
const { decisions, stats } = controller.getOrchestratorStatus();
|
|
519
|
+
const instanceManager = controller.getInstanceManager();
|
|
520
|
+
const allInstances = instanceManager.listAll();
|
|
521
|
+
return JSON.stringify({
|
|
522
|
+
recentDecisions: decisions.slice(-20),
|
|
523
|
+
routingStats: stats,
|
|
524
|
+
activeInstances: allInstances.filter(i => i.status === 'executing').length,
|
|
525
|
+
totalInstances: allInstances.length,
|
|
526
|
+
}, null, 2);
|
|
527
|
+
},
|
|
528
|
+
|
|
529
|
+
async fw_weaver_orchestrator_hint(args: Record<string, unknown>, ctx: AiChatToolContext) {
|
|
530
|
+
const hint = args.hint as string;
|
|
531
|
+
if (!hint) return JSON.stringify({ error: 'hint is required' });
|
|
532
|
+
|
|
533
|
+
const weaverDir = path.join(ctx.workspacePath, '.weaver');
|
|
534
|
+
fs.mkdirSync(weaverDir, { recursive: true });
|
|
535
|
+
const hintsPath = path.join(weaverDir, 'orchestrator-hints.json');
|
|
536
|
+
|
|
537
|
+
let hints: Array<{ hint: string; timestamp: number }> = [];
|
|
538
|
+
try {
|
|
539
|
+
if (fs.existsSync(hintsPath)) {
|
|
540
|
+
const raw = fs.readFileSync(hintsPath, 'utf-8');
|
|
541
|
+
const parsed = JSON.parse(raw);
|
|
542
|
+
if (Array.isArray(parsed)) hints = parsed;
|
|
543
|
+
}
|
|
544
|
+
} catch { /* corrupt file — start fresh */ }
|
|
545
|
+
|
|
546
|
+
hints.push({ hint, timestamp: Date.now() });
|
|
547
|
+
fs.writeFileSync(hintsPath, JSON.stringify(hints, null, 2), 'utf-8');
|
|
548
|
+
return JSON.stringify({ stored: true });
|
|
549
|
+
},
|
|
433
550
|
};
|
|
434
551
|
|
|
552
|
+
// ---------------------------------------------------------------------------
|
|
553
|
+
// Shared handler — used by both ai-chat-provider and mcp-tools
|
|
554
|
+
// ---------------------------------------------------------------------------
|
|
555
|
+
|
|
556
|
+
/**
|
|
557
|
+
* Execute a weaver tool by name. Shared core for ai-chat-provider and MCP.
|
|
558
|
+
*
|
|
559
|
+
* @param toolName One of the fw_weaver_* tool names
|
|
560
|
+
* @param args Tool arguments (as parsed from the caller)
|
|
561
|
+
* @param projectDir Workspace / project directory
|
|
562
|
+
* @returns The JSON-serialised result string
|
|
563
|
+
*/
|
|
564
|
+
export async function handleWeaverTool(
|
|
565
|
+
toolName: string,
|
|
566
|
+
args: Record<string, unknown>,
|
|
567
|
+
projectDir: string,
|
|
568
|
+
): Promise<{ result: string; isError: boolean }> {
|
|
569
|
+
const handler = toolHandlers[toolName];
|
|
570
|
+
if (!handler) {
|
|
571
|
+
return { result: `Unknown weaver tool: ${toolName}`, isError: true };
|
|
572
|
+
}
|
|
573
|
+
try {
|
|
574
|
+
const ctx: AiChatToolContext = { workspacePath: projectDir, userId: 'mcp' };
|
|
575
|
+
const result = await handler(args, ctx);
|
|
576
|
+
return { result, isError: false };
|
|
577
|
+
} catch (err) {
|
|
578
|
+
return { result: err instanceof Error ? err.message : String(err), isError: true };
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
|
|
435
582
|
// ---------------------------------------------------------------------------
|
|
436
583
|
// Provider implementation
|
|
437
584
|
// ---------------------------------------------------------------------------
|
|
@@ -442,16 +589,7 @@ export default {
|
|
|
442
589
|
args: Record<string, unknown>,
|
|
443
590
|
context: AiChatToolContext,
|
|
444
591
|
): Promise<AiChatToolResult> {
|
|
445
|
-
|
|
446
|
-
if (!handler) {
|
|
447
|
-
return { result: `Unknown weaver tool: ${name}`, isError: true };
|
|
448
|
-
}
|
|
449
|
-
try {
|
|
450
|
-
const result = await handler(args, context);
|
|
451
|
-
return { result, isError: false };
|
|
452
|
-
} catch (err) {
|
|
453
|
-
return { result: err instanceof Error ? err.message : String(err), isError: true };
|
|
454
|
-
}
|
|
592
|
+
return handleWeaverTool(name, args, context.workspacePath);
|
|
455
593
|
},
|
|
456
594
|
|
|
457
595
|
async getSystemPromptSections(
|
|
@@ -482,7 +620,7 @@ export default {
|
|
|
482
620
|
content: `Beyond standard Flow Weaver tools, you have swarm-based task and bot management:
|
|
483
621
|
|
|
484
622
|
**Task Management:**
|
|
485
|
-
- \`fw_weaver_task_create\`: Create tasks (with optional subtasks via \`subtasks\` array, \`^prev\` shorthand for dependency chaining, \`
|
|
623
|
+
- \`fw_weaver_task_create\`: Create tasks (with optional subtasks via \`subtasks\` array, \`^prev\` shorthand for dependency chaining, \`assignedProfile\` to target specific profiles)
|
|
486
624
|
- \`fw_weaver_task_list\`: List tasks (filter by status, parentId, botId)
|
|
487
625
|
- \`fw_weaver_task_get\`: Get task details including subtasks and context
|
|
488
626
|
- \`fw_weaver_task_update\`: Update task fields (title, description, priority, status)
|
|
@@ -512,7 +650,15 @@ export default {
|
|
|
512
650
|
- \`fw_weaver_validate_bot\`: Validate a bot's file and export
|
|
513
651
|
- \`fw_weaver_eject_bot\`: Mark a bot as ejected with a new file path
|
|
514
652
|
|
|
515
|
-
**
|
|
653
|
+
**Profiles & Orchestrator:**
|
|
654
|
+
- \`fw_weaver_profile_list\`: List all bot profiles with capabilities, instances, and routing info
|
|
655
|
+
- \`fw_weaver_profile_create\`: Create a new bot profile with free-form capabilities (name + description pairs) and preferences (costStrategy, maxCostPerRun, maxCostPerTask, requireApproval, instructions)
|
|
656
|
+
- \`fw_weaver_profile_update\`: Update a bot profile's capabilities, preferences, or scaling config
|
|
657
|
+
- \`fw_weaver_profile_delete\`: Delete a bot profile
|
|
658
|
+
- \`fw_weaver_orchestrator_status\`: Get orchestrator routing status — recent decisions, stats, active instances
|
|
659
|
+
- \`fw_weaver_orchestrator_hint\`: Provide a routing hint to influence orchestrator decisions
|
|
660
|
+
|
|
661
|
+
**Workflow:** To delegate work, create tasks with \`fw_weaver_task_create\`, then start the swarm with \`fw_weaver_swarm_start\`. The orchestrator automatically routes tasks to the best available bot instance based on free-form capability matching. Use \`fw_weaver_steer\` with a \`botId\` to control individual bots. Use \`fw_weaver_profile_create\` to create specialized profiles with capabilities (name + description pairs) and preferences (costStrategy, maxCostPerRun, instructions), then route tasks via \`assignedProfile\`.
|
|
516
662
|
|
|
517
663
|
Proactively offer these when relevant.`,
|
|
518
664
|
priority: 15,
|
|
@@ -574,24 +720,44 @@ Proactively offer these when relevant.`,
|
|
|
574
720
|
sections.push({
|
|
575
721
|
id: 'registered-bots',
|
|
576
722
|
title: 'Registered Bots',
|
|
577
|
-
content: `The user has registered bot workflows available for swarm execution. To assign work
|
|
723
|
+
content: `The user has registered bot workflows available for swarm execution. To assign work, use \`fw_weaver_task_create\` with \`assignedProfile\` to route tasks to the right profile.\n\n${botLines}`,
|
|
578
724
|
priority: 25,
|
|
579
725
|
});
|
|
580
726
|
}
|
|
581
727
|
} catch { /* non-fatal */ }
|
|
582
728
|
}
|
|
583
729
|
|
|
730
|
+
// Bot profiles — capabilities, scaling, routing info
|
|
731
|
+
if (context.workspacePath) {
|
|
732
|
+
try {
|
|
733
|
+
const controller = SwarmController.getInstance(context.workspacePath);
|
|
734
|
+
const profileStore = controller.getProfileStore();
|
|
735
|
+
const profiles = profileStore.list();
|
|
736
|
+
if (profiles.length > 0) {
|
|
737
|
+
const profileLines = profiles.map(p =>
|
|
738
|
+
`- ${p.name} (${p.id}): capabilities=[${p.capabilities.map(c => c.name).join(', ')}], maxInstances=${p.maxInstances}, costStrategy=${p.preferences.costStrategy}`,
|
|
739
|
+
).join('\n');
|
|
740
|
+
sections.push({
|
|
741
|
+
id: 'bot-profiles',
|
|
742
|
+
title: 'Bot Profiles',
|
|
743
|
+
content: `${profileLines}\n\nUse fw_weaver_profile_create to create new profiles. Use fw_weaver_task_create with assignedProfile to route tasks to the right profiles.`,
|
|
744
|
+
priority: 22,
|
|
745
|
+
});
|
|
746
|
+
}
|
|
747
|
+
} catch { /* non-fatal */ }
|
|
748
|
+
}
|
|
749
|
+
|
|
584
750
|
// Swarm status — include if swarm is active
|
|
585
751
|
if (context.workspacePath) {
|
|
586
752
|
try {
|
|
587
753
|
const controller = SwarmController.getInstance(context.workspacePath);
|
|
588
754
|
const swarmState = controller.getStatus();
|
|
589
755
|
if (swarmState.status !== 'idle') {
|
|
590
|
-
const
|
|
756
|
+
const activeInstances = Object.values(swarmState.instances).filter(i => i.status === 'executing').length;
|
|
591
757
|
sections.push({
|
|
592
758
|
id: 'swarm-status',
|
|
593
759
|
title: 'Swarm Status',
|
|
594
|
-
content: `The swarm is currently **${swarmState.status}** with ${
|
|
760
|
+
content: `The swarm is currently **${swarmState.status}** with ${activeInstances} active instance(s) out of ${Object.keys(swarmState.instances).length} total. Tasks completed: ${swarmState.tasksCompleted}, failed: ${swarmState.tasksFailed}. Total cost: $${swarmState.totalCost.toFixed(4)}, tokens: ${swarmState.totalTokensUsed}.`,
|
|
595
761
|
priority: 12,
|
|
596
762
|
});
|
|
597
763
|
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AIRouterImpl — LLM-powered routing for ambiguous profile selection.
|
|
3
|
+
*
|
|
4
|
+
* When multiple bot profiles match a task, the orchestrator delegates
|
|
5
|
+
* to this router to pick the best one.
|
|
6
|
+
* Uses the project's configured AI provider via the callAI infrastructure.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { AIRouter, AIRouterResult } from './orchestrator.js';
|
|
10
|
+
import type { BotProfile, OrchestratorInput } from './profile-types.js';
|
|
11
|
+
import type { ProviderInfo } from './types.js';
|
|
12
|
+
import { callAI, parseJsonResponse } from './ai-client.js';
|
|
13
|
+
import { resolveProviderConfig, detectProvider } from './agent-provider.js';
|
|
14
|
+
|
|
15
|
+
type Task = OrchestratorInput['pendingTasks'][number];
|
|
16
|
+
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
// Provider resolution — map BotProviderConfig.name to ProviderInfo.type
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
|
|
21
|
+
const NAME_TO_TYPE: Record<string, ProviderInfo['type']> = {
|
|
22
|
+
anthropic: 'anthropic',
|
|
23
|
+
'claude-cli': 'claude-cli',
|
|
24
|
+
'copilot-cli': 'copilot-cli',
|
|
25
|
+
platform: 'platform',
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
function resolveProviderInfo(projectDir: string): Pick<ProviderInfo, 'type' | 'apiKey' | 'model' | 'maxTokens'> {
|
|
29
|
+
// Try reading .weaver.json from the project directory
|
|
30
|
+
let providerInput: string | { name: string; model?: string; maxTokens?: number } = 'auto';
|
|
31
|
+
try {
|
|
32
|
+
const fs = require('node:fs');
|
|
33
|
+
const path = require('node:path');
|
|
34
|
+
const configPath = path.join(projectDir, '.weaver.json');
|
|
35
|
+
if (fs.existsSync(configPath)) {
|
|
36
|
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
37
|
+
if (config.provider) providerInput = config.provider;
|
|
38
|
+
}
|
|
39
|
+
} catch { /* use auto detection */ }
|
|
40
|
+
|
|
41
|
+
const resolved = resolveProviderConfig(providerInput);
|
|
42
|
+
const type = NAME_TO_TYPE[resolved.name] ?? 'claude-cli';
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
type,
|
|
46
|
+
model: resolved.model,
|
|
47
|
+
maxTokens: resolved.maxTokens,
|
|
48
|
+
apiKey: type === 'anthropic' ? process.env.ANTHROPIC_API_KEY : undefined,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// ---------------------------------------------------------------------------
|
|
53
|
+
// AIRouterImpl
|
|
54
|
+
// ---------------------------------------------------------------------------
|
|
55
|
+
|
|
56
|
+
const SYSTEM_PROMPT =
|
|
57
|
+
'You are a task routing orchestrator. Given a task and candidate bot profiles, ' +
|
|
58
|
+
'select the best profile to handle the task. Respond with ONLY valid JSON: ' +
|
|
59
|
+
'{ "profileId": "...", "reason": "...", "confidence": 0.0-1.0 }';
|
|
60
|
+
|
|
61
|
+
export class AIRouterImpl implements AIRouter {
|
|
62
|
+
private projectDir: string;
|
|
63
|
+
|
|
64
|
+
constructor(projectDir: string) {
|
|
65
|
+
this.projectDir = projectDir;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async route(task: Task, candidates: BotProfile[]): Promise<AIRouterResult> {
|
|
69
|
+
const prompt = this._buildPrompt(task, candidates);
|
|
70
|
+
const providerInfo = resolveProviderInfo(this.projectDir);
|
|
71
|
+
|
|
72
|
+
// Use a low max-tokens since this is just a routing decision
|
|
73
|
+
const response = await callAI(providerInfo, SYSTEM_PROMPT, prompt, 256);
|
|
74
|
+
|
|
75
|
+
const result = parseJsonResponse(response) as {
|
|
76
|
+
profileId?: string;
|
|
77
|
+
reason?: string;
|
|
78
|
+
confidence?: number;
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
if (!result.profileId) {
|
|
82
|
+
throw new Error('AI router returned no profileId');
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return {
|
|
86
|
+
profileId: result.profileId,
|
|
87
|
+
reason: result.reason || 'AI-selected',
|
|
88
|
+
confidence: typeof result.confidence === 'number' ? result.confidence : 0.5,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** @internal — exposed for testing */
|
|
93
|
+
_buildPrompt(task: Task, candidates: BotProfile[]): string {
|
|
94
|
+
const lines = [
|
|
95
|
+
`Task: "${task.title}"`,
|
|
96
|
+
task.description ? `Description: ${task.description}` : '',
|
|
97
|
+
task.complexity ? `Complexity: ${task.complexity}` : '',
|
|
98
|
+
'',
|
|
99
|
+
'Candidate profiles:',
|
|
100
|
+
...candidates.map((p) => {
|
|
101
|
+
const parts = [`- ${p.id} (${p.name}):`];
|
|
102
|
+
if (p.capabilities.length > 0) {
|
|
103
|
+
parts.push(' Capabilities:');
|
|
104
|
+
for (const cap of p.capabilities) {
|
|
105
|
+
parts.push(` - ${cap.name}: ${cap.description}`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
parts.push(` Cost strategy: ${p.preferences.costStrategy}`);
|
|
109
|
+
if (p.preferences.instructions) {
|
|
110
|
+
parts.push(` Instructions: ${p.preferences.instructions}`);
|
|
111
|
+
}
|
|
112
|
+
parts.push(` maxInstances=${p.maxInstances}`);
|
|
113
|
+
return parts.join('\n');
|
|
114
|
+
}),
|
|
115
|
+
];
|
|
116
|
+
|
|
117
|
+
// Add previous attempt info if available
|
|
118
|
+
const failures = task.context.runSummaries.filter((r) => r.outcome !== 'success');
|
|
119
|
+
if (failures.length > 0) {
|
|
120
|
+
lines.push(
|
|
121
|
+
'',
|
|
122
|
+
`Previous attempts: ${task.context.runSummaries.length} (${failures.length} failed)`,
|
|
123
|
+
);
|
|
124
|
+
for (const f of failures.slice(-3)) {
|
|
125
|
+
lines.push(` - ${f.botId}: ${f.outcome}`);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
lines.push('', 'Select the best profile. Respond with JSON only.');
|
|
130
|
+
return lines.filter(Boolean).join('\n');
|
|
131
|
+
}
|
|
132
|
+
}
|
package/src/bot/bot-registry.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Bot registry — manages .
|
|
2
|
+
* Bot registry — manages .weaver/bots.json in the user's workspace.
|
|
3
3
|
* Reads, writes, validates bot registrations.
|
|
4
4
|
*/
|
|
5
5
|
import * as fs from 'node:fs';
|
|
@@ -19,7 +19,7 @@ export interface BotRegistration {
|
|
|
19
19
|
ejected?: boolean;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
const BOTS_FILE = '.
|
|
22
|
+
const BOTS_FILE = '.weaver/bots.json';
|
|
23
23
|
|
|
24
24
|
export class BotRegistry {
|
|
25
25
|
private _cache: BotRegistration[] | null = null;
|
|
@@ -26,6 +26,7 @@ export interface ConversationRecord {
|
|
|
26
26
|
createdAt: number;
|
|
27
27
|
lastMessageAt: number;
|
|
28
28
|
botIds: string[];
|
|
29
|
+
cloudConversationId?: string;
|
|
29
30
|
}
|
|
30
31
|
|
|
31
32
|
interface StoredMessage {
|
|
@@ -221,7 +222,7 @@ export class ConversationStore {
|
|
|
221
222
|
},
|
|
222
223
|
body: JSON.stringify({
|
|
223
224
|
message: `[Synced from CLI] ${message}`,
|
|
224
|
-
conversationId:
|
|
225
|
+
conversationId: conversation.cloudConversationId,
|
|
225
226
|
}),
|
|
226
227
|
}).catch(() => {}); // fire-and-forget
|
|
227
228
|
} catch { /* sync not available */ }
|
package/src/bot/improve-loop.ts
CHANGED
|
@@ -526,9 +526,9 @@ function emptyResult(startedAt: string, branch: string, worktreePath: string, re
|
|
|
526
526
|
|
|
527
527
|
// --- Helpers ---
|
|
528
528
|
|
|
529
|
-
let cachedProvider:
|
|
530
|
-
let cachedTools:
|
|
531
|
-
let cachedExecutor:
|
|
529
|
+
let cachedProvider: import('@synergenius/flow-weaver/agent').AgentProvider | null = null;
|
|
530
|
+
let cachedTools: import('@synergenius/flow-weaver/agent').ToolDefinition[] = [];
|
|
531
|
+
let cachedExecutor: import('@synergenius/flow-weaver/agent').ToolExecutor | null = null;
|
|
532
532
|
|
|
533
533
|
async function runAssistantInDir(worktreeDir: string, message: string, _conversationId: string, steeringEngine?: import('./steering-engine.js').SteeringEngine): Promise<string> {
|
|
534
534
|
const originalCwd = process.cwd();
|
|
@@ -562,9 +562,9 @@ async function runAssistantInDir(worktreeDir: string, message: string, _conversa
|
|
|
562
562
|
const toolCalls: Array<{ name: string; isError: boolean }> = [];
|
|
563
563
|
|
|
564
564
|
const result = await runAgentLoop(
|
|
565
|
-
cachedProvider
|
|
566
|
-
cachedTools
|
|
567
|
-
cachedExecutor
|
|
565
|
+
cachedProvider!,
|
|
566
|
+
cachedTools,
|
|
567
|
+
cachedExecutor!,
|
|
568
568
|
[{ role: 'user' as const, content: message }],
|
|
569
569
|
{
|
|
570
570
|
maxIterations: 20,
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* InstanceManager — manages in-memory bot instances (workers) per profile.
|
|
3
|
+
* Instances represent running workers that don't survive process restart.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { BotProfile, BotInstance } from './profile-types.js';
|
|
7
|
+
|
|
8
|
+
export class InstanceManager {
|
|
9
|
+
private instances = new Map<string, BotInstance>();
|
|
10
|
+
|
|
11
|
+
/** Spawn a new instance for the given profile. Throws if maxInstances reached. */
|
|
12
|
+
spawn(profile: BotProfile): BotInstance {
|
|
13
|
+
const existing = this.listByProfile(profile.id);
|
|
14
|
+
if (existing.length >= profile.maxInstances) {
|
|
15
|
+
throw new Error(
|
|
16
|
+
`Cannot spawn: profile "${profile.id}" already at max instances (${profile.maxInstances})`,
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const usedIndices = new Set(existing.map((i) => i.index));
|
|
21
|
+
let index = 0;
|
|
22
|
+
while (usedIndices.has(index)) index++;
|
|
23
|
+
|
|
24
|
+
const instance: BotInstance = {
|
|
25
|
+
instanceId: `${profile.id}-${index}`,
|
|
26
|
+
profileId: profile.id,
|
|
27
|
+
index,
|
|
28
|
+
status: 'idle',
|
|
29
|
+
tokensUsed: 0,
|
|
30
|
+
cost: 0,
|
|
31
|
+
tasksCompleted: 0,
|
|
32
|
+
tasksFailed: 0,
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
this.instances.set(instance.instanceId, instance);
|
|
36
|
+
return { ...instance };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Get instance by ID (returns a copy), or null if not found. */
|
|
40
|
+
get(instanceId: string): BotInstance | null {
|
|
41
|
+
const inst = this.instances.get(instanceId);
|
|
42
|
+
return inst ? { ...inst } : null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Return copies of all instances. */
|
|
46
|
+
listAll(): BotInstance[] {
|
|
47
|
+
return [...this.instances.values()].map((i) => ({ ...i }));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Return copies of instances belonging to a specific profile. */
|
|
51
|
+
listByProfile(profileId: string): BotInstance[] {
|
|
52
|
+
return [...this.instances.values()]
|
|
53
|
+
.filter((i) => i.profileId === profileId)
|
|
54
|
+
.map((i) => ({ ...i }));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Return idle instances for a given profile. */
|
|
58
|
+
findIdle(profileId: string): BotInstance[] {
|
|
59
|
+
return [...this.instances.values()]
|
|
60
|
+
.filter((i) => i.profileId === profileId && i.status === 'idle')
|
|
61
|
+
.map((i) => ({ ...i }));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Mark an instance as executing a specific task/run. */
|
|
65
|
+
markExecuting(instanceId: string, taskId: string, runId: string): void {
|
|
66
|
+
const inst = this.instances.get(instanceId);
|
|
67
|
+
if (!inst) throw new Error(`Instance not found: ${instanceId}`);
|
|
68
|
+
inst.status = 'executing';
|
|
69
|
+
inst.currentTaskId = taskId;
|
|
70
|
+
inst.currentRunId = runId;
|
|
71
|
+
inst.startedAt = new Date().toISOString();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Mark an instance as idle after task completion/failure. */
|
|
75
|
+
markIdle(instanceId: string, success: boolean): void {
|
|
76
|
+
const inst = this.instances.get(instanceId);
|
|
77
|
+
if (!inst) throw new Error(`Instance not found: ${instanceId}`);
|
|
78
|
+
inst.status = 'idle';
|
|
79
|
+
inst.currentTaskId = undefined;
|
|
80
|
+
inst.currentRunId = undefined;
|
|
81
|
+
inst.startedAt = undefined;
|
|
82
|
+
if (success) {
|
|
83
|
+
inst.tasksCompleted++;
|
|
84
|
+
} else {
|
|
85
|
+
inst.tasksFailed++;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Record token/cost usage for an instance. */
|
|
90
|
+
recordUsage(instanceId: string, tokens: number, cost: number): void {
|
|
91
|
+
const inst = this.instances.get(instanceId);
|
|
92
|
+
if (!inst) throw new Error(`Instance not found: ${instanceId}`);
|
|
93
|
+
inst.tokensUsed += tokens;
|
|
94
|
+
inst.cost += cost;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** Stop and remove an instance. Returns true if it existed. */
|
|
98
|
+
stop(instanceId: string): boolean {
|
|
99
|
+
return this.instances.delete(instanceId);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** Stop and remove all instances. */
|
|
103
|
+
stopAll(): void {
|
|
104
|
+
this.instances.clear();
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** Scale instances for a profile to the target count. Clamps to [0, maxInstances]. */
|
|
108
|
+
scaleTo(profile: BotProfile, target: number): void {
|
|
109
|
+
const clamped = Math.max(0, Math.min(target, profile.maxInstances));
|
|
110
|
+
const current = this.listByProfile(profile.id);
|
|
111
|
+
|
|
112
|
+
if (current.length < clamped) {
|
|
113
|
+
// Spawn more
|
|
114
|
+
const toSpawn = clamped - current.length;
|
|
115
|
+
for (let i = 0; i < toSpawn; i++) {
|
|
116
|
+
this.spawn(profile);
|
|
117
|
+
}
|
|
118
|
+
} else if (current.length > clamped) {
|
|
119
|
+
// Stop idle instances first
|
|
120
|
+
const idle = current.filter((i) => i.status === 'idle');
|
|
121
|
+
const toStop = current.length - clamped;
|
|
122
|
+
const stopped = idle.slice(0, toStop);
|
|
123
|
+
for (const inst of stopped) {
|
|
124
|
+
this.stop(inst.instanceId);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|