@agfpd/iapeer 0.1.0 → 0.1.2

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.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Foundation core for the IAPeer multi-agent ecosystem: identity, registry, storage, codec.",
5
5
  "type": "module",
6
6
  "bin": {
package/src/cli/index.ts CHANGED
@@ -11,6 +11,7 @@
11
11
  // sentinel-marked always-on plist) are stop/start-able.
12
12
 
13
13
  import { spawnSync } from 'child_process'
14
+ import { readFileSync } from 'fs'
14
15
  import { fileURLToPath } from 'url'
15
16
  import {
16
17
  isInfraRuntime,
@@ -307,7 +308,7 @@ const USAGE = `usage: iapeer <verb> [args]
307
308
  list [--json] registered peers + per-runtime liveness
308
309
  stop <peer> [runtime] | --all durable-stop a warm peer / bootout an always-on one
309
310
  start <peer> [runtime] re-enable a stopped peer / bootstrap an always-on one
310
- send <target> --message <text> [--from <id>] [--topic <t>] manual IAP send (fallback)
311
+ send <target> (--message <text> | --message-file <f|->) [--from <id>] [--attachment <p>]… [--topic <t>] manual IAP send (fallback)
311
312
  <runtime> launch the cwd's peer (ALWAYS fresh)
312
313
  enable <plugin> [peer] [--no-setup] install + enable an agfpd capability for a peer
313
314
  attach <peer> [runtime] ensure-live + resume, then tmux attach
@@ -457,13 +458,33 @@ export async function runCli(argv: string[], env: NodeJS.ProcessEnv = process.en
457
458
  return outcomes.some(o => o.action === 'refused-foreign-launchd') ? 1 : 0
458
459
  }
459
460
  case 'send': {
460
- if (!positionals[0] || typeof flags.message !== 'string') return usage(errOut)
461
+ // Message body from EITHER --message <text> OR --message-file <f> (f='-'
462
+ // stdin). The runtime packages (telegram/notifier) + monitor deliver via
463
+ // --message-file (large/multi-line bodies, special chars); manual/peer-voice
464
+ // use --message. Both supported; keep both — do not replace one with the other.
465
+ let message: string | null = null
466
+ if (typeof flags.message === 'string') {
467
+ message = flags.message
468
+ } else if (typeof flags['message-file'] === 'string') {
469
+ const mf = flags['message-file']
470
+ message = mf === '-' ? readFileSync(0, 'utf8') : readFileSync(mf, 'utf8')
471
+ }
472
+ if (!positionals[0] || message === null) return usage(errOut)
473
+ // --attachment is REPEATABLE; parseArgs collapses repeats (last-wins), so
474
+ // re-scan the raw rest argv to collect every attachment path (else files
475
+ // silently drop — a text-only smoke test would not catch it).
476
+ const attachments: string[] = []
477
+ for (let i = 0; i < rest.length; i++) {
478
+ if (rest[i] === '--attachment' && rest[i + 1] !== undefined) attachments.push(rest[++i])
479
+ else if (rest[i].startsWith('--attachment=')) attachments.push(rest[i].slice('--attachment='.length))
480
+ }
461
481
  const r = await sendMessage({
462
482
  target: positionals[0],
463
483
  from: typeof flags.from === 'string' ? flags.from : defaultFromIdentity(env),
464
- message: flags.message,
484
+ message,
465
485
  runtime: typeof flags.runtime === 'string' ? flags.runtime : undefined,
466
486
  topic: typeof flags.topic === 'string' ? flags.topic : undefined,
487
+ attachments: attachments.length ? attachments : undefined,
467
488
  env,
468
489
  })
469
490
  out(`delivered to ${r.delivered_to.personality} (${r.delivered_to.runtime})\n`)
@@ -453,8 +453,29 @@ export function ensurePeerProfile(options: EnsurePeerProfileOptions): PeerProfil
453
453
  }
454
454
  const mergedRuntimes = uniqueRuntimes([...existing.runtimes, ...discoveredRuntimes])
455
455
  ensureLocalRuntimeScopes(cwd, mergedRuntimes)
456
- if (mergedRuntimes.length !== existing.runtimes.length) {
457
- const updated = { ...existing, runtimes: mergedRuntimes }
456
+ // RE-PROVISION parity with the new-profile branch: an EXISTING infra peer (a
457
+ // migration / re-provision e.g. `iapeer create arthur --runtime telegram` to move
458
+ // a live telegram human onto the foundation) must ALSO get its always-on plist
459
+ // installed and its intelligence set to the runtime's foundation default
460
+ // (telegram→natural, notifier→absent). The old code only merged runtimes here, so
461
+ // re-provisioning an infra peer wrote NO plist (bootstrap → refused-foreign on the
462
+ // missing plist) and left a stale legacy intelligence — the arthur cutover had to
463
+ // install the plist + flip vocab by hand. Install BEFORE the write so a
464
+ // collision-guard refusal (a foreign plist still sitting at the label) fails loudly
465
+ // and leaves no half-updated profile. Idempotent for our own (sentinel) plist.
466
+ let intelligence = existing.intelligence
467
+ if (isInfraRuntime(options.runtime)) {
468
+ installAlwaysOnPlist({
469
+ personality: existing.personality,
470
+ runtime: options.runtime,
471
+ cwd,
472
+ runtimeBin: options.runtimeBin,
473
+ env: options.env,
474
+ })
475
+ intelligence = defaultIntelligenceForRuntime(options.runtime)
476
+ }
477
+ if (mergedRuntimes.length !== existing.runtimes.length || intelligence !== existing.intelligence) {
478
+ const updated = { ...existing, runtimes: mergedRuntimes, intelligence }
458
479
  writePeerProfileAtomic(cwd, updated)
459
480
  return updated
460
481
  }