@synergenius/flow-weaver-pack-weaver 0.9.80 → 0.9.82

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.
@@ -12,9 +12,11 @@ import { DashboardServer } from './bot/dashboard.js';
12
12
  import { openBrowser } from './bot/utils.js';
13
13
  import type { ExecutionEvent, WeaverConfig, RunRecord, RunOutcome, RunCostSummary, CostSummary, StageStatus, WorkflowResult, AuditEvent } from './bot/types.js';
14
14
  import { AuditStore } from './bot/audit-store.js';
15
+ import { handleWeaverTool } from './ai-chat-provider.js';
15
16
 
16
17
  export interface ParsedArgs {
17
- command: 'run' | 'history' | 'costs' | 'providers' | 'watch' | 'cron' | 'pipeline' | 'dashboard' | 'eject' | 'bot' | 'session' | 'steer' | 'queue' | 'status' | 'genesis' | 'audit' | 'init' | 'assistant' | 'examples' | 'doctor' | 'improve';
18
+ command: 'run' | 'history' | 'costs' | 'providers' | 'watch' | 'cron' | 'pipeline' | 'dashboard' | 'eject' | 'bot' | 'swarm' | 'task' | 'profile' | 'reset' | 'steer' | 'status' | 'genesis' | 'audit' | 'init' | 'assistant' | 'examples' | 'doctor' | 'improve';
19
+ subcommand?: string;
18
20
  file?: string;
19
21
  verbose: boolean;
20
22
  dryRun: boolean;
@@ -58,11 +60,17 @@ export interface ParsedArgs {
58
60
  genesisWatch: boolean;
59
61
  // eject
60
62
  ejectWorkflow?: string;
61
- // session scheduling
62
- sessionContinuous: boolean;
63
- sessionUntil?: string;
64
- sessionMaxTasks?: number;
65
- sessionParallel?: number;
63
+ // swarm / task / profile
64
+ maxConcurrent?: number;
65
+ budgetTokens?: number;
66
+ budgetCost?: number;
67
+ profileId?: string;
68
+ costStrategy?: string;
69
+ botId?: string;
70
+ confirmFlag?: boolean;
71
+ taskDescription?: string;
72
+ complexity?: string;
73
+ autoRetry?: boolean;
66
74
  // assistant
67
75
  assistantNew?: boolean;
68
76
  assistantResume?: string;
@@ -102,7 +110,6 @@ export function parseArgs(argv: string[]): ParsedArgs {
102
110
  autoApprove: false,
103
111
  genesisInit: false,
104
112
  genesisWatch: false,
105
- sessionContinuous: false,
106
113
  };
107
114
 
108
115
  const args = argv.slice(2);
@@ -207,13 +214,46 @@ export function parseArgs(argv: string[]): ParsedArgs {
207
214
  result.dashboard = true;
208
215
  } else if (arg === 'bot') {
209
216
  result.command = 'bot';
210
- // Next non-flag arg is the task string
217
+ // Next non-flag arg is the task string or subcommand
211
218
  if (i + 1 < args.length && !args[i + 1]!.startsWith('-')) {
212
219
  i++;
213
- result.botTask = args[i];
220
+ result.subcommand = args[i];
221
+ // For non-registry subcommands, treat as task string
222
+ if (!['list', 'register', 'validate'].includes(result.subcommand!)) {
223
+ result.botTask = result.subcommand;
224
+ result.subcommand = undefined;
225
+ }
226
+ }
227
+ } else if (arg === 'swarm') {
228
+ result.command = 'swarm';
229
+ if (i + 1 < args.length && !args[i + 1]!.startsWith('-')) {
230
+ i++;
231
+ result.subcommand = args[i];
232
+ }
233
+ } else if (arg === 'task') {
234
+ result.command = 'task';
235
+ if (i + 1 < args.length && !args[i + 1]!.startsWith('-')) {
236
+ i++;
237
+ result.subcommand = args[i];
238
+ // For create/get/cancel/retry: next positional is the title/id
239
+ if (['create', 'get', 'cancel', 'retry'].includes(result.subcommand!) && i + 1 < args.length && !args[i + 1]!.startsWith('-')) {
240
+ i++;
241
+ result.botTask = args[i];
242
+ }
243
+ }
244
+ } else if (arg === 'profile') {
245
+ result.command = 'profile';
246
+ if (i + 1 < args.length && !args[i + 1]!.startsWith('-')) {
247
+ i++;
248
+ result.subcommand = args[i];
249
+ // For create/delete: next positional is the name/id
250
+ if (['create', 'delete'].includes(result.subcommand!) && i + 1 < args.length && !args[i + 1]!.startsWith('-')) {
251
+ i++;
252
+ result.botTask = args[i];
253
+ }
214
254
  }
215
- } else if (arg === 'session') {
216
- result.command = 'session';
255
+ } else if (arg === 'reset') {
256
+ result.command = 'reset';
217
257
  } else if (arg === 'assistant') {
218
258
  result.command = 'assistant';
219
259
  } else if (arg === 'examples') {
@@ -224,27 +264,10 @@ export function parseArgs(argv: string[]): ParsedArgs {
224
264
  result.command = 'status';
225
265
  } else if (arg === 'steer') {
226
266
  result.command = 'steer';
227
- // Next arg is the subcommand
267
+ // Next arg is the subcommand (pause/resume/cancel)
228
268
  if (i + 1 < args.length && !args[i + 1]!.startsWith('-')) {
229
269
  i++;
230
- result.botTask = args[i];
231
- // Next arg after redirect/queue is payload
232
- if ((args[i] === 'redirect' || args[i] === 'queue') && i + 1 < args.length && !args[i + 1]!.startsWith('-')) {
233
- i++;
234
- result.botFile = args[i];
235
- }
236
- }
237
- } else if (arg === 'queue') {
238
- result.command = 'queue';
239
- // Next arg is action (add/list/clear/remove)
240
- if (i + 1 < args.length && !args[i + 1]!.startsWith('-')) {
241
- i++;
242
- result.botTask = args[i];
243
- // Next arg is task/id
244
- if (i + 1 < args.length && !args[i + 1]!.startsWith('-')) {
245
- i++;
246
- result.botFile = args[i];
247
- }
270
+ result.subcommand = args[i];
248
271
  }
249
272
  } else if (arg === '--file' && i + 1 < args.length) {
250
273
  i++;
@@ -292,17 +315,37 @@ export function parseArgs(argv: string[]): ParsedArgs {
292
315
  } else if (arg === '--delete' && i + 1 < args.length) {
293
316
  i++;
294
317
  result.assistantDelete = args[i];
295
- } else if (arg === '--continuous') {
296
- result.sessionContinuous = true;
297
- } else if (arg === '--until' && i + 1 < args.length) {
318
+ } else if (arg === '--max-concurrent' && i + 1 < args.length) {
319
+ i++;
320
+ result.maxConcurrent = parseInt(args[i]!, 10) || undefined;
321
+ } else if (arg === '--budget-tokens' && i + 1 < args.length) {
322
+ i++;
323
+ result.budgetTokens = parseInt(args[i]!, 10) || undefined;
324
+ } else if (arg === '--budget-cost' && i + 1 < args.length) {
325
+ i++;
326
+ result.budgetCost = parseFloat(args[i]!) || undefined;
327
+ } else if (arg === '--profile' && i + 1 < args.length) {
328
+ i++;
329
+ result.profileId = args[i];
330
+ } else if (arg === '--cost-strategy' && i + 1 < args.length) {
298
331
  i++;
299
- result.sessionUntil = args[i];
300
- } else if (arg === '--max-tasks' && i + 1 < args.length) {
332
+ result.costStrategy = args[i];
333
+ } else if (arg === '--bot' && i + 1 < args.length) {
301
334
  i++;
302
- result.sessionMaxTasks = parseInt(args[i]!, 10) || undefined;
303
- } else if (arg === '--parallel' && i + 1 < args.length) {
335
+ result.botId = args[i];
336
+ } else if (arg === '--confirm') {
337
+ result.confirmFlag = true;
338
+ } else if (arg === '--description' && i + 1 < args.length) {
304
339
  i++;
305
- result.sessionParallel = Math.min(Math.max(parseInt(args[i]!, 10) || 1, 1), 5);
340
+ result.taskDescription = args[i];
341
+ } else if (arg === '--complexity' && i + 1 < args.length) {
342
+ i++;
343
+ result.complexity = args[i];
344
+ } else if (arg === '--auto-retry') {
345
+ result.autoRetry = true;
346
+ } else if (arg === '--status' && i + 1 < args.length) {
347
+ i++;
348
+ result.historyOutcome = args[i];
306
349
  } else if (arg === '--project-dir' && i + 1 < args.length) {
307
350
  i++;
308
351
  result.file = args[i];
@@ -1342,300 +1385,198 @@ export async function handleBot(opts: ParsedArgs): Promise<void> {
1342
1385
  }
1343
1386
  }
1344
1387
 
1345
- export async function handleSession(opts: ParsedArgs): Promise<void> {
1346
- const projectDir = opts.file ?? process.cwd();
1347
- // Set project dir for per-project queue isolation
1348
- process.env.WEAVER_PROJECT_DIR = projectDir;
1349
- const config = await loadConfig(opts.configPath);
1350
- const workflowPath = resolveWorkflowPath('agent', projectDir);
1351
-
1352
- // Create terminal renderer for all session output
1353
- const { TerminalRenderer } = await import('./bot/terminal-renderer.js');
1354
- const renderer = new TerminalRenderer({ verbose: opts.verbose, quiet: opts.quiet });
1355
-
1356
- // Parse --until HH:MM into a deadline timestamp
1357
- let deadline: number | undefined;
1358
- let deadlineStr: string | undefined;
1359
- if (opts.sessionUntil) {
1360
- const match = opts.sessionUntil.match(/^(\d{1,2}):(\d{2})$/);
1361
- if (match) {
1362
- const now = new Date();
1363
- const target = new Date(now);
1364
- target.setHours(parseInt(match[1]!, 10), parseInt(match[2]!, 10), 0, 0);
1365
- if (target.getTime() <= now.getTime()) target.setDate(target.getDate() + 1);
1366
- deadline = target.getTime();
1367
- deadlineStr = target.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
1368
- }
1369
- }
1370
-
1371
- const maxTasks = opts.sessionMaxTasks ?? Infinity;
1372
- const continuous = opts.sessionContinuous || !!opts.sessionUntil || maxTasks < Infinity;
1373
- const parallelism = opts.sessionParallel ?? 1;
1388
+ // --- Swarm / Task / Profile / Reset handlers (thin wrappers around handleWeaverTool) ---
1374
1389
 
1375
- // Crash recovery
1376
- if (continuous) {
1377
- const { TaskStore } = await import('./bot/task-store.js');
1378
- const recoveryStore = new TaskStore(projectDir);
1379
- // Recover orphaned in-progress tasks back to pending
1380
- const inProgressTasks = await recoveryStore.list({ status: 'in-progress' });
1381
- let recovered = 0;
1382
- for (const t of inProgressTasks) {
1383
- await recoveryStore.update(t.id, { status: 'pending' });
1384
- recovered++;
1385
- }
1386
- if (recovered > 0) renderer.info(`Recovered ${recovered} orphaned task(s)`);
1390
+ function colorStatus(status: string): string {
1391
+ switch (status) {
1392
+ case 'running': return '\x1b[32m' + status + '\x1b[0m';
1393
+ case 'paused': return '\x1b[33m' + status + '\x1b[0m';
1394
+ case 'stopped': case 'idle': return '\x1b[90m' + status + '\x1b[0m';
1395
+ default: return status;
1387
1396
  }
1397
+ }
1388
1398
 
1389
- // Clean stale cache files
1390
- try {
1391
- const { execSync: execSyncClean } = await import('node:child_process');
1392
- const staleOutput = execSyncClean(`find "${projectDir}" -name "fw-exec-*" -type f`, { encoding: 'utf-8', timeout: 5000 }).trim();
1393
- if (staleOutput) {
1394
- const staleFiles = staleOutput.split('\n').filter(Boolean);
1395
- for (const f of staleFiles) { try { fs.unlinkSync(f); } catch {} }
1399
+ export async function handleSwarm(opts: ParsedArgs): Promise<void> {
1400
+ const projectDir = opts.file || process.cwd();
1401
+ const sub = opts.subcommand;
1402
+
1403
+ if (!sub || sub === 'status') {
1404
+ const { result } = await handleWeaverTool('fw_weaver_swarm_status', {}, projectDir);
1405
+ const data = JSON.parse(result);
1406
+ console.log(`\x1b[1mSwarm: ${colorStatus(data.status)}\x1b[0m`);
1407
+ if (data.instances !== undefined) console.log(` Instances: ${data.instances}`);
1408
+ if (data.maxConcurrent !== undefined) console.log(` Max concurrent: ${data.maxConcurrent}`);
1409
+ if (data.taskCounts) {
1410
+ const tc = data.taskCounts;
1411
+ console.log(` Tasks: ${tc.pending ?? 0} pending, ${tc.running ?? 0} running, ${tc.done ?? 0} done, ${tc.failed ?? 0} failed`);
1396
1412
  }
1397
- } catch { /* non-fatal */ }
1398
-
1399
- // Detect provider label for session start
1400
- const providerType = config?.provider ?? 'auto';
1401
- const providerLabel = typeof providerType === 'object' ? providerType.name : String(providerType);
1402
- const sessionStartTime = Date.now();
1403
- renderer.sessionStart({ provider: providerLabel, parallel: parallelism, deadline: deadlineStr });
1404
-
1405
- // Single-run mode (backwards compatible)
1406
- if (!continuous) {
1407
- try {
1408
- const result = await runWorkflow(workflowPath, {
1409
- params: { projectDir },
1410
- verbose: opts.verbose,
1411
- dryRun: opts.dryRun,
1412
- config,
1413
- });
1414
- if (!opts.quiet) {
1415
- const color = result.success ? '\x1b[32m' : '\x1b[31m';
1416
- console.log(`${color}Session: ${result.outcome}\x1b[0m`);
1417
- }
1418
- process.exit(result.success ? 0 : 1);
1419
- } catch (err: unknown) {
1420
- const msg = err instanceof Error ? err.message : String(err);
1421
- console.error(`\x1b[31m[weaver] Fatal: ${msg}\x1b[0m`);
1422
- process.exit(1);
1413
+ if (data.budget) {
1414
+ const b = data.budget;
1415
+ if (b.tokensUsed !== undefined) console.log(` Tokens: ${b.tokensUsed.toLocaleString()} / ${(b.tokensLimit ?? '∞').toLocaleString()}`);
1416
+ if (b.costUsed !== undefined) console.log(` Cost: $${b.costUsed.toFixed(4)} / $${(b.costLimit ?? '')}`);
1423
1417
  }
1424
1418
  return;
1425
1419
  }
1426
1420
 
1427
- // Continuous mode: loop until deadline/maxTasks/interrupt
1428
- const { TaskStore } = await import('./bot/task-store.js');
1429
- const { isTransientError, getErrorGuidance } = await import('./bot/error-classifier.js');
1430
- const taskStore = new TaskStore(projectDir);
1431
- let taskCount = 0;
1432
- let interrupted = false;
1433
- let consecutiveErrors = 0;
1434
- let consecutiveNoOps = 0;
1435
- const MAX_CONSECUTIVE_ERRORS = 3;
1436
- const MAX_CONSECUTIVE_NO_OPS = 5;
1437
-
1438
- // Session stats
1439
- let sessionCompleted = 0, sessionFailed = 0, sessionNoOp = 0;
1440
- let sessionInputTokens = 0, sessionOutputTokens = 0, sessionCost = 0;
1441
-
1442
- process.on('SIGINT', () => { interrupted = true; });
1443
- process.on('SIGTERM', () => { interrupted = true; });
1444
-
1445
- // Parallel task tracking
1446
- const running = new Map<string, Promise<void>>();
1447
- const filesInUse = new Set<string>();
1448
-
1449
- const processTask = async (task: { id: string; title: string; description: string; context: { files: string[] } }) => {
1450
- const taskPayload = { id: task.id, instruction: task.title, description: task.description, targets: task.context.files };
1451
- try {
1452
- const result = await runWorkflow(workflowPath, {
1453
- params: { projectDir, taskJson: JSON.stringify(taskPayload) },
1454
- verbose: opts.verbose,
1455
- dryRun: opts.dryRun,
1456
- config,
1457
- });
1458
-
1459
- // Classify outcome: the summary contains "no changes" or "0 files" for no-ops
1460
- const isNoOp = result.success && (
1461
- result.summary.includes('no changes') ||
1462
- result.summary.includes('0 file') ||
1463
- result.summary.includes("doesn't exist") ||
1464
- result.summary.includes('does not exist') ||
1465
- result.summary.includes('nothing to') ||
1466
- result.outcome === 'no-op'
1467
- );
1468
-
1469
- if (isNoOp) {
1470
- await taskStore.update(task.id, { status: 'done' });
1471
- sessionNoOp++;
1472
- consecutiveNoOps++;
1473
- consecutiveErrors = 0;
1474
- } else if (result.success) {
1475
- await taskStore.update(task.id, { status: 'done' });
1476
- sessionCompleted++;
1477
- consecutiveErrors = 0;
1478
- consecutiveNoOps = 0;
1479
- } else {
1480
- await taskStore.update(task.id, { status: 'failed' });
1481
- sessionFailed++;
1482
- consecutiveErrors++;
1483
- consecutiveNoOps = 0;
1484
- }
1485
-
1486
- // Track cost from workflow result
1487
- if (result.cost) {
1488
- sessionInputTokens += result.cost.totalInputTokens ?? 0;
1489
- sessionOutputTokens += result.cost.totalOutputTokens ?? 0;
1490
- sessionCost += result.cost.totalCost ?? 0;
1491
- }
1492
-
1493
- } catch (err: unknown) {
1494
- const msg = err instanceof Error ? err.message : String(err);
1495
- const guidance = getErrorGuidance(msg);
1496
- renderer.error(`Task ${task.id.slice(0, 8)} error`, guidance ? `${msg}\n Hint: ${guidance}` : msg);
1497
- await taskStore.update(task.id, { status: 'failed' });
1498
- sessionFailed++;
1499
- if (!isTransientError(err)) {
1500
- consecutiveErrors++;
1501
- }
1502
- } finally {
1503
- for (const f of task.context.files) filesInUse.delete(f);
1504
- running.delete(task.id);
1421
+ switch (sub) {
1422
+ case 'start': {
1423
+ await handleWeaverTool('fw_weaver_swarm_start', {
1424
+ maxConcurrent: opts.maxConcurrent,
1425
+ sessionBudgetTokens: opts.budgetTokens,
1426
+ sessionBudgetCost: opts.budgetCost,
1427
+ }, projectDir);
1428
+ console.log('\x1b[32m→ Swarm started\x1b[0m');
1429
+ break;
1505
1430
  }
1506
- };
1507
-
1508
- while (taskCount < maxTasks && !interrupted) {
1509
- if (deadline && Date.now() >= deadline) {
1510
- renderer.info('Deadline reached, stopping session.');
1431
+ case 'stop': {
1432
+ await handleWeaverTool('fw_weaver_swarm_stop', {}, projectDir);
1433
+ console.log('\x1b[33m→ Swarm stopped\x1b[0m');
1511
1434
  break;
1512
1435
  }
1513
-
1514
- if (consecutiveErrors >= MAX_CONSECUTIVE_ERRORS) {
1515
- renderer.error('Session stopped', `${MAX_CONSECUTIVE_ERRORS} consecutive errors — check your API key or provider config.`);
1436
+ case 'pause': {
1437
+ await handleWeaverTool('fw_weaver_swarm_pause', {}, projectDir);
1438
+ console.log('\x1b[33m→ Swarm paused\x1b[0m');
1516
1439
  break;
1517
1440
  }
1518
-
1519
- // Pause on consecutive no-ops (bot is spinning without doing anything)
1520
- if (consecutiveNoOps >= MAX_CONSECUTIVE_NO_OPS) {
1521
- renderer.warn(`${MAX_CONSECUTIVE_NO_OPS} consecutive no-op tasks — pausing 60s`);
1522
- await new Promise(r => setTimeout(r, 60_000));
1523
- consecutiveNoOps = 0;
1441
+ case 'config': {
1442
+ await handleWeaverTool('fw_weaver_swarm_config', {
1443
+ maxConcurrent: opts.maxConcurrent,
1444
+ workspaceBudgetTokens: opts.budgetTokens,
1445
+ workspaceBudgetCost: opts.budgetCost,
1446
+ autoRetry: opts.autoRetry,
1447
+ }, projectDir);
1448
+ console.log('\x1b[32m→ Config updated\x1b[0m');
1449
+ break;
1524
1450
  }
1451
+ default:
1452
+ console.error(`Unknown swarm command: ${sub}. Use: start, stop, pause, status, config`);
1453
+ }
1454
+ }
1525
1455
 
1526
- // Wait if at capacity
1527
- if (running.size >= parallelism) {
1528
- await Promise.race(running.values());
1529
- continue;
1456
+ export async function handleTask(opts: ParsedArgs): Promise<void> {
1457
+ const projectDir = opts.file || process.cwd();
1458
+ const sub = opts.subcommand;
1459
+
1460
+ switch (sub) {
1461
+ case 'create': {
1462
+ const title = opts.botTask;
1463
+ if (!title) { console.error('Usage: weaver task create "title"'); return; }
1464
+ const { result } = await handleWeaverTool('fw_weaver_task_create', {
1465
+ title,
1466
+ description: opts.taskDescription,
1467
+ priority: opts.historyOutcome, // reuse for priority if needed
1468
+ assignedProfile: opts.profileId,
1469
+ complexity: opts.complexity,
1470
+ }, projectDir);
1471
+ const data = JSON.parse(result);
1472
+ console.log(`\x1b[32m→ Task created: ${data.task?.id ?? data.id ?? 'unknown'}\x1b[0m`);
1473
+ break;
1530
1474
  }
1531
-
1532
- const pendingTasks = await taskStore.list({ status: 'pending', limit: 1 });
1533
- const task = pendingTasks[0] ?? null;
1534
- if (!task) {
1535
- if (running.size > 0) {
1536
- // Tasks still running — wait for one to finish
1537
- await Promise.race(running.values());
1538
- continue;
1475
+ case 'list': {
1476
+ const { result } = await handleWeaverTool('fw_weaver_task_list', {
1477
+ status: opts.historyOutcome,
1478
+ limit: opts.historyLimit,
1479
+ }, projectDir);
1480
+ const tasks = JSON.parse(result);
1481
+ if (!Array.isArray(tasks) || tasks.length === 0) {
1482
+ console.log(' No tasks.');
1483
+ return;
1539
1484
  }
1540
- // No pending or running tasks — wait and retry
1541
- await new Promise(r => setTimeout(r, 5_000));
1542
- continue;
1543
- }
1544
-
1545
- // Auto-decompose broad tasks into per-file tasks
1546
- const { decomposeTask } = await import('./bot/task-decomposer.js');
1547
- const decomposable = { id: task.id, instruction: task.title, targets: task.context.files, priority: task.priority };
1548
- const { decomposed, tasks: subtasks } = decomposeTask(decomposable, projectDir);
1549
- if (decomposed && subtasks.length > 1) {
1550
- // Replace the broad task with per-file tasks
1551
- await taskStore.update(task.id, { status: 'done' });
1552
- for (const st of subtasks) {
1553
- await taskStore.create({
1554
- title: st.instruction,
1555
- description: st.instruction,
1556
- priority: st.priority ?? 0,
1557
- createdBy: 'ai',
1558
- });
1485
+ for (const t of tasks) {
1486
+ const icon = t.status === 'done' ? '\x1b[32m✓\x1b[0m' : t.status === 'failed' ? '\x1b[31m✗\x1b[0m' : t.status === 'in-progress' ? '\x1b[36m▸\x1b[0m' : '○';
1487
+ console.log(` ${icon} ${(t.id ?? '').slice(0, 8)} ${t.title ?? ''} [${t.status ?? ''}] ${t.assignedProfile ? `→ ${t.assignedProfile}` : ''}`);
1559
1488
  }
1560
- renderer.info(`Decomposed into ${subtasks.length} per-file tasks`);
1561
- continue;
1489
+ break;
1562
1490
  }
1563
-
1564
- // File conflict check: if task targets overlap with files in use, wait
1565
- const taskTargets = task.context.files;
1566
- const hasConflict = taskTargets.some((f: string) => filesInUse.has(f));
1567
- if (hasConflict && running.size > 0) {
1568
- await Promise.race(running.values());
1569
- continue;
1491
+ case 'get': {
1492
+ const id = opts.botTask;
1493
+ if (!id) { console.error('Usage: weaver task get <id>'); return; }
1494
+ const { result } = await handleWeaverTool('fw_weaver_task_get', { id }, projectDir);
1495
+ console.log(result);
1496
+ break;
1570
1497
  }
1571
-
1572
- taskCount++;
1573
- if (opts.verbose) process.env.WEAVER_VERBOSE = '1';
1574
- renderer.taskStart(taskCount, task.title ?? task.id);
1575
-
1576
- await taskStore.update(task.id, { status: 'in-progress' });
1577
- // Reserve files
1578
- for (const f of taskTargets) filesInUse.add(f);
1579
-
1580
- // Launch task (parallel or sequential based on parallelism setting)
1581
- const promise = processTask(task);
1582
- running.set(task.id, promise);
1583
-
1584
- // In sequential mode (parallelism=1), await immediately
1585
- if (parallelism <= 1) {
1586
- await promise;
1498
+ case 'clear': {
1499
+ const filter = opts.confirmFlag ? 'all' : 'completed';
1500
+ const { result } = await handleWeaverTool('fw_weaver_tasks_clear', { filter }, projectDir);
1501
+ const data = JSON.parse(result);
1502
+ console.log(`\x1b[32m→ Cleared ${data.cleared} tasks\x1b[0m`);
1503
+ break;
1587
1504
  }
1505
+ case 'cancel': {
1506
+ const id = opts.botTask;
1507
+ if (!id) { console.error('Usage: weaver task cancel <id>'); return; }
1508
+ await handleWeaverTool('fw_weaver_task_cancel', { id }, projectDir);
1509
+ console.log(`\x1b[33m→ Task ${id} cancelled\x1b[0m`);
1510
+ break;
1511
+ }
1512
+ case 'retry': {
1513
+ const id = opts.botTask;
1514
+ if (!id) { console.error('Usage: weaver task retry <id>'); return; }
1515
+ await handleWeaverTool('fw_weaver_task_retry', { id }, projectDir);
1516
+ console.log(`\x1b[32m→ Task ${id} retried\x1b[0m`);
1517
+ break;
1518
+ }
1519
+ default:
1520
+ // Default: list
1521
+ await handleTask({ ...opts, subcommand: 'list' });
1588
1522
  }
1523
+ }
1589
1524
 
1590
- // Wait for all remaining parallel tasks
1591
- if (running.size > 0) {
1592
- await Promise.allSettled(running.values());
1593
- }
1594
-
1595
- const elapsed = Date.now() - sessionStartTime;
1596
- renderer.sessionEnd({
1597
- tasks: taskCount,
1598
- completed: sessionCompleted,
1599
- failed: sessionFailed,
1600
- totalInputTokens: sessionInputTokens,
1601
- totalOutputTokens: sessionOutputTokens,
1602
- totalCost: sessionCost,
1603
- elapsed,
1604
- });
1605
-
1606
- // Desktop notification on session end (cross-platform)
1607
- if (taskCount > 0) {
1608
- try {
1609
- const { sendDesktopNotification } = await import('./bot/bot-manager.js');
1610
- sendDesktopNotification('Weaver Session Complete', `${sessionCompleted} done, ${sessionFailed} failed, ${sessionNoOp} no-op`);
1611
- } catch { /* non-fatal */ }
1525
+ export async function handleProfile(opts: ParsedArgs): Promise<void> {
1526
+ const projectDir = opts.file || process.cwd();
1527
+ const sub = opts.subcommand;
1528
+
1529
+ switch (sub) {
1530
+ case 'create': {
1531
+ const name = opts.botTask;
1532
+ if (!name) { console.error('Usage: weaver profile create "name" --bot <botId>'); return; }
1533
+ await handleWeaverTool('fw_weaver_profile_create', {
1534
+ name,
1535
+ botId: opts.botId,
1536
+ costStrategy: opts.costStrategy ?? 'balanced',
1537
+ instructions: opts.taskDescription,
1538
+ }, projectDir);
1539
+ console.log(`\x1b[32m→ Profile created\x1b[0m`);
1540
+ break;
1541
+ }
1542
+ case 'delete': {
1543
+ const id = opts.botTask;
1544
+ if (!id) { console.error('Usage: weaver profile delete <id>'); return; }
1545
+ await handleWeaverTool('fw_weaver_profile_delete', { id }, projectDir);
1546
+ console.log(`\x1b[32m→ Profile deleted\x1b[0m`);
1547
+ break;
1548
+ }
1549
+ default: {
1550
+ // Default: list
1551
+ const { result } = await handleWeaverTool('fw_weaver_profile_list', {}, projectDir);
1552
+ const profiles = JSON.parse(result);
1553
+ if (!Array.isArray(profiles) || profiles.length === 0) {
1554
+ console.log(' No profiles.');
1555
+ return;
1556
+ }
1557
+ for (const p of profiles) {
1558
+ const caps = (p.capabilities || []).map((c: { name: string }) => c.name).join(', ');
1559
+ console.log(` ${p.name} (${p.id}) [${p.preferences?.costStrategy ?? 'balanced'}] → ${p.botId} | ${caps || 'no capabilities'}`);
1560
+ }
1561
+ break;
1562
+ }
1612
1563
  }
1564
+ }
1613
1565
 
1614
- // Webhook notification if configured
1615
- if (config?.notify) {
1616
- try {
1617
- const webhookUrl = typeof config.notify === 'string' ? config.notify
1618
- : typeof config.notify === 'object' && 'webhook' in config.notify ? (config.notify as { webhook: string }).webhook
1619
- : null;
1620
- if (webhookUrl) {
1621
- fetch(webhookUrl, {
1622
- method: 'POST',
1623
- headers: { 'Content-Type': 'application/json' },
1624
- body: JSON.stringify({
1625
- event: 'session.completed',
1626
- timestamp: new Date().toISOString(),
1627
- tasks: taskCount,
1628
- completed: sessionCompleted,
1629
- failed: sessionFailed,
1630
- noOp: sessionNoOp,
1631
- tokens: sessionInputTokens + sessionOutputTokens,
1632
- cost: sessionCost,
1633
- elapsed: Date.now() - sessionStartTime,
1634
- }),
1635
- }).catch(() => {}); // fire-and-forget
1636
- }
1637
- } catch { /* non-fatal */ }
1566
+ export async function handleReset(opts: ParsedArgs): Promise<void> {
1567
+ if (!opts.confirmFlag) {
1568
+ console.error('This will delete ALL tasks, profiles, history, and config.');
1569
+ console.error('Use --confirm to proceed: weaver reset --confirm');
1570
+ return;
1638
1571
  }
1572
+ const projectDir = opts.file || process.cwd();
1573
+ const { result } = await handleWeaverTool('fw_weaver_reset_all', {}, projectDir);
1574
+ const data = JSON.parse(result);
1575
+ console.log(`\x1b[32m→ Reset complete\x1b[0m`);
1576
+ console.log(` Tasks cleared: ${data.tasksCleared ?? 0}`);
1577
+ console.log(` Profiles cleared: ${data.profilesCleared ?? 0}`);
1578
+ console.log(` Bots registered: ${data.botsRegistered ?? 0}`);
1579
+ console.log(` Profiles created: ${data.profilesCreated ?? 0}`);
1639
1580
  }
1640
1581
 
1641
1582
  export async function handleAssistant(opts: ParsedArgs): Promise<void> {
@@ -1776,148 +1717,54 @@ export async function handleAssistant(opts: ParsedArgs): Promise<void> {
1776
1717
  }
1777
1718
 
1778
1719
  export async function handleSteer(opts: ParsedArgs): Promise<void> {
1779
- const { SteeringController } = await import('./bot/steering.js');
1780
- const controller = new SteeringController();
1781
-
1782
- const subcommand = opts.botTask;
1783
- if (!subcommand || !['pause', 'resume', 'cancel', 'redirect', 'queue'].includes(subcommand)) {
1784
- console.error('[weaver] Usage: flow-weaver weaver steer <pause|resume|cancel|redirect|queue> [payload]');
1720
+ const projectDir = opts.file || process.cwd();
1721
+ const sub = opts.subcommand;
1722
+ if (!sub || !['pause', 'resume', 'cancel'].includes(sub)) {
1723
+ console.error('[weaver] Usage: flow-weaver weaver steer <pause|resume|cancel> [--bot <id>]');
1785
1724
  process.exit(1);
1786
1725
  }
1787
1726
 
1788
- const command = {
1789
- command: subcommand as 'pause' | 'resume' | 'cancel' | 'redirect' | 'queue',
1790
- payload: opts.botFile,
1791
- timestamp: Date.now(),
1792
- };
1793
-
1794
- await controller.write(command);
1795
- console.log(`[weaver] Steering command sent: ${subcommand}${opts.botFile ? ' "' + opts.botFile + '"' : ''}`);
1727
+ await handleWeaverTool('fw_weaver_steer', {
1728
+ command: sub,
1729
+ botId: opts.botId,
1730
+ }, projectDir);
1731
+ console.log(`\x1b[33m→ Steer: ${sub}${opts.botId ? ` (bot: ${opts.botId})` : ''}\x1b[0m`);
1796
1732
  }
1797
1733
 
1798
- export async function handleQueue(opts: ParsedArgs): Promise<void> {
1799
- const { TaskStore } = await import('./bot/task-store.js');
1800
- const projectDir = process.env.WEAVER_PROJECT_DIR ?? process.cwd();
1801
- const store = new TaskStore(projectDir);
1802
-
1803
- const action = opts.botTask;
1804
- if (!action || !['add', 'list', 'clear', 'remove', 'retry'].includes(action)) {
1805
- console.error('[weaver] Usage: flow-weaver weaver queue <add|list|clear|remove|retry> [task|id]');
1806
- process.exit(1);
1807
- }
1734
+ export async function handleStatus(opts: ParsedArgs): Promise<void> {
1735
+ const projectDir = opts.file || process.cwd();
1808
1736
 
1809
- switch (action) {
1810
- case 'add': {
1811
- const instruction = opts.botFile;
1812
- if (!instruction) {
1813
- console.error('[weaver] Usage: flow-weaver weaver queue add "task instruction"');
1814
- process.exit(1);
1815
- }
1816
- const task = await store.create({ title: instruction, description: instruction, priority: 0, createdBy: 'user' });
1817
- console.log(`[weaver] Task added: ${task.id}`);
1818
- break;
1819
- }
1820
- case 'list': {
1821
- const tasks = await store.list();
1822
- if (opts.historyJson) {
1823
- console.log(JSON.stringify(tasks, null, 2));
1824
- } else if (tasks.length === 0) {
1825
- console.log('No tasks in queue.');
1826
- } else {
1827
- console.log('ID'.padEnd(10) + 'STATUS'.padEnd(14) + 'PRIORITY'.padEnd(10) + 'TITLE');
1828
- for (const t of tasks) {
1829
- console.log(t.id.padEnd(10) + t.status.padEnd(14) + String(t.priority).padEnd(10) + t.title.slice(0, 60));
1830
- }
1831
- }
1832
- break;
1833
- }
1834
- case 'clear': {
1835
- const tasks = await store.list();
1836
- let count = 0;
1837
- for (const t of tasks) {
1838
- await store.update(t.id, { status: 'cancelled' });
1839
- count++;
1840
- }
1841
- console.log(`Cleared ${count} task(s).`);
1842
- break;
1843
- }
1844
- case 'remove': {
1845
- const id = opts.botFile;
1846
- if (!id) {
1847
- console.error('[weaver] Usage: flow-weaver weaver queue remove <id>');
1848
- process.exit(1);
1849
- }
1850
- try {
1851
- await store.update(id, { status: 'cancelled' });
1852
- console.log(`Cancelled task ${id}.`);
1853
- } catch {
1854
- console.log(`No task found with id "${id}".`);
1855
- }
1856
- break;
1857
- }
1858
- case 'retry': {
1859
- const id = opts.botFile;
1860
- if (id) {
1861
- try {
1862
- await store.update(id, { status: 'pending' });
1863
- console.log(`Task ${id} reset to pending.`);
1864
- } catch {
1865
- console.log(`No task found with id "${id}".`);
1866
- }
1867
- } else {
1868
- const failed = await store.list({ status: 'failed' });
1869
- let count = 0;
1870
- for (const t of failed) {
1871
- await store.update(t.id, { status: 'pending' });
1872
- count++;
1873
- }
1874
- console.log(`Reset ${count} failed task(s) to pending.`);
1875
- }
1876
- break;
1877
- }
1878
- }
1879
- }
1737
+ // Get swarm status
1738
+ const { result: swarmResult } = await handleWeaverTool('fw_weaver_swarm_status', {}, projectDir);
1739
+ const swarm = JSON.parse(swarmResult);
1880
1740
 
1881
- export async function handleStatus(opts: ParsedArgs): Promise<void> {
1882
- const store = new RunStore();
1883
- const { TaskStore } = await import('./bot/task-store.js');
1884
- const projectDir = process.env.WEAVER_PROJECT_DIR ?? process.cwd();
1885
- const taskStore = new TaskStore(projectDir);
1886
-
1887
- const orphans = store.checkOrphans();
1888
- const recentRuns = store.list({ limit: 5 });
1889
- const tasks = await taskStore.list();
1890
- const pending = tasks.filter(t => t.status === 'pending').length;
1891
- const running = tasks.filter(t => t.status === 'in-progress').length;
1892
- const completed = tasks.filter(t => t.status === 'done').length;
1893
- const failed = tasks.filter(t => t.status === 'failed').length;
1741
+ // Get task list
1742
+ const { result: taskResult } = await handleWeaverTool('fw_weaver_task_list', { limit: 10 }, projectDir);
1743
+ const tasks = JSON.parse(taskResult);
1894
1744
 
1895
1745
  if (opts.historyJson) {
1896
- console.log(JSON.stringify({
1897
- queue: { pending, running, completed, failed, total: tasks.length },
1898
- orphanedRuns: orphans.length,
1899
- recentRuns: recentRuns.map(r => ({
1900
- id: r.id, outcome: r.outcome, summary: r.summary,
1901
- startedAt: r.startedAt, durationMs: r.durationMs,
1902
- })),
1903
- }, null, 2));
1746
+ console.log(JSON.stringify({ swarm, tasks }, null, 2));
1904
1747
  return;
1905
1748
  }
1906
1749
 
1907
- console.log('\n\x1b[1mWeaver Status\x1b[0m\n');
1908
- console.log(` Queue: ${pending} pending, ${running} running, ${completed} completed, ${failed} failed`);
1909
- if (orphans.length > 0) {
1910
- console.log(` \x1b[33mOrphaned runs: ${orphans.length} (recovered)\x1b[0m`);
1911
- }
1750
+ console.log(`\n\x1b[1mWeaver Status\x1b[0m\n`);
1751
+ console.log(` Swarm: ${colorStatus(swarm.status ?? 'unknown')}`);
1752
+ if (swarm.maxConcurrent !== undefined) console.log(` Max concurrent: ${swarm.maxConcurrent}`);
1753
+
1754
+ if (Array.isArray(tasks) && tasks.length > 0) {
1755
+ const pending = tasks.filter((t: { status: string }) => t.status === 'pending').length;
1756
+ const running = tasks.filter((t: { status: string }) => t.status === 'in-progress').length;
1757
+ const done = tasks.filter((t: { status: string }) => t.status === 'done').length;
1758
+ const failed = tasks.filter((t: { status: string }) => t.status === 'failed').length;
1759
+ console.log(` Tasks: ${pending} pending, ${running} running, ${done} done, ${failed} failed`);
1912
1760
 
1913
- if (recentRuns.length > 0) {
1914
- console.log(`\n Recent runs:`);
1915
- for (const r of recentRuns) {
1916
- const icon = r.success ? '\x1b[32m✓\x1b[0m' : '\x1b[31m✗\x1b[0m';
1917
- console.log(` ${icon} ${r.id.slice(0, 8)} ${r.outcome.padEnd(9)} ${formatDuration(r.durationMs).padEnd(8)} ${r.summary.slice(0, 60)}`);
1761
+ console.log(`\n Recent tasks:`);
1762
+ for (const t of tasks.slice(0, 5)) {
1763
+ const icon = t.status === 'done' ? '\x1b[32m✓\x1b[0m' : t.status === 'failed' ? '\x1b[31m✗\x1b[0m' : t.status === 'in-progress' ? '\x1b[36m▸\x1b[0m' : '○';
1764
+ console.log(` ${icon} ${(t.id ?? '').slice(0, 8)} [${t.status}] ${(t.title ?? '').slice(0, 60)}`);
1918
1765
  }
1919
1766
  } else {
1920
- console.log('\n No recent runs.');
1767
+ console.log(' Tasks: none');
1921
1768
  }
1922
1769
  console.log('');
1923
1770
  }