@agfpd/iapeer 0.2.88 → 0.2.89

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agfpd/iapeer",
3
- "version": "0.2.88",
3
+ "version": "0.2.89",
4
4
  "description": "Foundation core for the iapeer multi-agent ecosystem: identity, registry, storage, codec.",
5
5
  "license": "Apache-2.0",
6
6
  "author": "agfpd",
@@ -30,22 +30,19 @@ export function modelToPlainText(term: Terminal, cols: number, rows: number): st
30
30
  }
31
31
 
32
32
  /**
33
- * ATTACH SNAPSHOT (scrollback fix): the FULL model buffer — scrollback (`0..baseY`) + live viewport
34
- * (`baseY..baseY+rows`) — as PHYSICAL rows of plain text. This is the resolved-buffer reconstruction:
35
- * the @xterm model has already flattened claude's thousands of relative-cursor in-place redraws
36
- * (no DECSTBM, no alt-screen — see the daemon census) into final COMMITTED lines, so emitting these
37
- * rows as `line\r\n` into a fresh terminal reproduces native scrollback with NO duplication. Replaying
38
- * the child's RAW frame bytes can't do this (relative cursor-up can't reach scrolled-off lines on a
39
- * linear dump → overlap), and SerializeAddon re-emits escapes that the no-scroll-region redraws turn
40
- * into duplicate scrollback. Trailing blank rows are dropped so a fresh/short session emits no wall of
41
- * blanks. SGR colour is a deliberate follow-up — plain text first kills the mush without colour as a
42
- * second source of truth.
33
+ * ATTACH HISTORY rows — the model's SCROLLBACK ONLY (rows `0..baseY`, the committed history ABOVE the
34
+ * live viewport), as physical rows of plain text. The LIVE viewport (`baseY..`) is DELIBERATELY EXCLUDED:
35
+ * claude paints the current screen itself (its live render, forwarded right after attach), so including
36
+ * the viewport here would DOUBLE-PAINT it — the snapshot's copy collides with claude's live repaint →
37
+ * smeared overlap (the recurring bug). So on attach we ONLY prepend the history; claude shows the current
38
+ * screen. The @xterm model has already flattened claude's relative-cursor redraws into committed lines, so
39
+ * these rows render as native scrollback with no duplication. Trailing blank rows are dropped. SGR colour
40
+ * is a deliberate follow-up — plain text first.
43
41
  */
44
- export function modelAttachSnapshotRows(term: Terminal, cols: number, rows: number): string[] {
42
+ export function modelAttachSnapshotRows(term: Terminal, cols: number): string[] {
45
43
  const b = term.buffer.active
46
44
  const out: string[] = []
47
- const end = b.baseY + rows
48
- for (let y = 0; y < end; y++) {
45
+ for (let y = 0; y < b.baseY; y++) {
49
46
  const l = b.getLine(y)
50
47
  out.push(l ? l.translateToString(true, 0, cols) : '')
51
48
  }
@@ -311,13 +311,14 @@ export function runSupervisorDaemon(opts: SupervisorDaemonOptions): void {
311
311
  // scrollback for claude's relative-cursor, no-scroll-region TUI. Scrollback rows are spinner-stripped, the
312
312
  // viewport is verbatim. NO clears (\x1b[2J/3J): a fresh window needs none; Apple Terminal mishandles them.
313
313
  const sendSnapshot = (s: { write(b: Buffer): number }): void => {
314
- const rowsArr = modelAttachSnapshotRows(xterm, cols, rows)
315
- if (rowsArr.length > 0) {
316
- const cleaned = stripSpinnerScrollback(rowsArr.join('\r\n'), rows) // dechrome scrollback, viewport verbatim
317
- const body = '\x1b[0m' + terminalModesPrefix(xterm) + cleaned + '\r\n'
318
- sendClient(s, frame(FRAME_DATA, Buffer.from(body, 'utf8'))) // UTF-8: rows carry real glyphs (❯, box chars)
319
- }
320
- initializing.delete(s as object) // snapshot sent → resume live forwarding for this client
314
+ const historyRows = modelAttachSnapshotRows(xterm, cols) // SCROLLBACK only — claude paints the viewport live
315
+ const cleaned = stripSpinnerScrollback(historyRows.join('\r\n'), 0) // strip spinner remnants from ALL of history
316
+ // Prepend the committed HISTORY above the screen claude is about to repaint, after a modes-restore prefix.
317
+ // NO viewport rows — that is claude's job (forwarded live), which avoids the snapshot↔live double-paint
318
+ // overlap. Always send at least the modes prefix (restores bracketed-paste / app-cursor for a fresh session).
319
+ const body = '\x1b[0m' + terminalModesPrefix(xterm) + (cleaned ? cleaned + '\r\n' : '')
320
+ sendClient(s, frame(FRAME_DATA, Buffer.from(body, 'utf8'))) // UTF-8: rows carry real glyphs (❯, box chars)
321
+ initializing.delete(s as object) // history sent → resume live forwarding (claude's viewport repaint)
321
322
  }
322
323
 
323
324
  // Resize the model + child pty to (c, r). Shared by LATER client resizes and the last-detach restore.
@@ -356,16 +357,16 @@ export function runSupervisorDaemon(opts: SupervisorDaemonOptions): void {
356
357
  cols = c
357
358
  rows = r
358
359
  }
359
- sendSnapshot(s) // synchronous: build + send + lift gate (no await before the child resize below)
360
- // CHILD resize only when the size actually CHANGED. A reattach at the SAME geometry must NOT SIGWINCH the
361
- // child — a gratuitous SIGWINCH makes claude re-render on-screen content, which (if taller than the
362
- // viewport) scrolls a DUPLICATE copy into the model. Same-size reattach = pure snapshot, zero pollution.
363
- if (changed) {
364
- try {
365
- child.terminal.resize(c, r) // SIGWINCH the child → live reflow (post-snapshot, forwarded live)
366
- } catch {
367
- /* transient */
368
- }
360
+ sendSnapshot(s) // prepend the scrollback HISTORY (no viewport) + lift gate — synchronous, no interleave
361
+ // Force claude to repaint its CURRENT viewport BELOW the prepended history. A SIGWINCH triggers a full
362
+ // redraw; the (r-1)→r nudge guarantees it fires even on a SAME-size reattach (else an idle claude would
363
+ // leave the viewport blank until its next frame). Safe now: the snapshot carries NO viewport, so claude's
364
+ // repaint has nothing to collide with — the smeared-overlap class is gone at its source.
365
+ try {
366
+ child.terminal.resize(c, Math.max(1, r - 1))
367
+ child.terminal.resize(c, r)
368
+ } catch {
369
+ /* transient */
369
370
  }
370
371
  }
371
372
  // Restore the serve geometry when the last client detaches, so the unattached session never sticks at