@agfpd/iapeer 0.4.21 → 0.4.23

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,33 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
10
10
 
11
11
  ## [Unreleased]
12
12
 
13
+ ## [0.4.23] - 2026-06-22
14
+
15
+ ### Added
16
+
17
+ - **`iapeer enable telegram <peer>`** — a discoverability alias for `iapeer connect telegram
18
+ <peer>` (give an existing peer a Telegram bot face). A layperson reaches for "enable
19
+ telegram", not "connect"; both now work and route through the same flow (bot add →
20
+ interface → router restart; asks only the token). telegram is a CHANNEL, not a marketplace
21
+ plugin, so the alias routes to the connect flow, not `enableCapability`. The `connect`
22
+ help line documents the alias. No telegram-runtime change (the mechanism already worked) —
23
+ purely CLI discoverability.
24
+
25
+ ## [0.4.22] - 2026-06-22
26
+
27
+ ### Added
28
+
29
+ - **`iapeer live-runtime <peer>`** — prints the peer's CURRENT live runtime: the
30
+ freshest-pane-log among its **pid-alive** supervisor sessions (a peer can be alive on
31
+ more than one runtime at once — a `/codex` flip can leave the old session running
32
+ alongside the new). This is the authoritative, real-time signal for "what runtime is
33
+ this peer running right now" — NOT `default_runtime` (only the wake-default) and NOT a
34
+ `.session` wake-record (alive-filtered only on a supervise-tick latency). Exit 0 + the
35
+ runtime on stdout when one is live; exit 1 + no output when none is alive. For external
36
+ consumers (e.g. telegram-runtime's typing/tool-use indicator) that must resolve the live
37
+ runtime of a peer running on a non-default runtime, instead of inferring it. Exposed as
38
+ `resolveLiveRuntime` (transport).
39
+
13
40
  ## [0.4.21] - 2026-06-22
14
41
 
15
42
  ### Fixed
@@ -325,7 +352,9 @@ Initial public release — the foundation core of the iapeer multi-agent ecosyst
325
352
  - **Heterogeneous peers** — AI agents (Claude, Codex), humans (Telegram), and services
326
353
  (timer = cron, watcher = event) are all first-class and addressed the same way.
327
354
 
328
- [Unreleased]: https://github.com/agfpd/iapeer/compare/v0.4.21...HEAD
355
+ [Unreleased]: https://github.com/agfpd/iapeer/compare/v0.4.23...HEAD
356
+ [0.4.23]: https://github.com/agfpd/iapeer/compare/v0.4.22...v0.4.23
357
+ [0.4.22]: https://github.com/agfpd/iapeer/compare/v0.4.21...v0.4.22
329
358
  [0.4.21]: https://github.com/agfpd/iapeer/compare/v0.4.20...v0.4.21
330
359
  [0.4.20]: https://github.com/agfpd/iapeer/compare/v0.4.19...v0.4.20
331
360
  [0.4.19]: https://github.com/agfpd/iapeer/compare/v0.4.18...v0.4.19
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agfpd/iapeer",
3
- "version": "0.4.21",
3
+ "version": "0.4.23",
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",
package/src/cli/index.ts CHANGED
@@ -875,6 +875,41 @@ export async function removePeerCli(
875
875
  }
876
876
  }
877
877
 
878
+ /**
879
+ * `connect telegram <peer>` core — give a peer a Telegram bot face (interfaces.telegram).
880
+ * Shared by the `connect telegram` verb AND the `enable telegram <peer>` discoverability
881
+ * alias (telegram is a CHANNEL, not a marketplace plugin, so both route here, not through
882
+ * enableCapability). Returns the process exit code. The human owes only the token (prompted
883
+ * when omitted on a tty); the system does bot-add → interface → router restart.
884
+ */
885
+ async function connectTelegramVerb(
886
+ peer: string,
887
+ token: string | undefined,
888
+ env: NodeJS.ProcessEnv,
889
+ out: (s: string) => void,
890
+ errOut: (s: string) => void,
891
+ ): Promise<number> {
892
+ const { connectTelegram } = await import('../connect/index.ts')
893
+ const r = await connectTelegram({ peer, token, env })
894
+ if (r.state === 'noop-same-token') {
895
+ out(`connect telegram ${r.peer}: ${r.detail}\n`)
896
+ return 0
897
+ }
898
+ if (r.state !== 'connected') {
899
+ errOut(`connect telegram ${r.peer}: ${r.state}${r.detail ? ` — ${r.detail}` : ''}\n`)
900
+ return 1
901
+ }
902
+ const rs = r.restart!
903
+ out(`bot ${r.username ?? `for "${r.peer}"`} added + interfaced to "${r.peer}"\n`)
904
+ out(
905
+ rs.state === 'restarted'
906
+ ? `router restarted — credentials loaded\n`
907
+ : `router restart ${rs.state}${rs.detail ? ` — ${rs.detail}` : ''} (the channel stays dead until the router restarts)\n`,
908
+ )
909
+ out(`activation: send the bot ${r.username ?? '(see @BotFather)'} its FIRST message — Telegram does not let a bot start the chat\n`)
910
+ return rs.state === 'restarted' ? 0 : 1
911
+ }
912
+
878
913
  // ─────────────────────────────────────────────────────────────────────────────
879
914
  // rename — first-class peer-identity rename (parity with remove/create). Wraps
880
915
  // renamePeer (registry + per-cwd profile, atomic, inside the lock) and KEEPS the
@@ -1139,6 +1174,7 @@ const VERBS: ReadonlyArray<{ sig: string; desc: string }> = [
1139
1174
  desc: 'backbone host-phase: marketplace → notifier → telegram (human peer) → memory → voice (all default YES). --accept-risk (or IAPEER_ACCEPT_RISK=1) accepts the security warning non-interactively',
1140
1175
  },
1141
1176
  { sig: 'status', desc: 'host snapshot: version, daemon health, memory + voice slots (<provider> | none)' },
1177
+ { sig: 'live-runtime <peer>', desc: 'print the peer\'s CURRENT live runtime (freshest pane-log among pid-alive sessions; NOT default_runtime). Exit 1 + no output if none alive' },
1142
1178
  { sig: 'install-runtime <runtime> [--package pkg] [--npx]', desc: 'npx-install a runtime package + deploy its declared peer-set' },
1143
1179
  { sig: 'update-runtime <runtime> | --all [--force]', desc: "version-gate → re-install + re-provision declared set → restart the runtime's peers" },
1144
1180
  { sig: 'init [cwd] [--runtime r] [--description d]', desc: 'onboard the CURRENT folder as a peer (name = folder name; identity + MCP + doctrine)' },
@@ -1167,7 +1203,7 @@ const VERBS: ReadonlyArray<{ sig: string; desc: string }> = [
1167
1203
  desc: 'manual IAP send (fallback)',
1168
1204
  },
1169
1205
  { sig: '<runtime>', desc: "launch the cwd's peer FRESH; on a TTY drop into it (like attach), else detached" },
1170
- { sig: 'connect telegram <peer> [--token <t>]', desc: 'attach a telegram bot to a peer (bot add → interface → router restart; asks only the token)' },
1206
+ { sig: 'connect telegram <peer> [--token <t>]', desc: 'attach a telegram bot to a peer (bot add → interface → router restart; asks only the token). Alias: `iapeer enable telegram <peer>`' },
1171
1207
  { sig: 'enable <plugin> [peer] [--no-setup]', desc: 'install + enable an agfpd capability for a peer' },
1172
1208
  {
1173
1209
  sig: 'new <peer> [runtime]',
@@ -1501,6 +1537,21 @@ export async function runCli(argv: string[], env: NodeJS.ProcessEnv = process.en
1501
1537
  out(formatHostStatus(s))
1502
1538
  return s.daemon.healthy ? 0 : 1
1503
1539
  }
1540
+ case 'live-runtime': {
1541
+ // The AUTHORITATIVE current live runtime of a peer (machine-readable, for external
1542
+ // consumers like telegram-runtime's typing indicator). Prints the runtime the peer
1543
+ // is RUNNING right now — the freshest-pane-log among its PID-alive supervisor
1544
+ // sessions — NOT default_runtime (the wake-default) and NOT a .session wake-record.
1545
+ // A peer can be alive on >1 runtime (a /codex flip leaves both); this picks the
1546
+ // currently-active. Exit 0 + the runtime on stdout when one is live; exit 1 + no
1547
+ // output when none is alive (queryable: `rt=$(iapeer live-runtime <p>) || handle-down`).
1548
+ if (!positionals[0]) return argErr(errOut, 'live-runtime needs a peer name — usage: iapeer live-runtime <peer>')
1549
+ const { resolveLiveRuntime } = await import('../transport/index.ts')
1550
+ const rt = resolveLiveRuntime(positionals[0])
1551
+ if (!rt) return 1 // no live session — no output, non-zero
1552
+ out(`${rt}\n`)
1553
+ return 0
1554
+ }
1504
1555
  case 'install-runtime': {
1505
1556
  // §6 onboard a runtime END-TO-END: npx-install the package (auto-resolved from
1506
1557
  // the built-in runtime→package registry, or --package; self-deploys bin +
@@ -2271,31 +2322,16 @@ export async function runCli(argv: string[], env: NodeJS.ProcessEnv = process.en
2271
2322
  // alias/bot-add/interface/router-restart are resolved by the system. The
2272
2323
  // FIRST message from the human to the bot activates the chat (platform rule).
2273
2324
  if (positionals[0] !== 'telegram' || !positionals[1]) return argErr(errOut, 'connect needs "telegram <peer>" — usage: iapeer connect telegram <peer> [--token <t>] (e.g. iapeer connect telegram boris)')
2274
- const { connectTelegram } = await import('../connect/index.ts')
2275
- const r = await connectTelegram({
2276
- peer: positionals[1],
2277
- token: typeof flags.token === 'string' ? flags.token : undefined,
2278
- env,
2279
- })
2280
- if (r.state === 'noop-same-token') {
2281
- out(`connect telegram ${r.peer}: ${r.detail}\n`)
2282
- return 0
2283
- }
2284
- if (r.state !== 'connected') {
2285
- errOut(`connect telegram ${r.peer}: ${r.state}${r.detail ? ` — ${r.detail}` : ''}\n`)
2286
- return 1
2287
- }
2288
- const rs = r.restart!
2289
- out(`bot ${r.username ?? `for "${r.peer}"`} added + interfaced to "${r.peer}"\n`)
2290
- out(
2291
- rs.state === 'restarted'
2292
- ? `router restarted — credentials loaded\n`
2293
- : `router restart ${rs.state}${rs.detail ? ` — ${rs.detail}` : ''} (the channel stays dead until the router restarts)\n`,
2294
- )
2295
- out(`activation: send the bot ${r.username ?? '(see @BotFather)'} its FIRST message — Telegram does not let a bot start the chat\n`)
2296
- return rs.state === 'restarted' ? 0 : 1
2325
+ return await connectTelegramVerb(positionals[1], typeof flags.token === 'string' ? flags.token : undefined, env, out, errOut)
2297
2326
  }
2298
2327
  case 'enable': {
2328
+ // DISCOVERABILITY ALIAS: `enable telegram <peer>` → `connect telegram <peer>`
2329
+ // (a layperson reaches for "enable telegram"; telegram is a CHANNEL, not a
2330
+ // marketplace plugin, so it routes to the connect flow, NOT enableCapability).
2331
+ if (positionals[0] === 'telegram') {
2332
+ if (!positionals[1]) return argErr(errOut, 'enable telegram needs a peer — usage: iapeer enable telegram <peer> [--token <t>] (alias of `iapeer connect telegram <peer>`)')
2333
+ return await connectTelegramVerb(positionals[1], typeof flags.token === 'string' ? flags.token : undefined, env, out, errOut)
2334
+ }
2299
2335
  // Per-peer capability install (contract Установка §3): install <plugin>@agfpd
2300
2336
  // per-runtime (claude project-scope IN the peer cwd / codex global) + enable +
2301
2337
  // call the plugin's `setup` ONLY if its iapeer.json declares it. Idempotent and
@@ -279,6 +279,33 @@ function defaultPaneLogMtime(address: string): number | null {
279
279
  }
280
280
  }
281
281
 
282
+ /**
283
+ * The CURRENT live runtime of a peer — the AUTHORITATIVE, real-time signal for "what
284
+ * runtime is this peer running right now" (NOT default_runtime, which is only the wake
285
+ * DEFAULT, and NOT a `.session` file, a wake-record cleaned on a supervise-tick latency).
286
+ *
287
+ * Resolution: among the peer's PID-ALIVE supervisor sessions (listHostedPeers — the same
288
+ * source delivery uses; `sessionAlive` = pidfile exists AND kill-0), pick the one with the
289
+ * FRESHEST pane-log. A peer can be alive on >1 runtime at once (a `/codex` flip can leave
290
+ * the old session running alongside the new), so the freshest-pane-log among the alive set
291
+ * is the currently-active surface. The alive-filter is what fixes the flip-race: a
292
+ * just-died runtime's pane-log / .sock / .session all LINGER, but its pid is dead → it is
293
+ * excluded here. Returns null when no session is alive.
294
+ *
295
+ * Deps are injectable for hermetic tests (the defaults read the real run-dir + pane-logs).
296
+ */
297
+ export function resolveLiveRuntime(
298
+ personality: string,
299
+ deps: { aliveRuntimes?: (p: string) => string[]; paneLogMtime?: (address: string) => number | null } = {},
300
+ ): string | null {
301
+ const aliveOf = deps.aliveRuntimes ?? ((p: string) => listHostedPeers().filter(h => h.personality === p).map(h => h.runtime))
302
+ const paneMt = deps.paneLogMtime ?? defaultPaneLogMtime
303
+ const alive = aliveOf(personality)
304
+ if (alive.length <= 1) return alive[0] ?? null
305
+ // >1 alive → the freshest pane-log mtime (the currently-rendering turn surface).
306
+ return alive.reduce((best, rt) => ((paneMt(`${rt}-${personality}`) ?? 0) > (paneMt(`${best}-${personality}`) ?? 0) ? rt : best))
307
+ }
308
+
282
309
 
283
310
  // ─── Compact done gate (dialogue control must report FACT, not keystroke delivery) ──
284
311