@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.
@@ -11,6 +11,7 @@ import { PipelineRunner } from './bot/pipeline-runner.js';
11
11
  import { DashboardServer } from './bot/dashboard.js';
12
12
  import { openBrowser } from './bot/utils.js';
13
13
  import { AuditStore } from './bot/audit-store.js';
14
+ import { handleWeaverTool } from './ai-chat-provider.js';
14
15
  export function parseArgs(argv) {
15
16
  const result = {
16
17
  command: 'run',
@@ -33,7 +34,6 @@ export function parseArgs(argv) {
33
34
  autoApprove: false,
34
35
  genesisInit: false,
35
36
  genesisWatch: false,
36
- sessionContinuous: false,
37
37
  };
38
38
  const args = argv.slice(2);
39
39
  let i = 0;
@@ -169,14 +169,50 @@ export function parseArgs(argv) {
169
169
  }
170
170
  else if (arg === 'bot') {
171
171
  result.command = 'bot';
172
- // Next non-flag arg is the task string
172
+ // Next non-flag arg is the task string or subcommand
173
173
  if (i + 1 < args.length && !args[i + 1].startsWith('-')) {
174
174
  i++;
175
- result.botTask = args[i];
175
+ result.subcommand = args[i];
176
+ // For non-registry subcommands, treat as task string
177
+ if (!['list', 'register', 'validate'].includes(result.subcommand)) {
178
+ result.botTask = result.subcommand;
179
+ result.subcommand = undefined;
180
+ }
181
+ }
182
+ }
183
+ else if (arg === 'swarm') {
184
+ result.command = 'swarm';
185
+ if (i + 1 < args.length && !args[i + 1].startsWith('-')) {
186
+ i++;
187
+ result.subcommand = args[i];
188
+ }
189
+ }
190
+ else if (arg === 'task') {
191
+ result.command = 'task';
192
+ if (i + 1 < args.length && !args[i + 1].startsWith('-')) {
193
+ i++;
194
+ result.subcommand = args[i];
195
+ // For create/get/cancel/retry: next positional is the title/id
196
+ if (['create', 'get', 'cancel', 'retry'].includes(result.subcommand) && i + 1 < args.length && !args[i + 1].startsWith('-')) {
197
+ i++;
198
+ result.botTask = args[i];
199
+ }
200
+ }
201
+ }
202
+ else if (arg === 'profile') {
203
+ result.command = 'profile';
204
+ if (i + 1 < args.length && !args[i + 1].startsWith('-')) {
205
+ i++;
206
+ result.subcommand = args[i];
207
+ // For create/delete: next positional is the name/id
208
+ if (['create', 'delete'].includes(result.subcommand) && i + 1 < args.length && !args[i + 1].startsWith('-')) {
209
+ i++;
210
+ result.botTask = args[i];
211
+ }
176
212
  }
177
213
  }
178
- else if (arg === 'session') {
179
- result.command = 'session';
214
+ else if (arg === 'reset') {
215
+ result.command = 'reset';
180
216
  }
181
217
  else if (arg === 'assistant') {
182
218
  result.command = 'assistant';
@@ -192,28 +228,10 @@ export function parseArgs(argv) {
192
228
  }
193
229
  else if (arg === 'steer') {
194
230
  result.command = 'steer';
195
- // Next arg is the subcommand
231
+ // Next arg is the subcommand (pause/resume/cancel)
196
232
  if (i + 1 < args.length && !args[i + 1].startsWith('-')) {
197
233
  i++;
198
- result.botTask = args[i];
199
- // Next arg after redirect/queue is payload
200
- if ((args[i] === 'redirect' || args[i] === 'queue') && i + 1 < args.length && !args[i + 1].startsWith('-')) {
201
- i++;
202
- result.botFile = args[i];
203
- }
204
- }
205
- }
206
- else if (arg === 'queue') {
207
- result.command = 'queue';
208
- // Next arg is action (add/list/clear/remove)
209
- if (i + 1 < args.length && !args[i + 1].startsWith('-')) {
210
- i++;
211
- result.botTask = args[i];
212
- // Next arg is task/id
213
- if (i + 1 < args.length && !args[i + 1].startsWith('-')) {
214
- i++;
215
- result.botFile = args[i];
216
- }
234
+ result.subcommand = args[i];
217
235
  }
218
236
  }
219
237
  else if (arg === '--file' && i + 1 < args.length) {
@@ -279,20 +297,47 @@ export function parseArgs(argv) {
279
297
  i++;
280
298
  result.assistantDelete = args[i];
281
299
  }
282
- else if (arg === '--continuous') {
283
- result.sessionContinuous = true;
300
+ else if (arg === '--max-concurrent' && i + 1 < args.length) {
301
+ i++;
302
+ result.maxConcurrent = parseInt(args[i], 10) || undefined;
303
+ }
304
+ else if (arg === '--budget-tokens' && i + 1 < args.length) {
305
+ i++;
306
+ result.budgetTokens = parseInt(args[i], 10) || undefined;
307
+ }
308
+ else if (arg === '--budget-cost' && i + 1 < args.length) {
309
+ i++;
310
+ result.budgetCost = parseFloat(args[i]) || undefined;
284
311
  }
285
- else if (arg === '--until' && i + 1 < args.length) {
312
+ else if (arg === '--profile' && i + 1 < args.length) {
286
313
  i++;
287
- result.sessionUntil = args[i];
314
+ result.profileId = args[i];
288
315
  }
289
- else if (arg === '--max-tasks' && i + 1 < args.length) {
316
+ else if (arg === '--cost-strategy' && i + 1 < args.length) {
290
317
  i++;
291
- result.sessionMaxTasks = parseInt(args[i], 10) || undefined;
318
+ result.costStrategy = args[i];
292
319
  }
293
- else if (arg === '--parallel' && i + 1 < args.length) {
320
+ else if (arg === '--bot' && i + 1 < args.length) {
294
321
  i++;
295
- result.sessionParallel = Math.min(Math.max(parseInt(args[i], 10) || 1, 1), 5);
322
+ result.botId = args[i];
323
+ }
324
+ else if (arg === '--confirm') {
325
+ result.confirmFlag = true;
326
+ }
327
+ else if (arg === '--description' && i + 1 < args.length) {
328
+ i++;
329
+ result.taskDescription = args[i];
330
+ }
331
+ else if (arg === '--complexity' && i + 1 < args.length) {
332
+ i++;
333
+ result.complexity = args[i];
334
+ }
335
+ else if (arg === '--auto-retry') {
336
+ result.autoRetry = true;
337
+ }
338
+ else if (arg === '--status' && i + 1 < args.length) {
339
+ i++;
340
+ result.historyOutcome = args[i];
296
341
  }
297
342
  else if (arg === '--project-dir' && i + 1 < args.length) {
298
343
  i++;
@@ -1260,286 +1305,213 @@ export async function handleBot(opts) {
1260
1305
  process.exit(1);
1261
1306
  }
1262
1307
  }
1263
- export async function handleSession(opts) {
1264
- const projectDir = opts.file ?? process.cwd();
1265
- // Set project dir for per-project queue isolation
1266
- process.env.WEAVER_PROJECT_DIR = projectDir;
1267
- const config = await loadConfig(opts.configPath);
1268
- const workflowPath = resolveWorkflowPath('agent', projectDir);
1269
- // Create terminal renderer for all session output
1270
- const { TerminalRenderer } = await import('./bot/terminal-renderer.js');
1271
- const renderer = new TerminalRenderer({ verbose: opts.verbose, quiet: opts.quiet });
1272
- // Parse --until HH:MM into a deadline timestamp
1273
- let deadline;
1274
- let deadlineStr;
1275
- if (opts.sessionUntil) {
1276
- const match = opts.sessionUntil.match(/^(\d{1,2}):(\d{2})$/);
1277
- if (match) {
1278
- const now = new Date();
1279
- const target = new Date(now);
1280
- target.setHours(parseInt(match[1], 10), parseInt(match[2], 10), 0, 0);
1281
- if (target.getTime() <= now.getTime())
1282
- target.setDate(target.getDate() + 1);
1283
- deadline = target.getTime();
1284
- deadlineStr = target.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
1285
- }
1286
- }
1287
- const maxTasks = opts.sessionMaxTasks ?? Infinity;
1288
- const continuous = opts.sessionContinuous || !!opts.sessionUntil || maxTasks < Infinity;
1289
- const parallelism = opts.sessionParallel ?? 1;
1290
- // Crash recovery
1291
- if (continuous) {
1292
- const { TaskStore } = await import('./bot/task-store.js');
1293
- const recoveryStore = new TaskStore(projectDir);
1294
- // Recover orphaned in-progress tasks back to pending
1295
- const inProgressTasks = await recoveryStore.list({ status: 'in-progress' });
1296
- let recovered = 0;
1297
- for (const t of inProgressTasks) {
1298
- await recoveryStore.update(t.id, { status: 'pending' });
1299
- recovered++;
1300
- }
1301
- if (recovered > 0)
1302
- renderer.info(`Recovered ${recovered} orphaned task(s)`);
1303
- }
1304
- // Clean stale cache files
1305
- try {
1306
- const { execSync: execSyncClean } = await import('node:child_process');
1307
- const staleOutput = execSyncClean(`find "${projectDir}" -name "fw-exec-*" -type f`, { encoding: 'utf-8', timeout: 5000 }).trim();
1308
- if (staleOutput) {
1309
- const staleFiles = staleOutput.split('\n').filter(Boolean);
1310
- for (const f of staleFiles) {
1311
- try {
1312
- fs.unlinkSync(f);
1313
- }
1314
- catch { }
1315
- }
1308
+ // --- Swarm / Task / Profile / Reset handlers (thin wrappers around handleWeaverTool) ---
1309
+ function colorStatus(status) {
1310
+ switch (status) {
1311
+ case 'running': return '\x1b[32m' + status + '\x1b[0m';
1312
+ case 'paused': return '\x1b[33m' + status + '\x1b[0m';
1313
+ case 'stopped':
1314
+ case 'idle': return '\x1b[90m' + status + '\x1b[0m';
1315
+ default: return status;
1316
+ }
1317
+ }
1318
+ export async function handleSwarm(opts) {
1319
+ const projectDir = opts.file || process.cwd();
1320
+ const sub = opts.subcommand;
1321
+ if (!sub || sub === 'status') {
1322
+ const { result } = await handleWeaverTool('fw_weaver_swarm_status', {}, projectDir);
1323
+ const data = JSON.parse(result);
1324
+ console.log(`\x1b[1mSwarm: ${colorStatus(data.status)}\x1b[0m`);
1325
+ if (data.instances !== undefined)
1326
+ console.log(` Instances: ${data.instances}`);
1327
+ if (data.maxConcurrent !== undefined)
1328
+ console.log(` Max concurrent: ${data.maxConcurrent}`);
1329
+ if (data.taskCounts) {
1330
+ const tc = data.taskCounts;
1331
+ console.log(` Tasks: ${tc.pending ?? 0} pending, ${tc.running ?? 0} running, ${tc.done ?? 0} done, ${tc.failed ?? 0} failed`);
1332
+ }
1333
+ if (data.budget) {
1334
+ const b = data.budget;
1335
+ if (b.tokensUsed !== undefined)
1336
+ console.log(` Tokens: ${b.tokensUsed.toLocaleString()} / ${(b.tokensLimit ?? '∞').toLocaleString()}`);
1337
+ if (b.costUsed !== undefined)
1338
+ console.log(` Cost: $${b.costUsed.toFixed(4)} / $${(b.costLimit ?? '∞')}`);
1316
1339
  }
1340
+ return;
1317
1341
  }
1318
- catch { /* non-fatal */ }
1319
- // Detect provider label for session start
1320
- const providerType = config?.provider ?? 'auto';
1321
- const providerLabel = typeof providerType === 'object' ? providerType.name : String(providerType);
1322
- const sessionStartTime = Date.now();
1323
- renderer.sessionStart({ provider: providerLabel, parallel: parallelism, deadline: deadlineStr });
1324
- // Single-run mode (backwards compatible)
1325
- if (!continuous) {
1326
- try {
1327
- const result = await runWorkflow(workflowPath, {
1328
- params: { projectDir },
1329
- verbose: opts.verbose,
1330
- dryRun: opts.dryRun,
1331
- config,
1332
- });
1333
- if (!opts.quiet) {
1334
- const color = result.success ? '\x1b[32m' : '\x1b[31m';
1335
- console.log(`${color}Session: ${result.outcome}\x1b[0m`);
1336
- }
1337
- process.exit(result.success ? 0 : 1);
1342
+ switch (sub) {
1343
+ case 'start': {
1344
+ await handleWeaverTool('fw_weaver_swarm_start', {
1345
+ maxConcurrent: opts.maxConcurrent,
1346
+ sessionBudgetTokens: opts.budgetTokens,
1347
+ sessionBudgetCost: opts.budgetCost,
1348
+ }, projectDir);
1349
+ console.log('\x1b[32m→ Swarm started\x1b[0m');
1350
+ break;
1338
1351
  }
1339
- catch (err) {
1340
- const msg = err instanceof Error ? err.message : String(err);
1341
- console.error(`\x1b[31m[weaver] Fatal: ${msg}\x1b[0m`);
1342
- process.exit(1);
1352
+ case 'stop': {
1353
+ await handleWeaverTool('fw_weaver_swarm_stop', {}, projectDir);
1354
+ console.log('\x1b[33m→ Swarm stopped\x1b[0m');
1355
+ break;
1343
1356
  }
1344
- return;
1357
+ case 'pause': {
1358
+ await handleWeaverTool('fw_weaver_swarm_pause', {}, projectDir);
1359
+ console.log('\x1b[33m→ Swarm paused\x1b[0m');
1360
+ break;
1361
+ }
1362
+ case 'config': {
1363
+ await handleWeaverTool('fw_weaver_swarm_config', {
1364
+ maxConcurrent: opts.maxConcurrent,
1365
+ workspaceBudgetTokens: opts.budgetTokens,
1366
+ workspaceBudgetCost: opts.budgetCost,
1367
+ autoRetry: opts.autoRetry,
1368
+ }, projectDir);
1369
+ console.log('\x1b[32m→ Config updated\x1b[0m');
1370
+ break;
1371
+ }
1372
+ default:
1373
+ console.error(`Unknown swarm command: ${sub}. Use: start, stop, pause, status, config`);
1345
1374
  }
1346
- // Continuous mode: loop until deadline/maxTasks/interrupt
1347
- const { TaskStore } = await import('./bot/task-store.js');
1348
- const { isTransientError, getErrorGuidance } = await import('./bot/error-classifier.js');
1349
- const taskStore = new TaskStore(projectDir);
1350
- let taskCount = 0;
1351
- let interrupted = false;
1352
- let consecutiveErrors = 0;
1353
- let consecutiveNoOps = 0;
1354
- const MAX_CONSECUTIVE_ERRORS = 3;
1355
- const MAX_CONSECUTIVE_NO_OPS = 5;
1356
- // Session stats
1357
- let sessionCompleted = 0, sessionFailed = 0, sessionNoOp = 0;
1358
- let sessionInputTokens = 0, sessionOutputTokens = 0, sessionCost = 0;
1359
- process.on('SIGINT', () => { interrupted = true; });
1360
- process.on('SIGTERM', () => { interrupted = true; });
1361
- // Parallel task tracking
1362
- const running = new Map();
1363
- const filesInUse = new Set();
1364
- const processTask = async (task) => {
1365
- const taskPayload = { id: task.id, instruction: task.title, description: task.description, targets: task.context.files };
1366
- try {
1367
- const result = await runWorkflow(workflowPath, {
1368
- params: { projectDir, taskJson: JSON.stringify(taskPayload) },
1369
- verbose: opts.verbose,
1370
- dryRun: opts.dryRun,
1371
- config,
1372
- });
1373
- // Classify outcome: the summary contains "no changes" or "0 files" for no-ops
1374
- const isNoOp = result.success && (result.summary.includes('no changes') ||
1375
- result.summary.includes('0 file') ||
1376
- result.summary.includes("doesn't exist") ||
1377
- result.summary.includes('does not exist') ||
1378
- result.summary.includes('nothing to') ||
1379
- result.outcome === 'no-op');
1380
- if (isNoOp) {
1381
- await taskStore.update(task.id, { status: 'done' });
1382
- sessionNoOp++;
1383
- consecutiveNoOps++;
1384
- consecutiveErrors = 0;
1385
- }
1386
- else if (result.success) {
1387
- await taskStore.update(task.id, { status: 'done' });
1388
- sessionCompleted++;
1389
- consecutiveErrors = 0;
1390
- consecutiveNoOps = 0;
1375
+ }
1376
+ export async function handleTask(opts) {
1377
+ const projectDir = opts.file || process.cwd();
1378
+ const sub = opts.subcommand;
1379
+ switch (sub) {
1380
+ case 'create': {
1381
+ const title = opts.botTask;
1382
+ if (!title) {
1383
+ console.error('Usage: weaver task create "title"');
1384
+ return;
1391
1385
  }
1392
- else {
1393
- await taskStore.update(task.id, { status: 'failed' });
1394
- sessionFailed++;
1395
- consecutiveErrors++;
1396
- consecutiveNoOps = 0;
1386
+ const { result } = await handleWeaverTool('fw_weaver_task_create', {
1387
+ title,
1388
+ description: opts.taskDescription,
1389
+ priority: opts.historyOutcome, // reuse for priority if needed
1390
+ assignedProfile: opts.profileId,
1391
+ complexity: opts.complexity,
1392
+ }, projectDir);
1393
+ const data = JSON.parse(result);
1394
+ console.log(`\x1b[32m→ Task created: ${data.task?.id ?? data.id ?? 'unknown'}\x1b[0m`);
1395
+ break;
1396
+ }
1397
+ case 'list': {
1398
+ const { result } = await handleWeaverTool('fw_weaver_task_list', {
1399
+ status: opts.historyOutcome,
1400
+ limit: opts.historyLimit,
1401
+ }, projectDir);
1402
+ const tasks = JSON.parse(result);
1403
+ if (!Array.isArray(tasks) || tasks.length === 0) {
1404
+ console.log(' No tasks.');
1405
+ return;
1397
1406
  }
1398
- // Track cost from workflow result
1399
- if (result.cost) {
1400
- sessionInputTokens += result.cost.totalInputTokens ?? 0;
1401
- sessionOutputTokens += result.cost.totalOutputTokens ?? 0;
1402
- sessionCost += result.cost.totalCost ?? 0;
1407
+ for (const t of tasks) {
1408
+ const icon = t.status === 'done' ? '\x1b[32m✓\x1b[0m' : t.status === 'failed' ? '\x1b[31m✗\x1b[0m' : t.status === 'in-progress' ? '\x1b[36m▸\x1b[0m' : '○';
1409
+ console.log(` ${icon} ${(t.id ?? '').slice(0, 8)} ${t.title ?? ''} [${t.status ?? ''}] ${t.assignedProfile ? `→ ${t.assignedProfile}` : ''}`);
1403
1410
  }
1411
+ break;
1404
1412
  }
1405
- catch (err) {
1406
- const msg = err instanceof Error ? err.message : String(err);
1407
- const guidance = getErrorGuidance(msg);
1408
- renderer.error(`Task ${task.id.slice(0, 8)} error`, guidance ? `${msg}\n Hint: ${guidance}` : msg);
1409
- await taskStore.update(task.id, { status: 'failed' });
1410
- sessionFailed++;
1411
- if (!isTransientError(err)) {
1412
- consecutiveErrors++;
1413
+ case 'get': {
1414
+ const id = opts.botTask;
1415
+ if (!id) {
1416
+ console.error('Usage: weaver task get <id>');
1417
+ return;
1413
1418
  }
1414
- }
1415
- finally {
1416
- for (const f of task.context.files)
1417
- filesInUse.delete(f);
1418
- running.delete(task.id);
1419
- }
1420
- };
1421
- while (taskCount < maxTasks && !interrupted) {
1422
- if (deadline && Date.now() >= deadline) {
1423
- renderer.info('Deadline reached, stopping session.');
1419
+ const { result } = await handleWeaverTool('fw_weaver_task_get', { id }, projectDir);
1420
+ console.log(result);
1424
1421
  break;
1425
1422
  }
1426
- if (consecutiveErrors >= MAX_CONSECUTIVE_ERRORS) {
1427
- renderer.error('Session stopped', `${MAX_CONSECUTIVE_ERRORS} consecutive errors check your API key or provider config.`);
1423
+ case 'clear': {
1424
+ const filter = opts.confirmFlag ? 'all' : 'completed';
1425
+ const { result } = await handleWeaverTool('fw_weaver_tasks_clear', { filter }, projectDir);
1426
+ const data = JSON.parse(result);
1427
+ console.log(`\x1b[32m→ Cleared ${data.cleared} tasks\x1b[0m`);
1428
1428
  break;
1429
1429
  }
1430
- // Pause on consecutive no-ops (bot is spinning without doing anything)
1431
- if (consecutiveNoOps >= MAX_CONSECUTIVE_NO_OPS) {
1432
- renderer.warn(`${MAX_CONSECUTIVE_NO_OPS} consecutive no-op tasks — pausing 60s`);
1433
- await new Promise(r => setTimeout(r, 60_000));
1434
- consecutiveNoOps = 0;
1435
- }
1436
- // Wait if at capacity
1437
- if (running.size >= parallelism) {
1438
- await Promise.race(running.values());
1439
- continue;
1440
- }
1441
- const pendingTasks = await taskStore.list({ status: 'pending', limit: 1 });
1442
- const task = pendingTasks[0] ?? null;
1443
- if (!task) {
1444
- if (running.size > 0) {
1445
- // Tasks still running — wait for one to finish
1446
- await Promise.race(running.values());
1447
- continue;
1430
+ case 'cancel': {
1431
+ const id = opts.botTask;
1432
+ if (!id) {
1433
+ console.error('Usage: weaver task cancel <id>');
1434
+ return;
1448
1435
  }
1449
- // No pending or running tasks — wait and retry
1450
- await new Promise(r => setTimeout(r, 5_000));
1451
- continue;
1436
+ await handleWeaverTool('fw_weaver_task_cancel', { id }, projectDir);
1437
+ console.log(`\x1b[33m→ Task ${id} cancelled\x1b[0m`);
1438
+ break;
1452
1439
  }
1453
- // Auto-decompose broad tasks into per-file tasks
1454
- const { decomposeTask } = await import('./bot/task-decomposer.js');
1455
- const decomposable = { id: task.id, instruction: task.title, targets: task.context.files, priority: task.priority };
1456
- const { decomposed, tasks: subtasks } = decomposeTask(decomposable, projectDir);
1457
- if (decomposed && subtasks.length > 1) {
1458
- // Replace the broad task with per-file tasks
1459
- await taskStore.update(task.id, { status: 'done' });
1460
- for (const st of subtasks) {
1461
- await taskStore.create({
1462
- title: st.instruction,
1463
- description: st.instruction,
1464
- priority: st.priority ?? 0,
1465
- createdBy: 'ai',
1466
- });
1440
+ case 'retry': {
1441
+ const id = opts.botTask;
1442
+ if (!id) {
1443
+ console.error('Usage: weaver task retry <id>');
1444
+ return;
1467
1445
  }
1468
- renderer.info(`Decomposed into ${subtasks.length} per-file tasks`);
1469
- continue;
1446
+ await handleWeaverTool('fw_weaver_task_retry', { id }, projectDir);
1447
+ console.log(`\x1b[32m→ Task ${id} retried\x1b[0m`);
1448
+ break;
1470
1449
  }
1471
- // File conflict check: if task targets overlap with files in use, wait
1472
- const taskTargets = task.context.files;
1473
- const hasConflict = taskTargets.some((f) => filesInUse.has(f));
1474
- if (hasConflict && running.size > 0) {
1475
- await Promise.race(running.values());
1476
- continue;
1450
+ default:
1451
+ // Default: list
1452
+ await handleTask({ ...opts, subcommand: 'list' });
1453
+ }
1454
+ }
1455
+ export async function handleProfile(opts) {
1456
+ const projectDir = opts.file || process.cwd();
1457
+ const sub = opts.subcommand;
1458
+ switch (sub) {
1459
+ case 'create': {
1460
+ const name = opts.botTask;
1461
+ if (!name) {
1462
+ console.error('Usage: weaver profile create "name" --bot <botId>');
1463
+ return;
1464
+ }
1465
+ await handleWeaverTool('fw_weaver_profile_create', {
1466
+ name,
1467
+ botId: opts.botId,
1468
+ costStrategy: opts.costStrategy ?? 'balanced',
1469
+ instructions: opts.taskDescription,
1470
+ }, projectDir);
1471
+ console.log(`\x1b[32m→ Profile created\x1b[0m`);
1472
+ break;
1477
1473
  }
1478
- taskCount++;
1479
- if (opts.verbose)
1480
- process.env.WEAVER_VERBOSE = '1';
1481
- renderer.taskStart(taskCount, task.title ?? task.id);
1482
- await taskStore.update(task.id, { status: 'in-progress' });
1483
- // Reserve files
1484
- for (const f of taskTargets)
1485
- filesInUse.add(f);
1486
- // Launch task (parallel or sequential based on parallelism setting)
1487
- const promise = processTask(task);
1488
- running.set(task.id, promise);
1489
- // In sequential mode (parallelism=1), await immediately
1490
- if (parallelism <= 1) {
1491
- await promise;
1492
- }
1493
- }
1494
- // Wait for all remaining parallel tasks
1495
- if (running.size > 0) {
1496
- await Promise.allSettled(running.values());
1497
- }
1498
- const elapsed = Date.now() - sessionStartTime;
1499
- renderer.sessionEnd({
1500
- tasks: taskCount,
1501
- completed: sessionCompleted,
1502
- failed: sessionFailed,
1503
- totalInputTokens: sessionInputTokens,
1504
- totalOutputTokens: sessionOutputTokens,
1505
- totalCost: sessionCost,
1506
- elapsed,
1507
- });
1508
- // Desktop notification on session end (cross-platform)
1509
- if (taskCount > 0) {
1510
- try {
1511
- const { sendDesktopNotification } = await import('./bot/bot-manager.js');
1512
- sendDesktopNotification('Weaver Session Complete', `${sessionCompleted} done, ${sessionFailed} failed, ${sessionNoOp} no-op`);
1474
+ case 'delete': {
1475
+ const id = opts.botTask;
1476
+ if (!id) {
1477
+ console.error('Usage: weaver profile delete <id>');
1478
+ return;
1479
+ }
1480
+ await handleWeaverTool('fw_weaver_profile_delete', { id }, projectDir);
1481
+ console.log(`\x1b[32m→ Profile deleted\x1b[0m`);
1482
+ break;
1513
1483
  }
1514
- catch { /* non-fatal */ }
1515
- }
1516
- // Webhook notification if configured
1517
- if (config?.notify) {
1518
- try {
1519
- const webhookUrl = typeof config.notify === 'string' ? config.notify
1520
- : typeof config.notify === 'object' && 'webhook' in config.notify ? config.notify.webhook
1521
- : null;
1522
- if (webhookUrl) {
1523
- fetch(webhookUrl, {
1524
- method: 'POST',
1525
- headers: { 'Content-Type': 'application/json' },
1526
- body: JSON.stringify({
1527
- event: 'session.completed',
1528
- timestamp: new Date().toISOString(),
1529
- tasks: taskCount,
1530
- completed: sessionCompleted,
1531
- failed: sessionFailed,
1532
- noOp: sessionNoOp,
1533
- tokens: sessionInputTokens + sessionOutputTokens,
1534
- cost: sessionCost,
1535
- elapsed: Date.now() - sessionStartTime,
1536
- }),
1537
- }).catch(() => { }); // fire-and-forget
1484
+ default: {
1485
+ // Default: list
1486
+ const { result } = await handleWeaverTool('fw_weaver_profile_list', {}, projectDir);
1487
+ const profiles = JSON.parse(result);
1488
+ if (!Array.isArray(profiles) || profiles.length === 0) {
1489
+ console.log(' No profiles.');
1490
+ return;
1538
1491
  }
1492
+ for (const p of profiles) {
1493
+ const caps = (p.capabilities || []).map((c) => c.name).join(', ');
1494
+ console.log(` ${p.name} (${p.id}) [${p.preferences?.costStrategy ?? 'balanced'}] → ${p.botId} | ${caps || 'no capabilities'}`);
1495
+ }
1496
+ break;
1539
1497
  }
1540
- catch { /* non-fatal */ }
1541
1498
  }
1542
1499
  }
1500
+ export async function handleReset(opts) {
1501
+ if (!opts.confirmFlag) {
1502
+ console.error('This will delete ALL tasks, profiles, history, and config.');
1503
+ console.error('Use --confirm to proceed: weaver reset --confirm');
1504
+ return;
1505
+ }
1506
+ const projectDir = opts.file || process.cwd();
1507
+ const { result } = await handleWeaverTool('fw_weaver_reset_all', {}, projectDir);
1508
+ const data = JSON.parse(result);
1509
+ console.log(`\x1b[32m→ Reset complete\x1b[0m`);
1510
+ console.log(` Tasks cleared: ${data.tasksCleared ?? 0}`);
1511
+ console.log(` Profiles cleared: ${data.profilesCleared ?? 0}`);
1512
+ console.log(` Bots registered: ${data.botsRegistered ?? 0}`);
1513
+ console.log(` Profiles created: ${data.profilesCreated ?? 0}`);
1514
+ }
1543
1515
  export async function handleAssistant(opts) {
1544
1516
  const projectDir = opts.file ?? process.cwd();
1545
1517
  // Handle --list and --delete before creating provider
@@ -1673,143 +1645,48 @@ export async function handleAssistant(opts) {
1673
1645
  });
1674
1646
  }
1675
1647
  export async function handleSteer(opts) {
1676
- const { SteeringController } = await import('./bot/steering.js');
1677
- const controller = new SteeringController();
1678
- const subcommand = opts.botTask;
1679
- if (!subcommand || !['pause', 'resume', 'cancel', 'redirect', 'queue'].includes(subcommand)) {
1680
- console.error('[weaver] Usage: flow-weaver weaver steer <pause|resume|cancel|redirect|queue> [payload]');
1648
+ const projectDir = opts.file || process.cwd();
1649
+ const sub = opts.subcommand;
1650
+ if (!sub || !['pause', 'resume', 'cancel'].includes(sub)) {
1651
+ console.error('[weaver] Usage: flow-weaver weaver steer <pause|resume|cancel> [--bot <id>]');
1681
1652
  process.exit(1);
1682
1653
  }
1683
- const command = {
1684
- command: subcommand,
1685
- payload: opts.botFile,
1686
- timestamp: Date.now(),
1687
- };
1688
- await controller.write(command);
1689
- console.log(`[weaver] Steering command sent: ${subcommand}${opts.botFile ? ' "' + opts.botFile + '"' : ''}`);
1690
- }
1691
- export async function handleQueue(opts) {
1692
- const { TaskStore } = await import('./bot/task-store.js');
1693
- const projectDir = process.env.WEAVER_PROJECT_DIR ?? process.cwd();
1694
- const store = new TaskStore(projectDir);
1695
- const action = opts.botTask;
1696
- if (!action || !['add', 'list', 'clear', 'remove', 'retry'].includes(action)) {
1697
- console.error('[weaver] Usage: flow-weaver weaver queue <add|list|clear|remove|retry> [task|id]');
1698
- process.exit(1);
1699
- }
1700
- switch (action) {
1701
- case 'add': {
1702
- const instruction = opts.botFile;
1703
- if (!instruction) {
1704
- console.error('[weaver] Usage: flow-weaver weaver queue add "task instruction"');
1705
- process.exit(1);
1706
- }
1707
- const task = await store.create({ title: instruction, description: instruction, priority: 0, createdBy: 'user' });
1708
- console.log(`[weaver] Task added: ${task.id}`);
1709
- break;
1710
- }
1711
- case 'list': {
1712
- const tasks = await store.list();
1713
- if (opts.historyJson) {
1714
- console.log(JSON.stringify(tasks, null, 2));
1715
- }
1716
- else if (tasks.length === 0) {
1717
- console.log('No tasks in queue.');
1718
- }
1719
- else {
1720
- console.log('ID'.padEnd(10) + 'STATUS'.padEnd(14) + 'PRIORITY'.padEnd(10) + 'TITLE');
1721
- for (const t of tasks) {
1722
- console.log(t.id.padEnd(10) + t.status.padEnd(14) + String(t.priority).padEnd(10) + t.title.slice(0, 60));
1723
- }
1724
- }
1725
- break;
1726
- }
1727
- case 'clear': {
1728
- const tasks = await store.list();
1729
- let count = 0;
1730
- for (const t of tasks) {
1731
- await store.update(t.id, { status: 'cancelled' });
1732
- count++;
1733
- }
1734
- console.log(`Cleared ${count} task(s).`);
1735
- break;
1736
- }
1737
- case 'remove': {
1738
- const id = opts.botFile;
1739
- if (!id) {
1740
- console.error('[weaver] Usage: flow-weaver weaver queue remove <id>');
1741
- process.exit(1);
1742
- }
1743
- try {
1744
- await store.update(id, { status: 'cancelled' });
1745
- console.log(`Cancelled task ${id}.`);
1746
- }
1747
- catch {
1748
- console.log(`No task found with id "${id}".`);
1749
- }
1750
- break;
1751
- }
1752
- case 'retry': {
1753
- const id = opts.botFile;
1754
- if (id) {
1755
- try {
1756
- await store.update(id, { status: 'pending' });
1757
- console.log(`Task ${id} reset to pending.`);
1758
- }
1759
- catch {
1760
- console.log(`No task found with id "${id}".`);
1761
- }
1762
- }
1763
- else {
1764
- const failed = await store.list({ status: 'failed' });
1765
- let count = 0;
1766
- for (const t of failed) {
1767
- await store.update(t.id, { status: 'pending' });
1768
- count++;
1769
- }
1770
- console.log(`Reset ${count} failed task(s) to pending.`);
1771
- }
1772
- break;
1773
- }
1774
- }
1654
+ await handleWeaverTool('fw_weaver_steer', {
1655
+ command: sub,
1656
+ botId: opts.botId,
1657
+ }, projectDir);
1658
+ console.log(`\x1b[33m→ Steer: ${sub}${opts.botId ? ` (bot: ${opts.botId})` : ''}\x1b[0m`);
1775
1659
  }
1776
1660
  export async function handleStatus(opts) {
1777
- const store = new RunStore();
1778
- const { TaskStore } = await import('./bot/task-store.js');
1779
- const projectDir = process.env.WEAVER_PROJECT_DIR ?? process.cwd();
1780
- const taskStore = new TaskStore(projectDir);
1781
- const orphans = store.checkOrphans();
1782
- const recentRuns = store.list({ limit: 5 });
1783
- const tasks = await taskStore.list();
1784
- const pending = tasks.filter(t => t.status === 'pending').length;
1785
- const running = tasks.filter(t => t.status === 'in-progress').length;
1786
- const completed = tasks.filter(t => t.status === 'done').length;
1787
- const failed = tasks.filter(t => t.status === 'failed').length;
1661
+ const projectDir = opts.file || process.cwd();
1662
+ // Get swarm status
1663
+ const { result: swarmResult } = await handleWeaverTool('fw_weaver_swarm_status', {}, projectDir);
1664
+ const swarm = JSON.parse(swarmResult);
1665
+ // Get task list
1666
+ const { result: taskResult } = await handleWeaverTool('fw_weaver_task_list', { limit: 10 }, projectDir);
1667
+ const tasks = JSON.parse(taskResult);
1788
1668
  if (opts.historyJson) {
1789
- console.log(JSON.stringify({
1790
- queue: { pending, running, completed, failed, total: tasks.length },
1791
- orphanedRuns: orphans.length,
1792
- recentRuns: recentRuns.map(r => ({
1793
- id: r.id, outcome: r.outcome, summary: r.summary,
1794
- startedAt: r.startedAt, durationMs: r.durationMs,
1795
- })),
1796
- }, null, 2));
1669
+ console.log(JSON.stringify({ swarm, tasks }, null, 2));
1797
1670
  return;
1798
1671
  }
1799
- console.log('\n\x1b[1mWeaver Status\x1b[0m\n');
1800
- console.log(` Queue: ${pending} pending, ${running} running, ${completed} completed, ${failed} failed`);
1801
- if (orphans.length > 0) {
1802
- console.log(` \x1b[33mOrphaned runs: ${orphans.length} (recovered)\x1b[0m`);
1803
- }
1804
- if (recentRuns.length > 0) {
1805
- console.log(`\n Recent runs:`);
1806
- for (const r of recentRuns) {
1807
- const icon = r.success ? '\x1b[32m✓\x1b[0m' : '\x1b[31m✗\x1b[0m';
1808
- console.log(` ${icon} ${r.id.slice(0, 8)} ${r.outcome.padEnd(9)} ${formatDuration(r.durationMs).padEnd(8)} ${r.summary.slice(0, 60)}`);
1672
+ console.log(`\n\x1b[1mWeaver Status\x1b[0m\n`);
1673
+ console.log(` Swarm: ${colorStatus(swarm.status ?? 'unknown')}`);
1674
+ if (swarm.maxConcurrent !== undefined)
1675
+ console.log(` Max concurrent: ${swarm.maxConcurrent}`);
1676
+ if (Array.isArray(tasks) && tasks.length > 0) {
1677
+ const pending = tasks.filter((t) => t.status === 'pending').length;
1678
+ const running = tasks.filter((t) => t.status === 'in-progress').length;
1679
+ const done = tasks.filter((t) => t.status === 'done').length;
1680
+ const failed = tasks.filter((t) => t.status === 'failed').length;
1681
+ console.log(` Tasks: ${pending} pending, ${running} running, ${done} done, ${failed} failed`);
1682
+ console.log(`\n Recent tasks:`);
1683
+ for (const t of tasks.slice(0, 5)) {
1684
+ const icon = t.status === 'done' ? '\x1b[32m✓\x1b[0m' : t.status === 'failed' ? '\x1b[31m✗\x1b[0m' : t.status === 'in-progress' ? '\x1b[36m▸\x1b[0m' : '○';
1685
+ console.log(` ${icon} ${(t.id ?? '').slice(0, 8)} [${t.status}] ${(t.title ?? '').slice(0, 60)}`);
1809
1686
  }
1810
1687
  }
1811
1688
  else {
1812
- console.log('\n No recent runs.');
1689
+ console.log(' Tasks: none');
1813
1690
  }
1814
1691
  console.log('');
1815
1692
  }