pi-ui-extend 0.1.49 → 0.1.51
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/app/icons.d.ts +1 -0
- package/dist/app/icons.js +2 -0
- package/dist/app/input/autocomplete-controller.js +1 -1
- package/dist/app/input/input-controller.d.ts +1 -0
- package/dist/app/input/input-controller.js +7 -24
- package/dist/app/input/input-paste-handler.d.ts +1 -0
- package/dist/app/input/input-paste-handler.js +13 -16
- package/dist/app/rendering/conversation-tool-renderer.js +1 -0
- package/dist/app/rendering/popup-menu-renderer.js +2 -2
- package/dist/app/rendering/render-controller.js +10 -16
- package/dist/app/rendering/status-line-renderer.d.ts +0 -7
- package/dist/app/rendering/status-line-renderer.js +7 -79
- package/dist/app/rendering/tab-line-renderer.js +1 -1
- package/dist/app/rendering/toast-renderer.js +40 -15
- package/dist/app/rendering/tool-block-renderer.d.ts +2 -1
- package/dist/app/rendering/tool-block-renderer.js +44 -3
- package/dist/app/screen/mouse-controller.d.ts +1 -0
- package/dist/app/screen/mouse-controller.js +30 -76
- package/dist/app/session/lazy-session-manager.js +0 -7
- package/dist/app/session/queued-message-controller.d.ts +0 -1
- package/dist/app/session/queued-message-controller.js +5 -13
- package/dist/app/session/queued-message-entries.d.ts +1 -0
- package/dist/app/session/queued-message-entries.js +8 -0
- package/dist/app/session/tabs-controller.d.ts +2 -1
- package/dist/app/session/tabs-controller.js +67 -24
- package/dist/app/types.d.ts +1 -0
- package/dist/bundled-extensions/session-title/title-generation.js +1 -1
- package/dist/tool-renderers/compress.js +28 -7
- package/dist/tool-renderers/patch-normalize.js +4 -4
- package/dist/tool-renderers/types.d.ts +10 -0
- package/external/pi-tools-suite/package.json +3 -3
- package/external/pi-tools-suite/src/async-subagents/core/cleanup.ts +2 -2
- package/external/pi-tools-suite/src/async-subagents/core/paths.ts +12 -0
- package/external/pi-tools-suite/src/async-subagents/core/registry.ts +3 -4
- package/external/pi-tools-suite/src/async-subagents/core/routing.ts +1 -1
- package/external/pi-tools-suite/src/async-subagents/core/sessions.ts +2 -2
- package/external/pi-tools-suite/src/async-subagents/core/state.ts +3 -3
- package/external/pi-tools-suite/src/async-subagents/core/ultrawork-auto.ts +1 -1
- package/external/pi-tools-suite/src/async-subagents/lib.ts +1 -1
- package/external/pi-tools-suite/src/coding-discipline/index.ts +1 -1
- package/external/pi-tools-suite/src/dcp/auto-compress.ts +1 -1
- package/package.json +4 -4
|
@@ -436,8 +436,8 @@ export class AppMouseController {
|
|
|
436
436
|
const inputEndRow = inputStartRow + renderedInput.lines.length;
|
|
437
437
|
if (localY < inputStartRow || localY >= inputEndRow)
|
|
438
438
|
return undefined;
|
|
439
|
-
const { contentWidth } = horizontalPaddingLayout(columns);
|
|
440
|
-
return { renderedInput, inputStartRow, localY, contentWidth };
|
|
439
|
+
const { left, contentWidth } = horizontalPaddingLayout(columns);
|
|
440
|
+
return { renderedInput, inputStartRow, localY, left, contentWidth };
|
|
441
441
|
}
|
|
442
442
|
tabLineTargetAt(event) {
|
|
443
443
|
return this.tabLineTargets.find((candidate) => (event.y === candidate.row
|
|
@@ -459,30 +459,21 @@ export class AppMouseController {
|
|
|
459
459
|
return true;
|
|
460
460
|
}
|
|
461
461
|
handleStatusModelClick(event) {
|
|
462
|
-
|
|
463
|
-
if (!target)
|
|
464
|
-
return false;
|
|
465
|
-
if (event.y !== target.row || event.x < target.startColumn || event.x >= target.endColumn)
|
|
462
|
+
if (!this.statusTargetContains(this.statusModelTarget, event))
|
|
466
463
|
return false;
|
|
467
464
|
this.popupMenus.openDirectPopupMenu("model");
|
|
468
465
|
this.host.render();
|
|
469
466
|
return true;
|
|
470
467
|
}
|
|
471
468
|
handleStatusThinkingClick(event) {
|
|
472
|
-
|
|
473
|
-
if (!target)
|
|
474
|
-
return false;
|
|
475
|
-
if (event.y !== target.row || event.x < target.startColumn || event.x >= target.endColumn)
|
|
469
|
+
if (!this.statusTargetContains(this.statusThinkingTarget, event))
|
|
476
470
|
return false;
|
|
477
471
|
this.popupMenus.openDirectPopupMenu("thinking");
|
|
478
472
|
this.host.render();
|
|
479
473
|
return true;
|
|
480
474
|
}
|
|
481
475
|
handleStatusContextClick(event) {
|
|
482
|
-
|
|
483
|
-
if (!target)
|
|
484
|
-
return false;
|
|
485
|
-
if (event.y !== target.row || event.x < target.startColumn || event.x >= target.endColumn)
|
|
476
|
+
if (!this.statusTargetContains(this.statusContextTarget, event))
|
|
486
477
|
return false;
|
|
487
478
|
const session = this.host.runtimeSession();
|
|
488
479
|
if (!session)
|
|
@@ -492,19 +483,13 @@ export class AppMouseController {
|
|
|
492
483
|
return true;
|
|
493
484
|
}
|
|
494
485
|
handleStatusModelUsageClick(event) {
|
|
495
|
-
|
|
496
|
-
if (!target)
|
|
497
|
-
return false;
|
|
498
|
-
if (event.y !== target.row || event.x < target.startColumn || event.x >= target.endColumn)
|
|
486
|
+
if (!this.statusTargetContains(this.statusModelUsageTarget, event))
|
|
499
487
|
return false;
|
|
500
488
|
void this.host.refreshModelUsageStatus();
|
|
501
489
|
return true;
|
|
502
490
|
}
|
|
503
491
|
handleStatusUserJumpClick(event) {
|
|
504
|
-
|
|
505
|
-
if (!target)
|
|
506
|
-
return false;
|
|
507
|
-
if (event.y !== target.row || event.x < target.startColumn || event.x >= target.endColumn)
|
|
492
|
+
if (!this.statusTargetContains(this.statusUserJumpTarget, event))
|
|
508
493
|
return false;
|
|
509
494
|
void this.openStatusUserJumpMenu();
|
|
510
495
|
return true;
|
|
@@ -535,102 +520,75 @@ export class AppMouseController {
|
|
|
535
520
|
}
|
|
536
521
|
}
|
|
537
522
|
handleStatusDraftQueueClick(event) {
|
|
538
|
-
|
|
539
|
-
if (!target)
|
|
540
|
-
return false;
|
|
541
|
-
if (event.y !== target.row || event.x < target.startColumn || event.x >= target.endColumn)
|
|
523
|
+
if (!this.statusTargetContains(this.statusDraftQueueTarget, event))
|
|
542
524
|
return false;
|
|
543
525
|
void this.host.queueInputFromStatus?.();
|
|
544
526
|
return true;
|
|
545
527
|
}
|
|
546
528
|
handleStatusThinkingExpandClick(event) {
|
|
547
|
-
|
|
548
|
-
if (!target)
|
|
549
|
-
return false;
|
|
550
|
-
if (event.y !== target.row || event.x < target.startColumn || event.x >= target.endColumn)
|
|
529
|
+
if (!this.statusTargetContains(this.statusThinkingExpandTarget, event))
|
|
551
530
|
return false;
|
|
552
531
|
this.host.toggleAllThinkingExpanded?.();
|
|
553
532
|
return true;
|
|
554
533
|
}
|
|
555
534
|
handleStatusCompactToolsClick(event) {
|
|
556
|
-
|
|
557
|
-
if (!target)
|
|
558
|
-
return false;
|
|
559
|
-
if (event.y !== target.row || event.x < target.startColumn || event.x >= target.endColumn)
|
|
535
|
+
if (!this.statusTargetContains(this.statusCompactToolsTarget, event))
|
|
560
536
|
return false;
|
|
561
537
|
this.host.toggleSuperCompactTools?.();
|
|
562
538
|
return true;
|
|
563
539
|
}
|
|
564
540
|
handleStatusTerminalBellSoundClick(event) {
|
|
565
|
-
|
|
566
|
-
if (!target)
|
|
567
|
-
return false;
|
|
568
|
-
if (event.y !== target.row || event.x < target.startColumn || event.x >= target.endColumn)
|
|
541
|
+
if (!this.statusTargetContains(this.statusTerminalBellSoundTarget, event))
|
|
569
542
|
return false;
|
|
570
543
|
this.host.toggleTerminalBellSound?.();
|
|
571
544
|
return true;
|
|
572
545
|
}
|
|
573
546
|
handleStatusSessionClick(event) {
|
|
574
|
-
|
|
575
|
-
if (!target)
|
|
576
|
-
return false;
|
|
577
|
-
if (event.y !== target.row || event.x < target.startColumn || event.x >= target.endColumn)
|
|
547
|
+
if (!this.statusTargetContains(this.statusSessionTarget, event))
|
|
578
548
|
return false;
|
|
579
549
|
void this.commandController.runResumeCommand({ preserveStatus: true });
|
|
580
550
|
return true;
|
|
581
551
|
}
|
|
582
552
|
handleStatusPromptEnhancerClick(event) {
|
|
583
|
-
|
|
584
|
-
if (!target)
|
|
585
|
-
return false;
|
|
586
|
-
if (event.y !== target.row || event.x < target.startColumn || event.x >= target.endColumn)
|
|
553
|
+
if (!this.statusTargetContains(this.statusPromptEnhancerTarget, event))
|
|
587
554
|
return false;
|
|
588
555
|
void this.host.enhancePrompt();
|
|
589
556
|
return true;
|
|
590
557
|
}
|
|
591
558
|
handleStatusVoiceMicClick(event) {
|
|
592
|
-
|
|
593
|
-
if (!target)
|
|
594
|
-
return false;
|
|
595
|
-
if (event.y !== target.row || event.x < target.startColumn || event.x >= target.endColumn)
|
|
559
|
+
if (!this.statusTargetContains(this.statusVoiceMicTarget, event))
|
|
596
560
|
return false;
|
|
597
561
|
this.host.toggleVoiceRecording();
|
|
598
562
|
return true;
|
|
599
563
|
}
|
|
600
564
|
handleStatusVoiceLanguageClick(event) {
|
|
601
|
-
|
|
602
|
-
if (!target)
|
|
603
|
-
return false;
|
|
604
|
-
if (event.y !== target.row || event.x < target.startColumn || event.x >= target.endColumn)
|
|
565
|
+
if (!this.statusTargetContains(this.statusVoiceLanguageTarget, event))
|
|
605
566
|
return false;
|
|
606
567
|
this.host.toggleVoiceLanguage();
|
|
607
568
|
return true;
|
|
608
569
|
}
|
|
609
570
|
handleStatusQuickScrollClick(event) {
|
|
610
|
-
const target = [this.statusQuickScrollUpTarget, this.statusQuickScrollDownTarget].find((candidate) =>
|
|
611
|
-
&& event.y === candidate.row
|
|
612
|
-
&& event.x >= candidate.startColumn
|
|
613
|
-
&& event.x < candidate.endColumn);
|
|
571
|
+
const target = [this.statusQuickScrollUpTarget, this.statusQuickScrollDownTarget].find((candidate) => this.statusTargetContains(candidate, event));
|
|
614
572
|
if (!target)
|
|
615
573
|
return false;
|
|
616
574
|
this.host.scrollConversationQuick(target.direction);
|
|
617
575
|
return true;
|
|
618
576
|
}
|
|
577
|
+
statusTargetContains(target, event) {
|
|
578
|
+
return !!target
|
|
579
|
+
&& event.y === target.row
|
|
580
|
+
&& event.x >= target.startColumn
|
|
581
|
+
&& event.x < target.endColumn;
|
|
582
|
+
}
|
|
619
583
|
handleInputClick(event) {
|
|
620
|
-
const
|
|
621
|
-
|
|
622
|
-
const tabPanelRows = this.host.tabPanelRows(terminalRows);
|
|
623
|
-
const rows = editorLayoutRows(terminalRows, tabPanelRows);
|
|
624
|
-
const localY = event.y - editorLayoutTopOffset(tabPanelRows);
|
|
625
|
-
const { renderedInput, inputStartRow } = this.host.editorLayoutRenderer().computeLayout(columns, rows);
|
|
626
|
-
const inputEndRow = inputStartRow + renderedInput.lines.length;
|
|
627
|
-
if (localY < inputStartRow || localY >= inputEndRow)
|
|
584
|
+
const geometry = this.inputGeometry(event);
|
|
585
|
+
if (!geometry)
|
|
628
586
|
return false;
|
|
587
|
+
const { renderedInput, inputStartRow, localY, left, contentWidth } = geometry;
|
|
629
588
|
if (localY < inputStartRow + renderedInput.editorStartRowOffset)
|
|
630
589
|
return false;
|
|
631
590
|
const visibleRowOffset = localY - inputStartRow - renderedInput.editorStartRowOffset;
|
|
632
591
|
const visualRow = renderedInput.scrollOffset + visibleRowOffset;
|
|
633
|
-
const { left, contentWidth } = horizontalPaddingLayout(columns);
|
|
634
592
|
const cursor = this.host.inputEditor().offsetAtVisualPosition(visualRow, event.x - left, contentWidth, "", "");
|
|
635
593
|
this.host.resetRequestHistoryNavigation();
|
|
636
594
|
this.host.inputEditor().setCursor(cursor, { preserveScroll: true });
|
|
@@ -638,16 +596,12 @@ export class AppMouseController {
|
|
|
638
596
|
return true;
|
|
639
597
|
}
|
|
640
598
|
handleExtensionInputClick(event) {
|
|
641
|
-
const
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
const
|
|
645
|
-
|
|
646
|
-
const { renderedInput, inputStartRow } = this.host.editorLayoutRenderer().computeLayout(columns, rows);
|
|
647
|
-
const inputEndRow = inputStartRow + renderedInput.editorStartRowOffset;
|
|
648
|
-
if (localY < inputStartRow || localY >= inputEndRow)
|
|
599
|
+
const geometry = this.inputGeometry(event);
|
|
600
|
+
if (!geometry)
|
|
601
|
+
return false;
|
|
602
|
+
const { renderedInput, inputStartRow, localY, left, contentWidth } = geometry;
|
|
603
|
+
if (localY >= inputStartRow + renderedInput.editorStartRowOffset)
|
|
649
604
|
return false;
|
|
650
|
-
const { left, contentWidth } = horizontalPaddingLayout(columns);
|
|
651
605
|
return this.host.handleExtensionInputMouse({
|
|
652
606
|
...event,
|
|
653
607
|
localRow: localY - inputStartRow,
|
|
@@ -20,7 +20,6 @@ class LazySessionManager {
|
|
|
20
20
|
entries = [];
|
|
21
21
|
byId = new Map();
|
|
22
22
|
labelsById = new Map();
|
|
23
|
-
labelTimestampsById = new Map();
|
|
24
23
|
leafId = null;
|
|
25
24
|
hydrated;
|
|
26
25
|
tailEntryCount;
|
|
@@ -69,7 +68,6 @@ class LazySessionManager {
|
|
|
69
68
|
this.entries = [];
|
|
70
69
|
this.byId.clear();
|
|
71
70
|
this.labelsById.clear();
|
|
72
|
-
this.labelTimestampsById.clear();
|
|
73
71
|
this.leafId = null;
|
|
74
72
|
mkdirSync(this.sessionDirPath, { recursive: true });
|
|
75
73
|
this.sessionFilePath = join(this.sessionDirPath, `${timestamp.replace(/[:.]/g, "-")}_${sessionId}.jsonl`);
|
|
@@ -212,11 +210,9 @@ class LazySessionManager {
|
|
|
212
210
|
this.appendEntry(entry);
|
|
213
211
|
if (label) {
|
|
214
212
|
this.labelsById.set(targetId, label);
|
|
215
|
-
this.labelTimestampsById.set(targetId, entry.timestamp);
|
|
216
213
|
}
|
|
217
214
|
else {
|
|
218
215
|
this.labelsById.delete(targetId);
|
|
219
|
-
this.labelTimestampsById.delete(targetId);
|
|
220
216
|
}
|
|
221
217
|
return entry.id;
|
|
222
218
|
}
|
|
@@ -290,7 +286,6 @@ class LazySessionManager {
|
|
|
290
286
|
rebuildIndexes() {
|
|
291
287
|
this.byId.clear();
|
|
292
288
|
this.labelsById.clear();
|
|
293
|
-
this.labelTimestampsById.clear();
|
|
294
289
|
this.leafId = null;
|
|
295
290
|
for (const entry of this.entries) {
|
|
296
291
|
this.byId.set(entry.id, entry);
|
|
@@ -298,11 +293,9 @@ class LazySessionManager {
|
|
|
298
293
|
if (entry.type === "label") {
|
|
299
294
|
if (entry.label) {
|
|
300
295
|
this.labelsById.set(entry.targetId, entry.label);
|
|
301
|
-
this.labelTimestampsById.set(entry.targetId, entry.timestamp);
|
|
302
296
|
}
|
|
303
297
|
else {
|
|
304
298
|
this.labelsById.delete(entry.targetId);
|
|
305
|
-
this.labelTimestampsById.delete(entry.targetId);
|
|
306
299
|
}
|
|
307
300
|
}
|
|
308
301
|
}
|
|
@@ -75,7 +75,6 @@ export declare class AppQueuedMessageController {
|
|
|
75
75
|
private requeueRemovedEntry;
|
|
76
76
|
private restoreSubmittedMessageToEditor;
|
|
77
77
|
private restorableSubmittedMessageText;
|
|
78
|
-
private cloneSubmittedUserMessage;
|
|
79
78
|
private notifyDeferredUserMessagesChanged;
|
|
80
79
|
private notifyAutoUserMessagesChanged;
|
|
81
80
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createId } from "../id.js";
|
|
2
2
|
import { stringifyUnknown, submittedUserDisplayText } from "../rendering/message-content.js";
|
|
3
|
-
import { autoQueuedMessageEntries, deferredQueuedMessageEntries, queuedMessageEntries } from "./queued-message-entries.js";
|
|
3
|
+
import { autoQueuedMessageEntries, cloneSubmittedUserMessage, deferredQueuedMessageEntries, queuedMessageEntries } from "./queued-message-entries.js";
|
|
4
4
|
export class AppQueuedMessageController {
|
|
5
5
|
host;
|
|
6
6
|
autoUserMessages = [];
|
|
@@ -16,20 +16,20 @@ export class AppQueuedMessageController {
|
|
|
16
16
|
this.promptSubmissionInFlightSession = undefined;
|
|
17
17
|
}
|
|
18
18
|
captureAutoUserMessages() {
|
|
19
|
-
return this.autoUserMessages.map(
|
|
19
|
+
return this.autoUserMessages.map(cloneSubmittedUserMessage);
|
|
20
20
|
}
|
|
21
21
|
restoreAutoUserMessages(messages) {
|
|
22
22
|
this.autoUserMessages.length = 0;
|
|
23
|
-
this.autoUserMessages.push(...messages.map(
|
|
23
|
+
this.autoUserMessages.push(...messages.map(cloneSubmittedUserMessage));
|
|
24
24
|
this.updateQueuedMessageStatus();
|
|
25
25
|
void this.flushAutoUserMessages();
|
|
26
26
|
}
|
|
27
27
|
captureDeferredUserMessages() {
|
|
28
|
-
return this.deferredUserMessages.map(
|
|
28
|
+
return this.deferredUserMessages.map(cloneSubmittedUserMessage);
|
|
29
29
|
}
|
|
30
30
|
restoreDeferredUserMessages(messages) {
|
|
31
31
|
this.deferredUserMessages.length = 0;
|
|
32
|
-
this.deferredUserMessages.push(...messages.map(
|
|
32
|
+
this.deferredUserMessages.push(...messages.map(cloneSubmittedUserMessage));
|
|
33
33
|
this.updateQueuedMessageStatus();
|
|
34
34
|
}
|
|
35
35
|
createSubmittedUserMessage(promptText, displayText, images) {
|
|
@@ -398,14 +398,6 @@ export class AppQueuedMessageController {
|
|
|
398
398
|
? message.promptText.replace(/\[Image \d+(?:: [^\]]+)?\]\s*/g, "").trimEnd()
|
|
399
399
|
: message.promptText.trimEnd();
|
|
400
400
|
}
|
|
401
|
-
cloneSubmittedUserMessage(message) {
|
|
402
|
-
return {
|
|
403
|
-
id: message.id,
|
|
404
|
-
promptText: message.promptText,
|
|
405
|
-
displayText: message.displayText,
|
|
406
|
-
images: message.images.map((image) => ({ ...image })),
|
|
407
|
-
};
|
|
408
|
-
}
|
|
409
401
|
notifyDeferredUserMessagesChanged() {
|
|
410
402
|
this.host.onDeferredUserMessagesChanged?.();
|
|
411
403
|
}
|
|
@@ -7,3 +7,4 @@ export declare function sdkQueuedMessageEntries(session: AgentSession | undefine
|
|
|
7
7
|
export declare function deferredQueuedMessageEntries(messages: readonly SubmittedUserMessage[]): QueuedEntry[];
|
|
8
8
|
export declare function autoQueuedMessageEntries(messages: readonly SubmittedUserMessage[]): QueuedEntry[];
|
|
9
9
|
export declare function queuedMessageEntries(session: AgentSession | undefined, autoUserMessages: readonly SubmittedUserMessage[], deferredUserMessages: readonly SubmittedUserMessage[]): QueuedEntry[];
|
|
10
|
+
export declare function cloneSubmittedUserMessage(message: SubmittedUserMessage): SubmittedUserMessage;
|
|
@@ -50,3 +50,11 @@ export function queuedMessageEntries(session, autoUserMessages, deferredUserMess
|
|
|
50
50
|
...deferredQueuedMessageEntries(deferredUserMessages),
|
|
51
51
|
];
|
|
52
52
|
}
|
|
53
|
+
export function cloneSubmittedUserMessage(message) {
|
|
54
|
+
return {
|
|
55
|
+
id: message.id,
|
|
56
|
+
promptText: message.promptText,
|
|
57
|
+
displayText: message.displayText,
|
|
58
|
+
images: message.images.map((image) => ({ ...image })),
|
|
59
|
+
};
|
|
60
|
+
}
|
|
@@ -119,7 +119,6 @@ export declare class AppTabsController {
|
|
|
119
119
|
private restoreInputState;
|
|
120
120
|
private inputStateFromText;
|
|
121
121
|
private restoreDeferredUserMessages;
|
|
122
|
-
private cloneSubmittedUserMessage;
|
|
123
122
|
private runtimeForTab;
|
|
124
123
|
private findTabForSession;
|
|
125
124
|
private findTabBySessionPath;
|
|
@@ -133,6 +132,7 @@ export declare class AppTabsController {
|
|
|
133
132
|
private updateTabFromSession;
|
|
134
133
|
private sessionPath;
|
|
135
134
|
private sessionTitle;
|
|
135
|
+
private sessionIsFork;
|
|
136
136
|
private sessionTitleFromParts;
|
|
137
137
|
private updatedSessionTitle;
|
|
138
138
|
private sessionActivity;
|
|
@@ -143,6 +143,7 @@ export declare class AppTabsController {
|
|
|
143
143
|
private restoredTabs;
|
|
144
144
|
private defaultSessionTitleFromPath;
|
|
145
145
|
private loadSessionTitles;
|
|
146
|
+
private loadForkedSessionPaths;
|
|
146
147
|
private scheduleRestoredTabTitleRefresh;
|
|
147
148
|
private refreshRestoredTabTitles;
|
|
148
149
|
private loadTabs;
|
|
@@ -6,6 +6,7 @@ import { getAgentDir, } from "@earendil-works/pi-coding-agent";
|
|
|
6
6
|
import { isRecord } from "../guards.js";
|
|
7
7
|
import { createId } from "../id.js";
|
|
8
8
|
import { tabPanelRows } from "../rendering/tab-line-renderer.js";
|
|
9
|
+
import { cloneSubmittedUserMessage } from "./queued-message-entries.js";
|
|
9
10
|
const TAB_STATE_VERSION = 4;
|
|
10
11
|
const MAX_RESTORED_TABS = 8;
|
|
11
12
|
const BACKGROUND_PREWARM_TAB_LIMIT = 2;
|
|
@@ -180,7 +181,9 @@ export class AppTabsController {
|
|
|
180
181
|
await this.saveTabs();
|
|
181
182
|
return;
|
|
182
183
|
}
|
|
183
|
-
const
|
|
184
|
+
const restoredSessionPaths = saved.tabs.map((tab) => tab.path);
|
|
185
|
+
const forkedSessionPaths = await this.loadForkedSessionPaths(restoredSessionPaths);
|
|
186
|
+
const restoredTabs = this.restoredTabs(saved, new Map(), forkedSessionPaths);
|
|
184
187
|
if (restoredTabs.length === 0) {
|
|
185
188
|
this.clearStartupTabPlaceholders();
|
|
186
189
|
await this.saveTabs();
|
|
@@ -198,7 +201,6 @@ export class AppTabsController {
|
|
|
198
201
|
this.replaceTabs(restoredTabs, desiredPath);
|
|
199
202
|
this.restorePersistedInputStates(saved);
|
|
200
203
|
this.restorePersistedQueuedUserMessages(saved);
|
|
201
|
-
const restoredSessionPaths = saved.tabs.map((tab) => tab.path);
|
|
202
204
|
if (explicitSessionPath && currentPath)
|
|
203
205
|
this.ensureCurrentSessionTab(runtime.session);
|
|
204
206
|
if (!desiredPath) {
|
|
@@ -1017,14 +1019,14 @@ export class AppTabsController {
|
|
|
1017
1019
|
return;
|
|
1018
1020
|
const autoMessages = this.host.captureAutoUserMessages?.() ?? [];
|
|
1019
1021
|
if (autoMessages.length > 0) {
|
|
1020
|
-
this.autoUserMessagesByTabId.set(this.activeTabId, autoMessages.map(
|
|
1022
|
+
this.autoUserMessagesByTabId.set(this.activeTabId, autoMessages.map(cloneSubmittedUserMessage));
|
|
1021
1023
|
}
|
|
1022
1024
|
else {
|
|
1023
1025
|
this.autoUserMessagesByTabId.delete(this.activeTabId);
|
|
1024
1026
|
}
|
|
1025
1027
|
const deferredMessages = this.host.captureDeferredUserMessages?.() ?? [];
|
|
1026
1028
|
if (deferredMessages.length > 0) {
|
|
1027
|
-
this.deferredUserMessagesByTabId.set(this.activeTabId, deferredMessages.map(
|
|
1029
|
+
this.deferredUserMessagesByTabId.set(this.activeTabId, deferredMessages.map(cloneSubmittedUserMessage));
|
|
1028
1030
|
}
|
|
1029
1031
|
else {
|
|
1030
1032
|
this.deferredUserMessagesByTabId.delete(this.activeTabId);
|
|
@@ -1040,14 +1042,6 @@ export class AppTabsController {
|
|
|
1040
1042
|
this.host.restoreAutoUserMessages?.(this.autoUserMessagesByTabId.get(tabId) ?? []);
|
|
1041
1043
|
this.host.restoreDeferredUserMessages?.(this.deferredUserMessagesByTabId.get(tabId) ?? []);
|
|
1042
1044
|
}
|
|
1043
|
-
cloneSubmittedUserMessage(message) {
|
|
1044
|
-
return {
|
|
1045
|
-
id: message.id,
|
|
1046
|
-
promptText: message.promptText,
|
|
1047
|
-
displayText: message.displayText,
|
|
1048
|
-
images: message.images.map((image) => ({ ...image })),
|
|
1049
|
-
};
|
|
1050
|
-
}
|
|
1051
1045
|
async runtimeForTab(tab) {
|
|
1052
1046
|
const existing = this.runtimesByTabId.get(tab.id);
|
|
1053
1047
|
if (existing)
|
|
@@ -1168,10 +1162,10 @@ export class AppTabsController {
|
|
|
1168
1162
|
for (const tab of saved.tabs) {
|
|
1169
1163
|
const sessionPath = resolve(tab.path);
|
|
1170
1164
|
if (tab.autoUserMessages && tab.autoUserMessages.length > 0) {
|
|
1171
|
-
autoMessagesByPath.set(sessionPath, tab.autoUserMessages.map(
|
|
1165
|
+
autoMessagesByPath.set(sessionPath, tab.autoUserMessages.map(cloneSubmittedUserMessage));
|
|
1172
1166
|
}
|
|
1173
1167
|
if (tab.deferredUserMessages && tab.deferredUserMessages.length > 0) {
|
|
1174
|
-
deferredMessagesByPath.set(sessionPath, tab.deferredUserMessages.map(
|
|
1168
|
+
deferredMessagesByPath.set(sessionPath, tab.deferredUserMessages.map(cloneSubmittedUserMessage));
|
|
1175
1169
|
}
|
|
1176
1170
|
}
|
|
1177
1171
|
for (const tab of this.tabItems) {
|
|
@@ -1207,6 +1201,7 @@ export class AppTabsController {
|
|
|
1207
1201
|
id: createId("tab"),
|
|
1208
1202
|
title: this.sessionTitle(session),
|
|
1209
1203
|
...(options.titlePlaceholder ? { titlePlaceholder: options.titlePlaceholder } : {}),
|
|
1204
|
+
...(this.sessionIsFork(session) ? { isFork: true } : {}),
|
|
1210
1205
|
status: "active",
|
|
1211
1206
|
activity: this.sessionActivity(session),
|
|
1212
1207
|
...(sessionPath ? { sessionPath } : {}),
|
|
@@ -1216,6 +1211,10 @@ export class AppTabsController {
|
|
|
1216
1211
|
const previousSessionPath = tab.sessionPath ? resolve(tab.sessionPath) : undefined;
|
|
1217
1212
|
const sessionPath = this.sessionPath(session);
|
|
1218
1213
|
tab.title = this.updatedSessionTitle(tab.title, this.sessionTitle(session), previousSessionPath, sessionPath, tab.titlePlaceholder !== undefined);
|
|
1214
|
+
if (this.sessionIsFork(session))
|
|
1215
|
+
tab.isFork = true;
|
|
1216
|
+
else
|
|
1217
|
+
delete tab.isFork;
|
|
1219
1218
|
tab.activity = this.sessionActivity(session);
|
|
1220
1219
|
if (sessionPath)
|
|
1221
1220
|
tab.sessionPath = sessionPath;
|
|
@@ -1226,6 +1225,10 @@ export class AppTabsController {
|
|
|
1226
1225
|
sessionTitle(session) {
|
|
1227
1226
|
return this.sessionTitleFromParts(session.sessionId, session.sessionName);
|
|
1228
1227
|
}
|
|
1228
|
+
sessionIsFork(session) {
|
|
1229
|
+
const sessionManager = session.sessionManager;
|
|
1230
|
+
return typeof sessionManager?.getHeader?.()?.parentSession === "string";
|
|
1231
|
+
}
|
|
1229
1232
|
sessionTitleFromParts(sessionId, sessionName) {
|
|
1230
1233
|
const name = sessionName?.trim();
|
|
1231
1234
|
return name && !LOADING_TAB_TITLE_PATTERN.test(name) ? name : sessionId.slice(0, 8);
|
|
@@ -1269,7 +1272,7 @@ export class AppTabsController {
|
|
|
1269
1272
|
initialVisible: true,
|
|
1270
1273
|
});
|
|
1271
1274
|
}
|
|
1272
|
-
restoredTabs(saved, sessionTitles = new Map()) {
|
|
1275
|
+
restoredTabs(saved, sessionTitles = new Map(), forkedSessionPaths = new Set()) {
|
|
1273
1276
|
const tabs = [];
|
|
1274
1277
|
const seen = new Set();
|
|
1275
1278
|
for (const tab of saved.tabs) {
|
|
@@ -1286,6 +1289,7 @@ export class AppTabsController {
|
|
|
1286
1289
|
tabs.push({
|
|
1287
1290
|
id: createId("tab"),
|
|
1288
1291
|
title: title || "session",
|
|
1292
|
+
...(forkedSessionPaths.has(sessionPath) ? { isFork: true } : {}),
|
|
1289
1293
|
status: "waiting",
|
|
1290
1294
|
sessionPath,
|
|
1291
1295
|
});
|
|
@@ -1308,6 +1312,13 @@ export class AppTabsController {
|
|
|
1308
1312
|
}));
|
|
1309
1313
|
return new Map(entries.filter((entry) => entry !== undefined));
|
|
1310
1314
|
}
|
|
1315
|
+
async loadForkedSessionPaths(sessionPaths) {
|
|
1316
|
+
const uniquePaths = [...new Set(sessionPaths.map((sessionPath) => resolve(sessionPath)))].slice(0, MAX_RESTORED_TABS);
|
|
1317
|
+
const entries = await Promise.all(uniquePaths.map(async (sessionPath) => {
|
|
1318
|
+
return await readSessionHasParent(sessionPath) ? sessionPath : undefined;
|
|
1319
|
+
}));
|
|
1320
|
+
return new Set(entries.filter((entry) => entry !== undefined));
|
|
1321
|
+
}
|
|
1311
1322
|
scheduleRestoredTabTitleRefresh(sessionPaths) {
|
|
1312
1323
|
if (sessionPaths.length === 0)
|
|
1313
1324
|
return;
|
|
@@ -1316,18 +1327,29 @@ export class AppTabsController {
|
|
|
1316
1327
|
}, 0).unref?.();
|
|
1317
1328
|
}
|
|
1318
1329
|
async refreshRestoredTabTitles(sessionPaths) {
|
|
1319
|
-
const titles = await
|
|
1320
|
-
|
|
1330
|
+
const [titles, forkedSessionPaths] = await Promise.all([
|
|
1331
|
+
this.loadSessionTitles(sessionPaths),
|
|
1332
|
+
this.loadForkedSessionPaths(sessionPaths),
|
|
1333
|
+
]);
|
|
1334
|
+
if (titles.size === 0 && forkedSessionPaths.size === 0)
|
|
1321
1335
|
return;
|
|
1322
1336
|
let changed = false;
|
|
1323
1337
|
for (const tab of this.tabItems) {
|
|
1324
1338
|
const sessionPath = tab.sessionPath ? resolve(tab.sessionPath) : undefined;
|
|
1325
1339
|
const title = sessionPath ? titles.get(sessionPath) : undefined;
|
|
1326
|
-
if (
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1340
|
+
if (title && tab.title !== title) {
|
|
1341
|
+
tab.title = title;
|
|
1342
|
+
delete tab.titlePlaceholder;
|
|
1343
|
+
changed = true;
|
|
1344
|
+
}
|
|
1345
|
+
const isFork = sessionPath ? forkedSessionPaths.has(sessionPath) : false;
|
|
1346
|
+
if (tab.isFork !== isFork) {
|
|
1347
|
+
if (isFork)
|
|
1348
|
+
tab.isFork = true;
|
|
1349
|
+
else
|
|
1350
|
+
delete tab.isFork;
|
|
1351
|
+
changed = true;
|
|
1352
|
+
}
|
|
1331
1353
|
}
|
|
1332
1354
|
if (!changed)
|
|
1333
1355
|
return;
|
|
@@ -1443,11 +1465,11 @@ export class AppTabsController {
|
|
|
1443
1465
|
}
|
|
1444
1466
|
const autoUserMessages = this.autoUserMessagesByTabId.get(tab.id);
|
|
1445
1467
|
if (autoUserMessages && autoUserMessages.length > 0) {
|
|
1446
|
-
persistedTab.autoUserMessages = autoUserMessages.map(
|
|
1468
|
+
persistedTab.autoUserMessages = autoUserMessages.map(cloneSubmittedUserMessage);
|
|
1447
1469
|
}
|
|
1448
1470
|
const deferredUserMessages = this.deferredUserMessagesByTabId.get(tab.id);
|
|
1449
1471
|
if (deferredUserMessages && deferredUserMessages.length > 0) {
|
|
1450
|
-
persistedTab.deferredUserMessages = deferredUserMessages.map(
|
|
1472
|
+
persistedTab.deferredUserMessages = deferredUserMessages.map(cloneSubmittedUserMessage);
|
|
1451
1473
|
}
|
|
1452
1474
|
tabs.push(persistedTab);
|
|
1453
1475
|
}
|
|
@@ -1700,6 +1722,27 @@ async function readSessionTitleChunk(file, position, byteCount, options = {}) {
|
|
|
1700
1722
|
}
|
|
1701
1723
|
return undefined;
|
|
1702
1724
|
}
|
|
1725
|
+
async function readSessionHasParent(sessionPath) {
|
|
1726
|
+
let file;
|
|
1727
|
+
try {
|
|
1728
|
+
file = await openFile(sessionPath, "r");
|
|
1729
|
+
const buffer = Buffer.alloc(4096);
|
|
1730
|
+
const { bytesRead } = await file.read(buffer, 0, buffer.length, 0);
|
|
1731
|
+
if (bytesRead <= 0)
|
|
1732
|
+
return false;
|
|
1733
|
+
const firstLine = buffer.subarray(0, bytesRead).toString("utf8").split("\n", 1)[0]?.trim();
|
|
1734
|
+
if (!firstLine)
|
|
1735
|
+
return false;
|
|
1736
|
+
const parsed = JSON.parse(firstLine);
|
|
1737
|
+
return isRecord(parsed) && parsed.type === "session" && typeof parsed.parentSession === "string";
|
|
1738
|
+
}
|
|
1739
|
+
catch {
|
|
1740
|
+
return false;
|
|
1741
|
+
}
|
|
1742
|
+
finally {
|
|
1743
|
+
await file?.close();
|
|
1744
|
+
}
|
|
1745
|
+
}
|
|
1703
1746
|
function validSessionTitle(value) {
|
|
1704
1747
|
const title = value?.trim();
|
|
1705
1748
|
return title && !LOADING_TAB_TITLE_PATTERN.test(title) ? title : undefined;
|
package/dist/app/types.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { complete } from "@earendil-works/pi-ai";
|
|
1
|
+
import { complete } from "@earendil-works/pi-ai/compat";
|
|
2
2
|
const TITLE_SYSTEM_PROMPT = [
|
|
3
3
|
"You name Pi coding-agent sessions from the user's first request.",
|
|
4
4
|
"Return only one concise title, without quotes, markdown, emoji, or explanations.",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { formatCompactProgressBar } from "../context-progress-bar.js";
|
|
1
|
+
import { compactProgressBarSegments, formatCompactProgressBar } from "../context-progress-bar.js";
|
|
2
2
|
import { parseArgsText, stringArg } from "./utils.js";
|
|
3
3
|
export const renderCompressTool = (input) => {
|
|
4
4
|
const topic = stringArg(input, ["topic"]);
|
|
@@ -6,6 +6,7 @@ export const renderCompressTool = (input) => {
|
|
|
6
6
|
const header = joinHeaderParts(topic ? { text: topic } : undefined, summary) ?? { text: "" };
|
|
7
7
|
return {
|
|
8
8
|
...(header.text ? { headerArgs: header.text } : {}),
|
|
9
|
+
...(header.segments && header.segments.length > 0 ? { headerArgSegments: header.segments } : {}),
|
|
9
10
|
collapsedBody: "",
|
|
10
11
|
expandedText: fullCompressResponse(input.output, input.status),
|
|
11
12
|
};
|
|
@@ -19,14 +20,14 @@ function formatCompressSummary(input) {
|
|
|
19
20
|
const parsed = parseArgsText(output);
|
|
20
21
|
if (!isRecord(parsed))
|
|
21
22
|
return { text: oneLine(output) };
|
|
22
|
-
return formatCompressSuccess(parsed);
|
|
23
|
+
return formatCompressSuccess(parsed, input.colors);
|
|
23
24
|
}
|
|
24
25
|
function fullCompressResponse(output, status) {
|
|
25
26
|
if (output.trim())
|
|
26
27
|
return output.trimEnd();
|
|
27
28
|
return status === "running" ? "running…" : "(empty)";
|
|
28
29
|
}
|
|
29
|
-
function formatCompressSuccess(result) {
|
|
30
|
+
function formatCompressSuccess(result, colors) {
|
|
30
31
|
const tokensSaved = numberValue(result.tokensSaved);
|
|
31
32
|
const contextPercent = numberValue(result.contextPercent);
|
|
32
33
|
const contextTokens = numberValue(result.contextTokens);
|
|
@@ -42,7 +43,7 @@ function formatCompressSuccess(result) {
|
|
|
42
43
|
const barPercent = compressedContextPercent ?? contextPercent;
|
|
43
44
|
const parts = [
|
|
44
45
|
{ text: tokensSaved != null ? `saved ${formatCompactNumber(tokensSaved)}` : "compressed" },
|
|
45
|
-
barPercent != null ? progressPart(barPercent, originalContextTokens) : undefined,
|
|
46
|
+
barPercent != null ? progressPart(barPercent, originalContextTokens, colors) : undefined,
|
|
46
47
|
compressedContextPercent != null && contextPercent != null ? { text: `context ${formatPercent(contextPercent)}` } : undefined,
|
|
47
48
|
itemCount != null ? { text: `${itemCount} ${plural(itemCount, "item")}` } : undefined,
|
|
48
49
|
summaryTokens != null ? { text: `${formatCompactNumber(summaryTokens)} summary tokens` } : undefined,
|
|
@@ -52,9 +53,17 @@ function formatCompressSuccess(result) {
|
|
|
52
53
|
].filter((part) => Boolean(part));
|
|
53
54
|
return joinHeaderParts(...parts) ?? { text: "compressed" };
|
|
54
55
|
}
|
|
55
|
-
function progressPart(percent, previousContextTokens) {
|
|
56
|
+
function progressPart(percent, previousContextTokens, colors) {
|
|
56
57
|
const text = `${formatCompactProgressBar(percent)} ${formatPercent(percent)}${previousContextTokens != null ? ` of ${formatCompactNumber(previousContextTokens)}` : ""}`;
|
|
57
|
-
return {
|
|
58
|
+
return {
|
|
59
|
+
text,
|
|
60
|
+
...(colors ? {
|
|
61
|
+
segments: compactProgressBarSegments(0, percent, {
|
|
62
|
+
fill: colors.statusForeground,
|
|
63
|
+
track: colors.statusDotBase,
|
|
64
|
+
}),
|
|
65
|
+
} : {}),
|
|
66
|
+
};
|
|
58
67
|
}
|
|
59
68
|
function inferContextTokens(contextPercent, contextWindow) {
|
|
60
69
|
if (contextPercent == null || contextWindow == null || contextWindow <= 0)
|
|
@@ -71,7 +80,19 @@ function joinHeaderParts(...parts) {
|
|
|
71
80
|
if (defined.length === 0)
|
|
72
81
|
return undefined;
|
|
73
82
|
const separator = " · ";
|
|
74
|
-
|
|
83
|
+
let offset = 0;
|
|
84
|
+
const segments = [];
|
|
85
|
+
for (const [index, part] of defined.entries()) {
|
|
86
|
+
if (index > 0)
|
|
87
|
+
offset += separator.length;
|
|
88
|
+
if (part.segments)
|
|
89
|
+
segments.push(...part.segments.map((segment) => ({ ...segment, start: segment.start + offset, end: segment.end + offset })));
|
|
90
|
+
offset += part.text.length;
|
|
91
|
+
}
|
|
92
|
+
return {
|
|
93
|
+
text: defined.map((part) => part.text).join(separator),
|
|
94
|
+
...(segments.length > 0 ? { segments } : {}),
|
|
95
|
+
};
|
|
75
96
|
}
|
|
76
97
|
function numberValue(value) {
|
|
77
98
|
return typeof value === "number" && Number.isFinite(value) ? value : undefined;
|