@agfpd/iapeer 0.4.7 → 0.4.8

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.7",
3
+ "version": "0.4.8",
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",
@@ -12,7 +12,7 @@
12
12
  // @xterm/headless bundles under `bun build --compile` (pure JS; the Bun-native pty has no native addon).
13
13
  import { Terminal } from '@xterm/headless'
14
14
  import { SerializeAddon } from '@xterm/addon-serialize'
15
- import { appendFileSync, closeSync, existsSync, mkdirSync, openSync, unlinkSync, writeFileSync, writeSync } from 'node:fs'
15
+ import { appendFileSync, closeSync, existsSync, fstatSync, mkdirSync, openSync, statSync, unlinkSync, writeFileSync, writeSync } from 'node:fs'
16
16
  import { dirname } from 'node:path'
17
17
  import {
18
18
  BackpressureWriter,
@@ -157,14 +157,43 @@ export function runSupervisorDaemon(opts: SupervisorDaemonOptions): void {
157
157
  // corrupts multibyte glyphs like ❯/›; the canonical latin1/Buffer grabli). Parent dir 0700, matching
158
158
  // launch's mkdir before pipe-pane. Unset paneLogPath → no log (throwaway/tick).
159
159
  let paneLogFd = -1
160
- if (opts.paneLogPath) {
160
+ let paneLogIno = -1 // inode the fd points at — to detect an out-from-under-us unlink/replace
161
+ let paneLogCheckedMs = 0
162
+ const PANELOG_HEAL_MS = 1000 // throttle the path-existence re-check to ≤1/sec regardless of output volume
163
+ const openPaneLog = (): void => {
164
+ if (!opts.paneLogPath) return
161
165
  try {
162
166
  mkdirSync(dirname(opts.paneLogPath), { recursive: true, mode: 0o700 })
167
+ if (paneLogFd >= 0) try { closeSync(paneLogFd) } catch { /* */ }
163
168
  paneLogFd = openSync(opts.paneLogPath, 'a')
169
+ paneLogIno = fstatSync(paneLogFd).ino
164
170
  } catch {
165
171
  paneLogFd = -1 // best-effort: a log open hiccup never blocks serving the session
172
+ paneLogIno = -1
166
173
  }
167
174
  }
175
+ // SELF-HEAL (Defect 2): the pane-log path can be unlinked out from under a live session (observed on
176
+ // the fleet: long-lived sessions' lifecycle pane-logs vanished while the supervisor ran). The fd then
177
+ // keeps writing to a now-unlinked inode — the path stays gone and every occupancy reader of
178
+ // `<logDir>/<identity>.log` goes blind: composer-busy delivery detection AND telegram-runtime's
179
+ // mtime-based typing/activity indicator (shared file). Throttled re-check before a write: if the path
180
+ // vanished or now maps to a DIFFERENT inode than the fd, reopen 'a'. capPaneLogs truncates IN PLACE
181
+ // (same inode) so a normal cap never triggers a spurious reopen. Tied to output (no idle wakeups) —
182
+ // exactly when the log matters.
183
+ const healPaneLog = (): void => {
184
+ if (!opts.paneLogPath || paneLogFd < 0) return
185
+ const now = Date.now()
186
+ if (now - paneLogCheckedMs < PANELOG_HEAL_MS) return
187
+ paneLogCheckedMs = now
188
+ let ino = -1
189
+ try {
190
+ ino = statSync(opts.paneLogPath).ino
191
+ } catch {
192
+ ino = -1 // path gone
193
+ }
194
+ if (ino !== paneLogIno) openPaneLog()
195
+ }
196
+ openPaneLog()
168
197
 
169
198
  const clients = new Set<unknown>()
170
199
  const writers = new WeakMap<object, BackpressureWriter>()
@@ -234,12 +263,14 @@ export function runSupervisorDaemon(opts: SupervisorDaemonOptions): void {
234
263
  const buf = Buffer.isBuffer(d) ? d : typeof d === 'string' ? Buffer.from(d, 'utf8') : Buffer.from(d)
235
264
  // PANE-LOG: the RAW child bytes, exactly as pipe-pane writes them (before any query-stripping
236
265
  // for clients) — written as a Buffer, never a string. The `s` below is for query DETECTION only.
237
- if (paneLogFd >= 0)
266
+ if (paneLogFd >= 0) {
267
+ healPaneLog() // reopen if the path was unlinked/replaced out from under us (Defect 2)
238
268
  try {
239
269
  writeSync(paneLogFd, buf)
240
270
  } catch {
241
271
  /* best-effort */
242
272
  }
273
+ }
243
274
  const s = buf.toString('latin1')
244
275
  writeSeq++ // a new write arrived (lets the attach drain detect bytes that land mid-await)
245
276
  writeChain = new Promise<void>(resolve => xterm.write(buf, () => resolve())) // authoritative model (+ parse barrier)