@agfpd/iapeer 0.4.54 → 0.4.55

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.4.54",
3
+ "version": "0.4.55",
4
4
  "description": "Run Claude Code and Codex CLI as local background agents that message each other, wake on demand, and share one memory.",
5
5
  "license": "Apache-2.0",
6
6
  "author": "agfpd",
@@ -41,6 +41,12 @@ function RuntimeCells({ row }: { row: PeerListing }): React.ReactElement {
41
41
  )
42
42
  }
43
43
 
44
+ /** Absolute screen reset written on every terminal resize: erase-all + cursor-home.
45
+ * Goes through Ink's OWN write path (useStdout().write → writeToStdout), which
46
+ * erases Ink's frame, writes these bytes, then REPLAYS the last frame with reset
47
+ * erase bookkeeping — so the replay lands at a KNOWN absolute origin (row 1). */
48
+ const RESIZE_SCREEN_RESET = '\x1b[2J\x1b[H'
49
+
44
50
  export function DashboardApp({
45
51
  env,
46
52
  onAction,
@@ -49,7 +55,7 @@ export function DashboardApp({
49
55
  onAction: (a: DashAction) => void
50
56
  }): React.ReactElement {
51
57
  const { exit } = useApp()
52
- const { stdout } = useStdout()
58
+ const { stdout, write } = useStdout()
53
59
  const [rows, setRows] = useState<PeerListing[]>(() => takePeersSnapshot(env))
54
60
  const [host, setHost] = useState<HostHeader | null>(null)
55
61
  const [now, setNow] = useState<number>(() => Date.now())
@@ -95,12 +101,26 @@ export function DashboardApp({
95
101
  }, [env])
96
102
  useEffect(() => {
97
103
  if (!stdout) return
98
- const onResize = (): void => setSize({ cols: dim(stdout.columns, 80), rows: dim(stdout.rows, 24) })
104
+ const onResize = (): void => {
105
+ // Ink erases frames by RELATIVE cursor math (cursor-up × line-count), and a real
106
+ // terminal displaces alt-screen content on window resize (Apple Terminal shifts it
107
+ // on height changes; a width change can re-wrap) — after which that relative erase
108
+ // misses rows and stale frame fragments survive FOREVER (live incident: a drag-
109
+ // resize stacked 9 footers; Ink itself full-clears only on width SHRINK). So on
110
+ // EVERY resize force an ABSOLUTE reset: wipe the screen + home the cursor, then let
111
+ // Ink replay the frame from that known origin. Written through Ink's write API —
112
+ // never raw to stdout behind its back (the 0.4.51 lesson): writeToStdout keeps its
113
+ // erase model in sync (clear → our bytes → replay with reset line-counts). This
114
+ // listener registers AFTER Ink's own resize handler (mount effect vs render), so
115
+ // the absolute reset is always the LAST word on the resize tick.
116
+ write(RESIZE_SCREEN_RESET)
117
+ setSize({ cols: dim(stdout.columns, 80), rows: dim(stdout.rows, 24) })
118
+ }
99
119
  stdout.on('resize', onResize)
100
120
  return () => {
101
121
  stdout.off('resize', onResize)
102
122
  }
103
- }, [stdout])
123
+ }, [stdout, write])
104
124
 
105
125
  const visible = filterRows(rows, filter)
106
126
  const cur = clampCursor(cursor, visible.length)
@@ -38,13 +38,25 @@ export const DASHBOARD_NOT_INTERACTIVE = -1
38
38
  /** argv prefix that re-invokes THIS package's CLI (compiled binary by verb / src via
39
39
  * bun). Same discriminator as supervisor/index.ts resolveDaemonSelfArgv: `$bunfs` in
40
40
  * the module path marks the compiled standalone (existsSync is NO discriminator —
41
- * Bun intercepts bunfs reads). */
42
- export function resolveSelfCliArgv(execPath: string, selfPath: string): string[] {
43
- return selfPath.includes('$bunfs') ? [execPath] : [execPath, selfPath]
41
+ * Bun intercepts bunfs reads).
42
+ *
43
+ * Discriminate on the MODULE's OWN url — NEVER on a `../..`-navigated path. The
44
+ * compiled bundle lives at `file:///$bunfs/root/<bundle>` (depth 2), so navigating
45
+ * two levels up ESCAPES /$bunfs and URL-normalization silently drops the marker
46
+ * (proven: compiled probe → `new URL('../../cli/index.ts', import.meta.url)` =
47
+ * `/cli/index.ts`). The prior code passed that navigated path here, so the COMPILED
48
+ * dashboard took the src branch and spawned `<binary> /cli/index.ts attach <peer>`
49
+ * → unknown verb → full usage + exit 2 (live incident: Enter→attach dead in
50
+ * `iapeer list`; from-src runs masked it — the navigated path is real there). The
51
+ * cli path is resolved lazily and ONLY on the src branch. */
52
+ export function resolveSelfCliArgv(execPath: string, moduleUrl: string): string[] {
53
+ return moduleUrl.includes('$bunfs')
54
+ ? [execPath]
55
+ : [execPath, fileURLToPath(new URL('../../cli/index.ts', moduleUrl))]
44
56
  }
45
57
 
46
58
  function selfCliArgv(): string[] {
47
- return resolveSelfCliArgv(process.execPath, fileURLToPath(new URL('../../cli/index.ts', import.meta.url)))
59
+ return resolveSelfCliArgv(process.execPath, import.meta.url)
48
60
  }
49
61
 
50
62
  export interface DashboardOptions {