mcp-agents-memory 0.9.19 → 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 -0
- package/README.md +1 -0
- package/build/index.js +829 -322
- package/package.json +1 -1
package/README.ko.md
CHANGED
|
@@ -106,6 +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로 한 건씩 메모리에 옮긴다. 인용된 글이 전부 행으로 들어오면 캡처된 버즈 턴의 이전 대화 인용도 걷어낸다(원문은 `raw_message`, 끄려면 `--no-reclean`). 타이머로 돌리고, 설정은 CHANGELOG 0.9.20–0.9.21 참고.
|
|
109
110
|
|
|
110
111
|
### Cold Path LLM 백엔드 (config로 교체)
|
|
111
112
|
|
package/README.md
CHANGED
|
@@ -107,6 +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. 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.
|
|
110
111
|
|
|
111
112
|
### Cold Path LLM backend
|
|
112
113
|
|
package/build/index.js
CHANGED
|
@@ -12924,7 +12924,7 @@ var require_agent = __commonJS({
|
|
|
12924
12924
|
var { Duplex } = __require("stream");
|
|
12925
12925
|
var { resolve } = __require("path");
|
|
12926
12926
|
var { readFile } = __require("fs");
|
|
12927
|
-
var { execFile, spawn: spawn2 } = __require("child_process");
|
|
12927
|
+
var { execFile: execFile2, spawn: spawn2 } = __require("child_process");
|
|
12928
12928
|
var { isParsedKey, parseKey } = require_keyParser();
|
|
12929
12929
|
var {
|
|
12930
12930
|
makeBufferParser,
|
|
@@ -13145,7 +13145,7 @@ var require_agent = __commonJS({
|
|
|
13145
13145
|
if (err3) {
|
|
13146
13146
|
if (triedCygpath)
|
|
13147
13147
|
return cb(new Error("Invalid cygwin unix socket path"));
|
|
13148
|
-
|
|
13148
|
+
execFile2("cygpath", ["-w", socketPath], (err4, stdout, stderr) => {
|
|
13149
13149
|
if (err4 || stdout.length === 0)
|
|
13150
13150
|
return cb(new Error("Invalid cygwin unix socket path"));
|
|
13151
13151
|
triedCygpath = true;
|
|
@@ -32739,6 +32739,389 @@ var require_dist2 = __commonJS({
|
|
|
32739
32739
|
}
|
|
32740
32740
|
});
|
|
32741
32741
|
|
|
32742
|
+
// src/auto_save/buzz_envelope.ts
|
|
32743
|
+
function isNostrMetaLine(line) {
|
|
32744
|
+
return /^Tags: \[\[".*\]\]\s*$/.test(line) && /[0-9a-f]{64}/.test(line) || /^Parsed: (?:root=[0-9a-f]{64}|mentions=\[.*\([0-9a-f]{64}\))/.test(line);
|
|
32745
|
+
}
|
|
32746
|
+
function skipWhitespace(s, i) {
|
|
32747
|
+
while (i < s.length && /\s/.test(s[i])) i++;
|
|
32748
|
+
return i;
|
|
32749
|
+
}
|
|
32750
|
+
function locateBuzzContext(text) {
|
|
32751
|
+
if (!text.startsWith("<base>")) return CONTEXT_OPEN.test(text.slice(0, 12)) ? { rest: text, pos: 0 } : null;
|
|
32752
|
+
const baseEnd = text.indexOf("</base>");
|
|
32753
|
+
if (baseEnd < 0) return null;
|
|
32754
|
+
const rest = text.slice(baseEnd + "</base>".length);
|
|
32755
|
+
let pos = skipWhitespace(rest, 0);
|
|
32756
|
+
for (; ; ) {
|
|
32757
|
+
const name = PREAMBLE_BLOCKS.find((b) => rest.startsWith(`<${b}>`, pos));
|
|
32758
|
+
if (!name) break;
|
|
32759
|
+
const close = rest.indexOf(`</${name}>`, pos);
|
|
32760
|
+
if (close < 0) return null;
|
|
32761
|
+
pos = skipWhitespace(rest, close + name.length + 3);
|
|
32762
|
+
}
|
|
32763
|
+
return CONTEXT_OPEN.test(rest.slice(pos, pos + 12)) ? { rest, pos } : null;
|
|
32764
|
+
}
|
|
32765
|
+
function cleanBuzzEnvelope(message, opts = {}) {
|
|
32766
|
+
try {
|
|
32767
|
+
const text = message.trimStart();
|
|
32768
|
+
const hasBase = text.startsWith("<base>");
|
|
32769
|
+
if (!hasBase && !opts.baseless) return null;
|
|
32770
|
+
const loc = locateBuzzContext(text);
|
|
32771
|
+
if (!loc) return null;
|
|
32772
|
+
const { rest, pos } = loc;
|
|
32773
|
+
const bodyStart = rest.indexOf("\n", pos) + 1;
|
|
32774
|
+
const contextClose = rest.indexOf(CONTEXT_CLOSE, bodyStart - 1);
|
|
32775
|
+
if (contextClose < 0) return null;
|
|
32776
|
+
const contextBody = rest.slice(bodyStart, contextClose).replace(/\r+$/, "");
|
|
32777
|
+
if (!hasBase && !(BUZZ_SCOPE.test(contextBody) && BUZZ_CHANNEL.test(contextBody))) return null;
|
|
32778
|
+
const tail = rest.slice(contextClose + CONTEXT_CLOSE.length);
|
|
32779
|
+
let turnStart = 0;
|
|
32780
|
+
const history = HISTORY_OPEN.exec(tail);
|
|
32781
|
+
if (history) {
|
|
32782
|
+
const closeTag = `</${history[1]}>`;
|
|
32783
|
+
const close = tail.indexOf(closeTag, history[0].length);
|
|
32784
|
+
if (close < 0) return null;
|
|
32785
|
+
turnStart = close + closeTag.length;
|
|
32786
|
+
}
|
|
32787
|
+
if (TURN_IN_DROPPED.test(rest.slice(0, pos)) || TURN_IN_DROPPED.test(tail.slice(0, turnStart))) return null;
|
|
32788
|
+
const turn = tail.slice(turnStart);
|
|
32789
|
+
if (!TURN_OPEN.test(turn)) return null;
|
|
32790
|
+
const turnText = turn.split("\n").filter((line) => !isNostrMetaLine(line)).join("\n").trim();
|
|
32791
|
+
if (!turnText) return null;
|
|
32792
|
+
const cleaned = `<context>
|
|
32793
|
+
${contextBody}
|
|
32794
|
+
</context>
|
|
32795
|
+
|
|
32796
|
+
${turnText}`;
|
|
32797
|
+
if (cleaned.length >= message.length) return null;
|
|
32798
|
+
return cleaned;
|
|
32799
|
+
} catch {
|
|
32800
|
+
return null;
|
|
32801
|
+
}
|
|
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
|
+
}
|
|
32834
|
+
function buzzEmbeddingText(message) {
|
|
32835
|
+
try {
|
|
32836
|
+
if (!message.startsWith("<context>\n")) return message;
|
|
32837
|
+
const close = message.indexOf(CONTEXT_CLOSE);
|
|
32838
|
+
if (close < 0) return message;
|
|
32839
|
+
const turn = message.slice(close + CONTEXT_CLOSE.length);
|
|
32840
|
+
if (!TURN_OPEN.test(turn)) return message;
|
|
32841
|
+
const text = turn.split("\n").filter((line) => !EVENT_HEADER.test(line) && !isNostrMetaLine(line)).map(
|
|
32842
|
+
(line) => (
|
|
32843
|
+
// `$` 없이: 끝까지 되짚는 역추적을 막는다 (긴 줄에서 제곱 시간)
|
|
32844
|
+
line.replace(/^From: (.*?) \(npub:.*/, "From: $1").replace(/^Channel: (\S+) \(#[0-9a-f-]+\)/, "Channel: $1")
|
|
32845
|
+
)
|
|
32846
|
+
).join("\n").trim();
|
|
32847
|
+
return text || message;
|
|
32848
|
+
} catch {
|
|
32849
|
+
return message;
|
|
32850
|
+
}
|
|
32851
|
+
}
|
|
32852
|
+
function venueFromBuzzContext(message) {
|
|
32853
|
+
if (!CONTEXT_OPEN.test(message.slice(0, 12))) return null;
|
|
32854
|
+
const end = message.indexOf(CONTEXT_CLOSE);
|
|
32855
|
+
if (end < 0) return null;
|
|
32856
|
+
const lines = message.slice(0, end).split("\n").map((l) => l.replace(/\r$/, ""));
|
|
32857
|
+
const scope = lines.find((l) => l.startsWith("Scope: "))?.slice("Scope: ".length).trim();
|
|
32858
|
+
if (scope !== "thread" && scope !== "channel" && scope !== "dm") return null;
|
|
32859
|
+
if (scope === "dm") return "buzz:dm";
|
|
32860
|
+
const channel = lines.find((l) => l.startsWith("Channel: "))?.trimEnd();
|
|
32861
|
+
const m = channel ? /^Channel: (.+?) \(#[0-9a-f-]{36}\)$/.exec(channel) : null;
|
|
32862
|
+
return m ? `buzz:${m[1]}` : "buzz";
|
|
32863
|
+
}
|
|
32864
|
+
function looksLikeBuzzTurn(message) {
|
|
32865
|
+
const text = message.trimStart();
|
|
32866
|
+
if (text.startsWith("<base>")) return true;
|
|
32867
|
+
if (!CONTEXT_OPEN.test(text.slice(0, 12))) return false;
|
|
32868
|
+
const close = text.indexOf(CONTEXT_CLOSE);
|
|
32869
|
+
return close >= 0 && TURN_OPEN.test(text.slice(close + CONTEXT_CLOSE.length));
|
|
32870
|
+
}
|
|
32871
|
+
function venueFromBuzzMessage(message) {
|
|
32872
|
+
try {
|
|
32873
|
+
const loc = locateBuzzContext(message.trimStart());
|
|
32874
|
+
return loc ? venueFromBuzzContext(loc.rest.slice(loc.pos)) : null;
|
|
32875
|
+
} catch {
|
|
32876
|
+
return null;
|
|
32877
|
+
}
|
|
32878
|
+
}
|
|
32879
|
+
var PREAMBLE_BLOCKS, HISTORY_OPEN, TURN_OPEN, TURN_IN_DROPPED, CONTEXT_OPEN, BUZZ_SCOPE, BUZZ_CHANNEL, CONTEXT_CLOSE, QUOTE_ENTRY, EVENT_HEADER;
|
|
32880
|
+
var init_buzz_envelope = __esm({
|
|
32881
|
+
"src/auto_save/buzz_envelope.ts"() {
|
|
32882
|
+
"use strict";
|
|
32883
|
+
PREAMBLE_BLOCKS = ["agent-instructions", "core-memory"];
|
|
32884
|
+
HISTORY_OPEN = /^\s*<((?:thread|conversation)-context)(?:\s[^>]*)?>/;
|
|
32885
|
+
TURN_OPEN = /^\s*<(?:what-you-were-working-on|buzz-events?|new-message-arrived-while-you-were-working)[\s>]/;
|
|
32886
|
+
TURN_IN_DROPPED = /(?:^|>)[ \t]*<(?:what-you-were-working-on|buzz-events?|new-message-arrived-while-you-were-working)[\s>]/m;
|
|
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|$)/;
|
|
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))\): ?/;
|
|
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*$/;
|
|
32893
|
+
}
|
|
32894
|
+
});
|
|
32895
|
+
|
|
32896
|
+
// src/auto_save/venue.ts
|
|
32897
|
+
import fs2 from "fs";
|
|
32898
|
+
function classifySessionVenue(firstUserText, entrypoint) {
|
|
32899
|
+
if (buzzTurnVenue(firstUserText)) return "buzz";
|
|
32900
|
+
if (entrypoint && INTERACTIVE_ENTRYPOINTS.has(entrypoint)) return "terminal";
|
|
32901
|
+
if (/\bslack\b/i.test(firstUserText.slice(0, 500))) return "slack";
|
|
32902
|
+
if (entrypoint) return "auto";
|
|
32903
|
+
return null;
|
|
32904
|
+
}
|
|
32905
|
+
function buzzTurnVenue(message) {
|
|
32906
|
+
return venueFromBuzzMessage(message) ?? (looksLikeBuzzTurn(message) ? "buzz" : null);
|
|
32907
|
+
}
|
|
32908
|
+
function lastBuzzVenueInFile(jsonlPath, uptoBytes, userText) {
|
|
32909
|
+
const fd = fs2.openSync(jsonlPath, "r");
|
|
32910
|
+
try {
|
|
32911
|
+
let end = uptoBytes;
|
|
32912
|
+
let carry = Buffer.alloc(0);
|
|
32913
|
+
while (end > 0 && uptoBytes - end < TAIL_CAP) {
|
|
32914
|
+
const start = Math.max(0, end - TAIL_CHUNK);
|
|
32915
|
+
const chunk = Buffer.allocUnsafe(end - start);
|
|
32916
|
+
fs2.readSync(fd, chunk, 0, chunk.length, start);
|
|
32917
|
+
const buf = carry.length ? Buffer.concat([chunk, carry]) : chunk;
|
|
32918
|
+
const firstNl = start > 0 ? buf.indexOf(NEWLINE) : -1;
|
|
32919
|
+
carry = firstNl >= 0 ? buf.subarray(0, firstNl) : start > 0 ? buf : Buffer.alloc(0);
|
|
32920
|
+
const whole = firstNl >= 0 ? buf.subarray(firstNl + 1) : start > 0 ? Buffer.alloc(0) : buf;
|
|
32921
|
+
const lines = whole.toString("utf-8").split("\n");
|
|
32922
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
32923
|
+
const text = lines[i] ? userText(lines[i]) : null;
|
|
32924
|
+
const venue = text ? buzzTurnVenue(text) : null;
|
|
32925
|
+
if (venue) return venue;
|
|
32926
|
+
}
|
|
32927
|
+
end = start;
|
|
32928
|
+
}
|
|
32929
|
+
return null;
|
|
32930
|
+
} finally {
|
|
32931
|
+
fs2.closeSync(fd);
|
|
32932
|
+
}
|
|
32933
|
+
}
|
|
32934
|
+
var INTERACTIVE_ENTRYPOINTS, TAIL_CHUNK, TAIL_CAP, NEWLINE;
|
|
32935
|
+
var init_venue = __esm({
|
|
32936
|
+
"src/auto_save/venue.ts"() {
|
|
32937
|
+
"use strict";
|
|
32938
|
+
init_buzz_envelope();
|
|
32939
|
+
INTERACTIVE_ENTRYPOINTS = /* @__PURE__ */ new Set(["cli", "claude-vscode"]);
|
|
32940
|
+
TAIL_CHUNK = 4 * 1024 * 1024;
|
|
32941
|
+
TAIL_CAP = 64 * 1024 * 1024;
|
|
32942
|
+
NEWLINE = 10;
|
|
32943
|
+
}
|
|
32944
|
+
});
|
|
32945
|
+
|
|
32946
|
+
// src/hot_path.ts
|
|
32947
|
+
function missingOptionalColumn(err3, sql, cols) {
|
|
32948
|
+
const e = err3;
|
|
32949
|
+
if (e.code !== "42703") return void 0;
|
|
32950
|
+
const at = Number(e.position);
|
|
32951
|
+
const named = at > 0 ? /^[a-z_]+/.exec(sql.slice(at - 1))?.[0] : void 0;
|
|
32952
|
+
return cols.find(([c]) => named ? c === named : !!e.message?.includes(c))?.[0];
|
|
32953
|
+
}
|
|
32954
|
+
async function insertRawMemory(params) {
|
|
32955
|
+
const {
|
|
32956
|
+
user_id,
|
|
32957
|
+
agent_platform,
|
|
32958
|
+
agent_model,
|
|
32959
|
+
subagent = false,
|
|
32960
|
+
subagent_model = null,
|
|
32961
|
+
subagent_role = null,
|
|
32962
|
+
role,
|
|
32963
|
+
message,
|
|
32964
|
+
is_pinned = false,
|
|
32965
|
+
p_tag_id = null,
|
|
32966
|
+
d_tag = [],
|
|
32967
|
+
embedding = null,
|
|
32968
|
+
external_uuid = null,
|
|
32969
|
+
device_name = null
|
|
32970
|
+
} = params;
|
|
32971
|
+
const embeddingSql = embedding && embedding.length > 0 ? `[${embedding.join(",")}]` : null;
|
|
32972
|
+
const tagProcessed = p_tag_id !== null;
|
|
32973
|
+
const cleaned = role === "user" && !is_pinned && p_tag_id === null && embeddingSql === null ? cleanBuzzEnvelope(message) : null;
|
|
32974
|
+
const venue = params.venue ?? (role === "user" ? buzzTurnVenue(cleaned ?? message) : null);
|
|
32975
|
+
const raw = cleaned !== null ? message : params.raw_message ?? null;
|
|
32976
|
+
const optional2 = [];
|
|
32977
|
+
if (raw !== null) optional2.push(["raw_message", raw]);
|
|
32978
|
+
if (venue) optional2.push(["venue", venue]);
|
|
32979
|
+
const buildInsert = (cols2) => {
|
|
32980
|
+
const text = cleaned !== null && cols2.some(([c]) => c === "raw_message") ? cleaned : message;
|
|
32981
|
+
const extra = [
|
|
32982
|
+
...cols2,
|
|
32983
|
+
...params.created_at ? [["created_at", params.created_at]] : []
|
|
32984
|
+
];
|
|
32985
|
+
const sql = `INSERT INTO memory (
|
|
32986
|
+
user_id, agent_platform, agent_model,
|
|
32987
|
+
subagent, subagent_model, subagent_role,
|
|
32988
|
+
role, message,
|
|
32989
|
+
p_tag_id, d_tag, embedding,
|
|
32990
|
+
is_pinned, tag_processed, external_uuid,
|
|
32991
|
+
device_name${extra.map(([c]) => `, ${c}`).join("")}
|
|
32992
|
+
) VALUES (
|
|
32993
|
+
$1, $2, $3,
|
|
32994
|
+
$4, $5, $6,
|
|
32995
|
+
$7, $8,
|
|
32996
|
+
$9, $10::text[], $11::halfvec,
|
|
32997
|
+
$12, $13, $14,
|
|
32998
|
+
$15${extra.map((_, i) => `, $${16 + i}`).join("")}
|
|
32999
|
+
)
|
|
33000
|
+
ON CONFLICT (external_uuid) WHERE external_uuid IS NOT NULL
|
|
33001
|
+
DO NOTHING
|
|
33002
|
+
RETURNING id, created_at`;
|
|
33003
|
+
const values = [
|
|
33004
|
+
user_id,
|
|
33005
|
+
agent_platform,
|
|
33006
|
+
agent_model,
|
|
33007
|
+
subagent,
|
|
33008
|
+
subagent_model,
|
|
33009
|
+
subagent_role,
|
|
33010
|
+
role,
|
|
33011
|
+
text,
|
|
33012
|
+
p_tag_id,
|
|
33013
|
+
d_tag,
|
|
33014
|
+
embeddingSql,
|
|
33015
|
+
is_pinned,
|
|
33016
|
+
tagProcessed,
|
|
33017
|
+
external_uuid,
|
|
33018
|
+
device_name,
|
|
33019
|
+
...extra.map(([, v]) => v)
|
|
33020
|
+
];
|
|
33021
|
+
return { sql, values };
|
|
33022
|
+
};
|
|
33023
|
+
let cols = optional2.filter(([c]) => !missingColumns.has(c));
|
|
33024
|
+
let result;
|
|
33025
|
+
for (; ; ) {
|
|
33026
|
+
const { sql, values } = buildInsert(cols);
|
|
33027
|
+
try {
|
|
33028
|
+
result = await db.query(sql, values);
|
|
33029
|
+
break;
|
|
33030
|
+
} catch (err3) {
|
|
33031
|
+
const missing = missingOptionalColumn(err3, sql, cols);
|
|
33032
|
+
if (!missing) throw err3;
|
|
33033
|
+
missingColumns.add(missing);
|
|
33034
|
+
console.error(
|
|
33035
|
+
`\u26A0\uFE0F [HotPath] memory.${missing} \uCE78\uC774 \uC5C6\uC2B5\uB2C8\uB2E4 \u2014 \`mcp-agents-memory migrate\` \uC2E4\uD589 \uD6C4 \uC774 \uD504\uB85C\uC138\uC2A4\uB97C \uC7AC\uC2DC\uC791\uD574\uC57C ${MISSING_COLUMN_EFFECT[missing]}`
|
|
33036
|
+
);
|
|
33037
|
+
cols = cols.filter(([c]) => c !== missing);
|
|
33038
|
+
}
|
|
33039
|
+
}
|
|
33040
|
+
if (result.rows.length === 0) {
|
|
33041
|
+
const existing = await db.query(
|
|
33042
|
+
`SELECT id, created_at FROM memory WHERE external_uuid = $1 LIMIT 1`,
|
|
33043
|
+
[external_uuid]
|
|
33044
|
+
);
|
|
33045
|
+
if (existing.rows.length > 0) {
|
|
33046
|
+
const row2 = existing.rows[0];
|
|
33047
|
+
return { id: Number(row2.id), created_at: row2.created_at, inserted: false };
|
|
33048
|
+
}
|
|
33049
|
+
throw new Error(`insertRawMemory: ON CONFLICT triggered but no existing row found (external_uuid=${external_uuid})`);
|
|
33050
|
+
}
|
|
33051
|
+
const row = result.rows[0];
|
|
33052
|
+
return { id: Number(row.id), created_at: row.created_at, inserted: true };
|
|
33053
|
+
}
|
|
33054
|
+
var missingColumns, MISSING_COLUMN_EFFECT;
|
|
33055
|
+
var init_hot_path = __esm({
|
|
33056
|
+
"src/hot_path.ts"() {
|
|
33057
|
+
"use strict";
|
|
33058
|
+
init_db();
|
|
33059
|
+
init_buzz_envelope();
|
|
33060
|
+
init_venue();
|
|
33061
|
+
missingColumns = /* @__PURE__ */ new Set();
|
|
33062
|
+
MISSING_COLUMN_EFFECT = {
|
|
33063
|
+
raw_message: "Buzz \uBD09\uD22C \uC815\uB9AC\uAC00 \uC2DC\uC791\uB429\uB2C8\uB2E4. \uADF8 \uC804\uAE4C\uC9C0\uB294 \uC6D0\uBB38 \uADF8\uB300\uB85C \uC800\uC7A5\uD569\uB2C8\uB2E4.",
|
|
33064
|
+
venue: "\uB300\uD654 \uC790\uB9AC(venue) \uAE30\uB85D\uC774 \uC2DC\uC791\uB429\uB2C8\uB2E4. \uADF8 \uC804\uAE4C\uC9C0\uB294 venue \uC5C6\uC774 \uC800\uC7A5\uD569\uB2C8\uB2E4."
|
|
33065
|
+
};
|
|
33066
|
+
}
|
|
33067
|
+
});
|
|
33068
|
+
|
|
33069
|
+
// src/users.ts
|
|
33070
|
+
async function getOrCreateUser(userName) {
|
|
33071
|
+
const cached2 = cache.get(userName);
|
|
33072
|
+
if (cached2 !== void 0) return cached2;
|
|
33073
|
+
const existing = await db.query(
|
|
33074
|
+
`SELECT user_id FROM users WHERE user_name = $1 LIMIT 1`,
|
|
33075
|
+
[userName]
|
|
33076
|
+
);
|
|
33077
|
+
if (existing.rows.length > 0) {
|
|
33078
|
+
const id2 = Number(existing.rows[0].user_id);
|
|
33079
|
+
cache.set(userName, id2);
|
|
33080
|
+
return id2;
|
|
33081
|
+
}
|
|
33082
|
+
const inserted = await db.query(
|
|
33083
|
+
`INSERT INTO users (user_name) VALUES ($1)
|
|
33084
|
+
ON CONFLICT (user_name) DO UPDATE SET updated_at = users.updated_at
|
|
33085
|
+
RETURNING user_id`,
|
|
33086
|
+
[userName]
|
|
33087
|
+
);
|
|
33088
|
+
const id = Number(inserted.rows[0].user_id);
|
|
33089
|
+
cache.set(userName, id);
|
|
33090
|
+
return id;
|
|
33091
|
+
}
|
|
33092
|
+
async function getDefaultUserId() {
|
|
33093
|
+
const name = process.env.USER_NAME ?? "hoon";
|
|
33094
|
+
return getOrCreateUser(name);
|
|
33095
|
+
}
|
|
33096
|
+
async function updateUserProfile(userId, field, value) {
|
|
33097
|
+
if (field === "core_profile") {
|
|
33098
|
+
await db.query(
|
|
33099
|
+
`UPDATE users SET core_profile = $1 WHERE user_id = $2`,
|
|
33100
|
+
[value, userId]
|
|
33101
|
+
);
|
|
33102
|
+
} else {
|
|
33103
|
+
await db.query(
|
|
33104
|
+
`UPDATE users SET sub_profile = $1 WHERE user_id = $2`,
|
|
33105
|
+
[value, userId]
|
|
33106
|
+
);
|
|
33107
|
+
}
|
|
33108
|
+
}
|
|
33109
|
+
async function getUserProfile(userId) {
|
|
33110
|
+
const r = await db.query(
|
|
33111
|
+
`SELECT core_profile, sub_profile FROM users WHERE user_id = $1`,
|
|
33112
|
+
[userId]
|
|
33113
|
+
);
|
|
33114
|
+
return r.rows[0] ?? { core_profile: null, sub_profile: null };
|
|
33115
|
+
}
|
|
33116
|
+
var cache;
|
|
33117
|
+
var init_users = __esm({
|
|
33118
|
+
"src/users.ts"() {
|
|
33119
|
+
"use strict";
|
|
33120
|
+
init_db();
|
|
33121
|
+
cache = /* @__PURE__ */ new Map();
|
|
33122
|
+
}
|
|
33123
|
+
});
|
|
33124
|
+
|
|
32742
33125
|
// src/setup.ts
|
|
32743
33126
|
var setup_exports = {};
|
|
32744
33127
|
__export(setup_exports, {
|
|
@@ -32763,6 +33146,401 @@ var init_setup = __esm({
|
|
|
32763
33146
|
}
|
|
32764
33147
|
});
|
|
32765
33148
|
|
|
33149
|
+
// src/auto_save/buzz_ingest.ts
|
|
33150
|
+
var buzz_ingest_exports = {};
|
|
33151
|
+
__export(buzz_ingest_exports, {
|
|
33152
|
+
authorLabel: () => authorLabel,
|
|
33153
|
+
channelVenue: () => channelVenue,
|
|
33154
|
+
chatMessage: () => chatMessage,
|
|
33155
|
+
latestEdits: () => latestEdits,
|
|
33156
|
+
listChannels: () => listChannels,
|
|
33157
|
+
newChatEvents: () => newChatEvents,
|
|
33158
|
+
ownerPubkeys: () => ownerPubkeys,
|
|
33159
|
+
parseIngestArgs: () => parseIngestArgs,
|
|
33160
|
+
runBuzzIngest: () => runBuzzIngest
|
|
33161
|
+
});
|
|
33162
|
+
import { execFile } from "node:child_process";
|
|
33163
|
+
function cliPath() {
|
|
33164
|
+
return process.env.BUZZ_CLI || "buzz";
|
|
33165
|
+
}
|
|
33166
|
+
function childEnv() {
|
|
33167
|
+
return Object.fromEntries(Object.entries(process.env).filter(([k]) => CHILD_ENV.test(k)));
|
|
33168
|
+
}
|
|
33169
|
+
function runCli(args) {
|
|
33170
|
+
return new Promise((resolve, reject) => {
|
|
33171
|
+
execFile(
|
|
33172
|
+
cliPath(),
|
|
33173
|
+
args,
|
|
33174
|
+
{ maxBuffer: 256 * 1024 * 1024, timeout: 12e4, env: childEnv() },
|
|
33175
|
+
(err3, stdout, stderr) => {
|
|
33176
|
+
if (err3) {
|
|
33177
|
+
reject(new Error(`buzz ${args.slice(0, 2).join(" ")} \uC2E4\uD328: ${String(stderr || err3.message).trim().slice(0, 500)}`));
|
|
33178
|
+
return;
|
|
33179
|
+
}
|
|
33180
|
+
try {
|
|
33181
|
+
resolve(JSON.parse(stdout));
|
|
33182
|
+
} catch {
|
|
33183
|
+
reject(new BuzzShapeError(`buzz ${args.slice(0, 2).join(" ")}: JSON\uC774 \uC544\uB2CC \uCD9C\uB825`));
|
|
33184
|
+
}
|
|
33185
|
+
}
|
|
33186
|
+
);
|
|
33187
|
+
});
|
|
33188
|
+
}
|
|
33189
|
+
function asEvent(v) {
|
|
33190
|
+
const e = v;
|
|
33191
|
+
const ok4 = e && typeof e === "object" && typeof e.id === "string" && HEX64.test(e.id) && typeof e.pubkey === "string" && HEX64.test(e.pubkey) && Number.isInteger(e.created_at) && Number.isInteger(e.kind) && typeof e.content === "string" && Array.isArray(e.tags) && e.tags.every((t) => Array.isArray(t) && t.every((x) => typeof x === "string"));
|
|
33192
|
+
if (!ok4) throw new BuzzShapeError(`\uC608\uC0C1\uACFC \uB2E4\uB978 \uC774\uBCA4\uD2B8 \uBAA8\uC591: ${JSON.stringify(v).slice(0, 200)}`);
|
|
33193
|
+
return e;
|
|
33194
|
+
}
|
|
33195
|
+
function asArray(v, what) {
|
|
33196
|
+
if (!Array.isArray(v)) throw new BuzzShapeError(`${what}: \uBC30\uC5F4\uC774 \uC544\uB2CC \uCD9C\uB825`);
|
|
33197
|
+
return v;
|
|
33198
|
+
}
|
|
33199
|
+
async function listChannels() {
|
|
33200
|
+
const parse3 = (v) => asArray(v, "channels list").map((c) => {
|
|
33201
|
+
const ch = c;
|
|
33202
|
+
if (typeof ch?.channel_id !== "string" || !UUID.test(ch.channel_id) || typeof ch.name !== "string") {
|
|
33203
|
+
throw new BuzzShapeError(`\uC608\uC0C1\uACFC \uB2E4\uB978 \uCC44\uB110 \uBAA8\uC591: ${JSON.stringify(c).slice(0, 200)}`);
|
|
33204
|
+
}
|
|
33205
|
+
return { id: ch.channel_id, name: ch.name };
|
|
33206
|
+
});
|
|
33207
|
+
const all = parse3(await runCli(["channels", "list", "--member"]));
|
|
33208
|
+
if (all.length === 0) throw new BuzzShapeError("channels list --member: CLI \uC2E0\uC6D0\uC774 \uBA64\uBC84\uC778 \uBC29\uC774 \uD558\uB098\uB3C4 \uC5C6\uC74C");
|
|
33209
|
+
const priv = new Set(parse3(await runCli(["channels", "list", "--member", "--visibility", "private"])).map((c) => c.id));
|
|
33210
|
+
return all.map((c) => ({ ...c, private: priv.has(c.id) }));
|
|
33211
|
+
}
|
|
33212
|
+
function channelVenue(ch) {
|
|
33213
|
+
const name = ch.name.trim();
|
|
33214
|
+
if (!name) return "buzz";
|
|
33215
|
+
return ch.private && name === "DM" ? "buzz:dm" : `buzz:${name}`;
|
|
33216
|
+
}
|
|
33217
|
+
async function getPage(channelId, kinds, before) {
|
|
33218
|
+
const args = ["messages", "get", "--channel", channelId, "--limit", String(PAGE), "--kinds", kinds.join(",")];
|
|
33219
|
+
if (before !== void 0) args.push("--before", String(before));
|
|
33220
|
+
return asArray(await runCli(args), "messages get").map(asEvent);
|
|
33221
|
+
}
|
|
33222
|
+
async function knownIds(eventIds) {
|
|
33223
|
+
if (eventIds.length === 0) return /* @__PURE__ */ new Set();
|
|
33224
|
+
const r = await db.query(
|
|
33225
|
+
`SELECT external_uuid FROM memory WHERE external_uuid = ANY($1::text[])`,
|
|
33226
|
+
[eventIds.map((id) => `buzz:${id}`)]
|
|
33227
|
+
);
|
|
33228
|
+
return new Set(r.rows.map((row) => String(row.external_uuid).slice("buzz:".length)));
|
|
33229
|
+
}
|
|
33230
|
+
async function pageBack(channelId, kinds, visit) {
|
|
33231
|
+
const seen = /* @__PURE__ */ new Set();
|
|
33232
|
+
let before;
|
|
33233
|
+
for (let pages = 0; ; pages++) {
|
|
33234
|
+
if (pages >= MAX_PAGES_PER_CHANNEL) {
|
|
33235
|
+
throw new BuzzShapeError(`${channelId}: ${MAX_PAGES_PER_CHANNEL}\uCABD\uC744 \uB118\uACA8\uB3C4 \uB05D\uB098\uC9C0 \uC54A\uC74C`);
|
|
33236
|
+
}
|
|
33237
|
+
const page = await getPage(channelId, kinds, before);
|
|
33238
|
+
if (page.length === 0) {
|
|
33239
|
+
if (before === void 0) return;
|
|
33240
|
+
throw new BuzzShapeError(`${channelId}: \uC774\uC5B4\uC9C0\uB294 \uCABD\uC774 \uBE44\uC5B4 \uC788\uC74C`);
|
|
33241
|
+
}
|
|
33242
|
+
const unseen = page.filter((e) => !seen.has(e.id));
|
|
33243
|
+
if (unseen.length === 0) {
|
|
33244
|
+
if (page.length >= PAGE) throw new BuzzShapeError(`${channelId}: \uCABD \uB118\uAE40\uC774 \uC55E\uC73C\uB85C \uAC00\uC9C0 \uC54A\uC74C`);
|
|
33245
|
+
return;
|
|
33246
|
+
}
|
|
33247
|
+
for (const e of unseen) seen.add(e.id);
|
|
33248
|
+
if (!await visit(page, unseen)) return;
|
|
33249
|
+
before = Math.min(...page.map((e) => e.created_at)) + 1;
|
|
33250
|
+
}
|
|
33251
|
+
}
|
|
33252
|
+
async function newChatEvents(channelId) {
|
|
33253
|
+
const fresh = /* @__PURE__ */ new Map();
|
|
33254
|
+
const nowSec = Math.floor(Date.now() / 1e3);
|
|
33255
|
+
let stopBelow;
|
|
33256
|
+
await pageBack(channelId, [KIND_CHAT], async (page, unseen) => {
|
|
33257
|
+
const chats = unseen.filter((e) => e.kind === KIND_CHAT);
|
|
33258
|
+
const known = await knownIds(chats.map((e) => e.id));
|
|
33259
|
+
for (const e of chats) if (!known.has(e.id)) fresh.set(e.id, e);
|
|
33260
|
+
if (known.size > 0 && stopBelow === void 0) {
|
|
33261
|
+
const newestKnown = Math.max(...chats.filter((e) => known.has(e.id)).map((e) => e.created_at));
|
|
33262
|
+
stopBelow = Math.min(newestKnown, nowSec) - LOOKBACK_SEC;
|
|
33263
|
+
}
|
|
33264
|
+
return !(stopBelow !== void 0 && Math.min(...page.map((e) => e.created_at)) < stopBelow);
|
|
33265
|
+
});
|
|
33266
|
+
return [...fresh.values()];
|
|
33267
|
+
}
|
|
33268
|
+
async function recentEvents(channelId, kinds, floor) {
|
|
33269
|
+
const out = [];
|
|
33270
|
+
await pageBack(channelId, kinds, async (page, unseen) => {
|
|
33271
|
+
out.push(...unseen.filter((e) => kinds.includes(e.kind)));
|
|
33272
|
+
return floor !== void 0 && Math.min(...page.map((e) => e.created_at)) >= floor;
|
|
33273
|
+
});
|
|
33274
|
+
return out;
|
|
33275
|
+
}
|
|
33276
|
+
async function displayNames(pubkeys) {
|
|
33277
|
+
const self2 = asArray(await runCli(["users", "get"]), "users get").map((u) => u?.pubkey).find((pk) => typeof pk === "string" && HEX64.test(pk));
|
|
33278
|
+
if (!self2) {
|
|
33279
|
+
throw new BuzzShapeError("users get: CLI \uC2E0\uC6D0\uC758 \uD504\uB85C\uD544\uC774 \uC548 \uBCF4\uC784 \u2014 \uC774\uB984 \uC751\uB2F5\uC744 \uD655\uC778\uD560 \uC218 \uC5C6\uC74C (\uC2E0\uC6D0\uC5D0 \uD504\uB85C\uD544\uC744 \uB123\uC5B4 \uC8FC\uC138\uC694)");
|
|
33280
|
+
}
|
|
33281
|
+
const names = /* @__PURE__ */ new Map();
|
|
33282
|
+
for (let i = 0; i < pubkeys.length; i += 20) {
|
|
33283
|
+
const chunk = pubkeys.slice(i, i + 20);
|
|
33284
|
+
const ask = chunk.includes(self2) ? chunk : [...chunk, self2];
|
|
33285
|
+
const out = asArray(await runCli(["users", "get", ...ask.flatMap((pk) => ["--pubkey", pk])]), "users get");
|
|
33286
|
+
const profiles = out.map((u) => u);
|
|
33287
|
+
if (!profiles.some((p) => p?.pubkey === self2)) {
|
|
33288
|
+
throw new BuzzShapeError("users get: \uC790\uAE30 \uD504\uB85C\uD544\uC774 \uBE60\uC9C4 \uC751\uB2F5 \u2014 \uC774\uB984\uC744 \uBBFF\uC744 \uC218 \uC5C6\uC74C");
|
|
33289
|
+
}
|
|
33290
|
+
for (const p of profiles) {
|
|
33291
|
+
const label = [p?.display_name, p?.name].find((v) => typeof v === "string" && v.trim() !== "");
|
|
33292
|
+
if (typeof p?.pubkey === "string" && label) names.set(p.pubkey, label.trim());
|
|
33293
|
+
}
|
|
33294
|
+
}
|
|
33295
|
+
return names;
|
|
33296
|
+
}
|
|
33297
|
+
function ownerPubkeys() {
|
|
33298
|
+
const raw = process.env.BUZZ_INGEST_OWNER ?? "";
|
|
33299
|
+
const keys = raw.split(",").map((s) => s.trim().toLowerCase()).filter(Boolean);
|
|
33300
|
+
if (keys.length === 0 || !keys.every((k) => HEX64.test(k))) {
|
|
33301
|
+
throw new Error(
|
|
33302
|
+
"BUZZ_INGEST_OWNER: \uC774 \uBA54\uBAA8\uB9AC \uC8FC\uC778\uC758 \uBC84\uC988 pubkey(64\uC790\uB9AC hex)\uB97C \uC27C\uD45C\uB85C \uC801\uC5B4\uC57C \uD569\uB2C8\uB2E4 \u2014 \uADF8 \uBC16\uC758 \uC791\uC131\uC790\uB294 assistant\uB85C \uC800\uC7A5\uB429\uB2C8\uB2E4"
|
|
33303
|
+
);
|
|
33304
|
+
}
|
|
33305
|
+
return new Set(keys);
|
|
33306
|
+
}
|
|
33307
|
+
function authorLabel(pubkey, names) {
|
|
33308
|
+
return names.get(pubkey) ?? pubkey.slice(0, 8);
|
|
33309
|
+
}
|
|
33310
|
+
function chatMessage(e, names) {
|
|
33311
|
+
return `[${authorLabel(e.pubkey, names)}] ${e.content}`;
|
|
33312
|
+
}
|
|
33313
|
+
function targetOf(e) {
|
|
33314
|
+
const t = e.tags.find((tag) => tag[0] === "e" && HEX64.test(tag[1] ?? ""));
|
|
33315
|
+
return t ? t[1] : null;
|
|
33316
|
+
}
|
|
33317
|
+
function latestEdits(events) {
|
|
33318
|
+
const latest = /* @__PURE__ */ new Map();
|
|
33319
|
+
for (const e of events) {
|
|
33320
|
+
if (e.kind !== KIND_EDIT) continue;
|
|
33321
|
+
const target = targetOf(e);
|
|
33322
|
+
if (!target) continue;
|
|
33323
|
+
const cur = latest.get(target);
|
|
33324
|
+
if (!cur || e.created_at > cur.created_at || e.created_at === cur.created_at && e.id > cur.id) {
|
|
33325
|
+
latest.set(target, e);
|
|
33326
|
+
}
|
|
33327
|
+
}
|
|
33328
|
+
return latest;
|
|
33329
|
+
}
|
|
33330
|
+
async function applyEdits(edits, dryRun) {
|
|
33331
|
+
if (edits.size === 0) return 0;
|
|
33332
|
+
const r = await db.query(
|
|
33333
|
+
`SELECT external_uuid, message, raw_message FROM memory
|
|
33334
|
+
WHERE external_uuid = ANY($1::text[]) AND agent_platform = 'buzz' AND raw_message IS NOT NULL`,
|
|
33335
|
+
[[...edits.keys()].map((id) => `buzz:${id}`)]
|
|
33336
|
+
);
|
|
33337
|
+
let n = 0;
|
|
33338
|
+
for (const row of r.rows) {
|
|
33339
|
+
const edit = edits.get(String(row.external_uuid).slice("buzz:".length));
|
|
33340
|
+
const raw = row.raw_message;
|
|
33341
|
+
const msg = row.message;
|
|
33342
|
+
if (raw === edit.content || !msg.endsWith(raw)) continue;
|
|
33343
|
+
n++;
|
|
33344
|
+
if (dryRun) continue;
|
|
33345
|
+
await db.query(
|
|
33346
|
+
`UPDATE memory
|
|
33347
|
+
SET message = $2, raw_message = $3,
|
|
33348
|
+
tag_processed = FALSE, embedding = NULL, cold_error = NULL
|
|
33349
|
+
WHERE external_uuid = $1`,
|
|
33350
|
+
[row.external_uuid, msg.slice(0, msg.length - raw.length) + edit.content, edit.content]
|
|
33351
|
+
);
|
|
33352
|
+
}
|
|
33353
|
+
return n;
|
|
33354
|
+
}
|
|
33355
|
+
async function hideDeleted(targets, dryRun) {
|
|
33356
|
+
if (targets.length === 0) return 0;
|
|
33357
|
+
const uuids = targets.map((id) => `buzz:${id}`);
|
|
33358
|
+
const sql = dryRun ? `SELECT count(*)::int AS n FROM memory
|
|
33359
|
+
WHERE external_uuid = ANY($1::text[]) AND agent_platform = 'buzz' AND is_active AND NOT is_pinned` : `WITH h AS (
|
|
33360
|
+
UPDATE memory SET is_active = FALSE, archived_at = NOW()
|
|
33361
|
+
WHERE external_uuid = ANY($1::text[]) AND agent_platform = 'buzz' AND is_active AND NOT is_pinned
|
|
33362
|
+
RETURNING 1)
|
|
33363
|
+
SELECT count(*)::int AS n FROM h`;
|
|
33364
|
+
const r = await db.query(sql, [uuids]);
|
|
33365
|
+
return Number(r.rows[0]?.n ?? 0);
|
|
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
|
+
}
|
|
33430
|
+
async function runBuzzIngest(opts = {}) {
|
|
33431
|
+
const dryRun = opts.dryRun ?? false;
|
|
33432
|
+
const max = opts.max ?? 20;
|
|
33433
|
+
const owner = ownerPubkeys();
|
|
33434
|
+
const channels = await listChannels();
|
|
33435
|
+
const pending = [];
|
|
33436
|
+
for (const ch of channels) {
|
|
33437
|
+
const venue = channelVenue(ch);
|
|
33438
|
+
for (const e of await newChatEvents(ch.id)) pending.push({ e, ch, venue });
|
|
33439
|
+
}
|
|
33440
|
+
pending.sort((a, b) => a.e.created_at - b.e.created_at || (a.e.id < b.e.id ? -1 : a.e.id > b.e.id ? 1 : 0));
|
|
33441
|
+
const deleteTargets = /* @__PURE__ */ new Set();
|
|
33442
|
+
const edits = [];
|
|
33443
|
+
let unsupported = 0;
|
|
33444
|
+
for (const ch of channels) {
|
|
33445
|
+
for (const d of await getPage(ch.id, DELETE_KINDS)) {
|
|
33446
|
+
const t = DELETE_KINDS.includes(d.kind) ? targetOf(d) : null;
|
|
33447
|
+
if (t) deleteTargets.add(t);
|
|
33448
|
+
}
|
|
33449
|
+
}
|
|
33450
|
+
const batch = pending.filter(({ e }) => !deleteTargets.has(e.id)).slice(0, Math.max(0, max));
|
|
33451
|
+
for (const ch of channels) {
|
|
33452
|
+
const mine = batch.filter((b) => b.ch.id === ch.id).map((b) => b.e.created_at);
|
|
33453
|
+
edits.push(...await recentEvents(ch.id, [KIND_EDIT], mine.length ? Math.min(...mine) - LOOKBACK_SEC : void 0));
|
|
33454
|
+
if (dryRun) unsupported += (await getPage(ch.id, UNSUPPORTED_KINDS)).filter((e) => UNSUPPORTED_KINDS.includes(e.kind)).length;
|
|
33455
|
+
}
|
|
33456
|
+
const pubkeys = [...new Set(batch.map(({ e }) => e.pubkey))];
|
|
33457
|
+
const names = pubkeys.length ? await displayNames(pubkeys) : /* @__PURE__ */ new Map();
|
|
33458
|
+
const report = {
|
|
33459
|
+
channels: channels.length,
|
|
33460
|
+
pending: pending.length,
|
|
33461
|
+
inserted: 0,
|
|
33462
|
+
edited: 0,
|
|
33463
|
+
hidden: 0,
|
|
33464
|
+
recleaned: 0,
|
|
33465
|
+
envelopesReady: 0,
|
|
33466
|
+
envelopesStuck: 0,
|
|
33467
|
+
byVenue: {},
|
|
33468
|
+
...dryRun ? { unsupported } : {}
|
|
33469
|
+
};
|
|
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();
|
|
33471
|
+
for (const { e, venue } of batch) {
|
|
33472
|
+
report.byVenue[venue] = (report.byVenue[venue] ?? 0) + 1;
|
|
33473
|
+
if (dryRun) {
|
|
33474
|
+
report.inserted++;
|
|
33475
|
+
continue;
|
|
33476
|
+
}
|
|
33477
|
+
let res;
|
|
33478
|
+
try {
|
|
33479
|
+
res = await insertRawMemory({
|
|
33480
|
+
user_id: userId,
|
|
33481
|
+
agent_platform: "buzz",
|
|
33482
|
+
agent_model: null,
|
|
33483
|
+
role: owner.has(e.pubkey) ? "user" : "assistant",
|
|
33484
|
+
message: chatMessage(e, names),
|
|
33485
|
+
raw_message: e.content,
|
|
33486
|
+
external_uuid: `buzz:${e.id}`,
|
|
33487
|
+
device_name: null,
|
|
33488
|
+
venue,
|
|
33489
|
+
created_at: new Date(e.created_at * 1e3)
|
|
33490
|
+
});
|
|
33491
|
+
} catch (err3) {
|
|
33492
|
+
throw new Error(`buzz:${e.id} \uC800\uC7A5 \uC2E4\uD328: ${err3 instanceof Error ? err3.message : String(err3)}`);
|
|
33493
|
+
}
|
|
33494
|
+
if (res.inserted) report.inserted++;
|
|
33495
|
+
}
|
|
33496
|
+
report.edited = await applyEdits(latestEdits(edits), dryRun);
|
|
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
|
+
}
|
|
33504
|
+
return report;
|
|
33505
|
+
}
|
|
33506
|
+
function parseIngestArgs(argv) {
|
|
33507
|
+
const opts = {};
|
|
33508
|
+
for (let i = 0; i < argv.length; i++) {
|
|
33509
|
+
const a = argv[i];
|
|
33510
|
+
if (a === "--dry-run") opts.dryRun = true;
|
|
33511
|
+
else if (a === "--no-reclean") opts.noReclean = true;
|
|
33512
|
+
else if (a === "--max") {
|
|
33513
|
+
const v = argv[++i];
|
|
33514
|
+
if (v === void 0 || !/^\d+$/.test(v)) throw new Error("--max: 0 \uC774\uC0C1\uC758 \uC815\uC218");
|
|
33515
|
+
opts.max = Number(v);
|
|
33516
|
+
} else throw new Error(`buzz-ingest: \uBAA8\uB974\uB294 \uC635\uC158 ${a}`);
|
|
33517
|
+
}
|
|
33518
|
+
return opts;
|
|
33519
|
+
}
|
|
33520
|
+
var KIND_CHAT, KIND_EDIT, DELETE_KINDS, UNSUPPORTED_KINDS, PAGE, MAX_PAGES_PER_CHANNEL, LOOKBACK_SEC, HEX64, UUID, CHILD_ENV, BuzzShapeError, norm;
|
|
33521
|
+
var init_buzz_ingest = __esm({
|
|
33522
|
+
"src/auto_save/buzz_ingest.ts"() {
|
|
33523
|
+
"use strict";
|
|
33524
|
+
init_db();
|
|
33525
|
+
init_hot_path();
|
|
33526
|
+
init_buzz_envelope();
|
|
33527
|
+
init_users();
|
|
33528
|
+
KIND_CHAT = 9;
|
|
33529
|
+
KIND_EDIT = 40003;
|
|
33530
|
+
DELETE_KINDS = [5, 9005];
|
|
33531
|
+
UNSUPPORTED_KINDS = [40002, 40008, 45001, 45003];
|
|
33532
|
+
PAGE = 200;
|
|
33533
|
+
MAX_PAGES_PER_CHANNEL = 500;
|
|
33534
|
+
LOOKBACK_SEC = 48 * 3600;
|
|
33535
|
+
HEX64 = /^[0-9a-f]{64}$/;
|
|
33536
|
+
UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
|
|
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;
|
|
33538
|
+
BuzzShapeError = class extends Error {
|
|
33539
|
+
};
|
|
33540
|
+
norm = (s) => s.replace(/\s+/g, " ").trim();
|
|
33541
|
+
}
|
|
33542
|
+
});
|
|
33543
|
+
|
|
32766
33544
|
// src/migrations/runner.ts
|
|
32767
33545
|
var runner_exports = {};
|
|
32768
33546
|
__export(runner_exports, {
|
|
@@ -39873,13 +40651,13 @@ function _array(Class2, element, params) {
|
|
|
39873
40651
|
});
|
|
39874
40652
|
}
|
|
39875
40653
|
function _custom(Class2, fn, _params) {
|
|
39876
|
-
const
|
|
39877
|
-
|
|
40654
|
+
const norm2 = normalizeParams(_params);
|
|
40655
|
+
norm2.abort ?? (norm2.abort = true);
|
|
39878
40656
|
const schema = new Class2({
|
|
39879
40657
|
type: "custom",
|
|
39880
40658
|
check: "custom",
|
|
39881
40659
|
fn,
|
|
39882
|
-
...
|
|
40660
|
+
...norm2
|
|
39883
40661
|
});
|
|
39884
40662
|
return schema;
|
|
39885
40663
|
}
|
|
@@ -47030,325 +47808,10 @@ var StdioServerTransport = class {
|
|
|
47030
47808
|
|
|
47031
47809
|
// src/tools/manage_knowledge.ts
|
|
47032
47810
|
init_db();
|
|
47811
|
+
init_hot_path();
|
|
47812
|
+
init_users();
|
|
47033
47813
|
import * as os3 from "node:os";
|
|
47034
47814
|
|
|
47035
|
-
// src/hot_path.ts
|
|
47036
|
-
init_db();
|
|
47037
|
-
|
|
47038
|
-
// src/auto_save/buzz_envelope.ts
|
|
47039
|
-
var PREAMBLE_BLOCKS = ["agent-instructions", "core-memory"];
|
|
47040
|
-
var HISTORY_OPEN = /^\s*<((?:thread|conversation)-context)(?:\s[^>]*)?>/;
|
|
47041
|
-
var TURN_OPEN = /^\s*<(?:what-you-were-working-on|buzz-events?|new-message-arrived-while-you-were-working)[\s>]/;
|
|
47042
|
-
var TURN_IN_DROPPED = /(?:^|>)[ \t]*<(?:what-you-were-working-on|buzz-events?|new-message-arrived-while-you-were-working)[\s>]/m;
|
|
47043
|
-
var CONTEXT_OPEN = /^<context>\r?\n/;
|
|
47044
|
-
var CONTEXT_CLOSE = "\n</context>";
|
|
47045
|
-
function isNostrMetaLine(line) {
|
|
47046
|
-
return /^Tags: \[\[".*\]\]\s*$/.test(line) && /[0-9a-f]{64}/.test(line) || /^Parsed: (?:root=[0-9a-f]{64}|mentions=\[.*\([0-9a-f]{64}\))/.test(line);
|
|
47047
|
-
}
|
|
47048
|
-
function skipWhitespace(s, i) {
|
|
47049
|
-
while (i < s.length && /\s/.test(s[i])) i++;
|
|
47050
|
-
return i;
|
|
47051
|
-
}
|
|
47052
|
-
function locateBuzzContext(text) {
|
|
47053
|
-
if (!text.startsWith("<base>")) return CONTEXT_OPEN.test(text.slice(0, 12)) ? { rest: text, pos: 0 } : null;
|
|
47054
|
-
const baseEnd = text.indexOf("</base>");
|
|
47055
|
-
if (baseEnd < 0) return null;
|
|
47056
|
-
const rest = text.slice(baseEnd + "</base>".length);
|
|
47057
|
-
let pos = skipWhitespace(rest, 0);
|
|
47058
|
-
for (; ; ) {
|
|
47059
|
-
const name = PREAMBLE_BLOCKS.find((b) => rest.startsWith(`<${b}>`, pos));
|
|
47060
|
-
if (!name) break;
|
|
47061
|
-
const close = rest.indexOf(`</${name}>`, pos);
|
|
47062
|
-
if (close < 0) return null;
|
|
47063
|
-
pos = skipWhitespace(rest, close + name.length + 3);
|
|
47064
|
-
}
|
|
47065
|
-
return CONTEXT_OPEN.test(rest.slice(pos, pos + 12)) ? { rest, pos } : null;
|
|
47066
|
-
}
|
|
47067
|
-
function cleanBuzzEnvelope(message) {
|
|
47068
|
-
try {
|
|
47069
|
-
const text = message.trimStart();
|
|
47070
|
-
if (!text.startsWith("<base>")) return null;
|
|
47071
|
-
const loc = locateBuzzContext(text);
|
|
47072
|
-
if (!loc) return null;
|
|
47073
|
-
const { rest, pos } = loc;
|
|
47074
|
-
const bodyStart = rest.indexOf("\n", pos) + 1;
|
|
47075
|
-
const contextClose = rest.indexOf(CONTEXT_CLOSE, bodyStart - 1);
|
|
47076
|
-
if (contextClose < 0) return null;
|
|
47077
|
-
const contextBody = rest.slice(bodyStart, contextClose).replace(/\r$/, "");
|
|
47078
|
-
const tail = rest.slice(contextClose + CONTEXT_CLOSE.length);
|
|
47079
|
-
let turnStart = 0;
|
|
47080
|
-
const history = HISTORY_OPEN.exec(tail);
|
|
47081
|
-
if (history) {
|
|
47082
|
-
const closeTag = `</${history[1]}>`;
|
|
47083
|
-
const close = tail.indexOf(closeTag, history[0].length);
|
|
47084
|
-
if (close < 0) return null;
|
|
47085
|
-
turnStart = close + closeTag.length;
|
|
47086
|
-
}
|
|
47087
|
-
if (TURN_IN_DROPPED.test(rest.slice(0, pos)) || TURN_IN_DROPPED.test(tail.slice(0, turnStart))) return null;
|
|
47088
|
-
const turn = tail.slice(turnStart);
|
|
47089
|
-
if (!TURN_OPEN.test(turn)) return null;
|
|
47090
|
-
const turnText = turn.split("\n").filter((line) => !isNostrMetaLine(line)).join("\n").trim();
|
|
47091
|
-
if (!turnText) return null;
|
|
47092
|
-
const cleaned = `<context>
|
|
47093
|
-
${contextBody}
|
|
47094
|
-
</context>
|
|
47095
|
-
|
|
47096
|
-
${turnText}`;
|
|
47097
|
-
if (cleaned.length >= message.length) return null;
|
|
47098
|
-
return cleaned;
|
|
47099
|
-
} catch {
|
|
47100
|
-
return null;
|
|
47101
|
-
}
|
|
47102
|
-
}
|
|
47103
|
-
var 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*$/;
|
|
47104
|
-
function buzzEmbeddingText(message) {
|
|
47105
|
-
try {
|
|
47106
|
-
if (!message.startsWith("<context>\n")) return message;
|
|
47107
|
-
const close = message.indexOf(CONTEXT_CLOSE);
|
|
47108
|
-
if (close < 0) return message;
|
|
47109
|
-
const turn = message.slice(close + CONTEXT_CLOSE.length);
|
|
47110
|
-
if (!TURN_OPEN.test(turn)) return message;
|
|
47111
|
-
const text = turn.split("\n").filter((line) => !EVENT_HEADER.test(line) && !isNostrMetaLine(line)).map(
|
|
47112
|
-
(line) => (
|
|
47113
|
-
// `$` 없이: 끝까지 되짚는 역추적을 막는다 (긴 줄에서 제곱 시간)
|
|
47114
|
-
line.replace(/^From: (.*?) \(npub:.*/, "From: $1").replace(/^Channel: (\S+) \(#[0-9a-f-]+\)/, "Channel: $1")
|
|
47115
|
-
)
|
|
47116
|
-
).join("\n").trim();
|
|
47117
|
-
return text || message;
|
|
47118
|
-
} catch {
|
|
47119
|
-
return message;
|
|
47120
|
-
}
|
|
47121
|
-
}
|
|
47122
|
-
function venueFromBuzzContext(message) {
|
|
47123
|
-
if (!CONTEXT_OPEN.test(message.slice(0, 12))) return null;
|
|
47124
|
-
const end = message.indexOf(CONTEXT_CLOSE);
|
|
47125
|
-
if (end < 0) return null;
|
|
47126
|
-
const lines = message.slice(0, end).split("\n").map((l) => l.replace(/\r$/, ""));
|
|
47127
|
-
const scope = lines.find((l) => l.startsWith("Scope: "))?.slice("Scope: ".length).trim();
|
|
47128
|
-
if (scope !== "thread" && scope !== "channel" && scope !== "dm") return null;
|
|
47129
|
-
if (scope === "dm") return "buzz:dm";
|
|
47130
|
-
const channel = lines.find((l) => l.startsWith("Channel: "))?.trimEnd();
|
|
47131
|
-
const m = channel ? /^Channel: (.+?) \(#[0-9a-f-]{36}\)$/.exec(channel) : null;
|
|
47132
|
-
return m ? `buzz:${m[1]}` : "buzz";
|
|
47133
|
-
}
|
|
47134
|
-
function looksLikeBuzzTurn(message) {
|
|
47135
|
-
const text = message.trimStart();
|
|
47136
|
-
if (text.startsWith("<base>")) return true;
|
|
47137
|
-
if (!CONTEXT_OPEN.test(text.slice(0, 12))) return false;
|
|
47138
|
-
const close = text.indexOf(CONTEXT_CLOSE);
|
|
47139
|
-
return close >= 0 && TURN_OPEN.test(text.slice(close + CONTEXT_CLOSE.length));
|
|
47140
|
-
}
|
|
47141
|
-
function venueFromBuzzMessage(message) {
|
|
47142
|
-
try {
|
|
47143
|
-
const loc = locateBuzzContext(message.trimStart());
|
|
47144
|
-
return loc ? venueFromBuzzContext(loc.rest.slice(loc.pos)) : null;
|
|
47145
|
-
} catch {
|
|
47146
|
-
return null;
|
|
47147
|
-
}
|
|
47148
|
-
}
|
|
47149
|
-
|
|
47150
|
-
// src/auto_save/venue.ts
|
|
47151
|
-
import fs2 from "fs";
|
|
47152
|
-
var INTERACTIVE_ENTRYPOINTS = /* @__PURE__ */ new Set(["cli", "claude-vscode"]);
|
|
47153
|
-
function classifySessionVenue(firstUserText, entrypoint) {
|
|
47154
|
-
if (buzzTurnVenue(firstUserText)) return "buzz";
|
|
47155
|
-
if (entrypoint && INTERACTIVE_ENTRYPOINTS.has(entrypoint)) return "terminal";
|
|
47156
|
-
if (/\bslack\b/i.test(firstUserText.slice(0, 500))) return "slack";
|
|
47157
|
-
if (entrypoint) return "auto";
|
|
47158
|
-
return null;
|
|
47159
|
-
}
|
|
47160
|
-
function buzzTurnVenue(message) {
|
|
47161
|
-
return venueFromBuzzMessage(message) ?? (looksLikeBuzzTurn(message) ? "buzz" : null);
|
|
47162
|
-
}
|
|
47163
|
-
var TAIL_CHUNK = 4 * 1024 * 1024;
|
|
47164
|
-
var TAIL_CAP = 64 * 1024 * 1024;
|
|
47165
|
-
var NEWLINE = 10;
|
|
47166
|
-
function lastBuzzVenueInFile(jsonlPath, uptoBytes, userText) {
|
|
47167
|
-
const fd = fs2.openSync(jsonlPath, "r");
|
|
47168
|
-
try {
|
|
47169
|
-
let end = uptoBytes;
|
|
47170
|
-
let carry = Buffer.alloc(0);
|
|
47171
|
-
while (end > 0 && uptoBytes - end < TAIL_CAP) {
|
|
47172
|
-
const start = Math.max(0, end - TAIL_CHUNK);
|
|
47173
|
-
const chunk = Buffer.allocUnsafe(end - start);
|
|
47174
|
-
fs2.readSync(fd, chunk, 0, chunk.length, start);
|
|
47175
|
-
const buf = carry.length ? Buffer.concat([chunk, carry]) : chunk;
|
|
47176
|
-
const firstNl = start > 0 ? buf.indexOf(NEWLINE) : -1;
|
|
47177
|
-
carry = firstNl >= 0 ? buf.subarray(0, firstNl) : start > 0 ? buf : Buffer.alloc(0);
|
|
47178
|
-
const whole = firstNl >= 0 ? buf.subarray(firstNl + 1) : start > 0 ? Buffer.alloc(0) : buf;
|
|
47179
|
-
const lines = whole.toString("utf-8").split("\n");
|
|
47180
|
-
for (let i = lines.length - 1; i >= 0; i--) {
|
|
47181
|
-
const text = lines[i] ? userText(lines[i]) : null;
|
|
47182
|
-
const venue = text ? buzzTurnVenue(text) : null;
|
|
47183
|
-
if (venue) return venue;
|
|
47184
|
-
}
|
|
47185
|
-
end = start;
|
|
47186
|
-
}
|
|
47187
|
-
return null;
|
|
47188
|
-
} finally {
|
|
47189
|
-
fs2.closeSync(fd);
|
|
47190
|
-
}
|
|
47191
|
-
}
|
|
47192
|
-
|
|
47193
|
-
// src/hot_path.ts
|
|
47194
|
-
var missingColumns = /* @__PURE__ */ new Set();
|
|
47195
|
-
var MISSING_COLUMN_EFFECT = {
|
|
47196
|
-
raw_message: "Buzz \uBD09\uD22C \uC815\uB9AC\uAC00 \uC2DC\uC791\uB429\uB2C8\uB2E4. \uADF8 \uC804\uAE4C\uC9C0\uB294 \uC6D0\uBB38 \uADF8\uB300\uB85C \uC800\uC7A5\uD569\uB2C8\uB2E4.",
|
|
47197
|
-
venue: "\uB300\uD654 \uC790\uB9AC(venue) \uAE30\uB85D\uC774 \uC2DC\uC791\uB429\uB2C8\uB2E4. \uADF8 \uC804\uAE4C\uC9C0\uB294 venue \uC5C6\uC774 \uC800\uC7A5\uD569\uB2C8\uB2E4."
|
|
47198
|
-
};
|
|
47199
|
-
function missingOptionalColumn(err3, sql, cols) {
|
|
47200
|
-
const e = err3;
|
|
47201
|
-
if (e.code !== "42703") return void 0;
|
|
47202
|
-
const at = Number(e.position);
|
|
47203
|
-
const named = at > 0 ? /^[a-z_]+/.exec(sql.slice(at - 1))?.[0] : void 0;
|
|
47204
|
-
return cols.find(([c]) => named ? c === named : !!e.message?.includes(c))?.[0];
|
|
47205
|
-
}
|
|
47206
|
-
async function insertRawMemory(params) {
|
|
47207
|
-
const {
|
|
47208
|
-
user_id,
|
|
47209
|
-
agent_platform,
|
|
47210
|
-
agent_model,
|
|
47211
|
-
subagent = false,
|
|
47212
|
-
subagent_model = null,
|
|
47213
|
-
subagent_role = null,
|
|
47214
|
-
role,
|
|
47215
|
-
message,
|
|
47216
|
-
is_pinned = false,
|
|
47217
|
-
p_tag_id = null,
|
|
47218
|
-
d_tag = [],
|
|
47219
|
-
embedding = null,
|
|
47220
|
-
external_uuid = null,
|
|
47221
|
-
device_name = null
|
|
47222
|
-
} = params;
|
|
47223
|
-
const embeddingSql = embedding && embedding.length > 0 ? `[${embedding.join(",")}]` : null;
|
|
47224
|
-
const tagProcessed = p_tag_id !== null;
|
|
47225
|
-
const cleaned = role === "user" && !is_pinned && p_tag_id === null && embeddingSql === null ? cleanBuzzEnvelope(message) : null;
|
|
47226
|
-
const venue = params.venue ?? (role === "user" ? buzzTurnVenue(cleaned ?? message) : null);
|
|
47227
|
-
const optional2 = [];
|
|
47228
|
-
if (cleaned !== null) optional2.push(["raw_message", message]);
|
|
47229
|
-
if (venue) optional2.push(["venue", venue]);
|
|
47230
|
-
const buildInsert = (cols2) => {
|
|
47231
|
-
const text = cols2.some(([c]) => c === "raw_message") ? cleaned : message;
|
|
47232
|
-
const sql = `INSERT INTO memory (
|
|
47233
|
-
user_id, agent_platform, agent_model,
|
|
47234
|
-
subagent, subagent_model, subagent_role,
|
|
47235
|
-
role, message,
|
|
47236
|
-
p_tag_id, d_tag, embedding,
|
|
47237
|
-
is_pinned, tag_processed, external_uuid,
|
|
47238
|
-
device_name${cols2.map(([c]) => `, ${c}`).join("")}
|
|
47239
|
-
) VALUES (
|
|
47240
|
-
$1, $2, $3,
|
|
47241
|
-
$4, $5, $6,
|
|
47242
|
-
$7, $8,
|
|
47243
|
-
$9, $10::text[], $11::halfvec,
|
|
47244
|
-
$12, $13, $14,
|
|
47245
|
-
$15${cols2.map((_, i) => `, $${16 + i}`).join("")}
|
|
47246
|
-
)
|
|
47247
|
-
ON CONFLICT (external_uuid) WHERE external_uuid IS NOT NULL
|
|
47248
|
-
DO NOTHING
|
|
47249
|
-
RETURNING id, created_at`;
|
|
47250
|
-
const values = [
|
|
47251
|
-
user_id,
|
|
47252
|
-
agent_platform,
|
|
47253
|
-
agent_model,
|
|
47254
|
-
subagent,
|
|
47255
|
-
subagent_model,
|
|
47256
|
-
subagent_role,
|
|
47257
|
-
role,
|
|
47258
|
-
text,
|
|
47259
|
-
p_tag_id,
|
|
47260
|
-
d_tag,
|
|
47261
|
-
embeddingSql,
|
|
47262
|
-
is_pinned,
|
|
47263
|
-
tagProcessed,
|
|
47264
|
-
external_uuid,
|
|
47265
|
-
device_name,
|
|
47266
|
-
...cols2.map(([, v]) => v)
|
|
47267
|
-
];
|
|
47268
|
-
return { sql, values };
|
|
47269
|
-
};
|
|
47270
|
-
let cols = optional2.filter(([c]) => !missingColumns.has(c));
|
|
47271
|
-
let result;
|
|
47272
|
-
for (; ; ) {
|
|
47273
|
-
const { sql, values } = buildInsert(cols);
|
|
47274
|
-
try {
|
|
47275
|
-
result = await db.query(sql, values);
|
|
47276
|
-
break;
|
|
47277
|
-
} catch (err3) {
|
|
47278
|
-
const missing = missingOptionalColumn(err3, sql, cols);
|
|
47279
|
-
if (!missing) throw err3;
|
|
47280
|
-
missingColumns.add(missing);
|
|
47281
|
-
console.error(
|
|
47282
|
-
`\u26A0\uFE0F [HotPath] memory.${missing} \uCE78\uC774 \uC5C6\uC2B5\uB2C8\uB2E4 \u2014 \`mcp-agents-memory migrate\` \uC2E4\uD589 \uD6C4 \uC774 \uD504\uB85C\uC138\uC2A4\uB97C \uC7AC\uC2DC\uC791\uD574\uC57C ${MISSING_COLUMN_EFFECT[missing]}`
|
|
47283
|
-
);
|
|
47284
|
-
cols = cols.filter(([c]) => c !== missing);
|
|
47285
|
-
}
|
|
47286
|
-
}
|
|
47287
|
-
if (result.rows.length === 0) {
|
|
47288
|
-
const existing = await db.query(
|
|
47289
|
-
`SELECT id, created_at FROM memory WHERE external_uuid = $1 LIMIT 1`,
|
|
47290
|
-
[external_uuid]
|
|
47291
|
-
);
|
|
47292
|
-
if (existing.rows.length > 0) {
|
|
47293
|
-
const row2 = existing.rows[0];
|
|
47294
|
-
return { id: Number(row2.id), created_at: row2.created_at, inserted: false };
|
|
47295
|
-
}
|
|
47296
|
-
throw new Error(`insertRawMemory: ON CONFLICT triggered but no existing row found (external_uuid=${external_uuid})`);
|
|
47297
|
-
}
|
|
47298
|
-
const row = result.rows[0];
|
|
47299
|
-
return { id: Number(row.id), created_at: row.created_at, inserted: true };
|
|
47300
|
-
}
|
|
47301
|
-
|
|
47302
|
-
// src/users.ts
|
|
47303
|
-
init_db();
|
|
47304
|
-
var cache = /* @__PURE__ */ new Map();
|
|
47305
|
-
async function getOrCreateUser(userName) {
|
|
47306
|
-
const cached2 = cache.get(userName);
|
|
47307
|
-
if (cached2 !== void 0) return cached2;
|
|
47308
|
-
const existing = await db.query(
|
|
47309
|
-
`SELECT user_id FROM users WHERE user_name = $1 LIMIT 1`,
|
|
47310
|
-
[userName]
|
|
47311
|
-
);
|
|
47312
|
-
if (existing.rows.length > 0) {
|
|
47313
|
-
const id2 = Number(existing.rows[0].user_id);
|
|
47314
|
-
cache.set(userName, id2);
|
|
47315
|
-
return id2;
|
|
47316
|
-
}
|
|
47317
|
-
const inserted = await db.query(
|
|
47318
|
-
`INSERT INTO users (user_name) VALUES ($1)
|
|
47319
|
-
ON CONFLICT (user_name) DO UPDATE SET updated_at = users.updated_at
|
|
47320
|
-
RETURNING user_id`,
|
|
47321
|
-
[userName]
|
|
47322
|
-
);
|
|
47323
|
-
const id = Number(inserted.rows[0].user_id);
|
|
47324
|
-
cache.set(userName, id);
|
|
47325
|
-
return id;
|
|
47326
|
-
}
|
|
47327
|
-
async function getDefaultUserId() {
|
|
47328
|
-
const name = process.env.USER_NAME ?? "hoon";
|
|
47329
|
-
return getOrCreateUser(name);
|
|
47330
|
-
}
|
|
47331
|
-
async function updateUserProfile(userId, field, value) {
|
|
47332
|
-
if (field === "core_profile") {
|
|
47333
|
-
await db.query(
|
|
47334
|
-
`UPDATE users SET core_profile = $1 WHERE user_id = $2`,
|
|
47335
|
-
[value, userId]
|
|
47336
|
-
);
|
|
47337
|
-
} else {
|
|
47338
|
-
await db.query(
|
|
47339
|
-
`UPDATE users SET sub_profile = $1 WHERE user_id = $2`,
|
|
47340
|
-
[value, userId]
|
|
47341
|
-
);
|
|
47342
|
-
}
|
|
47343
|
-
}
|
|
47344
|
-
async function getUserProfile(userId) {
|
|
47345
|
-
const r = await db.query(
|
|
47346
|
-
`SELECT core_profile, sub_profile FROM users WHERE user_id = $1`,
|
|
47347
|
-
[userId]
|
|
47348
|
-
);
|
|
47349
|
-
return r.rows[0] ?? { core_profile: null, sub_profile: null };
|
|
47350
|
-
}
|
|
47351
|
-
|
|
47352
47815
|
// ../mcp-agents-memory/node_modules/openai/internal/tslib.mjs
|
|
47353
47816
|
function __classPrivateFieldSet(receiver, state, value, kind, f) {
|
|
47354
47817
|
if (kind === "m")
|
|
@@ -56089,6 +56552,8 @@ async function tagMessage(input) {
|
|
|
56089
56552
|
}
|
|
56090
56553
|
|
|
56091
56554
|
// src/auto_save/opencode_capture.ts
|
|
56555
|
+
init_hot_path();
|
|
56556
|
+
init_users();
|
|
56092
56557
|
import * as fs4 from "node:fs";
|
|
56093
56558
|
import * as path4 from "node:path";
|
|
56094
56559
|
import * as os2 from "node:os";
|
|
@@ -56651,6 +57116,7 @@ ${args.content}` : args.content;
|
|
|
56651
57116
|
|
|
56652
57117
|
// src/tools/search_memory.ts
|
|
56653
57118
|
init_db();
|
|
57119
|
+
init_users();
|
|
56654
57120
|
|
|
56655
57121
|
// src/cold_path/embedder.ts
|
|
56656
57122
|
var MAX_RETRIES = 3;
|
|
@@ -56997,9 +57463,11 @@ date_range \uC778\uC2DD \uD615\uC2DD:
|
|
|
56997
57463
|
|
|
56998
57464
|
// src/tools/manage_project_tags.ts
|
|
56999
57465
|
init_db();
|
|
57466
|
+
init_users();
|
|
57000
57467
|
|
|
57001
57468
|
// src/cold_path/project_alias_promoter.ts
|
|
57002
57469
|
init_db();
|
|
57470
|
+
init_users();
|
|
57003
57471
|
var QUALIFYING_RELATIONS = /* @__PURE__ */ new Set([
|
|
57004
57472
|
"rename",
|
|
57005
57473
|
"alias",
|
|
@@ -58315,7 +58783,9 @@ Actions:
|
|
|
58315
58783
|
}
|
|
58316
58784
|
|
|
58317
58785
|
// src/briefing.ts
|
|
58786
|
+
init_buzz_envelope();
|
|
58318
58787
|
init_db();
|
|
58788
|
+
init_users();
|
|
58319
58789
|
import * as os5 from "node:os";
|
|
58320
58790
|
var RECENT_CURRENT_LIMIT = 8;
|
|
58321
58791
|
var RECENT_OTHERS_LIMIT = 4;
|
|
@@ -58633,9 +59103,13 @@ agent_platform_filter=false.`,
|
|
|
58633
59103
|
}
|
|
58634
59104
|
|
|
58635
59105
|
// src/auto_save/save_message_tool.ts
|
|
59106
|
+
init_hot_path();
|
|
59107
|
+
init_users();
|
|
58636
59108
|
import * as os10 from "node:os";
|
|
58637
59109
|
|
|
58638
59110
|
// src/auto_save/gemini_capture.ts
|
|
59111
|
+
init_hot_path();
|
|
59112
|
+
init_users();
|
|
58639
59113
|
import * as fs5 from "node:fs";
|
|
58640
59114
|
import * as path5 from "node:path";
|
|
58641
59115
|
import * as os6 from "node:os";
|
|
@@ -59020,6 +59494,8 @@ async function captureSessionEnd2() {
|
|
|
59020
59494
|
}
|
|
59021
59495
|
|
|
59022
59496
|
// src/auto_save/grok_capture.ts
|
|
59497
|
+
init_hot_path();
|
|
59498
|
+
init_users();
|
|
59023
59499
|
import * as fs6 from "node:fs";
|
|
59024
59500
|
import * as path6 from "node:path";
|
|
59025
59501
|
import * as os7 from "node:os";
|
|
@@ -59303,6 +59779,8 @@ function isCaptureArmed3() {
|
|
|
59303
59779
|
}
|
|
59304
59780
|
|
|
59305
59781
|
// src/auto_save/antigravity_capture.ts
|
|
59782
|
+
init_hot_path();
|
|
59783
|
+
init_users();
|
|
59306
59784
|
import * as fs7 from "node:fs";
|
|
59307
59785
|
import * as path7 from "node:path";
|
|
59308
59786
|
import * as os8 from "node:os";
|
|
@@ -59605,6 +60083,9 @@ function isCaptureArmed4() {
|
|
|
59605
60083
|
}
|
|
59606
60084
|
|
|
59607
60085
|
// src/auto_save/hermes_capture.ts
|
|
60086
|
+
init_hot_path();
|
|
60087
|
+
init_venue();
|
|
60088
|
+
init_users();
|
|
59608
60089
|
import * as fs8 from "node:fs";
|
|
59609
60090
|
import * as path8 from "node:path";
|
|
59610
60091
|
import * as os9 from "node:os";
|
|
@@ -59975,6 +60456,7 @@ subagent \uCEE8\uD14D\uC2A4\uD2B8\uB77C\uBA74 subagent=true + subagent_model + s
|
|
|
59975
60456
|
|
|
59976
60457
|
// src/tools/task_pin.ts
|
|
59977
60458
|
init_db();
|
|
60459
|
+
init_users();
|
|
59978
60460
|
import * as os11 from "node:os";
|
|
59979
60461
|
var DEVICE_NAME8 = os11.hostname();
|
|
59980
60462
|
var STATUSES = ["pending", "in_progress", "done", "failed"];
|
|
@@ -60125,9 +60607,11 @@ function registerTools(server) {
|
|
|
60125
60607
|
|
|
60126
60608
|
// src/cold_path/worker.ts
|
|
60127
60609
|
init_db();
|
|
60610
|
+
init_buzz_envelope();
|
|
60128
60611
|
|
|
60129
60612
|
// src/cold_path/dtag_promoter.ts
|
|
60130
60613
|
init_db();
|
|
60614
|
+
init_users();
|
|
60131
60615
|
function envInt2(name, fallback) {
|
|
60132
60616
|
const raw = process.env[name];
|
|
60133
60617
|
if (!raw) return fallback;
|
|
@@ -60276,6 +60760,7 @@ async function runDtagPromotion() {
|
|
|
60276
60760
|
|
|
60277
60761
|
// src/librarian.ts
|
|
60278
60762
|
init_db();
|
|
60763
|
+
init_users();
|
|
60279
60764
|
|
|
60280
60765
|
// src/context_budget.ts
|
|
60281
60766
|
var DEFAULT_CTX = 8192;
|
|
@@ -60893,6 +61378,9 @@ async function drainColdPath(maxTicks = 12) {
|
|
|
60893
61378
|
}
|
|
60894
61379
|
|
|
60895
61380
|
// src/auto_save/jsonl_capture.ts
|
|
61381
|
+
init_hot_path();
|
|
61382
|
+
init_venue();
|
|
61383
|
+
init_users();
|
|
60896
61384
|
import * as fs9 from "node:fs";
|
|
60897
61385
|
import * as path9 from "node:path";
|
|
60898
61386
|
import * as os12 from "node:os";
|
|
@@ -61260,6 +61748,8 @@ async function captureSessionEnd6() {
|
|
|
61260
61748
|
}
|
|
61261
61749
|
|
|
61262
61750
|
// src/auto_save/codex_capture.ts
|
|
61751
|
+
init_hot_path();
|
|
61752
|
+
init_users();
|
|
61263
61753
|
import * as fs10 from "node:fs";
|
|
61264
61754
|
import * as path10 from "node:path";
|
|
61265
61755
|
import * as os13 from "node:os";
|
|
@@ -61756,6 +62246,14 @@ Usage:
|
|
|
61756
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.
|
|
61757
62247
|
mcp-agents-memory setup Interactive setup \u2014 write config to ~/.config/mcp-agents-memory/.env and run migrations.
|
|
61758
62248
|
mcp-agents-memory migrate Apply any pending DB migrations against the configured database.
|
|
62249
|
+
mcp-agents-memory buzz-ingest [--dry-run] [--max N] [--no-reclean]
|
|
62250
|
+
(Buzz only) Copy chat messages from the Buzz rooms the CLI identity is a member of
|
|
62251
|
+
into memory, one row per message, via the official \`buzz\` CLI. Oldest first, at most
|
|
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
|
|
62255
|
+
(the memory owner's pubkeys, comma-separated) in the environment of this command only \u2014
|
|
62256
|
+
not in the shared .env; optional BUZZ_CLI.
|
|
61759
62257
|
mcp-agents-memory help Show this message.
|
|
61760
62258
|
|
|
61761
62259
|
Configuration is loaded from (first hit wins):
|
|
@@ -61848,6 +62346,15 @@ async function cli() {
|
|
|
61848
62346
|
await runSetupWizard2();
|
|
61849
62347
|
process.exit(0);
|
|
61850
62348
|
}
|
|
62349
|
+
if (cmd === "buzz-ingest") {
|
|
62350
|
+
const { runBuzzIngest: runBuzzIngest2, parseIngestArgs: parseIngestArgs2 } = await Promise.resolve().then(() => (init_buzz_ingest(), buzz_ingest_exports));
|
|
62351
|
+
const opts = parseIngestArgs2(process.argv.slice(3));
|
|
62352
|
+
await db.connect();
|
|
62353
|
+
const report = await runBuzzIngest2(opts);
|
|
62354
|
+
console.log(JSON.stringify({ dryRun: opts.dryRun ?? false, ...report }));
|
|
62355
|
+
await db.close();
|
|
62356
|
+
process.exit(0);
|
|
62357
|
+
}
|
|
61851
62358
|
if (cmd === "migrate") {
|
|
61852
62359
|
const { runAllMigrations: runAllMigrations2 } = await Promise.resolve().then(() => (init_runner(), runner_exports));
|
|
61853
62360
|
await runAllMigrations2();
|