@timurproko/a1 0.1.8-dev.259 → 0.1.8-dev.269

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.
Files changed (32) hide show
  1. package/bin/update-recovery.js +272 -0
  2. package/dist/foundation/release/bootstrap.d.ts +2 -0
  3. package/dist/foundation/release/bootstrap.js +4 -0
  4. package/dist/foundation/release/dependency-layer.d.ts +5 -1
  5. package/dist/foundation/release/dependency-layer.js +15 -6
  6. package/dist/foundation/release/index.d.ts +1 -0
  7. package/dist/foundation/release/index.js +1 -0
  8. package/dist/foundation/release/release-gc.js +2 -0
  9. package/dist/foundation/release/release-store.d.ts +4 -2
  10. package/dist/foundation/release/release-store.js +4 -4
  11. package/dist/foundation/release/update-recovery.d.ts +74 -0
  12. package/dist/foundation/release/update-recovery.js +352 -0
  13. package/dist/foundation/release/update-transaction.d.ts +10 -0
  14. package/dist/foundation/release/update-transaction.js +12 -1
  15. package/dist/foundation/release/update.d.ts +19 -1
  16. package/dist/foundation/release/update.js +67 -10
  17. package/dist/foundation/supervision/main.js +5 -2
  18. package/dist/integrations/pi/engine/conformance.d.ts +1 -1
  19. package/dist/integrations/pi/engine/conformance.js +9 -1
  20. package/dist/integrations/pi/engine/runtime-integration.js +10 -1
  21. package/dist/integrations/pi/engine/windows-filesystem-hygiene.d.ts +21 -0
  22. package/dist/integrations/pi/engine/windows-filesystem-hygiene.js +55 -0
  23. package/dist/integrations/pi/session-ui/session-shell-root.d.ts +1 -1
  24. package/dist/integrations/pi/session-ui/session-shell-root.js +29 -20
  25. package/dist/native/darwin-arm64/manifest.json +1 -1
  26. package/dist/native/linux-x64/manifest.json +1 -1
  27. package/dist/native/win32-x64/manifest.json +2 -2
  28. package/dist/native/win32-x64/process-guardian.exe +0 -0
  29. package/dist/runtime-payload-inventory.json +1 -0
  30. package/dist/ui/components/transcript-viewport.d.ts +8 -0
  31. package/dist/ui/components/transcript-viewport.js +30 -6
  32. package/package.json +1 -1
@@ -2,6 +2,7 @@ import { createAgentSessionFromServices, createAgentSessionRuntime, createAgentS
2
2
  import { resolvePiProjectTrustPreflight, } from "./project-trust-preflight.js";
3
3
  import { openSelectedPiSession, resolveSessionArgumentPath } from "./session-selection.js";
4
4
  import { markStartupPhase } from "../../../foundation/startup/index.js";
5
+ import { createWindowsNulCleanupExtension } from "./windows-filesystem-hygiene.js";
5
6
  /**
6
7
  * Mirrors pinned Pi's CLI startup: resolve the `models` patterns from settings
7
8
  * into a scoped model list, keep the resolver's warnings (e.g. "No models match
@@ -44,7 +45,15 @@ export async function createPiRuntimeServicesAfterTrust(options) {
44
45
  });
45
46
  const settingsManager = createSettingsManager(options.cwd, options.agentDir, trust.trusted);
46
47
  await markStartupPhase(process.env, "settings-loaded");
47
- const services = await createServices({ cwd: options.cwd, agentDir: options.agentDir, settingsManager });
48
+ const cleanupExtension = createWindowsNulCleanupExtension();
49
+ const services = await createServices({
50
+ cwd: options.cwd,
51
+ agentDir: options.agentDir,
52
+ settingsManager,
53
+ ...(cleanupExtension === null ? {} : {
54
+ resourceLoaderOptions: { extensionFactories: [cleanupExtension] },
55
+ }),
56
+ });
48
57
  await markStartupPhase(process.env, "pi-services");
49
58
  await markStartupPhase(process.env, "resource-discovery");
50
59
  return { services, trust };
@@ -0,0 +1,21 @@
1
+ import { type InlineExtension } from "@earendil-works/pi-coding-agent";
2
+ interface FileMetadata {
3
+ isFile(): boolean;
4
+ isSymbolicLink(): boolean;
5
+ }
6
+ interface CleanupFileSystem {
7
+ lstat(path: string): Promise<FileMetadata>;
8
+ unlink(path: string): Promise<void>;
9
+ }
10
+ interface CleanupPaths {
11
+ dirname(path: string): string;
12
+ join(...paths: string[]): string;
13
+ resolve(...paths: string[]): string;
14
+ }
15
+ export interface WindowsNulCleanupOptions {
16
+ readonly platform?: NodeJS.Platform;
17
+ readonly fileSystem?: CleanupFileSystem;
18
+ readonly paths?: CleanupPaths;
19
+ }
20
+ export declare function createWindowsNulCleanupExtension(options?: WindowsNulCleanupOptions): InlineExtension | null;
21
+ export {};
@@ -0,0 +1,55 @@
1
+ import { isBashToolResult, isEditToolResult, isWriteToolResult, } from "@earendil-works/pi-coding-agent";
2
+ import { lstat, unlink } from "node:fs/promises";
3
+ import { dirname, join, resolve } from "node:path";
4
+ const DEFAULT_FILE_SYSTEM = { lstat, unlink };
5
+ const DEFAULT_PATHS = { dirname, join, resolve };
6
+ const SIMPLE_CD = /(?:^|[;&|\n])\s*cd\s+(?:"([^"\r\n]+)"|'([^'\r\n]+)'|([^\s;&|]+))/;
7
+ export function createWindowsNulCleanupExtension(options = {}) {
8
+ if ((options.platform ?? process.platform) !== "win32")
9
+ return null;
10
+ const fileSystem = options.fileSystem ?? DEFAULT_FILE_SYSTEM;
11
+ const paths = options.paths ?? DEFAULT_PATHS;
12
+ return {
13
+ name: "windows-nul-file-cleanup",
14
+ factory: pi => registerNulCleanup(pi, fileSystem, paths),
15
+ };
16
+ }
17
+ function registerNulCleanup(pi, fileSystem, paths) {
18
+ const cleanDirectory = async (directory) => {
19
+ const candidate = paths.join(directory, "nul");
20
+ try {
21
+ const metadata = await fileSystem.lstat(candidate);
22
+ if (metadata.isFile() && !metadata.isSymbolicLink())
23
+ await fileSystem.unlink(candidate);
24
+ }
25
+ catch {
26
+ return;
27
+ }
28
+ };
29
+ pi.on("session_start", async (_event, ctx) => {
30
+ await cleanDirectory(ctx.cwd);
31
+ });
32
+ pi.on("tool_result", async (event, ctx) => {
33
+ const directories = new Set([ctx.cwd]);
34
+ if (isBashToolResult(event)) {
35
+ const command = stringInput(event.input, "command");
36
+ const target = command === null ? null : simpleCdTarget(command);
37
+ if (target !== null)
38
+ directories.add(paths.resolve(ctx.cwd, target));
39
+ }
40
+ if (isWriteToolResult(event) || isEditToolResult(event)) {
41
+ const target = stringInput(event.input, "path");
42
+ if (target !== null)
43
+ directories.add(paths.dirname(paths.resolve(ctx.cwd, target)));
44
+ }
45
+ await Promise.all(Array.from(directories, cleanDirectory));
46
+ });
47
+ }
48
+ function simpleCdTarget(command) {
49
+ const match = SIMPLE_CD.exec(command);
50
+ return match?.[1] ?? match?.[2] ?? match?.[3] ?? null;
51
+ }
52
+ function stringInput(input, key) {
53
+ const value = input[key];
54
+ return typeof value === "string" && value.length > 0 ? value : null;
55
+ }
@@ -92,7 +92,7 @@ export declare class OwnedUiSessionShellRoot implements PiTuiComponentPort {
92
92
  applyTranscriptBlock(block: OwnedUiSessionViewModel["transcript"][number]): void;
93
93
  render(width: number): readonly string[];
94
94
  viewportFrameDescriptor(): TranscriptViewportFrameDescriptor | null;
95
- /** Visible rows of the transient working-status tail, for rendering evidence. */
95
+ /** Visible non-selectable Steering, alignment, and Working rows, for rendering evidence. */
96
96
  viewportTransientTailRowCount(): number;
97
97
  hasActiveSelection(): boolean;
98
98
  setViewportConfig(config: OwnedUiViewportSettings): void;
@@ -45,7 +45,6 @@ export class OwnedUiSessionShellRoot {
45
45
  #dockInputReuseEnabled;
46
46
  #dockInputCandidate = false;
47
47
  #dockInputSnapshot;
48
- #dockInputStatusSignature;
49
48
  #visibleViewportSnapshot;
50
49
  #fullViewportCompositions = 0;
51
50
  #dockOnlyViewportCompositions = 0;
@@ -232,14 +231,18 @@ export class OwnedUiSessionShellRoot {
232
231
  // intentional final gutter after their right-aligned timestamp.
233
232
  const documentWidth = width;
234
233
  const document = this.#renderDocumentLayout(documentWidth);
234
+ const steeringRows = this.#renderQueued(width);
235
235
  const statusRows = this.#renderStatus(width);
236
- const statusSignature = statusRows.length === 0 ? undefined : `${statusRows.length}\u0000${statusRows.join("\u0000")}`;
236
+ const transientSignature = transientRowsSignature(steeringRows, statusRows);
237
237
  const snapshot = this.#visibleViewportSnapshot;
238
238
  const dockInputCandidate = this.#dockInputCandidate;
239
239
  const dockInputSnapshot = dockInputCandidate ? this.#dockInputSnapshot : undefined;
240
- const dockInputStatusSignature = dockInputCandidate ? this.#dockInputStatusSignature : statusSignature;
241
- // Invariant: queued rows stay docked while live Working rows form one scrollable tail.
242
- const scrollRows = [...document.rows, ...statusRows];
240
+ const dockInputTransientSignature = dockInputCandidate
241
+ ? dockInputSnapshot?.transientSignature ?? transientSignature
242
+ : transientSignature;
243
+ // Invariant: pending Steering and live Working rows share one non-selectable
244
+ // viewport tail; only non-working status, widgets, input, and footer stay docked.
245
+ const scrollRows = [...document.rows, ...steeringRows, ...statusRows];
243
246
  const dockRows = dock.rows;
244
247
  const selectableDocumentRowCount = document.rows.length;
245
248
  const dockStartRow = height - dockRows.length + 1;
@@ -256,7 +259,7 @@ export class OwnedUiSessionShellRoot {
256
259
  && snapshot.width === width
257
260
  && snapshot.height === height
258
261
  && snapshot.documentRows === (dockInputSnapshot?.documentRows ?? scrollRows)
259
- && snapshot.statusSignature === dockInputStatusSignature
262
+ && snapshot.transientSignature === dockInputTransientSignature
260
263
  && snapshot.promptAnchors === (dockInputSnapshot?.promptAnchors ?? document.promptAnchors)
261
264
  && snapshot.dockLength === dockRows.length
262
265
  && snapshot.inputSurface === (dockInputSnapshot?.inputSurface ?? this.#inputSurface)
@@ -264,9 +267,9 @@ export class OwnedUiSessionShellRoot {
264
267
  && snapshot.selectionRevision === (dockInputSnapshot?.selectionRevision ?? this.#viewportController.selectionRevision)
265
268
  ? this.#viewportController.composeDockOnly(dockRows, width, height)
266
269
  : null;
267
- // Performance: a still-active spinner can tick before its first delayed presentation.
268
- // Its newest tail signature is recomputed below and therefore cannot hide that change.
269
- if (frame !== null && dockInputStatusSignature !== statusSignature)
270
+ // Performance: a spinner tick or queue update can race the input-triggered
271
+ // render. Recompute the complete transient signature before reusing rows.
272
+ if (frame !== null && dockInputTransientSignature !== transientSignature)
270
273
  frame = null;
271
274
  if (frame === null) {
272
275
  frame = this.#viewportController.compose({
@@ -274,15 +277,16 @@ export class OwnedUiSessionShellRoot {
274
277
  ...(this.#viewportController.transcriptPointerSelecting
275
278
  ? { paintDocumentRow: heldNativeHyperlinkStyle }
276
279
  : {}),
277
- // Invariant: selection and copying stop at the real document tail; live Working
278
- // rows remain transient presentation chrome at every fit boundary.
280
+ // Invariant: selection and copying stop at the real document tail; Steering,
281
+ // fitting alignment, and live Working remain transient presentation chrome.
279
282
  selectableDocumentRowCount,
283
+ bottomAlignedTailRowCount: statusRows.length,
280
284
  dockRows,
281
285
  promptAnchors: document.promptAnchors,
282
286
  width,
283
287
  height,
284
- // Invariant: the control belongs immediately above the complete dock,
285
- // never on top of a queued or Working row.
288
+ // Invariant: the control belongs immediately above the complete dock and
289
+ // floats over transient viewport content rather than consuming a dock row.
286
290
  bottomControlRow: Math.max(0, height - Math.min(height, dockRows.length) - 1),
287
291
  theme: this.#viewportTheme,
288
292
  });
@@ -294,8 +298,7 @@ export class OwnedUiSessionShellRoot {
294
298
  width,
295
299
  height,
296
300
  documentRows: scrollRows,
297
- statusSignature,
298
- dockInputStatusSignature,
301
+ transientSignature,
299
302
  promptAnchors: document.promptAnchors,
300
303
  dockLength: dockRows.length,
301
304
  inputSurface: this.#inputSurface,
@@ -308,7 +311,7 @@ export class OwnedUiSessionShellRoot {
308
311
  viewportFrameDescriptor() {
309
312
  return this.#viewportController.frame?.descriptor ?? null;
310
313
  }
311
- /** Visible rows of the transient working-status tail, for rendering evidence. */
314
+ /** Visible non-selectable Steering, alignment, and Working rows, for rendering evidence. */
312
315
  viewportTransientTailRowCount() {
313
316
  return this.#viewportController.frame?.hits.transientTail.length ?? 0;
314
317
  }
@@ -338,7 +341,7 @@ export class OwnedUiSessionShellRoot {
338
341
  return this.#renderDockLayout(width).rows;
339
342
  }
340
343
  #renderDockLayout(width) {
341
- const queued = this.#view.editor.queuedSubmissions.length === 0 ? [] : this.#queued.render(width);
344
+ const queued = this.#customViewport ? [] : this.#renderQueued(width);
342
345
  const statusRows = this.#customViewport ? this.#status.renderDock(width) : this.#renderStatus(width);
343
346
  const transientRows = [...queued, ...statusRows];
344
347
  const aboveWidgets = this.#renderWidgets("aboveEditor", width);
@@ -352,6 +355,9 @@ export class OwnedUiSessionShellRoot {
352
355
  inputRows: input.length,
353
356
  };
354
357
  }
358
+ #renderQueued(width) {
359
+ return this.#view.editor.queuedSubmissions.length === 0 ? [] : this.#queued.render(width);
360
+ }
355
361
  layoutRoot() {
356
362
  const document = layoutPort(width => this.#renderDocument(width), () => this.invalidate());
357
363
  const queued = layoutPort(width => this.#view.editor.queuedSubmissions.length === 0 ? [] : this.#queued.render(width), () => this.#queued.invalidate());
@@ -781,14 +787,15 @@ export class OwnedUiSessionShellRoot {
781
787
  }
782
788
  #captureDockInputSnapshot() {
783
789
  const width = Math.max(1, this.#componentRuntime.getColumns());
790
+ const steeringRows = this.#renderQueued(width);
784
791
  const statusRows = this.#renderStatus(width);
785
- this.#dockInputStatusSignature = statusRows.length === 0 ? undefined : `${statusRows.length}\u0000${statusRows.join("\u0000")}`;
792
+ const transientSignature = transientRowsSignature(steeringRows, statusRows);
786
793
  const snapshot = this.#visibleViewportSnapshot;
787
794
  this.#dockInputSnapshot = snapshot === undefined
788
795
  ? undefined
789
796
  : {
790
797
  documentRows: snapshot.documentRows,
791
- statusSignature: this.#dockInputStatusSignature,
798
+ transientSignature,
792
799
  promptAnchors: snapshot.promptAnchors,
793
800
  inputSurface: this.#inputSurface,
794
801
  viewportRevision: this.#viewportController.presentationRevision,
@@ -824,7 +831,6 @@ export class OwnedUiSessionShellRoot {
824
831
  this.#visibleViewportSnapshot = undefined;
825
832
  this.#dockInputCandidate = false;
826
833
  this.#dockInputSnapshot = undefined;
827
- this.#dockInputStatusSignature = undefined;
828
834
  if (this.#inputSurface !== this.editor)
829
835
  this.#inputSurface.dispose?.();
830
836
  this.#extensionHeader?.dispose?.();
@@ -1156,3 +1162,6 @@ function isPackageExtensionSource(sourceInfo) {
1156
1162
  const source = sourceInfo?.source ?? "";
1157
1163
  return source.startsWith("npm:") || source.startsWith("git:");
1158
1164
  }
1165
+ function transientRowsSignature(steeringRows, statusRows) {
1166
+ return `${steeringRows.length}\u0000${steeringRows.join("\u0000")}\u0001${statusRows.length}\u0000${statusRows.join("\u0000")}`;
1167
+ }
@@ -5,7 +5,7 @@
5
5
  "platform": "darwin",
6
6
  "architecture": "arm64",
7
7
  "capability": "supported",
8
- "builtAt": "2026-09-06T11:53:56.572Z",
8
+ "builtAt": "2026-09-06T14:17:35.764Z",
9
9
  "artifact": {
10
10
  "filename": "process-guardian",
11
11
  "sha256": "dc03605e5780e2aeebb4ecafd62868dc673e22ddd38b024a369aff704ff136dc",
@@ -5,7 +5,7 @@
5
5
  "platform": "linux",
6
6
  "architecture": "x64",
7
7
  "capability": "supported",
8
- "builtAt": "2026-09-06T11:53:48.191Z",
8
+ "builtAt": "2026-09-06T14:17:46.307Z",
9
9
  "artifact": {
10
10
  "filename": "process-guardian",
11
11
  "sha256": "ee8a00eaaf79c707459bbbfb52518e9739314967049fbe5fa625f36ce33db9ee",
@@ -5,10 +5,10 @@
5
5
  "platform": "win32",
6
6
  "architecture": "x64",
7
7
  "capability": "supported",
8
- "builtAt": "2026-09-06T11:54:20.386Z",
8
+ "builtAt": "2026-09-06T14:18:04.630Z",
9
9
  "artifact": {
10
10
  "filename": "process-guardian.exe",
11
- "sha256": "4fc782bdb0b01e3aa12377e9d79d5e8d6222d58b6f0672d60c0dafbef9987586",
11
+ "sha256": "d78243dd7f7852c64abc40b2b544039eaa9eba0d1f0bd42317002a97e2e84c7b",
12
12
  "size": 177664
13
13
  },
14
14
  "provenance": {
@@ -5,6 +5,7 @@
5
5
  "bin/guardian.js",
6
6
  "bin/supervisor.js",
7
7
  "bin/ui.js",
8
+ "bin/update-recovery.js",
8
9
  "bin/warmup.js"
9
10
  ],
10
11
  "declaredAssets": [
@@ -24,6 +24,8 @@ export interface TranscriptViewportFrameInput {
24
24
  readonly paintDocumentRow?: (row: string) => string;
25
25
  /** Leading document rows that participate in pointer selection and copying. */
26
26
  readonly selectableDocumentRowCount?: number;
27
+ /** Final transient rows to bottom-align with unused viewport space while content fits. */
28
+ readonly bottomAlignedTailRowCount?: number;
27
29
  readonly dockRows: readonly string[];
28
30
  readonly promptAnchors: readonly TranscriptPromptAnchor[];
29
31
  readonly width: number;
@@ -86,6 +88,12 @@ export interface TranscriptViewportFrameDescriptor {
86
88
  };
87
89
  readonly previousFollowingEnd: boolean | null;
88
90
  readonly followingEnd: boolean;
91
+ /** Complete non-selectable suffix, including pending steering, alignment, and status rows. */
92
+ readonly transientRowCount: number;
93
+ /** Flexible rows inserted before the bottom-aligned live status while content fits. */
94
+ readonly transientAlignmentGapRows: number;
95
+ /** Live status rows at the end of the transient suffix. */
96
+ readonly bottomAlignedTailRowCount: number;
89
97
  readonly verticalShiftRows: number;
90
98
  readonly safeVerticalShift: boolean;
91
99
  /** Monotonic interaction revision used to reject stale selection evidence. */
@@ -240,7 +240,19 @@ export class TranscriptViewport {
240
240
  const previousDescriptor = this.#frame?.descriptor ?? null;
241
241
  const dock = input.dockRows.length > height ? input.dockRows.slice(-height) : [...input.dockRows];
242
242
  const viewportHeight = Math.max(0, height - dock.length);
243
- this.#maxScroll = Math.max(0, input.documentRows.length - viewportHeight);
243
+ const bottomAlignedTailRowCount = clamp(input.bottomAlignedTailRowCount ?? 0, 0, input.documentRows.length);
244
+ const transientAlignmentGapRows = bottomAlignedTailRowCount > 0
245
+ ? Math.max(0, viewportHeight - input.documentRows.length)
246
+ : 0;
247
+ const tailStart = input.documentRows.length - bottomAlignedTailRowCount;
248
+ const documentRows = transientAlignmentGapRows === 0
249
+ ? input.documentRows
250
+ : [
251
+ ...input.documentRows.slice(0, tailStart),
252
+ ...Array.from({ length: transientAlignmentGapRows }, () => ""),
253
+ ...input.documentRows.slice(tailStart),
254
+ ];
255
+ this.#maxScroll = Math.max(0, documentRows.length - viewportHeight);
244
256
  if (this.#followingEnd)
245
257
  this.#scrollTop = this.#maxScroll;
246
258
  else
@@ -250,7 +262,7 @@ export class TranscriptViewport {
250
262
  if (this.#followingEnd)
251
263
  this.#newMessages = 0;
252
264
  const geometry = scrollbarGeometry({
253
- contentLength: input.documentRows.length,
265
+ contentLength: documentRows.length,
254
266
  viewportHeight,
255
267
  scroll: this.#scrollTop,
256
268
  // Compatibility: the session rail deliberately starts one line below the viewport top.
@@ -266,7 +278,7 @@ export class TranscriptViewport {
266
278
  now: input.now ?? Date.now(),
267
279
  });
268
280
  const contentWidth = presentation.reservesSpace ? Math.max(1, width - 1) : width;
269
- this.#documentRows = input.documentRows;
281
+ this.#documentRows = documentRows;
270
282
  this.#selectableDocumentRowCount = clamp(input.selectableDocumentRowCount ?? input.documentRows.length, 0, input.documentRows.length);
271
283
  this.#promptAnchors = input.promptAnchors;
272
284
  this.#contentWidth = contentWidth;
@@ -278,7 +290,7 @@ export class TranscriptViewport {
278
290
  trimCache(this.#finalRowCache, cacheLimit);
279
291
  const paintId = this.#functionId(paintDocumentRow);
280
292
  const paintRecomputedRows = new Set();
281
- const visible = input.documentRows
293
+ const visible = documentRows
282
294
  .slice(this.#scrollTop, this.#scrollTop + viewportHeight)
283
295
  .map((row, index) => {
284
296
  const painted = cachedString(this.#paintedRowCache, `${paintId}\u0000${row}`, cacheLimit, () => paintDocumentRow(row));
@@ -366,7 +378,7 @@ export class TranscriptViewport {
366
378
  }
367
379
  const nextDocumentRange = {
368
380
  start: this.#scrollTop,
369
- end: Math.min(input.documentRows.length, this.#scrollTop + viewportHeight),
381
+ end: Math.min(documentRows.length, this.#scrollTop + viewportHeight),
370
382
  };
371
383
  const previousDocumentRange = previousDescriptor?.nextDocumentRange ?? null;
372
384
  const sameGeometry = previousDescriptor !== null
@@ -391,6 +403,9 @@ export class TranscriptViewport {
391
403
  nextDocumentRange,
392
404
  previousFollowingEnd: previousDescriptor?.followingEnd ?? null,
393
405
  followingEnd: this.#followingEnd,
406
+ transientRowCount: Math.max(0, documentRows.length - this.#selectableDocumentRowCount),
407
+ transientAlignmentGapRows,
408
+ bottomAlignedTailRowCount,
394
409
  verticalShiftRows,
395
410
  safeVerticalShift,
396
411
  selectionRevision: composingSelectionRevision,
@@ -416,7 +431,7 @@ export class TranscriptViewport {
416
431
  },
417
432
  sticky: stickyActive ? { row: 1, target: governing.firstRow, width: contentWidth } : null,
418
433
  bottom: bottomHit,
419
- transientTail: Array.from({ length: Math.max(0, Math.min(input.documentRows.length, this.#scrollTop + viewportHeight) - Math.max(this.#scrollTop, this.#selectableDocumentRowCount)) }, (_row, index) => Math.max(this.#scrollTop, this.#selectableDocumentRowCount) - this.#scrollTop + index + 1),
434
+ transientTail: Array.from({ length: Math.max(0, Math.min(documentRows.length, this.#scrollTop + viewportHeight) - Math.max(this.#scrollTop, this.#selectableDocumentRowCount)) }, (_row, index) => Math.max(this.#scrollTop, this.#selectableDocumentRowCount) - this.#scrollTop + index + 1),
420
435
  };
421
436
  const frame = {
422
437
  rows: frameRows,
@@ -462,6 +477,9 @@ export class TranscriptViewport {
462
477
  nextDocumentRange: previous.descriptor.nextDocumentRange,
463
478
  previousFollowingEnd: previous.followingEnd,
464
479
  followingEnd: previous.followingEnd,
480
+ transientRowCount: previous.descriptor.transientRowCount,
481
+ transientAlignmentGapRows: previous.descriptor.transientAlignmentGapRows,
482
+ bottomAlignedTailRowCount: previous.descriptor.bottomAlignedTailRowCount,
465
483
  verticalShiftRows: 0,
466
484
  safeVerticalShift: false,
467
485
  selectionRevision: this.#selectionRevision,
@@ -587,6 +605,12 @@ export function assertTranscriptViewportFrameDescriptor(descriptor) {
587
605
  assertDocumentRange(descriptor.nextDocumentRange, "next");
588
606
  if (descriptor.previousDocumentRange !== null)
589
607
  assertDocumentRange(descriptor.previousDocumentRange, "previous");
608
+ if (!Number.isSafeInteger(descriptor.transientRowCount) || descriptor.transientRowCount < 0
609
+ || !Number.isSafeInteger(descriptor.transientAlignmentGapRows) || descriptor.transientAlignmentGapRows < 0
610
+ || !Number.isSafeInteger(descriptor.bottomAlignedTailRowCount) || descriptor.bottomAlignedTailRowCount < 0
611
+ || descriptor.transientAlignmentGapRows + descriptor.bottomAlignedTailRowCount > descriptor.transientRowCount) {
612
+ throw new TypeError("viewport frame descriptor transient geometry is invalid");
613
+ }
590
614
  if (!Number.isSafeInteger(descriptor.verticalShiftRows))
591
615
  throw new TypeError("viewport frame descriptor shift is invalid");
592
616
  if (descriptor.previousDocumentRange === null && descriptor.verticalShiftRows !== 0) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@timurproko/a1",
3
- "version": "0.1.8-dev.259",
3
+ "version": "0.1.8-dev.269",
4
4
  "description": "Standalone terminal workspace for supervised native and managed agents",
5
5
  "type": "module",
6
6
  "packageManager": "npm@11.13.0",