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.
- package/README.md +20 -10
- package/dist/cli/index.js +36 -1
- package/dist/cli/index.js.map +1 -1
- package/dist/config/index.d.ts +9 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +19 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config/namespace.d.ts +86 -0
- package/dist/config/namespace.d.ts.map +1 -0
- package/dist/config/namespace.js +150 -0
- package/dist/config/namespace.js.map +1 -0
- package/dist/core/runner-core.d.ts +19 -0
- package/dist/core/runner-core.d.ts.map +1 -1
- package/dist/core/runner-core.js +67 -4
- package/dist/core/runner-core.js.map +1 -1
- package/dist/executor/claude-code-executor.d.ts +28 -0
- package/dist/executor/claude-code-executor.d.ts.map +1 -1
- package/dist/executor/claude-code-executor.js +184 -1
- package/dist/executor/claude-code-executor.js.map +1 -1
- package/dist/executor/deterministic-executor.d.ts +2 -1
- package/dist/executor/deterministic-executor.d.ts.map +1 -1
- package/dist/executor/deterministic-executor.js +7 -0
- package/dist/executor/deterministic-executor.js.map +1 -1
- package/dist/executor/recovery-executor.d.ts +2 -1
- package/dist/executor/recovery-executor.d.ts.map +1 -1
- package/dist/executor/recovery-executor.js +7 -0
- package/dist/executor/recovery-executor.js.map +1 -1
- package/dist/logging/task-log-manager.d.ts +17 -2
- package/dist/logging/task-log-manager.d.ts.map +1 -1
- package/dist/logging/task-log-manager.js +153 -63
- package/dist/logging/task-log-manager.js.map +1 -1
- package/dist/models/enums.d.ts +54 -0
- package/dist/models/enums.d.ts.map +1 -1
- package/dist/models/enums.js +59 -1
- package/dist/models/enums.js.map +1 -1
- package/dist/models/index.d.ts +4 -1
- package/dist/models/index.d.ts.map +1 -1
- package/dist/models/index.js +50 -2
- package/dist/models/index.js.map +1 -1
- package/dist/models/repl/task-log.d.ts +9 -0
- package/dist/models/repl/task-log.d.ts.map +1 -1
- package/dist/models/repl/task-log.js.map +1 -1
- package/dist/models/run.d.ts +82 -0
- package/dist/models/run.d.ts.map +1 -0
- package/dist/models/run.js +161 -0
- package/dist/models/run.js.map +1 -0
- package/dist/models/task-group.d.ts +164 -0
- package/dist/models/task-group.d.ts.map +1 -0
- package/dist/models/task-group.js +246 -0
- package/dist/models/task-group.js.map +1 -0
- package/dist/models/task.d.ts +7 -0
- package/dist/models/task.d.ts.map +1 -1
- package/dist/models/task.js.map +1 -1
- package/dist/models/thread.d.ts +53 -0
- package/dist/models/thread.d.ts.map +1 -0
- package/dist/models/thread.js +92 -0
- package/dist/models/thread.js.map +1 -0
- package/dist/pool/agent-pool.d.ts.map +1 -1
- package/dist/pool/agent-pool.js +2 -3
- package/dist/pool/agent-pool.js.map +1 -1
- package/dist/prompt/index.d.ts +8 -0
- package/dist/prompt/index.d.ts.map +1 -0
- package/dist/prompt/index.js +13 -0
- package/dist/prompt/index.js.map +1 -0
- package/dist/prompt/prompt-assembler.d.ts +145 -0
- package/dist/prompt/prompt-assembler.d.ts.map +1 -0
- package/dist/prompt/prompt-assembler.js +242 -0
- package/dist/prompt/prompt-assembler.js.map +1 -0
- package/dist/queue/index.d.ts +41 -0
- package/dist/queue/index.d.ts.map +1 -0
- package/dist/queue/index.js +42 -0
- package/dist/queue/index.js.map +1 -0
- package/dist/queue/queue-poller.d.ts +107 -0
- package/dist/queue/queue-poller.d.ts.map +1 -0
- package/dist/queue/queue-poller.js +181 -0
- package/dist/queue/queue-poller.js.map +1 -0
- package/dist/queue/queue-store.d.ts +163 -0
- package/dist/queue/queue-store.d.ts.map +1 -0
- package/dist/queue/queue-store.js +421 -0
- package/dist/queue/queue-store.js.map +1 -0
- package/dist/repl/commands/logs.d.ts +3 -1
- package/dist/repl/commands/logs.d.ts.map +1 -1
- package/dist/repl/commands/logs.js +23 -3
- package/dist/repl/commands/logs.js.map +1 -1
- package/dist/repl/index.d.ts +1 -0
- package/dist/repl/index.d.ts.map +1 -1
- package/dist/repl/index.js +3 -1
- package/dist/repl/index.js.map +1 -1
- package/dist/repl/repl-interface.d.ts +94 -6
- package/dist/repl/repl-interface.d.ts.map +1 -1
- package/dist/repl/repl-interface.js +350 -54
- package/dist/repl/repl-interface.js.map +1 -1
- package/dist/repl/two-pane-renderer.d.ts +148 -0
- package/dist/repl/two-pane-renderer.d.ts.map +1 -0
- package/dist/repl/two-pane-renderer.js +239 -0
- package/dist/repl/two-pane-renderer.js.map +1 -0
- package/dist/web/index.d.ts +45 -0
- package/dist/web/index.d.ts.map +1 -0
- package/dist/web/index.js +47 -0
- package/dist/web/index.js.map +1 -0
- package/dist/web/server.d.ts +71 -0
- package/dist/web/server.d.ts.map +1 -0
- package/dist/web/server.js +329 -0
- package/dist/web/server.js.map +1 -0
- 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
|
-
* -
|
|
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 || '
|
|
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:
|
|
169
|
-
//
|
|
170
|
-
//
|
|
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
|
-
:
|
|
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
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
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
|
|
733
|
+
// Generate task ID
|
|
670
734
|
// Per spec 05_DATA_MODELS.md Property 38
|
|
671
735
|
const taskId = 'task-' + Date.now();
|
|
672
|
-
|
|
673
|
-
|
|
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
|
|
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:
|
|
691
|
-
description:
|
|
692
|
-
naturalLanguageTask:
|
|
830
|
+
id: task.id,
|
|
831
|
+
description: task.description,
|
|
832
|
+
naturalLanguageTask: task.description,
|
|
693
833
|
}],
|
|
694
834
|
selectedModel,
|
|
695
835
|
});
|
|
696
|
-
console.log(`[DEBUG
|
|
697
|
-
//
|
|
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
|
-
|
|
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
|
|
711
|
-
|
|
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
|
|
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
|
-
//
|
|
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
|
-
|
|
782
|
-
this.print('
|
|
783
|
-
this.print('
|
|
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('
|
|
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
|
-
|
|
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
|
-
|
|
1082
|
-
this.print(
|
|
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
|
|
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 === '
|
|
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
|
-
|
|
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
|