pm-orchestrator-runner 1.0.5 → 1.0.7

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.
@@ -117,6 +117,9 @@ class REPLInterface extends events_1.EventEmitter {
117
117
  hasIncompleteTasks = false;
118
118
  // Session completion tracking (prevents double completion)
119
119
  sessionCompleted = false;
120
+ // Non-blocking task queue (allows input while tasks are running)
121
+ taskQueue = [];
122
+ isTaskWorkerRunning = false;
120
123
  // Project mode support (per spec 10_REPL_UX.md, Property 32, 33)
121
124
  projectMode;
122
125
  verificationRoot;
@@ -193,6 +196,8 @@ class REPLInterface extends events_1.EventEmitter {
193
196
  runner: null,
194
197
  supervisor: null,
195
198
  status: 'idle',
199
+ current_task_id: null,
200
+ last_task_id: null,
196
201
  };
197
202
  // Initialize command handlers
198
203
  this.initCommand = new init_1.InitCommand();
@@ -229,8 +234,12 @@ class REPLInterface extends events_1.EventEmitter {
229
234
  await this.initializeTempProjectRoot();
230
235
  }
231
236
  // Output PROJECT_PATH if requested (per spec 10_REPL_UX.md)
237
+ // Emit 'output' event for programmatic access (tests, wrappers)
238
+ // Also print to stdout for shell script parsing
232
239
  if (this.config.printProjectPath) {
233
- console.log('PROJECT_PATH=' + this.verificationRoot);
240
+ const projectPathLine = 'PROJECT_PATH=' + this.verificationRoot;
241
+ this.emit('output', projectPathLine);
242
+ console.log(projectPathLine);
234
243
  }
235
244
  }
236
245
  /**
@@ -611,27 +620,50 @@ class REPLInterface extends events_1.EventEmitter {
611
620
  }
612
621
  }
613
622
  /**
614
- * Process natural language input
623
+ * Check if input is a bare "exit" typo (should use /exit)
624
+ * Per spec 10_REPL_UX.md: Exit Typo Safety
625
+ * Pattern: ^exit\s*$ (case-insensitive, trimmed)
626
+ */
627
+ isExitTypo(input) {
628
+ const trimmed = input.trim().toLowerCase();
629
+ return trimmed === 'exit';
630
+ }
631
+ /**
632
+ * Process natural language input - NON-BLOCKING
615
633
  * Per spec 10_REPL_UX.md L117-118: Model selection is REPL-local
616
634
  * Model is read from .claude/repl.json and passed to executor via runner
617
635
  *
636
+ * Non-blocking design:
637
+ * - Creates task and adds to queue immediately
638
+ * - Returns control to input prompt right away
639
+ * - Background worker processes tasks asynchronously
640
+ *
618
641
  * Auto-start: In non-interactive mode, automatically start a session if none exists
619
642
  * This improves CLI usability for piped input and scripting
643
+ *
644
+ * Exit Typo Safety (per spec 10_REPL_UX.md):
645
+ * - Detects bare "exit" input (without slash)
646
+ * - Shows error and suggests /exit
647
+ * - Never passes "exit" to Claude Code
620
648
  */
621
649
  async processNaturalLanguage(input) {
622
650
  console.log(`[DEBUG processNaturalLanguage] start, input="${input}"`);
651
+ // Exit Typo Safety: Block bare "exit" input
652
+ // Per spec 10_REPL_UX.md: fail-closed - 2 lines max, return to input
653
+ if (this.isExitTypo(input)) {
654
+ this.print('ERROR: Did you mean /exit?');
655
+ this.print('HINT: /exit');
656
+ return;
657
+ }
658
+ // Auto-start session for any natural language input (no /start required)
659
+ // Per redesign: natural language input = automatic task creation
623
660
  if (this.session.status !== 'running') {
624
- // In non-interactive mode, auto-start a session for better UX
625
- if (this.executionMode === 'non_interactive') {
626
- console.log('[DEBUG processNaturalLanguage] auto-starting session for non-interactive mode');
627
- const startResult = await this.handleStart([]);
628
- if (!startResult.success) {
629
- this.print('Failed to auto-start session: ' + (startResult.error?.message || 'Unknown error'));
630
- return;
631
- }
632
- }
633
- else {
634
- this.print('No active session. Use /start to begin, or /init to set up a new project.');
661
+ console.log('[DEBUG processNaturalLanguage] auto-starting session');
662
+ this.print('');
663
+ this.print('[Auto-starting session...]');
664
+ const startResult = await this.handleStart([]);
665
+ if (!startResult.success) {
666
+ this.print('Failed to auto-start session: ' + (startResult.error?.message || 'Unknown error'));
635
667
  return;
636
668
  }
637
669
  }
@@ -639,46 +671,150 @@ class REPLInterface extends events_1.EventEmitter {
639
671
  this.print('Runner not initialized. Use /start first.');
640
672
  return;
641
673
  }
642
- this.print('Processing task: ' + input);
674
+ // Generate task ID
675
+ // Per spec 05_DATA_MODELS.md Property 38
676
+ const taskId = 'task-' + Date.now();
677
+ // Create queued task
678
+ const queuedTask = {
679
+ id: taskId,
680
+ description: input,
681
+ state: 'QUEUED',
682
+ queuedAt: Date.now(),
683
+ startedAt: null,
684
+ completedAt: null,
685
+ };
686
+ // Add to task queue
687
+ this.taskQueue.push(queuedTask);
688
+ // Display task queued info (per redesign: visibility)
689
+ // Per user requirement: Provider/Mode/Auth must be shown at task start
690
+ this.print('');
691
+ this.print('--- Task Queued ---');
692
+ this.print('Task ID: ' + taskId);
693
+ this.print('State: QUEUED');
694
+ // Show LLM layer info (Provider/Mode/Auth) - per redesign requirement
695
+ if (this.config.authMode === 'claude-code') {
696
+ this.print('Provider: Claude Code CLI (uses your Claude subscription, no API key required)');
697
+ }
698
+ else if (this.config.authMode === 'api-key') {
699
+ this.print('Provider: Anthropic API (API key configured)');
700
+ }
701
+ else {
702
+ this.print('Provider: ' + this.config.authMode);
703
+ }
704
+ // Show prompt summary (first 100 chars)
705
+ const promptSummary = input.length > 100 ? input.substring(0, 100) + '...' : input;
706
+ this.print('Prompt: ' + promptSummary);
707
+ this.print('-------------------');
643
708
  this.print('');
709
+ this.print('(Input is not blocked - you can submit more tasks with /tasks to view status)');
710
+ this.print('');
711
+ // Start background worker if not running
712
+ // The worker runs asynchronously - we don't await it
713
+ if (!this.isTaskWorkerRunning) {
714
+ this.startTaskWorker();
715
+ }
716
+ console.log(`[DEBUG processNaturalLanguage] task queued, returning immediately`);
717
+ }
718
+ /**
719
+ * Background task worker - processes queued tasks asynchronously
720
+ * Runs in background, allowing input to continue while tasks execute
721
+ */
722
+ async startTaskWorker() {
723
+ if (this.isTaskWorkerRunning) {
724
+ return;
725
+ }
726
+ this.isTaskWorkerRunning = true;
727
+ console.log('[DEBUG startTaskWorker] worker started');
728
+ try {
729
+ while (this.running) {
730
+ // Find next QUEUED task
731
+ const nextTask = this.taskQueue.find(t => t.state === 'QUEUED');
732
+ if (!nextTask) {
733
+ // No more queued tasks - worker exits
734
+ break;
735
+ }
736
+ // Execute the task
737
+ await this.executeQueuedTask(nextTask);
738
+ }
739
+ }
740
+ finally {
741
+ this.isTaskWorkerRunning = false;
742
+ console.log('[DEBUG startTaskWorker] worker stopped');
743
+ }
744
+ }
745
+ /**
746
+ * Execute a single queued task
747
+ * Updates task state and prints results
748
+ */
749
+ async executeQueuedTask(task) {
750
+ console.log(`[DEBUG executeQueuedTask] starting task ${task.id}`);
751
+ // Update state to RUNNING
752
+ task.state = 'RUNNING';
753
+ task.startedAt = Date.now();
754
+ this.session.current_task_id = task.id;
755
+ // Print status update
756
+ this.print('');
757
+ this.print('--- Task Started ---');
758
+ this.print('Task ID: ' + task.id);
759
+ this.print('State: RUNNING');
760
+ this.print('--------------------');
644
761
  try {
645
762
  // Per spec 10_REPL_UX.md L117-118: Get selected model from REPL config
646
- // Model is REPL-local, stored in .claude/repl.json
647
763
  let selectedModel;
648
764
  const modelResult = await this.modelCommand.getModel(this.session.projectPath);
649
765
  if (modelResult.success && modelResult.model && modelResult.model !== 'UNSET') {
650
766
  selectedModel = modelResult.model;
651
767
  }
652
- console.log(`[DEBUG processNaturalLanguage] calling runner.execute...`);
653
- // Create task from natural language input
654
- // CRITICAL: naturalLanguageTask must be set to trigger Claude Code execution
655
- // Without this, the task would only use fallback file creation patterns
768
+ console.log(`[DEBUG executeQueuedTask] calling runner.execute...`);
656
769
  const result = await this.session.runner.execute({
657
770
  tasks: [{
658
- id: 'task-' + Date.now(),
659
- description: input,
660
- naturalLanguageTask: input,
771
+ id: task.id,
772
+ description: task.description,
773
+ naturalLanguageTask: task.description,
661
774
  }],
662
775
  selectedModel,
663
776
  });
664
- console.log(`[DEBUG processNaturalLanguage] runner.execute returned, status=${result.overall_status}`);
665
- // Handle INCOMPLETE with clarification interactively
777
+ console.log(`[DEBUG executeQueuedTask] runner.execute returned, status=${result.overall_status}`);
778
+ // Update task state based on result
779
+ task.completedAt = Date.now();
780
+ task.resultStatus = result.overall_status;
781
+ task.filesModified = result.files_modified;
782
+ task.responseSummary = result.executor_output_summary;
783
+ switch (result.overall_status) {
784
+ case enums_1.OverallStatus.COMPLETE:
785
+ task.state = 'COMPLETE';
786
+ break;
787
+ case enums_1.OverallStatus.INCOMPLETE:
788
+ task.state = 'INCOMPLETE';
789
+ break;
790
+ case enums_1.OverallStatus.ERROR:
791
+ task.state = 'ERROR';
792
+ task.errorMessage = result.error?.message;
793
+ break;
794
+ default:
795
+ task.state = 'COMPLETE';
796
+ }
797
+ // Handle INCOMPLETE with clarification (but don't block)
666
798
  if (result.overall_status === enums_1.OverallStatus.INCOMPLETE &&
667
799
  result.incomplete_task_reasons &&
668
800
  result.incomplete_task_reasons.length > 0) {
669
- const clarificationNeeded = await this.handleClarificationNeeded(input, result.incomplete_task_reasons);
670
- if (clarificationNeeded) {
671
- // User provided clarification, will be processed in next input
672
- return;
673
- }
801
+ await this.handleClarificationNeeded(task.description, result.incomplete_task_reasons);
674
802
  }
675
803
  this.printExecutionResult(result);
676
804
  }
677
805
  catch (err) {
678
- console.log(`[DEBUG processNaturalLanguage] error: ${err.message}`);
806
+ console.log(`[DEBUG executeQueuedTask] error: ${err.message}`);
807
+ task.state = 'ERROR';
808
+ task.completedAt = Date.now();
809
+ task.errorMessage = err.message;
679
810
  this.printError(err);
680
811
  }
681
- console.log(`[DEBUG processNaturalLanguage] done`);
812
+ finally {
813
+ // Clear current_task_id
814
+ this.session.last_task_id = this.session.current_task_id;
815
+ this.session.current_task_id = null;
816
+ }
817
+ console.log(`[DEBUG executeQueuedTask] task ${task.id} done, state=${task.state}`);
682
818
  }
683
819
  /**
684
820
  * Handle clarification needed - prompt user interactively
@@ -1028,7 +1164,8 @@ class REPLInterface extends events_1.EventEmitter {
1028
1164
  // /logs <task-id> [--full]
1029
1165
  const taskId = args[0];
1030
1166
  const full = args.includes('--full');
1031
- const result = await this.logsCommand.getTaskDetail(this.session.projectPath, taskId, full);
1167
+ // Per redesign: Pass sessionId for visibility fields
1168
+ const result = await this.logsCommand.getTaskDetail(this.session.projectPath, taskId, full, this.session.sessionId ?? undefined);
1032
1169
  if (result.success) {
1033
1170
  this.print(result.output || 'No log detail found.');
1034
1171
  return { success: true };
@@ -1041,10 +1178,78 @@ class REPLInterface extends events_1.EventEmitter {
1041
1178
  }
1042
1179
  /**
1043
1180
  * Handle /tasks command
1181
+ * Shows task queue with RUNNING/QUEUED/COMPLETE/ERROR/INCOMPLETE states
1182
+ * Per redesign: proves non-blocking by showing multiple tasks simultaneously
1044
1183
  */
1045
1184
  async handleTasks() {
1046
- const result = await this.statusCommands.getTasks();
1047
- this.print(result);
1185
+ this.print('');
1186
+ this.print('=== Task Queue ===');
1187
+ this.print('');
1188
+ if (this.taskQueue.length === 0) {
1189
+ this.print('No tasks in queue.');
1190
+ this.print('');
1191
+ // Also show legacy tasks from statusCommands if available
1192
+ const legacyResult = await this.statusCommands.getTasks();
1193
+ if (legacyResult && legacyResult.trim()) {
1194
+ this.print('--- Session Tasks ---');
1195
+ this.print(legacyResult);
1196
+ }
1197
+ return;
1198
+ }
1199
+ // Count by state
1200
+ const running = this.taskQueue.filter(t => t.state === 'RUNNING').length;
1201
+ const queued = this.taskQueue.filter(t => t.state === 'QUEUED').length;
1202
+ const complete = this.taskQueue.filter(t => t.state === 'COMPLETE').length;
1203
+ const incomplete = this.taskQueue.filter(t => t.state === 'INCOMPLETE').length;
1204
+ const error = this.taskQueue.filter(t => t.state === 'ERROR').length;
1205
+ this.print('Summary: ' + running + ' RUNNING, ' + queued + ' QUEUED, ' + complete + ' COMPLETE, ' + incomplete + ' INCOMPLETE, ' + error + ' ERROR');
1206
+ this.print('');
1207
+ // List all tasks with state
1208
+ for (const task of this.taskQueue) {
1209
+ const promptSummary = task.description.length > 50
1210
+ ? task.description.substring(0, 50) + '...'
1211
+ : task.description;
1212
+ let durationStr = '';
1213
+ if (task.startedAt) {
1214
+ const endTime = task.completedAt || Date.now();
1215
+ const durationMs = endTime - task.startedAt;
1216
+ durationStr = ' (' + (durationMs / 1000).toFixed(1) + 's)';
1217
+ }
1218
+ // State indicator with visual marker
1219
+ let stateMarker = '';
1220
+ switch (task.state) {
1221
+ case 'RUNNING':
1222
+ stateMarker = '[*]';
1223
+ break;
1224
+ case 'QUEUED':
1225
+ stateMarker = '[ ]';
1226
+ break;
1227
+ case 'COMPLETE':
1228
+ stateMarker = '[v]';
1229
+ break;
1230
+ case 'INCOMPLETE':
1231
+ stateMarker = '[!]';
1232
+ break;
1233
+ case 'ERROR':
1234
+ stateMarker = '[X]';
1235
+ break;
1236
+ }
1237
+ this.print(stateMarker + ' ' + task.id + ' | ' + task.state + durationStr);
1238
+ this.print(' ' + promptSummary);
1239
+ // Show error message if error
1240
+ if (task.state === 'ERROR' && task.errorMessage) {
1241
+ this.print(' Error: ' + task.errorMessage);
1242
+ }
1243
+ // Show files modified if complete
1244
+ if (task.state === 'COMPLETE' && task.filesModified && task.filesModified.length > 0) {
1245
+ this.print(' Files: ' + task.filesModified.slice(0, 3).join(', ') +
1246
+ (task.filesModified.length > 3 ? ' (+' + (task.filesModified.length - 3) + ' more)' : ''));
1247
+ }
1248
+ }
1249
+ this.print('');
1250
+ this.print('Use /logs <task-id> for details.');
1251
+ this.print('==================');
1252
+ this.print('');
1048
1253
  }
1049
1254
  /**
1050
1255
  * Handle /status command
@@ -1175,6 +1380,7 @@ class REPLInterface extends events_1.EventEmitter {
1175
1380
  * Per spec 10_REPL_UX.md: Ensure clean exit with flushed output
1176
1381
  *
1177
1382
  * Guarantees:
1383
+ * - Waits for running tasks to complete (task worker)
1178
1384
  * - Session state is persisted before exit
1179
1385
  * - All output is flushed before readline closes
1180
1386
  * - Double-completion is prevented via sessionCompleted flag
@@ -1188,6 +1394,18 @@ class REPLInterface extends events_1.EventEmitter {
1188
1394
  return;
1189
1395
  }
1190
1396
  this.sessionCompleted = true;
1397
+ // Wait for task worker to complete any running tasks
1398
+ if (this.isTaskWorkerRunning) {
1399
+ const runningTasks = this.taskQueue.filter(t => t.state === 'RUNNING');
1400
+ const queuedTasks = this.taskQueue.filter(t => t.state === 'QUEUED');
1401
+ if (runningTasks.length > 0 || queuedTasks.length > 0) {
1402
+ this.print('Waiting for ' + runningTasks.length + ' running and ' + queuedTasks.length + ' queued tasks to complete...');
1403
+ }
1404
+ while (this.isTaskWorkerRunning) {
1405
+ await new Promise(r => setTimeout(r, 100));
1406
+ }
1407
+ this.print('All tasks completed.');
1408
+ }
1191
1409
  this.print('Saving session state...');
1192
1410
  if (this.session.runner && this.session.sessionId) {
1193
1411
  try {
@@ -1215,14 +1433,102 @@ class REPLInterface extends events_1.EventEmitter {
1215
1433
  this.running = false;
1216
1434
  this.rl?.close();
1217
1435
  }
1436
+ /**
1437
+ * Map OverallStatus to TaskLogStatus
1438
+ * Per spec 05_DATA_MODELS.md: Terminal states are complete/incomplete/error
1439
+ */
1440
+ mapToTaskLogStatus(status) {
1441
+ switch (status) {
1442
+ case enums_1.OverallStatus.COMPLETE:
1443
+ return 'complete';
1444
+ case enums_1.OverallStatus.INCOMPLETE:
1445
+ return 'incomplete';
1446
+ case enums_1.OverallStatus.ERROR:
1447
+ return 'error';
1448
+ default:
1449
+ return 'running';
1450
+ }
1451
+ }
1452
+ /**
1453
+ * Check if status is terminal
1454
+ * Per spec 05_DATA_MODELS.md: Terminal states are complete/incomplete/error
1455
+ */
1456
+ isTerminalStatus(status) {
1457
+ return status === 'complete' || status === 'incomplete' || status === 'error';
1458
+ }
1459
+ /**
1460
+ * Print immediate summary block
1461
+ * Per spec 10_REPL_UX.md: Immediate Summary Output
1462
+ *
1463
+ * COMPLETE (4 lines fixed):
1464
+ * RESULT: COMPLETE / TASK / NEXT: (none) / HINT
1465
+ *
1466
+ * INCOMPLETE/ERROR (5 lines fixed, WHY required):
1467
+ * RESULT / TASK / NEXT: /logs <id> / WHY / HINT
1468
+ */
1469
+ printImmediateSummary(taskId, status, reason) {
1470
+ if (!this.isTerminalStatus(status)) {
1471
+ return;
1472
+ }
1473
+ this.print('RESULT: ' + status.toUpperCase());
1474
+ this.print('TASK: ' + taskId);
1475
+ if (status === 'complete') {
1476
+ // COMPLETE: 4 lines fixed, no WHY
1477
+ this.print('NEXT: (none)');
1478
+ }
1479
+ else {
1480
+ // INCOMPLETE/ERROR: 5 lines fixed, WHY required
1481
+ this.print('NEXT: /logs ' + taskId);
1482
+ this.print('WHY: ' + (reason && reason.trim() ? reason : '(unknown)'));
1483
+ }
1484
+ this.print('HINT: /logs ' + taskId);
1485
+ }
1218
1486
  /**
1219
1487
  * Print execution result
1220
1488
  * Per spec 10_REPL_UX.md: Error details must be visible for fail-closed debugging
1489
+ * Also prints Immediate Summary for terminal states (per Property 39)
1221
1490
  */
1222
1491
  printExecutionResult(result) {
1492
+ // Get task ID from session tracking
1493
+ const taskId = this.session.current_task_id || this.session.last_task_id || 'unknown';
1494
+ // Map to TaskLogStatus
1495
+ const taskLogStatus = this.mapToTaskLogStatus(result.overall_status);
1496
+ // Update current_task_id / last_task_id on terminal state
1497
+ // Per spec 05_DATA_MODELS.md Property 38
1498
+ if (this.isTerminalStatus(taskLogStatus)) {
1499
+ this.session.last_task_id = this.session.current_task_id;
1500
+ this.session.current_task_id = null;
1501
+ }
1502
+ // Generate reason from result
1503
+ let reason;
1504
+ if (result.error) {
1505
+ reason = result.error.message;
1506
+ }
1507
+ else if (result.incomplete_task_reasons && result.incomplete_task_reasons.length > 0) {
1508
+ reason = result.incomplete_task_reasons[0].reason;
1509
+ }
1510
+ else if (result.overall_status === enums_1.OverallStatus.COMPLETE) {
1511
+ reason = 'タスクが正常に完了しました';
1512
+ }
1513
+ else {
1514
+ reason = '状態: ' + result.overall_status;
1515
+ }
1516
+ // Print Immediate Summary for terminal states (per Property 39)
1517
+ if (this.isTerminalStatus(taskLogStatus)) {
1518
+ this.printImmediateSummary(taskId, taskLogStatus, reason);
1519
+ }
1223
1520
  this.print('');
1224
1521
  this.print('--- Execution Result ---');
1225
1522
  this.print('Status: ' + result.overall_status);
1523
+ // Display executor mode (per redesign: visibility)
1524
+ if (result.executor_mode) {
1525
+ this.print('Executor: ' + result.executor_mode);
1526
+ }
1527
+ // Display duration (per redesign: visibility)
1528
+ if (result.duration_ms !== undefined && result.duration_ms > 0) {
1529
+ const seconds = (result.duration_ms / 1000).toFixed(1);
1530
+ this.print('Duration: ' + seconds + 's');
1531
+ }
1226
1532
  if (result.tasks_completed !== undefined) {
1227
1533
  this.print('Tasks: ' + result.tasks_completed + '/' + result.tasks_total + ' completed');
1228
1534
  // Track incomplete tasks for exit code (non-interactive mode)
@@ -1231,8 +1537,23 @@ class REPLInterface extends events_1.EventEmitter {
1231
1537
  this.updateExitCode();
1232
1538
  }
1233
1539
  }
1540
+ // Display files modified (per redesign: visibility)
1541
+ if (result.files_modified && result.files_modified.length > 0) {
1542
+ this.print('');
1543
+ this.print('Files Modified:');
1544
+ for (const file of result.files_modified) {
1545
+ this.print(' - ' + file);
1546
+ }
1547
+ }
1548
+ // Display executor output summary (per redesign: visibility)
1549
+ if (result.executor_output_summary) {
1550
+ this.print('');
1551
+ this.print('Response Summary:');
1552
+ this.print(' ' + result.executor_output_summary);
1553
+ }
1234
1554
  // Display error details when status is ERROR (critical for debugging fail-closed behavior)
1235
1555
  if (result.error) {
1556
+ this.print('');
1236
1557
  this.print('Error: ' + result.error.message);
1237
1558
  if (result.error.code) {
1238
1559
  this.print('Error Code: ' + result.error.code);
@@ -1243,6 +1564,7 @@ class REPLInterface extends events_1.EventEmitter {
1243
1564
  }
1244
1565
  // Display incomplete task reasons (helps identify why tasks failed)
1245
1566
  if (result.incomplete_task_reasons && result.incomplete_task_reasons.length > 0) {
1567
+ this.print('');
1246
1568
  this.print('Incomplete Tasks:');
1247
1569
  for (const reason of result.incomplete_task_reasons) {
1248
1570
  this.print(' - ' + reason.task_id + ': ' + reason.reason);
@@ -1260,6 +1582,16 @@ class REPLInterface extends events_1.EventEmitter {
1260
1582
  this.hasIncompleteTasks = true;
1261
1583
  this.updateExitCode();
1262
1584
  }
1585
+ // NO_EVIDENCE handling: show next action hint (per redesign requirement)
1586
+ // Per user requirement: NO_EVIDENCE should not complete silently
1587
+ if (result.overall_status === enums_1.OverallStatus.NO_EVIDENCE) {
1588
+ this.print('');
1589
+ this.print('HINT: No file changes were verified for this task.');
1590
+ this.print(' Possible next actions:');
1591
+ this.print(' - Check /logs for execution details');
1592
+ this.print(' - Retry with more specific instructions');
1593
+ this.print(' - Use /status to see current session state');
1594
+ }
1263
1595
  if (result.next_action !== undefined) {
1264
1596
  this.print('Next Action: ' + (result.next_action ? 'Yes' : 'No'));
1265
1597
  if (result.next_action_reason) {