alvin-bot 4.5.1 → 4.7.0
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 +278 -0
- package/README.md +25 -2
- package/bin/cli.js +325 -26
- package/dist/handlers/commands.js +505 -63
- package/dist/handlers/message.js +209 -14
- package/dist/i18n.js +470 -13
- package/dist/index.js +45 -5
- package/dist/providers/claude-sdk-provider.js +106 -14
- package/dist/providers/ollama-provider.js +32 -0
- package/dist/providers/openai-compatible.js +10 -1
- package/dist/providers/registry.js +112 -17
- package/dist/providers/types.js +25 -3
- package/dist/services/compaction.js +2 -0
- package/dist/services/cron.js +53 -42
- package/dist/services/heartbeat.js +41 -7
- package/dist/services/language-detect.js +12 -2
- package/dist/services/ollama-manager.js +339 -0
- package/dist/services/personality.js +20 -14
- package/dist/services/session.js +21 -3
- package/dist/services/subagent-delivery.js +266 -0
- package/dist/services/subagent-stats.js +123 -0
- package/dist/services/subagents.js +509 -42
- package/dist/services/telegram.js +28 -1
- package/dist/services/updater.js +158 -0
- package/dist/services/usage-tracker.js +11 -4
- package/dist/services/users.js +2 -1
- package/docs/HANDBOOK.md +856 -0
- package/package.json +7 -2
- package/test/claude-sdk-provider.test.ts +69 -0
- package/test/i18n.test.ts +108 -0
- package/test/registry.test.ts +201 -0
- package/test/subagent-delivery.test.ts +273 -0
- package/test/subagent-stats.test.ts +119 -0
- package/test/subagents-commands.test.ts +64 -0
- package/test/subagents-config.test.ts +114 -0
- package/test/subagents-depth.test.ts +58 -0
- package/test/subagents-inheritance.test.ts +67 -0
- package/test/subagents-name-resolver.test.ts +122 -0
- package/test/subagents-priority-reject.test.ts +88 -0
- package/test/subagents-queue.test.ts +127 -0
- package/test/subagents-shutdown.test.ts +126 -0
- package/test/subagents-toolset.test.ts +51 -0
- package/vitest.config.ts +17 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,284 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to Alvin Bot are documented here.
|
|
4
4
|
|
|
5
|
+
## [4.7.0] — 2026-04-11
|
|
6
|
+
|
|
7
|
+
### ✨ Sub-Agents Stufe 2 — live-stream, bounded queue, 24h stats
|
|
8
|
+
|
|
9
|
+
Stufe 2 of the sub-agents refinement spec lands alongside the same-day 4.6.0 release. Everything here builds on the Stufe 1 foundation and is fully unit-tested (85 passing tests).
|
|
10
|
+
|
|
11
|
+
#### A4 Live-Stream for user-spawns
|
|
12
|
+
|
|
13
|
+
`/subagents visibility live` enables a new delivery mode where user-spawned sub-agents stream their text incrementally into a single Telegram message, then post a completion banner as a separate message.
|
|
14
|
+
|
|
15
|
+
Implementation in `src/services/subagent-delivery.ts`:
|
|
16
|
+
|
|
17
|
+
- `LiveStream` class with `start()` / `update()` / `finalize()`
|
|
18
|
+
- `start()` posts an initial `⏳ <name> thinking…` placeholder and records its `message_id`
|
|
19
|
+
- `update()` is called on every text chunk from the agent's generator; it coalesces rapid updates via a throttle window of **800 ms** so we never exceed Telegram's edit rate limit. Multiple `update()` calls within the window collapse into a single edit with the latest accumulated text.
|
|
20
|
+
- `finalize()` flushes any pending text, replaces the `thinking…` header with the final body, then sends a new banner message so the user gets a completion notification (edits don't trigger push notifications).
|
|
21
|
+
- The live-stream message uses **plain text** (no `parse_mode`) so half-formed markdown during streaming can never cause an edit to be rejected. The final banner does use markdown.
|
|
22
|
+
|
|
23
|
+
Wiring in `runSubAgent`:
|
|
24
|
+
|
|
25
|
+
- Detects `effectiveVisibility === "live"` AND `source === "user"` AND `parentChatId`. Cron and implicit spawns are never live-streamed — cron because there's no interactive watcher, implicit because the parent Claude stream already shows everything inline.
|
|
26
|
+
- Creates the `LiveStream` via `createLiveStream()` before the for-await loop.
|
|
27
|
+
- Calls `liveStream.update(chunk.text)` on every text chunk.
|
|
28
|
+
- Calls `liveStream.finalize(info, result)` after the loop and marks `entry.delivered = true` so `spawnSubAgent.finally()` skips the regular `deliverSubAgentResult` path. If finalize fails, the `delivered` flag stays false and the normal banner delivery fires as a fallback.
|
|
29
|
+
- Falls back to `"banner"` mode transparently if the bot API doesn't support `editMessageText` (e.g. during tests or if `attachBotApi` was never called).
|
|
30
|
+
|
|
31
|
+
Tests added in `test/subagent-delivery.test.ts`:
|
|
32
|
+
|
|
33
|
+
- `start` posts an initial placeholder and stores the message_id
|
|
34
|
+
- `update` coalesces rapid calls into a single throttled edit within the 800 ms window
|
|
35
|
+
- `finalize` posts a banner as a new message
|
|
36
|
+
- `createLiveStream` returns `null` when `editMessageText` is missing
|
|
37
|
+
|
|
38
|
+
#### D3 Bounded priority queue
|
|
39
|
+
|
|
40
|
+
Previously, hitting `maxParallel` returned a hard reject. Now spawn requests that don't fit run into a **bounded priority queue**:
|
|
41
|
+
|
|
42
|
+
- Default cap: **20** slots (configurable via `/subagents queue <n>`, clamped to 0–200)
|
|
43
|
+
- Setting cap to 0 disables the queue entirely and restores the old reject-on-full behavior
|
|
44
|
+
- Priority order on drain: **user > cron > implicit**
|
|
45
|
+
- FIFO within each priority class
|
|
46
|
+
- Drains automatically when a running agent finishes — the `runSubAgent.finally()` now calls `drainQueue()` after cleanup
|
|
47
|
+
|
|
48
|
+
New fields:
|
|
49
|
+
|
|
50
|
+
- `SubAgentsConfig.queueCap: number` — persisted in `~/.alvin-bot/sub-agents.json`
|
|
51
|
+
- `SubAgentInfo.status: "queued"` — new valid state
|
|
52
|
+
- `SubAgentInfo.queuePosition?: number` — 1-based position in the queue, shown in `/subagents list` as `#N`
|
|
53
|
+
|
|
54
|
+
Functions in `subagents.ts`:
|
|
55
|
+
|
|
56
|
+
- `getQueueCap()` / `setQueueCap(n)` — public config accessors
|
|
57
|
+
- `drainQueue()` — called from `runSubAgent.finally()`, pops in priority order and transitions entries from `queued` to `running`
|
|
58
|
+
- `popHighestPriorityQueued()` — internal FIFO-per-priority scan
|
|
59
|
+
- `reindexQueue()` — keeps `SubAgentInfo.queuePosition` in sync after pop/cancel
|
|
60
|
+
- `cancelSubAgent()` now handles queued entries by removing them from the queue without starting `runSubAgent` at all
|
|
61
|
+
- `cancelAllSubAgents()` clears the pending queue before cancelling running agents, so shutdown doesn't spawn anything new
|
|
62
|
+
- `spawnSubAgent()` is split: queue decision first (run immediately vs queue vs reject), then `startRun()` helper starts the background loop
|
|
63
|
+
|
|
64
|
+
Reject messages stay priority-aware (D4) but now mention queue saturation:
|
|
65
|
+
|
|
66
|
+
- `user` spawn + pool full + cron/implicit in pool + queue full → *"Alle Slots belegt (N/M), davon X cron/implicit im Hintergrund. Queue voll (Q/C). /subagents list für Details …"*
|
|
67
|
+
- `user` spawn + pool full + user in pool + queue full → *"Alle Slots belegt (N/M) mit eigenen user-Spawns. Queue voll (Q/C). /subagents cancel <name> oder warten."*
|
|
68
|
+
- Non-user spawns + pool + queue full → *"Sub-agent limit reached (N running, Q/C queued). Wait for a running agent to finish or cancel one."*
|
|
69
|
+
|
|
70
|
+
Tests added in `test/subagents-queue.test.ts`:
|
|
71
|
+
|
|
72
|
+
- Default cap is 20
|
|
73
|
+
- Clamping (negative → 0, above 200 → 200, fractional floors)
|
|
74
|
+
- Round-trip through disk
|
|
75
|
+
- Third spawn at full pool lands as `status: "queued"` with `queuePosition: 1`
|
|
76
|
+
- Queue drains automatically when a running agent finishes
|
|
77
|
+
- Priority order: user spawns drain before cron at the same moment
|
|
78
|
+
- `cancelSubAgent` removes a queued entry
|
|
79
|
+
|
|
80
|
+
The existing priority-reject tests now explicitly set `queueCap = 0` to test the old reject path, and a new "queue enabled" test fills both pool and queue before asserting the reject message.
|
|
81
|
+
|
|
82
|
+
#### H3 24-hour run stats
|
|
83
|
+
|
|
84
|
+
New module `src/services/subagent-stats.ts` — a simple append-only JSON ring buffer persisted to `~/.alvin-bot/subagent-stats.json`. Each completed sub-agent run appends one entry:
|
|
85
|
+
|
|
86
|
+
```ts
|
|
87
|
+
{
|
|
88
|
+
completedAt: number;
|
|
89
|
+
name: string;
|
|
90
|
+
source: "user" | "cron" | "implicit";
|
|
91
|
+
status: "completed" | "timeout" | "error" | "cancelled";
|
|
92
|
+
durationMs: number;
|
|
93
|
+
inputTokens: number;
|
|
94
|
+
outputTokens: number;
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
On every load or append, entries older than 24 hours are pruned. A hard cap of 5000 entries protects against unbounded growth on high-frequency bots.
|
|
99
|
+
|
|
100
|
+
Accessors:
|
|
101
|
+
|
|
102
|
+
- `recordSubAgentRun(info, result)` — called from `runSubAgent.finally()` as a non-blocking side effect. Errors are logged but don't affect delivery.
|
|
103
|
+
- `getSubAgentStats()` — returns a `StatsSummary` with totals, per-source breakdown, and per-status counts.
|
|
104
|
+
|
|
105
|
+
New Telegram command **`/subagents stats`** renders the summary:
|
|
106
|
+
|
|
107
|
+
```
|
|
108
|
+
📊 Sub-Agent Stats — last 24h
|
|
109
|
+
|
|
110
|
+
Total: 44 runs · 165k in / 89k out · 12m
|
|
111
|
+
|
|
112
|
+
By source:
|
|
113
|
+
👤 user: 12 runs · 45k in / 22k out
|
|
114
|
+
⏰ cron: 8 runs · 31k in / 15k out
|
|
115
|
+
🔗 implicit: 24 runs · 89k in / 52k out
|
|
116
|
+
|
|
117
|
+
By status:
|
|
118
|
+
✅ completed: 42
|
|
119
|
+
⚠️ cancelled: 1
|
|
120
|
+
⏱️ timeout: 0
|
|
121
|
+
❌ error: 1
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
The JSON backing file is a deliberate short-term choice. When the SQLite migration lands (already scoped in a separate memory entry as `project_alvinbot_sqlite_migration.md`), we swap the backend without touching `getSubAgentStats()` or `recordSubAgentRun()` — both are designed as a narrow interface.
|
|
125
|
+
|
|
126
|
+
Tests added in `test/subagent-stats.test.ts`:
|
|
127
|
+
|
|
128
|
+
- Fresh install returns zeros
|
|
129
|
+
- Recording 3 runs updates totals + per-source breakdown
|
|
130
|
+
- Persistence + reload round-trip
|
|
131
|
+
- Entries older than 24h are pruned on load
|
|
132
|
+
- `byStatus` tracks cancelled/error/timeout separately
|
|
133
|
+
|
|
134
|
+
### 🖥 CLI: `alvin-bot start` / `stop` now auto-detect LaunchAgent
|
|
135
|
+
|
|
136
|
+
The `start` and `stop` commands previously always went through pm2. That created a conflict after `alvin-bot launchd install`: the LaunchAgent ran the bot, but `alvin-bot start` would happily spawn a second instance via pm2, and `alvin-bot stop` would try to stop a pm2 process that didn't exist.
|
|
137
|
+
|
|
138
|
+
Now both commands check for `~/Library/LaunchAgents/com.alvinbot.app.plist` on macOS and switch transparently:
|
|
139
|
+
|
|
140
|
+
- **`alvin-bot start`** with a LaunchAgent present → `launchctl kickstart -k gui/$UID/com.alvinbot.app` (or `launchctl load -w` if not loaded yet). No pm2 involvement.
|
|
141
|
+
- **`alvin-bot stop`** with a LaunchAgent present → `launchctl unload -w` (doesn't remove the plist, just stops the daemon).
|
|
142
|
+
- **`alvin-bot start`** on macOS without a LaunchAgent → pm2 path + a helpful tip: *"💡 Tip: on macOS with Claude Code, switch to launchd for automatic Keychain access: alvin-bot launchd install"*.
|
|
143
|
+
|
|
144
|
+
Linux and Windows users are unaffected — they always get the pm2 path.
|
|
145
|
+
|
|
146
|
+
### 🐛 Other
|
|
147
|
+
|
|
148
|
+
- `/subagents queue` is registered in the usage string for en/de/es/fr.
|
|
149
|
+
- `/subagents stats` is registered in the usage string for en/de/es/fr.
|
|
150
|
+
- `/subagents visibility` usage now lists `live` as a valid mode.
|
|
151
|
+
- Removed the leftover `alvin-bot-4.5.1.tgz` from the repo root.
|
|
152
|
+
|
|
153
|
+
## [4.6.0] — 2026-04-11
|
|
154
|
+
|
|
155
|
+
### ✨ Sub-Agents Stufe 1 — context-aware delivery, name-first addressing, shutdown notifications
|
|
156
|
+
|
|
157
|
+
**The big one.** Stufe 1 of the SubAgents refinement spec (9 design axes, two-stage rollout) is complete. Everything here is live-validated on a remote test MacBook via `@Alvin_testbot_bot` over Telegram with Claude Agent SDK + Max OAuth.
|
|
158
|
+
|
|
159
|
+
#### A4 + I3 — Source-aware delivery router
|
|
160
|
+
|
|
161
|
+
New module `src/services/subagent-delivery.ts`. Every completed sub-agent routes through a single entry point that picks its delivery path based on `SubAgentInfo.source`:
|
|
162
|
+
|
|
163
|
+
- `implicit` (Main-Claude calling the SDK `Task` tool) → **no-op**, the parent stream already shows the result.
|
|
164
|
+
- `user` (explicit user spawn) → **banner + final** to `parentChatId` in the originating chat.
|
|
165
|
+
- `cron` (scheduled job) → **banner + final** to the `chatId` from the cron job's target.
|
|
166
|
+
|
|
167
|
+
The banner format is fixed: `{icon} *{name}* {status} · {duration} · {input_tokens} in / {output_tokens} out` followed by the agent output. Status icons: ✅ completed, ⚠️ cancelled, ⏱️ timeout, ❌ error. Duration is human-formatted (`42s`, `3m 12s`). Token counts collapse at 1000 (`4.2k`).
|
|
168
|
+
|
|
169
|
+
Output chunking:
|
|
170
|
+
- ≤3800 chars → single message `banner + body`
|
|
171
|
+
- 3800–20000 chars → banner alone, then body chunks of 3800 chars each
|
|
172
|
+
- \>20000 chars → banner + the body as a `.md` file upload (via `grammy`'s `InputFile`)
|
|
173
|
+
|
|
174
|
+
The bot API is attached lazily at startup via `attachBotApi()` so `subagent-delivery.ts` stays free of a circular import on `index.ts`. Test hook `__setBotApiForTest()` lets Vitest inject a fake.
|
|
175
|
+
|
|
176
|
+
#### New command: `/subagents visibility <auto|banner|silent>`
|
|
177
|
+
|
|
178
|
+
Per-install persistent visibility setting, written to `~/.alvin-bot/sub-agents.json`. `silent` suppresses the delivery entirely — the result is still stored in the `activeAgents` map and pullable via `/subagents result <name>`. `auto` is the default and falls through to the source-based routing described above.
|
|
179
|
+
|
|
180
|
+
#### B2 — Name-first addressing with automatic `#N` collision suffixes
|
|
181
|
+
|
|
182
|
+
`/subagents cancel <name|id>` and `/subagents result <name|id>` now accept names, not just UUIDs. When a new spawn collides with an existing name, the resolver appends `#2`, `#3`, … using the smallest free index. Example: three parallel `review` spawns appear as `review`, `review#2`, `review#3` in `/subagents list`.
|
|
183
|
+
|
|
184
|
+
Resolution order:
|
|
185
|
+
1. Explicit `#N` suffix (e.g. `review#2`) → exact match wins, never falls through to ambiguity
|
|
186
|
+
2. Base name with a single sibling → that sibling
|
|
187
|
+
3. Base name with multiple siblings **and** `ambiguousAsList: true` opt-in → disambiguation reply listing all candidates
|
|
188
|
+
4. Base name with multiple siblings, no opt-in → first sibling
|
|
189
|
+
5. No name match → UUID prefix (back-compat)
|
|
190
|
+
|
|
191
|
+
#### C3 — Parent inheritance
|
|
192
|
+
|
|
193
|
+
Sub-agents now inherit `workingDir` (with `inheritCwd: false` opt-out), `CLAUDE.md` (via `settingSources: ["project"]`), and the registry's provider/model. Conversation history is **not** inherited — the sub-agent reads only its own prompt, which forces clean, self-describing spawn requests and keeps parallel agents from colliding on shared context.
|
|
194
|
+
|
|
195
|
+
#### D4 — Priority-aware reject messages
|
|
196
|
+
|
|
197
|
+
Pool is still strictly capped (no preemption), but the error message when it's full now depends on who holds the slots:
|
|
198
|
+
- User spawn + background (cron/implicit) hold slots → message points at `/subagents list` so the user knows the pool isn't stuck on another interactive task
|
|
199
|
+
- User spawn + other user spawns → suggests cancel-or-wait with command hints
|
|
200
|
+
- Cron/implicit rejects → generic "limit reached" (those callers handle retry themselves)
|
|
201
|
+
|
|
202
|
+
#### E2 — Shutdown notifications
|
|
203
|
+
|
|
204
|
+
`cancelAllSubAgents(notify: true)` is now async and fires a delivery to each still-running agent before the process exits. Each notification is a synth `cancelled` result with the body `⚠️ Agent wurde durch Bot-Restart unterbrochen. Bitte neu triggern.` and routes through the normal I3 delivery path. Total delivery phase is capped at 5s so a hanging Telegram send can't block shutdown.
|
|
205
|
+
|
|
206
|
+
The shutdown hook in `src/index.ts` now `await`s `cancelAllSubAgents(true)` before stopping the grammy bot and tearing down plugins.
|
|
207
|
+
|
|
208
|
+
#### F2 — Depth cap (hard limit = 2)
|
|
209
|
+
|
|
210
|
+
`SubAgentConfig.depth` is a new optional field (defaults to 0 = root). `spawnSubAgent` rejects any depth > 2 with a clear error. The depth shows in `/subagents list` as `d0` / `d1` / `d2` with 2-space indentation per level, so nested scatter-gather runs are visually nested.
|
|
211
|
+
|
|
212
|
+
#### G1 — Toolset preset infrastructure
|
|
213
|
+
|
|
214
|
+
New `SubAgentConfig.toolset` field with a single valid value `"full"`. Runtime validation rejects any other string. This is purely infrastructure for future `"readonly"` / `"research"` presets — no behavior change today, but adding a preset later is a one-line diff.
|
|
215
|
+
|
|
216
|
+
#### H2 — Per-run token accounting in the banner
|
|
217
|
+
|
|
218
|
+
Every completed sub-agent's banner carries the input/output token counts it actually consumed. No aggregation (H3) — that comes later with the SQLite migration. For now, you can see "this agent cost me 4.2k/2.1k" right next to the result.
|
|
219
|
+
|
|
220
|
+
#### Tests
|
|
221
|
+
|
|
222
|
+
67 passing Vitest tests across 12 files. New test files added for this release:
|
|
223
|
+
- `test/claude-sdk-provider.test.ts` — auth probe + `isAuthErrorOutput` helper
|
|
224
|
+
- `test/subagents-depth.test.ts` — depth cap (F2)
|
|
225
|
+
- `test/subagents-inheritance.test.ts` — cwd inheritance (C3)
|
|
226
|
+
- `test/subagents-toolset.test.ts` — toolset literal (G1)
|
|
227
|
+
- `test/subagents-name-resolver.test.ts` — `findSubAgentByName` including regression for exact-match vs ambiguity
|
|
228
|
+
- `test/subagents-commands.test.ts` — `cancelSubAgentByName`/`getSubAgentResultByName` helpers
|
|
229
|
+
- `test/subagent-delivery.test.ts` — I3 delivery router (all 5 source/visibility paths)
|
|
230
|
+
- `test/subagents-shutdown.test.ts` — E2 notify=true / notify=false + regression for shutdown double-delivery
|
|
231
|
+
- `test/subagents-priority-reject.test.ts` — D4 priority-aware reject messages
|
|
232
|
+
- `test/subagents-config.test.ts` — expanded with visibility config round-trip
|
|
233
|
+
|
|
234
|
+
### 🖥 New CLI: `alvin-bot launchd install|uninstall|status` (macOS only)
|
|
235
|
+
|
|
236
|
+
**Why this matters.** Claude Code 2.x stores the Max-subscription OAuth token in the macOS Keychain, service `"Claude Code-credentials"`. Accessing the token requires:
|
|
237
|
+
1. A Keychain ACL that permits the `claude` binary (granted via the "Always Allow" dialog on first GUI invocation)
|
|
238
|
+
2. An *unlocked* Keychain in the calling process's security context
|
|
239
|
+
|
|
240
|
+
Processes started via SSH, pm2, or `nohup` run in a detached launchd session that does **not** inherit the GUI user's unlocked-Keychain state. Even a manual `security unlock-keychain -p '...'` only unlocks the current SSH session — the pm2 daemon running in its own context stays locked out. Result: the Bot saw `Not logged in · Please run /login` on every sub-agent query, and the fix in 4.6.0's Phase 0 exposes that as a clean error instead of leaking it as chat text.
|
|
241
|
+
|
|
242
|
+
**The fix**: run the bot as a **launchd user agent**. LaunchAgents run inside the GUI login session and inherit the unlocked Keychain automatically. No SSH dance, no pm2 drama, no manual unlocks on every restart.
|
|
243
|
+
|
|
244
|
+
```
|
|
245
|
+
alvin-bot launchd install — Write ~/Library/LaunchAgents/com.alvinbot.app.plist,
|
|
246
|
+
unload any existing instance, launchctl load -w.
|
|
247
|
+
alvin-bot launchd uninstall — Unload and rm the plist.
|
|
248
|
+
alvin-bot launchd status — Plist existence, PID from `launchctl list`,
|
|
249
|
+
tail of ~/.alvin-bot/logs/alvin-bot.{out,err}.log.
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
Plist details:
|
|
253
|
+
- `KeepAlive` → auto-restart on crash, not on successful exit
|
|
254
|
+
- `RunAtLoad` → starts on login
|
|
255
|
+
- `ThrottleInterval 10` → prevents rapid restart loops
|
|
256
|
+
- `PATH` covers `~/.local/bin`, `/opt/homebrew/bin` (Apple Silicon), `/usr/local/bin` (Intel Homebrew)
|
|
257
|
+
- stdout → `~/.alvin-bot/logs/alvin-bot.out.log`
|
|
258
|
+
- stderr → `~/.alvin-bot/logs/alvin-bot.err.log`
|
|
259
|
+
|
|
260
|
+
macOS users should migrate from `alvin-bot start` (pm2) to `alvin-bot launchd install`. Pm2 still works and remains the Linux/Windows default.
|
|
261
|
+
|
|
262
|
+
### 🐛 Bug fixes
|
|
263
|
+
|
|
264
|
+
- **`ClaudeSDKProvider.isAvailable()` now actually probes authentication.** The old check only ran `claude --version`, which succeeds whether or not the CLI has a valid OAuth token. A locked-out CLI would be reported as available, and the `Not logged in` response would leak into the chat as a normal assistant message. New behavior: `claude --version` for the binary check, then `claude -p "ping"` to verify auth. If the output matches the "Not logged in" pattern, the provider reports `false` and the registry falls through to the next provider.
|
|
265
|
+
|
|
266
|
+
- **`ClaudeSDKProvider.query()` surfaces `Not logged in` as an error chunk.** Even in code paths where `isAvailable()` returned stale cache, a runtime failure during the stream would emit `Not logged in · Please run /login` as text. The query loop now detects the auth pattern on the first text chunk and yields a typed `error` chunk with a clear "Run `claude login`" message, instead of pretending it's a normal response.
|
|
267
|
+
|
|
268
|
+
- **`/subagents cancel|result <name#N>` now hits the exact entry.** Regression caught during the remote test: asking for `test-ping#2` returned the "Mehrdeutig — welchen meinst du?" ambiguity reply instead of the specific `#2` entry, because `findSubAgentByName` checked base-name siblings before the exact-name match when `ambiguousAsList: true` was set. Explicit `#N` queries now always win.
|
|
269
|
+
|
|
270
|
+
- **Shutdown double-delivery race fixed.** If the bot received SIGTERM while a sub-agent was mid-stream, Telegram saw two messages: a "completed · (empty output)" banner from `runSubAgent.finally()` (because the test generator exited gracefully after the abort), followed by the "cancelled · Bot-Restart" banner from `cancelAllSubAgents`. Fixed with a `delivered: boolean` flag on each `activeAgents` entry — whoever posts first sets it, the other skips.
|
|
271
|
+
|
|
272
|
+
- **`providerKeyMap` alignment in `src/index.ts`.** The pre-flight provider-key warning used `gemini-2.5-flash` as the map key, but the registry registers Google Gemini under `google`. Users who set `PRIMARY_PROVIDER=google` never saw the "GOOGLE_API_KEY missing" warning. Fixed by canonical `google → GOOGLE_API_KEY`; legacy custom-model aliases stay for rollback safety.
|
|
273
|
+
|
|
274
|
+
- **`cron.ts` ai-query triple-notification cleanup.** A single failed ai-query cron job was sending three legacy error messages (`slow-fox: cancelled — cancelled`, `AI-Query Error (slow-fox)`, `Cron Error (slow-fox)`) because the failure path fired `notifyCallback` in the inner `if`, the inner `catch`, and the outer `catch`. The I3 delivery router already posts the cancellation banner for ai-query jobs, so all three legacy notify calls are now skipped and ai-query errors propagate via the outer catch for bookkeeping only. Other job types (reminder, shell, http, message) keep the legacy notify path.
|
|
275
|
+
|
|
276
|
+
- **`/subagents` now shows up in Telegram's command autocomplete.** The grammy handler was registered from v4.0.0 but `setMyCommands` never listed it, so users had to know the exact spelling. Added.
|
|
277
|
+
|
|
278
|
+
### 📚 Documentation
|
|
279
|
+
|
|
280
|
+
- New English-language handbook at `docs/HANDBOOK.md` — covers installation, architecture, all providers, the sub-agents system, cron jobs, platform adapters, security audit, and the web UI. Written to be readable standalone without cross-referencing the README.
|
|
281
|
+
- README.md updated with a pointer to the handbook and the new `launchd` command.
|
|
282
|
+
|
|
5
283
|
## [4.5.1] — 2026-04-09
|
|
6
284
|
|
|
7
285
|
### 🐛 TUI Header Rendering Hotfix
|
package/README.md
CHANGED
|
@@ -109,13 +109,29 @@ alvin-bot start
|
|
|
109
109
|
|
|
110
110
|
That's it. The setup wizard validates everything:
|
|
111
111
|
- ✅ Tests your AI provider key
|
|
112
|
-
- ✅ Verifies your Telegram bot token
|
|
112
|
+
- ✅ Verifies your Telegram bot token
|
|
113
113
|
- ✅ Confirms the setup works before you start
|
|
114
114
|
|
|
115
115
|
**Requires:** Node.js 18+ ([nodejs.org](https://nodejs.org)) · Telegram bot token ([@BotFather](https://t.me/BotFather)) · Your Telegram user ID ([@userinfobot](https://t.me/userinfobot))
|
|
116
116
|
|
|
117
117
|
Free AI providers available — no credit card needed.
|
|
118
118
|
|
|
119
|
+
### macOS: use `launchd` instead of pm2 (recommended)
|
|
120
|
+
|
|
121
|
+
If you're on macOS and using Claude Code (Max subscription) as your provider, run the bot as a **LaunchAgent** — it inherits the GUI login session so the macOS Keychain stays unlocked and the Claude OAuth token just works without any manual `security unlock-keychain` dance:
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
alvin-bot launchd install # writes ~/Library/LaunchAgents/com.alvinbot.app.plist and starts the agent
|
|
125
|
+
alvin-bot launchd status # show PID + recent stdout/stderr logs
|
|
126
|
+
alvin-bot launchd uninstall # unload + remove the plist
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Pm2 still works and remains the default on Linux/Windows — but on macOS with Claude Code, `launchd` is the only path that reliably keeps Keychain access over restarts.
|
|
130
|
+
|
|
131
|
+
### 📖 Handbook
|
|
132
|
+
|
|
133
|
+
For a full walkthrough of everything Alvin Bot can do — providers, sub-agents, cron jobs, plugins, MCP, security audit, web UI — read **[`docs/HANDBOOK.md`](docs/HANDBOOK.md)**.
|
|
134
|
+
|
|
119
135
|
### AI Providers
|
|
120
136
|
|
|
121
137
|
| Provider | Cost | Best for |
|
|
@@ -436,7 +452,14 @@ alvin-bot tui # Terminal chat UI ✨
|
|
|
436
452
|
alvin-bot chat # Alias for tui
|
|
437
453
|
alvin-bot doctor # Health check
|
|
438
454
|
alvin-bot update # Pull latest & rebuild
|
|
439
|
-
alvin-bot start # Start the bot
|
|
455
|
+
alvin-bot start # Start the bot (background via pm2)
|
|
456
|
+
alvin-bot start -f # Start in foreground
|
|
457
|
+
alvin-bot stop # Stop the bot
|
|
458
|
+
alvin-bot launchd install # macOS only: install as LaunchAgent
|
|
459
|
+
alvin-bot launchd status # macOS only: show LaunchAgent state
|
|
460
|
+
alvin-bot launchd uninstall # macOS only: remove LaunchAgent
|
|
461
|
+
alvin-bot audit # Security health check
|
|
462
|
+
alvin-bot search # Search assets/memories/skills
|
|
440
463
|
alvin-bot version # Show version
|
|
441
464
|
```
|
|
442
465
|
|