@smyslenny/agent-memory 5.0.2 → 5.1.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 +39 -1
- package/README.md +1 -1
- package/dist/bin/agent-memory.js +197 -11
- package/dist/bin/agent-memory.js.map +1 -1
- package/dist/index.d.ts +65 -3
- package/dist/index.js +266 -11
- package/dist/index.js.map +1 -1
- package/dist/mcp/server.js +336 -14
- package/dist/mcp/server.js.map +1 -1
- package/docs/README-zh.md +1 -2
- package/docs/integrations/generic.md +43 -3
- package/docs/integrations/openclaw.md +47 -7
- package/docs/migration-v3-v4.md +15 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,43 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 5.1.0 (2026-03-20)
|
|
4
|
+
|
|
5
|
+
### ✨ Features
|
|
6
|
+
|
|
7
|
+
#### Archive on Eviction (淘汰归档)
|
|
8
|
+
|
|
9
|
+
- Memories evicted by governance are now **archived** to `memory_archive` instead
|
|
10
|
+
of permanently deleted. Only memories with `vitality ≥ 0.1` are archived;
|
|
11
|
+
lower-vitality memories (decayed noise) are still directly deleted.
|
|
12
|
+
- New schema v8: adds `memory_archive` table (migration `v7 → v8` runs
|
|
13
|
+
automatically on startup).
|
|
14
|
+
- New core functions: `archiveMemory()`, `restoreMemory()`, `listArchivedMemories()`,
|
|
15
|
+
`purgeArchive()`.
|
|
16
|
+
- New MCP tool **`archive`**: `list` / `restore` / `purge` actions for managing
|
|
17
|
+
archived memories.
|
|
18
|
+
- `GovernResult` now includes `archived` (count of memories actually written to
|
|
19
|
+
the archive table) and `evictedByType` breakdown.
|
|
20
|
+
|
|
21
|
+
#### Tiered Capacity (分层容量)
|
|
22
|
+
|
|
23
|
+
- Governance now enforces **per-type capacity limits** before the global cap.
|
|
24
|
+
Defaults: `identity: unlimited`, `emotion: 50`, `knowledge: 250`, `event: 50`,
|
|
25
|
+
`total: 350`.
|
|
26
|
+
- Configurable via environment variables: `AGENT_MEMORY_MAX_IDENTITY`,
|
|
27
|
+
`AGENT_MEMORY_MAX_EMOTION`, `AGENT_MEMORY_MAX_KNOWLEDGE`,
|
|
28
|
+
`AGENT_MEMORY_MAX_EVENT`, `AGENT_MEMORY_MAX_MEMORIES`.
|
|
29
|
+
- `status` MCP tool now returns a `capacity` object showing per-type counts and
|
|
30
|
+
limits.
|
|
31
|
+
- Identity memories (P0) are **never evicted** unless an explicit
|
|
32
|
+
`AGENT_MEMORY_MAX_IDENTITY` is set.
|
|
33
|
+
|
|
34
|
+
### ♻️ Notes
|
|
35
|
+
|
|
36
|
+
- Tidy phase (`runTidy`) still deletes low-vitality memories directly — no
|
|
37
|
+
archiving. Only govern-phase evictions (capacity-based) go to the archive.
|
|
38
|
+
- All new parameters have defaults; upgrading from 5.0.x requires no config
|
|
39
|
+
changes. Schema migration is automatic.
|
|
40
|
+
|
|
3
41
|
## 5.0.1 (2026-03-20)
|
|
4
42
|
|
|
5
43
|
### 🐛 Fixes
|
|
@@ -18,7 +56,7 @@
|
|
|
18
56
|
v5 is a major feature release that adds six intelligence capabilities to the
|
|
19
57
|
memory layer. All features are backward-compatible with v4 workflows.
|
|
20
58
|
|
|
21
|
-
Design document:
|
|
59
|
+
Design document: see the v5 feature table in [README.md](README.md).
|
|
22
60
|
|
|
23
61
|
#### F1: Memory Links (记忆关联)
|
|
24
62
|
|
package/README.md
CHANGED
|
@@ -22,7 +22,7 @@ AgentMemory is a SQLite-first memory layer for AI agents. It lets an agent:
|
|
|
22
22
|
- **maintain** them over time with `reflect`, `reindex`, and feedback signals
|
|
23
23
|
- **integrate** through **CLI**, **MCP stdio**, or **HTTP/SSE**
|
|
24
24
|
|
|
25
|
-
Current release: **`5.0.
|
|
25
|
+
Current release: **`5.0.2`**.
|
|
26
26
|
|
|
27
27
|
Without an embedding provider, AgentMemory still works in **BM25-only mode**.
|
|
28
28
|
With one configured, it adds **hybrid recall** and **semantic dedup**.
|
package/dist/bin/agent-memory.js
CHANGED
|
@@ -100,6 +100,11 @@ function migrateDatabase(db, from, to) {
|
|
|
100
100
|
v = 7;
|
|
101
101
|
continue;
|
|
102
102
|
}
|
|
103
|
+
if (v === 7) {
|
|
104
|
+
migrateV7ToV8(db);
|
|
105
|
+
v = 8;
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
103
108
|
throw new Error(`Unsupported schema migration path: v${from} \u2192 v${to} (stuck at v${v})`);
|
|
104
109
|
}
|
|
105
110
|
}
|
|
@@ -187,6 +192,8 @@ function inferSchemaVersion(db) {
|
|
|
187
192
|
const hasFeedbackEvents = tableExists(db, "feedback_events");
|
|
188
193
|
const hasEmotionTag = tableHasColumn(db, "memories", "emotion_tag");
|
|
189
194
|
const hasProvenance = tableHasColumn(db, "memories", "source_session") && tableHasColumn(db, "memories", "source_context") && tableHasColumn(db, "memories", "observed_at");
|
|
195
|
+
const hasMemoryArchive = tableExists(db, "memory_archive");
|
|
196
|
+
if (hasAgentScopedPaths && hasAgentScopedLinks && hasV4Embeddings && hasMaintenanceJobs && hasFeedbackEvents && hasEmotionTag && hasProvenance && hasMemoryArchive) return 8;
|
|
190
197
|
if (hasAgentScopedPaths && hasAgentScopedLinks && hasV4Embeddings && hasMaintenanceJobs && hasFeedbackEvents && hasEmotionTag && hasProvenance) return 7;
|
|
191
198
|
if (hasAgentScopedPaths && hasAgentScopedLinks && hasV4Embeddings && hasMaintenanceJobs && hasFeedbackEvents && hasEmotionTag) return 6;
|
|
192
199
|
if (hasAgentScopedPaths && hasAgentScopedLinks && hasV4Embeddings && hasMaintenanceJobs && hasFeedbackEvents) return 5;
|
|
@@ -228,6 +235,11 @@ function ensureIndexes(db) {
|
|
|
228
235
|
db.exec("CREATE INDEX IF NOT EXISTS idx_feedback_events_agent_source ON feedback_events(agent_id, source, created_at DESC);");
|
|
229
236
|
}
|
|
230
237
|
}
|
|
238
|
+
if (tableExists(db, "memory_archive")) {
|
|
239
|
+
db.exec("CREATE INDEX IF NOT EXISTS idx_memory_archive_agent ON memory_archive(agent_id);");
|
|
240
|
+
db.exec("CREATE INDEX IF NOT EXISTS idx_memory_archive_type ON memory_archive(type);");
|
|
241
|
+
db.exec("CREATE INDEX IF NOT EXISTS idx_memory_archive_archived_at ON memory_archive(archived_at);");
|
|
242
|
+
}
|
|
231
243
|
}
|
|
232
244
|
function ensureFeedbackEventSchema(db) {
|
|
233
245
|
if (!tableExists(db, "feedback_events")) return;
|
|
@@ -403,11 +415,55 @@ function migrateV6ToV7(db) {
|
|
|
403
415
|
throw e;
|
|
404
416
|
}
|
|
405
417
|
}
|
|
418
|
+
function migrateV7ToV8(db) {
|
|
419
|
+
if (tableExists(db, "memory_archive")) {
|
|
420
|
+
db.prepare("INSERT OR REPLACE INTO schema_meta (key, value) VALUES ('version', ?)").run(String(8));
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
423
|
+
try {
|
|
424
|
+
db.exec("BEGIN");
|
|
425
|
+
db.exec(`
|
|
426
|
+
CREATE TABLE IF NOT EXISTS memory_archive (
|
|
427
|
+
id TEXT PRIMARY KEY,
|
|
428
|
+
content TEXT NOT NULL,
|
|
429
|
+
type TEXT NOT NULL,
|
|
430
|
+
priority INTEGER NOT NULL,
|
|
431
|
+
emotion_val REAL NOT NULL DEFAULT 0.0,
|
|
432
|
+
vitality REAL NOT NULL DEFAULT 0.0,
|
|
433
|
+
stability REAL NOT NULL DEFAULT 1.0,
|
|
434
|
+
access_count INTEGER NOT NULL DEFAULT 0,
|
|
435
|
+
last_accessed TEXT,
|
|
436
|
+
created_at TEXT NOT NULL,
|
|
437
|
+
updated_at TEXT NOT NULL,
|
|
438
|
+
archived_at TEXT NOT NULL,
|
|
439
|
+
archive_reason TEXT NOT NULL DEFAULT 'eviction',
|
|
440
|
+
source TEXT,
|
|
441
|
+
agent_id TEXT NOT NULL DEFAULT 'default',
|
|
442
|
+
hash TEXT,
|
|
443
|
+
emotion_tag TEXT,
|
|
444
|
+
source_session TEXT,
|
|
445
|
+
source_context TEXT,
|
|
446
|
+
observed_at TEXT
|
|
447
|
+
);
|
|
448
|
+
`);
|
|
449
|
+
db.exec("CREATE INDEX IF NOT EXISTS idx_memory_archive_agent ON memory_archive(agent_id);");
|
|
450
|
+
db.exec("CREATE INDEX IF NOT EXISTS idx_memory_archive_type ON memory_archive(type);");
|
|
451
|
+
db.exec("CREATE INDEX IF NOT EXISTS idx_memory_archive_archived_at ON memory_archive(archived_at);");
|
|
452
|
+
db.prepare("INSERT OR REPLACE INTO schema_meta (key, value) VALUES ('version', ?)").run(String(8));
|
|
453
|
+
db.exec("COMMIT");
|
|
454
|
+
} catch (e) {
|
|
455
|
+
try {
|
|
456
|
+
db.exec("ROLLBACK");
|
|
457
|
+
} catch {
|
|
458
|
+
}
|
|
459
|
+
throw e;
|
|
460
|
+
}
|
|
461
|
+
}
|
|
406
462
|
var SCHEMA_VERSION, SCHEMA_SQL;
|
|
407
463
|
var init_db = __esm({
|
|
408
464
|
"src/core/db.ts"() {
|
|
409
465
|
"use strict";
|
|
410
|
-
SCHEMA_VERSION =
|
|
466
|
+
SCHEMA_VERSION = 8;
|
|
411
467
|
SCHEMA_SQL = `
|
|
412
468
|
-- Memory entries
|
|
413
469
|
CREATE TABLE IF NOT EXISTS memories (
|
|
@@ -513,6 +569,30 @@ CREATE TABLE IF NOT EXISTS schema_meta (
|
|
|
513
569
|
value TEXT NOT NULL
|
|
514
570
|
);
|
|
515
571
|
|
|
572
|
+
-- Memory archive (eviction archive)
|
|
573
|
+
CREATE TABLE IF NOT EXISTS memory_archive (
|
|
574
|
+
id TEXT PRIMARY KEY,
|
|
575
|
+
content TEXT NOT NULL,
|
|
576
|
+
type TEXT NOT NULL,
|
|
577
|
+
priority INTEGER NOT NULL,
|
|
578
|
+
emotion_val REAL NOT NULL DEFAULT 0.0,
|
|
579
|
+
vitality REAL NOT NULL DEFAULT 0.0,
|
|
580
|
+
stability REAL NOT NULL DEFAULT 1.0,
|
|
581
|
+
access_count INTEGER NOT NULL DEFAULT 0,
|
|
582
|
+
last_accessed TEXT,
|
|
583
|
+
created_at TEXT NOT NULL,
|
|
584
|
+
updated_at TEXT NOT NULL,
|
|
585
|
+
archived_at TEXT NOT NULL,
|
|
586
|
+
archive_reason TEXT NOT NULL DEFAULT 'eviction',
|
|
587
|
+
source TEXT,
|
|
588
|
+
agent_id TEXT NOT NULL DEFAULT 'default',
|
|
589
|
+
hash TEXT,
|
|
590
|
+
emotion_tag TEXT,
|
|
591
|
+
source_session TEXT,
|
|
592
|
+
source_context TEXT,
|
|
593
|
+
observed_at TEXT
|
|
594
|
+
);
|
|
595
|
+
|
|
516
596
|
-- Indexes for common queries
|
|
517
597
|
CREATE INDEX IF NOT EXISTS idx_memories_type ON memories(type);
|
|
518
598
|
CREATE INDEX IF NOT EXISTS idx_memories_priority ON memories(priority);
|
|
@@ -523,6 +603,9 @@ CREATE INDEX IF NOT EXISTS idx_paths_memory ON paths(memory_id);
|
|
|
523
603
|
CREATE INDEX IF NOT EXISTS idx_paths_domain ON paths(domain);
|
|
524
604
|
CREATE INDEX IF NOT EXISTS idx_maintenance_jobs_phase_status ON maintenance_jobs(phase, status, started_at DESC);
|
|
525
605
|
CREATE INDEX IF NOT EXISTS idx_feedback_events_memory ON feedback_events(memory_id, created_at DESC);
|
|
606
|
+
CREATE INDEX IF NOT EXISTS idx_memory_archive_agent ON memory_archive(agent_id);
|
|
607
|
+
CREATE INDEX IF NOT EXISTS idx_memory_archive_type ON memory_archive(type);
|
|
608
|
+
CREATE INDEX IF NOT EXISTS idx_memory_archive_archived_at ON memory_archive(archived_at);
|
|
526
609
|
`;
|
|
527
610
|
}
|
|
528
611
|
});
|
|
@@ -1152,9 +1235,54 @@ function updateMemory(db, id, input) {
|
|
|
1152
1235
|
}
|
|
1153
1236
|
function deleteMemory(db, id) {
|
|
1154
1237
|
db.prepare("DELETE FROM memories_fts WHERE id = ?").run(id);
|
|
1238
|
+
try {
|
|
1239
|
+
db.prepare("DELETE FROM embeddings WHERE memory_id = ?").run(id);
|
|
1240
|
+
} catch {
|
|
1241
|
+
}
|
|
1155
1242
|
const result = db.prepare("DELETE FROM memories WHERE id = ?").run(id);
|
|
1156
1243
|
return result.changes > 0;
|
|
1157
1244
|
}
|
|
1245
|
+
function archiveMemory(db, id, reason, opts) {
|
|
1246
|
+
const mem = getMemory(db, id);
|
|
1247
|
+
if (!mem) return false;
|
|
1248
|
+
const minVitality = opts?.minVitality ?? 0.1;
|
|
1249
|
+
if (mem.vitality < minVitality) {
|
|
1250
|
+
deleteMemory(db, id);
|
|
1251
|
+
return "deleted";
|
|
1252
|
+
}
|
|
1253
|
+
const archivedAt = now();
|
|
1254
|
+
const archiveReason = reason ?? "eviction";
|
|
1255
|
+
db.prepare(
|
|
1256
|
+
`INSERT OR REPLACE INTO memory_archive
|
|
1257
|
+
(id, content, type, priority, emotion_val, vitality, stability, access_count,
|
|
1258
|
+
last_accessed, created_at, updated_at, archived_at, archive_reason,
|
|
1259
|
+
source, agent_id, hash, emotion_tag, source_session, source_context, observed_at)
|
|
1260
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
1261
|
+
).run(
|
|
1262
|
+
mem.id,
|
|
1263
|
+
mem.content,
|
|
1264
|
+
mem.type,
|
|
1265
|
+
mem.priority,
|
|
1266
|
+
mem.emotion_val,
|
|
1267
|
+
mem.vitality,
|
|
1268
|
+
mem.stability,
|
|
1269
|
+
mem.access_count,
|
|
1270
|
+
mem.last_accessed,
|
|
1271
|
+
mem.created_at,
|
|
1272
|
+
mem.updated_at,
|
|
1273
|
+
archivedAt,
|
|
1274
|
+
archiveReason,
|
|
1275
|
+
mem.source,
|
|
1276
|
+
mem.agent_id,
|
|
1277
|
+
mem.hash,
|
|
1278
|
+
mem.emotion_tag,
|
|
1279
|
+
mem.source_session,
|
|
1280
|
+
mem.source_context,
|
|
1281
|
+
mem.observed_at
|
|
1282
|
+
);
|
|
1283
|
+
deleteMemory(db, id);
|
|
1284
|
+
return "archived";
|
|
1285
|
+
}
|
|
1158
1286
|
function listMemories(db, opts) {
|
|
1159
1287
|
const conditions = [];
|
|
1160
1288
|
const params = [];
|
|
@@ -3003,13 +3131,31 @@ function rankEvictionCandidates(db, opts) {
|
|
|
3003
3131
|
return left.memory.priority - right.memory.priority;
|
|
3004
3132
|
});
|
|
3005
3133
|
}
|
|
3134
|
+
function parseEnvInt(envKey) {
|
|
3135
|
+
const raw = process.env[envKey];
|
|
3136
|
+
if (raw === void 0 || raw === "") return null;
|
|
3137
|
+
const n = Number.parseInt(raw, 10);
|
|
3138
|
+
return Number.isFinite(n) && n > 0 ? n : null;
|
|
3139
|
+
}
|
|
3140
|
+
function getTieredCapacity(opts) {
|
|
3141
|
+
const envMax = parseEnvInt("AGENT_MEMORY_MAX_MEMORIES");
|
|
3142
|
+
return {
|
|
3143
|
+
identity: parseEnvInt("AGENT_MEMORY_MAX_IDENTITY"),
|
|
3144
|
+
// default: null (unlimited)
|
|
3145
|
+
emotion: parseEnvInt("AGENT_MEMORY_MAX_EMOTION") ?? 50,
|
|
3146
|
+
knowledge: parseEnvInt("AGENT_MEMORY_MAX_KNOWLEDGE") ?? 250,
|
|
3147
|
+
event: parseEnvInt("AGENT_MEMORY_MAX_EVENT") ?? 50,
|
|
3148
|
+
total: opts?.maxMemories ?? (envMax ?? 350)
|
|
3149
|
+
};
|
|
3150
|
+
}
|
|
3006
3151
|
function runGovern(db, opts) {
|
|
3007
3152
|
const agentId = opts?.agent_id;
|
|
3008
|
-
const
|
|
3009
|
-
const maxMemories = opts?.maxMemories ?? (Number.isFinite(envMax) && envMax > 0 ? envMax : 200);
|
|
3153
|
+
const capacity = getTieredCapacity(opts);
|
|
3010
3154
|
let orphanPaths = 0;
|
|
3011
3155
|
let emptyMemories = 0;
|
|
3012
3156
|
let evicted = 0;
|
|
3157
|
+
let archived = 0;
|
|
3158
|
+
const evictedByType = {};
|
|
3013
3159
|
const transaction = db.transaction(() => {
|
|
3014
3160
|
const pathResult = agentId ? db.prepare(
|
|
3015
3161
|
`DELETE FROM paths
|
|
@@ -3019,17 +3165,48 @@ function runGovern(db, opts) {
|
|
|
3019
3165
|
orphanPaths = pathResult.changes;
|
|
3020
3166
|
const emptyResult = agentId ? db.prepare("DELETE FROM memories WHERE agent_id = ? AND TRIM(content) = ''").run(agentId) : db.prepare("DELETE FROM memories WHERE TRIM(content) = ''").run();
|
|
3021
3167
|
emptyMemories = emptyResult.changes;
|
|
3168
|
+
const typeLimits = [
|
|
3169
|
+
{ type: "identity", limit: capacity.identity },
|
|
3170
|
+
{ type: "emotion", limit: capacity.emotion },
|
|
3171
|
+
{ type: "knowledge", limit: capacity.knowledge },
|
|
3172
|
+
{ type: "event", limit: capacity.event }
|
|
3173
|
+
];
|
|
3174
|
+
const allCandidates = rankEvictionCandidates(db, { agent_id: agentId });
|
|
3175
|
+
const evictedIds = /* @__PURE__ */ new Set();
|
|
3176
|
+
for (const { type, limit } of typeLimits) {
|
|
3177
|
+
if (limit === null) continue;
|
|
3178
|
+
const typeCount = db.prepare(
|
|
3179
|
+
agentId ? "SELECT COUNT(*) as c FROM memories WHERE agent_id = ? AND type = ?" : "SELECT COUNT(*) as c FROM memories WHERE type = ?"
|
|
3180
|
+
).get(...agentId ? [agentId, type] : [type]).c;
|
|
3181
|
+
const excess = Math.max(0, typeCount - limit);
|
|
3182
|
+
if (excess <= 0) continue;
|
|
3183
|
+
const typeCandidates = allCandidates.filter((c) => c.memory.type === type && !evictedIds.has(c.memory.id));
|
|
3184
|
+
const toEvict = typeCandidates.slice(0, excess);
|
|
3185
|
+
for (const candidate of toEvict) {
|
|
3186
|
+
const result = archiveMemory(db, candidate.memory.id, "eviction");
|
|
3187
|
+
evictedIds.add(candidate.memory.id);
|
|
3188
|
+
evicted += 1;
|
|
3189
|
+
if (result === "archived") archived += 1;
|
|
3190
|
+
evictedByType[type] = (evictedByType[type] ?? 0) + 1;
|
|
3191
|
+
}
|
|
3192
|
+
}
|
|
3022
3193
|
const total = db.prepare(agentId ? "SELECT COUNT(*) as c FROM memories WHERE agent_id = ?" : "SELECT COUNT(*) as c FROM memories").get(...agentId ? [agentId] : []).c;
|
|
3023
|
-
const
|
|
3024
|
-
if (
|
|
3025
|
-
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
|
|
3194
|
+
const globalExcess = Math.max(0, total - capacity.total);
|
|
3195
|
+
if (globalExcess > 0) {
|
|
3196
|
+
const globalCandidates = allCandidates.filter((c) => !evictedIds.has(c.memory.id));
|
|
3197
|
+
const toEvict = globalCandidates.slice(0, globalExcess);
|
|
3198
|
+
for (const candidate of toEvict) {
|
|
3199
|
+
const result = archiveMemory(db, candidate.memory.id, "eviction");
|
|
3200
|
+
evictedIds.add(candidate.memory.id);
|
|
3201
|
+
evicted += 1;
|
|
3202
|
+
if (result === "archived") archived += 1;
|
|
3203
|
+
const t = candidate.memory.type;
|
|
3204
|
+
evictedByType[t] = (evictedByType[t] ?? 0) + 1;
|
|
3205
|
+
}
|
|
3029
3206
|
}
|
|
3030
3207
|
});
|
|
3031
3208
|
transaction();
|
|
3032
|
-
return { orphanPaths, emptyMemories, evicted };
|
|
3209
|
+
return { orphanPaths, emptyMemories, evicted, archived, evictedByType };
|
|
3033
3210
|
}
|
|
3034
3211
|
|
|
3035
3212
|
// src/sleep/jobs.ts
|
|
@@ -3272,12 +3449,21 @@ function getMemoryStatus(db, input) {
|
|
|
3272
3449
|
const feedbackEvents = db.prepare(
|
|
3273
3450
|
"SELECT COUNT(*) as c FROM feedback_events WHERE agent_id = ?"
|
|
3274
3451
|
).get(agentId);
|
|
3452
|
+
const tiered = getTieredCapacity();
|
|
3453
|
+
const capacity = {
|
|
3454
|
+
identity: { count: stats.by_type.identity ?? 0, limit: tiered.identity },
|
|
3455
|
+
emotion: { count: stats.by_type.emotion ?? 0, limit: tiered.emotion },
|
|
3456
|
+
knowledge: { count: stats.by_type.knowledge ?? 0, limit: tiered.knowledge },
|
|
3457
|
+
event: { count: stats.by_type.event ?? 0, limit: tiered.event },
|
|
3458
|
+
total: { count: stats.total, limit: tiered.total }
|
|
3459
|
+
};
|
|
3275
3460
|
return {
|
|
3276
3461
|
...stats,
|
|
3277
3462
|
paths: totalPaths.c,
|
|
3278
3463
|
low_vitality: lowVitality.c,
|
|
3279
3464
|
feedback_events: feedbackEvents.c,
|
|
3280
|
-
agent_id: agentId
|
|
3465
|
+
agent_id: agentId,
|
|
3466
|
+
capacity
|
|
3281
3467
|
};
|
|
3282
3468
|
}
|
|
3283
3469
|
|