@timurproko/a1 0.1.8-dev.572 → 0.1.8-dev.573
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/session-shell/prompt-chips.js +20 -28
- package/dist/app/session-shell/session-shell-root.d.ts +2 -0
- package/dist/app/session-shell/session-shell-root.js +54 -14
- package/dist/app/session-shell/session-shell.js +30 -2
- package/dist/app/session-shell/session-viewport-controller.d.ts +1 -0
- package/dist/app/session-shell/session-viewport-controller.js +5 -1
- package/dist/contracts/owned-ui/index.d.ts +2 -0
- package/dist/contracts/owned-ui/index.js +1 -0
- package/dist/contracts/owned-ui/prompt-chips.d.ts +15 -0
- package/dist/contracts/owned-ui/prompt-chips.js +59 -0
- package/dist/contracts/owned-ui/validation.js +1 -1
- package/dist/integrations/pi/components/index.d.ts +1 -0
- package/dist/integrations/pi/components/index.js +1 -0
- package/dist/integrations/pi/components/modal-frame.d.ts +41 -0
- package/dist/integrations/pi/components/modal-frame.js +110 -0
- package/dist/integrations/pi/components/models-dialog.js +19 -5
- package/dist/integrations/pi/components/shell-selectors-dialogs.js +38 -2
- package/dist/integrations/pi/components/skills-dialog.js +3 -3
- package/dist/integrations/pi/components/submitted-prompt-adapter.d.ts +1 -1
- package/dist/integrations/pi/components/submitted-prompt-adapter.js +5 -2
- package/dist/integrations/pi/components/upstream/components/extension-editor.d.ts +2 -1
- package/dist/integrations/pi/components/upstream/components/extension-editor.js +7 -6
- package/dist/integrations/pi/components/upstream/components/extension-input.js +9 -8
- package/dist/integrations/pi/components/upstream/components/extension-selector.js +10 -9
- package/dist/integrations/pi/components/upstream/components/scoped-models-selector.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/components/scoped-models-selector.js +3 -3
- package/dist/integrations/pi/components/upstream/components/session-selector.js +13 -6
- package/dist/integrations/pi/components/upstream/components/thinking-selector.d.ts +7 -6
- package/dist/integrations/pi/components/upstream/components/thinking-selector.js +15 -12
- package/dist/integrations/pi/components/upstream/components/tree-selector.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/components/tree-selector.js +13 -6
- package/dist/integrations/pi/components/upstream/components/trust-selector.js +9 -9
- package/dist/integrations/pi/engine/compaction-progress.d.ts +2 -6
- package/dist/integrations/pi/engine/compaction-progress.js +10 -10
- package/dist/integrations/pi/engine/settings-bridge.js +1 -1
- package/dist/integrations/pi/engine/settings-port.js +1 -1
- package/dist/integrations/pi/tui-runtime/adapter.js +6 -2
- package/dist/integrations/pi/tui-runtime/contracts.d.ts +2 -0
- package/dist/integrations/pi/tui-runtime/mouse-report-input.d.ts +6 -0
- package/dist/integrations/pi/tui-runtime/mouse-report-input.js +34 -0
- package/dist/native/darwin-arm64/manifest.json +1 -1
- package/dist/native/linux-x64/manifest.json +1 -1
- package/dist/native/win32-x64/manifest.json +2 -2
- package/dist/native/win32-x64/process-guardian.exe +0 -0
- package/dist/ui/components/dialog-panel.js +10 -6
- package/dist/ui/components/mouse.d.ts +2 -2
- package/dist/ui/components/mouse.js +2 -2
- package/dist/ui/components/transcript-viewport.js +35 -13
- package/package.json +1 -1
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import { randomBytes } from "node:crypto";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { canonicalizeClipboardImage } from "./clipboard-image.js";
|
|
4
|
-
import { assertImageEncodedSize, assertPromptImages, ImageAttachmentError } from "../../contracts/owned-ui/index.js";
|
|
4
|
+
import { assertImageEncodedSize, assertPromptImages, canonicalPromptChipMatches, ImageAttachmentError, replaceCanonicalPromptChips, } from "../../contracts/owned-ui/index.js";
|
|
5
5
|
import { ImagePreparationClient } from "./image-preparation-client.js";
|
|
6
6
|
import { preparePasteText } from "./paste-text-preparation.js";
|
|
7
7
|
import { pathChipTag } from "./path-chip-presentation.js";
|
|
8
8
|
import { PastePreparationClient } from "./paste-preparation-client.js";
|
|
9
|
-
const CHIP_PATTERN = /\[(?:paste #\d+ (?:\+\d+ lines|\d+ chars)|📷 [^\]]+|📁 [^\]]+|📄 [^\]]+|🖼 {1,2}[^\]]+|🔗 [^\]]+)\]/gu;
|
|
10
9
|
const URL_SUBSTRING_PATTERN = /https?:\/\/[^\s\u0000-\u001f\u007f\]]+/giu;
|
|
11
10
|
const IMAGE_CHIP_IDENTIFIER_PATTERN = /^\[📷 screenshot-([a-f0-9]+)(?:-resized)?\]$/u;
|
|
12
11
|
const URL_DISPLAY_LENGTH = 40;
|
|
@@ -259,10 +258,9 @@ export class PromptChipStore {
|
|
|
259
258
|
return ranges.sort((left, right) => left.start - right.start);
|
|
260
259
|
}
|
|
261
260
|
atomicRanges(line) {
|
|
262
|
-
return
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
}));
|
|
261
|
+
return canonicalPromptChipMatches(line)
|
|
262
|
+
.filter(match => !match.text.startsWith("[paste #") || this.#chips.has(match.text))
|
|
263
|
+
.map(match => ({ start: match.start, end: match.end }));
|
|
266
264
|
}
|
|
267
265
|
hyperlinkRanges(text) {
|
|
268
266
|
const ranges = [];
|
|
@@ -306,8 +304,8 @@ export class PromptChipStore {
|
|
|
306
304
|
imageChipAttachments(text) {
|
|
307
305
|
const results = [];
|
|
308
306
|
const seen = new Set();
|
|
309
|
-
for (const match of text
|
|
310
|
-
const tag = match
|
|
307
|
+
for (const match of canonicalPromptChipMatches(text)) {
|
|
308
|
+
const tag = match.text;
|
|
311
309
|
if (seen.has(tag))
|
|
312
310
|
continue;
|
|
313
311
|
const chip = this.#chips.get(tag);
|
|
@@ -338,39 +336,32 @@ export class PromptChipStore {
|
|
|
338
336
|
// Rationale: resolve image chip tags in-place before further classification. Sidecar hits
|
|
339
337
|
// register a live image chip; misses silently strip the tag (no placeholder, no notice) per
|
|
340
338
|
// the durability contract.
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
const identifier = imageChipIdentifier(tag);
|
|
339
|
+
const resolvedImages = replaceCanonicalPromptChips(text, match => {
|
|
340
|
+
const identifier = imageChipIdentifier(match.text);
|
|
344
341
|
if (identifier === null)
|
|
345
|
-
return
|
|
342
|
+
return match.text;
|
|
346
343
|
const attachment = resolveImage(identifier);
|
|
347
344
|
if (attachment === null)
|
|
348
345
|
return "";
|
|
349
|
-
this.#chips.set(
|
|
350
|
-
return
|
|
346
|
+
this.#chips.set(match.text, { kind: "image", tag: match.text, image: attachment });
|
|
347
|
+
return match.text;
|
|
351
348
|
});
|
|
352
349
|
// Rationale: split around any surviving image tags so paste-time classification runs on the
|
|
353
350
|
// intervening prose exactly as it would on a fresh clipboard payload, plus a substring URL
|
|
354
351
|
// scan so embedded links become atomic chips even in the middle of surrounding text.
|
|
355
|
-
CHIP_PATTERN.lastIndex = 0;
|
|
356
352
|
const segments = [];
|
|
357
353
|
let cursor = 0;
|
|
358
|
-
for (const match of resolvedImages
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
cursor = index + match[0].length;
|
|
354
|
+
for (const match of canonicalPromptChipMatches(resolvedImages)) {
|
|
355
|
+
if (match.start > cursor)
|
|
356
|
+
segments.push(resolvedImages.slice(cursor, match.start));
|
|
357
|
+
segments.push(match.text);
|
|
358
|
+
cursor = match.end;
|
|
364
359
|
}
|
|
365
360
|
if (cursor < resolvedImages.length)
|
|
366
361
|
segments.push(resolvedImages.slice(cursor));
|
|
367
|
-
CHIP_PATTERN.lastIndex = 0;
|
|
368
362
|
return segments.map(segment => {
|
|
369
|
-
if (segment.length === 0 ||
|
|
370
|
-
CHIP_PATTERN.lastIndex = 0;
|
|
363
|
+
if (segment.length === 0 || canonicalPromptChipMatches(segment).length > 0)
|
|
371
364
|
return segment;
|
|
372
|
-
}
|
|
373
|
-
CHIP_PATTERN.lastIndex = 0;
|
|
374
365
|
// Invariant: preserve original whitespace framing so the concatenated recall value
|
|
375
366
|
// remains stable across rehydration.
|
|
376
367
|
const leading = segment.match(/^\s+/)?.[0] ?? "";
|
|
@@ -434,7 +425,8 @@ export class PromptChipStore {
|
|
|
434
425
|
return chip.kind === "url" ? chip.url : chip.path;
|
|
435
426
|
};
|
|
436
427
|
// Invariant: scan only draft tokens (and resolved reservation tokens), never emitted payloads.
|
|
437
|
-
const expanded = text
|
|
428
|
+
const expanded = replaceCanonicalPromptChips(text, match => {
|
|
429
|
+
const tag = match.text;
|
|
438
430
|
const entry = this.#pending.get(tag) ?? this.#pending.get(tag.replace("failed-", "screenshot-"));
|
|
439
431
|
if (entry === undefined)
|
|
440
432
|
return resolveChip(tag);
|
|
@@ -442,7 +434,7 @@ export class PromptChipStore {
|
|
|
442
434
|
throw entry.error;
|
|
443
435
|
if (includeImages && entry.replacement === undefined)
|
|
444
436
|
throw new ImageAttachmentError("image-pending");
|
|
445
|
-
return entry.replacement === undefined ? tag : entry.replacement
|
|
437
|
+
return entry.replacement === undefined ? tag : replaceCanonicalPromptChips(entry.replacement, replacement => resolveChip(replacement.text));
|
|
446
438
|
});
|
|
447
439
|
return { text: expanded, images };
|
|
448
440
|
}
|
|
@@ -229,6 +229,8 @@ export declare class OwnedUiSessionShellRoot implements PiTuiComponentPort {
|
|
|
229
229
|
viewportTransientTailRowCount(): number;
|
|
230
230
|
hasActiveSelection(): boolean;
|
|
231
231
|
setViewportConfig(config: OwnedUiViewportSettings): void;
|
|
232
|
+
setFullscreenCopyOnSelect(enabled: boolean): void;
|
|
233
|
+
showCopyAcknowledgement(message: string): void;
|
|
232
234
|
resumeViewportFollowing(): void;
|
|
233
235
|
noteCompletedAssistantMessage(): void;
|
|
234
236
|
/** A new session binding owns new mounts even when it reuses semantic invocation ids. */
|
|
@@ -62,6 +62,8 @@ export class OwnedUiSessionShellRoot {
|
|
|
62
62
|
#lastWorkflowStatusId;
|
|
63
63
|
// Invariant: the notice is dock chrome, never transcript content; the custom viewport alone uses it.
|
|
64
64
|
#dockNotice;
|
|
65
|
+
#copyAcknowledgement;
|
|
66
|
+
#copyAcknowledgementTimer;
|
|
65
67
|
#inputSurface;
|
|
66
68
|
#inputSurfaceCoordination = "editor";
|
|
67
69
|
#dockInputReuseEnabled;
|
|
@@ -374,13 +376,14 @@ export class OwnedUiSessionShellRoot {
|
|
|
374
376
|
}
|
|
375
377
|
const height = Math.max(0, this.#componentRuntime.getRows());
|
|
376
378
|
const dock = this.#renderDockLayout(width);
|
|
377
|
-
// Invariant:
|
|
378
|
-
//
|
|
379
|
-
|
|
380
|
-
|
|
379
|
+
// Invariant: auto/always own one stable final-column gutter. Every scrollable
|
|
380
|
+
// surface ends before it; the dock remains full-width and hidden mode returns it.
|
|
381
|
+
const documentWidth = this.#viewportController.config.scrollbarAppearance === "hidden"
|
|
382
|
+
? width
|
|
383
|
+
: Math.max(1, width - 1);
|
|
381
384
|
const document = this.#renderDocumentLayout(documentWidth);
|
|
382
|
-
const steeringRows = this.#renderQueued(
|
|
383
|
-
const statusRows = this.#renderStatus(
|
|
385
|
+
const steeringRows = this.#renderQueued(documentWidth);
|
|
386
|
+
const statusRows = this.#renderStatus(documentWidth);
|
|
384
387
|
const transientSignature = transientRowsSignature(steeringRows, statusRows);
|
|
385
388
|
const snapshot = this.#visibleViewportSnapshot;
|
|
386
389
|
const dockInputCandidate = this.#dockInputCandidate;
|
|
@@ -497,6 +500,25 @@ export class OwnedUiSessionShellRoot {
|
|
|
497
500
|
if (this.#viewportController.setConfig(config))
|
|
498
501
|
this.#documentLayouts.clear();
|
|
499
502
|
}
|
|
503
|
+
setFullscreenCopyOnSelect(enabled) {
|
|
504
|
+
this.#viewportController.setCopyOnSelect(enabled);
|
|
505
|
+
}
|
|
506
|
+
showCopyAcknowledgement(message) {
|
|
507
|
+
this.#clearCopyAcknowledgement(false);
|
|
508
|
+
this.#copyAcknowledgement = message;
|
|
509
|
+
const timer = setTimeout(() => {
|
|
510
|
+
if (this.#copyAcknowledgementTimer !== timer)
|
|
511
|
+
return;
|
|
512
|
+
this.#copyAcknowledgementTimer = undefined;
|
|
513
|
+
this.#copyAcknowledgement = undefined;
|
|
514
|
+
this.#invalidateChrome();
|
|
515
|
+
this.#componentRuntime.requestRender();
|
|
516
|
+
}, COPY_ACKNOWLEDGEMENT_DURATION_MS);
|
|
517
|
+
timer.unref?.();
|
|
518
|
+
this.#copyAcknowledgementTimer = timer;
|
|
519
|
+
this.#invalidateChrome();
|
|
520
|
+
this.#componentRuntime.requestRender();
|
|
521
|
+
}
|
|
500
522
|
resumeViewportFollowing() {
|
|
501
523
|
this.#viewportController.resumeFollowing();
|
|
502
524
|
}
|
|
@@ -537,18 +559,36 @@ export class OwnedUiSessionShellRoot {
|
|
|
537
559
|
const statusRows = this.#customViewport ? this.#status.renderDock(width) : this.#renderStatus(width);
|
|
538
560
|
const transientRows = [...queued, ...statusRows, ...this.#renderDockNotice(width)];
|
|
539
561
|
const aboveWidgets = this.#renderWidgets("aboveEditor", width);
|
|
562
|
+
const copyAcknowledgement = this.#renderCopyAcknowledgement(width);
|
|
540
563
|
const input = this.#inputSurface.render(width);
|
|
541
564
|
// Invariant: pointer rows describe the body, not the autocomplete block now preceding it.
|
|
542
565
|
const body = this.usesDefaultInputSurface() ? this.editor.bodyGeometry?.() : undefined;
|
|
543
566
|
const belowWidgets = this.#renderWidgets("belowEditor", width);
|
|
544
567
|
const footer = this.#renderFooter(width);
|
|
545
|
-
const rowsWithoutTransient = [...aboveWidgets, ...input, ...belowWidgets, ...footer];
|
|
568
|
+
const rowsWithoutTransient = [...aboveWidgets, ...copyAcknowledgement, ...input, ...belowWidgets, ...footer];
|
|
546
569
|
return {
|
|
547
570
|
rows: [...transientRows, ...rowsWithoutTransient],
|
|
548
|
-
editorOffset: transientRows.length + aboveWidgets.length + (body?.rowOffset ?? 0),
|
|
571
|
+
editorOffset: transientRows.length + aboveWidgets.length + copyAcknowledgement.length + (body?.rowOffset ?? 0),
|
|
549
572
|
inputRows: body?.rowCount ?? input.length,
|
|
550
573
|
};
|
|
551
574
|
}
|
|
575
|
+
#renderCopyAcknowledgement(width) {
|
|
576
|
+
if (this.#copyAcknowledgement === undefined || width <= 0)
|
|
577
|
+
return [];
|
|
578
|
+
const text = piShellTruncateToWidth(this.#copyAcknowledgement, width);
|
|
579
|
+
return [`${" ".repeat(Math.max(0, width - piShellVisibleWidth(text)))}${piTheme().fg("accent", text)}`];
|
|
580
|
+
}
|
|
581
|
+
#clearCopyAcknowledgement(requestRender) {
|
|
582
|
+
if (this.#copyAcknowledgementTimer !== undefined)
|
|
583
|
+
clearTimeout(this.#copyAcknowledgementTimer);
|
|
584
|
+
this.#copyAcknowledgementTimer = undefined;
|
|
585
|
+
if (this.#copyAcknowledgement === undefined)
|
|
586
|
+
return;
|
|
587
|
+
this.#copyAcknowledgement = undefined;
|
|
588
|
+
this.#invalidateChrome();
|
|
589
|
+
if (requestRender)
|
|
590
|
+
this.#componentRuntime.requestRender();
|
|
591
|
+
}
|
|
552
592
|
#renderDockNotice(width) {
|
|
553
593
|
const notice = this.#dockNotice;
|
|
554
594
|
if (notice === undefined)
|
|
@@ -654,12 +694,9 @@ export class OwnedUiSessionShellRoot {
|
|
|
654
694
|
continue;
|
|
655
695
|
const promptLike = block !== undefined
|
|
656
696
|
&& (block.kind === "user" || this.#customViewport && isPiPromptStyleCompaction(block));
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
&& width > 1
|
|
661
|
-
? width - 1
|
|
662
|
-
: width;
|
|
697
|
+
// Invariant: the custom viewport has already removed its rail gutter from this width.
|
|
698
|
+
// Prompt-like and ordinary blocks therefore share one right content edge.
|
|
699
|
+
const blockWidth = width;
|
|
663
700
|
const blockRows = this.#blockRows(id, block, blockWidth);
|
|
664
701
|
if (promptLike) {
|
|
665
702
|
// Compatibility: the first natural prompt gets one breathing row at the document top.
|
|
@@ -930,6 +967,7 @@ export class OwnedUiSessionShellRoot {
|
|
|
930
967
|
}
|
|
931
968
|
resetWorkflowPresentation() {
|
|
932
969
|
this.#dockNotice = undefined;
|
|
970
|
+
this.#clearCopyAcknowledgement(false);
|
|
933
971
|
for (const id of this.#workflowStatusAnchors.keys()) {
|
|
934
972
|
this.#transcript.get(id)?.dispose?.();
|
|
935
973
|
this.#transcript.delete(id);
|
|
@@ -1038,6 +1076,7 @@ export class OwnedUiSessionShellRoot {
|
|
|
1038
1076
|
this.#inputSurface.setFocused?.(focused);
|
|
1039
1077
|
}
|
|
1040
1078
|
dispose() {
|
|
1079
|
+
this.#clearCopyAcknowledgement(false);
|
|
1041
1080
|
this.clearViewportPointerState();
|
|
1042
1081
|
this.#themeUnsubscribe();
|
|
1043
1082
|
this.header.dispose?.();
|
|
@@ -1189,6 +1228,7 @@ export class OwnedUiSessionShellRoot {
|
|
|
1189
1228
|
this.invalidate();
|
|
1190
1229
|
}
|
|
1191
1230
|
}
|
|
1231
|
+
const COPY_ACKNOWLEDGEMENT_DURATION_MS = 1_000;
|
|
1192
1232
|
const SCROLLBAR_CELL_RESET = "\u001b[22;23;24;25;27;28;29;39;54;55m";
|
|
1193
1233
|
const KITTY_IMAGE_CONTROL = /\u001b_G[\s\S]*?\u001b\\/g;
|
|
1194
1234
|
const ITERM_IMAGE_CONTROL = /\u001b]1337;File=[^\u0007]*(?:\u0007|\u001b\\)/g;
|
|
@@ -61,6 +61,7 @@ export class OwnedUiSessionShell {
|
|
|
61
61
|
#responseCopy;
|
|
62
62
|
// Rationale: only an executor this shell created owns a spare copy helper worth warming and disposing.
|
|
63
63
|
#copyExecutor;
|
|
64
|
+
#copyIntentSequence = 0;
|
|
64
65
|
#unbindClipboardWriter;
|
|
65
66
|
#damageTerminal;
|
|
66
67
|
#quitOutro;
|
|
@@ -235,6 +236,10 @@ export class OwnedUiSessionShell {
|
|
|
235
236
|
root: this.root,
|
|
236
237
|
mode: tuiMode,
|
|
237
238
|
...(this.#customViewport ? {
|
|
239
|
+
// Invariant: the owned shell enables and routes mouse reports itself. Pi's
|
|
240
|
+
// enclosing fullscreen renderer must never establish a competing white selection.
|
|
241
|
+
mouse: false,
|
|
242
|
+
consumeUnhandledMouse: true,
|
|
238
243
|
onOverlayGeometry: surfaces => this.root.setViewportOverlaySurfaces(surfaces),
|
|
239
244
|
decorateTerminal: (terminal) => {
|
|
240
245
|
damageTerminal = new DamageAwareTerminalAdapter(terminal, {
|
|
@@ -357,11 +362,15 @@ export class OwnedUiSessionShell {
|
|
|
357
362
|
const routed = this.root.handleViewportPreInput(data, true, Date.now(), this.root.usesDefaultInputSurface() && !this.runtime.hasFocusedOverlay());
|
|
358
363
|
if (routed.copySelection !== undefined) {
|
|
359
364
|
const snapshot = routed.copySelection;
|
|
365
|
+
const acknowledgement = copyAcknowledgement(snapshot);
|
|
366
|
+
const intent = ++this.#copyIntentSequence;
|
|
360
367
|
void this.#responseCopy?.submit(snapshot, pendingClipboardWrite).then(result => {
|
|
361
|
-
if (this.#disposed || !runtime.active
|
|
368
|
+
if (this.#disposed || !runtime.active || intent !== this.#copyIntentSequence
|
|
362
369
|
|| result.outcome !== "delivered" && result.outcome !== "submitted-unverified")
|
|
363
370
|
return;
|
|
364
|
-
|
|
371
|
+
if (acknowledgement.hasNonWhitespace) {
|
|
372
|
+
this.root.showCopyAcknowledgement(`copied ${acknowledgement.characters} chars to clipboard`);
|
|
373
|
+
}
|
|
365
374
|
});
|
|
366
375
|
}
|
|
367
376
|
if (!routed.consumed)
|
|
@@ -384,6 +393,7 @@ export class OwnedUiSessionShell {
|
|
|
384
393
|
this.root.setEditorPaddingX(initialPiSettings.editorPaddingX);
|
|
385
394
|
this.root.setAutocompleteMaxVisible(initialPiSettings.autocompleteMaxVisible);
|
|
386
395
|
this.root.setOutputPad(initialPiSettings.outputPad);
|
|
396
|
+
this.root.setFullscreenCopyOnSelect(initialPiSettings.fullscreenCopyOnSelect);
|
|
387
397
|
this.root.setHideThinkingBlock(initialPiSettings.hideThinkingBlock);
|
|
388
398
|
this.root.setMermaidRenderingMode(initialPiSettings.mermaidRenderingMode);
|
|
389
399
|
this.#showImages = initialPiSettings.showImages;
|
|
@@ -427,6 +437,11 @@ export class OwnedUiSessionShell {
|
|
|
427
437
|
this.#imageWidthCells = value;
|
|
428
438
|
this.root.setImagePresentation(this.#showImages, this.#imageWidthCells);
|
|
429
439
|
} },
|
|
440
|
+
...(this.#customViewport ? { fullscreenCopyOnSelect: { apply: (value) => {
|
|
441
|
+
if (typeof value !== "boolean")
|
|
442
|
+
throw new TypeError("Fullscreen copy-on-select setting is invalid");
|
|
443
|
+
this.root.setFullscreenCopyOnSelect(value);
|
|
444
|
+
} } } : {}),
|
|
430
445
|
});
|
|
431
446
|
if (this.#customViewport && promptHistory !== undefined) {
|
|
432
447
|
this.#promptHistoryImageSidecar = promptHistory.imageSidecar;
|
|
@@ -1522,6 +1537,7 @@ export class OwnedUiSessionShell {
|
|
|
1522
1537
|
}
|
|
1523
1538
|
async #dispose() {
|
|
1524
1539
|
this.#disposed = true;
|
|
1540
|
+
this.#copyIntentSequence += 1;
|
|
1525
1541
|
// Invariant: the outro frame is what the terminal shows now, before any cleanup writes.
|
|
1526
1542
|
const outroFrame = this.#captureQuitOutroFrame();
|
|
1527
1543
|
this.#responseCopy?.dispose();
|
|
@@ -1662,6 +1678,7 @@ export class OwnedUiSessionShell {
|
|
|
1662
1678
|
this.#streamPresentation.noteImmediatePresentation();
|
|
1663
1679
|
const view = this.view();
|
|
1664
1680
|
if (this.backend.sessionGeneration !== this.#sessionGeneration) {
|
|
1681
|
+
this.#copyIntentSequence += 1;
|
|
1665
1682
|
this.#responseCopy?.reset();
|
|
1666
1683
|
this.#cancelWaitingImages();
|
|
1667
1684
|
this.root.resetPendingPastes();
|
|
@@ -2078,6 +2095,17 @@ export class OwnedUiSessionShell {
|
|
|
2078
2095
|
function isCoalescedStreamBlock(kind) {
|
|
2079
2096
|
return kind === "assistant" || kind === "thinking" || kind === "tool-call" || kind === "tool-result";
|
|
2080
2097
|
}
|
|
2098
|
+
function copyAcknowledgement(snapshot) {
|
|
2099
|
+
if (snapshot.literal !== true)
|
|
2100
|
+
return { characters: snapshot.sourceUnits, hasNonWhitespace: true };
|
|
2101
|
+
let characters = Math.max(0, snapshot.rows.length - 1);
|
|
2102
|
+
let hasNonWhitespace = false;
|
|
2103
|
+
for (const row of snapshot.rows) {
|
|
2104
|
+
characters += row.text.length;
|
|
2105
|
+
hasNonWhitespace ||= /\S/u.test(row.text);
|
|
2106
|
+
}
|
|
2107
|
+
return { characters, hasNonWhitespace };
|
|
2108
|
+
}
|
|
2081
2109
|
export function formatSessionResumeCommand(metadata) {
|
|
2082
2110
|
const args = [PRODUCT_TEXT.commandName];
|
|
2083
2111
|
if (!metadata.usesDefaultSessionDir)
|
|
@@ -39,6 +39,7 @@ export declare class SessionViewportController {
|
|
|
39
39
|
readonly column: number;
|
|
40
40
|
readonly row: number;
|
|
41
41
|
} | undefined;
|
|
42
|
+
setCopyOnSelect(enabled: boolean): void;
|
|
42
43
|
get frame(): TranscriptViewportFrame | null;
|
|
43
44
|
setEditorPointerFrame(frame: {
|
|
44
45
|
readonly rowStart: number;
|
|
@@ -44,6 +44,7 @@ export class SessionViewportController {
|
|
|
44
44
|
#hoveredHyperlinkKey;
|
|
45
45
|
#lastRequestedSelectionRevision = -1;
|
|
46
46
|
#presentationRevision = 0;
|
|
47
|
+
#copyOnSelect = true;
|
|
47
48
|
#activityTimer;
|
|
48
49
|
constructor(options) {
|
|
49
50
|
this.#enabled = options.enabled;
|
|
@@ -79,6 +80,9 @@ export class SessionViewportController {
|
|
|
79
80
|
get pointerPosition() {
|
|
80
81
|
return this.#pointerPosition;
|
|
81
82
|
}
|
|
83
|
+
setCopyOnSelect(enabled) {
|
|
84
|
+
this.#copyOnSelect = enabled;
|
|
85
|
+
}
|
|
82
86
|
get frame() {
|
|
83
87
|
return this.#viewport.frame;
|
|
84
88
|
}
|
|
@@ -504,7 +508,7 @@ export class SessionViewportController {
|
|
|
504
508
|
this.#editor.handlePointer({ kind: "press", button: 0, column: pendingEditorClick.column, row: editorRow });
|
|
505
509
|
this.#editor.handlePointer({ kind: "release", button: 0, column: pendingEditorClick.column, row: editorRow });
|
|
506
510
|
}
|
|
507
|
-
else {
|
|
511
|
+
else if (this.#copyOnSelect) {
|
|
508
512
|
completedCopy = this.#viewport.captureSelectedText() ?? undefined;
|
|
509
513
|
}
|
|
510
514
|
// Platform: restore OSC 8 links only after the held-button selection paint has
|
|
@@ -5,6 +5,8 @@ export type { OwnedUiBlockStatus, OwnedUiCommand, OwnedUiCommandOutcome, OwnedUi
|
|
|
5
5
|
export { acceptsTranscriptUpdate, transcriptToolState } from "./transcript-lifecycle.js";
|
|
6
6
|
export { ImageAttachmentError, MAX_CLIPBOARD_TEXT_BYTES, MAX_IMAGE_DATA_BYTES, MAX_PROMPT_IMAGES, assertImageEncodedSize, assertPromptImages, } from "./image-attachments.js";
|
|
7
7
|
export { CONTEXTUAL_PROMPT_SUGGESTION_INSTRUCTION, normalizePromptSuggestionCandidate } from "./prompt-suggestions.js";
|
|
8
|
+
export { canonicalPromptChipMatches, protectPromptChipWrapping, replaceCanonicalPromptChips } from "./prompt-chips.js";
|
|
9
|
+
export type { PromptChipTextMatch, PromptChipWrapProtection } from "./prompt-chips.js";
|
|
8
10
|
export { SUGGESTION_DECISION_REASONS, SUGGESTION_DIAGNOSTIC_EVENTS } from "./suggestion-diagnostics.js";
|
|
9
11
|
export type { SuggestionDecision, SuggestionDecisionReason, SuggestionDiagnosticEvent, SuggestionDiagnosticObserver, SuggestionDiagnosticRecord, } from "./suggestion-diagnostics.js";
|
|
10
12
|
export { PROMPT_HISTORY_EDITOR_REPLACEMENT, PROMPT_HISTORY_MAX_ENTRY_BYTES, PROMPT_HISTORY_MAX_PENDING, PROMPT_HISTORY_MAX_TEXT_BYTES, assertPromptHistorySubmission, } from "./prompt-history.js";
|
|
@@ -3,6 +3,7 @@ export { OWNED_UI_CONTRACT_VERSION } from "./model.js";
|
|
|
3
3
|
export { acceptsTranscriptUpdate, transcriptToolState } from "./transcript-lifecycle.js";
|
|
4
4
|
export { ImageAttachmentError, MAX_CLIPBOARD_TEXT_BYTES, MAX_IMAGE_DATA_BYTES, MAX_PROMPT_IMAGES, assertImageEncodedSize, assertPromptImages, } from "./image-attachments.js";
|
|
5
5
|
export { CONTEXTUAL_PROMPT_SUGGESTION_INSTRUCTION, normalizePromptSuggestionCandidate } from "./prompt-suggestions.js";
|
|
6
|
+
export { canonicalPromptChipMatches, protectPromptChipWrapping, replaceCanonicalPromptChips } from "./prompt-chips.js";
|
|
6
7
|
export { SUGGESTION_DECISION_REASONS, SUGGESTION_DIAGNOSTIC_EVENTS } from "./suggestion-diagnostics.js";
|
|
7
8
|
export { PROMPT_HISTORY_EDITOR_REPLACEMENT, PROMPT_HISTORY_MAX_ENTRY_BYTES, PROMPT_HISTORY_MAX_PENDING, PROMPT_HISTORY_MAX_TEXT_BYTES, assertPromptHistorySubmission, } from "./prompt-history.js";
|
|
8
9
|
export { assertOwnedUiCommand, assertOwnedUiCustomization, assertOwnedUiDiagnostics, assertOwnedUiEditorState, assertOwnedUiEvent, assertOwnedUiPromptSuggestionIdentity, assertOwnedUiPromptSuggestionRequest, assertOwnedUiPromptSuggestionResult, assertOwnedUiPromptSuggestionState, assertOwnedUiSessionViewModel, assertOwnedUiSnapshot, assertOwnedUiStatusView, assertOwnedUiTerminalSurface, assertOwnedUiTranscriptBlock, } from "./validation.js";
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export interface PromptChipTextMatch {
|
|
2
|
+
readonly text: string;
|
|
3
|
+
readonly start: number;
|
|
4
|
+
readonly end: number;
|
|
5
|
+
}
|
|
6
|
+
export interface PromptChipWrapProtection {
|
|
7
|
+
readonly text: string;
|
|
8
|
+
restore(rendered: string): string;
|
|
9
|
+
}
|
|
10
|
+
/** Finds canonical prompt-chip labels without requiring their process-local backing values. */
|
|
11
|
+
export declare function canonicalPromptChipMatches(text: string): readonly PromptChipTextMatch[];
|
|
12
|
+
/** Replaces only canonical prompt-chip labels while preserving every intervening source byte. */
|
|
13
|
+
export declare function replaceCanonicalPromptChips(text: string, replacement: (match: PromptChipTextMatch) => string): string;
|
|
14
|
+
/** Protects fitting chip labels for Markdown wrapping and restores their exact visible source text. */
|
|
15
|
+
export declare function protectPromptChipWrapping(text: string): PromptChipWrapProtection;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
const PROMPT_CHIP_PATTERN = /\[(?:paste #\d+ (?:\+\d+ lines|\d+ chars)|📷 [^\]]+|📁 [^\]]+|📄 [^\]]+|🖼 {1,2}[^\]]+|🔗 [^\]]+)\]/gu;
|
|
2
|
+
const ZERO_WIDTH_MARKERS = ["\u2060", "\u200b", "\u200c", "\u200d", "\ufeff"];
|
|
3
|
+
/** Finds canonical prompt-chip labels without requiring their process-local backing values. */
|
|
4
|
+
export function canonicalPromptChipMatches(text) {
|
|
5
|
+
return [...text.matchAll(PROMPT_CHIP_PATTERN)].map(match => ({
|
|
6
|
+
text: match[0], start: match.index, end: match.index + match[0].length,
|
|
7
|
+
}));
|
|
8
|
+
}
|
|
9
|
+
/** Replaces only canonical prompt-chip labels while preserving every intervening source byte. */
|
|
10
|
+
export function replaceCanonicalPromptChips(text, replacement) {
|
|
11
|
+
return text.replace(PROMPT_CHIP_PATTERN, (value, offset) => replacement({
|
|
12
|
+
text: value, start: offset, end: offset + value.length,
|
|
13
|
+
}));
|
|
14
|
+
}
|
|
15
|
+
/** Protects fitting chip labels for Markdown wrapping and restores their exact visible source text. */
|
|
16
|
+
export function protectPromptChipWrapping(text) {
|
|
17
|
+
const matches = canonicalPromptChipMatches(text);
|
|
18
|
+
if (matches.length === 0)
|
|
19
|
+
return { text, restore: identity };
|
|
20
|
+
const spaceMarker = unusedPrivateMarker(text);
|
|
21
|
+
const boundaryMarker = unusedZeroWidthMarker(text);
|
|
22
|
+
let protectedText = "", cursor = 0;
|
|
23
|
+
for (const match of matches) {
|
|
24
|
+
protectedText += text.slice(cursor, match.start);
|
|
25
|
+
// Rationale: adjacent chips need a temporary break opportunity or Markdown treats the complete run as one word.
|
|
26
|
+
if (match.start === cursor && cursor > 0)
|
|
27
|
+
protectedText += `${boundaryMarker} `;
|
|
28
|
+
protectedText += match.text.replaceAll(" ", spaceMarker);
|
|
29
|
+
cursor = match.end;
|
|
30
|
+
}
|
|
31
|
+
protectedText += text.slice(cursor);
|
|
32
|
+
return {
|
|
33
|
+
text: protectedText,
|
|
34
|
+
restore: rendered => rendered
|
|
35
|
+
.replaceAll(`${boundaryMarker} `, "")
|
|
36
|
+
.replaceAll(boundaryMarker, "")
|
|
37
|
+
.replaceAll(spaceMarker, " "),
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
function unusedPrivateMarker(text) {
|
|
41
|
+
for (let codePoint = 0xe000; codePoint <= 0xf8ff; codePoint++) {
|
|
42
|
+
const marker = String.fromCodePoint(codePoint);
|
|
43
|
+
if (!text.includes(marker))
|
|
44
|
+
return marker;
|
|
45
|
+
}
|
|
46
|
+
throw new Error("Prompt chip text exhausts the private presentation marker range");
|
|
47
|
+
}
|
|
48
|
+
function unusedZeroWidthMarker(text) {
|
|
49
|
+
for (const marker of ZERO_WIDTH_MARKERS)
|
|
50
|
+
if (!text.includes(marker))
|
|
51
|
+
return marker;
|
|
52
|
+
for (const left of ZERO_WIDTH_MARKERS) {
|
|
53
|
+
for (const right of ZERO_WIDTH_MARKERS)
|
|
54
|
+
if (!text.includes(left + right))
|
|
55
|
+
return left + right;
|
|
56
|
+
}
|
|
57
|
+
throw new Error("Prompt chip text exhausts the zero-width presentation marker range");
|
|
58
|
+
}
|
|
59
|
+
function identity(value) { return value; }
|
|
@@ -353,7 +353,7 @@ export function assertOwnedUiStatusView(status) {
|
|
|
353
353
|
assertBoundedText(status.title, "owned-UI status title", MAX_LABEL_LENGTH);
|
|
354
354
|
assertOptionalText(status.workingMessage, "owned-UI working message", MAX_MESSAGE_LENGTH);
|
|
355
355
|
if (status.workingProgress !== undefined && status.workingProgress !== null) {
|
|
356
|
-
assertIntegerInRange(status.workingProgress, 0,
|
|
356
|
+
assertIntegerInRange(status.workingProgress, 0, 100, "owned-UI working progress");
|
|
357
357
|
}
|
|
358
358
|
assertCollection(status.diagnostics, "owned-UI status diagnostics", MAX_STATUS_DIAGNOSTICS);
|
|
359
359
|
for (const diagnostic of status.diagnostics) {
|
|
@@ -10,6 +10,7 @@ export { PINNED_PI_LAYOUT, applyConfiguredPiTheme, applyPiTheme, applyPiThemeIns
|
|
|
10
10
|
export type { PiColorMode, PiTerminalTheme, PiTerminalThemeDetection, PiTerminalThemeDetector, PiThemeBackground, PiThemeResult } from "./theme.js";
|
|
11
11
|
export { OwnedPiThemeController } from "./upstream/theme/theme-controller.js";
|
|
12
12
|
export type { PiThemeRuntimePort, PiThemeSettingsPort } from "./upstream/theme/theme-controller.js";
|
|
13
|
+
export { PI_MODAL_CONTENT_PADDING_X, PiModalFrame, PiModalHeader, addPiModalHeader, adoptPiModalFrame, adoptPiModalHeader, } from "./modal-frame.js";
|
|
13
14
|
export { ModelsDialogComponent } from "./models-dialog.js";
|
|
14
15
|
export type { ModelsDialogCallbacks, ModelsDialogConfig, ModelsDialogFilter, ModelsDialogModel } from "./models-dialog.js";
|
|
15
16
|
export { CountdownTimer } from "./upstream/components/countdown-timer.js";
|
|
@@ -5,6 +5,7 @@ export { OWNED_BUILTIN_SLASH_COMMANDS, PINNED_PI_BUILTIN_SLASH_COMMANDS, compone
|
|
|
5
5
|
export { renderPiShellHotkeySections } from "./shell-hotkey-sections.js";
|
|
6
6
|
export { PINNED_PI_LAYOUT, applyConfiguredPiTheme, applyPiTheme, applyPiThemeInstance, currentPiThemeName, detectPiTerminalBackgroundFromEnv, detectPiTerminalBackgroundTheme, detectPiTerminalThemeForAuto, ensurePiTheme, getAvailablePiThemes, loadPiTheme, onPiThemeChange, parsePiAutoThemeSetting, piTheme, resolvePiThemeSetting, stopPiThemeWatcher, } from "./theme.js";
|
|
7
7
|
export { OwnedPiThemeController } from "./upstream/theme/theme-controller.js";
|
|
8
|
+
export { PI_MODAL_CONTENT_PADDING_X, PiModalFrame, PiModalHeader, addPiModalHeader, adoptPiModalFrame, adoptPiModalHeader, } from "./modal-frame.js";
|
|
8
9
|
export { ModelsDialogComponent } from "./models-dialog.js";
|
|
9
10
|
export { CountdownTimer } from "./upstream/components/countdown-timer.js";
|
|
10
11
|
export { ExtensionEditorComponent } from "./upstream/components/extension-editor.js";
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { Container, type Component } from "@earendil-works/pi-tui";
|
|
2
|
+
export declare const PI_MODAL_CONTENT_PADDING_X = 1;
|
|
3
|
+
/** The semantic top rule/title pair used while a modal assembles its frame. */
|
|
4
|
+
export declare class PiModalHeader implements Component {
|
|
5
|
+
readonly rule: Component;
|
|
6
|
+
readonly title: Component;
|
|
7
|
+
constructor(rule: Component, title: Component);
|
|
8
|
+
invalidate(): void;
|
|
9
|
+
render(width: number): string[];
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Complete framed-modal chrome. Rules remain frame-wide; every semantic content component renders
|
|
13
|
+
* in the same one-cell-smaller rectangle and receives the same left inset.
|
|
14
|
+
*/
|
|
15
|
+
export declare class PiModalFrame implements Component {
|
|
16
|
+
#private;
|
|
17
|
+
constructor(topRule: Component, content: readonly Component[], bottomRule: Component, fullWidthContent?: readonly Component[], preInsetContent?: readonly Component[]);
|
|
18
|
+
invalidate(): void;
|
|
19
|
+
render(width: number): string[];
|
|
20
|
+
}
|
|
21
|
+
/** Add compact titled-modal chrome while a component assembles its semantic body. */
|
|
22
|
+
export declare function addPiModalHeader(container: Container, rule: Component, title: Component): PiModalHeader;
|
|
23
|
+
export interface PiModalFrameAdoption {
|
|
24
|
+
readonly topIndex: number;
|
|
25
|
+
readonly bottomIndex: number;
|
|
26
|
+
readonly header?: PiModalHeader;
|
|
27
|
+
readonly fullWidthContent?: readonly Component[];
|
|
28
|
+
/** Public pinned children that already own the same one-cell outer inset. */
|
|
29
|
+
readonly preInsetContent?: readonly Component[];
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Replace explicitly identified structural modal children with the shared padded frame. Callers
|
|
33
|
+
* identify semantic rules/components; rendered text is never inspected or rewritten.
|
|
34
|
+
*/
|
|
35
|
+
export declare function adoptPiModalFrame(container: Container, adoption: PiModalFrameAdoption): PiModalFrame;
|
|
36
|
+
/**
|
|
37
|
+
* Adopt a public component's semantic rule/title children at the bare-A1 boundary. Only an optional
|
|
38
|
+
* structural Spacer between those explicitly identified children is accepted; rendered text is
|
|
39
|
+
* never inspected or rewritten. The pinned comparison profile does not call this adapter.
|
|
40
|
+
*/
|
|
41
|
+
export declare function adoptPiModalHeader(container: Container, ruleIndex: number, titleIndex: number): PiModalHeader;
|