@timurproko/a1 0.1.8-dev.368 → 0.1.8-dev.402
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/composition/owned-ui.d.ts +2 -0
- package/dist/composition/owned-ui.js +28 -11
- package/dist/contracts/owned-ui/image-attachments.d.ts +7 -0
- package/dist/contracts/owned-ui/image-attachments.js +7 -0
- package/dist/foundation/release/dependency-certification.js +65 -7
- package/dist/integrations/pi/components/owned-editor-ux.d.ts +8 -6
- package/dist/integrations/pi/components/owned-editor-ux.js +115 -26
- package/dist/integrations/pi/components/shell-editor-autocomplete.js +4 -0
- package/dist/integrations/pi/components/shell-presenters-transcript.js +15 -5
- package/dist/integrations/pi/components/shell-shared-facade.d.ts +14 -5
- package/dist/integrations/pi/components/tool-image-presentation.d.ts +21 -0
- package/dist/integrations/pi/components/tool-image-presentation.js +86 -0
- package/dist/integrations/pi/components/tool-result-adapter.d.ts +2 -2
- package/dist/integrations/pi/components/transcript-image-resolver.d.ts +7 -0
- package/dist/integrations/pi/components/transcript-image-resolver.js +19 -0
- 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 -1
- package/dist/integrations/pi/components/upstream/components/tool-execution.d.ts +61 -0
- package/dist/integrations/pi/components/upstream/components/tool-execution.js +344 -0
- package/dist/integrations/pi/engine/adapter.d.ts +4 -1
- package/dist/integrations/pi/engine/adapter.js +66 -28
- package/dist/integrations/pi/engine/pending-delivery.d.ts +4 -0
- package/dist/integrations/pi/engine/pending-delivery.js +15 -6
- package/dist/integrations/pi/engine/tool-rendering.js +26 -3
- package/dist/integrations/pi/engine/transcript-image-assets.d.ts +11 -0
- package/dist/integrations/pi/engine/transcript-image-assets.js +86 -0
- package/dist/integrations/pi/session-ui/clipboard-diagnostics.d.ts +16 -0
- package/dist/integrations/pi/session-ui/clipboard-diagnostics.js +65 -0
- package/dist/integrations/pi/session-ui/editor-hyperlink-budget.d.ts +9 -0
- package/dist/integrations/pi/session-ui/editor-hyperlink-budget.js +23 -0
- package/dist/integrations/pi/session-ui/index.d.ts +1 -0
- package/dist/integrations/pi/session-ui/index.js +1 -0
- package/dist/integrations/pi/session-ui/paste-executor.d.ts +10 -0
- package/dist/integrations/pi/session-ui/paste-executor.js +232 -0
- package/dist/integrations/pi/session-ui/paste-helper.d.ts +1 -0
- package/dist/integrations/pi/session-ui/paste-helper.js +173 -0
- package/dist/integrations/pi/session-ui/paste-preparation-client.d.ts +16 -0
- package/dist/integrations/pi/session-ui/paste-preparation-client.js +159 -0
- package/dist/integrations/pi/session-ui/paste-protocol.d.ts +78 -0
- package/dist/integrations/pi/session-ui/paste-protocol.js +18 -0
- package/dist/integrations/pi/session-ui/paste-text-preparation.d.ts +20 -0
- package/dist/integrations/pi/session-ui/paste-text-preparation.js +134 -0
- package/dist/integrations/pi/session-ui/paste-text-worker.d.ts +1 -0
- package/dist/integrations/pi/session-ui/paste-text-worker.js +12 -0
- package/dist/integrations/pi/session-ui/path-chip-presentation.d.ts +7 -0
- package/dist/integrations/pi/session-ui/path-chip-presentation.js +24 -0
- package/dist/integrations/pi/session-ui/prompt-chips.d.ts +8 -1
- package/dist/integrations/pi/session-ui/prompt-chips.js +129 -149
- package/dist/integrations/pi/session-ui/response-copy-coordinator.d.ts +27 -0
- package/dist/integrations/pi/session-ui/response-copy-coordinator.js +189 -0
- package/dist/integrations/pi/session-ui/response-copy-helper.d.ts +1 -0
- package/dist/integrations/pi/session-ui/response-copy-helper.js +122 -0
- package/dist/integrations/pi/session-ui/response-copy-protocol.d.ts +56 -0
- package/dist/integrations/pi/session-ui/response-copy-protocol.js +5 -0
- package/dist/integrations/pi/session-ui/response-copy-transport.d.ts +31 -0
- package/dist/integrations/pi/session-ui/response-copy-transport.js +187 -0
- package/dist/integrations/pi/session-ui/session-shell-root.d.ts +15 -6
- package/dist/integrations/pi/session-ui/session-shell-root.js +25 -6
- package/dist/integrations/pi/session-ui/session-shell.js +100 -28
- package/dist/integrations/pi/session-ui/session-viewport-controller.d.ts +2 -2
- package/dist/integrations/pi/session-ui/session-viewport-controller.js +4 -3
- package/dist/integrations/pi/session-ui/system-clipboard.d.ts +2 -2
- package/dist/integrations/pi/session-ui/system-clipboard.js +26 -11
- package/dist/integrations/pi/tui-runtime/adapter.js +3 -1
- 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/product-identity.d.ts +1 -1
- package/dist/product-identity.js +1 -1
- package/dist/product-identity.json +1 -0
- package/dist/ui/components/index.d.ts +1 -0
- package/dist/ui/components/index.js +1 -0
- package/dist/ui/components/selection-copy.d.ts +24 -0
- package/dist/ui/components/selection-copy.js +39 -0
- package/dist/ui/components/spans.d.ts +5 -2
- package/dist/ui/components/spans.js +12 -3
- package/dist/ui/components/text-selection.js +2 -0
- package/dist/ui/components/text.js +1 -1
- package/dist/ui/components/transcript-viewport.d.ts +5 -0
- package/dist/ui/components/transcript-viewport.js +100 -23
- package/dist/ui/components/visible-hyperlinks.js +40 -8
- package/docs/architecture/response-copy-delivery.md +83 -0
- package/docs/architecture/tool-image-presentation.md +62 -0
- package/docs/ci-release-runbook.md +19 -7
- package/docs/openspec-archive-automation.md +124 -0
- package/package.json +2 -1
|
@@ -17,9 +17,10 @@ const GRAPHEMES = new Intl.Segmenter(undefined, { granularity: "grapheme" });
|
|
|
17
17
|
* Replaces the visible columns `[from, to)` of `line` with `span`, preserving
|
|
18
18
|
* the styling before it and re-asserting whatever was still open afterwards, so
|
|
19
19
|
* the tail keeps the colour it had. A wide character straddling either edge is
|
|
20
|
-
* replaced by spaces rather than split.
|
|
20
|
+
* replaced by spaces rather than split. Opt into the replaced cell's boundary
|
|
21
|
+
* style for background-preserving overlays; the span still owns its decoration.
|
|
21
22
|
*/
|
|
22
|
-
export function overlaySpan(line, from, to, span) {
|
|
23
|
+
export function overlaySpan(line, from, to, span, options = {}) {
|
|
23
24
|
let head = "";
|
|
24
25
|
let replay = "";
|
|
25
26
|
let tail = "";
|
|
@@ -29,11 +30,19 @@ export function overlaySpan(line, from, to, span) {
|
|
|
29
30
|
if (!token)
|
|
30
31
|
continue;
|
|
31
32
|
if (token.startsWith("")) {
|
|
33
|
+
// Protocol: image transfers are one-shot commands, not styling to replay after an overlay.
|
|
34
|
+
if (token.startsWith("\u001b_G") || token.startsWith("\u001b]1337;File=")) {
|
|
35
|
+
if (column >= to)
|
|
36
|
+
tail += token;
|
|
37
|
+
else
|
|
38
|
+
head += token;
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
32
41
|
if (column >= to) {
|
|
33
42
|
tail += token;
|
|
34
43
|
continue;
|
|
35
44
|
}
|
|
36
|
-
if (column < from) {
|
|
45
|
+
if (column < from || (options.inheritStartStyle === true && column === from)) {
|
|
37
46
|
head += token;
|
|
38
47
|
const link = HYPERLINK.exec(token);
|
|
39
48
|
if (link)
|
|
@@ -180,6 +180,8 @@ function characterClass(character) {
|
|
|
180
180
|
function indexAtDisplayBoundary(plain, column, edge) {
|
|
181
181
|
if (column <= 0)
|
|
182
182
|
return 0;
|
|
183
|
+
if (column >= Number.MAX_SAFE_INTEGER - 1)
|
|
184
|
+
return plain.length;
|
|
183
185
|
let width = 0;
|
|
184
186
|
let index = 0;
|
|
185
187
|
for (const { segment } of GRAPHEMES.segment(plain)) {
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* and counting styling escapes as visible width breaks every layout that uses
|
|
5
5
|
* colour.
|
|
6
6
|
*/
|
|
7
|
-
const ANSI_PATTERN = /\[[0-9;:?]*[ -/]*[@-~]
|
|
7
|
+
const ANSI_PATTERN = /\[[0-9;:?]*[ -/]*[@-~]|[\]_][^]*(?:|\\)|[@-Z\\-_]/g;
|
|
8
8
|
const SEGMENTER = new Intl.Segmenter(undefined, { granularity: "grapheme" });
|
|
9
9
|
const WORD_SEGMENTER = new Intl.Segmenter(undefined, { granularity: "word" });
|
|
10
10
|
/**
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type SelectionCopySnapshot } from "./selection-copy.js";
|
|
1
2
|
import { type ScrollbarAppearance, type ScrollbarGeometry, type ScrollbarStyle } from "./scrollbar.js";
|
|
2
3
|
export interface TranscriptPromptAnchor {
|
|
3
4
|
readonly id: string;
|
|
@@ -5,6 +6,8 @@ export interface TranscriptPromptAnchor {
|
|
|
5
6
|
readonly lastRow: number;
|
|
6
7
|
/** The source first row, including its submitted-prompt styling and timestamp. */
|
|
7
8
|
readonly sourceRow: string;
|
|
9
|
+
/** Optional source-styled row used only while prominent and not hovered. */
|
|
10
|
+
readonly prominentSourceRow?: string;
|
|
8
11
|
}
|
|
9
12
|
export interface TranscriptViewportConfig {
|
|
10
13
|
readonly scrollbarAppearance: ScrollbarAppearance;
|
|
@@ -152,6 +155,8 @@ export declare class TranscriptViewport {
|
|
|
152
155
|
releaseSelection(): boolean;
|
|
153
156
|
clearSelection(): boolean;
|
|
154
157
|
selectedText(): string | null;
|
|
158
|
+
/** Captures selected source references, never the entire mutable transcript or prompt graph. */
|
|
159
|
+
captureSelectedText(): SelectionCopySnapshot | null;
|
|
155
160
|
scrollBy(lines: number, now?: number): boolean;
|
|
156
161
|
scrollTo(position: number, now?: number): boolean;
|
|
157
162
|
scrollToEnd(now?: number): boolean;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
import { MAX_COPY_ROWS, MAX_COPY_SOURCE_UNITS, selectionCopyLineContent, selectionCopyRowText } from "./selection-copy.js";
|
|
1
2
|
import { isThumbRow, scrollbarGeometry, scrollbarPresentation, } from "./scrollbar.js";
|
|
2
3
|
import { backgroundSgrSpan, overlaySpan } from "./spans.js";
|
|
3
4
|
import { faint, truncateToWidth, displayWidth, stripAnsi } from "./text.js";
|
|
4
|
-
import { extendTextSelection, orderedTextSelection, pressTextSelection, releaseTextSelection, textSelectionLineExtendColumn, textSelectionPointAt, textSelectionText,
|
|
5
|
+
import { extendTextSelection, orderedTextSelection, pressTextSelection, releaseTextSelection, textSelectionLineExtendColumn, textSelectionPointAt, textSelectionText, } from "./text-selection.js";
|
|
5
6
|
const CONTROL_STYLE_RESET = "\u001b]8;;\u001b\\\u001b[0m";
|
|
6
7
|
const IDENTITY_ROW = (row) => row;
|
|
7
8
|
const DEFAULT_CONFIG = {
|
|
@@ -33,6 +34,8 @@ export class TranscriptViewport {
|
|
|
33
34
|
#selection;
|
|
34
35
|
#selectionClick;
|
|
35
36
|
#selectionRevision = 0;
|
|
37
|
+
#copyableSelection = false;
|
|
38
|
+
#copyableRow;
|
|
36
39
|
#paintedRowCache = new Map();
|
|
37
40
|
#baseRowCache = new Map();
|
|
38
41
|
#selectedRowCache = new Map();
|
|
@@ -44,6 +47,7 @@ export class TranscriptViewport {
|
|
|
44
47
|
#selectableDocumentRowCount = 0;
|
|
45
48
|
#promptAnchors = [];
|
|
46
49
|
#contentWidth = 0;
|
|
50
|
+
#selectionWidth = 0;
|
|
47
51
|
#frameId = 0;
|
|
48
52
|
#frame = null;
|
|
49
53
|
get scrollTop() { return this.#scrollTop; }
|
|
@@ -93,6 +97,7 @@ export class TranscriptViewport {
|
|
|
93
97
|
this.#selection = pressed.selection;
|
|
94
98
|
this.#selectionClick = pressed.click;
|
|
95
99
|
this.#selectionRevision += 1;
|
|
100
|
+
this.#updateCopyableSelection();
|
|
96
101
|
return true;
|
|
97
102
|
}
|
|
98
103
|
extendSelection(column, viewportRow, now = Date.now(), autoScroll = true) {
|
|
@@ -113,8 +118,8 @@ export class TranscriptViewport {
|
|
|
113
118
|
const visibleRow = clamp(viewportRow, 1, viewportHeight);
|
|
114
119
|
const line = clamp(this.#scrollTop + visibleRow - 1, 0, lastSelectableLine);
|
|
115
120
|
const targetColumn = selection.fullRow
|
|
116
|
-
? textSelectionLineExtendColumn(selection, line, this.#
|
|
117
|
-
: clamp(column, 1, this.#
|
|
121
|
+
? textSelectionLineExtendColumn(selection, line, this.#selectionWidth)
|
|
122
|
+
: clamp(column, 1, this.#selectionWidth);
|
|
118
123
|
const point = selection.fullRow
|
|
119
124
|
? { line, column: targetColumn }
|
|
120
125
|
: textSelectionPointAt(line, targetColumn, this.#documentRows[line] ?? "");
|
|
@@ -122,6 +127,7 @@ export class TranscriptViewport {
|
|
|
122
127
|
if (next !== selection) {
|
|
123
128
|
this.#selection = next;
|
|
124
129
|
this.#selectionRevision += 1;
|
|
130
|
+
this.#updateCopyableSelection();
|
|
125
131
|
}
|
|
126
132
|
return true;
|
|
127
133
|
}
|
|
@@ -136,6 +142,8 @@ export class TranscriptViewport {
|
|
|
136
142
|
if (this.#selection === undefined)
|
|
137
143
|
return false;
|
|
138
144
|
this.#selection = undefined;
|
|
145
|
+
this.#copyableSelection = false;
|
|
146
|
+
this.#copyableRow = undefined;
|
|
139
147
|
this.#selectionRevision += 1;
|
|
140
148
|
return true;
|
|
141
149
|
}
|
|
@@ -143,7 +151,39 @@ export class TranscriptViewport {
|
|
|
143
151
|
const selection = orderedTextSelection(this.#selection);
|
|
144
152
|
if (selection === undefined)
|
|
145
153
|
return null;
|
|
146
|
-
|
|
154
|
+
const endLine = Math.min(selection.end.line, this.#selectableDocumentRowCount - 1);
|
|
155
|
+
return textSelectionText({ ...selection, end: { line: endLine, column: endLine === selection.end.line ? selection.end.column : Number.MAX_SAFE_INTEGER } }, this.#documentRows, line => this.#lineContentAt(line));
|
|
156
|
+
}
|
|
157
|
+
/** Captures selected source references, never the entire mutable transcript or prompt graph. */
|
|
158
|
+
captureSelectedText() {
|
|
159
|
+
if (!this.#copyableSelection)
|
|
160
|
+
return null;
|
|
161
|
+
const started = performance.now();
|
|
162
|
+
const selection = orderedTextSelection(this.#selection);
|
|
163
|
+
if (selection === undefined)
|
|
164
|
+
return null;
|
|
165
|
+
const start = selection.start.line;
|
|
166
|
+
const end = Math.min(selection.end.line, this.#selectableDocumentRowCount - 1, this.#documentRows.length - 1);
|
|
167
|
+
if (end < start)
|
|
168
|
+
return null;
|
|
169
|
+
const normalized = {
|
|
170
|
+
start: { line: 0, column: selection.start.column },
|
|
171
|
+
end: { line: end - start, column: end === selection.end.line ? selection.end.column : Number.MAX_SAFE_INTEGER },
|
|
172
|
+
};
|
|
173
|
+
const rejected = () => ({ selection: normalized, revision: this.#selectionRevision, rows: [], sourceUnits: 0, captureMs: performance.now() - started, rejected: "size" });
|
|
174
|
+
if (end - start + 1 > MAX_COPY_ROWS)
|
|
175
|
+
return rejected();
|
|
176
|
+
const rows = [];
|
|
177
|
+
let sourceUnits = 0;
|
|
178
|
+
for (let line = start; line <= end; line++) {
|
|
179
|
+
const text = this.#documentRows[line] ?? "";
|
|
180
|
+
sourceUnits += text.length;
|
|
181
|
+
if (sourceUnits > MAX_COPY_SOURCE_UNITS)
|
|
182
|
+
return rejected();
|
|
183
|
+
const prompt = this.#promptKindAt(line);
|
|
184
|
+
rows.push({ text, ...(prompt === undefined ? {} : { prompt }) });
|
|
185
|
+
}
|
|
186
|
+
return { selection: normalized, revision: this.#selectionRevision, rows, sourceUnits, captureMs: performance.now() - started };
|
|
147
187
|
}
|
|
148
188
|
scrollBy(lines, now = Date.now()) {
|
|
149
189
|
if (this.#maxScroll <= 0 || lines === 0)
|
|
@@ -214,6 +254,8 @@ export class TranscriptViewport {
|
|
|
214
254
|
if (this.#selection !== undefined)
|
|
215
255
|
this.#selectionRevision += 1;
|
|
216
256
|
this.#selection = undefined;
|
|
257
|
+
this.#copyableSelection = false;
|
|
258
|
+
this.#copyableRow = undefined;
|
|
217
259
|
this.#selectionClick = undefined;
|
|
218
260
|
this.#paintedRowCache.clear();
|
|
219
261
|
this.#baseRowCache.clear();
|
|
@@ -224,6 +266,7 @@ export class TranscriptViewport {
|
|
|
224
266
|
this.#selectableDocumentRowCount = 0;
|
|
225
267
|
this.#promptAnchors = [];
|
|
226
268
|
this.#contentWidth = 0;
|
|
269
|
+
this.#selectionWidth = 0;
|
|
227
270
|
this.clearTransient();
|
|
228
271
|
this.#frame = null;
|
|
229
272
|
}
|
|
@@ -282,6 +325,10 @@ export class TranscriptViewport {
|
|
|
282
325
|
this.#selectableDocumentRowCount = clamp(input.selectableDocumentRowCount ?? input.documentRows.length, 0, input.documentRows.length);
|
|
283
326
|
this.#promptAnchors = input.promptAnchors;
|
|
284
327
|
this.#contentWidth = contentWidth;
|
|
328
|
+
// Invariant: controls reserve the rail column, but source text beneath it
|
|
329
|
+
// remains reachable by a drag already owned by transcript selection.
|
|
330
|
+
this.#selectionWidth = width;
|
|
331
|
+
this.#updateCopyableSelection();
|
|
285
332
|
const paintDocumentRow = input.paintDocumentRow ?? IDENTITY_ROW;
|
|
286
333
|
const cacheLimit = Math.max(32, viewportHeight * 6);
|
|
287
334
|
trimCache(this.#paintedRowCache, cacheLimit);
|
|
@@ -304,10 +351,13 @@ export class TranscriptViewport {
|
|
|
304
351
|
const stickyActive = governing !== null && governing.firstRow < this.#scrollTop;
|
|
305
352
|
if (stickyActive && visible.length > 0) {
|
|
306
353
|
const quiet = this.#scrollTop > governing.lastRow;
|
|
307
|
-
// Compatibility:
|
|
308
|
-
//
|
|
309
|
-
//
|
|
310
|
-
const
|
|
354
|
+
// Compatibility: only non-hovered prominent rows may retain source metadata roles.
|
|
355
|
+
// Quiet and hover keep the established full-row styling; anchors without a
|
|
356
|
+
// variant retain their existing presentation in every state.
|
|
357
|
+
const sourceRow = !quiet && !this.#stickyHovered
|
|
358
|
+
? governing.prominentSourceRow ?? governing.sourceRow
|
|
359
|
+
: governing.sourceRow;
|
|
360
|
+
const source = cachedString(this.#paintedRowCache, `${paintId}\u0000${sourceRow}`, cacheLimit, () => paintDocumentRow(sourceRow));
|
|
311
361
|
if (!source.reused)
|
|
312
362
|
paintRecomputedRows.add(0);
|
|
313
363
|
const sticky = theme.sticky(source.value, this.#stickyHovered);
|
|
@@ -345,7 +395,7 @@ export class TranscriptViewport {
|
|
|
345
395
|
const glyph = thumb ? presentation.thumbGlyph : presentation.trackGlyph;
|
|
346
396
|
railCell = thumb ? theme.thumb(glyph, this.#railHovered || this.#railDragging) : theme.track(glyph);
|
|
347
397
|
}
|
|
348
|
-
const final = cachedString(this.#finalRowCache, `${width}\u0000${railCell}\u0000${selected.value}`, cacheLimit, () => railCell.length === 0 ? selected.value : overlaySpan(selected.value, width - 1, width, railCell));
|
|
398
|
+
const final = cachedString(this.#finalRowCache, `${width}\u0000${railCell}\u0000${selected.value}`, cacheLimit, () => railCell.length === 0 ? selected.value : overlaySpan(selected.value, width - 1, width, railCell, { inheritStartStyle: true }));
|
|
349
399
|
rowRecomputed ||= !final.reused;
|
|
350
400
|
visible[row] = final.value;
|
|
351
401
|
(rowRecomputed ? recomputedRows : reusedRows).push(row + 1);
|
|
@@ -516,22 +566,49 @@ export class TranscriptViewport {
|
|
|
516
566
|
this.#functionIds.set(value, id);
|
|
517
567
|
return id;
|
|
518
568
|
}
|
|
569
|
+
#updateCopyableSelection() {
|
|
570
|
+
const selection = orderedTextSelection(this.#selection);
|
|
571
|
+
if (!selection) {
|
|
572
|
+
this.#copyableSelection = false;
|
|
573
|
+
this.#copyableRow = undefined;
|
|
574
|
+
return;
|
|
575
|
+
}
|
|
576
|
+
const end = Math.min(selection.end.line, this.#selectableDocumentRowCount - 1);
|
|
577
|
+
if (end !== selection.start.line) {
|
|
578
|
+
this.#copyableSelection = end > selection.start.line;
|
|
579
|
+
this.#copyableRow = undefined;
|
|
580
|
+
return;
|
|
581
|
+
}
|
|
582
|
+
const text = this.#documentRows[end] ?? "";
|
|
583
|
+
const prompt = this.#promptKindAt(end);
|
|
584
|
+
const from = selection.start.column, to = end === selection.end.line ? selection.end.column : Number.MAX_SAFE_INTEGER;
|
|
585
|
+
const cached = this.#copyableRow;
|
|
586
|
+
if (cached?.text === text && cached.prompt === prompt && cached.from === from && cached.to === to) {
|
|
587
|
+
this.#copyableSelection = cached.value;
|
|
588
|
+
return;
|
|
589
|
+
}
|
|
590
|
+
// Compatibility: learn whether an actual range is empty while presenting selection, not on Ctrl+C.
|
|
591
|
+
const value = selectionCopyRowText({ selection: { start: { line: 0, column: from }, end: { line: 0, column: to } } }, { text, ...(prompt === undefined ? {} : { prompt }) }, 0).length > 0;
|
|
592
|
+
this.#copyableRow = { text, prompt, from, to, value };
|
|
593
|
+
this.#copyableSelection = value;
|
|
594
|
+
}
|
|
519
595
|
#lineContentAt(line) {
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
596
|
+
return selectionCopyLineContent(this.#documentRows[line] ?? "", this.#promptKindAt(line));
|
|
597
|
+
}
|
|
598
|
+
#promptKindAt(line) {
|
|
599
|
+
// Performance: anchors are ordered, disjoint source ranges; do not scan off-screen prompts per copied row.
|
|
600
|
+
let low = 0, high = this.#promptAnchors.length - 1;
|
|
601
|
+
while (low <= high) {
|
|
602
|
+
const middle = (low + high) >>> 1;
|
|
603
|
+
const anchor = this.#promptAnchors[middle];
|
|
604
|
+
if (line < anchor.firstRow)
|
|
605
|
+
high = middle - 1;
|
|
606
|
+
else if (line > anchor.lastRow)
|
|
607
|
+
low = middle + 1;
|
|
608
|
+
else
|
|
609
|
+
return line === anchor.firstRow ? "first" : "continuation";
|
|
529
610
|
}
|
|
530
|
-
|
|
531
|
-
const content = usefulTextLineContent(plain, from);
|
|
532
|
-
if (plain.trim().length === 0)
|
|
533
|
-
return { from, to: from + 1 };
|
|
534
|
-
return content;
|
|
611
|
+
return undefined;
|
|
535
612
|
}
|
|
536
613
|
}
|
|
537
614
|
function selectionRangeForLine(selection, documentLine, selectableRowCount, width) {
|
|
@@ -1,6 +1,38 @@
|
|
|
1
1
|
import { displayWidth } from "./text.js";
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
/** Linear tokenization: a valid large OSC target must not exhaust the regular-expression engine's stack. */
|
|
3
|
+
function* rowTokens(line) {
|
|
4
|
+
const oscBoundary = /[\u0007\u001b]/g;
|
|
5
|
+
for (let offset = 0; offset < line.length;) {
|
|
6
|
+
if (line.charCodeAt(offset) !== 27) {
|
|
7
|
+
const next = line.indexOf("\u001b", offset);
|
|
8
|
+
const end = next < 0 ? line.length : next;
|
|
9
|
+
yield line.slice(offset, end);
|
|
10
|
+
offset = end;
|
|
11
|
+
continue;
|
|
12
|
+
}
|
|
13
|
+
let end = offset + 1;
|
|
14
|
+
if (line[offset + 1] === "[") {
|
|
15
|
+
let cursor = offset + 2;
|
|
16
|
+
while (line.charCodeAt(cursor) >= 0x30 && line.charCodeAt(cursor) <= 0x3f)
|
|
17
|
+
cursor++;
|
|
18
|
+
while (line.charCodeAt(cursor) >= 0x20 && line.charCodeAt(cursor) <= 0x2f)
|
|
19
|
+
cursor++;
|
|
20
|
+
if (line.charCodeAt(cursor) >= 0x40 && line.charCodeAt(cursor) <= 0x7e)
|
|
21
|
+
end = cursor + 1;
|
|
22
|
+
}
|
|
23
|
+
else if (line[offset + 1] === "]") {
|
|
24
|
+
// Performance: search for one boundary, not a quantified alternation over the entire URI.
|
|
25
|
+
oscBoundary.lastIndex = offset + 2;
|
|
26
|
+
const boundary = oscBoundary.exec(line);
|
|
27
|
+
if (boundary?.[0] === "\u0007")
|
|
28
|
+
end = boundary.index + 1;
|
|
29
|
+
else if (boundary !== null && line[boundary.index + 1] === "\\")
|
|
30
|
+
end = boundary.index + 2;
|
|
31
|
+
}
|
|
32
|
+
yield line.slice(offset, end);
|
|
33
|
+
offset = end;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
4
36
|
const SGR = /^\u001b\[[\d;:]*m$/u;
|
|
5
37
|
const SEMANTIC_ZONE = /^\u001b\]133;[ABC](?:\u0007|\u001b\\)$/u;
|
|
6
38
|
// Platform: this is a conservative cleanup detector, NOT a link-activation
|
|
@@ -22,14 +54,14 @@ export function readVisibleHyperlinks(line, widthOf = displayWidth) {
|
|
|
22
54
|
}
|
|
23
55
|
active = undefined;
|
|
24
56
|
};
|
|
25
|
-
for (const
|
|
26
|
-
const token = match[0];
|
|
57
|
+
for (const token of rowTokens(line)) {
|
|
27
58
|
if (token.startsWith("\u001b")) {
|
|
28
|
-
const
|
|
29
|
-
if (
|
|
59
|
+
const separator = token.startsWith("\u001b]8;") ? token.indexOf(";", 4) : -1;
|
|
60
|
+
if (separator !== -1) {
|
|
30
61
|
finish();
|
|
31
|
-
|
|
32
|
-
|
|
62
|
+
const target = token.slice(separator + 1, token.endsWith("\u0007") ? -1 : -2);
|
|
63
|
+
if (target)
|
|
64
|
+
active = { from: column, target };
|
|
33
65
|
}
|
|
34
66
|
else if (!SGR.test(token) && !SEMANTIC_ZONE.test(token))
|
|
35
67
|
replaySafe = false;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# Clipboard copy and paste delivery
|
|
2
|
+
|
|
3
|
+
## Compatibility map and baseline
|
|
4
|
+
|
|
5
|
+
Accepted change: `fix-response-copy-freeze`, specification PRs #359 and #365. Initial expanded implementation base: `71e97ab6`; the review checkpoint is rebased onto `26b51b0b`, retaining the accepted timestamp, rendering, and truthful right-edge selection changes.
|
|
6
|
+
|
|
7
|
+
- Bare A1 response selection: `SessionViewportController.handlePreInput` -> `TranscriptViewport.selectedText` -> selection clear/render request -> shell OSC 52 -> runtime's underlying terminal writer. The prompt native clipboard queue is not on this route.
|
|
8
|
+
- Prompt copy/cut, workflow `/copy`, tree copy, and A1-owned paste: one owned write coordinator with literal prompt/workflow payloads and transaction-specific predecessor capture. A paste admitted during a pending write observes that write's result; an independent paste after a settled failure performs a fresh read. Preserve prompt-selection precedence, `/copy`, and terminal-provided paste.
|
|
9
|
+
- Standalone paste: ordinary editor reservation -> clipboard acquisition -> text/path classification or existing image preparation -> replacement of that reservation. Native reads, format detection and command fallbacks run in a child, not the UI. Text/path classification uses a worker within that child; a blocked probe falls back to the same acquired text after one second and cannot strand the UI. Image preparation reuses the existing image worker within the killable child and preserves all image limits.
|
|
10
|
+
- Terminal-provided paste: small non-path text has a fixed synchronous bound (at most 1,000 source code units); larger or path-shaped content uses the same isolated preparation policy, without a redundant native read. The owned framer keeps only a delimiter-sized lookbehind and bounded body fragments rather than repeatedly joining a growing body.
|
|
11
|
+
- Modal/replacement input: the current base permits owned transcript selection behind a modal outside its painted bounds. Preserve that accepted routing rather than restoring the older blanket overlay bypass. `/copy` and Ctrl+C without transcript selection retain their existing command/cancellation handlers.
|
|
12
|
+
- `keep-streaming-ui-responsive-during-selection` owns general selection/wheel/preparation optimization. This change owns selected-range extraction, copy-clearing damage, delivery and its lifecycle. Shared row caches and input coordination remain authoritative.
|
|
13
|
+
- Pinned `a1 pi`, upstream components, installed dependencies, and terminal-owned selection remain unchanged controls.
|
|
14
|
+
|
|
15
|
+
A generated probe on Windows Node 24.16.0 copied the same eight-character range from 100 and 100,000 rows. Before remediation, selected-text extraction read 101 and 100,001 source rows respectively (about 0.16 and 20.00 ms in that instrumented run). Proxy access counting exaggerates absolute timings; row counts establish the unnecessary whole-history slice. These measurements do **not** reproduce or establish the cause of the user's whole-UI freeze. Clipboard contention, terminal/version, freeze duration, and whether only physical painting stops remain unconfirmed. No clipboard payload or private session content is captured in evidence.
|
|
16
|
+
|
|
17
|
+
## Lifetimes, limits, and recovery
|
|
18
|
+
|
|
19
|
+
- Copy: 5-second admission deadline, one executing request and one newest pending request, at most 16 MiB UTF-8 text. Source snapshots retain only selected rows, capped at 65,536 rows and 32 Mi UTF-16 code units; they do not keep an obsolete whole-transcript graph.
|
|
20
|
+
- Paste: eight distinct admitted requests, a 5-second acquisition budget including the captured write wait, and a 15-second deadline through insertion. A canceled executor counts against capacity until its stop fence. Image conversion has one process-wide slot. Copy supersession is deliberately not used for paste edits.
|
|
21
|
+
- Text transfer: 16 MiB UTF-8 cap; IPC fragments are at most 16,384 code units and preserve surrogate pairs. The receiver acknowledges each fragment. Prepared large text is stored behind the existing compact chip rather than inserted as millions of editor characters. Existing source-image, pixel, encoded-output and attachment-count limits remain unchanged.
|
|
22
|
+
- Clipboard executors have a 250 ms stop grace. POSIX command fallbacks share the paste helper's isolated process group; strict command reads use a forceful kill signal. Unconfirmed executor termination retains its capacity instead of allowing unbounded replacements or late A1-controlled writes. No helper inherits the UI terminal or places clipboard payloads in argv, temporary files, or logs.
|
|
23
|
+
- Local copy prefers native delivery outside the terminal. OSC 52 uses the existing writer only on a non-blocking supported output route and is capped at a complete 64 KiB control, or a lower advertised limit. Submission is not clipboard acknowledgment. Remote/multiplexed sessions never silently read or write a different host's clipboard. Native Ctrl+V acquisition is unavailable when only the remote terminal owns the desired clipboard; normal terminal-provided paste remains supported.
|
|
24
|
+
- URL-chip decoration: at most 65,536 UTF-8 bytes of added OSC 8 metadata per editor render pass across rows and repeated occurrences, including opening/closing pairs and cleanup closes. Targets that do not fit are omitted whole before control construction; a UTF-16 length precheck avoids scanning huge backing values. The budget resets each pass. Full chip data, atomic editing and exact copy/history/submission expansion remain intact; ordinary in-budget links retain their targets. Terminal automatic URL recognition is separate from explicit metadata.
|
|
25
|
+
- Path-list presentation: the isolated classifier budgets 4,096 UTF-16 units per paste for existing individual tags plus 20 suffix-reserve units per occurrence. Over-budget lists use one existing text-paste chip backed by all classified full paths concatenated in order, including repetitions, without extra separators or re-normalization. No individual members are adopted into the UI before compaction. Small lists retain their file/folder/image-file chips; failed/timed-out classification retains the existing original-text fallback. Existing draft/history chips and the synchronous compatibility classifier are unchanged.
|
|
26
|
+
- Reservations protect later typing, distinct paste order, selection replacement and undo/redo. Cancellation retires the reservation from history snapshots. Provisional chip ownership prevents failed/canceled pastes retaining unused backing data or deleting a chip reused by another successful paste.
|
|
27
|
+
|
|
28
|
+
## Optional local diagnostics
|
|
29
|
+
|
|
30
|
+
Set `A1_CLIPBOARD_DIAGNOSTICS` to an existing-parent local file path to capture metadata in bare A1. It is disabled by default and ignored by comparison/settings-free compositions. Records include copy/paste phases, runtime receipt/composition/write phases, and a 100 ms heartbeat. At most 128 scalar-only rows and one newest pending asynchronous disk snapshot are retained. No copied/pasted text, paths, image data, encoded payloads, raw errors, or extra fields from event objects are serialized. Disk errors remain silent and disk completion is never awaited by input or teardown.
|
|
31
|
+
|
|
32
|
+
Diagnostic accounting includes the transient incoming copy before its older pending predecessor is superseded (eight pastes plus three transient copy records). Paste-helper exit no longer prematurely clears insertion accounting: request cleanup is recorded after the insertion lifetime. Literal workflow copies do not invent transcript selection-clear events. Composition tests verify explicit/environment opt-in, comparison/settings-free exclusion, destination precedence, and disposal even when construction or shell teardown fails. Request identifiers are local to their source; runtime identifiers are input revisions and negative framing identifiers identify terminal input observations. `pendingBytes` is observed payload/source accounting, not native heap measurement.
|
|
33
|
+
|
|
34
|
+
A healthy heartbeat with stalled visible painting differs from a blocked application event loop. These diagnostics support attribution but do not themselves establish the user's physical-terminal root cause. The baseline's main-thread path classifier and write/paste barriers are investigated independently of OSC 52.
|
|
35
|
+
|
|
36
|
+
## URL presentation evidence and approved refinement
|
|
37
|
+
|
|
38
|
+
Before the approved refinement, a source made from `https://example.com/` plus `16 * 1024 * 1024 - 64` ASCII characters was within the text limit and produced a 46-character editor chip, but its full OSC 8 target produced a 16,777,367-byte terminal write. The earlier regular-expression tokenization overflowed its stack; linear tokenization removed that crash but a subsequent headless run still measured a 305 ms maximum application timer gap. The 4 MiB control measured a 78 ms gap and a 4,194,519-byte write. These observations do not identify the user's physical freeze or measure terminal painting.
|
|
39
|
+
|
|
40
|
+
With PR #385 open, the user explicitly approved revising its specs and implementation to preserve full URLs and chips while omitting oversized explicit hyperlink metadata. `EditorHyperlinkBudget` now enforces the per-pass limit before OSC construction, including repeated occurrences, multibyte targets, and row cleanup. The former observational-only probe now asserts bounded output, exact expanded submission/history, following typing, and no duplicate native read for bracketed paste; unit tests also check exact copy expansion and rejection without whole-target byte counting.
|
|
41
|
+
|
|
42
|
+
One augmented synthetic run, including content checks and following typing, recorded 6,563 maximum terminal-write bytes for each near-limit route, with roughly 48 ms native / 55 ms bracketed maximum timer gaps and 632 / 647 ms overall test-observed insertion/check time. This is not an identical timing workload to the earlier probe, a physical latency distribution, or by itself evidence for task 5.3's entire large-text/path/image matrix. Deterministic metadata/output limits gate the regression; broader integrated evidence is recorded below, and physical acceptance is still required.
|
|
43
|
+
|
|
44
|
+
## Generated baseline and lifecycle evidence
|
|
45
|
+
|
|
46
|
+
The generated baseline covers 10 and 1,000 historical response blocks, idle and streaming, copy-only, standalone external-value paste-only, and copy/paste overlap. Each case runs once in a fresh shell and twice subsequently (36 samples). Helpers still start per request; warm-shell is not a claim of native-helper reuse. Copy uses the production isolated preparer with an injected clipboard sink; paste uses a supplied external-value reader and production isolated preparation. Neither is a physical clipboard or terminal measurement. Records separate input progress, copy phases, paste predecessor/acquisition/classification/insertion phases, composition/write counts, and timer progress, without payloads.
|
|
47
|
+
|
|
48
|
+
One focused run observed the following ranges across 12 samples per operation:
|
|
49
|
+
|
|
50
|
+
| Operation | Following-input handling (ms) | Completion/check time (ms) | Maximum timer gap per sample (ms) |
|
|
51
|
+
| --- | --- | --- | --- |
|
|
52
|
+
| Copy | 5.5–18.0 | 245.1–258.6 | 16.4–20.2 |
|
|
53
|
+
| Independent paste | 4.1–8.3 | 311.8–346.6 | 16.9–21.3 |
|
|
54
|
+
| Combined | 5.4–13.1 | 544.7–601.0 | 16.3–20.4 |
|
|
55
|
+
|
|
56
|
+
Generated 2 MiB single-line, 100,000-line text, and 1,024-square image controls retained compact presentation and exact text/valid image semantics, with maximum write sizes of 6,563 bytes and timer gaps of approximately 17/18/27 ms in that run. These timings are observations, not shared-CI wall-clock gates or physical input-to-paint distributions.
|
|
57
|
+
|
|
58
|
+
Lifecycle evidence covers 12 sequential real-helper copy/paste cycles, excess-admission rejection and full eight-helper capacity recovery after cancellation, and eight image helpers sharing exactly one conversion slot. Every observed child exited/disconnected and used ignored terminal descriptors. A reentrant acquisition-cancellation test found and fixed a conversion grant after cancellation. Session-shell tests preserve newer selections and drafts after late copy/text/image completion and verify alternate-screen restoration/no subsequent writes on disposal. Markdown render-call counters remain unchanged during copy-clear, typing, and wheel work on settled content. Cold emitted-helper/package tests remain separate from these source-helper tests.
|
|
59
|
+
|
|
60
|
+
## Approved compact path-list refinement
|
|
61
|
+
|
|
62
|
+
Indexed atomic segmentation uses the existing grapheme segmenter's boundary lookup rather than enumerating all discarded chip-interior graphemes. Equivalence tests cover Unicode boundaries and the existing iterable fallback; a structural test requires exactly two boundary lookups per chip rather than full enumeration. No pinned-source file or installed dependency is modified.
|
|
63
|
+
|
|
64
|
+
A 12,000 short-name path-list observation improved from roughly 208 ms to 109 ms maximum timer gap. However, 6,000 paths with 160-character filename stems still produced 1,014,006 editor characters from 1,409,999 input bytes and a roughly 1,387 ms timer gap. Exact expanded paths were retained and writes remained small, but that application-side presentation work was unacceptable.
|
|
65
|
+
|
|
66
|
+
The user approved the compact-path-list spec/code refinement in #385. `path-chip-presentation.ts` now shares tag formatting with adoption and selects a compact existing text-paste representation in the isolated preparer. The same 6,000-path source now produces 30 editor characters (including following typing), with approximately 18.3/18.5 ms observed timer gaps for native/bracketed routes in one focused run. A near-16 MiB path candidate (16,777,119 source bytes) exercised the one-second safe classification fallback, retaining all original text behind a 29-character presentation; observed gaps were about 16.8/20.7 ms. All four maximum writes were 6,563 bytes. These augmented workloads are synthetic observations, not physical acceptance.
|
|
67
|
+
|
|
68
|
+
The former observational path probes now gate compact representation, exact expanded content, following typing, undo/redo, no duplicate native read, and bounded terminal output. Unit tests cover exact budget boundaries, Unicode/duplicates, shared file/folder/image-file framing, stopping tag construction at the allowance, exact literal path expansion for copy/history/submission, no provisional member chips, and stale-result cancellation. Emitted-helper tests prove compaction before UI transfer and that an invalid suffix falls back to the whole original text rather than a classified prefix.
|
|
69
|
+
|
|
70
|
+
## Integrated automated matrix
|
|
71
|
+
|
|
72
|
+
The refinement checkpoint passed 597 tests across 24 focused suites plus 35 clipboard/image/preparation/package tests (632 total across 27 suites), with build and typecheck. Coverage supporting tasks 5.3, 5.8, and 6.1 includes:
|
|
73
|
+
|
|
74
|
+
- Selected-range/transport/coordinator tests: small and large exact payloads, Unicode, bounded source/encoding work, copy supersession, stalls/deadlines, backpressure, destination selection, late-side-effect fences, and cleanup.
|
|
75
|
+
- Paste executor/client, chip lifecycle, and image tests: standalone sources, 16 MiB boundary/rejection, actual blocked probes/native work/command descendants, format/read/preparation failures, recovery, reserved ordering, cancellation/undo, image safety limits and serialized conversion.
|
|
76
|
+
- Shell/editor/controller and diagnostics tests: copy-only/paste-only/combined cold/warm idle/streaming histories, native/bracketed text/URL/path/image presentation, following input/scroll/output, overlay ownership, comparison compatibility, bounded metadata/privacy, and session teardown.
|
|
77
|
+
- Cold emitted helper/image-worker and package-inventory tests: production helper resolution, native fixture read/write, compact and invalid-prefix path lists, images, and included runtime modules. No installed dependency or pinned source is patched.
|
|
78
|
+
|
|
79
|
+
This completes the integrated automated work, not supported-route physical certification. Required CI must run on the new candidate; tasks 4.3 and 7 remain dependent on actual terminal/clipboard evidence and explicit acceptance.
|
|
80
|
+
|
|
81
|
+
## Acceptance
|
|
82
|
+
|
|
83
|
+
Physical acceptance is pending. Automated scheduling and terminal replay cannot certify physical clipboard responsiveness. Test the exact candidate with repeated response Ctrl+C, independent Ctrl+V using content copied outside A1, and combined copy/paste, while idle and streaming. Check copied values in another application and pasted values in the prompt. Record terminal/version, topology, geometry, cold/warm behavior, subsequent-input latency and animation separately from clipboard completion latency. No Esc or other recovery action counts as a fix. Any repeated freeze fails acceptance; native Linux clipboard persistence and remote terminal behavior also require applicable physical evidence rather than inference from mocked transport tests.
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# Owned tool image presentation
|
|
2
|
+
|
|
3
|
+
## Why this source adaptation exists
|
|
4
|
+
|
|
5
|
+
The accepted `preserve-agent-content-rendering` design permits a minimal attributed content adaptation when the pinned public component cannot preserve supported behavior. The user explicitly approved this image-lifecycle adaptation after its failing baseline.
|
|
6
|
+
|
|
7
|
+
Pi 0.84.2's `ToolExecutionComponent` stores converted images by array index. Replacing a JPEG with another JPEG or a PNG can therefore display the previous pixels. Conversion failure has no visible fallback under Kitty. Its public API exposes neither conversion-cache reset nor mount disposal. Reconstructing the entire component on each attachment change would discard extension renderer state and asynchronous previews.
|
|
8
|
+
|
|
9
|
+
`src/integrations/pi/components/upstream/components/tool-execution.ts` retains that tool shell's composition, lifecycle, fallback, expansion and extension context. Actual built-in renderers still come from the public `create*ToolDefinition` factories; custom renderers receive the original content, MIME types, bytes, details, arguments, state and previous components. The owned shell never edits installed Pi, accesses private fields, patches prototypes, upgrades dependencies, or modifies the explicit Pi comparison route.
|
|
10
|
+
|
|
11
|
+
The source unit is from MIT-licensed repository <https://github.com/earendil-works/pi>, commit `914cf1472e715297caa30db4b9535d534a9eb718`, path `packages/coding-agent/src/modes/interactive/components/tool-execution.ts`. The source-map hash and local port hash are recorded in `config/baselines/pinned-pi-source-port-ledger.json`. Its small generic-output helper follows `core/tools/render-utils.ts::getTextOutput` and `utils/shell.ts::sanitizeBinaryOutput` from the same package; Node's public `stripVTControlCharacters` replaces the private ANSI helper. The public definition factories replace the private `createAllToolDefinitions` lookup. Public theme and TUI imports are mapped to the existing owned boundaries. No tool filesystem implementation or renderer layout is copied.
|
|
12
|
+
|
|
13
|
+
## Image ownership and bounded work
|
|
14
|
+
|
|
15
|
+
`ToolImagePresentation` owns only current image presentation. It shares immutable source strings rather than copying or hashing accumulated image data per chunk. Equal current sources share a conversion entry; entries disappear when their source is removed. The existing admission limits remain 16 references and 20 MiB per image; derived PNGs also receive the per-image limit before retention.
|
|
16
|
+
|
|
17
|
+
One conversion is active per mounted tool. The waiting set is the newest current source set, not an unbounded queue of superseded snapshots. Hidden images and non-Kitty protocols do not start conversions. Work removed before codec entry is skipped. A public codec call already in progress cannot be cancelled; its eventual result is discarded if the source or mount is obsolete. This limitation is explicit, not claimed as cancellable worker execution.
|
|
18
|
+
|
|
19
|
+
Pending Kitty conversion has a visible `[Image converting: ...]` marker. Failure, rejection, or oversized converted bytes produces `[Image unavailable: ...]`, without exposing codec diagnostics or changing the operation outcome. Converted pixels never replace the original payload passed to extension renderers. PNG and iTerm presentation use the actual public `Image` component. Hidden and no-image-capability text presentation matches pinned Pi.
|
|
20
|
+
|
|
21
|
+
Rebuild/disposal releases image ownership and invalidates the previous mount. Reentrant renderer invalidation is suppressed during synchronous reconstruction; valid asynchronous callbacks still request the existing scheduler and update presentation revision, not semantic revision.
|
|
22
|
+
|
|
23
|
+
## Terminal row boundaries
|
|
24
|
+
|
|
25
|
+
Kitty APC payloads are zero-column opaque controls, not displayable base64 text. Neutral width/truncation/copy handling preserves or excludes the complete control as appropriate. Rail overlays preserve each image transfer once rather than replaying it as an SGR style. The runtime width guard excludes complete Kitty payloads but still checks all ordinary columns around them; it does not exempt an arbitrary image-bearing row from width validation.
|
|
26
|
+
|
|
27
|
+
This correction does not introduce an ANSI parser, change link detection, relax text width limits, or change terminal erase policy. Image placement, scaling and clipping remain subject to the existing public Image and viewport visibility policies; physical partially clipped-image behavior is not established by a component byte comparison.
|
|
28
|
+
|
|
29
|
+
## Evidence
|
|
30
|
+
|
|
31
|
+
- `transcript-image-conversion.test.ts`: independent pinned stale-cache baseline, JPEG replacement, PNG replacement, hidden/non-Kitty parity, failed-conversion fallback and real scheduled Kitty publication at unchanged semantic revision.
|
|
32
|
+
- `tool-image-presentation.test.ts`: current-source sharing, one active conversion, superseded bursts, removed/disposed callbacks, hidden/iTerm work avoidance, rejection and size fallback without retry loops.
|
|
33
|
+
- `tool-shell-parity.test.ts`: independent actual pinned read/bash/edit/write/grep/find/ls and generic rendering at widths 40/80/192, partial/final/error states, expansion, binary text, and extension default/self shells with original inputs and stable renderer state.
|
|
34
|
+
- Existing attachment-ownership, payload-limit, content-retention and shell tests remain non-regression gates. The wrapped-URL counterexample remains explicitly unresolved under issue #353.
|
|
35
|
+
|
|
36
|
+
These are automated content and ownership checks, not Windows Terminal visual acceptance. The broader change stays open until scheduled-producer evidence, required CI, and exact-candidate user review satisfy its remaining tasks.
|
|
37
|
+
|
|
38
|
+
## Upstream license notice
|
|
39
|
+
|
|
40
|
+
```text
|
|
41
|
+
MIT License
|
|
42
|
+
|
|
43
|
+
Copyright (c) 2025 Mario Zechner
|
|
44
|
+
|
|
45
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
46
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
47
|
+
in the Software without restriction, including without limitation the rights
|
|
48
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
49
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
50
|
+
furnished to do so, subject to the following conditions:
|
|
51
|
+
|
|
52
|
+
The above copyright notice and this permission notice shall be included in all
|
|
53
|
+
copies or substantial portions of the Software.
|
|
54
|
+
|
|
55
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
56
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
57
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
58
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
59
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
60
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
61
|
+
SOFTWARE.
|
|
62
|
+
```
|
|
@@ -98,7 +98,23 @@ node scripts/release/report-resource-sensitive-validation.mjs --repeats 3 --outp
|
|
|
98
98
|
|
|
99
99
|
## Pull request integration
|
|
100
100
|
|
|
101
|
-
`Development validation required` remains the merge gate for every pull request. For code changes, it
|
|
101
|
+
`Development validation required` remains the merge gate for every pull request. For applicable code changes, it requires the dedicated Defender-enabled exact-package startup lane on Windows Node 22. Development validation (including manual dispatch) does not schedule a Windows Node 24 startup lane. The Node 22 lane retains the complete package-install, image preparation/package, and durable-history checks plus startup evidence artifacts. A missing, cancelled, failed, or unexpectedly skipped required startup result still blocks the aggregate; documentation-only, version-only, and draft exemptions are unchanged.
|
|
102
|
+
|
|
103
|
+
| Startup runtime | Development validation (PR or manual) | Nightly/release validation | Manual Full regression |
|
|
104
|
+
| --- | --- | --- | --- |
|
|
105
|
+
| Windows Node 22 | Required for applicable changes | Retained | Retained |
|
|
106
|
+
| Windows Node 24 | Not scheduled | Retained | Retained |
|
|
107
|
+
|
|
108
|
+
Each selected startup lane runs the package-install scenarios once: a failed budget remains failed and is never retried to obtain a warmed result. Both post-update profiles must reach input-ready state within five seconds, and both warm profiles must remain within three seconds. Node 24 runtime support, other PR jobs, Defender, and publication gates are unchanged. Full validation retains every deferred startup, image, and history test through its existing suite owners.
|
|
109
|
+
|
|
110
|
+
The trade-off is delayed detection: a Node-24-specific regression can reach `develop` before nightly catches it. A green Node 22 PR check does not certify Node 24, and nightly failure still blocks its publication. When Node 24 feedback is needed before nightly, explicitly request the non-publishing Full regression workflow for the desired branch or tag:
|
|
111
|
+
|
|
112
|
+
```sh
|
|
113
|
+
gh workflow run full-regression.yml --ref <branch-or-tag>
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Record the run's resolved source SHA and package evidence; do not substitute an unrelated historical green run for current-head acceptance. This full run is a deliberate additional operation, not an automatic step for every PR.
|
|
117
|
+
|
|
102
118
|
Documentation auto-merge reads the complete GitHub changed-file response and arms
|
|
103
119
|
squash auto-merge for an eligible pull request while required validation is pending;
|
|
104
120
|
branch protection prevents integration until `Development validation required`
|
|
@@ -106,7 +122,7 @@ succeeds. If validation finishes before auto-merge can be armed and GitHub alrea
|
|
|
106
122
|
reports the pull request clean, the reconciler squash-merges only the validated head
|
|
107
123
|
SHA. Every current and renamed-from path must be under `openspec/**`, under
|
|
108
124
|
`docs/**`, or exactly the root `README.md`. Eligible pull requests must use a
|
|
109
|
-
non-draft branch in this repository and target `develop`.
|
|
125
|
+
non-draft branch in this repository and target `develop`. Implementation-associated PRs and newly introduced active OpenSpec changes are held for manual integration even when their diff is documentation-only. Body edits trigger reconciliation; removing a marker cannot bypass the base/head tree check. Standalone existing-change revisions and archive moves remain eligible.
|
|
110
126
|
|
|
111
127
|
The exact allowlist covers maintained OpenSpec, architecture, feature, manual,
|
|
112
128
|
runbook, and root README documentation. Other root Markdown files, `LICENSE`,
|
|
@@ -119,11 +135,7 @@ inventoried OpenSpec occurrence fails that pull request rather than a later code
|
|
|
119
135
|
request. A legitimate generated baseline update remains outside the allowlist and
|
|
120
136
|
follows the manually accepted mixed/code path.
|
|
121
137
|
|
|
122
|
-
A specification
|
|
123
|
-
starts only after that specification merges and the maintainer explicitly requests
|
|
124
|
-
it, in a fresh worktree and pull request based on updated `origin/develop`. Every
|
|
125
|
-
code/operational pull request remains open after CI until the maintainer validates it
|
|
126
|
-
locally and explicitly authorizes manual integration.
|
|
138
|
+
A new implementation-bound specification starts as OpenSpec-only artifacts in one draft PR. Explicit approval to implement continues in that same worktree, branch, history, and PR; the plan does not merge first. Approved refinements reconcile the planning artifacts before code changes. Make the completed PR ready for final review, but leave integration manual after CI, exact-head maintainer acceptance, and explicit merge authorization. Closing a rejected unmerged draft integrates and archives nothing; local unmerged cleanup still needs separate approval. Existing merged plans retain their legacy implementation PRs and require explicit reconciliation if rejected. See [delivery and archive handoff](openspec-archive-automation.md) for the version-2 link and legacy version-1 compatibility.
|
|
127
139
|
|
|
128
140
|
## Numbered development previews
|
|
129
141
|
|