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.
- package/dist/core/runner-core.d.ts +17 -0
- package/dist/core/runner-core.d.ts.map +1 -1
- package/dist/core/runner-core.js +46 -3
- package/dist/core/runner-core.js.map +1 -1
- package/dist/executor/recovery-executor.d.ts +101 -0
- package/dist/executor/recovery-executor.d.ts.map +1 -0
- package/dist/executor/recovery-executor.js +228 -0
- package/dist/executor/recovery-executor.js.map +1 -0
- 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/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/repl/commands/keys.d.ts.map +1 -1
- package/dist/repl/commands/keys.js +9 -1
- package/dist/repl/commands/keys.js.map +1 -1
- 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/commands/status.d.ts +21 -3
- package/dist/repl/commands/status.d.ts.map +1 -1
- package/dist/repl/commands/status.js +205 -60
- package/dist/repl/commands/status.js.map +1 -1
- package/dist/repl/repl-interface.d.ts +98 -2
- package/dist/repl/repl-interface.d.ts.map +1 -1
- package/dist/repl/repl-interface.js +366 -34
- package/dist/repl/repl-interface.js.map +1 -1
- package/package.json +5 -2
|
@@ -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
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
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
|
-
|
|
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
|
|
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:
|
|
659
|
-
description:
|
|
660
|
-
naturalLanguageTask:
|
|
771
|
+
id: task.id,
|
|
772
|
+
description: task.description,
|
|
773
|
+
naturalLanguageTask: task.description,
|
|
661
774
|
}],
|
|
662
775
|
selectedModel,
|
|
663
776
|
});
|
|
664
|
-
console.log(`[DEBUG
|
|
665
|
-
//
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1047
|
-
this.print(
|
|
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) {
|