@exaudeus/workrail 3.33.0 → 3.34.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/dist/cli-worktrain.js +167 -8
  2. package/dist/console-ui/assets/{index-BuJFLLfY.js → index-C1JXnwZS.js} +1 -1
  3. package/dist/console-ui/index.html +1 -1
  4. package/dist/daemon/agent-loop.d.ts +1 -0
  5. package/dist/daemon/agent-loop.js +1 -1
  6. package/dist/daemon/daemon-events.d.ts +17 -1
  7. package/dist/daemon/workflow-runner.d.ts +1 -1
  8. package/dist/daemon/workflow-runner.js +96 -21
  9. package/dist/manifest.json +43 -67
  10. package/dist/mcp/handlers/v2-error-mapping.d.ts +3 -0
  11. package/dist/mcp/handlers/v2-error-mapping.js +2 -0
  12. package/dist/mcp/handlers/v2-execution/advance.js +25 -0
  13. package/dist/mcp/handlers/v2-execution/continue-advance.js +7 -0
  14. package/dist/mcp/transports/http-entry.js +0 -7
  15. package/dist/mcp/transports/stdio-entry.js +0 -8
  16. package/dist/mcp-server.d.ts +0 -2
  17. package/dist/mcp-server.js +1 -42
  18. package/dist/v2/durable-core/domain/observation-builder.d.ts +3 -0
  19. package/dist/v2/durable-core/domain/observation-builder.js +2 -2
  20. package/dist/v2/durable-core/domain/prompt-renderer.d.ts +2 -1
  21. package/dist/v2/durable-core/domain/prompt-renderer.js +10 -0
  22. package/dist/v2/usecases/console-service.js +65 -14
  23. package/dist/v2/usecases/console-types.d.ts +1 -0
  24. package/docs/design/bridge-removal-pr-a-candidates.md +115 -0
  25. package/docs/design/bridge-removal-pr-a-design-review.md +79 -0
  26. package/docs/design/bridge-removal-pr-a-implementation-plan.md +203 -0
  27. package/docs/discovery/design-candidates.md +180 -0
  28. package/docs/discovery/design-review-findings.md +110 -0
  29. package/docs/discovery/wr-discovery-goal-reframing.md +303 -0
  30. package/docs/ideas/backlog.md +266 -0
  31. package/package.json +1 -1
  32. package/workflows/wr.discovery.json +58 -7
  33. package/dist/mcp/transports/bridge-entry.d.ts +0 -102
  34. package/dist/mcp/transports/bridge-entry.js +0 -454
  35. package/dist/mcp/transports/bridge-events.d.ts +0 -55
  36. package/dist/mcp/transports/bridge-events.js +0 -24
  37. package/dist/mcp/transports/primary-tombstone.d.ts +0 -21
  38. package/dist/mcp/transports/primary-tombstone.js +0 -51
@@ -377,6 +377,8 @@ function formatDaemonEventLine(raw) {
377
377
  const sessionId = typeof obj['sessionId'] === 'string' ? obj['sessionId'].slice(0, 8) : null;
378
378
  const prefix = sessionId ? `[${ts}] [${sessionId}] ${kind}` : `[${ts}] ${kind}`;
379
379
  switch (kind) {
380
+ case 'agent_stuck':
381
+ return `${prefix} *** STUCK: ${obj['reason'] ?? '?'} -- ${String(obj['detail'] ?? '').slice(0, 100)}`;
380
382
  case 'llm_turn_started':
381
383
  return `${prefix} msgs=${obj['messageCount'] ?? '?'}`;
382
384
  case 'llm_turn_completed':
@@ -393,12 +395,33 @@ function formatDaemonEventLine(raw) {
393
395
  return `${prefix} tool=${obj['toolName'] ?? '?'} err=${String(obj['error'] ?? '').slice(0, 80)}`;
394
396
  case 'session_started':
395
397
  return `${prefix} workflow=${obj['workflowId'] ?? '?'} workspace=${obj['workspacePath'] ?? '?'}`;
396
- case 'session_completed':
397
- return `${prefix} workflow=${obj['workflowId'] ?? '?'} outcome=${obj['outcome'] ?? '?'}${obj['detail'] ? ` (${obj['detail']})` : ''}`;
398
+ case 'session_completed': {
399
+ const outcome = obj['outcome'];
400
+ const detail = obj['detail'] ? ` (${obj['detail']})` : '';
401
+ if (outcome === 'success') {
402
+ return `${prefix} workflow=${obj['workflowId'] ?? '?'} -- session complete${detail}`;
403
+ }
404
+ else if (outcome === 'error') {
405
+ return `${prefix} workflow=${obj['workflowId'] ?? '?'} -- session FAILED${detail}`;
406
+ }
407
+ else if (outcome === 'timeout') {
408
+ return `${prefix} workflow=${obj['workflowId'] ?? '?'} -- session TIMEOUT${detail}`;
409
+ }
410
+ return `${prefix} workflow=${obj['workflowId'] ?? '?'} outcome=${outcome ?? '?'}${detail}`;
411
+ }
398
412
  case 'step_advanced':
399
- return `${prefix}`;
400
- case 'issue_reported':
401
- return `${prefix} severity=${obj['severity'] ?? '?'} ${String(obj['summary'] ?? '').slice(0, 80)}`;
413
+ return `${prefix} -> step advanced`;
414
+ case 'issue_reported': {
415
+ const severity = obj['severity'];
416
+ const summary = String(obj['summary'] ?? '').slice(0, 100);
417
+ if (severity === 'fatal') {
418
+ return `${prefix} FATAL: ${summary}`;
419
+ }
420
+ else if (severity === 'error') {
421
+ return `${prefix} ERROR: ${summary}`;
422
+ }
423
+ return `${prefix} severity=${severity ?? '?'} ${summary}`;
424
+ }
402
425
  default:
403
426
  return `${prefix} ${JSON.stringify(obj).slice(0, 120)}`;
404
427
  }
@@ -407,7 +430,7 @@ program
407
430
  .command('logs')
408
431
  .description('Read and display the WorkRail daemon event log. Use --follow to stream new events in real time.')
409
432
  .option('--follow', 'Continuously poll the log file for new events (like tail -f)')
410
- .option('--session <id>', 'Filter events by sessionId prefix (first 8 chars or full UUID)')
433
+ .option('--session <id>', 'Filter events by sessionId (UUID prefix) or workrailSessionId (sess_xxx prefix)')
411
434
  .action(async (options) => {
412
435
  const eventsDir = path_1.default.join(os_1.default.homedir(), '.workrail', 'events', 'daemon');
413
436
  function todayFilePath() {
@@ -444,9 +467,11 @@ program
444
467
  try {
445
468
  const obj = JSON.parse(line);
446
469
  const sid = typeof obj['sessionId'] === 'string' ? obj['sessionId'] : '';
447
- if (!sid.startsWith(options.session) && sid !== options.session) {
470
+ const wrid = typeof obj['workrailSessionId'] === 'string' ? obj['workrailSessionId'] : '';
471
+ const matchesSession = sid.startsWith(options.session) || sid === options.session ||
472
+ wrid.startsWith(options.session) || wrid === options.session;
473
+ if (!matchesSession)
448
474
  continue;
449
- }
450
475
  }
451
476
  catch {
452
477
  continue;
@@ -468,6 +493,7 @@ program
468
493
  printLines(result.lines);
469
494
  return;
470
495
  }
496
+ process.once('SIGINT', () => process.exit(0));
471
497
  let currentFilePath = filePath;
472
498
  let offset = 0;
473
499
  const initial = readNewLines(currentFilePath, 0);
@@ -495,4 +521,137 @@ program
495
521
  }
496
522
  }
497
523
  });
524
+ program
525
+ .command('status <sessionId>')
526
+ .description('Print a health summary for a daemon session. Accepts sessionId (UUID prefix) or workrailSessionId (sess_xxx).')
527
+ .action(async (sessionId) => {
528
+ const eventsDir = path_1.default.join(os_1.default.homedir(), '.workrail', 'events', 'daemon');
529
+ const date = new Date().toISOString().slice(0, 10);
530
+ const filePath = path_1.default.join(eventsDir, `${date}.jsonl`);
531
+ let raw;
532
+ try {
533
+ raw = fs_1.default.readFileSync(filePath, 'utf8');
534
+ }
535
+ catch {
536
+ process.stdout.write(`No events today. Is the daemon running? (Expected: ${filePath})\n`);
537
+ return;
538
+ }
539
+ let workflowId = null;
540
+ let firstTs = null;
541
+ let lastTs = null;
542
+ let llmTurns = 0;
543
+ let stepAdvances = 0;
544
+ let totalToolCalls = 0;
545
+ let failedToolCalls = 0;
546
+ let fatalIssues = 0;
547
+ let errorIssues = 0;
548
+ let warnIssues = 0;
549
+ let sessionOutcome = null;
550
+ let lastToolName = null;
551
+ let lastToolArgs = null;
552
+ let stuckCount = 0;
553
+ let isLive = true;
554
+ for (const line of raw.split('\n')) {
555
+ if (!line.trim())
556
+ continue;
557
+ let obj;
558
+ try {
559
+ obj = JSON.parse(line);
560
+ }
561
+ catch {
562
+ continue;
563
+ }
564
+ const sid = typeof obj['sessionId'] === 'string' ? obj['sessionId'] : '';
565
+ const wrid = typeof obj['workrailSessionId'] === 'string' ? obj['workrailSessionId'] : '';
566
+ const matches = sid.startsWith(sessionId) || sid === sessionId ||
567
+ wrid.startsWith(sessionId) || wrid === sessionId;
568
+ if (!matches)
569
+ continue;
570
+ const ts = typeof obj['ts'] === 'number' ? obj['ts'] : null;
571
+ if (ts !== null) {
572
+ if (firstTs === null || ts < firstTs)
573
+ firstTs = ts;
574
+ if (lastTs === null || ts > lastTs)
575
+ lastTs = ts;
576
+ }
577
+ const kind = typeof obj['kind'] === 'string' ? obj['kind'] : '';
578
+ switch (kind) {
579
+ case 'session_started':
580
+ workflowId = typeof obj['workflowId'] === 'string' ? obj['workflowId'] : null;
581
+ break;
582
+ case 'llm_turn_completed':
583
+ llmTurns++;
584
+ break;
585
+ case 'step_advanced':
586
+ stepAdvances++;
587
+ break;
588
+ case 'tool_call_started':
589
+ totalToolCalls++;
590
+ lastToolName = typeof obj['toolName'] === 'string' ? obj['toolName'] : null;
591
+ lastToolArgs = typeof obj['argsSummary'] === 'string' ? String(obj['argsSummary']).slice(0, 60) : null;
592
+ break;
593
+ case 'tool_call_failed':
594
+ failedToolCalls++;
595
+ break;
596
+ case 'issue_reported': {
597
+ const severity = obj['severity'];
598
+ if (severity === 'fatal')
599
+ fatalIssues++;
600
+ else if (severity === 'error')
601
+ errorIssues++;
602
+ else if (severity === 'warn')
603
+ warnIssues++;
604
+ break;
605
+ }
606
+ case 'agent_stuck':
607
+ stuckCount++;
608
+ break;
609
+ case 'session_completed':
610
+ sessionOutcome = typeof obj['outcome'] === 'string' ? obj['outcome'] : null;
611
+ isLive = false;
612
+ break;
613
+ }
614
+ }
615
+ if (firstTs === null) {
616
+ process.stdout.write(`No events found for session: ${sessionId}\n`);
617
+ return;
618
+ }
619
+ const durationMs = (lastTs ?? firstTs) - firstTs;
620
+ const durationSec = Math.floor(durationMs / 1000);
621
+ const durationMin = Math.floor(durationSec / 60);
622
+ const durationRemSec = durationSec % 60;
623
+ const durationStr = durationMin > 0
624
+ ? `${durationMin}m ${durationRemSec}s`
625
+ : `${durationSec}s`;
626
+ const avgTurnSec = llmTurns > 0 ? (durationMs / llmTurns / 1000).toFixed(1) : '?';
627
+ const failRate = totalToolCalls > 0 ? ((failedToolCalls / totalToolCalls) * 100).toFixed(1) : '0';
628
+ const sessionStatus = sessionOutcome !== null
629
+ ? sessionOutcome.toUpperCase()
630
+ : (isLive ? 'RUNNING' : 'UNKNOWN');
631
+ const issueStr = (fatalIssues + errorIssues + warnIssues) > 0
632
+ ? `${fatalIssues + errorIssues + warnIssues} (${fatalIssues} fatal, ${errorIssues} error, ${warnIssues} warn)`
633
+ : '0';
634
+ const lastActivityStr = lastTs !== null
635
+ ? `${lastToolName ?? 'unknown'} ${lastToolArgs ? `"${lastToolArgs}"` : ''} ${Math.round((Date.now() - lastTs) / 1000)}s ago`
636
+ : 'unknown';
637
+ process.stdout.write(`\nSession: ${sessionId} [${sessionStatus}]\n`);
638
+ if (workflowId)
639
+ process.stdout.write(`Workflow: ${workflowId}\n`);
640
+ process.stdout.write(`Duration: ${durationStr}\n`);
641
+ process.stdout.write(`LLM turns: ${llmTurns}${llmTurns > 0 ? ` (avg ${avgTurnSec}s each)` : ''}\n`);
642
+ process.stdout.write(`Step advances: ${stepAdvances}\n`);
643
+ process.stdout.write(`Tool calls: ${totalToolCalls} (${failedToolCalls} failed, ${failRate}% failure rate)\n`);
644
+ process.stdout.write(`Issues reported: ${issueStr}\n`);
645
+ process.stdout.write(`Last activity: ${lastActivityStr}\n`);
646
+ if (stuckCount > 0) {
647
+ process.stdout.write(`*** WARNING: ${stuckCount} stuck signal(s) detected\n`);
648
+ }
649
+ if (fatalIssues > 0) {
650
+ process.stdout.write(`*** WARNING: ${fatalIssues} FATAL issue(s) reported\n`);
651
+ }
652
+ if (llmTurns >= 10 && stepAdvances === 0) {
653
+ process.stdout.write(`*** WARNING: ${llmTurns} turns with 0 step advances (possible stuck)\n`);
654
+ }
655
+ process.stdout.write('\n');
656
+ });
498
657
  program.parse();