@agfpd/iapeer 0.4.51 → 0.4.52

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.51",
3
+ "version": "0.4.52",
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",
@@ -24,7 +24,7 @@ import { spawnSync } from 'child_process'
24
24
  import { existsSync, readFileSync, realpathSync } from 'fs'
25
25
  import { basename, join } from 'path'
26
26
  import { homedir } from 'os'
27
- import { MARKETPLACE_NAME, isMarketplaceRegisteredAs, refreshMarketplace, registerMarketplace } from '../onboard/index.ts'
27
+ import { MARKETPLACE_NAME, isExecutable, isMarketplaceRegisteredAs, refreshMarketplace, registerMarketplace } from '../onboard/index.ts'
28
28
  import { findPeer, readPeersIndex } from '../registry/index.ts'
29
29
  import { IapError } from '../core/errors.ts'
30
30
  import { normalizeNameCandidate } from '../core/normalize.ts'
@@ -205,10 +205,14 @@ function resolvePeer(opts: EnableOptions): { personality: string; cwd: string; r
205
205
  return { personality, cwd: rec.cwd, runtimes: agentic }
206
206
  }
207
207
 
208
- function isExecutable(bin: string, env: NodeJS.ProcessEnv): boolean {
209
- const r = spawnSync(bin, ['--version'], { stdio: 'ignore', env: env as Record<string, string> })
210
- return r.error === undefined && r.status !== null
211
- }
208
+ // В57 — the local `--version` ANSWER probe was removed: it spawned the runtime CLI with NO
209
+ // timeout, so a wedged binary (the known macOS launch-approval park after a cask update —
210
+ // onboard's live incident: probes sat 25+ min) hung `enable` forever. enable now uses
211
+ // onboard's PRESENCE probe (accessSync X_OK / PATH scan — no spawn at all), and every real
212
+ // runtime-CLI invocation below carries a hard timeout, mirroring onboard.
213
+ const LIST_TIMEOUT_MS = 60_000 // read-only queries (plugin list)
214
+ const MUTATE_TIMEOUT_MS = 120_000 // install / enable / add (may clone from GitHub)
215
+ const SETUP_TIMEOUT_MS = 300_000 // a СЛОЖНЫЙ plugin's own installer — the longest legitimate leg
212
216
 
213
217
  /** Read the peer-scoped entry for this plugin from a fresh `claude plugin list --json`.
214
218
  * CRITICAL: `enabled` is evaluated relative to the CURRENT cwd's project (verified
@@ -227,6 +231,7 @@ function claudeEntry(
227
231
  encoding: 'utf8',
228
232
  env: env as Record<string, string>,
229
233
  maxBuffer: 64 * 1024 * 1024,
234
+ timeout: LIST_TIMEOUT_MS,
230
235
  })
231
236
  return findPeerScopedEntry(parseInstalledPlugins(list.stdout ?? ''), plugin, marketplace, peerCwd)
232
237
  }
@@ -251,6 +256,7 @@ function enableClaude(plugin: string, marketplace: string, peerCwd: string, env:
251
256
  cwd: peerCwd,
252
257
  encoding: 'utf8',
253
258
  env: env as Record<string, string>,
259
+ timeout: MUTATE_TIMEOUT_MS,
254
260
  })
255
261
  if (inst.status !== 0) {
256
262
  return { runtime: 'claude', state: 'failed', detail: (inst.stderr || inst.stdout || `exit ${inst.status}`).trim() }
@@ -264,6 +270,7 @@ function enableClaude(plugin: string, marketplace: string, peerCwd: string, env:
264
270
  cwd: peerCwd,
265
271
  encoding: 'utf8',
266
272
  env: env as Record<string, string>,
273
+ timeout: MUTATE_TIMEOUT_MS,
267
274
  })
268
275
  const confirmed = claudeEntry(bin, plugin, marketplace, peerCwd, env)
269
276
  if (!confirmed?.enabled) {
@@ -300,7 +307,12 @@ export function parseCodexPluginStatus(jsonOutput: string, id: string): 'enabled
300
307
  }
301
308
 
302
309
  function codexState(bin: string, id: string, env: NodeJS.ProcessEnv): 'enabled' | 'disabled' | 'absent' {
303
- const r = spawnSync(bin, ['plugin', 'list', '--json'], { encoding: 'utf8', env: env as Record<string, string>, maxBuffer: 64 * 1024 * 1024 })
310
+ const r = spawnSync(bin, ['plugin', 'list', '--json'], {
311
+ encoding: 'utf8',
312
+ env: env as Record<string, string>,
313
+ maxBuffer: 64 * 1024 * 1024,
314
+ timeout: LIST_TIMEOUT_MS,
315
+ })
304
316
  return parseCodexPluginStatus(r.stdout ?? '', id)
305
317
  }
306
318
 
@@ -314,7 +326,11 @@ function enableCodex(plugin: string, marketplace: string, env: NodeJS.ProcessEnv
314
326
  const id = `${plugin}@${marketplace}`
315
327
  const before = codexState(bin, id, env)
316
328
  if (before === 'enabled') return { runtime: 'codex', state: 'already-enabled' }
317
- const add = spawnSync(bin, ['plugin', 'add', id], { encoding: 'utf8', env: env as Record<string, string> })
329
+ const add = spawnSync(bin, ['plugin', 'add', id], {
330
+ encoding: 'utf8',
331
+ env: env as Record<string, string>,
332
+ timeout: MUTATE_TIMEOUT_MS,
333
+ })
318
334
  if (add.status !== 0) {
319
335
  return { runtime: 'codex', state: 'failed', detail: (add.stderr || add.stdout || `exit ${add.status}`).trim() }
320
336
  }
@@ -346,6 +362,7 @@ function callSetup(
346
362
  cwd: installPath,
347
363
  encoding: 'utf8',
348
364
  env: setupEnv as Record<string, string>,
365
+ timeout: SETUP_TIMEOUT_MS,
349
366
  })
350
367
  if (r.error || (r.status ?? 1) !== 0) {
351
368
  return { ok: false, detail: (r.stderr || r.stdout || r.error?.message || `exit ${r.status}`).trim() }
@@ -120,6 +120,12 @@ export interface EnsurePeerProfileOptions {
120
120
  * always-on plist so launchd's minimal PATH resolves it. Forwarded to
121
121
  * installAlwaysOnPlist; ignored for warm-on-demand runtimes. */
122
122
  runtimeBin?: string
123
+ /** В36 — peer description, persisted into the LOCAL profile (the source of truth the
124
+ * registry is reindexed FROM). Writing it only to the registry made it a stale copy:
125
+ * the first reindexFromLocals (addRuntime on a dual-runtime create, connect telegram,
126
+ * verify --fix) projected the profile's empty description over it, wiping the Layer-3
127
+ * addressing signal fleet-wide. */
128
+ description?: string
123
129
  }
124
130
 
125
131
  // ─────────────────────────────────────────────────────────────────────────────
@@ -513,7 +519,9 @@ export function ensurePeerProfile(options: EnsurePeerProfileOptions): PeerProfil
513
519
  personality,
514
520
  runtime: options.runtime,
515
521
  runtimes: discoveredRuntimes,
516
- description: '',
522
+ // В36 — the description lands in the LOCAL profile at birth (source of truth);
523
+ // the registry row is a projection of this file, not the other way around.
524
+ description: options.description?.trim() ?? '',
517
525
  intelligence: defaultIntelligenceForRuntime(options.runtime),
518
526
  }
519
527
  ensureLocalRuntimeScopes(cwd, profile.runtimes)
@@ -573,8 +581,17 @@ export function ensurePeerProfile(options: EnsurePeerProfileOptions): PeerProfil
573
581
  })
574
582
  intelligence = defaultIntelligenceForRuntime(options.runtime)
575
583
  }
576
- if (mergedRuntimes.length !== existing.runtimes.length || intelligence !== existing.intelligence) {
577
- const updated = { ...existing, runtimes: mergedRuntimes, intelligence }
584
+ // В36 — an explicit description on RE-provision updates the local profile too (H1 in
585
+ // writePeerProfileAtomic still guards: absent/empty never wipes an existing value).
586
+ const explicitDescription = options.description?.trim()
587
+ const descriptionChanged = Boolean(explicitDescription) && explicitDescription !== existing.description
588
+ if (mergedRuntimes.length !== existing.runtimes.length || intelligence !== existing.intelligence || descriptionChanged) {
589
+ const updated = {
590
+ ...existing,
591
+ runtimes: mergedRuntimes,
592
+ intelligence,
593
+ ...(descriptionChanged ? { description: explicitDescription! } : {}),
594
+ }
578
595
  writePeerProfileAtomic(cwd, updated)
579
596
  return updated
580
597
  }
@@ -50,7 +50,13 @@ export function buildLaunchInvocation(
50
50
  PEER_PERSONALITY: spec.personality,
51
51
  PEER_RUNTIME: spec.runtime,
52
52
  PEER_IDENTITY: spec.identity,
53
- PATH: env.PATH ?? `${homedir()}/.bun/bin:${homedir()}/.local/bin:/opt/homebrew/bin:/opt/homebrew/sbin:/usr/local/bin:/usr/bin:/bin`,
53
+ // В45 — a per-peer launch.env PATH override WINS: this literal sits after the launchEnv
54
+ // spread, so `env.PATH ?? default` silently clobbered the operator's override (env.PATH
55
+ // is nearly always set). The legacy launcher sourced the file — there PATH took effect.
56
+ PATH:
57
+ launchEnv.env.PATH ??
58
+ env.PATH ??
59
+ `${homedir()}/.bun/bin:${homedir()}/.local/bin:/opt/homebrew/bin:/opt/homebrew/sbin:/usr/local/bin:/usr/bin:/bin`,
54
60
  }
55
61
  // Strip the claude-code-internal session namespace from the inherited env. When a launch is
56
62
  // INITIATED from inside a running claude session (an operator CLI, or a claude peer shelling out),
@@ -71,7 +71,10 @@ function runtimeBin(runtime: OnboardRuntime, env: NodeJS.ProcessEnv): string {
71
71
  return env.IAPEER_CODEX_BIN?.trim() || 'codex'
72
72
  }
73
73
 
74
- function isExecutable(binOrName: string, env: NodeJS.ProcessEnv = process.env): boolean {
74
+ /** PRESENCE probe (no spawn) — exported for enable/ (В57): the same wedged-runtime class
75
+ * (macOS launch-approval parking every invocation pre-main) hung enable's `--version`
76
+ * ANSWER probe forever; presence answers "is it installed" without executing anything. */
77
+ export function isExecutable(binOrName: string, env: NodeJS.ProcessEnv = process.env): boolean {
75
78
  if (binOrName.includes('/')) {
76
79
  try {
77
80
  accessSync(binOrName, FS.X_OK)
@@ -89,6 +89,9 @@ export async function provisionPeer(opts: ProvisionPeerOptions): Promise<Provisi
89
89
  peers,
90
90
  personality: opts.personality,
91
91
  runtimeBin,
92
+ // В36 — description goes into the LOCAL profile (source of truth); the registry
93
+ // upsert below then persists a value reindexFromLocals can no longer wipe.
94
+ description: opts.description,
92
95
  warn: opts.warn,
93
96
  })
94
97
  // Birth-time native-memory lever (контракт «Слот памяти» §Native-память рантаймов):