deepclause-sdk 0.0.73 → 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 +610 -36
- package/dist/cli/tui.js.map +1 -1
- package/dist/compaction.d.ts +25 -2
- package/dist/compaction.d.ts.map +1 -1
- package/dist/compaction.js +167 -8
- package/dist/compaction.js.map +1 -1
- package/dist/prolog/bridge.d.ts +16 -0
- package/dist/prolog/bridge.d.ts.map +1 -1
- package/dist/prolog/bridge.js +12 -0
- package/dist/prolog/bridge.js.map +1 -1
- package/dist/prolog-src/deepclause_mi.pl +69 -5
- package/dist/runner.d.ts +10 -5
- package/dist/runner.d.ts.map +1 -1
- package/dist/runner.js +89 -23
- package/dist/runner.js.map +1 -1
- package/dist/system/assets/compactors/default-loop-compactor.dml +15 -5
- package/dist/system/assets/compactors/default-session-compactor.dml +15 -5
- 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 +15 -29
- package/dist/system/runtime/conductor.js.map +1 -1
- package/dist/system/runtime/dml-executor.d.ts +5 -5
- 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/dist/types.d.ts +3 -6
- package/dist/types.d.ts.map +1 -1
- package/package.json +2 -1
- package/src/prolog-src/deepclause_mi.pl +69 -5
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;
|
|
@@ -329,8 +391,21 @@ class FullscreenTui {
|
|
|
329
391
|
process: 0,
|
|
330
392
|
context: 0,
|
|
331
393
|
};
|
|
394
|
+
lastPaneMetrics = {
|
|
395
|
+
sessions: { maxStart: 0, viewportLines: 0, maxStartIsExact: true },
|
|
396
|
+
messages: { maxStart: 0, viewportLines: 0, maxStartIsExact: true },
|
|
397
|
+
process: { maxStart: 0, viewportLines: 0, maxStartIsExact: true },
|
|
398
|
+
context: { maxStart: 0, viewportLines: 0, maxStartIsExact: true },
|
|
399
|
+
};
|
|
332
400
|
currentPreview = null;
|
|
333
401
|
currentAbortController = null;
|
|
402
|
+
toolAbortController = null;
|
|
403
|
+
toolAbortSignalRef = {};
|
|
404
|
+
mouseParser = null;
|
|
405
|
+
mouseDataHandler = null;
|
|
406
|
+
mouseEscapeState = 'idle';
|
|
407
|
+
selection = null;
|
|
408
|
+
isDraggingSelection = false;
|
|
334
409
|
inputValue = '';
|
|
335
410
|
cursor = 0;
|
|
336
411
|
busy = false;
|
|
@@ -350,6 +425,8 @@ class FullscreenTui {
|
|
|
350
425
|
lastRenderAt = 0;
|
|
351
426
|
previousFrameLines = null;
|
|
352
427
|
previousFrameSize = null;
|
|
428
|
+
resizeHandler = null;
|
|
429
|
+
wrapCache = new Map();
|
|
353
430
|
constructor(workspaceRoot, options = {}) {
|
|
354
431
|
this.workspaceRoot = workspaceRoot;
|
|
355
432
|
this.options = options;
|
|
@@ -360,12 +437,22 @@ class FullscreenTui {
|
|
|
360
437
|
await this.refreshCommands();
|
|
361
438
|
await this.refreshSessions({ createIfMissing: true });
|
|
362
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.`;
|
|
363
|
-
emitKeypressEvents(input);
|
|
364
440
|
if (input.isTTY) {
|
|
365
441
|
input.setRawMode(true);
|
|
366
442
|
}
|
|
443
|
+
emitKeypressEvents(input);
|
|
367
444
|
input.on('keypress', this.onKeypress);
|
|
368
|
-
|
|
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');
|
|
369
456
|
this.requestRender();
|
|
370
457
|
await new Promise((resolve) => {
|
|
371
458
|
this.closeResolver = resolve;
|
|
@@ -380,16 +467,315 @@ class FullscreenTui {
|
|
|
380
467
|
clearInterval(this.animationTimer);
|
|
381
468
|
this.animationTimer = null;
|
|
382
469
|
}
|
|
383
|
-
|
|
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
|
+
}
|
|
384
480
|
releaseTuiInputStream(input);
|
|
385
481
|
this.previousFrameLines = null;
|
|
386
482
|
this.previousFrameSize = null;
|
|
387
|
-
output.write('\u001b[?25h\u001b[?1049l');
|
|
483
|
+
output.write('\u001b[?25h\u001b[?1006l\u001b[?1002l\u001b[?1049l');
|
|
388
484
|
}
|
|
389
485
|
}
|
|
390
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
|
+
}
|
|
391
497
|
void this.handleKeypress(text, key);
|
|
392
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
|
+
}
|
|
393
779
|
async handleKeypress(text, key) {
|
|
394
780
|
if ((key.ctrl && key.name === 'g') || key.name === 'f10') {
|
|
395
781
|
this.toggleMenuMode();
|
|
@@ -422,6 +808,19 @@ class FullscreenTui {
|
|
|
422
808
|
this.close();
|
|
423
809
|
return;
|
|
424
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
|
+
}
|
|
425
824
|
if (this.uiMode === 'menu') {
|
|
426
825
|
await this.handleMenuKeypress(text, key);
|
|
427
826
|
return;
|
|
@@ -454,7 +853,7 @@ class FullscreenTui {
|
|
|
454
853
|
return;
|
|
455
854
|
}
|
|
456
855
|
if (this.cursor > 0) {
|
|
457
|
-
this.cursor
|
|
856
|
+
this.cursor = this.graphemeLeftIndex(this.inputValue, this.cursor);
|
|
458
857
|
}
|
|
459
858
|
break;
|
|
460
859
|
case 'right':
|
|
@@ -463,7 +862,7 @@ class FullscreenTui {
|
|
|
463
862
|
return;
|
|
464
863
|
}
|
|
465
864
|
if (this.cursor < this.inputValue.length) {
|
|
466
|
-
this.cursor
|
|
865
|
+
this.cursor = this.graphemeRightIndex(this.inputValue, this.cursor);
|
|
467
866
|
}
|
|
468
867
|
break;
|
|
469
868
|
case 'home':
|
|
@@ -636,13 +1035,13 @@ class FullscreenTui {
|
|
|
636
1035
|
return;
|
|
637
1036
|
case 'left':
|
|
638
1037
|
if (this.cursor > 0) {
|
|
639
|
-
this.cursor
|
|
1038
|
+
this.cursor = this.graphemeLeftIndex(this.inputValue, this.cursor);
|
|
640
1039
|
this.requestRender();
|
|
641
1040
|
}
|
|
642
1041
|
return;
|
|
643
1042
|
case 'right':
|
|
644
1043
|
if (this.cursor < this.inputValue.length) {
|
|
645
|
-
this.cursor
|
|
1044
|
+
this.cursor = this.graphemeRightIndex(this.inputValue, this.cursor);
|
|
646
1045
|
this.requestRender();
|
|
647
1046
|
}
|
|
648
1047
|
return;
|
|
@@ -869,6 +1268,8 @@ class FullscreenTui {
|
|
|
869
1268
|
activity.pushLine(`task ${promptText}`);
|
|
870
1269
|
this.busy = true;
|
|
871
1270
|
this.currentAbortController = new AbortController();
|
|
1271
|
+
this.toolAbortController = new AbortController();
|
|
1272
|
+
this.toolAbortSignalRef.signal = this.toolAbortController.signal;
|
|
872
1273
|
this.startAnimation();
|
|
873
1274
|
this.statusLine = 'Running conductor...';
|
|
874
1275
|
this.requestRender();
|
|
@@ -880,8 +1281,10 @@ class FullscreenTui {
|
|
|
880
1281
|
headless: true,
|
|
881
1282
|
sandbox: this.options.sandbox,
|
|
882
1283
|
signal: this.currentAbortController.signal,
|
|
1284
|
+
toolAbortSignalRef: this.toolAbortSignalRef,
|
|
883
1285
|
onUserInput: (question) => this.requestClarification(question),
|
|
884
1286
|
onEvent: (event) => {
|
|
1287
|
+
this.manageToolAbortSignal(event.event);
|
|
885
1288
|
activity.handle(event);
|
|
886
1289
|
this.updatePreviewFromEvent(event);
|
|
887
1290
|
this.requestRender();
|
|
@@ -906,6 +1309,8 @@ class FullscreenTui {
|
|
|
906
1309
|
finally {
|
|
907
1310
|
this.busy = false;
|
|
908
1311
|
this.currentAbortController = null;
|
|
1312
|
+
this.toolAbortController = null;
|
|
1313
|
+
this.toolAbortSignalRef.signal = undefined;
|
|
909
1314
|
this.stopAnimation();
|
|
910
1315
|
if (this.exitRequested) {
|
|
911
1316
|
this.close();
|
|
@@ -927,6 +1332,8 @@ class FullscreenTui {
|
|
|
927
1332
|
activity.pushLine(`skill /${skillName}${args.length > 0 ? ` ${args.join(' ')}` : ''}`);
|
|
928
1333
|
this.busy = true;
|
|
929
1334
|
this.currentAbortController = new AbortController();
|
|
1335
|
+
this.toolAbortController = new AbortController();
|
|
1336
|
+
this.toolAbortSignalRef.signal = this.toolAbortController.signal;
|
|
930
1337
|
this.startAnimation();
|
|
931
1338
|
this.statusLine = `Running /${skillName}...`;
|
|
932
1339
|
this.requestRender();
|
|
@@ -935,14 +1342,17 @@ class FullscreenTui {
|
|
|
935
1342
|
sessionId: this.selectedSessionId || undefined,
|
|
936
1343
|
sandbox: this.options.sandbox,
|
|
937
1344
|
signal: this.currentAbortController.signal,
|
|
1345
|
+
toolAbortSignalRef: this.toolAbortSignalRef,
|
|
938
1346
|
onUserInput: (question) => this.requestClarification(question),
|
|
939
1347
|
onEvent: (event) => {
|
|
1348
|
+
this.manageToolAbortSignal(event.event);
|
|
940
1349
|
activity.handle(event);
|
|
941
1350
|
this.updatePreviewFromEvent(event);
|
|
942
1351
|
this.requestRender();
|
|
943
1352
|
},
|
|
944
1353
|
});
|
|
945
1354
|
activity.finish();
|
|
1355
|
+
await this.refreshSessions({ selectedSessionId: this.selectedSessionId });
|
|
946
1356
|
await this.refreshCommands();
|
|
947
1357
|
this.finishExecutionPreview({
|
|
948
1358
|
persist: true,
|
|
@@ -961,6 +1371,8 @@ class FullscreenTui {
|
|
|
961
1371
|
finally {
|
|
962
1372
|
this.busy = false;
|
|
963
1373
|
this.currentAbortController = null;
|
|
1374
|
+
this.toolAbortController = null;
|
|
1375
|
+
this.toolAbortSignalRef.signal = undefined;
|
|
964
1376
|
this.stopAnimation();
|
|
965
1377
|
if (this.exitRequested) {
|
|
966
1378
|
this.close();
|
|
@@ -989,6 +1401,8 @@ class FullscreenTui {
|
|
|
989
1401
|
activity.pushLine(`run ${commandText}`);
|
|
990
1402
|
this.busy = true;
|
|
991
1403
|
this.currentAbortController = new AbortController();
|
|
1404
|
+
this.toolAbortController = new AbortController();
|
|
1405
|
+
this.toolAbortSignalRef.signal = this.toolAbortController.signal;
|
|
992
1406
|
this.startAnimation();
|
|
993
1407
|
this.statusLine = `Running ${commandText}...`;
|
|
994
1408
|
this.requestRender();
|
|
@@ -997,14 +1411,17 @@ class FullscreenTui {
|
|
|
997
1411
|
sessionId: this.selectedSessionId || undefined,
|
|
998
1412
|
sandbox: this.options.sandbox,
|
|
999
1413
|
signal: this.currentAbortController.signal,
|
|
1414
|
+
toolAbortSignalRef: this.toolAbortSignalRef,
|
|
1000
1415
|
onUserInput: (question) => this.requestClarification(question),
|
|
1001
1416
|
onEvent: (event) => {
|
|
1417
|
+
this.manageToolAbortSignal(event.event);
|
|
1002
1418
|
activity.handle(event);
|
|
1003
1419
|
this.updatePreviewFromEvent(event);
|
|
1004
1420
|
this.requestRender();
|
|
1005
1421
|
},
|
|
1006
1422
|
});
|
|
1007
1423
|
activity.finish();
|
|
1424
|
+
await this.refreshSessions({ selectedSessionId: this.selectedSessionId });
|
|
1008
1425
|
await this.refreshCommands();
|
|
1009
1426
|
this.finishExecutionPreview({
|
|
1010
1427
|
persist: true,
|
|
@@ -1023,6 +1440,8 @@ class FullscreenTui {
|
|
|
1023
1440
|
finally {
|
|
1024
1441
|
this.busy = false;
|
|
1025
1442
|
this.currentAbortController = null;
|
|
1443
|
+
this.toolAbortController = null;
|
|
1444
|
+
this.toolAbortSignalRef.signal = undefined;
|
|
1026
1445
|
this.stopAnimation();
|
|
1027
1446
|
if (this.exitRequested) {
|
|
1028
1447
|
this.close();
|
|
@@ -1050,6 +1469,8 @@ class FullscreenTui {
|
|
|
1050
1469
|
activity.pushLine(`skill ${commandText}`);
|
|
1051
1470
|
this.busy = true;
|
|
1052
1471
|
this.currentAbortController = new AbortController();
|
|
1472
|
+
this.toolAbortController = new AbortController();
|
|
1473
|
+
this.toolAbortSignalRef.signal = this.toolAbortController.signal;
|
|
1053
1474
|
this.startAnimation();
|
|
1054
1475
|
this.statusLine = `Running /${commandName}...`;
|
|
1055
1476
|
this.requestRender();
|
|
@@ -1059,12 +1480,14 @@ class FullscreenTui {
|
|
|
1059
1480
|
signal: this.currentAbortController.signal,
|
|
1060
1481
|
onUserInput: (question) => this.requestClarification(question),
|
|
1061
1482
|
onEvent: (event) => {
|
|
1483
|
+
this.manageToolAbortSignal(event.event);
|
|
1062
1484
|
activity.handle(event);
|
|
1063
1485
|
this.updatePreviewFromEvent(event);
|
|
1064
1486
|
this.requestRender();
|
|
1065
1487
|
},
|
|
1066
1488
|
});
|
|
1067
1489
|
activity.finish();
|
|
1490
|
+
await this.refreshSessions({ selectedSessionId: this.selectedSessionId });
|
|
1068
1491
|
await this.refreshCommands();
|
|
1069
1492
|
this.finishExecutionPreview({
|
|
1070
1493
|
persist: true,
|
|
@@ -1082,6 +1505,8 @@ class FullscreenTui {
|
|
|
1082
1505
|
finally {
|
|
1083
1506
|
this.busy = false;
|
|
1084
1507
|
this.currentAbortController = null;
|
|
1508
|
+
this.toolAbortController = null;
|
|
1509
|
+
this.toolAbortSignalRef.signal = undefined;
|
|
1085
1510
|
this.stopAnimation();
|
|
1086
1511
|
if (this.exitRequested) {
|
|
1087
1512
|
this.close();
|
|
@@ -1113,6 +1538,8 @@ class FullscreenTui {
|
|
|
1113
1538
|
activity.pushLine(`shell ${persistOutput ? '!!' : '!'}${trimmedCommand}`);
|
|
1114
1539
|
this.busy = true;
|
|
1115
1540
|
this.currentAbortController = new AbortController();
|
|
1541
|
+
this.toolAbortController = this.currentAbortController;
|
|
1542
|
+
this.toolAbortSignalRef.signal = this.toolAbortController.signal;
|
|
1116
1543
|
this.startAnimation();
|
|
1117
1544
|
this.statusLine = `Running ${persistOutput ? '!!' : '!'}${trimmedCommand}...`;
|
|
1118
1545
|
this.requestRender();
|
|
@@ -1157,6 +1584,8 @@ class FullscreenTui {
|
|
|
1157
1584
|
finally {
|
|
1158
1585
|
this.busy = false;
|
|
1159
1586
|
this.currentAbortController = null;
|
|
1587
|
+
this.toolAbortController = null;
|
|
1588
|
+
this.toolAbortSignalRef.signal = undefined;
|
|
1160
1589
|
this.stopAnimation();
|
|
1161
1590
|
if (this.exitRequested) {
|
|
1162
1591
|
this.close();
|
|
@@ -2278,13 +2707,13 @@ class FullscreenTui {
|
|
|
2278
2707
|
this.animationTimer = null;
|
|
2279
2708
|
}
|
|
2280
2709
|
scrollFocusedPane(direction, amount) {
|
|
2281
|
-
const metrics = this.getPaneMetrics(this.focusedPane);
|
|
2282
|
-
if (metrics.maxStart === 0) {
|
|
2283
|
-
this.statusLine = `${paneDisplayName(this.focusedPane)} fits on screen.`;
|
|
2284
|
-
this.requestRender();
|
|
2285
|
-
return;
|
|
2286
|
-
}
|
|
2287
2710
|
if (this.focusedPane === 'sessions') {
|
|
2711
|
+
const metrics = this.getLastPaneMetrics(this.focusedPane) ?? this.getPaneMetrics(this.focusedPane);
|
|
2712
|
+
if (metrics.maxStart === 0) {
|
|
2713
|
+
this.statusLine = `${paneDisplayName(this.focusedPane)} fits on screen.`;
|
|
2714
|
+
this.requestRender();
|
|
2715
|
+
return;
|
|
2716
|
+
}
|
|
2288
2717
|
const delta = direction === 'down' ? amount : -amount;
|
|
2289
2718
|
this.paneScroll.sessions = clamp(this.paneScroll.sessions + delta, 0, metrics.maxStart);
|
|
2290
2719
|
this.statusLine = `Sessions scroll ${this.paneScroll.sessions + 1}/${metrics.maxStart + 1}.`;
|
|
@@ -2292,8 +2721,19 @@ class FullscreenTui {
|
|
|
2292
2721
|
return;
|
|
2293
2722
|
}
|
|
2294
2723
|
const pane = this.focusedPane;
|
|
2724
|
+
const metrics = this.getLastPaneMetrics(pane)
|
|
2725
|
+
?? (this.paneScroll[pane] === 0 ? this.getRenderPaneMetrics(pane) : this.getPaneMetrics(pane));
|
|
2726
|
+
if (metrics.maxStart === 0) {
|
|
2727
|
+
this.paneScroll[pane] = 0;
|
|
2728
|
+
this.statusLine = `${paneDisplayName(pane)} fits on screen.`;
|
|
2729
|
+
this.requestRender();
|
|
2730
|
+
return;
|
|
2731
|
+
}
|
|
2295
2732
|
const delta = direction === 'up' ? amount : -amount;
|
|
2296
|
-
|
|
2733
|
+
const nextScroll = this.paneScroll[pane] + delta;
|
|
2734
|
+
this.paneScroll[pane] = metrics.maxStartIsExact
|
|
2735
|
+
? clamp(nextScroll, 0, metrics.maxStart)
|
|
2736
|
+
: Math.max(0, nextScroll);
|
|
2297
2737
|
this.statusLine = this.paneScroll[pane] === 0
|
|
2298
2738
|
? `${paneDisplayName(pane)} following newest content.`
|
|
2299
2739
|
: `${paneDisplayName(pane)} scrolled ${this.paneScroll[pane]} line${this.paneScroll[pane] === 1 ? '' : 's'} above latest.`;
|
|
@@ -2338,6 +2778,36 @@ class FullscreenTui {
|
|
|
2338
2778
|
currentProcessActivity() {
|
|
2339
2779
|
return this.activityFor(this.selectedSessionId || 'global');
|
|
2340
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
|
+
}
|
|
2341
2811
|
insertText(text) {
|
|
2342
2812
|
const previousInput = this.inputValue;
|
|
2343
2813
|
this.inputValue = `${this.inputValue.slice(0, this.cursor)}${text}${this.inputValue.slice(this.cursor)}`;
|
|
@@ -2349,8 +2819,9 @@ class FullscreenTui {
|
|
|
2349
2819
|
return;
|
|
2350
2820
|
}
|
|
2351
2821
|
const previousInput = this.inputValue;
|
|
2352
|
-
|
|
2353
|
-
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;
|
|
2354
2825
|
this.refreshCommandBarStatus(previousInput);
|
|
2355
2826
|
}
|
|
2356
2827
|
deleteForward() {
|
|
@@ -2492,23 +2963,28 @@ class FullscreenTui {
|
|
|
2492
2963
|
}
|
|
2493
2964
|
lines.push(status);
|
|
2494
2965
|
lines.push(inputLine);
|
|
2966
|
+
if (this.selection) {
|
|
2967
|
+
this.applySelectionHighlight(lines, widths, rightHeights, contentHeight);
|
|
2968
|
+
}
|
|
2495
2969
|
this.paintFrame(lines, { columns, rows });
|
|
2496
2970
|
if (workspaceView?.cursor && !this.pendingPrompt) {
|
|
2497
2971
|
output.write(`\u001b[${workspaceView.cursor.row};${workspaceView.cursor.column}H`);
|
|
2498
2972
|
return;
|
|
2499
2973
|
}
|
|
2500
|
-
const
|
|
2974
|
+
const textBeforeCursor = this.inputValue.slice(0, this.cursor);
|
|
2975
|
+
const cursorColumn = Math.min(columns, inputPrefix.length + measureDisplayWidth(textBeforeCursor) + 1);
|
|
2501
2976
|
output.write(`\u001b[${rows};${cursorColumn}H`);
|
|
2502
2977
|
}
|
|
2503
2978
|
paintFrame(lines, size) {
|
|
2504
|
-
const
|
|
2979
|
+
const paddedLines = lines.map((line) => padRight(line, size.columns));
|
|
2980
|
+
const patch = computeFramePatch(this.previousFrameLines, paddedLines, this.previousFrameSize, size);
|
|
2505
2981
|
let buffer = '\u001b[0m';
|
|
2506
2982
|
if (patch.fullRender) {
|
|
2507
|
-
buffer += `\u001b[H
|
|
2983
|
+
buffer += `\u001b[H${paddedLines.join('\n')}\u001b[?25h`;
|
|
2508
2984
|
}
|
|
2509
2985
|
else if (patch.changedRows.length > 0) {
|
|
2510
2986
|
for (const change of patch.changedRows) {
|
|
2511
|
-
buffer += `\u001b[${change.row};1H
|
|
2987
|
+
buffer += `\u001b[${change.row};1H${change.line}`;
|
|
2512
2988
|
}
|
|
2513
2989
|
buffer += '\u001b[?25h';
|
|
2514
2990
|
}
|
|
@@ -2516,7 +2992,7 @@ class FullscreenTui {
|
|
|
2516
2992
|
buffer += '\u001b[?25h';
|
|
2517
2993
|
}
|
|
2518
2994
|
output.write(buffer);
|
|
2519
|
-
this.previousFrameLines = [...
|
|
2995
|
+
this.previousFrameLines = [...paddedLines];
|
|
2520
2996
|
this.previousFrameSize = size;
|
|
2521
2997
|
}
|
|
2522
2998
|
buildHeaderLine() {
|
|
@@ -2530,32 +3006,35 @@ class FullscreenTui {
|
|
|
2530
3006
|
return `${sessionLabel} ${mode} ${skillLabel}`;
|
|
2531
3007
|
}
|
|
2532
3008
|
buildStatusLine() {
|
|
3009
|
+
const spinner = this.busy || this.currentPreview?.entries.some((entry) => entry.pending)
|
|
3010
|
+
? ` ${currentSpinnerFrame()}`
|
|
3011
|
+
: '';
|
|
2533
3012
|
if (this.uiMode === 'menu') {
|
|
2534
3013
|
const menu = this.getMenuDefinitions()[this.menuState.activeIndex];
|
|
2535
|
-
return
|
|
3014
|
+
return `${spinner} Menu ${menu?.label ?? ''} | ${menu?.items[this.menuState.selectedIndex]?.description ?? 'Choose an action.'}`;
|
|
2536
3015
|
}
|
|
2537
3016
|
if ((this.uiMode === 'picker' || this.uiMode === 'palette') && this.pickerState) {
|
|
2538
3017
|
const selected = this.pickerState.filteredItems[this.pickerState.selectedIndex];
|
|
2539
|
-
return `${this.pickerState.title} | ${selected?.description ?? this.pickerState.emptyText}`;
|
|
3018
|
+
return `${spinner} ${this.pickerState.title} | ${selected?.description ?? this.pickerState.emptyText}`;
|
|
2540
3019
|
}
|
|
2541
3020
|
if (this.uiMode === 'viewer' && this.viewerState) {
|
|
2542
|
-
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'}`;
|
|
2543
3022
|
}
|
|
2544
3023
|
if (this.uiMode === 'editor' && this.editorState) {
|
|
2545
3024
|
const dirty = this.editorState.dirty ? 'modified' : 'saved';
|
|
2546
|
-
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}`;
|
|
2547
3026
|
}
|
|
2548
3027
|
const focus = paneDisplayName(this.focusedPane);
|
|
2549
3028
|
if (!this.pendingPrompt && this.focusedPane !== 'sessions') {
|
|
2550
3029
|
const follow = this.paneScroll[this.focusedPane] === 0 ? 'follow' : `scroll ${this.paneScroll[this.focusedPane]}`;
|
|
2551
|
-
return `${this.statusLine} | Focus ${focus} | ${follow}`;
|
|
3030
|
+
return `${spinner} ${this.statusLine} | Focus ${focus} | ${follow}`;
|
|
2552
3031
|
}
|
|
2553
3032
|
if (this.pendingPrompt) {
|
|
2554
3033
|
return this.pendingPrompt.kind === 'clarify'
|
|
2555
|
-
?
|
|
2556
|
-
:
|
|
3034
|
+
? `${spinner} Clarify: ${condenseWhitespace(this.pendingPrompt.prompt)} | Focus ${focus}`
|
|
3035
|
+
: `${spinner} Input: ${condenseWhitespace(this.pendingPrompt.prompt)} | Focus ${focus}`;
|
|
2557
3036
|
}
|
|
2558
|
-
return `${this.statusLine} | Focus ${focus}`;
|
|
3037
|
+
return `${spinner} ${this.statusLine} | Focus ${focus}`;
|
|
2559
3038
|
}
|
|
2560
3039
|
buildInputPrefix() {
|
|
2561
3040
|
if (this.uiMode === 'menu') {
|
|
@@ -2708,6 +3187,11 @@ class FullscreenTui {
|
|
|
2708
3187
|
}
|
|
2709
3188
|
buildPaneView(kind, title, width, height) {
|
|
2710
3189
|
const metrics = this.getRenderPaneMetrics(kind, width, height);
|
|
3190
|
+
this.lastPaneMetrics[kind] = {
|
|
3191
|
+
maxStart: metrics.maxStart,
|
|
3192
|
+
viewportLines: metrics.viewportLines,
|
|
3193
|
+
maxStartIsExact: metrics.maxStartIsExact,
|
|
3194
|
+
};
|
|
2711
3195
|
const paneTitle = this.buildPaneTitle(title, kind, metrics);
|
|
2712
3196
|
return buildPane(paneTitle, metrics.lines.slice(metrics.start, metrics.start + metrics.viewportLines), width, height, kind, this.focusedPane === kind);
|
|
2713
3197
|
}
|
|
@@ -2843,6 +3327,8 @@ class FullscreenTui {
|
|
|
2843
3327
|
['<- ->', 'Pane'],
|
|
2844
3328
|
[this.focusedPane === 'sessions' ? 'Up/Down' : 'PgUp/PgDn', this.focusedPane === 'sessions' ? 'Select' : 'Scroll'],
|
|
2845
3329
|
['End', this.focusedPane === 'sessions' ? 'Bottom' : 'Follow'],
|
|
3330
|
+
['Drag', 'Select'],
|
|
3331
|
+
...(this.busy ? [['Ctrl+T', 'Cancel']] : []),
|
|
2846
3332
|
['/cancel', 'Stop'],
|
|
2847
3333
|
];
|
|
2848
3334
|
}
|
|
@@ -2857,6 +3343,16 @@ class FullscreenTui {
|
|
|
2857
3343
|
? `${title}${this.focusedPane === kind ? ' [focus]' : ''} [follow]`
|
|
2858
3344
|
: `${title}${this.focusedPane === kind ? ' [focus]' : ''} [-${this.paneScroll[kind]}]`;
|
|
2859
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
|
+
}
|
|
2860
3356
|
getRenderPaneMetrics(kind, width = paneDimensionsForKind(kind, output.columns ?? 120, output.rows ?? 40).width, height = paneDimensionsForKind(kind, output.columns ?? 120, output.rows ?? 40).height) {
|
|
2861
3357
|
if ((kind === 'process' || kind === 'messages') && this.paneScroll[kind] === 0) {
|
|
2862
3358
|
const innerWidth = Math.max(1, width - 2);
|
|
@@ -2864,20 +3360,25 @@ class FullscreenTui {
|
|
|
2864
3360
|
const rawBody = kind === 'messages'
|
|
2865
3361
|
? this.buildMessagesPaneBody({ recentEntryLimit: Math.max(8, viewportLines * 2) })
|
|
2866
3362
|
: this.buildProcessPaneBody({ tailLineLimit: Math.max(16, viewportLines * 4) });
|
|
2867
|
-
const tail =
|
|
3363
|
+
const tail = this.cachedCollectTailWrappedLines(`${kind}:tail`, rawBody, innerWidth, viewportLines);
|
|
2868
3364
|
return {
|
|
2869
3365
|
lines: tail.lines,
|
|
2870
3366
|
start: 0,
|
|
2871
3367
|
maxStart: tail.truncated ? 1 : 0,
|
|
2872
3368
|
viewportLines,
|
|
3369
|
+
maxStartIsExact: !tail.truncated,
|
|
2873
3370
|
};
|
|
2874
3371
|
}
|
|
2875
3372
|
return this.getPaneMetrics(kind, width, height);
|
|
2876
3373
|
}
|
|
3374
|
+
getLastPaneMetrics(kind) {
|
|
3375
|
+
const metrics = this.lastPaneMetrics[kind];
|
|
3376
|
+
return metrics.viewportLines > 0 ? metrics : null;
|
|
3377
|
+
}
|
|
2877
3378
|
getPaneMetrics(kind, width = paneDimensionsForKind(kind, output.columns ?? 120, output.rows ?? 40).width, height = paneDimensionsForKind(kind, output.columns ?? 120, output.rows ?? 40).height) {
|
|
2878
3379
|
const innerWidth = Math.max(1, width - 2);
|
|
2879
3380
|
const rawBody = this.getPaneBody(kind);
|
|
2880
|
-
const lines =
|
|
3381
|
+
const lines = this.cachedWrapBodyLines(kind, rawBody, innerWidth);
|
|
2881
3382
|
const viewportLines = Math.max(0, height - 2);
|
|
2882
3383
|
const maxStart = Math.max(0, lines.length - viewportLines);
|
|
2883
3384
|
if (kind === 'sessions') {
|
|
@@ -2887,6 +3388,7 @@ class FullscreenTui {
|
|
|
2887
3388
|
start: this.paneScroll.sessions,
|
|
2888
3389
|
maxStart,
|
|
2889
3390
|
viewportLines,
|
|
3391
|
+
maxStartIsExact: true,
|
|
2890
3392
|
};
|
|
2891
3393
|
}
|
|
2892
3394
|
this.paneScroll[kind] = clamp(this.paneScroll[kind], 0, maxStart);
|
|
@@ -2895,8 +3397,19 @@ class FullscreenTui {
|
|
|
2895
3397
|
start: Math.max(0, maxStart - this.paneScroll[kind]),
|
|
2896
3398
|
maxStart,
|
|
2897
3399
|
viewportLines,
|
|
3400
|
+
maxStartIsExact: true,
|
|
2898
3401
|
};
|
|
2899
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
|
+
}
|
|
2900
3413
|
getPaneBody(kind) {
|
|
2901
3414
|
switch (kind) {
|
|
2902
3415
|
case 'sessions':
|
|
@@ -2904,7 +3417,7 @@ class FullscreenTui {
|
|
|
2904
3417
|
case 'messages':
|
|
2905
3418
|
return this.buildMessagesPaneBody();
|
|
2906
3419
|
case 'process':
|
|
2907
|
-
return this.buildProcessPaneBody();
|
|
3420
|
+
return this.buildProcessPaneBody({ tailLineLimit: MAX_ACTIVITY_LINES });
|
|
2908
3421
|
case 'context':
|
|
2909
3422
|
return this.buildContextPaneBody();
|
|
2910
3423
|
}
|
|
@@ -2922,8 +3435,7 @@ class FullscreenTui {
|
|
|
2922
3435
|
}
|
|
2923
3436
|
}
|
|
2924
3437
|
formatMessageHeader(entry) {
|
|
2925
|
-
|
|
2926
|
-
return formatDisplayMessageHeader(entry, spinner);
|
|
3438
|
+
return formatDisplayMessageHeader(entry, '');
|
|
2927
3439
|
}
|
|
2928
3440
|
close() {
|
|
2929
3441
|
if (!this.closeResolver) {
|
|
@@ -2933,6 +3445,21 @@ class FullscreenTui {
|
|
|
2933
3445
|
this.closeResolver = null;
|
|
2934
3446
|
resolve();
|
|
2935
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
|
+
}
|
|
2936
3463
|
isEditingInput() {
|
|
2937
3464
|
return this.uiMode !== 'editor' && this.inputValue.length > 0;
|
|
2938
3465
|
}
|
|
@@ -3225,6 +3752,7 @@ export async function runFileCommand(workspaceRoot, fileSpec, args, options = {}
|
|
|
3225
3752
|
}, args, options);
|
|
3226
3753
|
}
|
|
3227
3754
|
async function executeRunnableCommand(workspaceRoot, target, args, options = {}) {
|
|
3755
|
+
const usageByModel = {};
|
|
3228
3756
|
const executionLog = options.sessionId
|
|
3229
3757
|
? createSessionExecutionLogWriter({
|
|
3230
3758
|
workspaceRoot,
|
|
@@ -3236,6 +3764,9 @@ async function executeRunnableCommand(workspaceRoot, target, args, options = {})
|
|
|
3236
3764
|
})
|
|
3237
3765
|
: null;
|
|
3238
3766
|
const emitLogEvent = (event) => {
|
|
3767
|
+
if (event.event.type === 'usage') {
|
|
3768
|
+
recordTokenUsage(usageByModel, event.modelId, event.event.usage);
|
|
3769
|
+
}
|
|
3239
3770
|
executionLog?.recordEvent(event);
|
|
3240
3771
|
options.onEvent?.(event);
|
|
3241
3772
|
};
|
|
@@ -3246,12 +3777,14 @@ async function executeRunnableCommand(workspaceRoot, target, args, options = {})
|
|
|
3246
3777
|
stream: true,
|
|
3247
3778
|
sandbox: options.sandbox,
|
|
3248
3779
|
signal: options.signal,
|
|
3780
|
+
toolAbortSignalRef: options.toolAbortSignalRef,
|
|
3249
3781
|
onUserInput: options.onUserInput,
|
|
3250
3782
|
onEvent: (event) => emitLogEvent({ scope: 'child', childSlug: target.childSlug, event }),
|
|
3251
3783
|
onChildEvent: (childSlug, event) => emitLogEvent({ scope: 'child', childSlug, event }),
|
|
3252
3784
|
});
|
|
3253
3785
|
if (options.sessionId) {
|
|
3254
3786
|
await appendConductorSessionMessages(workspaceRoot, options.sessionId, buildPersistedRunnableTranscript(target.inputText, result));
|
|
3787
|
+
await mergeSessionUsage(workspaceRoot, options.sessionId, usageByModel);
|
|
3255
3788
|
}
|
|
3256
3789
|
await executionLog?.finish({
|
|
3257
3790
|
status: result.error ? 'error' : 'success',
|
|
@@ -3267,6 +3800,7 @@ async function executeRunnableCommand(workspaceRoot, target, args, options = {})
|
|
|
3267
3800
|
output: [],
|
|
3268
3801
|
error: error.message,
|
|
3269
3802
|
}));
|
|
3803
|
+
await mergeSessionUsage(workspaceRoot, options.sessionId, usageByModel);
|
|
3270
3804
|
}
|
|
3271
3805
|
await executionLog?.finish({
|
|
3272
3806
|
status: 'error',
|
|
@@ -3494,6 +4028,15 @@ export function previewMessageFromEvent(logEvent) {
|
|
|
3494
4028
|
return null;
|
|
3495
4029
|
}
|
|
3496
4030
|
return previewQuestionMessage(logEvent.event.prompt, logEvent.scope === 'child' ? logEvent.childSlug : undefined);
|
|
4031
|
+
case 'memory_compaction':
|
|
4032
|
+
return {
|
|
4033
|
+
role: 'system',
|
|
4034
|
+
kind: 'output',
|
|
4035
|
+
tag: logEvent.scope === 'child' ? logEvent.childSlug : undefined,
|
|
4036
|
+
content: logEvent.event.content
|
|
4037
|
+
? `Memory compaction: ${logEvent.event.content}`
|
|
4038
|
+
: 'Memory compaction triggered.',
|
|
4039
|
+
};
|
|
3497
4040
|
default:
|
|
3498
4041
|
return null;
|
|
3499
4042
|
}
|
|
@@ -4053,6 +4596,37 @@ function expandTabs(text, tabWidth) {
|
|
|
4053
4596
|
export function measureDisplayWidth(text) {
|
|
4054
4597
|
return splitGraphemes(text).reduce((total, grapheme) => total + graphemeDisplayWidth(grapheme), 0);
|
|
4055
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
|
+
}
|
|
4056
4630
|
export function padRight(text, width) {
|
|
4057
4631
|
const visibleWidth = measureDisplayWidth(text);
|
|
4058
4632
|
return visibleWidth >= width ? text : `${text}${' '.repeat(width - visibleWidth)}`;
|