@oh-my-pi/pi-coding-agent 16.3.4 → 16.3.6
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/CHANGELOG.md +36 -0
- package/dist/cli.js +3522 -3485
- package/dist/types/edit/file-snapshot-store.d.ts +4 -2
- package/dist/types/edit/renderer.d.ts +0 -1
- package/dist/types/extensibility/custom-tools/types.d.ts +2 -0
- package/dist/types/extensibility/shared-events.d.ts +8 -1
- package/dist/types/hindsight/content.d.ts +4 -0
- package/dist/types/internal-urls/artifact-protocol.d.ts +8 -0
- package/dist/types/internal-urls/types.d.ts +10 -0
- package/dist/types/lsp/config.d.ts +4 -0
- package/dist/types/lsp/edits.d.ts +2 -0
- package/dist/types/mcp/oauth-discovery.d.ts +30 -0
- package/dist/types/modes/components/assistant-message.d.ts +15 -10
- package/dist/types/modes/components/bash-execution.d.ts +6 -0
- package/dist/types/modes/components/eval-execution.d.ts +6 -0
- package/dist/types/modes/components/login-dialog.d.ts +10 -2
- package/dist/types/modes/components/tool-execution.d.ts +3 -13
- package/dist/types/modes/components/transcript-container.d.ts +26 -29
- package/dist/types/modes/controllers/mcp-command-controller.d.ts +20 -3
- package/dist/types/modes/rpc/rpc-client.d.ts +7 -3
- package/dist/types/modes/rpc/rpc-types.d.ts +6 -0
- package/dist/types/modes/utils/transcript-render-helpers.d.ts +15 -6
- package/dist/types/session/agent-session.d.ts +2 -0
- package/dist/types/subprocess/worker-runtime.d.ts +11 -0
- package/dist/types/tools/bash-skill-urls.d.ts +2 -2
- package/dist/types/tools/bash.d.ts +0 -2
- package/dist/types/tools/browser/tab-supervisor.d.ts +10 -0
- package/dist/types/tools/eval-render.d.ts +0 -2
- package/dist/types/tools/renderers.d.ts +0 -20
- package/dist/types/tools/ssh.d.ts +0 -2
- package/dist/types/tools/write.d.ts +1 -1
- package/dist/types/utils/git.d.ts +16 -0
- package/package.json +12 -12
- package/src/cli/auth-broker-cli.ts +13 -2
- package/src/cli/tiny-models-cli.ts +12 -5
- package/src/edit/file-snapshot-store.ts +5 -2
- package/src/edit/renderer.ts +0 -5
- package/src/extensibility/custom-tools/types.ts +2 -0
- package/src/extensibility/shared-events.ts +9 -1
- package/src/hindsight/content.ts +31 -0
- package/src/internal-urls/artifact-protocol.ts +97 -53
- package/src/internal-urls/types.ts +10 -0
- package/src/lsp/config.ts +15 -0
- package/src/lsp/edits.ts +28 -7
- package/src/lsp/index.ts +46 -4
- package/src/mcp/oauth-discovery.ts +88 -18
- package/src/mnemopi/state.ts +26 -2
- package/src/modes/components/assistant-message.ts +134 -82
- package/src/modes/components/bash-execution.ts +9 -0
- package/src/modes/components/chat-transcript-builder.ts +8 -4
- package/src/modes/components/eval-execution.ts +9 -0
- package/src/modes/components/login-dialog.ts +16 -2
- package/src/modes/components/mcp-add-wizard.ts +9 -1
- package/src/modes/components/tool-execution.ts +4 -50
- package/src/modes/components/transcript-container.ts +82 -458
- package/src/modes/components/tree-selector.ts +9 -3
- package/src/modes/controllers/command-controller.ts +0 -3
- package/src/modes/controllers/event-controller.ts +74 -14
- package/src/modes/controllers/extension-ui-controller.ts +0 -1
- package/src/modes/controllers/input-controller.ts +4 -10
- package/src/modes/controllers/mcp-command-controller.ts +106 -29
- package/src/modes/controllers/selector-controller.ts +9 -1
- package/src/modes/interactive-mode.ts +12 -8
- package/src/modes/rpc/rpc-client.ts +8 -4
- package/src/modes/rpc/rpc-mode.ts +1 -0
- package/src/modes/rpc/rpc-types.ts +13 -1
- package/src/modes/setup-wizard/scenes/sign-in.ts +18 -0
- package/src/modes/utils/transcript-render-helpers.ts +40 -13
- package/src/modes/utils/ui-helpers.ts +12 -7
- package/src/prompts/tools/read.md +1 -1
- package/src/sdk.ts +1 -0
- package/src/session/agent-session.ts +148 -1
- package/src/session/session-context.ts +7 -0
- package/src/subprocess/worker-runtime.ts +219 -2
- package/src/task/worktree.ts +28 -6
- package/src/tiny/worker.ts +14 -4
- package/src/tools/bash-skill-urls.ts +3 -3
- package/src/tools/bash.ts +0 -4
- package/src/tools/browser/tab-supervisor.ts +47 -3
- package/src/tools/eval-render.ts +0 -20
- package/src/tools/grep.ts +19 -2
- package/src/tools/path-utils.ts +4 -0
- package/src/tools/read.ts +261 -21
- package/src/tools/renderers.ts +0 -20
- package/src/tools/ssh.ts +0 -16
- package/src/tools/write.ts +13 -6
- package/src/utils/git.ts +20 -0
- package/src/utils/open.ts +51 -6
|
@@ -7,45 +7,6 @@ import {
|
|
|
7
7
|
type ViewportTailProvider,
|
|
8
8
|
} from "@oh-my-pi/pi-tui";
|
|
9
9
|
|
|
10
|
-
const kSnapshot = Symbol("transcript.liveDiffSnapshot");
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Per-block render cache: the block's previous stripped contribution plus the
|
|
14
|
-
* derived append-only state. Still-live blocks use it as input to
|
|
15
|
-
* {@link deriveLiveCommitState}; finalized blocks wholly inside already
|
|
16
|
-
* committed native scrollback can replay it without calling render().
|
|
17
|
-
*/
|
|
18
|
-
interface LiveDiffSnapshot {
|
|
19
|
-
width: number;
|
|
20
|
-
lines: readonly string[];
|
|
21
|
-
generation: number;
|
|
22
|
-
appendOnly: boolean;
|
|
23
|
-
/**
|
|
24
|
-
* Frames remaining until a block that rewrote an interior row may re-earn
|
|
25
|
-
* append-only status. `0` means the block is not under rewrite suspicion.
|
|
26
|
-
*/
|
|
27
|
-
volatileCooldown: number;
|
|
28
|
-
/**
|
|
29
|
-
* Stable-prefix ratchet (see {@link deriveLiveCommitState}): leading rows
|
|
30
|
-
* promoted as commit-safe because they stayed visibly identical for
|
|
31
|
-
* {@link STABLE_PREFIX_COMMIT_FRAMES} consecutive frames, plus the in-flight
|
|
32
|
-
* candidate run and its age.
|
|
33
|
-
*/
|
|
34
|
-
stablePrefixLength: number;
|
|
35
|
-
candidatePrefixLength: number;
|
|
36
|
-
candidatePrefixAge: number;
|
|
37
|
-
/**
|
|
38
|
-
* Topmost row index ever observed rewritten in place (see
|
|
39
|
-
* {@link deriveLiveCommitState}): the stable-prefix ratchet never promotes
|
|
40
|
-
* rows at/after it. `Infinity` until the first rewrite.
|
|
41
|
-
*/
|
|
42
|
-
rewriteFloor: number;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
interface SnapshotCarrier {
|
|
46
|
-
[kSnapshot]?: LiveDiffSnapshot;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
10
|
/**
|
|
50
11
|
* A transcript block that is still mutating (a foreground tool awaiting its
|
|
51
12
|
* result, an assistant message mid-stream) reports `false` so the container
|
|
@@ -65,18 +26,20 @@ interface FinalizableBlock {
|
|
|
65
26
|
*/
|
|
66
27
|
getTranscriptBlockVersion?(): number;
|
|
67
28
|
/**
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
*
|
|
29
|
+
* Leading rows of the block's current render() output that are declared
|
|
30
|
+
* FINAL while the block is still live: byte-stable at the current width
|
|
31
|
+
* until the block finalizes, monotone non-decreasing under streaming
|
|
32
|
+
* growth, re-derived per render (the container reads it right after
|
|
33
|
+
* calling render()). The container extends the native-scrollback commit
|
|
34
|
+
* boundary through these rows so a long streaming reply's scrolled-off
|
|
35
|
+
* head reaches terminal history mid-stream. Declaring a row that later
|
|
36
|
+
* changes strands a stale copy in immutable history (the engine audit
|
|
37
|
+
* repairs by recommitting below — duplication, never loss), so
|
|
38
|
+
* implementers report only rows whose bytes provably cannot change (e.g.
|
|
39
|
+
* rendered output of markdown's frozen token prefix). Absent = 0: nothing
|
|
40
|
+
* commits until the block finalizes.
|
|
78
41
|
*/
|
|
79
|
-
|
|
42
|
+
getTranscriptBlockSettledRows?(): number;
|
|
80
43
|
}
|
|
81
44
|
|
|
82
45
|
function isBlockFinalized(child: Component): boolean {
|
|
@@ -89,9 +52,12 @@ function getBlockVersion(child: Component): number | undefined {
|
|
|
89
52
|
return fn ? fn.call(child) : undefined;
|
|
90
53
|
}
|
|
91
54
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
55
|
+
/** Clamped read of a block's declared settled rows (see {@link FinalizableBlock}). */
|
|
56
|
+
function getBlockSettledRows(child: Component): number {
|
|
57
|
+
const fn = (child as Component & FinalizableBlock).getTranscriptBlockSettledRows;
|
|
58
|
+
if (!fn) return 0;
|
|
59
|
+
const value = fn.call(child);
|
|
60
|
+
return Number.isFinite(value) ? Math.max(0, Math.trunc(value)) : 0;
|
|
95
61
|
}
|
|
96
62
|
|
|
97
63
|
// A "plain blank" row is empty or whitespace-only with no ANSI bytes. It marks
|
|
@@ -143,270 +109,22 @@ const EMPTY_SEGMENTS: BlockSegment[] = [];
|
|
|
143
109
|
/** Shared empty result for an empty viewport-tail render (no allocation). */
|
|
144
110
|
const EMPTY_TAIL: readonly string[] = [];
|
|
145
111
|
|
|
146
|
-
interface LiveCommitState {
|
|
147
|
-
appendOnly: boolean;
|
|
148
|
-
volatileCooldown: number;
|
|
149
|
-
stablePrefixLength: number;
|
|
150
|
-
candidatePrefixLength: number;
|
|
151
|
-
candidatePrefixAge: number;
|
|
152
|
-
rewriteFloor: number;
|
|
153
|
-
safeLength: number;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
/**
|
|
157
|
-
* Render frames a block must stay clean (static or append-shaped) after an
|
|
158
|
-
* interior rewrite before its rows become committable again. A one-off
|
|
159
|
-
* re-layout (a codespan finalizing across a wrap boundary, a paragraph
|
|
160
|
-
* re-parsed as a heading) only suspends commits briefly — the pinned emitter
|
|
161
|
-
* appends from the stalled high-water mark, so the gap backfills contiguously
|
|
162
|
-
* once the block re-earns append-only. Periodic animations (a spinner rewrites
|
|
163
|
-
* its row every few frames) keep resetting the countdown and never re-earn it,
|
|
164
|
-
* so genuinely volatile blocks stay deferred. Frames arrive at most at the
|
|
165
|
-
* TUI's 30 Hz render cadence, so 30 frames ≈ 1s of clean streaming.
|
|
166
|
-
*/
|
|
167
|
-
const VOLATILE_REARM_FRAMES = 30;
|
|
168
|
-
|
|
169
|
-
/**
|
|
170
|
-
* Consecutive frames a leading row run must stay visibly identical before it
|
|
171
|
-
* is promoted as commit-safe even though the block's tail keeps rewriting.
|
|
172
|
-
* Append-only detection alone is all-or-nothing per block: one perpetually
|
|
173
|
-
* ticking row (a task tool's progress tree, per-agent cost/tool counters, a
|
|
174
|
-
* log line spinner) suspends commits for the WHOLE block forever, so once the
|
|
175
|
-
* block outgrows the viewport its static head — e.g. a task's prompt/context
|
|
176
|
-
* markdown — is neither committed to native scrollback nor on screen: the
|
|
177
|
-
* transcript reads as cut off for the entire (possibly minutes-long) run.
|
|
178
|
-
* The ratchet commits the settled head while only the genuinely volatile tail
|
|
179
|
-
* stays deferred. If a promoted row is later rewritten (a collapsing
|
|
180
|
-
* preview), the engine's committed-prefix audit re-anchors and recommits —
|
|
181
|
-
* duplication, never loss — and the ratchet retreats to the divergence.
|
|
182
|
-
*/
|
|
183
|
-
const STABLE_PREFIX_COMMIT_FRAMES = 30;
|
|
184
|
-
|
|
185
|
-
/**
|
|
186
|
-
* Rows at a live block's tail treated as the volatile streaming edge. Real
|
|
187
|
-
* streaming is not strictly append-only at the bottom: the in-flight markdown
|
|
188
|
-
* paragraph re-wraps as words arrive (rewriting its last 1-2 visual rows), an
|
|
189
|
-
* unclosed token (`**bold`, a half-streamed link) re-renders when its closer
|
|
190
|
-
* arrives, and a wrap-shrink moves the last word onto a new row. Divergence
|
|
191
|
-
* confined to this zone is clean growth, and the zone itself is held back
|
|
192
|
-
* from the offered commit boundary — so a tolerated rewrite can never touch a
|
|
193
|
-
* row the engine may have committed. Width 4 covers the observed shapes (≤2
|
|
194
|
-
* rows) with margin for wide glyphs and multi-row token spans; the cost is
|
|
195
|
-
* only that the last 4 rows of a live block commit at finalization instead of
|
|
196
|
-
* mid-stream, which is invisible (they are on screen — the viewport is always
|
|
197
|
-
* taller than the holdback).
|
|
198
|
-
*/
|
|
199
|
-
const TAIL_VOLATILITY_ROWS = 4;
|
|
200
|
-
|
|
201
|
-
/**
|
|
202
|
-
* Visible-content form of a row: SGR/OSC bytes and trailing pad spaces are
|
|
203
|
-
* write framing, not content. A styled line's closing escape moves when the
|
|
204
|
-
* line stops being the last of its span (a wrapped thinking paragraph growing
|
|
205
|
-
* by one row), and width-padded rows shift their trailing spaces as text
|
|
206
|
-
* grows; both leave the on-screen cells identical and must not count as a
|
|
207
|
-
* rewrite of a committed-candidate row. Committed scrollback rows are written
|
|
208
|
-
* with a full SGR/OSC reset terminator, so escape-placement drift between
|
|
209
|
-
* visually identical renders cannot bleed styles across rows.
|
|
210
|
-
*/
|
|
211
|
-
function normalizeRow(line: string): string {
|
|
212
|
-
return Bun.stripANSI(line).trimEnd();
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
function rowsVisiblyEqual(prev: string, cur: string): boolean {
|
|
216
|
-
return prev === cur || normalizeRow(prev) === normalizeRow(cur);
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
/**
|
|
220
|
-
* Whether `cur` is `prev` grown in place: the visible content of `prev` is a
|
|
221
|
-
* strict-or-equal prefix of `cur`'s (token streaming appending to the cursor
|
|
222
|
-
* row). Escape placement and pad drift are ignored, same as rowsVisiblyEqual.
|
|
223
|
-
*/
|
|
224
|
-
function rowVisiblyGrew(prev: string, cur: string): boolean {
|
|
225
|
-
return normalizeRow(cur).startsWith(normalizeRow(prev));
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
function hasValidSnapshot(
|
|
229
|
-
snapshot: LiveDiffSnapshot | undefined,
|
|
230
|
-
width: number,
|
|
231
|
-
generation: number,
|
|
232
|
-
): snapshot is LiveDiffSnapshot {
|
|
233
|
-
return snapshot !== undefined && snapshot.generation === generation && snapshot.width === width;
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
function commonPrefixLength(prev: readonly string[], cur: readonly string[]): number {
|
|
237
|
-
const limit = Math.min(prev.length, cur.length);
|
|
238
|
-
let i = 0;
|
|
239
|
-
while (i < limit && rowsVisiblyEqual(prev[i]!, cur[i]!)) i++;
|
|
240
|
-
return i;
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
function commonSuffixLength(prev: readonly string[], cur: readonly string[], prefixLength: number): number {
|
|
244
|
-
const limit = Math.min(prev.length - prefixLength, cur.length - prefixLength);
|
|
245
|
-
let i = 0;
|
|
246
|
-
while (i < limit && rowsVisiblyEqual(prev[prev.length - 1 - i]!, cur[cur.length - 1 - i]!)) i++;
|
|
247
|
-
return i;
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
function deriveLiveCommitState(
|
|
251
|
-
previous: LiveDiffSnapshot | undefined,
|
|
252
|
-
current: readonly string[],
|
|
253
|
-
width: number,
|
|
254
|
-
generation: number,
|
|
255
|
-
): LiveCommitState {
|
|
256
|
-
let appendOnly = false;
|
|
257
|
-
let volatileCooldown = 0;
|
|
258
|
-
let stablePrefixLength = 0;
|
|
259
|
-
let candidatePrefixLength = 0;
|
|
260
|
-
let candidatePrefixAge = 0;
|
|
261
|
-
let rewriteFloor = Number.POSITIVE_INFINITY;
|
|
262
|
-
let trailingRowGrowth = false;
|
|
263
|
-
if (hasValidSnapshot(previous, width, generation)) {
|
|
264
|
-
appendOnly = previous.appendOnly;
|
|
265
|
-
volatileCooldown = previous.volatileCooldown;
|
|
266
|
-
stablePrefixLength = previous.stablePrefixLength;
|
|
267
|
-
candidatePrefixLength = previous.candidatePrefixLength;
|
|
268
|
-
candidatePrefixAge = previous.candidatePrefixAge;
|
|
269
|
-
rewriteFloor = previous.rewriteFloor;
|
|
270
|
-
|
|
271
|
-
const prefixLength = commonPrefixLength(previous.lines, current);
|
|
272
|
-
const staticRender = prefixLength === previous.lines.length && prefixLength === current.length;
|
|
273
|
-
let cleanFrame = true;
|
|
274
|
-
if (!staticRender) {
|
|
275
|
-
const suffixLength = commonSuffixLength(previous.lines, current, prefixLength);
|
|
276
|
-
// Append-only growth never rewrites a row that may already have scrolled
|
|
277
|
-
// into native scrollback; it only grows the block at/near its tail. Two
|
|
278
|
-
// shapes qualify:
|
|
279
|
-
// - a pure insertion that preserves every previous row across a
|
|
280
|
-
// matching prefix + suffix (a bottom append, or an insertion above
|
|
281
|
-
// stable trailing chrome like a streaming tool's footer/border);
|
|
282
|
-
// - a rewrite whose divergence BEGINS inside the trailing
|
|
283
|
-
// TAIL_VOLATILITY_ROWS of the previous render — the streaming edge:
|
|
284
|
-
// the in-flight paragraph re-wrapping as words arrive (its last 1-2
|
|
285
|
-
// visual rows), an unclosed markdown token (`**bold`) re-rendering
|
|
286
|
-
// when its closer streams in, a wrap-shrink pushing the last word
|
|
287
|
-
// onto an appended row. That zone is held back from `safeLength`
|
|
288
|
-
// below, so a tolerated rewrite can never touch a row that was
|
|
289
|
-
// offered for commit.
|
|
290
|
-
// The anchor matters: the gap must START in the tail zone, not merely
|
|
291
|
-
// be small — a one-row ticker mid-block with stable rows beneath it
|
|
292
|
-
// would otherwise classify clean, get offered past, and rewrite
|
|
293
|
-
// committed rows on every tick. Any deeper divergent row means the
|
|
294
|
-
// block re-laid-out committed-candidate content — a rewrite, which
|
|
295
|
-
// suspends commits until the block re-earns append-only.
|
|
296
|
-
const preservedEveryRow = prefixLength + suffixLength >= previous.lines.length;
|
|
297
|
-
const tailConfined = preservedEveryRow || prefixLength >= previous.lines.length - TAIL_VOLATILITY_ROWS;
|
|
298
|
-
if (tailConfined && current.length >= previous.lines.length) {
|
|
299
|
-
// Strict trailing-row growth: every previous row except the last
|
|
300
|
-
// is visibly unchanged and the last grew in place as a visible
|
|
301
|
-
// prefix, with no rows appended — a line accumulating tokens.
|
|
302
|
-
// The sole divergent row is the block's physical last row, which
|
|
303
|
-
// the engine's window floor never commits while it stays last
|
|
304
|
-
// (chunkTo ≤ windowTop ≤ last row index), so the volatile-tail
|
|
305
|
-
// holdback below is unnecessary: the whole body is offerable and
|
|
306
|
-
// the block's scrolled-off head reaches native scrollback.
|
|
307
|
-
trailingRowGrowth =
|
|
308
|
-
current.length === previous.lines.length &&
|
|
309
|
-
prefixLength === previous.lines.length - 1 &&
|
|
310
|
-
rowVisiblyGrew(previous.lines[prefixLength]!, current[prefixLength]!);
|
|
311
|
-
if (volatileCooldown === 0) appendOnly = true;
|
|
312
|
-
// Clean growth inserts/rewrites rows at the divergence; a floor
|
|
313
|
-
// inside the preserved suffix travels down with it, a floor at or
|
|
314
|
-
// above the divergent zone stays put (conservative: a stale floor
|
|
315
|
-
// index can only point at an earlier row, never a later one).
|
|
316
|
-
const delta = current.length - previous.lines.length;
|
|
317
|
-
if (delta > 0 && Number.isFinite(rewriteFloor)) {
|
|
318
|
-
const suffixStart = Math.max(prefixLength, previous.lines.length - suffixLength);
|
|
319
|
-
if (rewriteFloor >= suffixStart) rewriteFloor += delta;
|
|
320
|
-
}
|
|
321
|
-
} else {
|
|
322
|
-
cleanFrame = false;
|
|
323
|
-
appendOnly = false;
|
|
324
|
-
volatileCooldown = VOLATILE_REARM_FRAMES;
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
if (cleanFrame && volatileCooldown > 0) volatileCooldown--;
|
|
328
|
-
|
|
329
|
-
// Stable-prefix ratchet, independent of append-only. `prefixLength` is
|
|
330
|
-
// this frame's visibly-unchanged leading run; the candidate accumulates
|
|
331
|
-
// the MINIMUM prefix across a STABLE_PREFIX_COMMIT_FRAMES window, so
|
|
332
|
-
// promotion means every promoted row stayed identical for the whole
|
|
333
|
-
// window (row r is inside frame i's common prefix iff r < p_i, so
|
|
334
|
-
// r < min(p) holds for every frame of the window). A row settling
|
|
335
|
-
// mid-window promotes at most two windows later. The engine audit owns
|
|
336
|
-
// any promoted rows that already committed (recommit, never loss).
|
|
337
|
-
if (prefixLength < stablePrefixLength) {
|
|
338
|
-
// A divergence inside the promoted run is the ratchet's proof of
|
|
339
|
-
// over-promotion: this row was visibly stable for a full window,
|
|
340
|
-
// got promoted (and likely committed), and then mutated anyway — a
|
|
341
|
-
// slow ticker (an agent row's tool/cost counter, a growing progress
|
|
342
|
-
// tree), not settling content. It will mutate again, and every
|
|
343
|
-
// promote→mutate cycle makes the engine audit recommit, spraying a
|
|
344
|
-
// stale snapshot of the block into native scrollback. Floor the
|
|
345
|
-
// ratchet at the divergence permanently: rows above it may still
|
|
346
|
-
// promote, rows at/below it never re-promote while the block lives.
|
|
347
|
-
// One-off re-layouts before any promotion (a call→result frame
|
|
348
|
-
// transition, a codespan finalizing) never hit this branch, and the
|
|
349
|
-
// append-only re-arm path commits the full block regardless of the
|
|
350
|
-
// floor.
|
|
351
|
-
rewriteFloor = Math.min(rewriteFloor, prefixLength);
|
|
352
|
-
stablePrefixLength = prefixLength;
|
|
353
|
-
candidatePrefixLength = prefixLength;
|
|
354
|
-
candidatePrefixAge = 0;
|
|
355
|
-
} else {
|
|
356
|
-
candidatePrefixLength =
|
|
357
|
-
candidatePrefixAge === 0 ? prefixLength : Math.min(candidatePrefixLength, prefixLength);
|
|
358
|
-
candidatePrefixAge++;
|
|
359
|
-
if (candidatePrefixAge >= STABLE_PREFIX_COMMIT_FRAMES) {
|
|
360
|
-
// Cap at the volatile-tail holdback: a long static stretch would
|
|
361
|
-
// otherwise promote the streaming edge itself (min prefix == full
|
|
362
|
-
// length), and the next chunk's tail re-wrap would then rewrite
|
|
363
|
-
// offered rows.
|
|
364
|
-
stablePrefixLength = Math.min(
|
|
365
|
-
candidatePrefixLength,
|
|
366
|
-
rewriteFloor,
|
|
367
|
-
Math.max(0, current.length - TAIL_VOLATILITY_ROWS),
|
|
368
|
-
);
|
|
369
|
-
candidatePrefixLength = prefixLength;
|
|
370
|
-
candidatePrefixAge = 0;
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
return {
|
|
376
|
-
appendOnly,
|
|
377
|
-
volatileCooldown,
|
|
378
|
-
stablePrefixLength,
|
|
379
|
-
candidatePrefixLength,
|
|
380
|
-
candidatePrefixAge,
|
|
381
|
-
rewriteFloor,
|
|
382
|
-
// A clean-streaming block's body is committable up to the volatile-tail
|
|
383
|
-
// holdback (the streaming edge is never offered, so its tolerated
|
|
384
|
-
// rewrites can never touch committed rows); otherwise the settled head
|
|
385
|
-
// still is — only the volatile tail stays deferred. Strict in-place
|
|
386
|
-
// growth of the trailing row skips the holdback: its only mutable row
|
|
387
|
-
// is the block's last, which cannot commit while it remains last.
|
|
388
|
-
safeLength: appendOnly
|
|
389
|
-
? trailingRowGrowth
|
|
390
|
-
? current.length
|
|
391
|
-
: Math.max(stablePrefixLength, current.length - TAIL_VOLATILITY_ROWS, 0)
|
|
392
|
-
: stablePrefixLength,
|
|
393
|
-
};
|
|
394
|
-
}
|
|
395
|
-
|
|
396
112
|
/**
|
|
397
113
|
* Transcript container that renders every block's current content each frame
|
|
398
|
-
* and reports the
|
|
399
|
-
* the
|
|
114
|
+
* and reports the native-scrollback exactness boundary
|
|
115
|
+
* (`NativeScrollbackLiveRegion`): the frame row below which every rendered
|
|
116
|
+
* row is final. The boundary covers the leading run of finalized blocks plus
|
|
117
|
+
* the first still-live block's declared settled rows
|
|
118
|
+
* ({@link FinalizableBlock.getTranscriptBlockSettledRows}). Rows below it
|
|
119
|
+
* commit to native scrollback as exact, audited content; rows above it that
|
|
120
|
+
* scroll off the window commit as frozen visual snapshots the engine never
|
|
121
|
+
* re-anchors or recommits (the tape records what was on screen).
|
|
400
122
|
*
|
|
401
|
-
* The engine never rewrites committed history: rows
|
|
402
|
-
*
|
|
403
|
-
*
|
|
404
|
-
*
|
|
405
|
-
*
|
|
406
|
-
* unfinalized tool, a streaming assistant message) stay below the seam so
|
|
407
|
-
* their rows do not enter history while they can still change; a streaming
|
|
408
|
-
* block whose render grows append-only deepens the seam through its settled
|
|
409
|
-
* head so a long reply's scrolled-off rows still reach scrollback mid-stream.
|
|
123
|
+
* The engine never rewrites committed history: rows that have entered the
|
|
124
|
+
* tape keep whatever bytes they were committed with ("let the history be"),
|
|
125
|
+
* while the visible window always repaints from each block's latest render —
|
|
126
|
+
* a late tool result, a post-finalize error pin, or an expand toggle is
|
|
127
|
+
* always reflected on screen while it remains in the window.
|
|
410
128
|
*
|
|
411
129
|
* Assembly is incremental: the returned array is persistent and mutated in
|
|
412
130
|
* place. Each block's render is still called every frame, but a block whose
|
|
@@ -420,33 +138,13 @@ export class TranscriptContainer
|
|
|
420
138
|
extends Container
|
|
421
139
|
implements NativeScrollbackLiveRegion, NativeScrollbackCommittedRows, RenderStablePrefix, ViewportTailProvider
|
|
422
140
|
{
|
|
423
|
-
// Bumped to retire every block
|
|
424
|
-
//
|
|
141
|
+
// Bumped to retire every block segment at once (theme change / clear); a
|
|
142
|
+
// segment is only reused when its stored generation matches.
|
|
425
143
|
#generation = 0;
|
|
426
|
-
// Local line index
|
|
427
|
-
//
|
|
428
|
-
//
|
|
144
|
+
// Local line index below which every row of the most recent render is
|
|
145
|
+
// final: the leading finalized blocks plus the first live block's declared
|
|
146
|
+
// settled rows. TUI commits rows to native scrollback only above it.
|
|
429
147
|
#nativeScrollbackLiveRegionStart: number | undefined;
|
|
430
|
-
// Local line index up to which the leading run of live blocks is safe to
|
|
431
|
-
// commit. Finalized blocks contribute their full body; still-live blocks
|
|
432
|
-
// contribute only while their render has been observed growing without
|
|
433
|
-
// visibly rewriting a previously rendered interior row (escape placement
|
|
434
|
-
// and pad drift are ignored). A rewrite suspends the block's contribution
|
|
435
|
-
// until it re-earns append-only via VOLATILE_REARM_FRAMES clean frames;
|
|
436
|
-
// the engine then backfills the stalled gap.
|
|
437
|
-
#nativeScrollbackCommitSafeEnd: number | undefined;
|
|
438
|
-
// Local line index up to which the leading run of live blocks is DURABLE: a
|
|
439
|
-
// commit-stable block's full body is permanent content even while its interior
|
|
440
|
-
// rows re-lay-out (a streaming markdown table re-aligning columns), so the
|
|
441
|
-
// engine must append their scroll-off snapshot rather than drop it. Reported
|
|
442
|
-
// separately from the byte-stable commit-safe end because these rows may still
|
|
443
|
-
// drift after commit; the engine commits them audit-exempt. Provisional
|
|
444
|
-
// (commit-unstable) blocks never extend it.
|
|
445
|
-
#nativeScrollbackSnapshotSafeEnd: number | undefined;
|
|
446
|
-
// Local line index through which lower finalized siblings are safe to OFFER to
|
|
447
|
-
// native scrollback while still audited. Unlike snapshotSafeEnd, rows below a
|
|
448
|
-
// live block are not durable: growth above them must repair stale history.
|
|
449
|
-
#nativeScrollbackOfferSafeEnd: number | undefined;
|
|
450
148
|
// Persistent assembled transcript rows. Rows before the stable floor are
|
|
451
149
|
// byte-identical to the previous render; rows at/after it were re-pushed.
|
|
452
150
|
#lines: string[] = [];
|
|
@@ -487,46 +185,31 @@ export class TranscriptContainer
|
|
|
487
185
|
return this.#nativeScrollbackLiveRegionStart;
|
|
488
186
|
}
|
|
489
187
|
|
|
490
|
-
getNativeScrollbackCommitSafeEnd(): number | undefined {
|
|
491
|
-
return this.#nativeScrollbackCommitSafeEnd;
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
getNativeScrollbackSnapshotSafeEnd(): number | undefined {
|
|
495
|
-
return this.#nativeScrollbackSnapshotSafeEnd;
|
|
496
|
-
}
|
|
497
|
-
|
|
498
|
-
getNativeScrollbackOfferSafeEnd(): number | undefined {
|
|
499
|
-
return this.#nativeScrollbackOfferSafeEnd;
|
|
500
|
-
}
|
|
501
|
-
|
|
502
188
|
/**
|
|
503
|
-
* Whether `component`
|
|
504
|
-
*
|
|
505
|
-
*
|
|
506
|
-
*
|
|
507
|
-
*
|
|
508
|
-
*
|
|
509
|
-
*
|
|
189
|
+
* Whether none of `component`'s rows (per the most recent render) have
|
|
190
|
+
* entered native scrollback. Callers that retract ephemeral blocks (IRC
|
|
191
|
+
* cards, displaceable todo/job snapshots) must check this: removing a
|
|
192
|
+
* block whose rows are already on the tape is an interior deletion of
|
|
193
|
+
* committed history the engine cannot express — the block must be sealed
|
|
194
|
+
* in place as history instead. A component that has never rendered has no
|
|
195
|
+
* committed rows and is safely removable.
|
|
510
196
|
*/
|
|
511
|
-
|
|
512
|
-
const
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
if (!isBlockFinalized(this.children[i]!)) return true;
|
|
197
|
+
isBlockUncommitted(component: Component): boolean {
|
|
198
|
+
for (const segment of this.#segments) {
|
|
199
|
+
if (segment.component !== component) continue;
|
|
200
|
+
return segment.rowCount === 0 || segment.startRow >= this.#committedRows;
|
|
516
201
|
}
|
|
517
|
-
return
|
|
202
|
+
return true;
|
|
518
203
|
}
|
|
519
204
|
|
|
520
205
|
/**
|
|
521
206
|
* Whether `component` is inside the live (repaintable) region exactly as
|
|
522
207
|
* {@link render} computes it: at/after the first still-mutating block, or
|
|
523
|
-
* the transcript tail when every block has finalized.
|
|
524
|
-
*
|
|
525
|
-
*
|
|
526
|
-
* the
|
|
527
|
-
*
|
|
528
|
-
* static bytes — the moment they sit above the seam, where their rows
|
|
529
|
-
* become commit-eligible native-scrollback history.
|
|
208
|
+
* the transcript tail when every block has finalized. Self-animating
|
|
209
|
+
* finalized blocks (a detached task's shimmering progress rows) poll this
|
|
210
|
+
* to stop animating — and settle on static bytes — the moment they sit
|
|
211
|
+
* above the seam, where their rows become commit-eligible native-scrollback
|
|
212
|
+
* history.
|
|
530
213
|
*/
|
|
531
214
|
isBlockInLiveRegion(component: Component): boolean {
|
|
532
215
|
const children = this.children;
|
|
@@ -589,22 +272,21 @@ export class TranscriptContainer
|
|
|
589
272
|
override render(width: number): readonly string[] {
|
|
590
273
|
width = Math.max(1, width);
|
|
591
274
|
this.#nativeScrollbackLiveRegionStart = undefined;
|
|
592
|
-
this.#nativeScrollbackCommitSafeEnd = undefined;
|
|
593
|
-
this.#nativeScrollbackSnapshotSafeEnd = undefined;
|
|
594
|
-
this.#nativeScrollbackOfferSafeEnd = undefined;
|
|
595
275
|
|
|
596
276
|
const count = this.children.length;
|
|
597
277
|
|
|
598
|
-
// The
|
|
599
|
-
//
|
|
600
|
-
//
|
|
601
|
-
//
|
|
602
|
-
//
|
|
603
|
-
//
|
|
604
|
-
let liveStartIndex =
|
|
278
|
+
// The commit boundary stops at the earliest still-mutating block. A
|
|
279
|
+
// block that has not finalized must gate it: out-of-band inserts
|
|
280
|
+
// (TTSR/todo cards) can append a finalized block *below* a tool that is
|
|
281
|
+
// still awaiting its result, and committing rows there would strand the
|
|
282
|
+
// tool's history rows on a mid-stream preview the late result never
|
|
283
|
+
// reaches.
|
|
284
|
+
let liveStartIndex = -1;
|
|
285
|
+
let hasLiveBlock = false;
|
|
605
286
|
for (let i = 0; i < count; i++) {
|
|
606
287
|
if (!isBlockFinalized(this.children[i]!)) {
|
|
607
288
|
liveStartIndex = i;
|
|
289
|
+
hasLiveBlock = true;
|
|
608
290
|
break;
|
|
609
291
|
}
|
|
610
292
|
}
|
|
@@ -630,27 +312,11 @@ export class TranscriptContainer
|
|
|
630
312
|
// invariant — otherwise re-pushed rows land after the stale frame.
|
|
631
313
|
if (!chainStable) lines.length = 0;
|
|
632
314
|
|
|
633
|
-
// Tracks whether we are still inside the leading run of commit-safe live
|
|
634
|
-
// blocks. The first still-live volatile block closes it, but rendering
|
|
635
|
-
// continues so lower blocks remain visible.
|
|
636
|
-
let commitSafeOpen = true;
|
|
637
|
-
// The live-region start is recorded at the first visible row at/after
|
|
638
|
-
// liveStartIndex; empty leading blocks (or a separator) must not claim it
|
|
639
|
-
// early.
|
|
640
|
-
let liveRecorded = false;
|
|
641
|
-
// Prefix boundary for finalized siblings rendered below the first live
|
|
642
|
-
// block. These rows may be offered to native scrollback, but they cannot
|
|
643
|
-
// extend snapshotSafeEnd because a live block above can still move them.
|
|
644
|
-
let offerSafeEnd: number | undefined;
|
|
645
|
-
// Offer rows must be a contiguous finalized run below the first live
|
|
646
|
-
// block. A later live/provisional block can still push rows below it, so
|
|
647
|
-
// finalized siblings after that barrier must stay forced-overflow.
|
|
648
|
-
let offerSafeOpen = true;
|
|
649
315
|
// Frame row cursor: rows emitted (reused or pushed) so far.
|
|
650
316
|
let row = 0;
|
|
651
317
|
let stableRows = 0;
|
|
652
318
|
for (let i = 0; i < count; i++) {
|
|
653
|
-
const child = this.children[i]
|
|
319
|
+
const child = this.children[i]!;
|
|
654
320
|
|
|
655
321
|
// This child's contribution: its current render with plain-blank
|
|
656
322
|
// top/bottom edges stripped (the container owns inter-block gaps).
|
|
@@ -659,7 +325,6 @@ export class TranscriptContainer
|
|
|
659
325
|
// immutable terminal history for the current width/generation. Blocks
|
|
660
326
|
// outside committed history still render normally so late results,
|
|
661
327
|
// post-finalize re-layouts, and expand toggles remain visible.
|
|
662
|
-
const previousSnapshot = child[kSnapshot];
|
|
663
328
|
const previous = previousSegments[i];
|
|
664
329
|
const finalized = isBlockFinalized(child);
|
|
665
330
|
const version = getBlockVersion(child);
|
|
@@ -691,34 +356,15 @@ export class TranscriptContainer
|
|
|
691
356
|
previous.width === width &&
|
|
692
357
|
previous.generation === this.#generation);
|
|
693
358
|
const contribution = reusable ? previous.contribution : stripPlainBlankEdges(raw);
|
|
694
|
-
let liveCommitState: LiveCommitState | undefined;
|
|
695
|
-
// Provisional live renders (commit-unstable blocks) never feed the
|
|
696
|
-
// promotion machinery: their settled-looking rows are replaced
|
|
697
|
-
// wholesale on finalize, so offering them would commit a stale
|
|
698
|
-
// preview the result render can only duplicate, never erase.
|
|
699
|
-
if (i >= liveStartIndex && !finalized && isBlockCommitStable(child)) {
|
|
700
|
-
liveCommitState = deriveLiveCommitState(previousSnapshot, contribution, width, this.#generation);
|
|
701
|
-
}
|
|
702
|
-
// Cache the latest contribution as the next frame's diff input.
|
|
703
|
-
child[kSnapshot] = {
|
|
704
|
-
width,
|
|
705
|
-
lines: contribution,
|
|
706
|
-
generation: this.#generation,
|
|
707
|
-
appendOnly: liveCommitState?.appendOnly ?? false,
|
|
708
|
-
volatileCooldown: liveCommitState?.volatileCooldown ?? 0,
|
|
709
|
-
stablePrefixLength: liveCommitState?.stablePrefixLength ?? 0,
|
|
710
|
-
candidatePrefixLength: liveCommitState?.candidatePrefixLength ?? 0,
|
|
711
|
-
candidatePrefixAge: liveCommitState?.candidatePrefixAge ?? 0,
|
|
712
|
-
rewriteFloor: liveCommitState?.rewriteFloor ?? Number.POSITIVE_INFINITY,
|
|
713
|
-
};
|
|
714
359
|
|
|
715
360
|
// Empty (or stripped-to-nothing) children contribute nothing and never
|
|
716
|
-
// affect spacing
|
|
717
|
-
//
|
|
361
|
+
// affect spacing. An empty still-live child still gates the commit
|
|
362
|
+
// boundary at its position: if it later gains rows, it pushes
|
|
718
363
|
// everything below it.
|
|
719
364
|
if (contribution.length === 0) {
|
|
720
|
-
if (
|
|
721
|
-
|
|
365
|
+
if (hasLiveBlock && i === liveStartIndex) {
|
|
366
|
+
this.#nativeScrollbackLiveRegionStart = row;
|
|
367
|
+
}
|
|
722
368
|
if (chainStable && !(reusable && previous.rowCount === 0 && previous.startRow === row)) {
|
|
723
369
|
chainStable = false;
|
|
724
370
|
lines.length = row;
|
|
@@ -747,11 +393,19 @@ export class TranscriptContainer
|
|
|
747
393
|
const sep = row > 0 && !isPlainBlank(lines[row - 1]!) ? 1 : 0;
|
|
748
394
|
|
|
749
395
|
// The separator before the first live block stays in the committed
|
|
750
|
-
// prefix (it is deterministic once the prior block's body is
|
|
751
|
-
//
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
396
|
+
// prefix (it is deterministic once the prior block's body is
|
|
397
|
+
// settled); the boundary then extends through the live block's
|
|
398
|
+
// declared settled rows, mapped from its raw render into the
|
|
399
|
+
// stripped contribution.
|
|
400
|
+
if (hasLiveBlock && i === liveStartIndex) {
|
|
401
|
+
let settled = 0;
|
|
402
|
+
const settledRaw = getBlockSettledRows(child);
|
|
403
|
+
if (settledRaw > 0) {
|
|
404
|
+
let lead = 0;
|
|
405
|
+
while (lead < raw.length && isPlainBlank(raw[lead]!)) lead++;
|
|
406
|
+
settled = Math.max(0, Math.min(contribution.length, settledRaw - lead));
|
|
407
|
+
}
|
|
408
|
+
this.#nativeScrollbackLiveRegionStart = row + sep + settled;
|
|
755
409
|
}
|
|
756
410
|
|
|
757
411
|
const rowCount = sep + contribution.length;
|
|
@@ -767,35 +421,6 @@ export class TranscriptContainer
|
|
|
767
421
|
for (let j = 0; j < contribution.length; j++) lines.push(contribution[j]!);
|
|
768
422
|
}
|
|
769
423
|
|
|
770
|
-
const blockStart = row + sep;
|
|
771
|
-
if (i >= liveStartIndex && commitSafeOpen) {
|
|
772
|
-
const safeLength = finalized ? contribution.length : (liveCommitState?.safeLength ?? 0);
|
|
773
|
-
if (safeLength > 0) {
|
|
774
|
-
this.#nativeScrollbackCommitSafeEnd = blockStart + safeLength;
|
|
775
|
-
}
|
|
776
|
-
// Durable snapshot end: a commit-stable block's whole body is durable
|
|
777
|
-
// content — its scrolled-off rows are permanent even while interior
|
|
778
|
-
// rows re-lay-out (a streaming table re-aligning columns), so the
|
|
779
|
-
// engine must commit their snapshot on scroll-off rather than drop it.
|
|
780
|
-
// Finalized blocks are wholly durable; provisional (commit-unstable)
|
|
781
|
-
// blocks offer nothing beyond their byte-stable safe length.
|
|
782
|
-
const snapshotLength = finalized || isBlockCommitStable(child) ? contribution.length : safeLength;
|
|
783
|
-
if (snapshotLength > 0) {
|
|
784
|
-
this.#nativeScrollbackSnapshotSafeEnd = blockStart + snapshotLength;
|
|
785
|
-
}
|
|
786
|
-
// A finalized, fully safe block may let the contiguous safe run extend
|
|
787
|
-
// into blocks rendered below it. A still-live block keeps pushing lower
|
|
788
|
-
// rows around as it grows, so the run closes there.
|
|
789
|
-
if (!(finalized && safeLength >= contribution.length)) commitSafeOpen = false;
|
|
790
|
-
}
|
|
791
|
-
if (i > liveStartIndex) {
|
|
792
|
-
if (offerSafeOpen && finalized) {
|
|
793
|
-
offerSafeEnd = blockStart + contribution.length;
|
|
794
|
-
} else if (!finalized) {
|
|
795
|
-
offerSafeOpen = false;
|
|
796
|
-
}
|
|
797
|
-
}
|
|
798
|
-
|
|
799
424
|
segments[i] = {
|
|
800
425
|
component: child,
|
|
801
426
|
rawRef: raw,
|
|
@@ -813,7 +438,6 @@ export class TranscriptContainer
|
|
|
813
438
|
// Trailing shrink: blocks removed from the tail leave stale rows behind
|
|
814
439
|
// when every surviving segment was reused.
|
|
815
440
|
if (lines.length !== row) lines.length = row;
|
|
816
|
-
this.#nativeScrollbackOfferSafeEnd = offerSafeEnd;
|
|
817
441
|
this.#segments = segments;
|
|
818
442
|
this.#stableRowsFloor = Math.min(stableFloorBefore, stableRows, row);
|
|
819
443
|
return lines;
|