mcp-agents-memory 0.9.19 → 0.9.20

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 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로 한 건씩 메모리에 옮긴다. 타이머로 돌리고, 설정은 CHANGELOG 0.9.20 참고.
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. Run it from a timer; see CHANGELOG 0.9.20 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
- execFile("cygpath", ["-w", socketPath], (err4, stdout, stderr) => {
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,353 @@ 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) {
32766
+ try {
32767
+ const text = message.trimStart();
32768
+ if (!text.startsWith("<base>")) return null;
32769
+ const loc = locateBuzzContext(text);
32770
+ if (!loc) return null;
32771
+ const { rest, pos } = loc;
32772
+ const bodyStart = rest.indexOf("\n", pos) + 1;
32773
+ const contextClose = rest.indexOf(CONTEXT_CLOSE, bodyStart - 1);
32774
+ if (contextClose < 0) return null;
32775
+ const contextBody = rest.slice(bodyStart, contextClose).replace(/\r$/, "");
32776
+ const tail = rest.slice(contextClose + CONTEXT_CLOSE.length);
32777
+ let turnStart = 0;
32778
+ const history = HISTORY_OPEN.exec(tail);
32779
+ if (history) {
32780
+ const closeTag = `</${history[1]}>`;
32781
+ const close = tail.indexOf(closeTag, history[0].length);
32782
+ if (close < 0) return null;
32783
+ turnStart = close + closeTag.length;
32784
+ }
32785
+ if (TURN_IN_DROPPED.test(rest.slice(0, pos)) || TURN_IN_DROPPED.test(tail.slice(0, turnStart))) return null;
32786
+ const turn = tail.slice(turnStart);
32787
+ if (!TURN_OPEN.test(turn)) return null;
32788
+ const turnText = turn.split("\n").filter((line) => !isNostrMetaLine(line)).join("\n").trim();
32789
+ if (!turnText) return null;
32790
+ const cleaned = `<context>
32791
+ ${contextBody}
32792
+ </context>
32793
+
32794
+ ${turnText}`;
32795
+ if (cleaned.length >= message.length) return null;
32796
+ return cleaned;
32797
+ } catch {
32798
+ return null;
32799
+ }
32800
+ }
32801
+ function buzzEmbeddingText(message) {
32802
+ try {
32803
+ if (!message.startsWith("<context>\n")) return message;
32804
+ const close = message.indexOf(CONTEXT_CLOSE);
32805
+ if (close < 0) return message;
32806
+ const turn = message.slice(close + CONTEXT_CLOSE.length);
32807
+ if (!TURN_OPEN.test(turn)) return message;
32808
+ const text = turn.split("\n").filter((line) => !EVENT_HEADER.test(line) && !isNostrMetaLine(line)).map(
32809
+ (line) => (
32810
+ // `$` 없이: 끝까지 되짚는 역추적을 막는다 (긴 줄에서 제곱 시간)
32811
+ line.replace(/^From: (.*?) \(npub:.*/, "From: $1").replace(/^Channel: (\S+) \(#[0-9a-f-]+\)/, "Channel: $1")
32812
+ )
32813
+ ).join("\n").trim();
32814
+ return text || message;
32815
+ } catch {
32816
+ return message;
32817
+ }
32818
+ }
32819
+ function venueFromBuzzContext(message) {
32820
+ if (!CONTEXT_OPEN.test(message.slice(0, 12))) return null;
32821
+ const end = message.indexOf(CONTEXT_CLOSE);
32822
+ if (end < 0) return null;
32823
+ const lines = message.slice(0, end).split("\n").map((l) => l.replace(/\r$/, ""));
32824
+ const scope = lines.find((l) => l.startsWith("Scope: "))?.slice("Scope: ".length).trim();
32825
+ if (scope !== "thread" && scope !== "channel" && scope !== "dm") return null;
32826
+ if (scope === "dm") return "buzz:dm";
32827
+ const channel = lines.find((l) => l.startsWith("Channel: "))?.trimEnd();
32828
+ const m = channel ? /^Channel: (.+?) \(#[0-9a-f-]{36}\)$/.exec(channel) : null;
32829
+ return m ? `buzz:${m[1]}` : "buzz";
32830
+ }
32831
+ function looksLikeBuzzTurn(message) {
32832
+ const text = message.trimStart();
32833
+ if (text.startsWith("<base>")) return true;
32834
+ if (!CONTEXT_OPEN.test(text.slice(0, 12))) return false;
32835
+ const close = text.indexOf(CONTEXT_CLOSE);
32836
+ return close >= 0 && TURN_OPEN.test(text.slice(close + CONTEXT_CLOSE.length));
32837
+ }
32838
+ function venueFromBuzzMessage(message) {
32839
+ try {
32840
+ const loc = locateBuzzContext(message.trimStart());
32841
+ return loc ? venueFromBuzzContext(loc.rest.slice(loc.pos)) : null;
32842
+ } catch {
32843
+ return null;
32844
+ }
32845
+ }
32846
+ var PREAMBLE_BLOCKS, HISTORY_OPEN, TURN_OPEN, TURN_IN_DROPPED, CONTEXT_OPEN, CONTEXT_CLOSE, EVENT_HEADER;
32847
+ var init_buzz_envelope = __esm({
32848
+ "src/auto_save/buzz_envelope.ts"() {
32849
+ "use strict";
32850
+ PREAMBLE_BLOCKS = ["agent-instructions", "core-memory"];
32851
+ HISTORY_OPEN = /^\s*<((?:thread|conversation)-context)(?:\s[^>]*)?>/;
32852
+ TURN_OPEN = /^\s*<(?:what-you-were-working-on|buzz-events?|new-message-arrived-while-you-were-working)[\s>]/;
32853
+ TURN_IN_DROPPED = /(?:^|>)[ \t]*<(?:what-you-were-working-on|buzz-events?|new-message-arrived-while-you-were-working)[\s>]/m;
32854
+ CONTEXT_OPEN = /^<context>\r?\n/;
32855
+ CONTEXT_CLOSE = "\n</context>";
32856
+ 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
+ }
32858
+ });
32859
+
32860
+ // src/auto_save/venue.ts
32861
+ import fs2 from "fs";
32862
+ function classifySessionVenue(firstUserText, entrypoint) {
32863
+ if (buzzTurnVenue(firstUserText)) return "buzz";
32864
+ if (entrypoint && INTERACTIVE_ENTRYPOINTS.has(entrypoint)) return "terminal";
32865
+ if (/\bslack\b/i.test(firstUserText.slice(0, 500))) return "slack";
32866
+ if (entrypoint) return "auto";
32867
+ return null;
32868
+ }
32869
+ function buzzTurnVenue(message) {
32870
+ return venueFromBuzzMessage(message) ?? (looksLikeBuzzTurn(message) ? "buzz" : null);
32871
+ }
32872
+ function lastBuzzVenueInFile(jsonlPath, uptoBytes, userText) {
32873
+ const fd = fs2.openSync(jsonlPath, "r");
32874
+ try {
32875
+ let end = uptoBytes;
32876
+ let carry = Buffer.alloc(0);
32877
+ while (end > 0 && uptoBytes - end < TAIL_CAP) {
32878
+ const start = Math.max(0, end - TAIL_CHUNK);
32879
+ const chunk = Buffer.allocUnsafe(end - start);
32880
+ fs2.readSync(fd, chunk, 0, chunk.length, start);
32881
+ const buf = carry.length ? Buffer.concat([chunk, carry]) : chunk;
32882
+ const firstNl = start > 0 ? buf.indexOf(NEWLINE) : -1;
32883
+ carry = firstNl >= 0 ? buf.subarray(0, firstNl) : start > 0 ? buf : Buffer.alloc(0);
32884
+ const whole = firstNl >= 0 ? buf.subarray(firstNl + 1) : start > 0 ? Buffer.alloc(0) : buf;
32885
+ const lines = whole.toString("utf-8").split("\n");
32886
+ for (let i = lines.length - 1; i >= 0; i--) {
32887
+ const text = lines[i] ? userText(lines[i]) : null;
32888
+ const venue = text ? buzzTurnVenue(text) : null;
32889
+ if (venue) return venue;
32890
+ }
32891
+ end = start;
32892
+ }
32893
+ return null;
32894
+ } finally {
32895
+ fs2.closeSync(fd);
32896
+ }
32897
+ }
32898
+ var INTERACTIVE_ENTRYPOINTS, TAIL_CHUNK, TAIL_CAP, NEWLINE;
32899
+ var init_venue = __esm({
32900
+ "src/auto_save/venue.ts"() {
32901
+ "use strict";
32902
+ init_buzz_envelope();
32903
+ INTERACTIVE_ENTRYPOINTS = /* @__PURE__ */ new Set(["cli", "claude-vscode"]);
32904
+ TAIL_CHUNK = 4 * 1024 * 1024;
32905
+ TAIL_CAP = 64 * 1024 * 1024;
32906
+ NEWLINE = 10;
32907
+ }
32908
+ });
32909
+
32910
+ // src/hot_path.ts
32911
+ function missingOptionalColumn(err3, sql, cols) {
32912
+ const e = err3;
32913
+ if (e.code !== "42703") return void 0;
32914
+ const at = Number(e.position);
32915
+ const named = at > 0 ? /^[a-z_]+/.exec(sql.slice(at - 1))?.[0] : void 0;
32916
+ return cols.find(([c]) => named ? c === named : !!e.message?.includes(c))?.[0];
32917
+ }
32918
+ async function insertRawMemory(params) {
32919
+ const {
32920
+ user_id,
32921
+ agent_platform,
32922
+ agent_model,
32923
+ subagent = false,
32924
+ subagent_model = null,
32925
+ subagent_role = null,
32926
+ role,
32927
+ message,
32928
+ is_pinned = false,
32929
+ p_tag_id = null,
32930
+ d_tag = [],
32931
+ embedding = null,
32932
+ external_uuid = null,
32933
+ device_name = null
32934
+ } = params;
32935
+ const embeddingSql = embedding && embedding.length > 0 ? `[${embedding.join(",")}]` : null;
32936
+ const tagProcessed = p_tag_id !== null;
32937
+ const cleaned = role === "user" && !is_pinned && p_tag_id === null && embeddingSql === null ? cleanBuzzEnvelope(message) : null;
32938
+ const venue = params.venue ?? (role === "user" ? buzzTurnVenue(cleaned ?? message) : null);
32939
+ const raw = cleaned !== null ? message : params.raw_message ?? null;
32940
+ const optional2 = [];
32941
+ if (raw !== null) optional2.push(["raw_message", raw]);
32942
+ if (venue) optional2.push(["venue", venue]);
32943
+ const buildInsert = (cols2) => {
32944
+ const text = cleaned !== null && cols2.some(([c]) => c === "raw_message") ? cleaned : message;
32945
+ const extra = [
32946
+ ...cols2,
32947
+ ...params.created_at ? [["created_at", params.created_at]] : []
32948
+ ];
32949
+ const sql = `INSERT INTO memory (
32950
+ user_id, agent_platform, agent_model,
32951
+ subagent, subagent_model, subagent_role,
32952
+ role, message,
32953
+ p_tag_id, d_tag, embedding,
32954
+ is_pinned, tag_processed, external_uuid,
32955
+ device_name${extra.map(([c]) => `, ${c}`).join("")}
32956
+ ) VALUES (
32957
+ $1, $2, $3,
32958
+ $4, $5, $6,
32959
+ $7, $8,
32960
+ $9, $10::text[], $11::halfvec,
32961
+ $12, $13, $14,
32962
+ $15${extra.map((_, i) => `, $${16 + i}`).join("")}
32963
+ )
32964
+ ON CONFLICT (external_uuid) WHERE external_uuid IS NOT NULL
32965
+ DO NOTHING
32966
+ RETURNING id, created_at`;
32967
+ const values = [
32968
+ user_id,
32969
+ agent_platform,
32970
+ agent_model,
32971
+ subagent,
32972
+ subagent_model,
32973
+ subagent_role,
32974
+ role,
32975
+ text,
32976
+ p_tag_id,
32977
+ d_tag,
32978
+ embeddingSql,
32979
+ is_pinned,
32980
+ tagProcessed,
32981
+ external_uuid,
32982
+ device_name,
32983
+ ...extra.map(([, v]) => v)
32984
+ ];
32985
+ return { sql, values };
32986
+ };
32987
+ let cols = optional2.filter(([c]) => !missingColumns.has(c));
32988
+ let result;
32989
+ for (; ; ) {
32990
+ const { sql, values } = buildInsert(cols);
32991
+ try {
32992
+ result = await db.query(sql, values);
32993
+ break;
32994
+ } catch (err3) {
32995
+ const missing = missingOptionalColumn(err3, sql, cols);
32996
+ if (!missing) throw err3;
32997
+ missingColumns.add(missing);
32998
+ console.error(
32999
+ `\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]}`
33000
+ );
33001
+ cols = cols.filter(([c]) => c !== missing);
33002
+ }
33003
+ }
33004
+ if (result.rows.length === 0) {
33005
+ const existing = await db.query(
33006
+ `SELECT id, created_at FROM memory WHERE external_uuid = $1 LIMIT 1`,
33007
+ [external_uuid]
33008
+ );
33009
+ if (existing.rows.length > 0) {
33010
+ const row2 = existing.rows[0];
33011
+ return { id: Number(row2.id), created_at: row2.created_at, inserted: false };
33012
+ }
33013
+ throw new Error(`insertRawMemory: ON CONFLICT triggered but no existing row found (external_uuid=${external_uuid})`);
33014
+ }
33015
+ const row = result.rows[0];
33016
+ return { id: Number(row.id), created_at: row.created_at, inserted: true };
33017
+ }
33018
+ var missingColumns, MISSING_COLUMN_EFFECT;
33019
+ var init_hot_path = __esm({
33020
+ "src/hot_path.ts"() {
33021
+ "use strict";
33022
+ init_db();
33023
+ init_buzz_envelope();
33024
+ init_venue();
33025
+ missingColumns = /* @__PURE__ */ new Set();
33026
+ MISSING_COLUMN_EFFECT = {
33027
+ 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.",
33028
+ 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."
33029
+ };
33030
+ }
33031
+ });
33032
+
33033
+ // src/users.ts
33034
+ async function getOrCreateUser(userName) {
33035
+ const cached2 = cache.get(userName);
33036
+ if (cached2 !== void 0) return cached2;
33037
+ const existing = await db.query(
33038
+ `SELECT user_id FROM users WHERE user_name = $1 LIMIT 1`,
33039
+ [userName]
33040
+ );
33041
+ if (existing.rows.length > 0) {
33042
+ const id2 = Number(existing.rows[0].user_id);
33043
+ cache.set(userName, id2);
33044
+ return id2;
33045
+ }
33046
+ const inserted = await db.query(
33047
+ `INSERT INTO users (user_name) VALUES ($1)
33048
+ ON CONFLICT (user_name) DO UPDATE SET updated_at = users.updated_at
33049
+ RETURNING user_id`,
33050
+ [userName]
33051
+ );
33052
+ const id = Number(inserted.rows[0].user_id);
33053
+ cache.set(userName, id);
33054
+ return id;
33055
+ }
33056
+ async function getDefaultUserId() {
33057
+ const name = process.env.USER_NAME ?? "hoon";
33058
+ return getOrCreateUser(name);
33059
+ }
33060
+ async function updateUserProfile(userId, field, value) {
33061
+ if (field === "core_profile") {
33062
+ await db.query(
33063
+ `UPDATE users SET core_profile = $1 WHERE user_id = $2`,
33064
+ [value, userId]
33065
+ );
33066
+ } else {
33067
+ await db.query(
33068
+ `UPDATE users SET sub_profile = $1 WHERE user_id = $2`,
33069
+ [value, userId]
33070
+ );
33071
+ }
33072
+ }
33073
+ async function getUserProfile(userId) {
33074
+ const r = await db.query(
33075
+ `SELECT core_profile, sub_profile FROM users WHERE user_id = $1`,
33076
+ [userId]
33077
+ );
33078
+ return r.rows[0] ?? { core_profile: null, sub_profile: null };
33079
+ }
33080
+ var cache;
33081
+ var init_users = __esm({
33082
+ "src/users.ts"() {
33083
+ "use strict";
33084
+ init_db();
33085
+ cache = /* @__PURE__ */ new Map();
33086
+ }
33087
+ });
33088
+
32742
33089
  // src/setup.ts
32743
33090
  var setup_exports = {};
32744
33091
  __export(setup_exports, {
@@ -32763,6 +33110,326 @@ var init_setup = __esm({
32763
33110
  }
32764
33111
  });
32765
33112
 
33113
+ // src/auto_save/buzz_ingest.ts
33114
+ var buzz_ingest_exports = {};
33115
+ __export(buzz_ingest_exports, {
33116
+ authorLabel: () => authorLabel,
33117
+ channelVenue: () => channelVenue,
33118
+ chatMessage: () => chatMessage,
33119
+ latestEdits: () => latestEdits,
33120
+ listChannels: () => listChannels,
33121
+ newChatEvents: () => newChatEvents,
33122
+ ownerPubkeys: () => ownerPubkeys,
33123
+ parseIngestArgs: () => parseIngestArgs,
33124
+ runBuzzIngest: () => runBuzzIngest
33125
+ });
33126
+ import { execFile } from "node:child_process";
33127
+ function cliPath() {
33128
+ return process.env.BUZZ_CLI || "buzz";
33129
+ }
33130
+ function childEnv() {
33131
+ return Object.fromEntries(Object.entries(process.env).filter(([k]) => CHILD_ENV.test(k)));
33132
+ }
33133
+ function runCli(args) {
33134
+ return new Promise((resolve, reject) => {
33135
+ execFile(
33136
+ cliPath(),
33137
+ args,
33138
+ { maxBuffer: 256 * 1024 * 1024, timeout: 12e4, env: childEnv() },
33139
+ (err3, stdout, stderr) => {
33140
+ if (err3) {
33141
+ reject(new Error(`buzz ${args.slice(0, 2).join(" ")} \uC2E4\uD328: ${String(stderr || err3.message).trim().slice(0, 500)}`));
33142
+ return;
33143
+ }
33144
+ try {
33145
+ resolve(JSON.parse(stdout));
33146
+ } catch {
33147
+ reject(new BuzzShapeError(`buzz ${args.slice(0, 2).join(" ")}: JSON\uC774 \uC544\uB2CC \uCD9C\uB825`));
33148
+ }
33149
+ }
33150
+ );
33151
+ });
33152
+ }
33153
+ function asEvent(v) {
33154
+ const e = v;
33155
+ 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"));
33156
+ if (!ok4) throw new BuzzShapeError(`\uC608\uC0C1\uACFC \uB2E4\uB978 \uC774\uBCA4\uD2B8 \uBAA8\uC591: ${JSON.stringify(v).slice(0, 200)}`);
33157
+ return e;
33158
+ }
33159
+ function asArray(v, what) {
33160
+ if (!Array.isArray(v)) throw new BuzzShapeError(`${what}: \uBC30\uC5F4\uC774 \uC544\uB2CC \uCD9C\uB825`);
33161
+ return v;
33162
+ }
33163
+ async function listChannels() {
33164
+ const parse3 = (v) => asArray(v, "channels list").map((c) => {
33165
+ const ch = c;
33166
+ if (typeof ch?.channel_id !== "string" || !UUID.test(ch.channel_id) || typeof ch.name !== "string") {
33167
+ throw new BuzzShapeError(`\uC608\uC0C1\uACFC \uB2E4\uB978 \uCC44\uB110 \uBAA8\uC591: ${JSON.stringify(c).slice(0, 200)}`);
33168
+ }
33169
+ return { id: ch.channel_id, name: ch.name };
33170
+ });
33171
+ const all = parse3(await runCli(["channels", "list", "--member"]));
33172
+ if (all.length === 0) throw new BuzzShapeError("channels list --member: CLI \uC2E0\uC6D0\uC774 \uBA64\uBC84\uC778 \uBC29\uC774 \uD558\uB098\uB3C4 \uC5C6\uC74C");
33173
+ const priv = new Set(parse3(await runCli(["channels", "list", "--member", "--visibility", "private"])).map((c) => c.id));
33174
+ return all.map((c) => ({ ...c, private: priv.has(c.id) }));
33175
+ }
33176
+ function channelVenue(ch) {
33177
+ const name = ch.name.trim();
33178
+ if (!name) return "buzz";
33179
+ return ch.private && name === "DM" ? "buzz:dm" : `buzz:${name}`;
33180
+ }
33181
+ async function getPage(channelId, kinds, before) {
33182
+ const args = ["messages", "get", "--channel", channelId, "--limit", String(PAGE), "--kinds", kinds.join(",")];
33183
+ if (before !== void 0) args.push("--before", String(before));
33184
+ return asArray(await runCli(args), "messages get").map(asEvent);
33185
+ }
33186
+ async function knownIds(eventIds) {
33187
+ if (eventIds.length === 0) return /* @__PURE__ */ new Set();
33188
+ const r = await db.query(
33189
+ `SELECT external_uuid FROM memory WHERE external_uuid = ANY($1::text[])`,
33190
+ [eventIds.map((id) => `buzz:${id}`)]
33191
+ );
33192
+ return new Set(r.rows.map((row) => String(row.external_uuid).slice("buzz:".length)));
33193
+ }
33194
+ async function pageBack(channelId, kinds, visit) {
33195
+ const seen = /* @__PURE__ */ new Set();
33196
+ let before;
33197
+ for (let pages = 0; ; pages++) {
33198
+ if (pages >= MAX_PAGES_PER_CHANNEL) {
33199
+ throw new BuzzShapeError(`${channelId}: ${MAX_PAGES_PER_CHANNEL}\uCABD\uC744 \uB118\uACA8\uB3C4 \uB05D\uB098\uC9C0 \uC54A\uC74C`);
33200
+ }
33201
+ const page = await getPage(channelId, kinds, before);
33202
+ if (page.length === 0) {
33203
+ if (before === void 0) return;
33204
+ throw new BuzzShapeError(`${channelId}: \uC774\uC5B4\uC9C0\uB294 \uCABD\uC774 \uBE44\uC5B4 \uC788\uC74C`);
33205
+ }
33206
+ const unseen = page.filter((e) => !seen.has(e.id));
33207
+ if (unseen.length === 0) {
33208
+ if (page.length >= PAGE) throw new BuzzShapeError(`${channelId}: \uCABD \uB118\uAE40\uC774 \uC55E\uC73C\uB85C \uAC00\uC9C0 \uC54A\uC74C`);
33209
+ return;
33210
+ }
33211
+ for (const e of unseen) seen.add(e.id);
33212
+ if (!await visit(page, unseen)) return;
33213
+ before = Math.min(...page.map((e) => e.created_at)) + 1;
33214
+ }
33215
+ }
33216
+ async function newChatEvents(channelId) {
33217
+ const fresh = /* @__PURE__ */ new Map();
33218
+ const nowSec = Math.floor(Date.now() / 1e3);
33219
+ let stopBelow;
33220
+ await pageBack(channelId, [KIND_CHAT], async (page, unseen) => {
33221
+ const chats = unseen.filter((e) => e.kind === KIND_CHAT);
33222
+ const known = await knownIds(chats.map((e) => e.id));
33223
+ for (const e of chats) if (!known.has(e.id)) fresh.set(e.id, e);
33224
+ if (known.size > 0 && stopBelow === void 0) {
33225
+ const newestKnown = Math.max(...chats.filter((e) => known.has(e.id)).map((e) => e.created_at));
33226
+ stopBelow = Math.min(newestKnown, nowSec) - LOOKBACK_SEC;
33227
+ }
33228
+ return !(stopBelow !== void 0 && Math.min(...page.map((e) => e.created_at)) < stopBelow);
33229
+ });
33230
+ return [...fresh.values()];
33231
+ }
33232
+ async function recentEvents(channelId, kinds, floor) {
33233
+ const out = [];
33234
+ await pageBack(channelId, kinds, async (page, unseen) => {
33235
+ out.push(...unseen.filter((e) => kinds.includes(e.kind)));
33236
+ return floor !== void 0 && Math.min(...page.map((e) => e.created_at)) >= floor;
33237
+ });
33238
+ return out;
33239
+ }
33240
+ async function displayNames(pubkeys) {
33241
+ const self2 = asArray(await runCli(["users", "get"]), "users get").map((u) => u?.pubkey).find((pk) => typeof pk === "string" && HEX64.test(pk));
33242
+ if (!self2) {
33243
+ 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)");
33244
+ }
33245
+ const names = /* @__PURE__ */ new Map();
33246
+ for (let i = 0; i < pubkeys.length; i += 20) {
33247
+ const chunk = pubkeys.slice(i, i + 20);
33248
+ const ask = chunk.includes(self2) ? chunk : [...chunk, self2];
33249
+ const out = asArray(await runCli(["users", "get", ...ask.flatMap((pk) => ["--pubkey", pk])]), "users get");
33250
+ const profiles = out.map((u) => u);
33251
+ if (!profiles.some((p) => p?.pubkey === self2)) {
33252
+ throw new BuzzShapeError("users get: \uC790\uAE30 \uD504\uB85C\uD544\uC774 \uBE60\uC9C4 \uC751\uB2F5 \u2014 \uC774\uB984\uC744 \uBBFF\uC744 \uC218 \uC5C6\uC74C");
33253
+ }
33254
+ for (const p of profiles) {
33255
+ const label = [p?.display_name, p?.name].find((v) => typeof v === "string" && v.trim() !== "");
33256
+ if (typeof p?.pubkey === "string" && label) names.set(p.pubkey, label.trim());
33257
+ }
33258
+ }
33259
+ return names;
33260
+ }
33261
+ function ownerPubkeys() {
33262
+ const raw = process.env.BUZZ_INGEST_OWNER ?? "";
33263
+ const keys = raw.split(",").map((s) => s.trim().toLowerCase()).filter(Boolean);
33264
+ if (keys.length === 0 || !keys.every((k) => HEX64.test(k))) {
33265
+ throw new Error(
33266
+ "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"
33267
+ );
33268
+ }
33269
+ return new Set(keys);
33270
+ }
33271
+ function authorLabel(pubkey, names) {
33272
+ return names.get(pubkey) ?? pubkey.slice(0, 8);
33273
+ }
33274
+ function chatMessage(e, names) {
33275
+ return `[${authorLabel(e.pubkey, names)}] ${e.content}`;
33276
+ }
33277
+ function targetOf(e) {
33278
+ const t = e.tags.find((tag) => tag[0] === "e" && HEX64.test(tag[1] ?? ""));
33279
+ return t ? t[1] : null;
33280
+ }
33281
+ function latestEdits(events) {
33282
+ const latest = /* @__PURE__ */ new Map();
33283
+ for (const e of events) {
33284
+ if (e.kind !== KIND_EDIT) continue;
33285
+ const target = targetOf(e);
33286
+ if (!target) continue;
33287
+ const cur = latest.get(target);
33288
+ if (!cur || e.created_at > cur.created_at || e.created_at === cur.created_at && e.id > cur.id) {
33289
+ latest.set(target, e);
33290
+ }
33291
+ }
33292
+ return latest;
33293
+ }
33294
+ async function applyEdits(edits, dryRun) {
33295
+ if (edits.size === 0) return 0;
33296
+ const r = await db.query(
33297
+ `SELECT external_uuid, message, raw_message FROM memory
33298
+ WHERE external_uuid = ANY($1::text[]) AND agent_platform = 'buzz' AND raw_message IS NOT NULL`,
33299
+ [[...edits.keys()].map((id) => `buzz:${id}`)]
33300
+ );
33301
+ let n = 0;
33302
+ for (const row of r.rows) {
33303
+ const edit = edits.get(String(row.external_uuid).slice("buzz:".length));
33304
+ const raw = row.raw_message;
33305
+ const msg = row.message;
33306
+ if (raw === edit.content || !msg.endsWith(raw)) continue;
33307
+ n++;
33308
+ if (dryRun) continue;
33309
+ await db.query(
33310
+ `UPDATE memory
33311
+ SET message = $2, raw_message = $3,
33312
+ tag_processed = FALSE, embedding = NULL, cold_error = NULL
33313
+ WHERE external_uuid = $1`,
33314
+ [row.external_uuid, msg.slice(0, msg.length - raw.length) + edit.content, edit.content]
33315
+ );
33316
+ }
33317
+ return n;
33318
+ }
33319
+ async function hideDeleted(targets, dryRun) {
33320
+ if (targets.length === 0) return 0;
33321
+ const uuids = targets.map((id) => `buzz:${id}`);
33322
+ const sql = dryRun ? `SELECT count(*)::int AS n FROM memory
33323
+ WHERE external_uuid = ANY($1::text[]) AND agent_platform = 'buzz' AND is_active AND NOT is_pinned` : `WITH h AS (
33324
+ UPDATE memory SET is_active = FALSE, archived_at = NOW()
33325
+ WHERE external_uuid = ANY($1::text[]) AND agent_platform = 'buzz' AND is_active AND NOT is_pinned
33326
+ RETURNING 1)
33327
+ SELECT count(*)::int AS n FROM h`;
33328
+ const r = await db.query(sql, [uuids]);
33329
+ return Number(r.rows[0]?.n ?? 0);
33330
+ }
33331
+ async function runBuzzIngest(opts = {}) {
33332
+ const dryRun = opts.dryRun ?? false;
33333
+ const max = opts.max ?? 20;
33334
+ const owner = ownerPubkeys();
33335
+ const channels = await listChannels();
33336
+ const pending = [];
33337
+ for (const ch of channels) {
33338
+ const venue = channelVenue(ch);
33339
+ for (const e of await newChatEvents(ch.id)) pending.push({ e, ch, venue });
33340
+ }
33341
+ 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));
33342
+ const deleteTargets = /* @__PURE__ */ new Set();
33343
+ const edits = [];
33344
+ let unsupported = 0;
33345
+ for (const ch of channels) {
33346
+ for (const d of await getPage(ch.id, DELETE_KINDS)) {
33347
+ const t = DELETE_KINDS.includes(d.kind) ? targetOf(d) : null;
33348
+ if (t) deleteTargets.add(t);
33349
+ }
33350
+ }
33351
+ const batch = pending.filter(({ e }) => !deleteTargets.has(e.id)).slice(0, Math.max(0, max));
33352
+ for (const ch of channels) {
33353
+ const mine = batch.filter((b) => b.ch.id === ch.id).map((b) => b.e.created_at);
33354
+ edits.push(...await recentEvents(ch.id, [KIND_EDIT], mine.length ? Math.min(...mine) - LOOKBACK_SEC : void 0));
33355
+ if (dryRun) unsupported += (await getPage(ch.id, UNSUPPORTED_KINDS)).filter((e) => UNSUPPORTED_KINDS.includes(e.kind)).length;
33356
+ }
33357
+ const pubkeys = [...new Set(batch.map(({ e }) => e.pubkey))];
33358
+ const names = pubkeys.length ? await displayNames(pubkeys) : /* @__PURE__ */ new Map();
33359
+ const report = {
33360
+ channels: channels.length,
33361
+ pending: pending.length,
33362
+ inserted: 0,
33363
+ edited: 0,
33364
+ hidden: 0,
33365
+ byVenue: {},
33366
+ ...dryRun ? { unsupported } : {}
33367
+ };
33368
+ const userId = dryRun ? 0 : await getDefaultUserId();
33369
+ for (const { e, venue } of batch) {
33370
+ report.byVenue[venue] = (report.byVenue[venue] ?? 0) + 1;
33371
+ if (dryRun) {
33372
+ report.inserted++;
33373
+ continue;
33374
+ }
33375
+ let res;
33376
+ try {
33377
+ res = await insertRawMemory({
33378
+ user_id: userId,
33379
+ agent_platform: "buzz",
33380
+ agent_model: null,
33381
+ role: owner.has(e.pubkey) ? "user" : "assistant",
33382
+ message: chatMessage(e, names),
33383
+ raw_message: e.content,
33384
+ external_uuid: `buzz:${e.id}`,
33385
+ device_name: null,
33386
+ venue,
33387
+ created_at: new Date(e.created_at * 1e3)
33388
+ });
33389
+ } catch (err3) {
33390
+ throw new Error(`buzz:${e.id} \uC800\uC7A5 \uC2E4\uD328: ${err3 instanceof Error ? err3.message : String(err3)}`);
33391
+ }
33392
+ if (res.inserted) report.inserted++;
33393
+ }
33394
+ report.edited = await applyEdits(latestEdits(edits), dryRun);
33395
+ report.hidden = await hideDeleted([...deleteTargets], dryRun);
33396
+ return report;
33397
+ }
33398
+ function parseIngestArgs(argv) {
33399
+ const opts = {};
33400
+ for (let i = 0; i < argv.length; i++) {
33401
+ const a = argv[i];
33402
+ if (a === "--dry-run") opts.dryRun = true;
33403
+ else if (a === "--max") {
33404
+ const v = argv[++i];
33405
+ if (v === void 0 || !/^\d+$/.test(v)) throw new Error("--max: 0 \uC774\uC0C1\uC758 \uC815\uC218");
33406
+ opts.max = Number(v);
33407
+ } else throw new Error(`buzz-ingest: \uBAA8\uB974\uB294 \uC635\uC158 ${a}`);
33408
+ }
33409
+ return opts;
33410
+ }
33411
+ var KIND_CHAT, KIND_EDIT, DELETE_KINDS, UNSUPPORTED_KINDS, PAGE, MAX_PAGES_PER_CHANNEL, LOOKBACK_SEC, HEX64, UUID, CHILD_ENV, BuzzShapeError;
33412
+ var init_buzz_ingest = __esm({
33413
+ "src/auto_save/buzz_ingest.ts"() {
33414
+ "use strict";
33415
+ init_db();
33416
+ init_hot_path();
33417
+ init_users();
33418
+ KIND_CHAT = 9;
33419
+ KIND_EDIT = 40003;
33420
+ DELETE_KINDS = [5, 9005];
33421
+ UNSUPPORTED_KINDS = [40002, 40008, 45001, 45003];
33422
+ PAGE = 200;
33423
+ MAX_PAGES_PER_CHANNEL = 500;
33424
+ LOOKBACK_SEC = 48 * 3600;
33425
+ HEX64 = /^[0-9a-f]{64}$/;
33426
+ UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
33427
+ 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
+ BuzzShapeError = class extends Error {
33429
+ };
33430
+ }
33431
+ });
33432
+
32766
33433
  // src/migrations/runner.ts
32767
33434
  var runner_exports = {};
32768
33435
  __export(runner_exports, {
@@ -47030,325 +47697,10 @@ var StdioServerTransport = class {
47030
47697
 
47031
47698
  // src/tools/manage_knowledge.ts
47032
47699
  init_db();
47700
+ init_hot_path();
47701
+ init_users();
47033
47702
  import * as os3 from "node:os";
47034
47703
 
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
47704
  // ../mcp-agents-memory/node_modules/openai/internal/tslib.mjs
47353
47705
  function __classPrivateFieldSet(receiver, state, value, kind, f) {
47354
47706
  if (kind === "m")
@@ -56089,6 +56441,8 @@ async function tagMessage(input) {
56089
56441
  }
56090
56442
 
56091
56443
  // src/auto_save/opencode_capture.ts
56444
+ init_hot_path();
56445
+ init_users();
56092
56446
  import * as fs4 from "node:fs";
56093
56447
  import * as path4 from "node:path";
56094
56448
  import * as os2 from "node:os";
@@ -56651,6 +57005,7 @@ ${args.content}` : args.content;
56651
57005
 
56652
57006
  // src/tools/search_memory.ts
56653
57007
  init_db();
57008
+ init_users();
56654
57009
 
56655
57010
  // src/cold_path/embedder.ts
56656
57011
  var MAX_RETRIES = 3;
@@ -56997,9 +57352,11 @@ date_range \uC778\uC2DD \uD615\uC2DD:
56997
57352
 
56998
57353
  // src/tools/manage_project_tags.ts
56999
57354
  init_db();
57355
+ init_users();
57000
57356
 
57001
57357
  // src/cold_path/project_alias_promoter.ts
57002
57358
  init_db();
57359
+ init_users();
57003
57360
  var QUALIFYING_RELATIONS = /* @__PURE__ */ new Set([
57004
57361
  "rename",
57005
57362
  "alias",
@@ -58315,7 +58672,9 @@ Actions:
58315
58672
  }
58316
58673
 
58317
58674
  // src/briefing.ts
58675
+ init_buzz_envelope();
58318
58676
  init_db();
58677
+ init_users();
58319
58678
  import * as os5 from "node:os";
58320
58679
  var RECENT_CURRENT_LIMIT = 8;
58321
58680
  var RECENT_OTHERS_LIMIT = 4;
@@ -58633,9 +58992,13 @@ agent_platform_filter=false.`,
58633
58992
  }
58634
58993
 
58635
58994
  // src/auto_save/save_message_tool.ts
58995
+ init_hot_path();
58996
+ init_users();
58636
58997
  import * as os10 from "node:os";
58637
58998
 
58638
58999
  // src/auto_save/gemini_capture.ts
59000
+ init_hot_path();
59001
+ init_users();
58639
59002
  import * as fs5 from "node:fs";
58640
59003
  import * as path5 from "node:path";
58641
59004
  import * as os6 from "node:os";
@@ -59020,6 +59383,8 @@ async function captureSessionEnd2() {
59020
59383
  }
59021
59384
 
59022
59385
  // src/auto_save/grok_capture.ts
59386
+ init_hot_path();
59387
+ init_users();
59023
59388
  import * as fs6 from "node:fs";
59024
59389
  import * as path6 from "node:path";
59025
59390
  import * as os7 from "node:os";
@@ -59303,6 +59668,8 @@ function isCaptureArmed3() {
59303
59668
  }
59304
59669
 
59305
59670
  // src/auto_save/antigravity_capture.ts
59671
+ init_hot_path();
59672
+ init_users();
59306
59673
  import * as fs7 from "node:fs";
59307
59674
  import * as path7 from "node:path";
59308
59675
  import * as os8 from "node:os";
@@ -59605,6 +59972,9 @@ function isCaptureArmed4() {
59605
59972
  }
59606
59973
 
59607
59974
  // src/auto_save/hermes_capture.ts
59975
+ init_hot_path();
59976
+ init_venue();
59977
+ init_users();
59608
59978
  import * as fs8 from "node:fs";
59609
59979
  import * as path8 from "node:path";
59610
59980
  import * as os9 from "node:os";
@@ -59975,6 +60345,7 @@ subagent \uCEE8\uD14D\uC2A4\uD2B8\uB77C\uBA74 subagent=true + subagent_model + s
59975
60345
 
59976
60346
  // src/tools/task_pin.ts
59977
60347
  init_db();
60348
+ init_users();
59978
60349
  import * as os11 from "node:os";
59979
60350
  var DEVICE_NAME8 = os11.hostname();
59980
60351
  var STATUSES = ["pending", "in_progress", "done", "failed"];
@@ -60125,9 +60496,11 @@ function registerTools(server) {
60125
60496
 
60126
60497
  // src/cold_path/worker.ts
60127
60498
  init_db();
60499
+ init_buzz_envelope();
60128
60500
 
60129
60501
  // src/cold_path/dtag_promoter.ts
60130
60502
  init_db();
60503
+ init_users();
60131
60504
  function envInt2(name, fallback) {
60132
60505
  const raw = process.env[name];
60133
60506
  if (!raw) return fallback;
@@ -60276,6 +60649,7 @@ async function runDtagPromotion() {
60276
60649
 
60277
60650
  // src/librarian.ts
60278
60651
  init_db();
60652
+ init_users();
60279
60653
 
60280
60654
  // src/context_budget.ts
60281
60655
  var DEFAULT_CTX = 8192;
@@ -60893,6 +61267,9 @@ async function drainColdPath(maxTicks = 12) {
60893
61267
  }
60894
61268
 
60895
61269
  // src/auto_save/jsonl_capture.ts
61270
+ init_hot_path();
61271
+ init_venue();
61272
+ init_users();
60896
61273
  import * as fs9 from "node:fs";
60897
61274
  import * as path9 from "node:path";
60898
61275
  import * as os12 from "node:os";
@@ -61260,6 +61637,8 @@ async function captureSessionEnd6() {
61260
61637
  }
61261
61638
 
61262
61639
  // src/auto_save/codex_capture.ts
61640
+ init_hot_path();
61641
+ init_users();
61263
61642
  import * as fs10 from "node:fs";
61264
61643
  import * as path10 from "node:path";
61265
61644
  import * as os13 from "node:os";
@@ -61756,6 +62135,12 @@ Usage:
61756
62135
  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
62136
  mcp-agents-memory setup Interactive setup \u2014 write config to ~/.config/mcp-agents-memory/.env and run migrations.
61758
62137
  mcp-agents-memory migrate Apply any pending DB migrations against the configured database.
62138
+ mcp-agents-memory buzz-ingest [--dry-run] [--max N]
62139
+ (Buzz only) Copy chat messages from the Buzz rooms the CLI identity is a member of
62140
+ into memory, one row per message, via the official \`buzz\` CLI. Oldest first, at most
62141
+ N per run (default 20). Needs BUZZ_RELAY_URL, BUZZ_PRIVATE_KEY and BUZZ_INGEST_OWNER
62142
+ (the memory owner's pubkeys, comma-separated) in the environment of this command only \u2014
62143
+ not in the shared .env; optional BUZZ_CLI.
61759
62144
  mcp-agents-memory help Show this message.
61760
62145
 
61761
62146
  Configuration is loaded from (first hit wins):
@@ -61848,6 +62233,15 @@ async function cli() {
61848
62233
  await runSetupWizard2();
61849
62234
  process.exit(0);
61850
62235
  }
62236
+ if (cmd === "buzz-ingest") {
62237
+ const { runBuzzIngest: runBuzzIngest2, parseIngestArgs: parseIngestArgs2 } = await Promise.resolve().then(() => (init_buzz_ingest(), buzz_ingest_exports));
62238
+ const opts = parseIngestArgs2(process.argv.slice(3));
62239
+ await db.connect();
62240
+ const report = await runBuzzIngest2(opts);
62241
+ console.log(JSON.stringify({ dryRun: opts.dryRun ?? false, ...report }));
62242
+ await db.close();
62243
+ process.exit(0);
62244
+ }
61851
62245
  if (cmd === "migrate") {
61852
62246
  const { runAllMigrations: runAllMigrations2 } = await Promise.resolve().then(() => (init_runner(), runner_exports));
61853
62247
  await runAllMigrations2();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mcp-agents-memory",
3
- "version": "0.9.19",
3
+ "version": "0.9.20",
4
4
  "description": "Long-term memory for AI agents with provenance tracking",
5
5
  "main": "build/index.js",
6
6
  "type": "module",