pm-orchestrator-runner 1.0.6 → 1.0.8

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 (105) hide show
  1. package/README.md +20 -10
  2. package/dist/cli/index.js +36 -1
  3. package/dist/cli/index.js.map +1 -1
  4. package/dist/config/index.d.ts +9 -0
  5. package/dist/config/index.d.ts.map +1 -0
  6. package/dist/config/index.js +19 -0
  7. package/dist/config/index.js.map +1 -0
  8. package/dist/config/namespace.d.ts +86 -0
  9. package/dist/config/namespace.d.ts.map +1 -0
  10. package/dist/config/namespace.js +150 -0
  11. package/dist/config/namespace.js.map +1 -0
  12. package/dist/core/runner-core.d.ts +19 -0
  13. package/dist/core/runner-core.d.ts.map +1 -1
  14. package/dist/core/runner-core.js +67 -4
  15. package/dist/core/runner-core.js.map +1 -1
  16. package/dist/executor/claude-code-executor.d.ts +28 -0
  17. package/dist/executor/claude-code-executor.d.ts.map +1 -1
  18. package/dist/executor/claude-code-executor.js +184 -1
  19. package/dist/executor/claude-code-executor.js.map +1 -1
  20. package/dist/executor/deterministic-executor.d.ts +2 -1
  21. package/dist/executor/deterministic-executor.d.ts.map +1 -1
  22. package/dist/executor/deterministic-executor.js +7 -0
  23. package/dist/executor/deterministic-executor.js.map +1 -1
  24. package/dist/executor/recovery-executor.d.ts +2 -1
  25. package/dist/executor/recovery-executor.d.ts.map +1 -1
  26. package/dist/executor/recovery-executor.js +7 -0
  27. package/dist/executor/recovery-executor.js.map +1 -1
  28. package/dist/logging/task-log-manager.d.ts +17 -2
  29. package/dist/logging/task-log-manager.d.ts.map +1 -1
  30. package/dist/logging/task-log-manager.js +153 -63
  31. package/dist/logging/task-log-manager.js.map +1 -1
  32. package/dist/models/enums.d.ts +54 -0
  33. package/dist/models/enums.d.ts.map +1 -1
  34. package/dist/models/enums.js +59 -1
  35. package/dist/models/enums.js.map +1 -1
  36. package/dist/models/index.d.ts +4 -1
  37. package/dist/models/index.d.ts.map +1 -1
  38. package/dist/models/index.js +50 -2
  39. package/dist/models/index.js.map +1 -1
  40. package/dist/models/repl/task-log.d.ts +9 -0
  41. package/dist/models/repl/task-log.d.ts.map +1 -1
  42. package/dist/models/repl/task-log.js.map +1 -1
  43. package/dist/models/run.d.ts +82 -0
  44. package/dist/models/run.d.ts.map +1 -0
  45. package/dist/models/run.js +161 -0
  46. package/dist/models/run.js.map +1 -0
  47. package/dist/models/task-group.d.ts +164 -0
  48. package/dist/models/task-group.d.ts.map +1 -0
  49. package/dist/models/task-group.js +246 -0
  50. package/dist/models/task-group.js.map +1 -0
  51. package/dist/models/task.d.ts +7 -0
  52. package/dist/models/task.d.ts.map +1 -1
  53. package/dist/models/task.js.map +1 -1
  54. package/dist/models/thread.d.ts +53 -0
  55. package/dist/models/thread.d.ts.map +1 -0
  56. package/dist/models/thread.js +92 -0
  57. package/dist/models/thread.js.map +1 -0
  58. package/dist/pool/agent-pool.d.ts.map +1 -1
  59. package/dist/pool/agent-pool.js +2 -3
  60. package/dist/pool/agent-pool.js.map +1 -1
  61. package/dist/prompt/index.d.ts +8 -0
  62. package/dist/prompt/index.d.ts.map +1 -0
  63. package/dist/prompt/index.js +13 -0
  64. package/dist/prompt/index.js.map +1 -0
  65. package/dist/prompt/prompt-assembler.d.ts +145 -0
  66. package/dist/prompt/prompt-assembler.d.ts.map +1 -0
  67. package/dist/prompt/prompt-assembler.js +242 -0
  68. package/dist/prompt/prompt-assembler.js.map +1 -0
  69. package/dist/queue/index.d.ts +41 -0
  70. package/dist/queue/index.d.ts.map +1 -0
  71. package/dist/queue/index.js +42 -0
  72. package/dist/queue/index.js.map +1 -0
  73. package/dist/queue/queue-poller.d.ts +107 -0
  74. package/dist/queue/queue-poller.d.ts.map +1 -0
  75. package/dist/queue/queue-poller.js +181 -0
  76. package/dist/queue/queue-poller.js.map +1 -0
  77. package/dist/queue/queue-store.d.ts +163 -0
  78. package/dist/queue/queue-store.d.ts.map +1 -0
  79. package/dist/queue/queue-store.js +421 -0
  80. package/dist/queue/queue-store.js.map +1 -0
  81. package/dist/repl/commands/logs.d.ts +3 -1
  82. package/dist/repl/commands/logs.d.ts.map +1 -1
  83. package/dist/repl/commands/logs.js +23 -3
  84. package/dist/repl/commands/logs.js.map +1 -1
  85. package/dist/repl/index.d.ts +1 -0
  86. package/dist/repl/index.d.ts.map +1 -1
  87. package/dist/repl/index.js +3 -1
  88. package/dist/repl/index.js.map +1 -1
  89. package/dist/repl/repl-interface.d.ts +94 -6
  90. package/dist/repl/repl-interface.d.ts.map +1 -1
  91. package/dist/repl/repl-interface.js +350 -54
  92. package/dist/repl/repl-interface.js.map +1 -1
  93. package/dist/repl/two-pane-renderer.d.ts +148 -0
  94. package/dist/repl/two-pane-renderer.d.ts.map +1 -0
  95. package/dist/repl/two-pane-renderer.js +239 -0
  96. package/dist/repl/two-pane-renderer.js.map +1 -0
  97. package/dist/web/index.d.ts +45 -0
  98. package/dist/web/index.d.ts.map +1 -0
  99. package/dist/web/index.js +47 -0
  100. package/dist/web/index.js.map +1 -0
  101. package/dist/web/server.d.ts +71 -0
  102. package/dist/web/server.d.ts.map +1 -0
  103. package/dist/web/server.js +329 -0
  104. package/dist/web/server.js.map +1 -0
  105. package/package.json +12 -3
@@ -13,7 +13,8 @@
13
13
  * - Deterministic Exit Code: 0=COMPLETE, 1=ERROR, 2=INCOMPLETE
14
14
  *
15
15
  * Project Mode (per spec 10_REPL_UX.md, Property 32, 33):
16
- * - temp: Use temporary directory (default, cleaned up on exit)
16
+ * - cwd: Use current working directory (DEFAULT per spec)
17
+ * - temp: Use temporary directory (cleaned up on exit)
17
18
  * - fixed: Use specified directory (persists after exit)
18
19
  */
19
20
  var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
@@ -66,7 +67,9 @@ const provider_1 = require("./commands/provider");
66
67
  const models_1 = require("./commands/models");
67
68
  const keys_1 = require("./commands/keys");
68
69
  const logs_1 = require("./commands/logs");
70
+ const two_pane_renderer_1 = require("./two-pane-renderer");
69
71
  const global_config_1 = require("../config/global-config");
72
+ const claude_code_executor_1 = require("../executor/claude-code-executor");
70
73
  /**
71
74
  * Exit codes for non-interactive mode - per spec 10_REPL_UX.md
72
75
  * These are deterministic based on session state
@@ -117,6 +120,9 @@ class REPLInterface extends events_1.EventEmitter {
117
120
  hasIncompleteTasks = false;
118
121
  // Session completion tracking (prevents double completion)
119
122
  sessionCompleted = false;
123
+ // Non-blocking task queue (allows input while tasks are running)
124
+ taskQueue = [];
125
+ isTaskWorkerRunning = false;
120
126
  // Project mode support (per spec 10_REPL_UX.md, Property 32, 33)
121
127
  projectMode;
122
128
  verificationRoot;
@@ -131,6 +137,8 @@ class REPLInterface extends events_1.EventEmitter {
131
137
  modelsCommand;
132
138
  keysCommand;
133
139
  logsCommand;
140
+ // Two-pane renderer per spec 18_CLI_TWO_PANE.md
141
+ renderer;
134
142
  constructor(config = {}) {
135
143
  super();
136
144
  // Validate fixed mode configuration (Property 32)
@@ -142,12 +150,18 @@ class REPLInterface extends events_1.EventEmitter {
142
150
  throw new Error('project-root does not exist: ' + config.projectRoot);
143
151
  }
144
152
  }
145
- // Determine project mode
146
- this.projectMode = config.projectMode || 'temp';
153
+ // Determine project mode - per spec 10_REPL_UX.md: cwd is default
154
+ this.projectMode = config.projectMode || 'cwd';
147
155
  // Set verification root based on mode
148
156
  if (this.projectMode === 'fixed') {
149
157
  this.verificationRoot = config.projectRoot;
150
158
  }
159
+ else if (this.projectMode === 'cwd') {
160
+ // CWD mode: use current working directory (DEFAULT per spec 10_REPL_UX.md)
161
+ // Per spec lines 74-83: カレントディレクトリをそのまま使用
162
+ // But respect explicitly provided projectPath from --project flag
163
+ this.verificationRoot = config.projectPath || process.cwd();
164
+ }
151
165
  else {
152
166
  // Temp mode: create temporary directory immediately (synchronous)
153
167
  // This ensures getVerificationRoot() always returns a valid path
@@ -165,12 +179,15 @@ class REPLInterface extends events_1.EventEmitter {
165
179
  }, null, 2);
166
180
  fs.writeFileSync(path.join(claudeDir, 'settings.json'), settingsContent, 'utf-8');
167
181
  }
168
- // CRITICAL: In temp mode, use verificationRoot for projectPath and evidenceDir
169
- // This ensures files are created in the temp directory, not process.cwd()
170
- // In fixed mode, use projectRoot (from --project-root) if provided
182
+ // CRITICAL: Resolve projectPath based on mode
183
+ // - cwd mode: use process.cwd() (already set in verificationRoot)
184
+ // - temp mode: use verificationRoot (temp directory)
185
+ // - fixed mode: use projectRoot (from --project-root)
171
186
  const resolvedProjectPath = this.projectMode === 'temp'
172
187
  ? this.verificationRoot
173
- : (config.projectRoot || config.projectPath || process.cwd());
188
+ : this.projectMode === 'cwd'
189
+ ? this.verificationRoot // verificationRoot is process.cwd() in cwd mode
190
+ : (config.projectRoot || config.projectPath || process.cwd());
174
191
  this.config = {
175
192
  projectPath: resolvedProjectPath,
176
193
  evidenceDir: config.evidenceDir || path.join(resolvedProjectPath, '.claude', 'evidence'),
@@ -206,6 +223,10 @@ class REPLInterface extends events_1.EventEmitter {
206
223
  this.modelsCommand = new models_1.ModelsCommand();
207
224
  this.keysCommand = new keys_1.KeysCommand();
208
225
  this.logsCommand = new logs_1.LogsCommand();
226
+ // Initialize two-pane renderer per spec 18_CLI_TWO_PANE.md
227
+ this.renderer = new two_pane_renderer_1.TwoPaneRenderer({
228
+ prompt: this.config.prompt,
229
+ });
209
230
  }
210
231
  /**
211
232
  * Get project mode - per spec 10_REPL_UX.md
@@ -314,6 +335,19 @@ class REPLInterface extends events_1.EventEmitter {
314
335
  // The OS will clean it up eventually, and leaving it allows for debugging
315
336
  // In fixed mode, the directory persists as expected
316
337
  }
338
+ /**
339
+ * Check Claude Code CLI auth status
340
+ * Per spec/15_API_KEY_ENV_SANITIZE.md: Check at startup, exit on failure
341
+ *
342
+ * @returns AuthCheckResult with availability and login status
343
+ */
344
+ async checkClaudeCodeAuth() {
345
+ const executor = new claude_code_executor_1.ClaudeCodeExecutor({
346
+ projectPath: this.session.projectPath,
347
+ timeout: 30000,
348
+ });
349
+ return executor.checkAuthStatus();
350
+ }
317
351
  /**
318
352
  * Check API key status and show warning if not configured
319
353
  * API keys are stored in global config file (~/.pm-orchestrator-runner/config.json)
@@ -367,6 +401,29 @@ class REPLInterface extends events_1.EventEmitter {
367
401
  async start() {
368
402
  // Initialize project root
369
403
  await this.initialize();
404
+ // Per spec/15_API_KEY_ENV_SANITIZE.md lines 91-97: Startup checks
405
+ // Check Claude Code CLI availability and login status
406
+ if (this.config.authMode === 'claude-code') {
407
+ const authStatus = await this.checkClaudeCodeAuth();
408
+ if (!authStatus.available) {
409
+ console.error('');
410
+ console.error('ERROR: Claude Code CLI not available');
411
+ console.error(' ' + (authStatus.error || 'CLI not found'));
412
+ console.error('');
413
+ console.error('Please install Claude Code CLI and try again.');
414
+ console.error('');
415
+ process.exit(1);
416
+ }
417
+ if (!authStatus.loggedIn) {
418
+ console.error('');
419
+ console.error('ERROR: Claude Code CLI not logged in');
420
+ console.error(' ' + (authStatus.error || 'Login required'));
421
+ console.error('');
422
+ console.error('Please run: claude setup-token');
423
+ console.error('');
424
+ process.exit(1);
425
+ }
426
+ }
370
427
  // Check API keys for api-key mode
371
428
  if (this.config.authMode === 'api-key') {
372
429
  await this.checkApiKeyStatus();
@@ -423,6 +480,8 @@ class REPLInterface extends events_1.EventEmitter {
423
480
  }
424
481
  this.running = false;
425
482
  // Ensure all output is flushed before cleanup
483
+ // Per spec 18_CLI_TWO_PANE.md: Flush renderer pending logs
484
+ this.renderer.flush();
426
485
  await this.flushStdout();
427
486
  await this.cleanup();
428
487
  // In non-interactive mode, set process exit code
@@ -455,6 +514,8 @@ class REPLInterface extends events_1.EventEmitter {
455
514
  * - This allows long messages with newlines to be sent together
456
515
  */
457
516
  enqueueInput(line) {
517
+ // Per spec 18_CLI_TWO_PANE.md: Clear input state after line is committed
518
+ this.renderer.clearInput();
458
519
  const trimmed = line.trim();
459
520
  if (!trimmed) {
460
521
  // Empty line - submit accumulated multi-line buffer
@@ -526,6 +587,7 @@ class REPLInterface extends events_1.EventEmitter {
526
587
  // In non-interactive mode, flush output after each command
527
588
  // This ensures output is visible before processing next command
528
589
  if (this.executionMode === 'non_interactive') {
590
+ this.renderer.flush();
529
591
  await this.flushStdout();
530
592
  }
531
593
  // Show prompt after each input if still running (interactive mode only)
@@ -626,10 +688,15 @@ class REPLInterface extends events_1.EventEmitter {
626
688
  return trimmed === 'exit';
627
689
  }
628
690
  /**
629
- * Process natural language input
691
+ * Process natural language input - NON-BLOCKING
630
692
  * Per spec 10_REPL_UX.md L117-118: Model selection is REPL-local
631
693
  * Model is read from .claude/repl.json and passed to executor via runner
632
694
  *
695
+ * Non-blocking design:
696
+ * - Creates task and adds to queue immediately
697
+ * - Returns control to input prompt right away
698
+ * - Background worker processes tasks asynchronously
699
+ *
633
700
  * Auto-start: In non-interactive mode, automatically start a session if none exists
634
701
  * This improves CLI usability for piped input and scripting
635
702
  *
@@ -647,18 +714,15 @@ class REPLInterface extends events_1.EventEmitter {
647
714
  this.print('HINT: /exit');
648
715
  return;
649
716
  }
717
+ // Auto-start session for any natural language input (no /start required)
718
+ // Per redesign: natural language input = automatic task creation
650
719
  if (this.session.status !== 'running') {
651
- // In non-interactive mode, auto-start a session for better UX
652
- if (this.executionMode === 'non_interactive') {
653
- console.log('[DEBUG processNaturalLanguage] auto-starting session for non-interactive mode');
654
- const startResult = await this.handleStart([]);
655
- if (!startResult.success) {
656
- this.print('Failed to auto-start session: ' + (startResult.error?.message || 'Unknown error'));
657
- return;
658
- }
659
- }
660
- else {
661
- this.print('No active session. Use /start to begin, or /init to set up a new project.');
720
+ console.log('[DEBUG processNaturalLanguage] auto-starting session');
721
+ this.print('');
722
+ this.print('[Auto-starting session...]');
723
+ const startResult = await this.handleStart([]);
724
+ if (!startResult.success) {
725
+ this.print('Failed to auto-start session: ' + (startResult.error?.message || 'Unknown error'));
662
726
  return;
663
727
  }
664
728
  }
@@ -666,54 +730,150 @@ class REPLInterface extends events_1.EventEmitter {
666
730
  this.print('Runner not initialized. Use /start first.');
667
731
  return;
668
732
  }
669
- // Generate task ID and set current_task_id
733
+ // Generate task ID
670
734
  // Per spec 05_DATA_MODELS.md Property 38
671
735
  const taskId = 'task-' + Date.now();
672
- this.session.current_task_id = taskId;
673
- this.print('Processing task: ' + input);
736
+ // Create queued task
737
+ const queuedTask = {
738
+ id: taskId,
739
+ description: input,
740
+ state: 'QUEUED',
741
+ queuedAt: Date.now(),
742
+ startedAt: null,
743
+ completedAt: null,
744
+ };
745
+ // Add to task queue
746
+ this.taskQueue.push(queuedTask);
747
+ // Display task queued info (per redesign: visibility)
748
+ // Per user requirement: Provider/Mode/Auth must be shown at task start
749
+ this.print('');
750
+ this.print('--- Task Queued ---');
674
751
  this.print('Task ID: ' + taskId);
752
+ this.print('State: QUEUED');
753
+ // Show LLM layer info (Provider/Mode/Auth) - per redesign requirement
754
+ if (this.config.authMode === 'claude-code') {
755
+ this.print('Provider: Claude Code CLI (uses your Claude subscription, no API key required)');
756
+ }
757
+ else if (this.config.authMode === 'api-key') {
758
+ this.print('Provider: Anthropic API (API key configured)');
759
+ }
760
+ else {
761
+ this.print('Provider: ' + this.config.authMode);
762
+ }
763
+ // Show prompt summary (first 100 chars)
764
+ const promptSummary = input.length > 100 ? input.substring(0, 100) + '...' : input;
765
+ this.print('Prompt: ' + promptSummary);
766
+ this.print('-------------------');
767
+ this.print('');
768
+ this.print('(Input is not blocked - you can submit more tasks with /tasks to view status)');
675
769
  this.print('');
770
+ // Start background worker if not running
771
+ // The worker runs asynchronously - we don't await it
772
+ if (!this.isTaskWorkerRunning) {
773
+ this.startTaskWorker();
774
+ }
775
+ console.log(`[DEBUG processNaturalLanguage] task queued, returning immediately`);
776
+ }
777
+ /**
778
+ * Background task worker - processes queued tasks asynchronously
779
+ * Runs in background, allowing input to continue while tasks execute
780
+ */
781
+ async startTaskWorker() {
782
+ if (this.isTaskWorkerRunning) {
783
+ return;
784
+ }
785
+ this.isTaskWorkerRunning = true;
786
+ console.log('[DEBUG startTaskWorker] worker started');
787
+ try {
788
+ while (this.running) {
789
+ // Find next QUEUED task
790
+ const nextTask = this.taskQueue.find(t => t.state === 'QUEUED');
791
+ if (!nextTask) {
792
+ // No more queued tasks - worker exits
793
+ break;
794
+ }
795
+ // Execute the task
796
+ await this.executeQueuedTask(nextTask);
797
+ }
798
+ }
799
+ finally {
800
+ this.isTaskWorkerRunning = false;
801
+ console.log('[DEBUG startTaskWorker] worker stopped');
802
+ }
803
+ }
804
+ /**
805
+ * Execute a single queued task
806
+ * Updates task state and prints results
807
+ */
808
+ async executeQueuedTask(task) {
809
+ console.log(`[DEBUG executeQueuedTask] starting task ${task.id}`);
810
+ // Update state to RUNNING
811
+ task.state = 'RUNNING';
812
+ task.startedAt = Date.now();
813
+ this.session.current_task_id = task.id;
814
+ // Print status update
815
+ this.print('');
816
+ this.print('--- Task Started ---');
817
+ this.print('Task ID: ' + task.id);
818
+ this.print('State: RUNNING');
819
+ this.print('--------------------');
676
820
  try {
677
821
  // Per spec 10_REPL_UX.md L117-118: Get selected model from REPL config
678
- // Model is REPL-local, stored in .claude/repl.json
679
822
  let selectedModel;
680
823
  const modelResult = await this.modelCommand.getModel(this.session.projectPath);
681
824
  if (modelResult.success && modelResult.model && modelResult.model !== 'UNSET') {
682
825
  selectedModel = modelResult.model;
683
826
  }
684
- console.log(`[DEBUG processNaturalLanguage] calling runner.execute...`);
685
- // Create task from natural language input
686
- // CRITICAL: naturalLanguageTask must be set to trigger Claude Code execution
687
- // Without this, the task would only use fallback file creation patterns
827
+ console.log(`[DEBUG executeQueuedTask] calling runner.execute...`);
688
828
  const result = await this.session.runner.execute({
689
829
  tasks: [{
690
- id: taskId,
691
- description: input,
692
- naturalLanguageTask: input,
830
+ id: task.id,
831
+ description: task.description,
832
+ naturalLanguageTask: task.description,
693
833
  }],
694
834
  selectedModel,
695
835
  });
696
- console.log(`[DEBUG processNaturalLanguage] runner.execute returned, status=${result.overall_status}`);
697
- // Handle INCOMPLETE with clarification interactively
836
+ console.log(`[DEBUG executeQueuedTask] runner.execute returned, status=${result.overall_status}`);
837
+ // Update task state based on result
838
+ task.completedAt = Date.now();
839
+ task.resultStatus = result.overall_status;
840
+ task.filesModified = result.files_modified;
841
+ task.responseSummary = result.executor_output_summary;
842
+ switch (result.overall_status) {
843
+ case enums_1.OverallStatus.COMPLETE:
844
+ task.state = 'COMPLETE';
845
+ break;
846
+ case enums_1.OverallStatus.INCOMPLETE:
847
+ task.state = 'INCOMPLETE';
848
+ break;
849
+ case enums_1.OverallStatus.ERROR:
850
+ task.state = 'ERROR';
851
+ task.errorMessage = result.error?.message;
852
+ break;
853
+ default:
854
+ task.state = 'COMPLETE';
855
+ }
856
+ // Handle INCOMPLETE with clarification (but don't block)
698
857
  if (result.overall_status === enums_1.OverallStatus.INCOMPLETE &&
699
858
  result.incomplete_task_reasons &&
700
859
  result.incomplete_task_reasons.length > 0) {
701
- const clarificationNeeded = await this.handleClarificationNeeded(input, result.incomplete_task_reasons);
702
- if (clarificationNeeded) {
703
- // User provided clarification, will be processed in next input
704
- return;
705
- }
860
+ await this.handleClarificationNeeded(task.description, result.incomplete_task_reasons);
706
861
  }
707
862
  this.printExecutionResult(result);
708
863
  }
709
864
  catch (err) {
710
- console.log(`[DEBUG processNaturalLanguage] error: ${err.message}`);
711
- // Clear current_task_id on exception (fail-closed)
865
+ console.log(`[DEBUG executeQueuedTask] error: ${err.message}`);
866
+ task.state = 'ERROR';
867
+ task.completedAt = Date.now();
868
+ task.errorMessage = err.message;
869
+ this.printError(err);
870
+ }
871
+ finally {
872
+ // Clear current_task_id
712
873
  this.session.last_task_id = this.session.current_task_id;
713
874
  this.session.current_task_id = null;
714
- this.printError(err);
715
875
  }
716
- console.log(`[DEBUG processNaturalLanguage] done`);
876
+ console.log(`[DEBUG executeQueuedTask] task ${task.id} done, state=${task.state}`);
717
877
  }
718
878
  /**
719
879
  * Handle clarification needed - prompt user interactively
@@ -764,6 +924,7 @@ class REPLInterface extends events_1.EventEmitter {
764
924
  }
765
925
  /**
766
926
  * Print welcome message with clear auth status
927
+ * Per spec/15_API_KEY_ENV_SANITIZE.md: Show required startup display
767
928
  */
768
929
  printWelcome() {
769
930
  this.print('');
@@ -775,18 +936,18 @@ class REPLInterface extends events_1.EventEmitter {
775
936
  this.print('Verification Root: ' + this.verificationRoot);
776
937
  }
777
938
  this.print('');
778
- // Clear auth status display
779
- this.print('Authentication Status:');
939
+ // Per spec/15_API_KEY_ENV_SANITIZE.md lines 100-108: Required startup display
780
940
  if (this.config.authMode === 'claude-code') {
781
- this.print(' Provider: Claude Code CLI');
782
- this.print(' API Key: Not required (uses your Claude subscription)');
783
- this.print(' Status: Ready');
941
+ // Spec-mandated display format - MUST be shown exactly as specified
942
+ this.print('Executor: Claude Code CLI');
943
+ this.print('Auth: Uses Claude subscription (no API key required)');
944
+ this.print('Env: ALLOWLIST mode (only PATH, HOME, etc. passed to subprocess)');
784
945
  }
785
946
  else {
786
947
  // Check for API keys when using API mode
787
948
  const anthropicKey = process.env.ANTHROPIC_API_KEY;
788
949
  const openaiKey = process.env.OPENAI_API_KEY;
789
- this.print(' Provider: API Mode');
950
+ this.print('Executor: API Mode');
790
951
  this.print(' ANTHROPIC_API_KEY: ' + (anthropicKey ? 'SET' : 'NOT SET'));
791
952
  this.print(' OPENAI_API_KEY: ' + (openaiKey ? 'SET' : 'NOT SET'));
792
953
  if (!anthropicKey && !openaiKey) {
@@ -1063,7 +1224,8 @@ class REPLInterface extends events_1.EventEmitter {
1063
1224
  // /logs <task-id> [--full]
1064
1225
  const taskId = args[0];
1065
1226
  const full = args.includes('--full');
1066
- const result = await this.logsCommand.getTaskDetail(this.session.projectPath, taskId, full);
1227
+ // Per redesign: Pass sessionId for visibility fields
1228
+ const result = await this.logsCommand.getTaskDetail(this.session.projectPath, taskId, full, this.session.sessionId ?? undefined);
1067
1229
  if (result.success) {
1068
1230
  this.print(result.output || 'No log detail found.');
1069
1231
  return { success: true };
@@ -1076,10 +1238,78 @@ class REPLInterface extends events_1.EventEmitter {
1076
1238
  }
1077
1239
  /**
1078
1240
  * Handle /tasks command
1241
+ * Shows task queue with RUNNING/QUEUED/COMPLETE/ERROR/INCOMPLETE states
1242
+ * Per redesign: proves non-blocking by showing multiple tasks simultaneously
1079
1243
  */
1080
1244
  async handleTasks() {
1081
- const result = await this.statusCommands.getTasks();
1082
- this.print(result);
1245
+ this.print('');
1246
+ this.print('=== Task Queue ===');
1247
+ this.print('');
1248
+ if (this.taskQueue.length === 0) {
1249
+ this.print('No tasks in queue.');
1250
+ this.print('');
1251
+ // Also show legacy tasks from statusCommands if available
1252
+ const legacyResult = await this.statusCommands.getTasks();
1253
+ if (legacyResult && legacyResult.trim()) {
1254
+ this.print('--- Session Tasks ---');
1255
+ this.print(legacyResult);
1256
+ }
1257
+ return;
1258
+ }
1259
+ // Count by state
1260
+ const running = this.taskQueue.filter(t => t.state === 'RUNNING').length;
1261
+ const queued = this.taskQueue.filter(t => t.state === 'QUEUED').length;
1262
+ const complete = this.taskQueue.filter(t => t.state === 'COMPLETE').length;
1263
+ const incomplete = this.taskQueue.filter(t => t.state === 'INCOMPLETE').length;
1264
+ const error = this.taskQueue.filter(t => t.state === 'ERROR').length;
1265
+ this.print('Summary: ' + running + ' RUNNING, ' + queued + ' QUEUED, ' + complete + ' COMPLETE, ' + incomplete + ' INCOMPLETE, ' + error + ' ERROR');
1266
+ this.print('');
1267
+ // List all tasks with state
1268
+ for (const task of this.taskQueue) {
1269
+ const promptSummary = task.description.length > 50
1270
+ ? task.description.substring(0, 50) + '...'
1271
+ : task.description;
1272
+ let durationStr = '';
1273
+ if (task.startedAt) {
1274
+ const endTime = task.completedAt || Date.now();
1275
+ const durationMs = endTime - task.startedAt;
1276
+ durationStr = ' (' + (durationMs / 1000).toFixed(1) + 's)';
1277
+ }
1278
+ // State indicator with visual marker
1279
+ let stateMarker = '';
1280
+ switch (task.state) {
1281
+ case 'RUNNING':
1282
+ stateMarker = '[*]';
1283
+ break;
1284
+ case 'QUEUED':
1285
+ stateMarker = '[ ]';
1286
+ break;
1287
+ case 'COMPLETE':
1288
+ stateMarker = '[v]';
1289
+ break;
1290
+ case 'INCOMPLETE':
1291
+ stateMarker = '[!]';
1292
+ break;
1293
+ case 'ERROR':
1294
+ stateMarker = '[X]';
1295
+ break;
1296
+ }
1297
+ this.print(stateMarker + ' ' + task.id + ' | ' + task.state + durationStr);
1298
+ this.print(' ' + promptSummary);
1299
+ // Show error message if error
1300
+ if (task.state === 'ERROR' && task.errorMessage) {
1301
+ this.print(' Error: ' + task.errorMessage);
1302
+ }
1303
+ // Show files modified if complete
1304
+ if (task.state === 'COMPLETE' && task.filesModified && task.filesModified.length > 0) {
1305
+ this.print(' Files: ' + task.filesModified.slice(0, 3).join(', ') +
1306
+ (task.filesModified.length > 3 ? ' (+' + (task.filesModified.length - 3) + ' more)' : ''));
1307
+ }
1308
+ }
1309
+ this.print('');
1310
+ this.print('Use /logs <task-id> for details.');
1311
+ this.print('==================');
1312
+ this.print('');
1083
1313
  }
1084
1314
  /**
1085
1315
  * Handle /status command
@@ -1090,13 +1320,18 @@ class REPLInterface extends events_1.EventEmitter {
1090
1320
  }
1091
1321
  /**
1092
1322
  * Handle /start command
1093
- * Per spec Property 32, 33: Use verification_root for file operations in temp mode
1323
+ * Per spec Property 32, 33: Use verification_root for file operations
1324
+ * - cwd mode: use process.cwd() (verificationRoot)
1325
+ * - temp mode: use temp directory (verificationRoot)
1326
+ * - fixed mode: use projectRoot
1094
1327
  */
1095
1328
  async handleStart(args) {
1096
- // In temp mode, use verificationRoot for file operations
1329
+ // In cwd/temp mode, use verificationRoot for file operations
1097
1330
  // In fixed mode or with explicit args, use the provided/session path
1098
1331
  const projectPath = args[0] ||
1099
- (this.projectMode === 'temp' ? this.verificationRoot : this.session.projectPath);
1332
+ (this.projectMode === 'cwd' || this.projectMode === 'temp'
1333
+ ? this.verificationRoot
1334
+ : this.session.projectPath);
1100
1335
  try {
1101
1336
  const result = await this.sessionCommands.start(projectPath);
1102
1337
  if (result.success) {
@@ -1210,6 +1445,7 @@ class REPLInterface extends events_1.EventEmitter {
1210
1445
  * Per spec 10_REPL_UX.md: Ensure clean exit with flushed output
1211
1446
  *
1212
1447
  * Guarantees:
1448
+ * - Waits for running tasks to complete (task worker)
1213
1449
  * - Session state is persisted before exit
1214
1450
  * - All output is flushed before readline closes
1215
1451
  * - Double-completion is prevented via sessionCompleted flag
@@ -1223,6 +1459,18 @@ class REPLInterface extends events_1.EventEmitter {
1223
1459
  return;
1224
1460
  }
1225
1461
  this.sessionCompleted = true;
1462
+ // Wait for task worker to complete any running tasks
1463
+ if (this.isTaskWorkerRunning) {
1464
+ const runningTasks = this.taskQueue.filter(t => t.state === 'RUNNING');
1465
+ const queuedTasks = this.taskQueue.filter(t => t.state === 'QUEUED');
1466
+ if (runningTasks.length > 0 || queuedTasks.length > 0) {
1467
+ this.print('Waiting for ' + runningTasks.length + ' running and ' + queuedTasks.length + ' queued tasks to complete...');
1468
+ }
1469
+ while (this.isTaskWorkerRunning) {
1470
+ await new Promise(r => setTimeout(r, 100));
1471
+ }
1472
+ this.print('All tasks completed.');
1473
+ }
1226
1474
  this.print('Saving session state...');
1227
1475
  if (this.session.runner && this.session.sessionId) {
1228
1476
  try {
@@ -1246,6 +1494,7 @@ class REPLInterface extends events_1.EventEmitter {
1246
1494
  }
1247
1495
  this.print('Goodbye!');
1248
1496
  // Flush output before closing (critical for non-interactive mode)
1497
+ this.renderer.flush();
1249
1498
  await this.flushStdout();
1250
1499
  this.running = false;
1251
1500
  this.rl?.close();
@@ -1337,6 +1586,15 @@ class REPLInterface extends events_1.EventEmitter {
1337
1586
  this.print('');
1338
1587
  this.print('--- Execution Result ---');
1339
1588
  this.print('Status: ' + result.overall_status);
1589
+ // Display executor mode (per redesign: visibility)
1590
+ if (result.executor_mode) {
1591
+ this.print('Executor: ' + result.executor_mode);
1592
+ }
1593
+ // Display duration (per redesign: visibility)
1594
+ if (result.duration_ms !== undefined && result.duration_ms > 0) {
1595
+ const seconds = (result.duration_ms / 1000).toFixed(1);
1596
+ this.print('Duration: ' + seconds + 's');
1597
+ }
1340
1598
  if (result.tasks_completed !== undefined) {
1341
1599
  this.print('Tasks: ' + result.tasks_completed + '/' + result.tasks_total + ' completed');
1342
1600
  // Track incomplete tasks for exit code (non-interactive mode)
@@ -1345,8 +1603,23 @@ class REPLInterface extends events_1.EventEmitter {
1345
1603
  this.updateExitCode();
1346
1604
  }
1347
1605
  }
1606
+ // Display files modified (per redesign: visibility)
1607
+ if (result.files_modified && result.files_modified.length > 0) {
1608
+ this.print('');
1609
+ this.print('Files Modified:');
1610
+ for (const file of result.files_modified) {
1611
+ this.print(' - ' + file);
1612
+ }
1613
+ }
1614
+ // Display executor output summary (per redesign: visibility)
1615
+ if (result.executor_output_summary) {
1616
+ this.print('');
1617
+ this.print('Response Summary:');
1618
+ this.print(' ' + result.executor_output_summary);
1619
+ }
1348
1620
  // Display error details when status is ERROR (critical for debugging fail-closed behavior)
1349
1621
  if (result.error) {
1622
+ this.print('');
1350
1623
  this.print('Error: ' + result.error.message);
1351
1624
  if (result.error.code) {
1352
1625
  this.print('Error Code: ' + result.error.code);
@@ -1357,6 +1630,7 @@ class REPLInterface extends events_1.EventEmitter {
1357
1630
  }
1358
1631
  // Display incomplete task reasons (helps identify why tasks failed)
1359
1632
  if (result.incomplete_task_reasons && result.incomplete_task_reasons.length > 0) {
1633
+ this.print('');
1360
1634
  this.print('Incomplete Tasks:');
1361
1635
  for (const reason of result.incomplete_task_reasons) {
1362
1636
  this.print(' - ' + reason.task_id + ': ' + reason.reason);
@@ -1374,6 +1648,16 @@ class REPLInterface extends events_1.EventEmitter {
1374
1648
  this.hasIncompleteTasks = true;
1375
1649
  this.updateExitCode();
1376
1650
  }
1651
+ // NO_EVIDENCE handling: show next action hint (per redesign requirement)
1652
+ // Per user requirement: NO_EVIDENCE should not complete silently
1653
+ if (result.overall_status === enums_1.OverallStatus.NO_EVIDENCE) {
1654
+ this.print('');
1655
+ this.print('HINT: No file changes were verified for this task.');
1656
+ this.print(' Possible next actions:');
1657
+ this.print(' - Check /logs for execution details');
1658
+ this.print(' - Retry with more specific instructions');
1659
+ this.print(' - Use /status to see current session state');
1660
+ }
1377
1661
  if (result.next_action !== undefined) {
1378
1662
  this.print('Next Action: ' + (result.next_action ? 'Yes' : 'No'));
1379
1663
  if (result.next_action_reason) {
@@ -1386,9 +1670,21 @@ class REPLInterface extends events_1.EventEmitter {
1386
1670
  /**
1387
1671
  * Print message with flush guarantee for non-interactive mode
1388
1672
  * Per spec 10_REPL_UX.md: Output Flush Guarantee
1673
+ * Per spec 18_CLI_TWO_PANE.md: Use TwoPaneRenderer for TTY output
1674
+ *
1675
+ * Syncs readline input state to renderer before output,
1676
+ * ensuring input line is preserved during log output.
1389
1677
  */
1390
1678
  print(message) {
1391
- console.log(message);
1679
+ // Sync input state from readline to renderer
1680
+ // Per spec 18_CLI_TWO_PANE.md: Input line must never be disrupted
1681
+ if (this.rl && this.renderer.isEnabled()) {
1682
+ const rlAny = this.rl;
1683
+ const line = rlAny.line || '';
1684
+ const cursor = rlAny.cursor ?? line.length;
1685
+ this.renderer.updateInput(line, cursor);
1686
+ }
1687
+ this.renderer.writeLog(message);
1392
1688
  }
1393
1689
  /**
1394
1690
  * Flush stdout - ensures all output is written before continuing