@agfpd/iapeer 0.4.23 → 0.4.24
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 +16 -1
- package/package.json +1 -1
- package/src/connect/index.ts +43 -15
package/CHANGELOG.md
CHANGED
|
@@ -10,6 +10,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
10
10
|
|
|
11
11
|
## [Unreleased]
|
|
12
12
|
|
|
13
|
+
## [0.4.24] - 2026-06-22
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
|
|
17
|
+
- **`iapeer connect telegram <peer>` (and the `enable telegram` alias) no longer fails for a
|
|
18
|
+
peer whose personality isn't a valid Telegram @username** (e.g. a hyphen: "impact-finder").
|
|
19
|
+
connect passed the PERSONALITY as the bot @username to telegram-runtime (which keys bots by
|
|
20
|
+
`<bot-username>`), so a hyphenated personality was rejected ("must be a telegram @username").
|
|
21
|
+
The bot @username is TOKEN-derived (set in @BotFather), never the personality. connect now
|
|
22
|
+
resolves it from the token via Telegram **getMe** and uses that for `bot add` / `interface
|
|
23
|
+
bot` / storage; the human still owes only the token. An invalid/unreachable token fails
|
|
24
|
+
early with a getMe-rejected message. (The getMe lives in the telegram-glue module
|
|
25
|
+
`connect/index.ts` — the foundation core stays Telegram-API-free.)
|
|
26
|
+
|
|
13
27
|
## [0.4.23] - 2026-06-22
|
|
14
28
|
|
|
15
29
|
### Added
|
|
@@ -352,7 +366,8 @@ Initial public release — the foundation core of the iapeer multi-agent ecosyst
|
|
|
352
366
|
- **Heterogeneous peers** — AI agents (Claude, Codex), humans (Telegram), and services
|
|
353
367
|
(timer = cron, watcher = event) are all first-class and addressed the same way.
|
|
354
368
|
|
|
355
|
-
[Unreleased]: https://github.com/agfpd/iapeer/compare/v0.4.
|
|
369
|
+
[Unreleased]: https://github.com/agfpd/iapeer/compare/v0.4.24...HEAD
|
|
370
|
+
[0.4.24]: https://github.com/agfpd/iapeer/compare/v0.4.23...v0.4.24
|
|
356
371
|
[0.4.23]: https://github.com/agfpd/iapeer/compare/v0.4.22...v0.4.23
|
|
357
372
|
[0.4.22]: https://github.com/agfpd/iapeer/compare/v0.4.21...v0.4.22
|
|
358
373
|
[0.4.21]: https://github.com/agfpd/iapeer/compare/v0.4.20...v0.4.21
|
package/package.json
CHANGED
package/src/connect/index.ts
CHANGED
|
@@ -5,9 +5,11 @@
|
|
|
5
5
|
//
|
|
6
6
|
// The human owes EXACTLY ONE external fact: the bot token (prompt walks them
|
|
7
7
|
// through @BotFather → /newbot). Everything else the system resolves: the bot
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
8
|
+
// @username via Telegram getMe(token) — the bot key is TOKEN-derived, NOT the peer
|
|
9
|
+
// personality (a personality may contain a hyphen, structurally invalid as a
|
|
10
|
+
// @username; telegram-runtime keys bots by <bot-username>), `telegram-runtime bot add`
|
|
11
|
+
// (also validates the token), `telegram-runtime interface bot` (profile merge;
|
|
12
|
+
// precondition: the peer is registered), then the MANDATORY
|
|
11
13
|
// router-session restart (the live poller reads bots/ ONCE at start, no fs-watch —
|
|
12
14
|
// without the restart the channel is dead both ways), and the activation hint:
|
|
13
15
|
// the FIRST message from the human to the bot opens the chat (a Telegram platform
|
|
@@ -51,6 +53,9 @@ export interface ConnectTelegramOptions {
|
|
|
51
53
|
runTg?: TgRunner
|
|
52
54
|
/** Injectable router restart (tests). Default: stopPeer→startPeer strictly in order. */
|
|
53
55
|
restart?: RestartFn
|
|
56
|
+
/** Injectable bot-@username resolver (tests). Default: Telegram getMe(token). Returns the
|
|
57
|
+
* bare @username (no leading @) or null on a rejected/unreachable token. */
|
|
58
|
+
resolveUsername?: (token: string) => Promise<string | null>
|
|
54
59
|
}
|
|
55
60
|
|
|
56
61
|
export interface ConnectTelegramResult {
|
|
@@ -60,6 +65,7 @@ export interface ConnectTelegramResult {
|
|
|
60
65
|
| 'refused-no-token' // non-tty and no --token (or an empty tty answer)
|
|
61
66
|
| 'unregistered-peer' // precondition: the peer must be in the registry
|
|
62
67
|
| 'runtime-missing' // no telegram runtime manifest — install it first
|
|
68
|
+
| 'bad-token' // Telegram getMe rejected the token (invalid/revoked) or was unreachable
|
|
63
69
|
| 'bot-add-failed' // telegram-runtime bot add exited non-zero (incl. getMe refusal)
|
|
64
70
|
| 'interface-failed' // telegram-runtime interface bot exited non-zero
|
|
65
71
|
peer: string
|
|
@@ -93,6 +99,26 @@ function readBotEnv(alias: string, env: NodeJS.ProcessEnv): string | null {
|
|
|
93
99
|
}
|
|
94
100
|
}
|
|
95
101
|
|
|
102
|
+
/**
|
|
103
|
+
* Resolve the bot's REAL @username from the token via Telegram getMe — the bot identity
|
|
104
|
+
* is TOKEN-derived (BotFather sets it), NOT the peer personality. telegram-runtime keys
|
|
105
|
+
* bots by `<bot-username>` (`bot add <bot-username>`, `interface bot <bot-username>`), and a
|
|
106
|
+
* personality can contain a hyphen which is structurally invalid as a @username — so the
|
|
107
|
+
* personality can NEVER be the bot-username. Returns the bare username (no leading @) or
|
|
108
|
+
* null on a rejected/unreachable token. The token goes literally in the URL path
|
|
109
|
+
* (Telegram tokens are `<digits>:<urlsafe>`; no encoding).
|
|
110
|
+
*/
|
|
111
|
+
async function defaultResolveBotUsername(token: string): Promise<string | null> {
|
|
112
|
+
try {
|
|
113
|
+
const r = await fetch(`https://api.telegram.org/bot${token}/getMe`)
|
|
114
|
+
const j = (await r.json()) as { ok?: boolean; result?: { username?: unknown } }
|
|
115
|
+
const u = j?.ok === true && typeof j.result?.username === 'string' ? j.result.username.trim().replace(/^@/, '') : ''
|
|
116
|
+
return u || null
|
|
117
|
+
} catch {
|
|
118
|
+
return null // network error / non-JSON → treat as unresolvable (bad-token)
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
96
122
|
/** Resolve the telegram-runtime CLI bin from the manifest's selfConfig command (the
|
|
97
123
|
* package's own absolute bin — PATH-independent, launchd-safe). */
|
|
98
124
|
function resolveTgBin(env: NodeJS.ProcessEnv): string | null {
|
|
@@ -173,8 +199,17 @@ export async function connectTelegram(opts: ConnectTelegramOptions): Promise<Con
|
|
|
173
199
|
if (!token) return { state: 'refused-no-token', peer, detail: 'no answer — nothing connected' }
|
|
174
200
|
}
|
|
175
201
|
|
|
202
|
+
// Resolve the bot's REAL @username from the token (getMe) — NOT the personality. The
|
|
203
|
+
// bot-username is token-derived; a personality with a hyphen (e.g. "impact-finder") is
|
|
204
|
+
// structurally invalid as a @username and telegram-runtime (which keys bots by
|
|
205
|
+
// <bot-username>) rejects it. getMe also validates the token early (bad token → fail here).
|
|
206
|
+
const resolveUsername = opts.resolveUsername ?? defaultResolveBotUsername
|
|
207
|
+
const botUsername = await resolveUsername(token)
|
|
208
|
+
if (!botUsername) {
|
|
209
|
+
return { state: 'bad-token', peer, detail: 'Telegram getMe rejected the token (invalid/revoked) or was unreachable — recheck the token with @BotFather' }
|
|
210
|
+
}
|
|
176
211
|
const runTg = opts.runTg ?? defaultRunTg(tgBin)
|
|
177
|
-
const alias =
|
|
212
|
+
const alias = botUsername // bot key = the REAL @username from getMe (telegram-runtime keys bots by <bot-username>)
|
|
178
213
|
const before = readBotEnv(alias, env)
|
|
179
214
|
|
|
180
215
|
// (1) bot add — token → bots/<alias>/.env. Owner adds getMe validation here: an
|
|
@@ -183,17 +218,10 @@ export async function connectTelegram(opts: ConnectTelegramOptions): Promise<Con
|
|
|
183
218
|
if (add.status !== 0) {
|
|
184
219
|
return { state: 'bot-add-failed', peer, detail: (add.stderr || add.stdout || `exit ${add.status}`).trim() }
|
|
185
220
|
}
|
|
186
|
-
// @username: the
|
|
187
|
-
//
|
|
188
|
-
//
|
|
189
|
-
|
|
190
|
-
const envAfterAdd = readBotEnv(alias, env)
|
|
191
|
-
const envUser = envAfterAdd?.match(/^TELEGRAM_BOT_USERNAME=(.+)$/m)?.[1]?.trim()
|
|
192
|
-
const username = envUser
|
|
193
|
-
? envUser.startsWith('@')
|
|
194
|
-
? envUser
|
|
195
|
-
: `@${envUser}`
|
|
196
|
-
: add.stdout.match(/@[A-Za-z0-9_]{3,}/)?.[0]
|
|
221
|
+
// @username for display / the activation hint: the bot key (alias) IS the
|
|
222
|
+
// getMe-resolved username, so the display form is simply `@<alias>` — no need to
|
|
223
|
+
// re-parse bots/<alias>/.env (telegram-runtime writes the same value there).
|
|
224
|
+
const username = `@${alias}`
|
|
197
225
|
|
|
198
226
|
// (2) interface bot — merge the channel binding into the peer's profile.
|
|
199
227
|
const iface = runTg(['interface', 'bot', alias, '--peer', peer], env)
|