@agfpd/iapeer 0.4.19 → 0.4.20

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 CHANGED
@@ -10,6 +10,24 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
10
10
 
11
11
  ## [Unreleased]
12
12
 
13
+ ## [0.4.20] - 2026-06-22
14
+
15
+ ### Fixed
16
+
17
+ - **Codex hosted-delivery no longer false-fails.** The warm-delivery landing-confirm
18
+ (`deliverViaHost`) confirmed a message "landed" only by a transcript/session-jsonl mtime
19
+ advance within a 3000ms grace. Claude writes the user turn to its transcript promptly, but
20
+ **codex writes its session jsonl only at model-turn-start** (gated by time-to-first-token,
21
+ ~4-6s after submit) — so every codex delivery structurally false-failed with `"no transcript
22
+ advance within 3000ms"` even though the message landed and codex was processing it (proven in
23
+ delivery.log: an `ok=false` immediately followed by codex recording the input and replying).
24
+ The confirm now also accepts a **pane-log (TUI render-stream) mtime advance** — the same true
25
+ active-turn signal the 0.4.16 idle-reap fix uses; it ticks ~1s as the session renders the
26
+ working state on submit, giving codex delivery parity with claude without lengthening the
27
+ (synchronous) grace. An advance proves the session is alive and reacted to the just-flushed
28
+ bytes (a dead/wedged pty renders nothing → still fails correctly). The typing/tool-use
29
+ indicator for codex is a separate telegram-runtime concern (its pane-log parsing).
30
+
13
31
  ## [0.4.19] - 2026-06-22
14
32
 
15
33
  ### Added
@@ -294,7 +312,8 @@ Initial public release — the foundation core of the iapeer multi-agent ecosyst
294
312
  - **Heterogeneous peers** — AI agents (Claude, Codex), humans (Telegram), and services
295
313
  (timer = cron, watcher = event) are all first-class and addressed the same way.
296
314
 
297
- [Unreleased]: https://github.com/agfpd/iapeer/compare/v0.4.19...HEAD
315
+ [Unreleased]: https://github.com/agfpd/iapeer/compare/v0.4.20...HEAD
316
+ [0.4.20]: https://github.com/agfpd/iapeer/compare/v0.4.19...v0.4.20
298
317
  [0.4.19]: https://github.com/agfpd/iapeer/compare/v0.4.18...v0.4.19
299
318
  [0.4.18]: https://github.com/agfpd/iapeer/compare/v0.4.17...v0.4.18
300
319
  [0.4.17]: https://github.com/agfpd/iapeer/compare/v0.4.16...v0.4.17
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agfpd/iapeer",
3
- "version": "0.4.19",
3
+ "version": "0.4.20",
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",
@@ -33,6 +33,7 @@ import {
33
33
  type ProcessAddress,
34
34
  } from '../core/socket.ts'
35
35
  import { buildEnvelope } from '../codec/index.ts'
36
+ import { resolveGlobalRoot } from '../storage/index.ts'
36
37
  import { findPeer, readPeersIndex, type PeerRecord } from '../registry/index.ts'
37
38
  import type { ResolvedCaller } from '../identity/index.ts'
38
39
  // Delivery markers are OWNED by the runtime adapter. transport
@@ -185,6 +186,12 @@ export interface WarmDeliverSeam {
185
186
  deliverHosted?: (identity: string, envelope: string) => Promise<DeliverResult>
186
187
  /** Activity proxy for the landed-confirm. Default: the target runtime adapter's transcript mtime. */
187
188
  newestActivityMtime?: (cwd: string) => number | null
189
+ /** SECOND landed-confirm proxy: the pane-log (TUI render-stream) mtime, keyed by process
190
+ * ADDRESS. Default: the real `<root>/logs/lifecycle/<address>.log` mtime. The pane-log ticks
191
+ * ~1s as the session renders its working state on submit — for codex it advances ~1s while the
192
+ * session-jsonl only advances at model-turn-start (~4-6s, past the grace), so this is the signal
193
+ * that gives codex delivery parity. See `deliverViaHost`. */
194
+ paneLogMtime?: (address: string) => number | null
188
195
  }
189
196
 
190
197
  /**
@@ -211,9 +218,12 @@ async function deliverViaHost(
211
218
  ): Promise<Result<void>> {
212
219
  const adapter = getAdapter(target.runtime)
213
220
  const mtimeOf = seam.newestActivityMtime ?? ((c: string) => adapter.newestActivityMtime(c))
214
- // Baseline the activity proxy BEFORE delivery (a paste/CR does not move the transcript — only a
215
- // model turn does), so an advance afterwards proves the session is alive and took our message.
221
+ const paneMtimeOf = seam.paneLogMtime ?? ((addr: string) => defaultPaneLogMtime(addr))
222
+ // Baseline BOTH landed-proxies BEFORE delivery (a paste/CR does not move the transcript — only a
223
+ // model turn does; the pane-log moves as the session renders), so an advance in EITHER afterwards
224
+ // proves the session is alive and took our message.
216
225
  const baseline = cwd ? mtimeOf(cwd) ?? 0 : 0
226
+ const paneBaseline = paneMtimeOf(target.address) ?? 0
217
227
  const send = seam.deliverHosted ?? deliverHosted
218
228
  const sent = await send(target.address, envelope)
219
229
  if (!sent.ok) {
@@ -232,22 +242,43 @@ async function deliverViaHost(
232
242
  // socket-ack ≠ landed. With no activity proxy (direct callers/tests without a cwd) we can only
233
243
  // trust the flushed submit — confirmed-only, mirroring deliverViaTmux's legacy-caller path.
234
244
  if (!cwd) return ok(undefined)
235
- // Confirm landing by the SAME final arbiter as the tmux busy path: a transcript-mtime advance.
236
- // Grace-poll the proxy (the model may take a beat to write its turn); a hosted session has no
237
- // input-clear fast path (no capture-pane), so this is the SOLE confirm. Prefer a false-FAIL
238
- // (sender retries) over a false-OK (silent loss, forbidden).
245
+ // Confirm landing by EITHER of two proxies within the grace:
246
+ // (a) transcript/session-jsonl mtime advance — the strong "model wrote a turn" signal. claude
247
+ // writes the user turn to its transcript promptly (sub-second); CODEX writes its session
248
+ // jsonl only at model-turn-START (gated by TTFT, measured ~4-6s after submit), so on the
249
+ // transcript-ONLY confirm every codex delivery STRUCTURALLY false-FAILED (the 3000ms grace
250
+ // expires before the first session write — proven in delivery.log).
251
+ // (b) PANE-LOG (TUI render-stream) mtime advance — the SAME true active-turn signal the 0.4.16
252
+ // idle-reap fix uses: it ticks ~1s as the session renders the working state on submit, so it
253
+ // catches codex in ~1s, giving codex delivery parity with claude WITHOUT lengthening the
254
+ // (synchronous) grace. NOT a false-OK: an advance proves the session is alive and rendered
255
+ // output in response to our just-flushed bytes (a wedged/dead pty renders nothing → no
256
+ // advance → correct fail); the bytes are then in the session's input (codex queues input
257
+ // during a turn). Prefer a false-FAIL (sender retries) over a false-OK (silent loss).
239
258
  const graceMs = hostLivenessGraceMs()
240
259
  const graceDeadline = monotonicMs() + graceMs
241
260
  do {
242
261
  if ((mtimeOf(cwd) ?? 0) > baseline) return ok(undefined)
262
+ if ((paneMtimeOf(target.address) ?? 0) > paneBaseline) return ok(undefined)
243
263
  sleepSync(LIVENESS_POLL_MS)
244
264
  } while (monotonicMs() < graceDeadline)
245
265
  return err(
246
266
  `hosted peer "${target.personality}" (${target.runtime}) is listed live but did not accept the message ` +
247
- `(no transcript advance within ${graceMs}ms) — live but unresponsive to this delivery (not dead); message NOT delivered`,
267
+ `(no transcript or pane-log advance within ${graceMs}ms) — live but unresponsive to this delivery (not dead); message NOT delivered`,
248
268
  )
249
269
  }
250
270
 
271
+ /** Pane-log (TUI render-stream) mtime for a process address, or null when absent/unreadable.
272
+ * The pane-log is the canonical `<root>/logs/lifecycle/<address>.log` the supervisor appends raw
273
+ * pty bytes to (§1 contract). Used as the SECOND landed-confirm proxy in deliverViaHost. */
274
+ function defaultPaneLogMtime(address: string): number | null {
275
+ try {
276
+ return statSync(join(resolveGlobalRoot(), 'logs', 'lifecycle', `${address}.log`)).mtimeMs
277
+ } catch {
278
+ return null
279
+ }
280
+ }
281
+
251
282
 
252
283
  // ─── Compact done gate (dialogue control must report FACT, not keystroke delivery) ──
253
284