maistro 1.0.424 → 1.0.454
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/app.d.ts.map +1 -1
- package/dist/app.js +147 -25
- package/dist/app.js.map +1 -1
- package/dist/caffeinate.js +4 -4
- package/dist/caffeinate.js.map +1 -1
- package/dist/executor.d.ts +0 -1
- package/dist/executor.d.ts.map +1 -1
- package/dist/executor.js +0 -2
- package/dist/executor.js.map +1 -1
- package/dist/logger.d.ts +2 -1
- package/dist/logger.d.ts.map +1 -1
- package/dist/logger.js +23 -5
- package/dist/logger.js.map +1 -1
- package/dist/logo.d.ts.map +1 -1
- package/dist/logo.js +26 -1
- package/dist/logo.js.map +1 -1
- package/dist/orchestrator.d.ts.map +1 -1
- package/dist/orchestrator.js +0 -1
- package/dist/orchestrator.js.map +1 -1
- package/dist/planner.d.ts +2 -0
- package/dist/planner.d.ts.map +1 -1
- package/dist/planner.js +209 -145
- package/dist/planner.js.map +1 -1
- package/dist/screen.d.ts.map +1 -1
- package/dist/screen.js +8 -1
- package/dist/screen.js.map +1 -1
- package/dist/ui.d.ts +1 -1
- package/dist/ui.d.ts.map +1 -1
- package/dist/ui.js +39 -6
- package/dist/ui.js.map +1 -1
- package/dist/validator.d.ts.map +1 -1
- package/dist/validator.js +3 -1
- package/dist/validator.js.map +1 -1
- package/package.json +1 -1
package/dist/app.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../src/app.ts"],"names":[],"mappings":"AAiOA,qBAAa,UAAU;IACrB,OAAO,CAAC,EAAE,CAAgB;IAC1B,OAAO,CAAC,YAAY,CAA6B;IACjD,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,aAAa,CAAS;IAG9B,OAAO,CAAC,cAAc,CAA+B;gBAEzC,WAAW,GAAE,MAAY;IAsBrC,OAAO,CAAC,eAAe;IASvB;;;OAGG;YACW,qBAAqB;IA+CnC;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../src/app.ts"],"names":[],"mappings":"AAiOA,qBAAa,UAAU;IACrB,OAAO,CAAC,EAAE,CAAgB;IAC1B,OAAO,CAAC,YAAY,CAA6B;IACjD,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,aAAa,CAAS;IAG9B,OAAO,CAAC,cAAc,CAA+B;gBAEzC,WAAW,GAAE,MAAY;IAsBrC,OAAO,CAAC,eAAe;IASvB;;;OAGG;YACW,qBAAqB;IA+CnC;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAiH5B;;;OAGG;YACW,iBAAiB;IA8D/B;;OAEG;IACH,OAAO,CAAC,kBAAkB;IA4C1B;;;OAGG;IACG,YAAY,CAAC,SAAS,UAAO,GAAG,OAAO,CAAC,OAAO,CAAC;IAqFtD;;;OAGG;YACW,iBAAiB;IAwD/B;;OAEG;YACW,sBAAsB;IAsCpC;;;OAGG;YACW,iBAAiB;IAoB/B;;;OAGG;YACW,oBAAoB;IAkClC;;OAEG;YACW,qBAAqB;IA4BnC;;OAEG;YACW,gBAAgB;IAoB9B;;;OAGG;YACW,qBAAqB;IAoEnC;;;OAGG;YACW,eAAe;IAyH7B;;;OAGG;YACW,uBAAuB;IAyErC;;OAEG;YACW,4BAA4B;IAiE1C;;OAEG;YACW,oBAAoB;IAgElC;;;;OAIG;YACW,yBAAyB;IAgIvC;;;OAGG;YACW,qBAAqB;IAmHnC;;OAEG;IACH,OAAO,CAAC,sBAAsB;IAW9B;;OAEG;YACW,mBAAmB;IA0BjC;;OAEG;YACW,qBAAqB;IAiDnC;;OAEG;IACH,OAAO,CAAC,cAAc;IAatB,OAAO,CAAC,UAAU;IAQlB;;;;;;OAMG;IACH,OAAO,CAAC,qBAAqB;IA2J7B,OAAO,CAAC,iBAAiB,CAAM;IAC/B,OAAO,CAAC,gBAAgB,CAAK;IAE7B;;;OAGG;YACW,YAAY;IAqT1B;;;OAGG;IACH,OAAO,CAAC,gBAAgB;IA2CxB;;OAEG;YACW,aAAa;IAsmB3B;;;OAGG;YACW,iBAAiB;IAgI/B;;OAEG;YACW,cAAc;IAwU5B,OAAO,CAAC,YAAY,CAAgB;IACpC,OAAO,CAAC,YAAY,CAAM;IAC1B,OAAO,CAAC,aAAa,CAAS;IAE9B;;OAEG;YACW,gBAAgB;IAkB9B;;OAEG;YACW,gBAAgB;IAe9B;;;OAGG;YACW,WAAW;IAsEzB;;;OAGG;YACW,mBAAmB;IA8BjC;;;OAGG;YACW,cAAc;IAkB5B;;;;OAIG;YACW,6BAA6B;IAqI3C;;;OAGG;YACW,aAAa;IA+B3B;;;;OAIG;YACW,sBAAsB;IAwPpC;;;;OAIG;YACW,sBAAsB;YA0GtB,UAAU;IA4HxB,OAAO,CAAC,YAAY;IAmFpB;;;OAGG;YACW,eAAe;IAqD7B;;OAEG;YACW,cAAc;IAsO5B;;OAEG;YACW,oBAAoB;IAwBlC;;OAEG;YACW,eAAe;IAU7B;;OAEG;YACW,wBAAwB;YAsBxB,gBAAgB;IA4B9B,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAsD;IAEpF;;OAEG;IACH,OAAO,CAAC,mBAAmB;IAoB3B;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAO1B;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAS1B;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAKzB;;OAEG;IACH,OAAO,CAAC,oBAAoB;IAqB5B;;OAEG;IACH,OAAO,CAAC,uBAAuB;IAQ/B;;OAEG;IACH,OAAO,CAAC,eAAe;IASvB;;OAEG;IACH,OAAO,CAAC,sBAAsB;YAoEhB,SAAS;YAiIT,sBAAsB;CAkcrC"}
|
package/dist/app.js
CHANGED
|
@@ -13,7 +13,7 @@ import { VERSION } from './version.js';
|
|
|
13
13
|
import { buildHeaderWithLogo } from './logo.js';
|
|
14
14
|
import { addImageFromFile, addImageFromUrl, addImageFromPaste, getImages, deleteImage, looksLikeImagePath, looksLikeImageUrl, formatImageSize, sanitizeImageId, generatePasteImageId, extractImageUrls, processDescriptionImageUrls, } from './imageManager.js';
|
|
15
15
|
import { InputBox, getDisplayWidth, charIndexAtDisplayColumn } from './inputBox.js';
|
|
16
|
-
import { startCaffeinate, stopCaffeinate, isCaffeinateAvailable } from './caffeinate.js';
|
|
16
|
+
import { startCaffeinate, stopCaffeinate, isCaffeinateAvailable, isCaffeinateRunning } from './caffeinate.js';
|
|
17
17
|
import { getPreventSleep, setPreventSleep, getConfigPath, detectTerminal, getShiftEnterEnabled, setShiftEnterEnabled, configureTerminalShiftEnter, removeTerminalShiftEnter, getNewlineHint, getCmdVPasteEnabled, setCmdVPasteEnabled, configureTerminalCmdVPaste, removeTerminalCmdVPaste, getCmdVPasteInstructions, } from './config.js';
|
|
18
18
|
import { hasClipboardImage, saveClipboardImage, cleanupTempImage } from './clipboard.js';
|
|
19
19
|
import { isGitRepo, ensureEnvInGitignore } from './git.js';
|
|
@@ -220,6 +220,13 @@ export class MaistroApp {
|
|
|
220
220
|
* Start the interactive application
|
|
221
221
|
*/
|
|
222
222
|
async start() {
|
|
223
|
+
// If this is a restart after update, wait for terminal to settle
|
|
224
|
+
// This prevents EIO errors when the parent process just exited
|
|
225
|
+
if (process.env.MAISTRO_IS_RESTART) {
|
|
226
|
+
await new Promise(resolve => setTimeout(resolve, 300));
|
|
227
|
+
// Clear the flag so nested restarts don't accumulate delays
|
|
228
|
+
delete process.env.MAISTRO_IS_RESTART;
|
|
229
|
+
}
|
|
223
230
|
this.ui.init();
|
|
224
231
|
// Simple startup check (no header yet - showPlanView will show full UI)
|
|
225
232
|
console.log(`\n\x1b[90mStarting maistro v${VERSION}\x1b[0m`);
|
|
@@ -371,24 +378,33 @@ export class MaistroApp {
|
|
|
371
378
|
restartAfterUpdate() {
|
|
372
379
|
console.log(`\x1b[32m✓ Updated successfully!\x1b[0m`);
|
|
373
380
|
console.log(`\x1b[90mRestarting maistro...\x1b[0m\n`);
|
|
381
|
+
// Ensure terminal is in a clean state before spawning
|
|
382
|
+
this.ui.disableRawMode();
|
|
383
|
+
// Close the readline interface to release stdin
|
|
384
|
+
this.ui.close();
|
|
374
385
|
// Get the original command arguments (skip node and script path)
|
|
375
386
|
const args = process.argv.slice(2);
|
|
376
|
-
// Spawn new maistro process
|
|
377
|
-
//
|
|
387
|
+
// Spawn new maistro process in detached mode so it becomes the session leader
|
|
388
|
+
// This ensures proper terminal control handoff
|
|
378
389
|
const child = spawn(process.argv[0], [process.argv[1], ...args], {
|
|
379
390
|
stdio: 'inherit',
|
|
380
|
-
detached:
|
|
391
|
+
detached: true,
|
|
381
392
|
env: {
|
|
382
393
|
...process.env,
|
|
383
394
|
// Skip update check on restart to avoid infinite loop
|
|
384
395
|
MAISTRO_SKIP_UPDATE_CHECK: '1',
|
|
396
|
+
// Signal that this is a restart so child can wait for terminal to settle
|
|
397
|
+
MAISTRO_IS_RESTART: '1',
|
|
385
398
|
},
|
|
386
399
|
});
|
|
387
|
-
//
|
|
388
|
-
child.
|
|
400
|
+
// Unreference the child so parent can exit independently
|
|
401
|
+
child.unref();
|
|
402
|
+
// Exit immediately - the detached child will take over
|
|
403
|
+
// Using setImmediate to ensure the spawn completes first
|
|
404
|
+
setImmediate(() => {
|
|
389
405
|
process.exit(0);
|
|
390
406
|
});
|
|
391
|
-
// If spawn fails
|
|
407
|
+
// If spawn fails before we exit, show error
|
|
392
408
|
child.on('error', (err) => {
|
|
393
409
|
console.error(`\x1b[31m✗ Failed to restart: ${err.message}\x1b[0m`);
|
|
394
410
|
console.log(`\x1b[90mPlease restart maistro manually.\x1b[0m\n`);
|
|
@@ -1308,7 +1324,7 @@ export class MaistroApp {
|
|
|
1308
1324
|
if (this.executionState?.isExecuting) {
|
|
1309
1325
|
this.renderExecutionSection(termWidth);
|
|
1310
1326
|
}
|
|
1311
|
-
// Show progress bar with total duration
|
|
1327
|
+
// Show progress bar with total duration and caffeinate indicator
|
|
1312
1328
|
// Calculate total duration: completed tasks + current task elapsed (if executing)
|
|
1313
1329
|
const completedDurationMs = tasks.reduce((sum, t) => sum + (t.durationMs || 0), 0);
|
|
1314
1330
|
const currentTaskElapsedMs = this.executionState?.isExecuting
|
|
@@ -1316,7 +1332,8 @@ export class MaistroApp {
|
|
|
1316
1332
|
: 0;
|
|
1317
1333
|
const totalDurationMs = completedDurationMs + currentTaskElapsedMs;
|
|
1318
1334
|
const durationStr = totalDurationMs > 0 ? ` • ${this.formatDuration(totalDurationMs)}` : '';
|
|
1319
|
-
|
|
1335
|
+
const mainCaffeinateIndicator = isCaffeinateRunning() ? ' \x1b[33m☕ caffeinating\x1b[0m' : '';
|
|
1336
|
+
console.log(`${progressBar(status.tasks.completed, status.tasks.total, 30, status.tasks.skipped)}${durationStr}${mainCaffeinateIndicator}\n`);
|
|
1320
1337
|
// Calculate viewport for tasks
|
|
1321
1338
|
const termRows = process.stdout.rows || 24;
|
|
1322
1339
|
// Fixed overhead: header(~7) + progress(2) + position indicator(1) + failed section(variable) + commands(2) + input(4 base) + margins
|
|
@@ -1356,7 +1373,7 @@ export class MaistroApp {
|
|
|
1356
1373
|
}
|
|
1357
1374
|
// Get visible slice of tasks
|
|
1358
1375
|
const visibleTasks = tasks.slice(this.taskScrollOffset, this.taskScrollOffset + maxVisibleTasks);
|
|
1359
|
-
this.displayTasks(visibleTasks, this.taskScrollOffset, selectedIndex, tasks);
|
|
1376
|
+
this.displayTasks(visibleTasks, this.taskScrollOffset, selectedIndex, tasks, this.executionState?.phase);
|
|
1360
1377
|
// Show scroll down indicator
|
|
1361
1378
|
const belowCount = totalTasks - this.taskScrollOffset - visibleTasks.length;
|
|
1362
1379
|
if (belowCount > 0) {
|
|
@@ -1485,12 +1502,31 @@ export class MaistroApp {
|
|
|
1485
1502
|
const availableRows = Math.max(6, termRows - fixedOverhead);
|
|
1486
1503
|
const maxVisibleTasks = Math.min(UI.MAX_VISIBLE_TASKS, Math.max(UI.MIN_VISIBLE_TASKS, Math.floor(availableRows / UI.LINES_PER_TASK)));
|
|
1487
1504
|
// Auto-scroll to first relevant task if no manual selection yet
|
|
1488
|
-
// Priority:
|
|
1505
|
+
// Priority: last completed task (to show summary) > first pending task > last task
|
|
1489
1506
|
const executingTaskIndex = tasks.findIndex(t => t.status === 'in_progress');
|
|
1490
1507
|
const firstPendingIndex = tasks.findIndex(t => t.status === 'pending');
|
|
1491
1508
|
if (this.selectedTaskIndex === -1) {
|
|
1492
|
-
if (executingTaskIndex
|
|
1493
|
-
|
|
1509
|
+
if (executingTaskIndex > 0) {
|
|
1510
|
+
// When a task is executing, select the last completed task before it
|
|
1511
|
+
// This allows users to see the achievement summary
|
|
1512
|
+
let lastCompletedIndex = -1;
|
|
1513
|
+
for (let i = executingTaskIndex - 1; i >= 0; i--) {
|
|
1514
|
+
if (tasks[i].status === 'completed') {
|
|
1515
|
+
lastCompletedIndex = i;
|
|
1516
|
+
break;
|
|
1517
|
+
}
|
|
1518
|
+
}
|
|
1519
|
+
if (lastCompletedIndex >= 0) {
|
|
1520
|
+
this.selectedTaskIndex = lastCompletedIndex;
|
|
1521
|
+
}
|
|
1522
|
+
else {
|
|
1523
|
+
// No completed task before executing one, select executing task
|
|
1524
|
+
this.selectedTaskIndex = executingTaskIndex;
|
|
1525
|
+
}
|
|
1526
|
+
}
|
|
1527
|
+
else if (executingTaskIndex === 0) {
|
|
1528
|
+
// First task is executing, select it
|
|
1529
|
+
this.selectedTaskIndex = 0;
|
|
1494
1530
|
}
|
|
1495
1531
|
else if (firstPendingIndex >= 0) {
|
|
1496
1532
|
// Default to first pending task - shows what's next to be done
|
|
@@ -2010,6 +2046,16 @@ export class MaistroApp {
|
|
|
2010
2046
|
}, 50);
|
|
2011
2047
|
return;
|
|
2012
2048
|
}
|
|
2049
|
+
// Partial CSI sequence (ESC [) - wait for final character(s)
|
|
2050
|
+
// This handles cases where escape sequences arrive character-by-character (e.g., in PTY tests)
|
|
2051
|
+
if (key === '\x1b[' || (key.startsWith('\x1b[') && key.length < 4 && !/[A-Za-z~]$/.test(key))) {
|
|
2052
|
+
escapeBuffer = key;
|
|
2053
|
+
escapeTimeout = setTimeout(() => {
|
|
2054
|
+
// Timeout - discard partial sequence
|
|
2055
|
+
escapeBuffer = '';
|
|
2056
|
+
}, 50);
|
|
2057
|
+
return;
|
|
2058
|
+
}
|
|
2013
2059
|
// Clear escape pending states on any other input
|
|
2014
2060
|
if ((escPendingClear || escPendingPause) && key !== '\x1b') {
|
|
2015
2061
|
if (escTimeout) {
|
|
@@ -3156,6 +3202,30 @@ export class MaistroApp {
|
|
|
3156
3202
|
if (!hasOutput && result.response) {
|
|
3157
3203
|
console.log(`${result.response}\n`);
|
|
3158
3204
|
}
|
|
3205
|
+
// Check for structured questions first (from new JSON format)
|
|
3206
|
+
if (result.questions && result.questions.length > 0) {
|
|
3207
|
+
// Use the existing discovery question UI to present questions
|
|
3208
|
+
const answers = {};
|
|
3209
|
+
const totalQuestions = result.questions.length;
|
|
3210
|
+
for (let i = 0; i < result.questions.length; i++) {
|
|
3211
|
+
const question = result.questions[i];
|
|
3212
|
+
const answer = await this.askDiscoveryQuestionRequired(question, i + 1, totalQuestions, '' // No summary needed for plan refinement
|
|
3213
|
+
);
|
|
3214
|
+
if (answer === null) {
|
|
3215
|
+
// User cancelled - return to main view
|
|
3216
|
+
return null;
|
|
3217
|
+
}
|
|
3218
|
+
answers[question.id] = answer;
|
|
3219
|
+
}
|
|
3220
|
+
// Format answers as full text and continue conversation
|
|
3221
|
+
const answerText = Object.entries(answers)
|
|
3222
|
+
.map(([id, value]) => {
|
|
3223
|
+
const q = result.questions.find(q => q.id === id);
|
|
3224
|
+
return `${q?.question || id}: ${value}`;
|
|
3225
|
+
})
|
|
3226
|
+
.join('\n');
|
|
3227
|
+
return this.handleInlineDiscussion(answerText, currentTasks, goal);
|
|
3228
|
+
}
|
|
3159
3229
|
// Return updated tasks if any (or null if no changes)
|
|
3160
3230
|
if (result.updatedTasks && result.updatedTasks.length > 0) {
|
|
3161
3231
|
// Process any image URLs in the updated task descriptions
|
|
@@ -3172,8 +3242,28 @@ export class MaistroApp {
|
|
|
3172
3242
|
await new Promise(r => setTimeout(r, 500));
|
|
3173
3243
|
return tasksWithImages;
|
|
3174
3244
|
}
|
|
3175
|
-
// No task changes -
|
|
3176
|
-
|
|
3245
|
+
// No task changes and no structured questions - allow text input for follow-up
|
|
3246
|
+
if (hasOutput && result.response) {
|
|
3247
|
+
// Fall back to text input
|
|
3248
|
+
console.log(''); // Add spacing
|
|
3249
|
+
const followUp = await this.ui.promptWithEscape('\x1b[90mReply to continue discussion (Enter to go back):\x1b[0m ', '');
|
|
3250
|
+
if (followUp && followUp.trim()) {
|
|
3251
|
+
// User wants to continue the conversation - recursively call with follow-up
|
|
3252
|
+
return this.handleInlineDiscussion(followUp.trim(), currentTasks, goal);
|
|
3253
|
+
}
|
|
3254
|
+
// Empty input or Escape - return to main view
|
|
3255
|
+
}
|
|
3256
|
+
else if (hasOutput) {
|
|
3257
|
+
// No result.response available - simple text input fallback
|
|
3258
|
+
console.log(''); // Add spacing
|
|
3259
|
+
const followUp = await this.ui.promptWithEscape('\x1b[90mReply to continue discussion (Enter to go back):\x1b[0m ', '');
|
|
3260
|
+
if (followUp && followUp.trim()) {
|
|
3261
|
+
return this.handleInlineDiscussion(followUp.trim(), currentTasks, goal);
|
|
3262
|
+
}
|
|
3263
|
+
}
|
|
3264
|
+
else {
|
|
3265
|
+
await new Promise(r => setTimeout(r, 300));
|
|
3266
|
+
}
|
|
3177
3267
|
return null;
|
|
3178
3268
|
}
|
|
3179
3269
|
/**
|
|
@@ -3377,7 +3467,7 @@ ${detection.projectContext}`;
|
|
|
3377
3467
|
getLogger()?.error('handleInit error', { error: String(err), stack: err instanceof Error ? err.stack : undefined });
|
|
3378
3468
|
}
|
|
3379
3469
|
}
|
|
3380
|
-
displayTasks(tasks, startIndex = 0, selectedIndex = -1, allTasks) {
|
|
3470
|
+
displayTasks(tasks, startIndex = 0, selectedIndex = -1, allTasks, executionPhase) {
|
|
3381
3471
|
const termWidth = process.stdout.columns || 80;
|
|
3382
3472
|
// Helper to get visible length (strip ANSI codes)
|
|
3383
3473
|
const visibleLength = (s) => s.replace(/\x1b\[[0-9;]*m/g, '').length;
|
|
@@ -3389,15 +3479,27 @@ ${detection.projectContext}`;
|
|
|
3389
3479
|
? formatBlockedStatus()
|
|
3390
3480
|
: formatTaskStatusIcon(task.status, task.failureType);
|
|
3391
3481
|
// Format status name in brackets
|
|
3482
|
+
// For in_progress tasks, show the current phase (Implementation/Validation/Committing)
|
|
3483
|
+
// Default to 'executing' (Implementation) if no phase is provided but task is in_progress
|
|
3484
|
+
const phaseForStatus = task.status === 'in_progress' ? (executionPhase ?? 'executing') : undefined;
|
|
3392
3485
|
const statusBracket = blocked
|
|
3393
3486
|
? ' \x1b[31m[Blocked]\x1b[0m'
|
|
3394
|
-
: ` [${formatStatusName(task.status, task.failureType)}]`;
|
|
3487
|
+
: ` [${formatStatusName(task.status, task.failureType, phaseForStatus)}]`;
|
|
3488
|
+
// Format task duration for completed and in_progress tasks
|
|
3489
|
+
let durationStr = '';
|
|
3490
|
+
if (task.status === 'completed' && task.durationMs) {
|
|
3491
|
+
durationStr = ` \x1b[90m${this.formatDuration(task.durationMs)}\x1b[0m`;
|
|
3492
|
+
}
|
|
3493
|
+
else if (task.status === 'in_progress' && this.executionState?.taskStartTime) {
|
|
3494
|
+
const elapsed = Date.now() - this.executionState.taskStartTime;
|
|
3495
|
+
durationStr = ` \x1b[36m${this.formatDuration(elapsed)}\x1b[0m`;
|
|
3496
|
+
}
|
|
3395
3497
|
const taskNum = startIndex + i + 1;
|
|
3396
3498
|
const isSelected = (startIndex + i) === selectedIndex;
|
|
3397
3499
|
const cursor = isSelected ? '\x1b[36m▶\x1b[0m ' : ' ';
|
|
3398
3500
|
// Build line template and measure total visible length
|
|
3399
3501
|
const prefix = `${cursor}${statusIcon} ${taskNum}. `;
|
|
3400
|
-
const suffix = statusBracket;
|
|
3502
|
+
const suffix = statusBracket + durationStr;
|
|
3401
3503
|
const prefixLen = visibleLength(prefix);
|
|
3402
3504
|
const suffixLen = visibleLength(suffix);
|
|
3403
3505
|
// Calculate max title length (leave 1 char margin for safety)
|
|
@@ -3410,10 +3512,10 @@ ${detection.projectContext}`;
|
|
|
3410
3512
|
}
|
|
3411
3513
|
// Output the line
|
|
3412
3514
|
if (isSelected) {
|
|
3413
|
-
console.log(`${cursor}${statusIcon} \x1b[1;36m${taskNum}.\x1b[0m \x1b[1;36m${title}\x1b[0m${statusBracket}`);
|
|
3515
|
+
console.log(`${cursor}${statusIcon} \x1b[1;36m${taskNum}.\x1b[0m \x1b[1;36m${title}\x1b[0m${statusBracket}${durationStr}`);
|
|
3414
3516
|
}
|
|
3415
3517
|
else {
|
|
3416
|
-
console.log(`${cursor}${statusIcon} \x1b[1m${taskNum}.\x1b[0m ${title}${statusBracket}`);
|
|
3518
|
+
console.log(`${cursor}${statusIcon} \x1b[1m${taskNum}.\x1b[0m ${title}${statusBracket}${durationStr}`);
|
|
3417
3519
|
}
|
|
3418
3520
|
// Show 5-line result summary preview for selected completed tasks
|
|
3419
3521
|
if (isSelected && task.status === 'completed' && task.resultSummary) {
|
|
@@ -3938,7 +4040,7 @@ ${detection.projectContext}`;
|
|
|
3938
4040
|
validating: '\x1b[33mValidating\x1b[0m',
|
|
3939
4041
|
committing: '\x1b[32mCommitting\x1b[0m',
|
|
3940
4042
|
}[this.executionState.phase];
|
|
3941
|
-
console.log(`${taskBar}
|
|
4043
|
+
console.log(`${taskBar} 📋 \x1b[1mTask ${this.executionState.currentTaskIndex + 1}\x1b[0m ${phaseLabel} \x1b[90m(${taskElapsed})\x1b[0m`);
|
|
3942
4044
|
}
|
|
3943
4045
|
}
|
|
3944
4046
|
// ==================== END EXECUTION STATE MANAGEMENT ====================
|
|
@@ -4130,11 +4232,31 @@ ${detection.projectContext}`;
|
|
|
4130
4232
|
return;
|
|
4131
4233
|
const failed = tasks.filter(t => t.status === 'failed');
|
|
4132
4234
|
const allDone = tasks.every(t => t.status === 'completed' || t.status === 'skipped');
|
|
4133
|
-
// Update selected task to
|
|
4235
|
+
// Update selected task to last completed task (only if user hasn't navigated)
|
|
4236
|
+
// This shows the achievement summary for the most recently completed task
|
|
4237
|
+
// while the next task is executing
|
|
4134
4238
|
if (!userNavigated) {
|
|
4135
|
-
const
|
|
4136
|
-
if (
|
|
4137
|
-
|
|
4239
|
+
const executingTaskIndex = tasks.findIndex(t => t.status === 'in_progress');
|
|
4240
|
+
if (executingTaskIndex > 0) {
|
|
4241
|
+
// Find the last completed task before the executing one
|
|
4242
|
+
let lastCompletedIndex = -1;
|
|
4243
|
+
for (let i = executingTaskIndex - 1; i >= 0; i--) {
|
|
4244
|
+
if (tasks[i].status === 'completed') {
|
|
4245
|
+
lastCompletedIndex = i;
|
|
4246
|
+
break;
|
|
4247
|
+
}
|
|
4248
|
+
}
|
|
4249
|
+
if (lastCompletedIndex >= 0) {
|
|
4250
|
+
this.selectedTaskIndex = lastCompletedIndex;
|
|
4251
|
+
}
|
|
4252
|
+
else {
|
|
4253
|
+
// No completed task before executing one, select the executing task
|
|
4254
|
+
this.selectedTaskIndex = executingTaskIndex;
|
|
4255
|
+
}
|
|
4256
|
+
}
|
|
4257
|
+
else if (executingTaskIndex === 0) {
|
|
4258
|
+
// First task is executing, select it
|
|
4259
|
+
this.selectedTaskIndex = 0;
|
|
4138
4260
|
}
|
|
4139
4261
|
}
|
|
4140
4262
|
// Clear and render
|