alvin-bot 4.4.6 โ†’ 4.4.7

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
@@ -2,6 +2,43 @@
2
2
 
3
3
  All notable changes to Alvin Bot are documented here.
4
4
 
5
+ ## [4.4.7] โ€” 2026-04-09
6
+
7
+ ### ๐Ÿ” Security / Dependencies
8
+
9
+ **6 of 9 npm audit vulnerabilities fixed (non-breaking)** โ€” Ran `npm audit fix` to patch the transitive `@xmldom/xmldom` XML injection, `basic-ftp` CRLF command injection, and `brace-expansion` DoS vulnerabilities. Also upgraded the direct dependency `@anthropic-ai/claude-agent-sdk` from `0.2.92` to `0.2.97` (latest, non-breaking patch release with no changes to the `query()` API surface Alvin-Bot uses).
10
+
11
+ Remaining unaddressed (by design, require breaking upgrades or overrides):
12
+ - `@anthropic-ai/sdk` Memory Tool sandbox escape โ€” **not exploitable** in Alvin-Bot because the `Memory` tool is not listed in `allowedTools` (we only use `Read`, `Write`, `Edit`, `Bash`, `Glob`, `Grep`, `WebSearch`, `WebFetch`, `Task`).
13
+ - `electron` (17 advisories) โ€” waiting for a planned breaking upgrade to `electron@41.x`.
14
+
15
+ ### โœจ Stability Improvements
16
+
17
+ **Session memory hygiene (`src/services/session.ts`)** โ€” The in-memory `sessions` Map grew unbounded: every user that ever messaged the bot kept a full session object (including conversation history, cost breakdown, abort controller) forever. On a single-user bot like Ali's this is a non-issue; on any multi-user deployment it's a steady leak.
18
+
19
+ New behavior:
20
+ - **Conservative 7-day TTL**: a session is only eligible for cleanup after 7 full days of complete inactivity. Configurable via `ALVIN_SESSION_TTL_DAYS` env var.
21
+ - **Never touches active sessions**: the cleanup loop explicitly skips any session with `isProcessing === true`.
22
+ - **`lastActivity` touched on every `getSession()` call**: any interaction at all keeps the session alive indefinitely.
23
+ - **Orphaned `abortController` cleanup** before removal (defensive).
24
+ - Runs hourly; logs a message when it actually purges something.
25
+
26
+ This is memory hygiene only โ€” it cannot reduce Alvin-Bot's capabilities, permissions, or responsiveness. Active users see zero behavioral change.
27
+
28
+ **MAX_BUDGET_USD tracking (`src/services/session.ts:trackProviderUsage`)** โ€” The `MAX_BUDGET_USD` config was declared but never read anywhere. Now it's tracked as a **soft warning** (never a block):
29
+ - When a session's cumulative cost crosses 80% of the configured budget, a `โš ๏ธ Session budget 80% consumed` message is logged.
30
+ - When it crosses 100%, a `๐Ÿ’ธ Session budget exceeded โ€ฆ bot continues (no hard limit enforced)` message is logged.
31
+ - **The bot never blocks** โ€” the warnings exist purely as operator signals. `/new` resets the warning flags so subsequent sessions get fresh thresholds.
32
+ - `session.totalCost` is now correctly incremented (previously declared in the interface but never written to).
33
+
34
+ ### ๐Ÿ“ฆ Compatibility
35
+
36
+ No breaking changes. User-facing behavior is identical โ€” same commands, same permissions, same response patterns. The only visible change is new log messages for cleanup events and budget thresholds.
37
+
38
+ ```bash
39
+ npm update -g alvin-bot
40
+ ```
41
+
5
42
  ## [4.4.6] โ€” 2026-04-09
6
43
 
7
44
  ### ๐Ÿ› Bug Fixes
package/dist/index.js CHANGED
@@ -60,6 +60,7 @@ import { loadPlugins, registerPluginCommands, unloadPlugins } from "./services/p
60
60
  import { initMCP, disconnectMCP, hasMCPConfig } from "./services/mcp.js";
61
61
  import { startWebServer } from "./web/server.js";
62
62
  import { startScheduler, stopScheduler, setNotifyCallback } from "./services/cron.js";
63
+ import { startSessionCleanup, stopSessionCleanup } from "./services/session.js";
63
64
  import { processQueue, cleanupQueue, setSenders, enqueue } from "./services/delivery-queue.js";
64
65
  import { discoverTools } from "./services/tool-discovery.js";
65
66
  import { startHeartbeat } from "./services/heartbeat.js";
@@ -214,6 +215,7 @@ const shutdown = async () => {
214
215
  console.log("Graceful shutdown initiated...");
215
216
  cancelAllSubAgents();
216
217
  stopScheduler();
218
+ stopSessionCleanup();
217
219
  if (queueInterval)
218
220
  clearInterval(queueInterval);
219
221
  if (queueCleanupInterval)
@@ -348,6 +350,9 @@ setNotifyCallback(async (target, text) => {
348
350
  enqueue(target.platform, String(target.chatId), text);
349
351
  });
350
352
  startScheduler();
353
+ // Session memory hygiene: purge sessions idle > 7 days (configurable via
354
+ // ALVIN_SESSION_TTL_DAYS). Never touches active sessions โ€” see session.ts.
355
+ startSessionCleanup();
351
356
  // Wire delivery queue senders
352
357
  setSenders({
353
358
  telegram: async (chatId, content) => {
@@ -39,6 +39,11 @@ export function getSession(key) {
39
39
  };
40
40
  sessions.set(k, session);
41
41
  }
42
+ else {
43
+ // Touch lastActivity on every access so the cleanup interval
44
+ // never kills a session that's still being interacted with.
45
+ session.lastActivity = Date.now();
46
+ }
42
47
  return session;
43
48
  }
44
49
  export function resetSession(key) {
@@ -53,16 +58,85 @@ export function resetSession(key) {
53
58
  session.totalOutputTokens = 0;
54
59
  session.history = [];
55
60
  session.startedAt = Date.now();
61
+ // Reset budget warning flags so the user gets fresh warnings in the new session.
62
+ session._budgetWarned80 = false;
63
+ session._budgetWarned100 = false;
56
64
  }
57
65
  /** Track cost, query count, and tokens for a provider. */
58
66
  export function trackProviderUsage(key, providerKey, cost, inputTokens, outputTokens) {
59
67
  const session = getSession(key);
60
68
  session.costByProvider[providerKey] = (session.costByProvider[providerKey] || 0) + cost;
61
69
  session.queriesByProvider[providerKey] = (session.queriesByProvider[providerKey] || 0) + 1;
70
+ session.totalCost += cost;
62
71
  if (inputTokens)
63
72
  session.totalInputTokens += inputTokens;
64
73
  if (outputTokens)
65
74
  session.totalOutputTokens += outputTokens;
75
+ // Soft budget warnings โ€” these NEVER block the bot. They exist purely
76
+ // as log signals so the operator (Ali) can notice unusually expensive
77
+ // sessions. Each threshold fires at most once per session (reset on /new).
78
+ const budget = config.maxBudgetUsd;
79
+ if (budget > 0) {
80
+ const pct = (session.totalCost / budget) * 100;
81
+ if (pct >= 100 && !session._budgetWarned100) {
82
+ console.warn(`๐Ÿ’ธ Session budget exceeded: $${session.totalCost.toFixed(4)} / $${budget.toFixed(2)} (${pct.toFixed(0)}%) โ€” bot continues (no hard limit enforced)`);
83
+ session._budgetWarned100 = true;
84
+ }
85
+ else if (pct >= 80 && !session._budgetWarned80) {
86
+ console.warn(`โš ๏ธ Session budget 80% consumed: $${session.totalCost.toFixed(4)} / $${budget.toFixed(2)}`);
87
+ session._budgetWarned80 = true;
88
+ }
89
+ }
90
+ }
91
+ // โ”€โ”€ Session Cleanup โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
92
+ //
93
+ // Memory hygiene for long-running deployments. The sessions Map would
94
+ // otherwise grow unbounded as new users arrive. The cleanup is deliberately
95
+ // *conservative*:
96
+ // โ€ข Default TTL: 7 days of complete inactivity (not 24h)
97
+ // โ€ข Never touches sessions where isProcessing === true
98
+ // โ€ข Touches lastActivity on every getSession() call, so any interaction
99
+ // in the last 7 days keeps the session alive indefinitely
100
+ // โ€ข Aborts orphaned abort controllers defensively before removal
101
+ //
102
+ // Override via ALVIN_SESSION_TTL_DAYS env var if you want different behavior.
103
+ const SESSION_TTL_DAYS = Number(process.env.ALVIN_SESSION_TTL_DAYS) || 7;
104
+ const SESSION_INACTIVE_TTL_MS = SESSION_TTL_DAYS * 24 * 60 * 60 * 1000;
105
+ const CLEANUP_INTERVAL_MS = 60 * 60 * 1000; // check hourly
106
+ let cleanupTimer = null;
107
+ /** Start the periodic session cleanup. Safe to call multiple times. */
108
+ export function startSessionCleanup() {
109
+ if (cleanupTimer)
110
+ return;
111
+ cleanupTimer = setInterval(() => {
112
+ const now = Date.now();
113
+ let purged = 0;
114
+ for (const [key, s] of sessions) {
115
+ // NEVER kill a session that's actively processing a query.
116
+ if (s.isProcessing)
117
+ continue;
118
+ if (now - s.lastActivity > SESSION_INACTIVE_TTL_MS) {
119
+ if (s.abortController) {
120
+ try {
121
+ s.abortController.abort();
122
+ }
123
+ catch { /* ignore */ }
124
+ }
125
+ sessions.delete(key);
126
+ purged++;
127
+ }
128
+ }
129
+ if (purged > 0) {
130
+ console.log(`๐Ÿงน Session cleanup: purged ${purged} inactive session(s) (TTL: ${SESSION_TTL_DAYS} days)`);
131
+ }
132
+ }, CLEANUP_INTERVAL_MS);
133
+ }
134
+ /** Stop the cleanup timer (for graceful shutdown). */
135
+ export function stopSessionCleanup() {
136
+ if (cleanupTimer) {
137
+ clearInterval(cleanupTimer);
138
+ cleanupTimer = null;
139
+ }
66
140
  }
67
141
  /** Add a message to conversation history (for non-SDK providers). */
68
142
  export function addToHistory(key, message) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "alvin-bot",
3
- "version": "4.4.6",
3
+ "version": "4.4.7",
4
4
  "description": "Alvin Bot โ€” Your personal AI agent on Telegram, WhatsApp, Discord, Signal, and Web.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -159,7 +159,7 @@
159
159
  "url": "git+https://github.com/alvbln/Alvin-Bot.git"
160
160
  },
161
161
  "dependencies": {
162
- "@anthropic-ai/claude-agent-sdk": "^0.2.92",
162
+ "@anthropic-ai/claude-agent-sdk": "^0.2.97",
163
163
  "@hapi/boom": "^10.0.1",
164
164
  "@slack/bolt": "^4.6.0",
165
165
  "@types/node": "^22.0.0",