mcp-agents-memory 0.9.20 → 0.9.21
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/README.ko.md +1 -1
- package/README.md +1 -1
- package/build/index.js +124 -11
- package/package.json +1 -1
package/README.ko.md
CHANGED
|
@@ -106,7 +106,7 @@
|
|
|
106
106
|
mcp-agents-memory coldpath # MCP 서버 없이 Cold Path 워커만 도는 데몬 (systemd 권장)
|
|
107
107
|
```
|
|
108
108
|
데몬은 PostgreSQL advisory lock으로 **싱글톤** 보장 — 인스턴스가 몇 개든 락을 잡은 1개만 처리한다(중복 방지·자동 failover).
|
|
109
|
-
- **버즈 (선택)**: `mcp-agents-memory buzz-ingest`는 지정한 신원이 멤버인 버즈 방의 채팅을 공식 `buzz` CLI로 한 건씩 메모리에 옮긴다. 타이머로 돌리고, 설정은 CHANGELOG 0.9.20 참고.
|
|
109
|
+
- **버즈 (선택)**: `mcp-agents-memory buzz-ingest`는 지정한 신원이 멤버인 버즈 방의 채팅을 공식 `buzz` CLI로 한 건씩 메모리에 옮긴다. 인용된 글이 전부 행으로 들어오면 캡처된 버즈 턴의 이전 대화 인용도 걷어낸다(원문은 `raw_message`, 끄려면 `--no-reclean`). 타이머로 돌리고, 설정은 CHANGELOG 0.9.20–0.9.21 참고.
|
|
110
110
|
|
|
111
111
|
### Cold Path LLM 백엔드 (config로 교체)
|
|
112
112
|
|
package/README.md
CHANGED
|
@@ -107,7 +107,7 @@ When several machines share **one** database, the Cold Path (tagging / profiling
|
|
|
107
107
|
mcp-agents-memory coldpath # Cold Path worker only, no MCP server (systemd recommended)
|
|
108
108
|
```
|
|
109
109
|
The daemon is a **singleton** via a PostgreSQL advisory lock — no matter how many instances exist, only the one holding the lock processes (dedup + automatic failover).
|
|
110
|
-
- **Buzz (optional)**: `mcp-agents-memory buzz-ingest` copies chat messages from the Buzz rooms a given identity is a member of into memory, one row per message, through the official `buzz` CLI. Run it from a timer; see CHANGELOG 0.9.20 for the settings.
|
|
110
|
+
- **Buzz (optional)**: `mcp-agents-memory buzz-ingest` copies chat messages from the Buzz rooms a given identity is a member of into memory, one row per message, through the official `buzz` CLI. It also strips the quoted history from captured Buzz turns once every quoted message exists as a row (the original stays in `raw_message`; `--no-reclean` turns this off). Run it from a timer; see CHANGELOG 0.9.20–0.9.21 for the settings.
|
|
111
111
|
|
|
112
112
|
### Cold Path LLM backend
|
|
113
113
|
|
package/build/index.js
CHANGED
|
@@ -32762,17 +32762,19 @@ function locateBuzzContext(text) {
|
|
|
32762
32762
|
}
|
|
32763
32763
|
return CONTEXT_OPEN.test(rest.slice(pos, pos + 12)) ? { rest, pos } : null;
|
|
32764
32764
|
}
|
|
32765
|
-
function cleanBuzzEnvelope(message) {
|
|
32765
|
+
function cleanBuzzEnvelope(message, opts = {}) {
|
|
32766
32766
|
try {
|
|
32767
32767
|
const text = message.trimStart();
|
|
32768
|
-
|
|
32768
|
+
const hasBase = text.startsWith("<base>");
|
|
32769
|
+
if (!hasBase && !opts.baseless) return null;
|
|
32769
32770
|
const loc = locateBuzzContext(text);
|
|
32770
32771
|
if (!loc) return null;
|
|
32771
32772
|
const { rest, pos } = loc;
|
|
32772
32773
|
const bodyStart = rest.indexOf("\n", pos) + 1;
|
|
32773
32774
|
const contextClose = rest.indexOf(CONTEXT_CLOSE, bodyStart - 1);
|
|
32774
32775
|
if (contextClose < 0) return null;
|
|
32775
|
-
const contextBody = rest.slice(bodyStart, contextClose).replace(/\r
|
|
32776
|
+
const contextBody = rest.slice(bodyStart, contextClose).replace(/\r+$/, "");
|
|
32777
|
+
if (!hasBase && !(BUZZ_SCOPE.test(contextBody) && BUZZ_CHANNEL.test(contextBody))) return null;
|
|
32776
32778
|
const tail = rest.slice(contextClose + CONTEXT_CLOSE.length);
|
|
32777
32779
|
let turnStart = 0;
|
|
32778
32780
|
const history = HISTORY_OPEN.exec(tail);
|
|
@@ -32798,6 +32800,37 @@ ${turnText}`;
|
|
|
32798
32800
|
return null;
|
|
32799
32801
|
}
|
|
32800
32802
|
}
|
|
32803
|
+
function buzzQuotedMessages(message) {
|
|
32804
|
+
try {
|
|
32805
|
+
const loc = locateBuzzContext(message.trimStart());
|
|
32806
|
+
if (!loc) return null;
|
|
32807
|
+
const { rest, pos } = loc;
|
|
32808
|
+
const contextClose = rest.indexOf(CONTEXT_CLOSE, pos);
|
|
32809
|
+
if (contextClose < 0) return null;
|
|
32810
|
+
const tail = rest.slice(contextClose + CONTEXT_CLOSE.length);
|
|
32811
|
+
const history = HISTORY_OPEN.exec(tail);
|
|
32812
|
+
if (!history) return [];
|
|
32813
|
+
const closeTag = `</${history[1]}>`;
|
|
32814
|
+
const close = tail.indexOf(closeTag, history[0].length);
|
|
32815
|
+
if (close < 0) return null;
|
|
32816
|
+
const body = tail.slice(history[0].length, close).replace(/^\r?\n/, "").replace(/\r?\n$/, "");
|
|
32817
|
+
if (!body.trim()) return [];
|
|
32818
|
+
const out = [];
|
|
32819
|
+
for (const line of body.split(/\r?\n/)) {
|
|
32820
|
+
const e = QUOTE_ENTRY.exec(line);
|
|
32821
|
+
if (e && Number(e[1]) === out.length + 1) {
|
|
32822
|
+
out.push({ pubkey: e[2], time: e[3], content: line.slice(e[0].length) });
|
|
32823
|
+
} else if (out.length > 0) {
|
|
32824
|
+
out[out.length - 1].content += "\n" + line;
|
|
32825
|
+
} else {
|
|
32826
|
+
return null;
|
|
32827
|
+
}
|
|
32828
|
+
}
|
|
32829
|
+
return out;
|
|
32830
|
+
} catch {
|
|
32831
|
+
return null;
|
|
32832
|
+
}
|
|
32833
|
+
}
|
|
32801
32834
|
function buzzEmbeddingText(message) {
|
|
32802
32835
|
try {
|
|
32803
32836
|
if (!message.startsWith("<context>\n")) return message;
|
|
@@ -32843,7 +32876,7 @@ function venueFromBuzzMessage(message) {
|
|
|
32843
32876
|
return null;
|
|
32844
32877
|
}
|
|
32845
32878
|
}
|
|
32846
|
-
var PREAMBLE_BLOCKS, HISTORY_OPEN, TURN_OPEN, TURN_IN_DROPPED, CONTEXT_OPEN, CONTEXT_CLOSE, EVENT_HEADER;
|
|
32879
|
+
var PREAMBLE_BLOCKS, HISTORY_OPEN, TURN_OPEN, TURN_IN_DROPPED, CONTEXT_OPEN, BUZZ_SCOPE, BUZZ_CHANNEL, CONTEXT_CLOSE, QUOTE_ENTRY, EVENT_HEADER;
|
|
32847
32880
|
var init_buzz_envelope = __esm({
|
|
32848
32881
|
"src/auto_save/buzz_envelope.ts"() {
|
|
32849
32882
|
"use strict";
|
|
@@ -32852,7 +32885,10 @@ var init_buzz_envelope = __esm({
|
|
|
32852
32885
|
TURN_OPEN = /^\s*<(?:what-you-were-working-on|buzz-events?|new-message-arrived-while-you-were-working)[\s>]/;
|
|
32853
32886
|
TURN_IN_DROPPED = /(?:^|>)[ \t]*<(?:what-you-were-working-on|buzz-events?|new-message-arrived-while-you-were-working)[\s>]/m;
|
|
32854
32887
|
CONTEXT_OPEN = /^<context>\r?\n/;
|
|
32888
|
+
BUZZ_SCOPE = /^Scope: (?:thread|channel|dm)\r?\n/;
|
|
32889
|
+
BUZZ_CHANNEL = /(?:^|\n)Channel: .+ \(#[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\)\r?(?=\n|$)/;
|
|
32855
32890
|
CONTEXT_CLOSE = "\n</context>";
|
|
32891
|
+
QUOTE_ENTRY = /^\[(\d+)\] .*? \(([0-9a-f]{64})\) \((\d{4}-\d\d-\d\dT\d\d:\d\d:\d\d(?:\.\d+)?(?:Z|[+-]\d\d:\d\d))\): ?/;
|
|
32856
32892
|
EVENT_HEADER = /^(?:Event ID: [0-9a-f]{64}|Kind: \d+|Time: \d{4}-\d{2}-\d{2}T\S+|--- Event \d+ \(.*\) ---|Note: A new message arrived while you were working\..*)\s*$/;
|
|
32857
32893
|
}
|
|
32858
32894
|
});
|
|
@@ -33328,6 +33364,69 @@ async function hideDeleted(targets, dryRun) {
|
|
|
33328
33364
|
const r = await db.query(sql, [uuids]);
|
|
33329
33365
|
return Number(r.rows[0]?.n ?? 0);
|
|
33330
33366
|
}
|
|
33367
|
+
async function recleanEnvelopes(userId, budget, dryRun) {
|
|
33368
|
+
const rows = (await db.query(
|
|
33369
|
+
`SELECT id, message, venue FROM memory
|
|
33370
|
+
WHERE user_id = $1 AND role = 'user' AND raw_message IS NULL AND message LIKE '<context>%'
|
|
33371
|
+
AND agent_platform <> 'buzz' AND is_active AND NOT is_pinned
|
|
33372
|
+
ORDER BY created_at, id`,
|
|
33373
|
+
[userId]
|
|
33374
|
+
)).rows;
|
|
33375
|
+
const cands = [];
|
|
33376
|
+
let stuck = 0;
|
|
33377
|
+
for (const r of rows) {
|
|
33378
|
+
const cleaned = cleanBuzzEnvelope(r.message, { baseless: true });
|
|
33379
|
+
const quotes = buzzQuotedMessages(r.message);
|
|
33380
|
+
if (cleaned === null && quotes !== null && quotes.length === 0) continue;
|
|
33381
|
+
const venue = [r.venue, venueFromBuzzMessage(r.message)].find((v) => typeof v === "string" && v.startsWith("buzz")) ?? null;
|
|
33382
|
+
const q = (quotes ?? []).map((x) => ({ ms: Date.parse(x.time), text: norm(x.content) }));
|
|
33383
|
+
if (cleaned === null || quotes === null || !venue || q.some((x) => !Number.isFinite(x.ms))) {
|
|
33384
|
+
if (venue || looksLikeBuzzTurn(r.message)) stuck++;
|
|
33385
|
+
continue;
|
|
33386
|
+
}
|
|
33387
|
+
cands.push({ id: Number(r.id), message: r.message, cleaned, venue, quotes: q });
|
|
33388
|
+
}
|
|
33389
|
+
const times = [...new Set(cands.flatMap((c) => c.quotes.map((x) => x.ms)))];
|
|
33390
|
+
const have = /* @__PURE__ */ new Set();
|
|
33391
|
+
if (times.length) {
|
|
33392
|
+
const r = await db.query(
|
|
33393
|
+
`SELECT venue, created_at, raw_message FROM memory
|
|
33394
|
+
WHERE user_id = $1 AND agent_platform = 'buzz' AND external_uuid LIKE 'buzz:%'
|
|
33395
|
+
AND raw_message IS NOT NULL AND created_at = ANY($2::timestamptz[])`,
|
|
33396
|
+
[userId, times.map((ms) => new Date(ms).toISOString())]
|
|
33397
|
+
);
|
|
33398
|
+
for (const row of r.rows) have.add(`${row.venue}\0${new Date(row.created_at).getTime()}\0${norm(row.raw_message)}`);
|
|
33399
|
+
}
|
|
33400
|
+
const ready = cands.filter((c) => c.quotes.every((x) => have.has(`${c.venue}\0${x.ms}\0${x.text}`)));
|
|
33401
|
+
stuck += cands.length - ready.length;
|
|
33402
|
+
let n = 0;
|
|
33403
|
+
for (const c of ready.slice(0, Math.max(0, budget))) {
|
|
33404
|
+
if (dryRun) {
|
|
33405
|
+
n++;
|
|
33406
|
+
continue;
|
|
33407
|
+
}
|
|
33408
|
+
const client2 = await db.getClient();
|
|
33409
|
+
try {
|
|
33410
|
+
await client2.query("BEGIN");
|
|
33411
|
+
await client2.query("SET LOCAL lock_timeout = '2s'");
|
|
33412
|
+
const r = await client2.query(
|
|
33413
|
+
`UPDATE memory
|
|
33414
|
+
SET message = $2, raw_message = $3, tag_processed = FALSE, embedding = NULL, cold_error = NULL
|
|
33415
|
+
WHERE id = $1 AND raw_message IS NULL AND message = $3`,
|
|
33416
|
+
[c.id, c.cleaned, c.message]
|
|
33417
|
+
);
|
|
33418
|
+
await client2.query("COMMIT");
|
|
33419
|
+
n += r.rowCount ?? 0;
|
|
33420
|
+
} catch (err3) {
|
|
33421
|
+
await client2.query("ROLLBACK").catch(() => {
|
|
33422
|
+
});
|
|
33423
|
+
if (err3.code !== "55P03") throw err3;
|
|
33424
|
+
} finally {
|
|
33425
|
+
client2.release();
|
|
33426
|
+
}
|
|
33427
|
+
}
|
|
33428
|
+
return { recleaned: n, ready: ready.length - n, stuck };
|
|
33429
|
+
}
|
|
33331
33430
|
async function runBuzzIngest(opts = {}) {
|
|
33332
33431
|
const dryRun = opts.dryRun ?? false;
|
|
33333
33432
|
const max = opts.max ?? 20;
|
|
@@ -33362,10 +33461,13 @@ async function runBuzzIngest(opts = {}) {
|
|
|
33362
33461
|
inserted: 0,
|
|
33363
33462
|
edited: 0,
|
|
33364
33463
|
hidden: 0,
|
|
33464
|
+
recleaned: 0,
|
|
33465
|
+
envelopesReady: 0,
|
|
33466
|
+
envelopesStuck: 0,
|
|
33365
33467
|
byVenue: {},
|
|
33366
33468
|
...dryRun ? { unsupported } : {}
|
|
33367
33469
|
};
|
|
33368
|
-
const userId = dryRun ? 0 : await getDefaultUserId();
|
|
33470
|
+
const userId = dryRun ? Number((await db.query(`SELECT user_id FROM users WHERE user_name = $1`, [process.env.USER_NAME ?? "hoon"])).rows[0]?.user_id ?? 0) : await getDefaultUserId();
|
|
33369
33471
|
for (const { e, venue } of batch) {
|
|
33370
33472
|
report.byVenue[venue] = (report.byVenue[venue] ?? 0) + 1;
|
|
33371
33473
|
if (dryRun) {
|
|
@@ -33393,6 +33495,12 @@ async function runBuzzIngest(opts = {}) {
|
|
|
33393
33495
|
}
|
|
33394
33496
|
report.edited = await applyEdits(latestEdits(edits), dryRun);
|
|
33395
33497
|
report.hidden = await hideDeleted([...deleteTargets], dryRun);
|
|
33498
|
+
if (!opts.noReclean && userId > 0) {
|
|
33499
|
+
const env = await recleanEnvelopes(userId, max - batch.length, dryRun);
|
|
33500
|
+
report.recleaned = env.recleaned;
|
|
33501
|
+
report.envelopesReady = env.ready;
|
|
33502
|
+
report.envelopesStuck = env.stuck;
|
|
33503
|
+
}
|
|
33396
33504
|
return report;
|
|
33397
33505
|
}
|
|
33398
33506
|
function parseIngestArgs(argv) {
|
|
@@ -33400,6 +33508,7 @@ function parseIngestArgs(argv) {
|
|
|
33400
33508
|
for (let i = 0; i < argv.length; i++) {
|
|
33401
33509
|
const a = argv[i];
|
|
33402
33510
|
if (a === "--dry-run") opts.dryRun = true;
|
|
33511
|
+
else if (a === "--no-reclean") opts.noReclean = true;
|
|
33403
33512
|
else if (a === "--max") {
|
|
33404
33513
|
const v = argv[++i];
|
|
33405
33514
|
if (v === void 0 || !/^\d+$/.test(v)) throw new Error("--max: 0 \uC774\uC0C1\uC758 \uC815\uC218");
|
|
@@ -33408,12 +33517,13 @@ function parseIngestArgs(argv) {
|
|
|
33408
33517
|
}
|
|
33409
33518
|
return opts;
|
|
33410
33519
|
}
|
|
33411
|
-
var KIND_CHAT, KIND_EDIT, DELETE_KINDS, UNSUPPORTED_KINDS, PAGE, MAX_PAGES_PER_CHANNEL, LOOKBACK_SEC, HEX64, UUID, CHILD_ENV, BuzzShapeError;
|
|
33520
|
+
var KIND_CHAT, KIND_EDIT, DELETE_KINDS, UNSUPPORTED_KINDS, PAGE, MAX_PAGES_PER_CHANNEL, LOOKBACK_SEC, HEX64, UUID, CHILD_ENV, BuzzShapeError, norm;
|
|
33412
33521
|
var init_buzz_ingest = __esm({
|
|
33413
33522
|
"src/auto_save/buzz_ingest.ts"() {
|
|
33414
33523
|
"use strict";
|
|
33415
33524
|
init_db();
|
|
33416
33525
|
init_hot_path();
|
|
33526
|
+
init_buzz_envelope();
|
|
33417
33527
|
init_users();
|
|
33418
33528
|
KIND_CHAT = 9;
|
|
33419
33529
|
KIND_EDIT = 40003;
|
|
@@ -33427,6 +33537,7 @@ var init_buzz_ingest = __esm({
|
|
|
33427
33537
|
CHILD_ENV = /^(PATH|PATHEXT|HOME|USER|LANG|LC_[A-Z]+|TZ|TMPDIR|TEMP|TMP|SSL_CERT_(FILE|DIR)|NODE_EXTRA_CA_CERTS|(HTTPS?|ALL|NO)_PROXY|SYSTEMROOT|WINDIR|COMSPEC|APPDATA|LOCALAPPDATA|USERPROFILE|BUZZ_[A-Z0-9_]+)$/i;
|
|
33428
33538
|
BuzzShapeError = class extends Error {
|
|
33429
33539
|
};
|
|
33540
|
+
norm = (s) => s.replace(/\s+/g, " ").trim();
|
|
33430
33541
|
}
|
|
33431
33542
|
});
|
|
33432
33543
|
|
|
@@ -40540,13 +40651,13 @@ function _array(Class2, element, params) {
|
|
|
40540
40651
|
});
|
|
40541
40652
|
}
|
|
40542
40653
|
function _custom(Class2, fn, _params) {
|
|
40543
|
-
const
|
|
40544
|
-
|
|
40654
|
+
const norm2 = normalizeParams(_params);
|
|
40655
|
+
norm2.abort ?? (norm2.abort = true);
|
|
40545
40656
|
const schema = new Class2({
|
|
40546
40657
|
type: "custom",
|
|
40547
40658
|
check: "custom",
|
|
40548
40659
|
fn,
|
|
40549
|
-
...
|
|
40660
|
+
...norm2
|
|
40550
40661
|
});
|
|
40551
40662
|
return schema;
|
|
40552
40663
|
}
|
|
@@ -62135,10 +62246,12 @@ Usage:
|
|
|
62135
62246
|
mcp-agents-memory coldpath Run ONLY the cold-path worker as a standalone always-on daemon (no MCP server). For the processing/GPU machine via systemd.
|
|
62136
62247
|
mcp-agents-memory setup Interactive setup \u2014 write config to ~/.config/mcp-agents-memory/.env and run migrations.
|
|
62137
62248
|
mcp-agents-memory migrate Apply any pending DB migrations against the configured database.
|
|
62138
|
-
mcp-agents-memory buzz-ingest [--dry-run] [--max N]
|
|
62249
|
+
mcp-agents-memory buzz-ingest [--dry-run] [--max N] [--no-reclean]
|
|
62139
62250
|
(Buzz only) Copy chat messages from the Buzz rooms the CLI identity is a member of
|
|
62140
62251
|
into memory, one row per message, via the official \`buzz\` CLI. Oldest first, at most
|
|
62141
|
-
N per run (default 20).
|
|
62252
|
+
N per run (default 20). With the remaining budget it also strips the quoted history
|
|
62253
|
+
from captured Buzz turns once every quoted message exists as a row (original kept in
|
|
62254
|
+
raw_message; --no-reclean skips this). Needs BUZZ_RELAY_URL, BUZZ_PRIVATE_KEY and BUZZ_INGEST_OWNER
|
|
62142
62255
|
(the memory owner's pubkeys, comma-separated) in the environment of this command only \u2014
|
|
62143
62256
|
not in the shared .env; optional BUZZ_CLI.
|
|
62144
62257
|
mcp-agents-memory help Show this message.
|