deepclause-sdk 0.0.74 → 0.0.75
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/cli/run.d.ts +3 -0
- package/dist/cli/run.d.ts.map +1 -1
- package/dist/cli/run.js +1 -0
- package/dist/cli/run.js.map +1 -1
- package/dist/cli/search.d.ts +4 -11
- package/dist/cli/search.d.ts.map +1 -1
- package/dist/cli/search.js +100 -42
- package/dist/cli/search.js.map +1 -1
- package/dist/cli/tools.js +2 -2
- package/dist/cli/tools.js.map +1 -1
- package/dist/cli/tui.d.ts +6 -0
- package/dist/cli/tui.d.ts.map +1 -1
- package/dist/cli/tui.js +565 -29
- package/dist/cli/tui.js.map +1 -1
- package/dist/system/runtime/conductor.d.ts +4 -0
- package/dist/system/runtime/conductor.d.ts.map +1 -1
- package/dist/system/runtime/conductor.js +2 -1
- package/dist/system/runtime/conductor.js.map +1 -1
- package/dist/system/runtime/dml-executor.d.ts +3 -0
- package/dist/system/runtime/dml-executor.d.ts.map +1 -1
- package/dist/system/runtime/dml-executor.js +2 -0
- package/dist/system/runtime/dml-executor.js.map +1 -1
- package/dist/system/runtime/runtime-tools.d.ts +3 -0
- package/dist/system/runtime/runtime-tools.d.ts.map +1 -1
- package/dist/system/runtime/runtime-tools.js +12 -5
- package/dist/system/runtime/runtime-tools.js.map +1 -1
- package/dist/tools.js +2 -2
- package/dist/tools.js.map +1 -1
- package/package.json +1 -1
package/dist/cli/tui.js
CHANGED
|
@@ -7,10 +7,11 @@ import { listCommands } from './commands.js';
|
|
|
7
7
|
import { getConfigPath, loadConfig, resolveModelSlot, setModel } from './config.js';
|
|
8
8
|
import { run } from './run.js';
|
|
9
9
|
import { formatToolArgs } from './tool-args.js';
|
|
10
|
-
import { appendConductorSessionMessages, createSessionExecutionLogWriter, createLocalSkill, createConductorSession, getConductorSessionDetail, listConductorSessions, runConductorTurn, } from '../system/runtime/conductor.js';
|
|
10
|
+
import { appendConductorSessionMessages, createSessionExecutionLogWriter, createLocalSkill, createConductorSession, getConductorSessionDetail, listConductorSessions, mergeSessionUsage, runConductorTurn, } from '../system/runtime/conductor.js';
|
|
11
11
|
import { getSystemAssetSourcePaths } from '../system/assets/index.js';
|
|
12
12
|
import { createShellManager, describeShellExecutionBackend, } from '../system/runtime/shell-manager.js';
|
|
13
13
|
import { buildToolCompletionEvent, buildToolFailureEvent, buildToolStartEvent, createShellToolEventBridge, } from '../system/runtime/shell-tool-events.js';
|
|
14
|
+
import { recordTokenUsage } from '../system/runtime/token-usage.js';
|
|
14
15
|
const IGNORED_LIVE_LOG_TOOLS = new Set(['set_result', 'update_memory']);
|
|
15
16
|
const BUILTIN_SLASH_COMMANDS = ['new', 'sessions', 'help', 'run', 'compile', 'skill-creator', 'set-model', 'cancel', 'exit', 'quit'];
|
|
16
17
|
const MODEL_SLOTS = ['gateway', 'run', 'compile'];
|
|
@@ -305,6 +306,67 @@ export function releaseTuiInputStream(stream) {
|
|
|
305
306
|
}
|
|
306
307
|
stream.pause();
|
|
307
308
|
}
|
|
309
|
+
class MouseParser {
|
|
310
|
+
buffer = '';
|
|
311
|
+
feed(data, onEvent) {
|
|
312
|
+
this.buffer += data.toString('utf8');
|
|
313
|
+
while (this.buffer.length > 0) {
|
|
314
|
+
const mouseStart = this.buffer.indexOf('\u001b[<');
|
|
315
|
+
if (mouseStart === -1) {
|
|
316
|
+
if (this.buffer.length > 0) {
|
|
317
|
+
this.buffer = '';
|
|
318
|
+
}
|
|
319
|
+
break;
|
|
320
|
+
}
|
|
321
|
+
this.buffer = this.buffer.slice(mouseStart);
|
|
322
|
+
const parsed = this.tryParseMouse();
|
|
323
|
+
if (parsed === null) {
|
|
324
|
+
if (this.buffer.length > 256) {
|
|
325
|
+
this.buffer = '';
|
|
326
|
+
}
|
|
327
|
+
break;
|
|
328
|
+
}
|
|
329
|
+
onEvent(parsed);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
tryParseMouse() {
|
|
333
|
+
const mIdx = this.buffer.indexOf('M', 3);
|
|
334
|
+
const mIdxSgr = this.buffer.indexOf('m', 3);
|
|
335
|
+
const end = mIdx !== -1 && (mIdxSgr === -1 || mIdx < mIdxSgr) ? mIdx : mIdxSgr;
|
|
336
|
+
if (end === -1)
|
|
337
|
+
return null;
|
|
338
|
+
const payload = this.buffer.slice(3, end);
|
|
339
|
+
const isRelease = this.buffer[end] === 'm';
|
|
340
|
+
this.buffer = this.buffer.slice(end + 1);
|
|
341
|
+
const semicolon = payload.indexOf(';');
|
|
342
|
+
if (semicolon === -1)
|
|
343
|
+
return null;
|
|
344
|
+
const secondSemicolon = payload.indexOf(';', semicolon + 1);
|
|
345
|
+
if (secondSemicolon === -1)
|
|
346
|
+
return null;
|
|
347
|
+
const cb = Number.parseInt(payload.slice(0, semicolon), 10);
|
|
348
|
+
const col = Number.parseInt(payload.slice(semicolon + 1, secondSemicolon), 10);
|
|
349
|
+
const row = Number.parseInt(payload.slice(secondSemicolon + 1), 10);
|
|
350
|
+
if (Number.isNaN(cb) || Number.isNaN(col) || Number.isNaN(row))
|
|
351
|
+
return null;
|
|
352
|
+
const shift = (cb & 4) !== 0;
|
|
353
|
+
const meta = (cb & 8) !== 0;
|
|
354
|
+
const ctrl = (cb & 16) !== 0;
|
|
355
|
+
const buttonCode = cb & 3;
|
|
356
|
+
if (isRelease) {
|
|
357
|
+
return { button: 'release', row, column: col, shift, ctrl, meta };
|
|
358
|
+
}
|
|
359
|
+
if ((cb & 64) !== 0) {
|
|
360
|
+
return { button: buttonCode === 0 ? 'wheel-up' : 'wheel-down', row, column: col, shift, ctrl, meta };
|
|
361
|
+
}
|
|
362
|
+
const isMotion = (cb & 32) !== 0;
|
|
363
|
+
if (isMotion) {
|
|
364
|
+
return { button: 'motion', row, column: col, shift, ctrl, meta };
|
|
365
|
+
}
|
|
366
|
+
const buttonMap = { 0: 'left', 1: 'middle', 2: 'right' };
|
|
367
|
+
return { button: buttonMap[buttonCode] ?? 'left', row, column: col, shift, ctrl, meta };
|
|
368
|
+
}
|
|
369
|
+
}
|
|
308
370
|
class FullscreenTui {
|
|
309
371
|
workspaceRoot;
|
|
310
372
|
options;
|
|
@@ -337,6 +399,13 @@ class FullscreenTui {
|
|
|
337
399
|
};
|
|
338
400
|
currentPreview = null;
|
|
339
401
|
currentAbortController = null;
|
|
402
|
+
toolAbortController = null;
|
|
403
|
+
toolAbortSignalRef = {};
|
|
404
|
+
mouseParser = null;
|
|
405
|
+
mouseDataHandler = null;
|
|
406
|
+
mouseEscapeState = 'idle';
|
|
407
|
+
selection = null;
|
|
408
|
+
isDraggingSelection = false;
|
|
340
409
|
inputValue = '';
|
|
341
410
|
cursor = 0;
|
|
342
411
|
busy = false;
|
|
@@ -356,6 +425,8 @@ class FullscreenTui {
|
|
|
356
425
|
lastRenderAt = 0;
|
|
357
426
|
previousFrameLines = null;
|
|
358
427
|
previousFrameSize = null;
|
|
428
|
+
resizeHandler = null;
|
|
429
|
+
wrapCache = new Map();
|
|
359
430
|
constructor(workspaceRoot, options = {}) {
|
|
360
431
|
this.workspaceRoot = workspaceRoot;
|
|
361
432
|
this.options = options;
|
|
@@ -366,12 +437,22 @@ class FullscreenTui {
|
|
|
366
437
|
await this.refreshCommands();
|
|
367
438
|
await this.refreshSessions({ createIfMissing: true });
|
|
368
439
|
this.statusLine = `Ready. Shell ${this.getShellBackend().backendLabel} in ${path.relative(this.workspaceRoot, this.shellWorkspacePath) || '.'}. F10 or Ctrl+G opens menus. Left/right changes pane when the menu is closed.`;
|
|
369
|
-
emitKeypressEvents(input);
|
|
370
440
|
if (input.isTTY) {
|
|
371
441
|
input.setRawMode(true);
|
|
372
442
|
}
|
|
443
|
+
emitKeypressEvents(input);
|
|
373
444
|
input.on('keypress', this.onKeypress);
|
|
374
|
-
|
|
445
|
+
this.mouseParser = new MouseParser();
|
|
446
|
+
this.mouseDataHandler = (chunk) => {
|
|
447
|
+
this.mouseParser.feed(chunk, (event) => this.handleMouseEvent(event));
|
|
448
|
+
};
|
|
449
|
+
input.prependListener('data', this.mouseDataHandler);
|
|
450
|
+
this.resizeHandler = () => {
|
|
451
|
+
this.wrapCache.clear();
|
|
452
|
+
this.requestRender();
|
|
453
|
+
};
|
|
454
|
+
output.on('resize', this.resizeHandler);
|
|
455
|
+
output.write('\u001b[?1049h\u001b[?1002h\u001b[?1006h\u001b[2J\u001b[H');
|
|
375
456
|
this.requestRender();
|
|
376
457
|
await new Promise((resolve) => {
|
|
377
458
|
this.closeResolver = resolve;
|
|
@@ -386,16 +467,315 @@ class FullscreenTui {
|
|
|
386
467
|
clearInterval(this.animationTimer);
|
|
387
468
|
this.animationTimer = null;
|
|
388
469
|
}
|
|
389
|
-
|
|
470
|
+
if (this.mouseDataHandler) {
|
|
471
|
+
input.removeListener('data', this.mouseDataHandler);
|
|
472
|
+
this.mouseDataHandler = null;
|
|
473
|
+
}
|
|
474
|
+
this.mouseParser = null;
|
|
475
|
+
input.removeListener('keypress', this.onKeypress);
|
|
476
|
+
if (this.resizeHandler) {
|
|
477
|
+
output.off('resize', this.resizeHandler);
|
|
478
|
+
this.resizeHandler = null;
|
|
479
|
+
}
|
|
390
480
|
releaseTuiInputStream(input);
|
|
391
481
|
this.previousFrameLines = null;
|
|
392
482
|
this.previousFrameSize = null;
|
|
393
|
-
output.write('\u001b[?25h\u001b[?1049l');
|
|
483
|
+
output.write('\u001b[?25h\u001b[?1006l\u001b[?1002l\u001b[?1049l');
|
|
394
484
|
}
|
|
395
485
|
}
|
|
396
486
|
onKeypress = (text, key) => {
|
|
487
|
+
if (key.sequence === '\x1b[<') {
|
|
488
|
+
this.mouseEscapeState = 'consuming';
|
|
489
|
+
return;
|
|
490
|
+
}
|
|
491
|
+
if (this.mouseEscapeState === 'consuming') {
|
|
492
|
+
if (text === 'M' || text === 'm') {
|
|
493
|
+
this.mouseEscapeState = 'idle';
|
|
494
|
+
}
|
|
495
|
+
return;
|
|
496
|
+
}
|
|
397
497
|
void this.handleKeypress(text, key);
|
|
398
498
|
};
|
|
499
|
+
handleMouseEvent(event) {
|
|
500
|
+
if (event.button === 'wheel-up' || event.button === 'wheel-down') {
|
|
501
|
+
if (event.shift)
|
|
502
|
+
return;
|
|
503
|
+
const direction = event.button === 'wheel-up' ? 'up' : 'down';
|
|
504
|
+
const paneKind = this.paneKindAtPosition(event.row, event.column);
|
|
505
|
+
if (paneKind) {
|
|
506
|
+
this.focusedPane = paneKind;
|
|
507
|
+
}
|
|
508
|
+
this.scrollFocusedPane(direction, 3);
|
|
509
|
+
return;
|
|
510
|
+
}
|
|
511
|
+
if (event.button === 'left' && !event.shift && !event.ctrl && !event.meta) {
|
|
512
|
+
const menuIndex = this.menuIndexAtColumn(event.column);
|
|
513
|
+
if (menuIndex !== null && event.row === 1) {
|
|
514
|
+
this.clearSelection();
|
|
515
|
+
if (this.uiMode !== 'menu') {
|
|
516
|
+
this.uiMode = 'menu';
|
|
517
|
+
}
|
|
518
|
+
this.menuState.activeIndex = menuIndex;
|
|
519
|
+
this.menuState.selectedIndex = 0;
|
|
520
|
+
this.menuState.typeahead = '';
|
|
521
|
+
this.requestRender();
|
|
522
|
+
return;
|
|
523
|
+
}
|
|
524
|
+
if (this.uiMode === 'menu' && event.row >= 2) {
|
|
525
|
+
const menuItemIndex = this.menuItemAtRow(event.row);
|
|
526
|
+
if (menuItemIndex !== null) {
|
|
527
|
+
this.menuState.selectedIndex = menuItemIndex;
|
|
528
|
+
this.requestRender();
|
|
529
|
+
void this.activateCurrentMenuItem();
|
|
530
|
+
return;
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
const paneKind = this.paneKindAtPosition(event.row, event.column);
|
|
534
|
+
if (!paneKind) {
|
|
535
|
+
this.clearSelection();
|
|
536
|
+
this.requestRender();
|
|
537
|
+
return;
|
|
538
|
+
}
|
|
539
|
+
const panePos = this.paneContentPosition(paneKind, event.row, event.column);
|
|
540
|
+
if (!panePos) {
|
|
541
|
+
this.clearSelection();
|
|
542
|
+
this.focusedPane = paneKind;
|
|
543
|
+
this.requestRender();
|
|
544
|
+
return;
|
|
545
|
+
}
|
|
546
|
+
this.selection = { pane: paneKind, startRow: panePos.bodyRow, startCol: panePos.innerCol, endRow: panePos.bodyRow, endCol: panePos.innerCol };
|
|
547
|
+
this.isDraggingSelection = true;
|
|
548
|
+
this.focusedPane = paneKind;
|
|
549
|
+
this.requestRender();
|
|
550
|
+
return;
|
|
551
|
+
}
|
|
552
|
+
if (event.button === 'motion' && !event.shift && this.isDraggingSelection && this.selection) {
|
|
553
|
+
const paneKind = this.paneKindAtPosition(event.row, event.column);
|
|
554
|
+
if (paneKind && paneKind === this.selection.pane) {
|
|
555
|
+
const panePos = this.paneContentPosition(paneKind, event.row, event.column);
|
|
556
|
+
if (panePos) {
|
|
557
|
+
this.selection.endRow = panePos.bodyRow;
|
|
558
|
+
this.selection.endCol = panePos.innerCol;
|
|
559
|
+
this.requestRender();
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
return;
|
|
563
|
+
}
|
|
564
|
+
if (event.button === 'release' && !event.shift && this.isDraggingSelection) {
|
|
565
|
+
this.isDraggingSelection = false;
|
|
566
|
+
if (this.selection) {
|
|
567
|
+
const text = this.extractSelectionText();
|
|
568
|
+
if (text) {
|
|
569
|
+
this.copyToClipboard(text);
|
|
570
|
+
this.statusLine = 'Copied selection to clipboard.';
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
this.clearSelection();
|
|
574
|
+
this.requestRender();
|
|
575
|
+
return;
|
|
576
|
+
}
|
|
577
|
+
if (event.button === 'release') {
|
|
578
|
+
this.isDraggingSelection = false;
|
|
579
|
+
return;
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
clearSelection() {
|
|
583
|
+
if (this.selection) {
|
|
584
|
+
this.selection = null;
|
|
585
|
+
}
|
|
586
|
+
this.isDraggingSelection = false;
|
|
587
|
+
}
|
|
588
|
+
paneContentPosition(paneKind, screenRow, screenCol) {
|
|
589
|
+
const columns = output.columns ?? 120;
|
|
590
|
+
const rows = output.rows ?? 40;
|
|
591
|
+
if (screenRow < 2 || screenRow >= rows - 1)
|
|
592
|
+
return null;
|
|
593
|
+
if (this.uiMode !== 'command')
|
|
594
|
+
return null;
|
|
595
|
+
const contentHeight = rows - 3;
|
|
596
|
+
const widths = computePaneWidths(columns);
|
|
597
|
+
const rightHeights = computeRightPaneHeights(contentHeight);
|
|
598
|
+
const contentRow = screenRow - 2;
|
|
599
|
+
let paneLeft;
|
|
600
|
+
let paneWidth;
|
|
601
|
+
let paneTop;
|
|
602
|
+
switch (paneKind) {
|
|
603
|
+
case 'sessions':
|
|
604
|
+
paneLeft = 0;
|
|
605
|
+
paneWidth = widths.left;
|
|
606
|
+
paneTop = 0;
|
|
607
|
+
break;
|
|
608
|
+
case 'messages':
|
|
609
|
+
paneLeft = widths.left + 1;
|
|
610
|
+
paneWidth = widths.center;
|
|
611
|
+
paneTop = 0;
|
|
612
|
+
break;
|
|
613
|
+
case 'process':
|
|
614
|
+
paneLeft = widths.left + 1 + widths.center + 1;
|
|
615
|
+
paneWidth = widths.right;
|
|
616
|
+
paneTop = 0;
|
|
617
|
+
break;
|
|
618
|
+
case 'context':
|
|
619
|
+
paneLeft = widths.left + 1 + widths.center + 1;
|
|
620
|
+
paneWidth = widths.right;
|
|
621
|
+
paneTop = rightHeights.process;
|
|
622
|
+
break;
|
|
623
|
+
}
|
|
624
|
+
if (screenCol < paneLeft + 2 || screenCol > paneLeft + paneWidth - 1)
|
|
625
|
+
return null;
|
|
626
|
+
const bodyRow = contentRow - paneTop - 1;
|
|
627
|
+
if (bodyRow < 0)
|
|
628
|
+
return null;
|
|
629
|
+
const innerCol = screenCol - paneLeft - 2;
|
|
630
|
+
return { bodyRow, innerCol };
|
|
631
|
+
}
|
|
632
|
+
extractSelectionText() {
|
|
633
|
+
if (!this.selection)
|
|
634
|
+
return '';
|
|
635
|
+
const sel = this.selection;
|
|
636
|
+
const paneBody = this.getPaneBody(sel.pane);
|
|
637
|
+
const columns = output.columns ?? 120;
|
|
638
|
+
const widths = computePaneWidths(columns);
|
|
639
|
+
const width = sel.pane === 'sessions' ? widths.left : sel.pane === 'messages' ? widths.center : widths.right;
|
|
640
|
+
const innerWidth = Math.max(1, width - 2);
|
|
641
|
+
const wrappedLines = wrapBodyLines(paneBody, innerWidth);
|
|
642
|
+
const startRow = Math.min(sel.startRow, sel.endRow);
|
|
643
|
+
const endRow = Math.max(sel.startRow, sel.endRow);
|
|
644
|
+
const startCol = sel.startRow <= sel.endRow ? sel.startCol : sel.endCol;
|
|
645
|
+
const endCol = sel.startRow <= sel.endRow ? sel.endCol : sel.startCol;
|
|
646
|
+
if (startRow === endRow) {
|
|
647
|
+
const line = wrappedLines[startRow] ?? '';
|
|
648
|
+
return this.stripAnsi(line.slice(startCol, endCol + 1));
|
|
649
|
+
}
|
|
650
|
+
const parts = [];
|
|
651
|
+
const firstLine = wrappedLines[startRow] ?? '';
|
|
652
|
+
parts.push(this.stripAnsi(firstLine.slice(startCol)));
|
|
653
|
+
for (let row = startRow + 1; row < endRow; row++) {
|
|
654
|
+
parts.push(this.stripAnsi(wrappedLines[row] ?? ''));
|
|
655
|
+
}
|
|
656
|
+
const lastLine = wrappedLines[endRow] ?? '';
|
|
657
|
+
parts.push(this.stripAnsi(lastLine.slice(0, endCol + 1)));
|
|
658
|
+
return parts.join('\n');
|
|
659
|
+
}
|
|
660
|
+
stripAnsi(text) {
|
|
661
|
+
return text.replace(/\u001b\[[0-9;]*m/g, '');
|
|
662
|
+
}
|
|
663
|
+
copyToClipboard(text) {
|
|
664
|
+
const osc52 = `\u001b]52;c;${Buffer.from(text).toString('base64')}\u0007`;
|
|
665
|
+
output.write(osc52);
|
|
666
|
+
}
|
|
667
|
+
applySelectionHighlight(lines, widths, rightHeights, _contentHeight) {
|
|
668
|
+
const sel = this.selection;
|
|
669
|
+
if (!sel)
|
|
670
|
+
return;
|
|
671
|
+
const startRow = Math.min(sel.startRow, sel.endRow);
|
|
672
|
+
const endRow = Math.max(sel.startRow, sel.endRow);
|
|
673
|
+
const startCol = sel.startRow <= sel.endRow ? sel.startCol : sel.endCol;
|
|
674
|
+
const endCol = sel.startRow <= sel.endRow ? sel.endCol : sel.startCol;
|
|
675
|
+
let paneLeft;
|
|
676
|
+
let paneWidth;
|
|
677
|
+
let paneTop;
|
|
678
|
+
switch (sel.pane) {
|
|
679
|
+
case 'sessions':
|
|
680
|
+
paneLeft = 0;
|
|
681
|
+
paneWidth = widths.left;
|
|
682
|
+
paneTop = 0;
|
|
683
|
+
break;
|
|
684
|
+
case 'messages':
|
|
685
|
+
paneLeft = widths.left + 1;
|
|
686
|
+
paneWidth = widths.center;
|
|
687
|
+
paneTop = 0;
|
|
688
|
+
break;
|
|
689
|
+
case 'process':
|
|
690
|
+
paneLeft = widths.left + 1 + widths.center + 1;
|
|
691
|
+
paneWidth = widths.right;
|
|
692
|
+
paneTop = 0;
|
|
693
|
+
break;
|
|
694
|
+
case 'context':
|
|
695
|
+
paneLeft = widths.left + 1 + widths.center + 1;
|
|
696
|
+
paneWidth = widths.right;
|
|
697
|
+
paneTop = rightHeights.process;
|
|
698
|
+
break;
|
|
699
|
+
}
|
|
700
|
+
const innerDisplayStart = paneLeft + 1;
|
|
701
|
+
const innerDisplayEnd = paneLeft + paneWidth - 2;
|
|
702
|
+
for (let bodyRow = startRow; bodyRow <= endRow; bodyRow++) {
|
|
703
|
+
const screenLineIdx = 1 + paneTop + 1 + bodyRow;
|
|
704
|
+
if (screenLineIdx >= lines.length)
|
|
705
|
+
break;
|
|
706
|
+
const line = lines[screenLineIdx];
|
|
707
|
+
const rowStartCol = bodyRow === startRow ? innerDisplayStart + startCol : innerDisplayStart;
|
|
708
|
+
const rowEndCol = bodyRow === endRow ? innerDisplayStart + endCol + 1 : innerDisplayEnd + 1;
|
|
709
|
+
const clampedStartCol = Math.max(innerDisplayStart, rowStartCol);
|
|
710
|
+
const clampedEndCol = Math.min(innerDisplayEnd + 1, rowEndCol);
|
|
711
|
+
if (clampedStartCol >= clampedEndCol)
|
|
712
|
+
continue;
|
|
713
|
+
const strStart = displayColumnToStringIndex(line, clampedStartCol);
|
|
714
|
+
const strEnd = displayColumnToStringIndex(line, clampedEndCol);
|
|
715
|
+
const before = line.slice(0, strStart);
|
|
716
|
+
const segment = line.slice(strStart, strEnd);
|
|
717
|
+
const after = line.slice(strEnd);
|
|
718
|
+
const highlighted = this.invertAnsiColors(segment);
|
|
719
|
+
lines[screenLineIdx] = `${before}${highlighted}${after}`;
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
invertAnsiColors(text) {
|
|
723
|
+
return `\u001b[7m${text}\u001b[27m`;
|
|
724
|
+
}
|
|
725
|
+
paneKindAtPosition(row, column) {
|
|
726
|
+
const columns = output.columns ?? 120;
|
|
727
|
+
const rows = output.rows ?? 40;
|
|
728
|
+
if (row < 2 || row >= rows - 1)
|
|
729
|
+
return null;
|
|
730
|
+
if (this.uiMode !== 'command')
|
|
731
|
+
return null;
|
|
732
|
+
const widths = computePaneWidths(columns);
|
|
733
|
+
const rightHeights = computeRightPaneHeights(rows - 3);
|
|
734
|
+
const contentRow = row - 1;
|
|
735
|
+
if (column <= widths.left)
|
|
736
|
+
return 'sessions';
|
|
737
|
+
if (column <= widths.left + 1 + widths.center)
|
|
738
|
+
return 'messages';
|
|
739
|
+
if (contentRow <= rightHeights.process)
|
|
740
|
+
return 'process';
|
|
741
|
+
return 'context';
|
|
742
|
+
}
|
|
743
|
+
menuIndexAtColumn(column) {
|
|
744
|
+
const menuLabels = ['Session', 'Skills', 'Files', 'Run', 'View', 'Help'];
|
|
745
|
+
let pos = 1 + measureDisplayWidth(' DeepClause ');
|
|
746
|
+
for (let i = 0; i < menuLabels.length; i++) {
|
|
747
|
+
const labelWidth = measureDisplayWidth(` [${i + 1}] ${menuLabels[i]} `);
|
|
748
|
+
if (column >= pos && column < pos + labelWidth)
|
|
749
|
+
return i;
|
|
750
|
+
pos += labelWidth;
|
|
751
|
+
}
|
|
752
|
+
return null;
|
|
753
|
+
}
|
|
754
|
+
menuItemAtRow(row) {
|
|
755
|
+
if (this.uiMode !== 'menu')
|
|
756
|
+
return null;
|
|
757
|
+
const contentRow = row - 2;
|
|
758
|
+
if (contentRow < 2)
|
|
759
|
+
return null;
|
|
760
|
+
const itemRow = contentRow - 2;
|
|
761
|
+
if (itemRow < 0)
|
|
762
|
+
return null;
|
|
763
|
+
const linesPerItem = 3;
|
|
764
|
+
const itemIndex = Math.floor(itemRow / linesPerItem);
|
|
765
|
+
const menu = this.getMenuDefinitions()[this.menuState.activeIndex];
|
|
766
|
+
if (!menu || itemIndex >= menu.items.length || itemIndex < 0)
|
|
767
|
+
return null;
|
|
768
|
+
return itemIndex;
|
|
769
|
+
}
|
|
770
|
+
async activateCurrentMenuItem() {
|
|
771
|
+
const menu = this.getMenuDefinitions()[this.menuState.activeIndex];
|
|
772
|
+
if (!menu)
|
|
773
|
+
return;
|
|
774
|
+
const item = menu.items[this.menuState.selectedIndex];
|
|
775
|
+
if (!item || item.enabled === false)
|
|
776
|
+
return;
|
|
777
|
+
await this.runMenuAction(item.id);
|
|
778
|
+
}
|
|
399
779
|
async handleKeypress(text, key) {
|
|
400
780
|
if ((key.ctrl && key.name === 'g') || key.name === 'f10') {
|
|
401
781
|
this.toggleMenuMode();
|
|
@@ -428,6 +808,19 @@ class FullscreenTui {
|
|
|
428
808
|
this.close();
|
|
429
809
|
return;
|
|
430
810
|
}
|
|
811
|
+
if (key.ctrl && key.name === 't' && this.busy) {
|
|
812
|
+
if (this.toolAbortController && !this.toolAbortController.signal.aborted) {
|
|
813
|
+
this.toolAbortController.abort('Aborted by user');
|
|
814
|
+
this.toolAbortController = null;
|
|
815
|
+
this.toolAbortSignalRef.signal = undefined;
|
|
816
|
+
this.statusLine = 'Tool execution cancelled.';
|
|
817
|
+
}
|
|
818
|
+
else {
|
|
819
|
+
this.statusLine = 'No active tool to cancel.';
|
|
820
|
+
}
|
|
821
|
+
this.requestRender();
|
|
822
|
+
return;
|
|
823
|
+
}
|
|
431
824
|
if (this.uiMode === 'menu') {
|
|
432
825
|
await this.handleMenuKeypress(text, key);
|
|
433
826
|
return;
|
|
@@ -460,7 +853,7 @@ class FullscreenTui {
|
|
|
460
853
|
return;
|
|
461
854
|
}
|
|
462
855
|
if (this.cursor > 0) {
|
|
463
|
-
this.cursor
|
|
856
|
+
this.cursor = this.graphemeLeftIndex(this.inputValue, this.cursor);
|
|
464
857
|
}
|
|
465
858
|
break;
|
|
466
859
|
case 'right':
|
|
@@ -469,7 +862,7 @@ class FullscreenTui {
|
|
|
469
862
|
return;
|
|
470
863
|
}
|
|
471
864
|
if (this.cursor < this.inputValue.length) {
|
|
472
|
-
this.cursor
|
|
865
|
+
this.cursor = this.graphemeRightIndex(this.inputValue, this.cursor);
|
|
473
866
|
}
|
|
474
867
|
break;
|
|
475
868
|
case 'home':
|
|
@@ -642,13 +1035,13 @@ class FullscreenTui {
|
|
|
642
1035
|
return;
|
|
643
1036
|
case 'left':
|
|
644
1037
|
if (this.cursor > 0) {
|
|
645
|
-
this.cursor
|
|
1038
|
+
this.cursor = this.graphemeLeftIndex(this.inputValue, this.cursor);
|
|
646
1039
|
this.requestRender();
|
|
647
1040
|
}
|
|
648
1041
|
return;
|
|
649
1042
|
case 'right':
|
|
650
1043
|
if (this.cursor < this.inputValue.length) {
|
|
651
|
-
this.cursor
|
|
1044
|
+
this.cursor = this.graphemeRightIndex(this.inputValue, this.cursor);
|
|
652
1045
|
this.requestRender();
|
|
653
1046
|
}
|
|
654
1047
|
return;
|
|
@@ -875,6 +1268,8 @@ class FullscreenTui {
|
|
|
875
1268
|
activity.pushLine(`task ${promptText}`);
|
|
876
1269
|
this.busy = true;
|
|
877
1270
|
this.currentAbortController = new AbortController();
|
|
1271
|
+
this.toolAbortController = new AbortController();
|
|
1272
|
+
this.toolAbortSignalRef.signal = this.toolAbortController.signal;
|
|
878
1273
|
this.startAnimation();
|
|
879
1274
|
this.statusLine = 'Running conductor...';
|
|
880
1275
|
this.requestRender();
|
|
@@ -886,8 +1281,10 @@ class FullscreenTui {
|
|
|
886
1281
|
headless: true,
|
|
887
1282
|
sandbox: this.options.sandbox,
|
|
888
1283
|
signal: this.currentAbortController.signal,
|
|
1284
|
+
toolAbortSignalRef: this.toolAbortSignalRef,
|
|
889
1285
|
onUserInput: (question) => this.requestClarification(question),
|
|
890
1286
|
onEvent: (event) => {
|
|
1287
|
+
this.manageToolAbortSignal(event.event);
|
|
891
1288
|
activity.handle(event);
|
|
892
1289
|
this.updatePreviewFromEvent(event);
|
|
893
1290
|
this.requestRender();
|
|
@@ -912,6 +1309,8 @@ class FullscreenTui {
|
|
|
912
1309
|
finally {
|
|
913
1310
|
this.busy = false;
|
|
914
1311
|
this.currentAbortController = null;
|
|
1312
|
+
this.toolAbortController = null;
|
|
1313
|
+
this.toolAbortSignalRef.signal = undefined;
|
|
915
1314
|
this.stopAnimation();
|
|
916
1315
|
if (this.exitRequested) {
|
|
917
1316
|
this.close();
|
|
@@ -933,6 +1332,8 @@ class FullscreenTui {
|
|
|
933
1332
|
activity.pushLine(`skill /${skillName}${args.length > 0 ? ` ${args.join(' ')}` : ''}`);
|
|
934
1333
|
this.busy = true;
|
|
935
1334
|
this.currentAbortController = new AbortController();
|
|
1335
|
+
this.toolAbortController = new AbortController();
|
|
1336
|
+
this.toolAbortSignalRef.signal = this.toolAbortController.signal;
|
|
936
1337
|
this.startAnimation();
|
|
937
1338
|
this.statusLine = `Running /${skillName}...`;
|
|
938
1339
|
this.requestRender();
|
|
@@ -941,14 +1342,17 @@ class FullscreenTui {
|
|
|
941
1342
|
sessionId: this.selectedSessionId || undefined,
|
|
942
1343
|
sandbox: this.options.sandbox,
|
|
943
1344
|
signal: this.currentAbortController.signal,
|
|
1345
|
+
toolAbortSignalRef: this.toolAbortSignalRef,
|
|
944
1346
|
onUserInput: (question) => this.requestClarification(question),
|
|
945
1347
|
onEvent: (event) => {
|
|
1348
|
+
this.manageToolAbortSignal(event.event);
|
|
946
1349
|
activity.handle(event);
|
|
947
1350
|
this.updatePreviewFromEvent(event);
|
|
948
1351
|
this.requestRender();
|
|
949
1352
|
},
|
|
950
1353
|
});
|
|
951
1354
|
activity.finish();
|
|
1355
|
+
await this.refreshSessions({ selectedSessionId: this.selectedSessionId });
|
|
952
1356
|
await this.refreshCommands();
|
|
953
1357
|
this.finishExecutionPreview({
|
|
954
1358
|
persist: true,
|
|
@@ -967,6 +1371,8 @@ class FullscreenTui {
|
|
|
967
1371
|
finally {
|
|
968
1372
|
this.busy = false;
|
|
969
1373
|
this.currentAbortController = null;
|
|
1374
|
+
this.toolAbortController = null;
|
|
1375
|
+
this.toolAbortSignalRef.signal = undefined;
|
|
970
1376
|
this.stopAnimation();
|
|
971
1377
|
if (this.exitRequested) {
|
|
972
1378
|
this.close();
|
|
@@ -995,6 +1401,8 @@ class FullscreenTui {
|
|
|
995
1401
|
activity.pushLine(`run ${commandText}`);
|
|
996
1402
|
this.busy = true;
|
|
997
1403
|
this.currentAbortController = new AbortController();
|
|
1404
|
+
this.toolAbortController = new AbortController();
|
|
1405
|
+
this.toolAbortSignalRef.signal = this.toolAbortController.signal;
|
|
998
1406
|
this.startAnimation();
|
|
999
1407
|
this.statusLine = `Running ${commandText}...`;
|
|
1000
1408
|
this.requestRender();
|
|
@@ -1003,14 +1411,17 @@ class FullscreenTui {
|
|
|
1003
1411
|
sessionId: this.selectedSessionId || undefined,
|
|
1004
1412
|
sandbox: this.options.sandbox,
|
|
1005
1413
|
signal: this.currentAbortController.signal,
|
|
1414
|
+
toolAbortSignalRef: this.toolAbortSignalRef,
|
|
1006
1415
|
onUserInput: (question) => this.requestClarification(question),
|
|
1007
1416
|
onEvent: (event) => {
|
|
1417
|
+
this.manageToolAbortSignal(event.event);
|
|
1008
1418
|
activity.handle(event);
|
|
1009
1419
|
this.updatePreviewFromEvent(event);
|
|
1010
1420
|
this.requestRender();
|
|
1011
1421
|
},
|
|
1012
1422
|
});
|
|
1013
1423
|
activity.finish();
|
|
1424
|
+
await this.refreshSessions({ selectedSessionId: this.selectedSessionId });
|
|
1014
1425
|
await this.refreshCommands();
|
|
1015
1426
|
this.finishExecutionPreview({
|
|
1016
1427
|
persist: true,
|
|
@@ -1029,6 +1440,8 @@ class FullscreenTui {
|
|
|
1029
1440
|
finally {
|
|
1030
1441
|
this.busy = false;
|
|
1031
1442
|
this.currentAbortController = null;
|
|
1443
|
+
this.toolAbortController = null;
|
|
1444
|
+
this.toolAbortSignalRef.signal = undefined;
|
|
1032
1445
|
this.stopAnimation();
|
|
1033
1446
|
if (this.exitRequested) {
|
|
1034
1447
|
this.close();
|
|
@@ -1056,6 +1469,8 @@ class FullscreenTui {
|
|
|
1056
1469
|
activity.pushLine(`skill ${commandText}`);
|
|
1057
1470
|
this.busy = true;
|
|
1058
1471
|
this.currentAbortController = new AbortController();
|
|
1472
|
+
this.toolAbortController = new AbortController();
|
|
1473
|
+
this.toolAbortSignalRef.signal = this.toolAbortController.signal;
|
|
1059
1474
|
this.startAnimation();
|
|
1060
1475
|
this.statusLine = `Running /${commandName}...`;
|
|
1061
1476
|
this.requestRender();
|
|
@@ -1065,12 +1480,14 @@ class FullscreenTui {
|
|
|
1065
1480
|
signal: this.currentAbortController.signal,
|
|
1066
1481
|
onUserInput: (question) => this.requestClarification(question),
|
|
1067
1482
|
onEvent: (event) => {
|
|
1483
|
+
this.manageToolAbortSignal(event.event);
|
|
1068
1484
|
activity.handle(event);
|
|
1069
1485
|
this.updatePreviewFromEvent(event);
|
|
1070
1486
|
this.requestRender();
|
|
1071
1487
|
},
|
|
1072
1488
|
});
|
|
1073
1489
|
activity.finish();
|
|
1490
|
+
await this.refreshSessions({ selectedSessionId: this.selectedSessionId });
|
|
1074
1491
|
await this.refreshCommands();
|
|
1075
1492
|
this.finishExecutionPreview({
|
|
1076
1493
|
persist: true,
|
|
@@ -1088,6 +1505,8 @@ class FullscreenTui {
|
|
|
1088
1505
|
finally {
|
|
1089
1506
|
this.busy = false;
|
|
1090
1507
|
this.currentAbortController = null;
|
|
1508
|
+
this.toolAbortController = null;
|
|
1509
|
+
this.toolAbortSignalRef.signal = undefined;
|
|
1091
1510
|
this.stopAnimation();
|
|
1092
1511
|
if (this.exitRequested) {
|
|
1093
1512
|
this.close();
|
|
@@ -1119,6 +1538,8 @@ class FullscreenTui {
|
|
|
1119
1538
|
activity.pushLine(`shell ${persistOutput ? '!!' : '!'}${trimmedCommand}`);
|
|
1120
1539
|
this.busy = true;
|
|
1121
1540
|
this.currentAbortController = new AbortController();
|
|
1541
|
+
this.toolAbortController = this.currentAbortController;
|
|
1542
|
+
this.toolAbortSignalRef.signal = this.toolAbortController.signal;
|
|
1122
1543
|
this.startAnimation();
|
|
1123
1544
|
this.statusLine = `Running ${persistOutput ? '!!' : '!'}${trimmedCommand}...`;
|
|
1124
1545
|
this.requestRender();
|
|
@@ -1163,6 +1584,8 @@ class FullscreenTui {
|
|
|
1163
1584
|
finally {
|
|
1164
1585
|
this.busy = false;
|
|
1165
1586
|
this.currentAbortController = null;
|
|
1587
|
+
this.toolAbortController = null;
|
|
1588
|
+
this.toolAbortSignalRef.signal = undefined;
|
|
1166
1589
|
this.stopAnimation();
|
|
1167
1590
|
if (this.exitRequested) {
|
|
1168
1591
|
this.close();
|
|
@@ -2355,6 +2778,36 @@ class FullscreenTui {
|
|
|
2355
2778
|
currentProcessActivity() {
|
|
2356
2779
|
return this.activityFor(this.selectedSessionId || 'global');
|
|
2357
2780
|
}
|
|
2781
|
+
graphemeLeftIndex(text, index) {
|
|
2782
|
+
if (index <= 0)
|
|
2783
|
+
return 0;
|
|
2784
|
+
if (!graphemeSegmenter)
|
|
2785
|
+
return index - 1;
|
|
2786
|
+
const graphemes = Array.from(graphemeSegmenter.segment(text), (g) => g.segment);
|
|
2787
|
+
let pos = 0;
|
|
2788
|
+
for (const g of graphemes) {
|
|
2789
|
+
if (pos + g.length >= index)
|
|
2790
|
+
break;
|
|
2791
|
+
pos += g.length;
|
|
2792
|
+
}
|
|
2793
|
+
return pos;
|
|
2794
|
+
}
|
|
2795
|
+
graphemeRightIndex(text, index) {
|
|
2796
|
+
if (index >= text.length)
|
|
2797
|
+
return text.length;
|
|
2798
|
+
if (!graphemeSegmenter)
|
|
2799
|
+
return index + 1;
|
|
2800
|
+
const graphemes = Array.from(graphemeSegmenter.segment(text), (g) => g.segment);
|
|
2801
|
+
let pos = 0;
|
|
2802
|
+
for (const g of graphemes) {
|
|
2803
|
+
if (pos >= index) {
|
|
2804
|
+
pos += g.length;
|
|
2805
|
+
break;
|
|
2806
|
+
}
|
|
2807
|
+
pos += g.length;
|
|
2808
|
+
}
|
|
2809
|
+
return Math.min(pos, text.length);
|
|
2810
|
+
}
|
|
2358
2811
|
insertText(text) {
|
|
2359
2812
|
const previousInput = this.inputValue;
|
|
2360
2813
|
this.inputValue = `${this.inputValue.slice(0, this.cursor)}${text}${this.inputValue.slice(this.cursor)}`;
|
|
@@ -2366,8 +2819,9 @@ class FullscreenTui {
|
|
|
2366
2819
|
return;
|
|
2367
2820
|
}
|
|
2368
2821
|
const previousInput = this.inputValue;
|
|
2369
|
-
|
|
2370
|
-
this.
|
|
2822
|
+
const deleteFrom = this.graphemeLeftIndex(this.inputValue, this.cursor);
|
|
2823
|
+
this.inputValue = `${this.inputValue.slice(0, deleteFrom)}${this.inputValue.slice(this.cursor)}`;
|
|
2824
|
+
this.cursor = deleteFrom;
|
|
2371
2825
|
this.refreshCommandBarStatus(previousInput);
|
|
2372
2826
|
}
|
|
2373
2827
|
deleteForward() {
|
|
@@ -2509,23 +2963,28 @@ class FullscreenTui {
|
|
|
2509
2963
|
}
|
|
2510
2964
|
lines.push(status);
|
|
2511
2965
|
lines.push(inputLine);
|
|
2966
|
+
if (this.selection) {
|
|
2967
|
+
this.applySelectionHighlight(lines, widths, rightHeights, contentHeight);
|
|
2968
|
+
}
|
|
2512
2969
|
this.paintFrame(lines, { columns, rows });
|
|
2513
2970
|
if (workspaceView?.cursor && !this.pendingPrompt) {
|
|
2514
2971
|
output.write(`\u001b[${workspaceView.cursor.row};${workspaceView.cursor.column}H`);
|
|
2515
2972
|
return;
|
|
2516
2973
|
}
|
|
2517
|
-
const
|
|
2974
|
+
const textBeforeCursor = this.inputValue.slice(0, this.cursor);
|
|
2975
|
+
const cursorColumn = Math.min(columns, inputPrefix.length + measureDisplayWidth(textBeforeCursor) + 1);
|
|
2518
2976
|
output.write(`\u001b[${rows};${cursorColumn}H`);
|
|
2519
2977
|
}
|
|
2520
2978
|
paintFrame(lines, size) {
|
|
2521
|
-
const
|
|
2979
|
+
const paddedLines = lines.map((line) => padRight(line, size.columns));
|
|
2980
|
+
const patch = computeFramePatch(this.previousFrameLines, paddedLines, this.previousFrameSize, size);
|
|
2522
2981
|
let buffer = '\u001b[0m';
|
|
2523
2982
|
if (patch.fullRender) {
|
|
2524
|
-
buffer += `\u001b[H
|
|
2983
|
+
buffer += `\u001b[H${paddedLines.join('\n')}\u001b[?25h`;
|
|
2525
2984
|
}
|
|
2526
2985
|
else if (patch.changedRows.length > 0) {
|
|
2527
2986
|
for (const change of patch.changedRows) {
|
|
2528
|
-
buffer += `\u001b[${change.row};1H
|
|
2987
|
+
buffer += `\u001b[${change.row};1H${change.line}`;
|
|
2529
2988
|
}
|
|
2530
2989
|
buffer += '\u001b[?25h';
|
|
2531
2990
|
}
|
|
@@ -2533,7 +2992,7 @@ class FullscreenTui {
|
|
|
2533
2992
|
buffer += '\u001b[?25h';
|
|
2534
2993
|
}
|
|
2535
2994
|
output.write(buffer);
|
|
2536
|
-
this.previousFrameLines = [...
|
|
2995
|
+
this.previousFrameLines = [...paddedLines];
|
|
2537
2996
|
this.previousFrameSize = size;
|
|
2538
2997
|
}
|
|
2539
2998
|
buildHeaderLine() {
|
|
@@ -2547,32 +3006,35 @@ class FullscreenTui {
|
|
|
2547
3006
|
return `${sessionLabel} ${mode} ${skillLabel}`;
|
|
2548
3007
|
}
|
|
2549
3008
|
buildStatusLine() {
|
|
3009
|
+
const spinner = this.busy || this.currentPreview?.entries.some((entry) => entry.pending)
|
|
3010
|
+
? ` ${currentSpinnerFrame()}`
|
|
3011
|
+
: '';
|
|
2550
3012
|
if (this.uiMode === 'menu') {
|
|
2551
3013
|
const menu = this.getMenuDefinitions()[this.menuState.activeIndex];
|
|
2552
|
-
return
|
|
3014
|
+
return `${spinner} Menu ${menu?.label ?? ''} | ${menu?.items[this.menuState.selectedIndex]?.description ?? 'Choose an action.'}`;
|
|
2553
3015
|
}
|
|
2554
3016
|
if ((this.uiMode === 'picker' || this.uiMode === 'palette') && this.pickerState) {
|
|
2555
3017
|
const selected = this.pickerState.filteredItems[this.pickerState.selectedIndex];
|
|
2556
|
-
return `${this.pickerState.title} | ${selected?.description ?? this.pickerState.emptyText}`;
|
|
3018
|
+
return `${spinner} ${this.pickerState.title} | ${selected?.description ?? this.pickerState.emptyText}`;
|
|
2557
3019
|
}
|
|
2558
3020
|
if (this.uiMode === 'viewer' && this.viewerState) {
|
|
2559
|
-
return `${this.viewerState.title} | ${this.viewerState.path ? path.relative(this.workspaceRoot, this.viewerState.path) : 'read-only view'}`;
|
|
3021
|
+
return `${spinner} ${this.viewerState.title} | ${this.viewerState.path ? path.relative(this.workspaceRoot, this.viewerState.path) : 'read-only view'}`;
|
|
2560
3022
|
}
|
|
2561
3023
|
if (this.uiMode === 'editor' && this.editorState) {
|
|
2562
3024
|
const dirty = this.editorState.dirty ? 'modified' : 'saved';
|
|
2563
|
-
return `${this.editorState.title} | ${dirty} | Ln ${this.editorState.cursorLine + 1}, Col ${this.editorState.cursorColumn + 1}`;
|
|
3025
|
+
return `${spinner} ${this.editorState.title} | ${dirty} | Ln ${this.editorState.cursorLine + 1}, Col ${this.editorState.cursorColumn + 1}`;
|
|
2564
3026
|
}
|
|
2565
3027
|
const focus = paneDisplayName(this.focusedPane);
|
|
2566
3028
|
if (!this.pendingPrompt && this.focusedPane !== 'sessions') {
|
|
2567
3029
|
const follow = this.paneScroll[this.focusedPane] === 0 ? 'follow' : `scroll ${this.paneScroll[this.focusedPane]}`;
|
|
2568
|
-
return `${this.statusLine} | Focus ${focus} | ${follow}`;
|
|
3030
|
+
return `${spinner} ${this.statusLine} | Focus ${focus} | ${follow}`;
|
|
2569
3031
|
}
|
|
2570
3032
|
if (this.pendingPrompt) {
|
|
2571
3033
|
return this.pendingPrompt.kind === 'clarify'
|
|
2572
|
-
?
|
|
2573
|
-
:
|
|
3034
|
+
? `${spinner} Clarify: ${condenseWhitespace(this.pendingPrompt.prompt)} | Focus ${focus}`
|
|
3035
|
+
: `${spinner} Input: ${condenseWhitespace(this.pendingPrompt.prompt)} | Focus ${focus}`;
|
|
2574
3036
|
}
|
|
2575
|
-
return `${this.statusLine} | Focus ${focus}`;
|
|
3037
|
+
return `${spinner} ${this.statusLine} | Focus ${focus}`;
|
|
2576
3038
|
}
|
|
2577
3039
|
buildInputPrefix() {
|
|
2578
3040
|
if (this.uiMode === 'menu') {
|
|
@@ -2865,6 +3327,8 @@ class FullscreenTui {
|
|
|
2865
3327
|
['<- ->', 'Pane'],
|
|
2866
3328
|
[this.focusedPane === 'sessions' ? 'Up/Down' : 'PgUp/PgDn', this.focusedPane === 'sessions' ? 'Select' : 'Scroll'],
|
|
2867
3329
|
['End', this.focusedPane === 'sessions' ? 'Bottom' : 'Follow'],
|
|
3330
|
+
['Drag', 'Select'],
|
|
3331
|
+
...(this.busy ? [['Ctrl+T', 'Cancel']] : []),
|
|
2868
3332
|
['/cancel', 'Stop'],
|
|
2869
3333
|
];
|
|
2870
3334
|
}
|
|
@@ -2879,6 +3343,16 @@ class FullscreenTui {
|
|
|
2879
3343
|
? `${title}${this.focusedPane === kind ? ' [focus]' : ''} [follow]`
|
|
2880
3344
|
: `${title}${this.focusedPane === kind ? ' [focus]' : ''} [-${this.paneScroll[kind]}]`;
|
|
2881
3345
|
}
|
|
3346
|
+
cachedCollectTailWrappedLines(cacheKey, body, innerWidth, limit) {
|
|
3347
|
+
const cached = this.wrapCache.get(cacheKey);
|
|
3348
|
+
const bodyKey = `${body.length}:${body.length > 0 ? body[body.length - 1] : ''}:${limit}`;
|
|
3349
|
+
if (cached && cached.bodyRef === body && cached.bodyKey === bodyKey && cached.innerWidth === innerWidth) {
|
|
3350
|
+
return { lines: cached.result, truncated: body.length > 0 && cached.result.length < body.length };
|
|
3351
|
+
}
|
|
3352
|
+
const result = collectTailWrappedLines(body, innerWidth, limit);
|
|
3353
|
+
this.wrapCache.set(cacheKey, { bodyRef: body, bodyKey, innerWidth, result: result.lines });
|
|
3354
|
+
return result;
|
|
3355
|
+
}
|
|
2882
3356
|
getRenderPaneMetrics(kind, width = paneDimensionsForKind(kind, output.columns ?? 120, output.rows ?? 40).width, height = paneDimensionsForKind(kind, output.columns ?? 120, output.rows ?? 40).height) {
|
|
2883
3357
|
if ((kind === 'process' || kind === 'messages') && this.paneScroll[kind] === 0) {
|
|
2884
3358
|
const innerWidth = Math.max(1, width - 2);
|
|
@@ -2886,7 +3360,7 @@ class FullscreenTui {
|
|
|
2886
3360
|
const rawBody = kind === 'messages'
|
|
2887
3361
|
? this.buildMessagesPaneBody({ recentEntryLimit: Math.max(8, viewportLines * 2) })
|
|
2888
3362
|
: this.buildProcessPaneBody({ tailLineLimit: Math.max(16, viewportLines * 4) });
|
|
2889
|
-
const tail =
|
|
3363
|
+
const tail = this.cachedCollectTailWrappedLines(`${kind}:tail`, rawBody, innerWidth, viewportLines);
|
|
2890
3364
|
return {
|
|
2891
3365
|
lines: tail.lines,
|
|
2892
3366
|
start: 0,
|
|
@@ -2904,7 +3378,7 @@ class FullscreenTui {
|
|
|
2904
3378
|
getPaneMetrics(kind, width = paneDimensionsForKind(kind, output.columns ?? 120, output.rows ?? 40).width, height = paneDimensionsForKind(kind, output.columns ?? 120, output.rows ?? 40).height) {
|
|
2905
3379
|
const innerWidth = Math.max(1, width - 2);
|
|
2906
3380
|
const rawBody = this.getPaneBody(kind);
|
|
2907
|
-
const lines =
|
|
3381
|
+
const lines = this.cachedWrapBodyLines(kind, rawBody, innerWidth);
|
|
2908
3382
|
const viewportLines = Math.max(0, height - 2);
|
|
2909
3383
|
const maxStart = Math.max(0, lines.length - viewportLines);
|
|
2910
3384
|
if (kind === 'sessions') {
|
|
@@ -2926,6 +3400,16 @@ class FullscreenTui {
|
|
|
2926
3400
|
maxStartIsExact: true,
|
|
2927
3401
|
};
|
|
2928
3402
|
}
|
|
3403
|
+
cachedWrapBodyLines(cacheKey, body, innerWidth) {
|
|
3404
|
+
const cached = this.wrapCache.get(cacheKey);
|
|
3405
|
+
const bodyKey = `${body.length}:${body.length > 0 ? body[body.length - 1] : ''}`;
|
|
3406
|
+
if (cached && cached.bodyRef === body && cached.bodyKey === bodyKey && cached.innerWidth === innerWidth) {
|
|
3407
|
+
return cached.result;
|
|
3408
|
+
}
|
|
3409
|
+
const result = wrapBodyLines(body, innerWidth);
|
|
3410
|
+
this.wrapCache.set(cacheKey, { bodyRef: body, bodyKey, innerWidth, result });
|
|
3411
|
+
return result;
|
|
3412
|
+
}
|
|
2929
3413
|
getPaneBody(kind) {
|
|
2930
3414
|
switch (kind) {
|
|
2931
3415
|
case 'sessions':
|
|
@@ -2933,7 +3417,7 @@ class FullscreenTui {
|
|
|
2933
3417
|
case 'messages':
|
|
2934
3418
|
return this.buildMessagesPaneBody();
|
|
2935
3419
|
case 'process':
|
|
2936
|
-
return this.buildProcessPaneBody();
|
|
3420
|
+
return this.buildProcessPaneBody({ tailLineLimit: MAX_ACTIVITY_LINES });
|
|
2937
3421
|
case 'context':
|
|
2938
3422
|
return this.buildContextPaneBody();
|
|
2939
3423
|
}
|
|
@@ -2951,8 +3435,7 @@ class FullscreenTui {
|
|
|
2951
3435
|
}
|
|
2952
3436
|
}
|
|
2953
3437
|
formatMessageHeader(entry) {
|
|
2954
|
-
|
|
2955
|
-
return formatDisplayMessageHeader(entry, spinner);
|
|
3438
|
+
return formatDisplayMessageHeader(entry, '');
|
|
2956
3439
|
}
|
|
2957
3440
|
close() {
|
|
2958
3441
|
if (!this.closeResolver) {
|
|
@@ -2962,6 +3445,21 @@ class FullscreenTui {
|
|
|
2962
3445
|
this.closeResolver = null;
|
|
2963
3446
|
resolve();
|
|
2964
3447
|
}
|
|
3448
|
+
manageToolAbortSignal(event) {
|
|
3449
|
+
if (event.type !== 'tool_call' || !event.toolState) {
|
|
3450
|
+
return;
|
|
3451
|
+
}
|
|
3452
|
+
if (event.toolState === 'starting' || event.toolState === 'running') {
|
|
3453
|
+
if (!this.toolAbortController || this.toolAbortController.signal.aborted) {
|
|
3454
|
+
this.toolAbortController = new AbortController();
|
|
3455
|
+
}
|
|
3456
|
+
this.toolAbortSignalRef.signal = this.toolAbortController.signal;
|
|
3457
|
+
}
|
|
3458
|
+
else {
|
|
3459
|
+
this.toolAbortController = null;
|
|
3460
|
+
this.toolAbortSignalRef.signal = undefined;
|
|
3461
|
+
}
|
|
3462
|
+
}
|
|
2965
3463
|
isEditingInput() {
|
|
2966
3464
|
return this.uiMode !== 'editor' && this.inputValue.length > 0;
|
|
2967
3465
|
}
|
|
@@ -3254,6 +3752,7 @@ export async function runFileCommand(workspaceRoot, fileSpec, args, options = {}
|
|
|
3254
3752
|
}, args, options);
|
|
3255
3753
|
}
|
|
3256
3754
|
async function executeRunnableCommand(workspaceRoot, target, args, options = {}) {
|
|
3755
|
+
const usageByModel = {};
|
|
3257
3756
|
const executionLog = options.sessionId
|
|
3258
3757
|
? createSessionExecutionLogWriter({
|
|
3259
3758
|
workspaceRoot,
|
|
@@ -3265,6 +3764,9 @@ async function executeRunnableCommand(workspaceRoot, target, args, options = {})
|
|
|
3265
3764
|
})
|
|
3266
3765
|
: null;
|
|
3267
3766
|
const emitLogEvent = (event) => {
|
|
3767
|
+
if (event.event.type === 'usage') {
|
|
3768
|
+
recordTokenUsage(usageByModel, event.modelId, event.event.usage);
|
|
3769
|
+
}
|
|
3268
3770
|
executionLog?.recordEvent(event);
|
|
3269
3771
|
options.onEvent?.(event);
|
|
3270
3772
|
};
|
|
@@ -3275,12 +3777,14 @@ async function executeRunnableCommand(workspaceRoot, target, args, options = {})
|
|
|
3275
3777
|
stream: true,
|
|
3276
3778
|
sandbox: options.sandbox,
|
|
3277
3779
|
signal: options.signal,
|
|
3780
|
+
toolAbortSignalRef: options.toolAbortSignalRef,
|
|
3278
3781
|
onUserInput: options.onUserInput,
|
|
3279
3782
|
onEvent: (event) => emitLogEvent({ scope: 'child', childSlug: target.childSlug, event }),
|
|
3280
3783
|
onChildEvent: (childSlug, event) => emitLogEvent({ scope: 'child', childSlug, event }),
|
|
3281
3784
|
});
|
|
3282
3785
|
if (options.sessionId) {
|
|
3283
3786
|
await appendConductorSessionMessages(workspaceRoot, options.sessionId, buildPersistedRunnableTranscript(target.inputText, result));
|
|
3787
|
+
await mergeSessionUsage(workspaceRoot, options.sessionId, usageByModel);
|
|
3284
3788
|
}
|
|
3285
3789
|
await executionLog?.finish({
|
|
3286
3790
|
status: result.error ? 'error' : 'success',
|
|
@@ -3296,6 +3800,7 @@ async function executeRunnableCommand(workspaceRoot, target, args, options = {})
|
|
|
3296
3800
|
output: [],
|
|
3297
3801
|
error: error.message,
|
|
3298
3802
|
}));
|
|
3803
|
+
await mergeSessionUsage(workspaceRoot, options.sessionId, usageByModel);
|
|
3299
3804
|
}
|
|
3300
3805
|
await executionLog?.finish({
|
|
3301
3806
|
status: 'error',
|
|
@@ -4091,6 +4596,37 @@ function expandTabs(text, tabWidth) {
|
|
|
4091
4596
|
export function measureDisplayWidth(text) {
|
|
4092
4597
|
return splitGraphemes(text).reduce((total, grapheme) => total + graphemeDisplayWidth(grapheme), 0);
|
|
4093
4598
|
}
|
|
4599
|
+
function displayColumnToStringIndex(text, displayCol) {
|
|
4600
|
+
let col = 0;
|
|
4601
|
+
let i = 0;
|
|
4602
|
+
const len = text.length;
|
|
4603
|
+
while (i < len) {
|
|
4604
|
+
if (text.charCodeAt(i) === 0x1b && i + 1 < len && text[i + 1] === '[') {
|
|
4605
|
+
const end = text.indexOf('m', i + 2);
|
|
4606
|
+
if (end !== -1) {
|
|
4607
|
+
i = end + 1;
|
|
4608
|
+
continue;
|
|
4609
|
+
}
|
|
4610
|
+
}
|
|
4611
|
+
if (col >= displayCol)
|
|
4612
|
+
return i;
|
|
4613
|
+
const code = text.codePointAt(i);
|
|
4614
|
+
const charWidth = code >= 0x1100 && (code <= 0x115f || code === 0x2329 || code === 0x232a ||
|
|
4615
|
+
(code >= 0x2e80 && code <= 0xa4cf && code !== 0x303f) ||
|
|
4616
|
+
(code >= 0xac00 && code <= 0xd7a3) ||
|
|
4617
|
+
(code >= 0xf900 && code <= 0xfaff) ||
|
|
4618
|
+
(code >= 0xfe10 && code <= 0xfe19) ||
|
|
4619
|
+
(code >= 0xfe30 && code <= 0xfe6f) ||
|
|
4620
|
+
(code >= 0xff01 && code <= 0xff60) ||
|
|
4621
|
+
(code >= 0xffe0 && code <= 0xffe6) ||
|
|
4622
|
+
(code >= 0x1f300 && code <= 0x1f9ff) ||
|
|
4623
|
+
(code >= 0x20000 && code <= 0x2fffd) ||
|
|
4624
|
+
(code >= 0x30000 && code <= 0x3fffd)) ? 2 : 1;
|
|
4625
|
+
col += charWidth;
|
|
4626
|
+
i += code > 0xffff ? 2 : 1;
|
|
4627
|
+
}
|
|
4628
|
+
return i;
|
|
4629
|
+
}
|
|
4094
4630
|
export function padRight(text, width) {
|
|
4095
4631
|
const visibleWidth = measureDisplayWidth(text);
|
|
4096
4632
|
return visibleWidth >= width ? text : `${text}${' '.repeat(width - visibleWidth)}`;
|