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 +37 -0
- package/dist/index.js +5 -0
- package/dist/services/session.js +74 -0
- package/package.json +2 -2
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) => {
|
package/dist/services/session.js
CHANGED
|
@@ -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.
|
|
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.
|
|
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",
|