@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 +30 -1
- package/package.json +1 -1
- package/src/cli/index.ts +60 -24
- package/src/transport/index.ts +27 -0
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.
|
|
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
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
|
-
|
|
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
|
package/src/transport/index.ts
CHANGED
|
@@ -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
|
|