@agfpd/iapeer 0.4.44 → 0.4.45

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.44",
3
+ "version": "0.4.45",
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",
@@ -200,6 +200,9 @@ function errResult(text: string): ToolResult {
200
200
  /** Injected seams for callTool — all OPTIONAL, all wired by the composition point
201
201
  * (daemon/main.ts); library/test callers omit them and stay hermetic. */
202
202
  export interface CallToolDeps {
203
+ /** Б7 — injected env for the request-path registry read + routeSend (daemon:286 isolation). Default
204
+ * process.env; the production daemon threads its startDaemon env here. */
205
+ env?: NodeJS.ProcessEnv
203
206
  /** Wake-on-miss primitive (Ф2) — see StartDaemonOptions.wake. */
204
207
  wake?: WakeFn
205
208
  /** Per-delivery outcome log dir (Ф-#8a) — see StartDaemonOptions.deliveryLogDir. */
@@ -243,6 +246,7 @@ export async function callTool(
243
246
  }
244
247
  const t0 = Date.now()
245
248
  const sent = await routeSend(caller, input, {
249
+ env: deps.env,
246
250
  wake: deps.wake,
247
251
  ephemeral: deps.ephemeral,
248
252
  noteLiveTopic: deps.noteLiveTopic,
@@ -311,7 +315,7 @@ export function createMcpServer(deps: CallToolDeps = {}): Server {
311
315
  const headerIdentity = headerFromRequestInfo(extra as never)
312
316
  let caller: ResolvedCaller
313
317
  try {
314
- caller = resolveCallerFromHeader(headerIdentity, readPeersIndex())
318
+ caller = resolveCallerFromHeader(headerIdentity, readPeersIndex({ env: deps.env }))
315
319
  } catch (e) {
316
320
  // No silent default — reject the CallTool when identity is missing/invalid.
317
321
  if (process.env.IAPEER_DAEMON_LOG) {
@@ -459,6 +463,7 @@ export async function startDaemon(opts: StartDaemonOptions = {}): Promise<Daemon
459
463
  return
460
464
  }
461
465
  const server = createMcpServer({
466
+ env: opts.env, // Б7 — thread the daemon's storage env into the request path (registry read + routeSend)
462
467
  wake: opts.wake,
463
468
  deliveryLogDir: opts.deliveryLogDir,
464
469
  onDelivered: opts.onDelivered,
@@ -107,8 +107,11 @@ export function resolveDeliveryTarget(args: {
107
107
  personality: string
108
108
  runtime?: string
109
109
  sockDir?: string
110
+ /** Б7 — injected env sandboxes the liveness reads (supervisor run-dir); default process.env (prod). */
111
+ env?: NodeJS.ProcessEnv
110
112
  }): Result<DeliveryTarget> {
111
- const sockDir = args.sockDir ?? resolveSockDir()
113
+ const env = args.env ?? process.env
114
+ const sockDir = args.sockDir ?? resolveSockDir(env)
112
115
  if (!isValidName(args.personality)) {
113
116
  return err(`invalid personality "${args.personality}" — must match /^[a-z][a-z0-9-]{0,31}$/`)
114
117
  }
@@ -117,12 +120,12 @@ export function resolveDeliveryTarget(args: {
117
120
  const runtime = args.runtime
118
121
  const socketPath = buildSocketPath(runtime, args.personality, sockDir)
119
122
  const address = buildProcessAddress(runtime, args.personality)
120
- if (!sessionAlive(socketPath, address)) {
123
+ if (!sessionAlive(socketPath, address, env)) {
121
124
  return err(`peer offline: ${args.personality} (${args.runtime})`)
122
125
  }
123
126
  return ok({ runtime, personality: args.personality, address, socketPath })
124
127
  }
125
- const matches = listOnlinePeers(sockDir).filter(peer => peer.personality === args.personality)
128
+ const matches = listOnlinePeers(sockDir, env).filter(peer => peer.personality === args.personality)
126
129
  if (matches.length === 0) return err(`peer offline: ${args.personality}`)
127
130
  if (matches.length > 1) {
128
131
  return err(
@@ -145,13 +148,14 @@ export function resolvePeerDeliveryTarget(
145
148
  personality: string,
146
149
  runtime: string | undefined,
147
150
  peer: PeerRecord,
151
+ env: NodeJS.ProcessEnv = process.env,
148
152
  ): Result<DeliveryTarget> {
149
- if (runtime) return resolveDeliveryTarget({ personality, runtime })
153
+ if (runtime) return resolveDeliveryTarget({ personality, runtime, env })
150
154
  if (peer.runtime) {
151
- const exactDefault = resolveDeliveryTarget({ personality, runtime: peer.runtime })
155
+ const exactDefault = resolveDeliveryTarget({ personality, runtime: peer.runtime, env })
152
156
  if (exactDefault.ok) return exactDefault
153
157
  }
154
- return resolveDeliveryTarget({ personality })
158
+ return resolveDeliveryTarget({ personality, env })
155
159
  }
156
160
 
157
161
  // Confirm poll interval — how often deliverViaHost re-reads the transcript for a NEW record carrying
@@ -232,13 +236,14 @@ export async function deliverWarm(
232
236
  envelope: string,
233
237
  cwd?: string,
234
238
  seam: WarmDeliverSeam = {},
239
+ env: NodeJS.ProcessEnv = process.env,
235
240
  ): Promise<Result<void>> {
236
241
  const address = target.address
237
242
  const prev = deliverTails.get(address) ?? Promise.resolve()
238
243
  // run AFTER prev settles (success or failure) — a prior delivery's error must not stall the chain
239
244
  const run = prev.then(
240
- () => deliverViaHost(target, envelope, cwd, seam),
241
- () => deliverViaHost(target, envelope, cwd, seam),
245
+ () => deliverViaHost(target, envelope, cwd, seam, env),
246
+ () => deliverViaHost(target, envelope, cwd, seam, env),
242
247
  )
243
248
  deliverTails.set(
244
249
  address,
@@ -255,6 +260,7 @@ async function deliverViaHost(
255
260
  envelope: string,
256
261
  cwd: string | undefined,
257
262
  seam: WarmDeliverSeam,
263
+ env: NodeJS.ProcessEnv = process.env,
258
264
  ): Promise<Result<void>> {
259
265
  const adapter = getAdapter(target.runtime)
260
266
  // Baseline the transcript/session-jsonl file SIZES BEFORE delivery (per-runtime candidate files).
@@ -264,8 +270,9 @@ async function deliverViaHost(
264
270
  // socket-ack runtimes (codex) return before it — computing it for them recursively scanned all of
265
271
  // ~/.codex/sessions (full-file read per session) on every delivery for nothing.
266
272
  const usesBaseline = adapter.kind !== 'router' && adapter.deliveryConfirm !== 'socket-ack'
267
- const baseline = cwd && usesBaseline ? compactDoneBaseline(target.runtime, cwd) : null
268
- const send = seam.deliverHosted ?? deliverHosted
273
+ const baseline = cwd && usesBaseline ? compactDoneBaseline(target.runtime, cwd, { env }) : null
274
+ // Б7 — the default deliverHosted resolves the supervisor run-dir from env (sandboxed under a test root).
275
+ const send = seam.deliverHosted ?? ((identity: string, msg: string) => deliverHosted(identity, msg, env))
269
276
  const sent = await send(target.address, envelope)
270
277
  if (!sent.ok) {
271
278
  return err(
@@ -892,6 +899,11 @@ export interface ComposerQueueRouteDeps {
892
899
  }
893
900
 
894
901
  export interface RouteDeps {
902
+ /** Б7 — injected env sandboxes ALL of routeSend's env-resolved reads (the registry, the supervisor
903
+ * run-dir liveness/delivery). Default process.env (prod: env === process.env). Closes the daemon:286
904
+ * isolation invariant — a library/test daemon with an injected root no longer reads the real registry
905
+ * or the real fleet's live sessions. */
906
+ env?: NodeJS.ProcessEnv
895
907
  /** On a miss, wake the dead peer instead of returning offline (Ф2). */
896
908
  wake?: WakeFn
897
909
  /** Ephemeral-target serial-queue delivery (M3); absent → normal routing. */
@@ -1275,7 +1287,8 @@ export async function routeSend(
1275
1287
  return err(`message exceeds ${MAX_MESSAGE_LEN} char limit (got ${message.length})`)
1276
1288
  }
1277
1289
 
1278
- const index = readPeersIndex()
1290
+ const env = deps.env ?? process.env
1291
+ const index = readPeersIndex({ env })
1279
1292
  const peer = findPeer(index, personality)
1280
1293
  if (!peer) {
1281
1294
  return err(`peer "${personality}" is not in the iapeer peers index; message NOT delivered`)
@@ -1309,7 +1322,7 @@ export async function routeSend(
1309
1322
  return deps.ephemeral.deliver({ peer, envelope, topic, runtime })
1310
1323
  }
1311
1324
 
1312
- const target = resolvePeerDeliveryTarget(personality, runtime, peer)
1325
+ const target = resolvePeerDeliveryTarget(personality, runtime, peer, env)
1313
1326
  if (target.ok) {
1314
1327
  if (target.value.address === caller.address) return err('cannot send to self')
1315
1328
  // Telegram sender policy — RESOLVED channel (the only-live-session fallback can
@@ -1321,7 +1334,7 @@ export async function routeSend(
1321
1334
  // peer.cwd enables the Ф-B transcript-mtime liveness probe (busy-session case).
1322
1335
  // deliverWarm is HOST-AWARE (spawn-flip Ф0b-2): a supervisor-hosted target delivers over its
1323
1336
  // socket, any other target keeps the tmux path. Flag-off → byte-identical to deliverViaTmux.
1324
- const delivered = await deliverWarm(target.value, envelope, peer.cwd)
1337
+ const delivered = await deliverWarm(target.value, envelope, peer.cwd, {}, env)
1325
1338
  if (!delivered.ok) return delivered
1326
1339
  noteTopic(deps, target.value.address, topic)
1327
1340
  noteDelivered(deps, target.value.address) // В7 — floor the idle proxy so this delivery isn't reaped away
@@ -1349,12 +1362,12 @@ export async function routeSend(
1349
1362
  const reviveDeadline = monotonicMs() + launchdReviveGraceMs()
1350
1363
  while (monotonicMs() < reviveDeadline) {
1351
1364
  await sleep(LAUNCHD_REVIVE_POLL_MS)
1352
- const revived = resolvePeerDeliveryTarget(personality, runtime, peer)
1365
+ const revived = resolvePeerDeliveryTarget(personality, runtime, peer, env)
1353
1366
  if (!revived.ok) continue // not back yet — keep polling until the deadline
1354
1367
  if (revived.value.address === caller.address) return err('cannot send to self')
1355
1368
  const reviveGuard = telegramSenderGuard(caller, revived.value.runtime, personality)
1356
1369
  if (!reviveGuard.ok) return reviveGuard
1357
- const delivered = await deliverWarm(revived.value, envelope, peer.cwd)
1370
+ const delivered = await deliverWarm(revived.value, envelope, peer.cwd, {}, env)
1358
1371
  if (delivered.ok) {
1359
1372
  noteTopic(deps, revived.value.address, topic)
1360
1373
  return ok({
@@ -1399,7 +1412,7 @@ export async function routeSend(
1399
1412
  if (woke.taskDelivered === false) {
1400
1413
  const queued = await deps.composerQueue?.tryEnqueue({ caller, peer, target: live.value, envelope, topic })
1401
1414
  if (queued) return queued
1402
- const delivered = await deliverWarm(live.value, envelope, peer.cwd)
1415
+ const delivered = await deliverWarm(live.value, envelope, peer.cwd, {}, env)
1403
1416
  if (!delivered.ok) return delivered
1404
1417
  noteTopic(deps, live.value.address, topic)
1405
1418
  return ok({