mcp-agents-memory 0.9.5 → 0.9.10

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/build/index.js CHANGED
@@ -1655,11 +1655,11 @@ var require_pg_connection_string = __commonJS({
1655
1655
  config2.client_encoding = result.searchParams.get("encoding");
1656
1656
  return config2;
1657
1657
  }
1658
- const hostname8 = dummyHost ? "" : result.hostname;
1658
+ const hostname10 = dummyHost ? "" : result.hostname;
1659
1659
  if (!config2.host) {
1660
- config2.host = decodeURIComponent(hostname8);
1661
- } else if (hostname8 && /^%2f/i.test(hostname8)) {
1662
- result.pathname = hostname8 + result.pathname;
1660
+ config2.host = decodeURIComponent(hostname10);
1661
+ } else if (hostname10 && /^%2f/i.test(hostname10)) {
1662
+ result.pathname = hostname10 + result.pathname;
1663
1663
  }
1664
1664
  if (!config2.port) {
1665
1665
  config2.port = result.port;
@@ -1675,15 +1675,15 @@ var require_pg_connection_string = __commonJS({
1675
1675
  if (config2.sslcert || config2.sslkey || config2.sslrootcert || config2.sslmode) {
1676
1676
  config2.ssl = {};
1677
1677
  }
1678
- const fs8 = config2.sslcert || config2.sslkey || config2.sslrootcert ? __require("fs") : null;
1678
+ const fs10 = config2.sslcert || config2.sslkey || config2.sslrootcert ? __require("fs") : null;
1679
1679
  if (config2.sslcert) {
1680
- config2.ssl.cert = fs8.readFileSync(config2.sslcert).toString();
1680
+ config2.ssl.cert = fs10.readFileSync(config2.sslcert).toString();
1681
1681
  }
1682
1682
  if (config2.sslkey) {
1683
- config2.ssl.key = fs8.readFileSync(config2.sslkey).toString();
1683
+ config2.ssl.key = fs10.readFileSync(config2.sslkey).toString();
1684
1684
  }
1685
1685
  if (config2.sslrootcert) {
1686
- config2.ssl.ca = fs8.readFileSync(config2.sslrootcert).toString();
1686
+ config2.ssl.ca = fs10.readFileSync(config2.sslrootcert).toString();
1687
1687
  }
1688
1688
  if (options.useLibpqCompat && config2.uselibpqcompat) {
1689
1689
  throw new Error("Both useLibpqCompat and uselibpqcompat are set. Please use only one of them.");
@@ -3448,7 +3448,7 @@ var require_split2 = __commonJS({
3448
3448
  var require_helper = __commonJS({
3449
3449
  "node_modules/pgpass/lib/helper.js"(exports, module) {
3450
3450
  "use strict";
3451
- var path8 = __require("path");
3451
+ var path10 = __require("path");
3452
3452
  var Stream2 = __require("stream").Stream;
3453
3453
  var split = require_split2();
3454
3454
  var util2 = __require("util");
@@ -3487,7 +3487,7 @@ var require_helper = __commonJS({
3487
3487
  };
3488
3488
  module.exports.getFileName = function(rawEnv) {
3489
3489
  var env = rawEnv || process.env;
3490
- var file = env.PGPASSFILE || (isWin ? path8.join(env.APPDATA || "./", "postgresql", "pgpass.conf") : path8.join(env.HOME || "./", ".pgpass"));
3490
+ var file = env.PGPASSFILE || (isWin ? path10.join(env.APPDATA || "./", "postgresql", "pgpass.conf") : path10.join(env.HOME || "./", ".pgpass"));
3491
3491
  return file;
3492
3492
  };
3493
3493
  module.exports.usePgPass = function(stats, fname) {
@@ -3619,16 +3619,16 @@ var require_helper = __commonJS({
3619
3619
  var require_lib = __commonJS({
3620
3620
  "node_modules/pgpass/lib/index.js"(exports, module) {
3621
3621
  "use strict";
3622
- var path8 = __require("path");
3623
- var fs8 = __require("fs");
3622
+ var path10 = __require("path");
3623
+ var fs10 = __require("fs");
3624
3624
  var helper = require_helper();
3625
3625
  module.exports = function(connInfo, cb) {
3626
3626
  var file = helper.getFileName();
3627
- fs8.stat(file, function(err2, stat) {
3627
+ fs10.stat(file, function(err2, stat) {
3628
3628
  if (err2 || !helper.usePgPass(stat, file)) {
3629
3629
  return cb(void 0);
3630
3630
  }
3631
- var st = fs8.createReadStream(file);
3631
+ var st = fs10.createReadStream(file);
3632
3632
  helper.getPassword(connInfo, st, cb);
3633
3633
  });
3634
3634
  };
@@ -13224,11 +13224,11 @@ var require_agent = __commonJS({
13224
13224
  };
13225
13225
  })();
13226
13226
  var WINDOWS_PIPE_REGEX = /^[/\\][/\\]\.[/\\]pipe[/\\].+/;
13227
- function createAgent(path8) {
13228
- if (process.platform === "win32" && !WINDOWS_PIPE_REGEX.test(path8)) {
13229
- return path8 === "pageant" ? new PageantAgent() : new CygwinAgent(path8);
13227
+ function createAgent(path10) {
13228
+ if (process.platform === "win32" && !WINDOWS_PIPE_REGEX.test(path10)) {
13229
+ return path10 === "pageant" ? new PageantAgent() : new CygwinAgent(path10);
13230
13230
  }
13231
- return new OpenSSHAgent(path8);
13231
+ return new OpenSSHAgent(path10);
13232
13232
  }
13233
13233
  var AgentProtocol = (() => {
13234
13234
  const SSH_AGENTC_REQUEST_IDENTITIES = 11;
@@ -17103,7 +17103,7 @@ var require_Protocol = __commonJS({
17103
17103
  sendPacket(this, this._packetRW.write.finalize(packet));
17104
17104
  });
17105
17105
  }
17106
- authHostbased(username, pubKey, hostname8, userlocal, keyAlgo, cbSign) {
17106
+ authHostbased(username, pubKey, hostname10, userlocal, keyAlgo, cbSign) {
17107
17107
  if (this._server)
17108
17108
  throw new Error("Client-only method called in server mode");
17109
17109
  pubKey = parseKey(pubKey);
@@ -17122,7 +17122,7 @@ var require_Protocol = __commonJS({
17122
17122
  const pubKeyLen = pubKey.length;
17123
17123
  const sessionID = this._kex.sessionID;
17124
17124
  const sesLen = sessionID.length;
17125
- const hostnameLen = Buffer.byteLength(hostname8);
17125
+ const hostnameLen = Buffer.byteLength(hostname10);
17126
17126
  const userlocalLen = Buffer.byteLength(userlocal);
17127
17127
  const data = Buffer.allocUnsafe(
17128
17128
  4 + sesLen + 1 + 4 + userLen + 4 + 14 + 4 + 9 + 4 + algoLen + 4 + pubKeyLen + 4 + hostnameLen + 4 + userlocalLen
@@ -17142,7 +17142,7 @@ var require_Protocol = __commonJS({
17142
17142
  writeUInt32BE(data, pubKeyLen, p += algoLen);
17143
17143
  data.set(pubKey, p += 4);
17144
17144
  writeUInt32BE(data, hostnameLen, p += pubKeyLen);
17145
- data.utf8Write(hostname8, p += 4, hostnameLen);
17145
+ data.utf8Write(hostname10, p += 4, hostnameLen);
17146
17146
  writeUInt32BE(data, userlocalLen, p += hostnameLen);
17147
17147
  data.utf8Write(userlocal, p += 4, userlocalLen);
17148
17148
  cbSign(data, (signature) => {
@@ -18203,8 +18203,8 @@ var require_SFTP = __commonJS({
18203
18203
  "node_modules/ssh2/lib/protocol/SFTP.js"(exports, module) {
18204
18204
  "use strict";
18205
18205
  var EventEmitter = __require("events");
18206
- var fs8 = __require("fs");
18207
- var { constants } = fs8;
18206
+ var fs10 = __require("fs");
18207
+ var { constants } = fs10;
18208
18208
  var {
18209
18209
  Readable: ReadableStream2,
18210
18210
  Writable: WritableStream
@@ -18472,17 +18472,17 @@ var require_SFTP = __commonJS({
18472
18472
  // ===========================================================================
18473
18473
  // Client-specific ===========================================================
18474
18474
  // ===========================================================================
18475
- createReadStream(path8, options) {
18475
+ createReadStream(path10, options) {
18476
18476
  if (this.server)
18477
18477
  throw new Error("Client-only method called in server mode");
18478
- return new ReadStream(this, path8, options);
18478
+ return new ReadStream(this, path10, options);
18479
18479
  }
18480
- createWriteStream(path8, options) {
18480
+ createWriteStream(path10, options) {
18481
18481
  if (this.server)
18482
18482
  throw new Error("Client-only method called in server mode");
18483
- return new WriteStream(this, path8, options);
18483
+ return new WriteStream(this, path10, options);
18484
18484
  }
18485
- open(path8, flags_, attrs, cb) {
18485
+ open(path10, flags_, attrs, cb) {
18486
18486
  if (this.server)
18487
18487
  throw new Error("Client-only method called in server mode");
18488
18488
  if (typeof attrs === "function") {
@@ -18501,7 +18501,7 @@ var require_SFTP = __commonJS({
18501
18501
  attrsFlags = attrs.flags;
18502
18502
  attrsLen = attrs.nb;
18503
18503
  }
18504
- const pathLen = Buffer.byteLength(path8);
18504
+ const pathLen = Buffer.byteLength(path10);
18505
18505
  let p = 9;
18506
18506
  const buf = Buffer.allocUnsafe(4 + 1 + 4 + 4 + pathLen + 4 + 4 + attrsLen);
18507
18507
  writeUInt32BE(buf, buf.length - 4, 0);
@@ -18509,7 +18509,7 @@ var require_SFTP = __commonJS({
18509
18509
  const reqid = this._writeReqid = this._writeReqid + 1 & MAX_REQID;
18510
18510
  writeUInt32BE(buf, reqid, 5);
18511
18511
  writeUInt32BE(buf, pathLen, p);
18512
- buf.utf8Write(path8, p += 4, pathLen);
18512
+ buf.utf8Write(path10, p += 4, pathLen);
18513
18513
  writeUInt32BE(buf, flags, p += pathLen);
18514
18514
  writeUInt32BE(buf, attrsFlags, p += 4);
18515
18515
  if (attrsLen) {
@@ -18633,14 +18633,14 @@ var require_SFTP = __commonJS({
18633
18633
  fastGet(remotePath, localPath, opts, cb) {
18634
18634
  if (this.server)
18635
18635
  throw new Error("Client-only method called in server mode");
18636
- fastXfer(this, fs8, remotePath, localPath, opts, cb);
18636
+ fastXfer(this, fs10, remotePath, localPath, opts, cb);
18637
18637
  }
18638
18638
  fastPut(localPath, remotePath, opts, cb) {
18639
18639
  if (this.server)
18640
18640
  throw new Error("Client-only method called in server mode");
18641
- fastXfer(fs8, this, localPath, remotePath, opts, cb);
18641
+ fastXfer(fs10, this, localPath, remotePath, opts, cb);
18642
18642
  }
18643
- readFile(path8, options, callback_) {
18643
+ readFile(path10, options, callback_) {
18644
18644
  if (this.server)
18645
18645
  throw new Error("Client-only method called in server mode");
18646
18646
  let callback;
@@ -18713,13 +18713,13 @@ var require_SFTP = __commonJS({
18713
18713
  return callback && callback(er, buffer);
18714
18714
  });
18715
18715
  };
18716
- this.open(path8, flag, 438, (er, handle_) => {
18716
+ this.open(path10, flag, 438, (er, handle_) => {
18717
18717
  if (er)
18718
18718
  return callback && callback(er);
18719
18719
  handle = handle_;
18720
18720
  const tryStat = (er2, st) => {
18721
18721
  if (er2) {
18722
- this.stat(path8, (er_, st_) => {
18722
+ this.stat(path10, (er_, st_) => {
18723
18723
  if (er_) {
18724
18724
  return this.close(handle, () => {
18725
18725
  callback && callback(er2);
@@ -18740,7 +18740,7 @@ var require_SFTP = __commonJS({
18740
18740
  this.fstat(handle, tryStat);
18741
18741
  });
18742
18742
  }
18743
- writeFile(path8, data, options, callback_) {
18743
+ writeFile(path10, data, options, callback_) {
18744
18744
  if (this.server)
18745
18745
  throw new Error("Client-only method called in server mode");
18746
18746
  let callback;
@@ -18759,7 +18759,7 @@ var require_SFTP = __commonJS({
18759
18759
  if (options.encoding && !Buffer.isEncoding(options.encoding))
18760
18760
  throw new Error(`Unknown encoding: ${options.encoding}`);
18761
18761
  const flag = options.flag || "w";
18762
- this.open(path8, flag, options.mode, (openErr, handle) => {
18762
+ this.open(path10, flag, options.mode, (openErr, handle) => {
18763
18763
  if (openErr) {
18764
18764
  callback && callback(openErr);
18765
18765
  } else {
@@ -18768,7 +18768,7 @@ var require_SFTP = __commonJS({
18768
18768
  if (position === null) {
18769
18769
  const tryStat = (er, st) => {
18770
18770
  if (er) {
18771
- this.stat(path8, (er_, st_) => {
18771
+ this.stat(path10, (er_, st_) => {
18772
18772
  if (er_) {
18773
18773
  return this.close(handle, () => {
18774
18774
  callback && callback(er);
@@ -18787,7 +18787,7 @@ var require_SFTP = __commonJS({
18787
18787
  }
18788
18788
  });
18789
18789
  }
18790
- appendFile(path8, data, options, callback_) {
18790
+ appendFile(path10, data, options, callback_) {
18791
18791
  if (this.server)
18792
18792
  throw new Error("Client-only method called in server mode");
18793
18793
  let callback;
@@ -18805,12 +18805,12 @@ var require_SFTP = __commonJS({
18805
18805
  throw new TypeError("Bad arguments");
18806
18806
  if (!options.flag)
18807
18807
  options = Object.assign({ flag: "a" }, options);
18808
- this.writeFile(path8, data, options, callback);
18808
+ this.writeFile(path10, data, options, callback);
18809
18809
  }
18810
- exists(path8, cb) {
18810
+ exists(path10, cb) {
18811
18811
  if (this.server)
18812
18812
  throw new Error("Client-only method called in server mode");
18813
- this.stat(path8, (err2) => {
18813
+ this.stat(path10, (err2) => {
18814
18814
  cb && cb(err2 ? false : true);
18815
18815
  });
18816
18816
  }
@@ -18853,7 +18853,7 @@ var require_SFTP = __commonJS({
18853
18853
  `SFTP: Outbound: ${isBuffered ? "Buffered" : "Sending"} RENAME`
18854
18854
  );
18855
18855
  }
18856
- mkdir(path8, attrs, cb) {
18856
+ mkdir(path10, attrs, cb) {
18857
18857
  if (this.server)
18858
18858
  throw new Error("Client-only method called in server mode");
18859
18859
  let flags = 0;
@@ -18867,7 +18867,7 @@ var require_SFTP = __commonJS({
18867
18867
  flags = attrs.flags;
18868
18868
  attrsLen = attrs.nb;
18869
18869
  }
18870
- const pathLen = Buffer.byteLength(path8);
18870
+ const pathLen = Buffer.byteLength(path10);
18871
18871
  let p = 9;
18872
18872
  const buf = Buffer.allocUnsafe(4 + 1 + 4 + 4 + pathLen + 4 + attrsLen);
18873
18873
  writeUInt32BE(buf, buf.length - 4, 0);
@@ -18875,7 +18875,7 @@ var require_SFTP = __commonJS({
18875
18875
  const reqid = this._writeReqid = this._writeReqid + 1 & MAX_REQID;
18876
18876
  writeUInt32BE(buf, reqid, 5);
18877
18877
  writeUInt32BE(buf, pathLen, p);
18878
- buf.utf8Write(path8, p += 4, pathLen);
18878
+ buf.utf8Write(path10, p += 4, pathLen);
18879
18879
  writeUInt32BE(buf, flags, p += pathLen);
18880
18880
  if (attrsLen) {
18881
18881
  p += 4;
@@ -18891,10 +18891,10 @@ var require_SFTP = __commonJS({
18891
18891
  `SFTP: Outbound: ${isBuffered ? "Buffered" : "Sending"} MKDIR`
18892
18892
  );
18893
18893
  }
18894
- rmdir(path8, cb) {
18894
+ rmdir(path10, cb) {
18895
18895
  if (this.server)
18896
18896
  throw new Error("Client-only method called in server mode");
18897
- const pathLen = Buffer.byteLength(path8);
18897
+ const pathLen = Buffer.byteLength(path10);
18898
18898
  let p = 9;
18899
18899
  const buf = Buffer.allocUnsafe(4 + 1 + 4 + 4 + pathLen);
18900
18900
  writeUInt32BE(buf, buf.length - 4, 0);
@@ -18902,7 +18902,7 @@ var require_SFTP = __commonJS({
18902
18902
  const reqid = this._writeReqid = this._writeReqid + 1 & MAX_REQID;
18903
18903
  writeUInt32BE(buf, reqid, 5);
18904
18904
  writeUInt32BE(buf, pathLen, p);
18905
- buf.utf8Write(path8, p += 4, pathLen);
18905
+ buf.utf8Write(path10, p += 4, pathLen);
18906
18906
  this._requests[reqid] = { cb };
18907
18907
  const isBuffered = sendOrBuffer(this, buf);
18908
18908
  this._debug && this._debug(
@@ -18992,10 +18992,10 @@ var require_SFTP = __commonJS({
18992
18992
  `SFTP: Outbound: ${isBuffered ? "Buffered" : "Sending"} FSTAT`
18993
18993
  );
18994
18994
  }
18995
- stat(path8, cb) {
18995
+ stat(path10, cb) {
18996
18996
  if (this.server)
18997
18997
  throw new Error("Client-only method called in server mode");
18998
- const pathLen = Buffer.byteLength(path8);
18998
+ const pathLen = Buffer.byteLength(path10);
18999
18999
  let p = 9;
19000
19000
  const buf = Buffer.allocUnsafe(4 + 1 + 4 + 4 + pathLen);
19001
19001
  writeUInt32BE(buf, buf.length - 4, 0);
@@ -19003,17 +19003,17 @@ var require_SFTP = __commonJS({
19003
19003
  const reqid = this._writeReqid = this._writeReqid + 1 & MAX_REQID;
19004
19004
  writeUInt32BE(buf, reqid, 5);
19005
19005
  writeUInt32BE(buf, pathLen, p);
19006
- buf.utf8Write(path8, p += 4, pathLen);
19006
+ buf.utf8Write(path10, p += 4, pathLen);
19007
19007
  this._requests[reqid] = { cb };
19008
19008
  const isBuffered = sendOrBuffer(this, buf);
19009
19009
  this._debug && this._debug(
19010
19010
  `SFTP: Outbound: ${isBuffered ? "Buffered" : "Sending"} STAT`
19011
19011
  );
19012
19012
  }
19013
- lstat(path8, cb) {
19013
+ lstat(path10, cb) {
19014
19014
  if (this.server)
19015
19015
  throw new Error("Client-only method called in server mode");
19016
- const pathLen = Buffer.byteLength(path8);
19016
+ const pathLen = Buffer.byteLength(path10);
19017
19017
  let p = 9;
19018
19018
  const buf = Buffer.allocUnsafe(4 + 1 + 4 + 4 + pathLen);
19019
19019
  writeUInt32BE(buf, buf.length - 4, 0);
@@ -19021,17 +19021,17 @@ var require_SFTP = __commonJS({
19021
19021
  const reqid = this._writeReqid = this._writeReqid + 1 & MAX_REQID;
19022
19022
  writeUInt32BE(buf, reqid, 5);
19023
19023
  writeUInt32BE(buf, pathLen, p);
19024
- buf.utf8Write(path8, p += 4, pathLen);
19024
+ buf.utf8Write(path10, p += 4, pathLen);
19025
19025
  this._requests[reqid] = { cb };
19026
19026
  const isBuffered = sendOrBuffer(this, buf);
19027
19027
  this._debug && this._debug(
19028
19028
  `SFTP: Outbound: ${isBuffered ? "Buffered" : "Sending"} LSTAT`
19029
19029
  );
19030
19030
  }
19031
- opendir(path8, cb) {
19031
+ opendir(path10, cb) {
19032
19032
  if (this.server)
19033
19033
  throw new Error("Client-only method called in server mode");
19034
- const pathLen = Buffer.byteLength(path8);
19034
+ const pathLen = Buffer.byteLength(path10);
19035
19035
  let p = 9;
19036
19036
  const buf = Buffer.allocUnsafe(4 + 1 + 4 + 4 + pathLen);
19037
19037
  writeUInt32BE(buf, buf.length - 4, 0);
@@ -19039,14 +19039,14 @@ var require_SFTP = __commonJS({
19039
19039
  const reqid = this._writeReqid = this._writeReqid + 1 & MAX_REQID;
19040
19040
  writeUInt32BE(buf, reqid, 5);
19041
19041
  writeUInt32BE(buf, pathLen, p);
19042
- buf.utf8Write(path8, p += 4, pathLen);
19042
+ buf.utf8Write(path10, p += 4, pathLen);
19043
19043
  this._requests[reqid] = { cb };
19044
19044
  const isBuffered = sendOrBuffer(this, buf);
19045
19045
  this._debug && this._debug(
19046
19046
  `SFTP: Outbound: ${isBuffered ? "Buffered" : "Sending"} OPENDIR`
19047
19047
  );
19048
19048
  }
19049
- setstat(path8, attrs, cb) {
19049
+ setstat(path10, attrs, cb) {
19050
19050
  if (this.server)
19051
19051
  throw new Error("Client-only method called in server mode");
19052
19052
  let flags = 0;
@@ -19058,7 +19058,7 @@ var require_SFTP = __commonJS({
19058
19058
  } else if (typeof attrs === "function") {
19059
19059
  cb = attrs;
19060
19060
  }
19061
- const pathLen = Buffer.byteLength(path8);
19061
+ const pathLen = Buffer.byteLength(path10);
19062
19062
  let p = 9;
19063
19063
  const buf = Buffer.allocUnsafe(4 + 1 + 4 + 4 + pathLen + 4 + attrsLen);
19064
19064
  writeUInt32BE(buf, buf.length - 4, 0);
@@ -19066,7 +19066,7 @@ var require_SFTP = __commonJS({
19066
19066
  const reqid = this._writeReqid = this._writeReqid + 1 & MAX_REQID;
19067
19067
  writeUInt32BE(buf, reqid, 5);
19068
19068
  writeUInt32BE(buf, pathLen, p);
19069
- buf.utf8Write(path8, p += 4, pathLen);
19069
+ buf.utf8Write(path10, p += 4, pathLen);
19070
19070
  writeUInt32BE(buf, flags, p += pathLen);
19071
19071
  if (attrsLen) {
19072
19072
  p += 4;
@@ -19126,8 +19126,8 @@ var require_SFTP = __commonJS({
19126
19126
  mtime: toUnixTimestamp(mtime)
19127
19127
  }, cb);
19128
19128
  }
19129
- utimes(path8, atime, mtime, cb) {
19130
- return this.setstat(path8, {
19129
+ utimes(path10, atime, mtime, cb) {
19130
+ return this.setstat(path10, {
19131
19131
  atime: toUnixTimestamp(atime),
19132
19132
  mtime: toUnixTimestamp(mtime)
19133
19133
  }, cb);
@@ -19138,8 +19138,8 @@ var require_SFTP = __commonJS({
19138
19138
  gid
19139
19139
  }, cb);
19140
19140
  }
19141
- chown(path8, uid, gid, cb) {
19142
- return this.setstat(path8, {
19141
+ chown(path10, uid, gid, cb) {
19142
+ return this.setstat(path10, {
19143
19143
  uid,
19144
19144
  gid
19145
19145
  }, cb);
@@ -19149,15 +19149,15 @@ var require_SFTP = __commonJS({
19149
19149
  mode
19150
19150
  }, cb);
19151
19151
  }
19152
- chmod(path8, mode, cb) {
19153
- return this.setstat(path8, {
19152
+ chmod(path10, mode, cb) {
19153
+ return this.setstat(path10, {
19154
19154
  mode
19155
19155
  }, cb);
19156
19156
  }
19157
- readlink(path8, cb) {
19157
+ readlink(path10, cb) {
19158
19158
  if (this.server)
19159
19159
  throw new Error("Client-only method called in server mode");
19160
- const pathLen = Buffer.byteLength(path8);
19160
+ const pathLen = Buffer.byteLength(path10);
19161
19161
  let p = 9;
19162
19162
  const buf = Buffer.allocUnsafe(4 + 1 + 4 + 4 + pathLen);
19163
19163
  writeUInt32BE(buf, buf.length - 4, 0);
@@ -19165,7 +19165,7 @@ var require_SFTP = __commonJS({
19165
19165
  const reqid = this._writeReqid = this._writeReqid + 1 & MAX_REQID;
19166
19166
  writeUInt32BE(buf, reqid, 5);
19167
19167
  writeUInt32BE(buf, pathLen, p);
19168
- buf.utf8Write(path8, p += 4, pathLen);
19168
+ buf.utf8Write(path10, p += 4, pathLen);
19169
19169
  this._requests[reqid] = {
19170
19170
  cb: (err2, names) => {
19171
19171
  if (typeof cb !== "function")
@@ -19210,10 +19210,10 @@ var require_SFTP = __commonJS({
19210
19210
  `SFTP: Outbound: ${isBuffered ? "Buffered" : "Sending"} SYMLINK`
19211
19211
  );
19212
19212
  }
19213
- realpath(path8, cb) {
19213
+ realpath(path10, cb) {
19214
19214
  if (this.server)
19215
19215
  throw new Error("Client-only method called in server mode");
19216
- const pathLen = Buffer.byteLength(path8);
19216
+ const pathLen = Buffer.byteLength(path10);
19217
19217
  let p = 9;
19218
19218
  const buf = Buffer.allocUnsafe(4 + 1 + 4 + 4 + pathLen);
19219
19219
  writeUInt32BE(buf, buf.length - 4, 0);
@@ -19221,7 +19221,7 @@ var require_SFTP = __commonJS({
19221
19221
  const reqid = this._writeReqid = this._writeReqid + 1 & MAX_REQID;
19222
19222
  writeUInt32BE(buf, reqid, 5);
19223
19223
  writeUInt32BE(buf, pathLen, p);
19224
- buf.utf8Write(path8, p += 4, pathLen);
19224
+ buf.utf8Write(path10, p += 4, pathLen);
19225
19225
  this._requests[reqid] = {
19226
19226
  cb: (err2, names) => {
19227
19227
  if (typeof cb !== "function")
@@ -19266,13 +19266,13 @@ var require_SFTP = __commonJS({
19266
19266
  this._debug(`SFTP: Outbound: ${which} posix-rename@openssh.com`);
19267
19267
  }
19268
19268
  }
19269
- ext_openssh_statvfs(path8, cb) {
19269
+ ext_openssh_statvfs(path10, cb) {
19270
19270
  if (this.server)
19271
19271
  throw new Error("Client-only method called in server mode");
19272
19272
  const ext = this._extensions["statvfs@openssh.com"];
19273
19273
  if (!ext || ext !== "2")
19274
19274
  throw new Error("Server does not support this extended request");
19275
- const pathLen = Buffer.byteLength(path8);
19275
+ const pathLen = Buffer.byteLength(path10);
19276
19276
  let p = 9;
19277
19277
  const buf = Buffer.allocUnsafe(4 + 1 + 4 + 4 + 19 + 4 + pathLen);
19278
19278
  writeUInt32BE(buf, buf.length - 4, 0);
@@ -19282,7 +19282,7 @@ var require_SFTP = __commonJS({
19282
19282
  writeUInt32BE(buf, 19, p);
19283
19283
  buf.utf8Write("statvfs@openssh.com", p += 4, 19);
19284
19284
  writeUInt32BE(buf, pathLen, p += 19);
19285
- buf.utf8Write(path8, p += 4, pathLen);
19285
+ buf.utf8Write(path10, p += 4, pathLen);
19286
19286
  this._requests[reqid] = { extended: "statvfs@openssh.com", cb };
19287
19287
  const isBuffered = sendOrBuffer(this, buf);
19288
19288
  if (this._debug) {
@@ -19368,7 +19368,7 @@ var require_SFTP = __commonJS({
19368
19368
  `SFTP: Outbound: ${isBuffered ? "Buffered" : "Sending"} fsync@openssh.com`
19369
19369
  );
19370
19370
  }
19371
- ext_openssh_lsetstat(path8, attrs, cb) {
19371
+ ext_openssh_lsetstat(path10, attrs, cb) {
19372
19372
  if (this.server)
19373
19373
  throw new Error("Client-only method called in server mode");
19374
19374
  const ext = this._extensions["lsetstat@openssh.com"];
@@ -19383,7 +19383,7 @@ var require_SFTP = __commonJS({
19383
19383
  } else if (typeof attrs === "function") {
19384
19384
  cb = attrs;
19385
19385
  }
19386
- const pathLen = Buffer.byteLength(path8);
19386
+ const pathLen = Buffer.byteLength(path10);
19387
19387
  let p = 9;
19388
19388
  const buf = Buffer.allocUnsafe(4 + 1 + 4 + 4 + 20 + 4 + pathLen + 4 + attrsLen);
19389
19389
  writeUInt32BE(buf, buf.length - 4, 0);
@@ -19393,7 +19393,7 @@ var require_SFTP = __commonJS({
19393
19393
  writeUInt32BE(buf, 20, p);
19394
19394
  buf.utf8Write("lsetstat@openssh.com", p += 4, 20);
19395
19395
  writeUInt32BE(buf, pathLen, p += 20);
19396
- buf.utf8Write(path8, p += 4, pathLen);
19396
+ buf.utf8Write(path10, p += 4, pathLen);
19397
19397
  writeUInt32BE(buf, flags, p += pathLen);
19398
19398
  if (attrsLen) {
19399
19399
  p += 4;
@@ -19410,13 +19410,13 @@ var require_SFTP = __commonJS({
19410
19410
  this._debug(`SFTP: Outbound: ${status} lsetstat@openssh.com`);
19411
19411
  }
19412
19412
  }
19413
- ext_openssh_expandPath(path8, cb) {
19413
+ ext_openssh_expandPath(path10, cb) {
19414
19414
  if (this.server)
19415
19415
  throw new Error("Client-only method called in server mode");
19416
19416
  const ext = this._extensions["expand-path@openssh.com"];
19417
19417
  if (ext !== "1")
19418
19418
  throw new Error("Server does not support this extended request");
19419
- const pathLen = Buffer.byteLength(path8);
19419
+ const pathLen = Buffer.byteLength(path10);
19420
19420
  let p = 9;
19421
19421
  const buf = Buffer.allocUnsafe(4 + 1 + 4 + 4 + 23 + 4 + pathLen);
19422
19422
  writeUInt32BE(buf, buf.length - 4, 0);
@@ -19426,7 +19426,7 @@ var require_SFTP = __commonJS({
19426
19426
  writeUInt32BE(buf, 23, p);
19427
19427
  buf.utf8Write("expand-path@openssh.com", p += 4, 23);
19428
19428
  writeUInt32BE(buf, pathLen, p += 20);
19429
- buf.utf8Write(path8, p += 4, pathLen);
19429
+ buf.utf8Write(path10, p += 4, pathLen);
19430
19430
  this._requests[reqid] = {
19431
19431
  cb: (err2, names) => {
19432
19432
  if (typeof cb !== "function")
@@ -19899,13 +19899,13 @@ var require_SFTP = __commonJS({
19899
19899
  if (--left === 0)
19900
19900
  cb(err2);
19901
19901
  };
19902
- if (srcHandle && (src === fs8 || src.outgoing.state === "open"))
19902
+ if (srcHandle && (src === fs10 || src.outgoing.state === "open"))
19903
19903
  ++left;
19904
- if (dstHandle && (dst === fs8 || dst.outgoing.state === "open"))
19904
+ if (dstHandle && (dst === fs10 || dst.outgoing.state === "open"))
19905
19905
  ++left;
19906
- if (srcHandle && (src === fs8 || src.outgoing.state === "open"))
19906
+ if (srcHandle && (src === fs10 || src.outgoing.state === "open"))
19907
19907
  src.close(srcHandle, cbfinal);
19908
- if (dstHandle && (dst === fs8 || dst.outgoing.state === "open"))
19908
+ if (dstHandle && (dst === fs10 || dst.outgoing.state === "open"))
19909
19909
  dst.close(dstHandle, cbfinal);
19910
19910
  } else {
19911
19911
  cb(err2);
@@ -19921,7 +19921,7 @@ var require_SFTP = __commonJS({
19921
19921
  tryStat(null, { size: fileSize });
19922
19922
  function tryStat(err3, attrs) {
19923
19923
  if (err3) {
19924
- if (src !== fs8) {
19924
+ if (src !== fs10) {
19925
19925
  src.stat(srcPath, (err_, attrs_) => {
19926
19926
  if (err_)
19927
19927
  return onerror(err3);
@@ -20704,12 +20704,12 @@ var require_SFTP = __commonJS({
20704
20704
  [REQUEST.LSTAT]: (sftp, payload) => {
20705
20705
  bufferParser.init(payload, 1);
20706
20706
  const reqID = bufferParser.readUInt32BE();
20707
- const path8 = bufferParser.readString(true);
20707
+ const path10 = bufferParser.readString(true);
20708
20708
  bufferParser.clear();
20709
- if (path8 === void 0)
20709
+ if (path10 === void 0)
20710
20710
  return doFatalSFTPError(sftp, "Malformed LSTAT packet");
20711
20711
  sftp._debug && sftp._debug(`SFTP: Inbound: Received LSTAT (id:${reqID})`);
20712
- if (!sftp.emit("LSTAT", reqID, path8)) {
20712
+ if (!sftp.emit("LSTAT", reqID, path10)) {
20713
20713
  sftp.status(reqID, STATUS_CODE.OP_UNSUPPORTED);
20714
20714
  }
20715
20715
  },
@@ -20728,13 +20728,13 @@ var require_SFTP = __commonJS({
20728
20728
  [REQUEST.SETSTAT]: (sftp, payload) => {
20729
20729
  bufferParser.init(payload, 1);
20730
20730
  const reqID = bufferParser.readUInt32BE();
20731
- const path8 = bufferParser.readString(true);
20731
+ const path10 = bufferParser.readString(true);
20732
20732
  const attrs = readAttrs(sftp._biOpt);
20733
20733
  bufferParser.clear();
20734
20734
  if (attrs === void 0)
20735
20735
  return doFatalSFTPError(sftp, "Malformed SETSTAT packet");
20736
20736
  sftp._debug && sftp._debug(`SFTP: Inbound: Received SETSTAT (id:${reqID})`);
20737
- if (!sftp.emit("SETSTAT", reqID, path8, attrs)) {
20737
+ if (!sftp.emit("SETSTAT", reqID, path10, attrs)) {
20738
20738
  sftp.status(reqID, STATUS_CODE.OP_UNSUPPORTED);
20739
20739
  }
20740
20740
  },
@@ -20756,12 +20756,12 @@ var require_SFTP = __commonJS({
20756
20756
  [REQUEST.OPENDIR]: (sftp, payload) => {
20757
20757
  bufferParser.init(payload, 1);
20758
20758
  const reqID = bufferParser.readUInt32BE();
20759
- const path8 = bufferParser.readString(true);
20759
+ const path10 = bufferParser.readString(true);
20760
20760
  bufferParser.clear();
20761
- if (path8 === void 0)
20761
+ if (path10 === void 0)
20762
20762
  return doFatalSFTPError(sftp, "Malformed OPENDIR packet");
20763
20763
  sftp._debug && sftp._debug(`SFTP: Inbound: Received OPENDIR (id:${reqID})`);
20764
- if (!sftp.emit("OPENDIR", reqID, path8)) {
20764
+ if (!sftp.emit("OPENDIR", reqID, path10)) {
20765
20765
  sftp.status(reqID, STATUS_CODE.OP_UNSUPPORTED);
20766
20766
  }
20767
20767
  },
@@ -20780,63 +20780,63 @@ var require_SFTP = __commonJS({
20780
20780
  [REQUEST.REMOVE]: (sftp, payload) => {
20781
20781
  bufferParser.init(payload, 1);
20782
20782
  const reqID = bufferParser.readUInt32BE();
20783
- const path8 = bufferParser.readString(true);
20783
+ const path10 = bufferParser.readString(true);
20784
20784
  bufferParser.clear();
20785
- if (path8 === void 0)
20785
+ if (path10 === void 0)
20786
20786
  return doFatalSFTPError(sftp, "Malformed REMOVE packet");
20787
20787
  sftp._debug && sftp._debug(`SFTP: Inbound: Received REMOVE (id:${reqID})`);
20788
- if (!sftp.emit("REMOVE", reqID, path8)) {
20788
+ if (!sftp.emit("REMOVE", reqID, path10)) {
20789
20789
  sftp.status(reqID, STATUS_CODE.OP_UNSUPPORTED);
20790
20790
  }
20791
20791
  },
20792
20792
  [REQUEST.MKDIR]: (sftp, payload) => {
20793
20793
  bufferParser.init(payload, 1);
20794
20794
  const reqID = bufferParser.readUInt32BE();
20795
- const path8 = bufferParser.readString(true);
20795
+ const path10 = bufferParser.readString(true);
20796
20796
  const attrs = readAttrs(sftp._biOpt);
20797
20797
  bufferParser.clear();
20798
20798
  if (attrs === void 0)
20799
20799
  return doFatalSFTPError(sftp, "Malformed MKDIR packet");
20800
20800
  sftp._debug && sftp._debug(`SFTP: Inbound: Received MKDIR (id:${reqID})`);
20801
- if (!sftp.emit("MKDIR", reqID, path8, attrs)) {
20801
+ if (!sftp.emit("MKDIR", reqID, path10, attrs)) {
20802
20802
  sftp.status(reqID, STATUS_CODE.OP_UNSUPPORTED);
20803
20803
  }
20804
20804
  },
20805
20805
  [REQUEST.RMDIR]: (sftp, payload) => {
20806
20806
  bufferParser.init(payload, 1);
20807
20807
  const reqID = bufferParser.readUInt32BE();
20808
- const path8 = bufferParser.readString(true);
20808
+ const path10 = bufferParser.readString(true);
20809
20809
  bufferParser.clear();
20810
- if (path8 === void 0)
20810
+ if (path10 === void 0)
20811
20811
  return doFatalSFTPError(sftp, "Malformed RMDIR packet");
20812
20812
  sftp._debug && sftp._debug(`SFTP: Inbound: Received RMDIR (id:${reqID})`);
20813
- if (!sftp.emit("RMDIR", reqID, path8)) {
20813
+ if (!sftp.emit("RMDIR", reqID, path10)) {
20814
20814
  sftp.status(reqID, STATUS_CODE.OP_UNSUPPORTED);
20815
20815
  }
20816
20816
  },
20817
20817
  [REQUEST.REALPATH]: (sftp, payload) => {
20818
20818
  bufferParser.init(payload, 1);
20819
20819
  const reqID = bufferParser.readUInt32BE();
20820
- const path8 = bufferParser.readString(true);
20820
+ const path10 = bufferParser.readString(true);
20821
20821
  bufferParser.clear();
20822
- if (path8 === void 0)
20822
+ if (path10 === void 0)
20823
20823
  return doFatalSFTPError(sftp, "Malformed REALPATH packet");
20824
20824
  sftp._debug && sftp._debug(
20825
20825
  `SFTP: Inbound: Received REALPATH (id:${reqID})`
20826
20826
  );
20827
- if (!sftp.emit("REALPATH", reqID, path8)) {
20827
+ if (!sftp.emit("REALPATH", reqID, path10)) {
20828
20828
  sftp.status(reqID, STATUS_CODE.OP_UNSUPPORTED);
20829
20829
  }
20830
20830
  },
20831
20831
  [REQUEST.STAT]: (sftp, payload) => {
20832
20832
  bufferParser.init(payload, 1);
20833
20833
  const reqID = bufferParser.readUInt32BE();
20834
- const path8 = bufferParser.readString(true);
20834
+ const path10 = bufferParser.readString(true);
20835
20835
  bufferParser.clear();
20836
- if (path8 === void 0)
20836
+ if (path10 === void 0)
20837
20837
  return doFatalSFTPError(sftp, "Malformed STAT packet");
20838
20838
  sftp._debug && sftp._debug(`SFTP: Inbound: Received STAT (id:${reqID})`);
20839
- if (!sftp.emit("STAT", reqID, path8)) {
20839
+ if (!sftp.emit("STAT", reqID, path10)) {
20840
20840
  sftp.status(reqID, STATUS_CODE.OP_UNSUPPORTED);
20841
20841
  }
20842
20842
  },
@@ -20856,14 +20856,14 @@ var require_SFTP = __commonJS({
20856
20856
  [REQUEST.READLINK]: (sftp, payload) => {
20857
20857
  bufferParser.init(payload, 1);
20858
20858
  const reqID = bufferParser.readUInt32BE();
20859
- const path8 = bufferParser.readString(true);
20859
+ const path10 = bufferParser.readString(true);
20860
20860
  bufferParser.clear();
20861
- if (path8 === void 0)
20861
+ if (path10 === void 0)
20862
20862
  return doFatalSFTPError(sftp, "Malformed READLINK packet");
20863
20863
  sftp._debug && sftp._debug(
20864
20864
  `SFTP: Inbound: Received READLINK (id:${reqID})`
20865
20865
  );
20866
- if (!sftp.emit("READLINK", reqID, path8)) {
20866
+ if (!sftp.emit("READLINK", reqID, path10)) {
20867
20867
  sftp.status(reqID, STATUS_CODE.OP_UNSUPPORTED);
20868
20868
  }
20869
20869
  },
@@ -20934,7 +20934,7 @@ var require_SFTP = __commonJS({
20934
20934
  function roundUpToMultipleOf8(n) {
20935
20935
  return n + 7 & ~7;
20936
20936
  }
20937
- function ReadStream(sftp, path8, options) {
20937
+ function ReadStream(sftp, path10, options) {
20938
20938
  if (options === void 0)
20939
20939
  options = {};
20940
20940
  else if (typeof options === "string")
@@ -20948,7 +20948,7 @@ var require_SFTP = __commonJS({
20948
20948
  options.emitClose = false;
20949
20949
  options.autoDestroy = false;
20950
20950
  ReadableStream2.call(this, options);
20951
- this.path = path8;
20951
+ this.path = path10;
20952
20952
  this.flags = options.flags === void 0 ? "r" : options.flags;
20953
20953
  this.mode = options.mode === void 0 ? 438 : options.mode;
20954
20954
  this.start = options.start;
@@ -21079,7 +21079,7 @@ var require_SFTP = __commonJS({
21079
21079
  },
21080
21080
  configurable: true
21081
21081
  });
21082
- function WriteStream(sftp, path8, options) {
21082
+ function WriteStream(sftp, path10, options) {
21083
21083
  if (options === void 0)
21084
21084
  options = {};
21085
21085
  else if (typeof options === "string")
@@ -21091,7 +21091,7 @@ var require_SFTP = __commonJS({
21091
21091
  options.emitClose = false;
21092
21092
  options.autoDestroy = false;
21093
21093
  WritableStream.call(this, options);
21094
- this.path = path8;
21094
+ this.path = path10;
21095
21095
  this.flags = options.flags === void 0 ? "w" : options.flags;
21096
21096
  this.mode = options.mode === void 0 ? 438 : options.mode;
21097
21097
  this.start = options.start;
@@ -25300,7 +25300,7 @@ var require_tunnel_ssh = __commonJS({
25300
25300
  "node_modules/tunnel-ssh/index.js"(exports) {
25301
25301
  var net = __require("net");
25302
25302
  var { Client: Client2 } = require_lib4();
25303
- var os8 = __require("os");
25303
+ var os11 = __require("os");
25304
25304
  function autoClose(server, connection) {
25305
25305
  connection.on("close", () => {
25306
25306
  server.getConnections((error2, count) => {
@@ -25485,9 +25485,9 @@ var require_package2 = __commonJS({
25485
25485
  // node_modules/dotenv/lib/main.js
25486
25486
  var require_main = __commonJS({
25487
25487
  "node_modules/dotenv/lib/main.js"(exports, module) {
25488
- var fs8 = __require("fs");
25489
- var path8 = __require("path");
25490
- var os8 = __require("os");
25488
+ var fs10 = __require("fs");
25489
+ var path10 = __require("path");
25490
+ var os11 = __require("os");
25491
25491
  var crypto3 = __require("crypto");
25492
25492
  var packageJson2 = require_package2();
25493
25493
  var version2 = packageJson2.version;
@@ -25594,7 +25594,7 @@ var require_main = __commonJS({
25594
25594
  if (options && options.path && options.path.length > 0) {
25595
25595
  if (Array.isArray(options.path)) {
25596
25596
  for (const filepath of options.path) {
25597
- if (fs8.existsSync(filepath)) {
25597
+ if (fs10.existsSync(filepath)) {
25598
25598
  possibleVaultPath = filepath.endsWith(".vault") ? filepath : `${filepath}.vault`;
25599
25599
  }
25600
25600
  }
@@ -25602,15 +25602,15 @@ var require_main = __commonJS({
25602
25602
  possibleVaultPath = options.path.endsWith(".vault") ? options.path : `${options.path}.vault`;
25603
25603
  }
25604
25604
  } else {
25605
- possibleVaultPath = path8.resolve(process.cwd(), ".env.vault");
25605
+ possibleVaultPath = path10.resolve(process.cwd(), ".env.vault");
25606
25606
  }
25607
- if (fs8.existsSync(possibleVaultPath)) {
25607
+ if (fs10.existsSync(possibleVaultPath)) {
25608
25608
  return possibleVaultPath;
25609
25609
  }
25610
25610
  return null;
25611
25611
  }
25612
25612
  function _resolveHome(envPath) {
25613
- return envPath[0] === "~" ? path8.join(os8.homedir(), envPath.slice(1)) : envPath;
25613
+ return envPath[0] === "~" ? path10.join(os11.homedir(), envPath.slice(1)) : envPath;
25614
25614
  }
25615
25615
  function _configVault(options) {
25616
25616
  const debug = Boolean(options && options.debug);
@@ -25627,7 +25627,7 @@ var require_main = __commonJS({
25627
25627
  return { parsed };
25628
25628
  }
25629
25629
  function configDotenv(options) {
25630
- const dotenvPath = path8.resolve(process.cwd(), ".env");
25630
+ const dotenvPath = path10.resolve(process.cwd(), ".env");
25631
25631
  let encoding = "utf8";
25632
25632
  const debug = Boolean(options && options.debug);
25633
25633
  const quiet = options && "quiet" in options ? options.quiet : true;
@@ -25651,13 +25651,13 @@ var require_main = __commonJS({
25651
25651
  }
25652
25652
  let lastError;
25653
25653
  const parsedAll = {};
25654
- for (const path9 of optionPaths) {
25654
+ for (const path11 of optionPaths) {
25655
25655
  try {
25656
- const parsed = DotenvModule.parse(fs8.readFileSync(path9, { encoding }));
25656
+ const parsed = DotenvModule.parse(fs10.readFileSync(path11, { encoding }));
25657
25657
  DotenvModule.populate(parsedAll, parsed, options);
25658
25658
  } catch (e) {
25659
25659
  if (debug) {
25660
- _debug(`Failed to load ${path9} ${e.message}`);
25660
+ _debug(`Failed to load ${path11} ${e.message}`);
25661
25661
  }
25662
25662
  lastError = e;
25663
25663
  }
@@ -25672,7 +25672,7 @@ var require_main = __commonJS({
25672
25672
  const shortPaths = [];
25673
25673
  for (const filePath of optionPaths) {
25674
25674
  try {
25675
- const relative = path8.relative(process.cwd(), filePath);
25675
+ const relative = path10.relative(process.cwd(), filePath);
25676
25676
  shortPaths.push(relative);
25677
25677
  } catch (e) {
25678
25678
  if (debug) {
@@ -25788,19 +25788,20 @@ import { fileURLToPath } from "url";
25788
25788
  function configSearchPaths() {
25789
25789
  const paths = [];
25790
25790
  if (process.env.MEMORY_CONFIG_PATH) paths.push(process.env.MEMORY_CONFIG_PATH);
25791
- paths.push(path.join(os.homedir(), ".config", "mcp-agents-memory", ".env"));
25792
25791
  paths.push(path.resolve(process.cwd(), ".env"));
25792
+ paths.push(path.join(os.homedir(), ".config", "mcp-agents-memory", ".env"));
25793
25793
  paths.push(path.resolve(__dirname2, "..", ".env"));
25794
25794
  return paths;
25795
25795
  }
25796
25796
  function loadEnv() {
25797
25797
  if (envLoadedFrom) return envLoadedFrom;
25798
25798
  for (const candidate of configSearchPaths()) {
25799
- if (fs.existsSync(candidate)) {
25800
- import_dotenv.default.config({ path: candidate });
25801
- envLoadedFrom = candidate;
25802
- return candidate;
25803
- }
25799
+ if (!fs.existsSync(candidate)) continue;
25800
+ const parsed = import_dotenv.default.parse(fs.readFileSync(candidate, "utf-8"));
25801
+ if (!parsed.DATABASE_URL && !parsed.DB_HOST) continue;
25802
+ import_dotenv.default.config({ path: candidate });
25803
+ envLoadedFrom = candidate;
25804
+ return candidate;
25804
25805
  }
25805
25806
  return null;
25806
25807
  }
@@ -29162,8 +29163,8 @@ var require_utils5 = __commonJS({
29162
29163
  }
29163
29164
  return ind;
29164
29165
  }
29165
- function removeDotSegments(path8) {
29166
- let input = path8;
29166
+ function removeDotSegments(path10) {
29167
+ let input = path10;
29167
29168
  const output = [];
29168
29169
  let nextSlash = -1;
29169
29170
  let len = 0;
@@ -29362,8 +29363,8 @@ var require_schemes = __commonJS({
29362
29363
  wsComponent.secure = void 0;
29363
29364
  }
29364
29365
  if (wsComponent.resourceName) {
29365
- const [path8, query] = wsComponent.resourceName.split("?");
29366
- wsComponent.path = path8 && path8 !== "/" ? path8 : void 0;
29366
+ const [path10, query] = wsComponent.resourceName.split("?");
29367
+ wsComponent.path = path10 && path10 !== "/" ? path10 : void 0;
29367
29368
  wsComponent.query = query;
29368
29369
  wsComponent.resourceName = void 0;
29369
29370
  }
@@ -32725,12 +32726,12 @@ var require_dist2 = __commonJS({
32725
32726
  throw new Error(`Unknown format "${name}"`);
32726
32727
  return f;
32727
32728
  };
32728
- function addFormats(ajv, list, fs8, exportName) {
32729
+ function addFormats(ajv, list, fs10, exportName) {
32729
32730
  var _a3;
32730
32731
  var _b;
32731
32732
  (_a3 = (_b = ajv.opts.code).formats) !== null && _a3 !== void 0 ? _a3 : _b.formats = (0, codegen_1._)`require("ajv-formats/dist/formats").${exportName}`;
32732
32733
  for (const f of list)
32733
- ajv.addFormat(f, fs8[f]);
32734
+ ajv.addFormat(f, fs10[f]);
32734
32735
  }
32735
32736
  module.exports = exports = formatsPlugin;
32736
32737
  Object.defineProperty(exports, "__esModule", { value: true });
@@ -32769,12 +32770,12 @@ __export(runner_exports, {
32769
32770
  runAllMigrations: () => runAllMigrations
32770
32771
  });
32771
32772
  import { spawn } from "child_process";
32772
- import fs6 from "fs";
32773
- import path7 from "path";
32773
+ import fs8 from "fs";
32774
+ import path9 from "path";
32774
32775
  import { fileURLToPath as fileURLToPath3 } from "url";
32775
32776
  function listMigrationFiles() {
32776
- if (!fs6.existsSync(migrationsDir)) return [];
32777
- return fs6.readdirSync(migrationsDir).filter((f) => MIGRATION_FILE_RE.test(f)).sort();
32777
+ if (!fs8.existsSync(migrationsDir)) return [];
32778
+ return fs8.readdirSync(migrationsDir).filter((f) => MIGRATION_FILE_RE.test(f)).sort();
32778
32779
  }
32779
32780
  function runOne(file) {
32780
32781
  return new Promise((resolve, reject) => {
@@ -32784,7 +32785,7 @@ function runOne(file) {
32784
32785
  });
32785
32786
  child.on("exit", (code) => {
32786
32787
  if (code === 0) return resolve();
32787
- reject(new Error(`Migration ${path7.basename(file)} exited with code ${code}`));
32788
+ reject(new Error(`Migration ${path9.basename(file)} exited with code ${code}`));
32788
32789
  });
32789
32790
  child.on("error", reject);
32790
32791
  });
@@ -32797,7 +32798,7 @@ async function runAllMigrations() {
32797
32798
  }
32798
32799
  console.log(`\u{1F4E6} Running ${files.length} migration(s) from ${migrationsDir}`);
32799
32800
  for (const f of files) {
32800
- await runOne(path7.join(migrationsDir, f));
32801
+ await runOne(path9.join(migrationsDir, f));
32801
32802
  }
32802
32803
  console.log(`\u2705 Migration runner complete \u2014 ${files.length} file(s) processed.`);
32803
32804
  return { ran: files.length, files };
@@ -32806,8 +32807,8 @@ var __dirname4, migrationsDir, MIGRATION_FILE_RE;
32806
32807
  var init_runner = __esm({
32807
32808
  "src/migrations/runner.ts"() {
32808
32809
  "use strict";
32809
- __dirname4 = path7.dirname(fileURLToPath3(import.meta.url));
32810
- migrationsDir = fs6.existsSync(path7.join(__dirname4, "migrations")) ? path7.join(__dirname4, "migrations") : __dirname4;
32810
+ __dirname4 = path9.dirname(fileURLToPath3(import.meta.url));
32811
+ migrationsDir = fs8.existsSync(path9.join(__dirname4, "migrations")) ? path9.join(__dirname4, "migrations") : __dirname4;
32811
32812
  MIGRATION_FILE_RE = /^\d{3}_.+\.js$/;
32812
32813
  }
32813
32814
  });
@@ -33293,8 +33294,8 @@ function getErrorMap() {
33293
33294
 
33294
33295
  // node_modules/zod/v3/helpers/parseUtil.js
33295
33296
  var makeIssue = (params) => {
33296
- const { data, path: path8, errorMaps, issueData } = params;
33297
- const fullPath = [...path8, ...issueData.path || []];
33297
+ const { data, path: path10, errorMaps, issueData } = params;
33298
+ const fullPath = [...path10, ...issueData.path || []];
33298
33299
  const fullIssue = {
33299
33300
  ...issueData,
33300
33301
  path: fullPath
@@ -33410,11 +33411,11 @@ var errorUtil;
33410
33411
 
33411
33412
  // node_modules/zod/v3/types.js
33412
33413
  var ParseInputLazyPath = class {
33413
- constructor(parent, value, path8, key) {
33414
+ constructor(parent, value, path10, key) {
33414
33415
  this._cachedPath = [];
33415
33416
  this.parent = parent;
33416
33417
  this.data = value;
33417
- this._path = path8;
33418
+ this._path = path10;
33418
33419
  this._key = key;
33419
33420
  }
33420
33421
  get path() {
@@ -37051,10 +37052,10 @@ function assignProp(target, prop, value) {
37051
37052
  configurable: true
37052
37053
  });
37053
37054
  }
37054
- function getElementAtPath(obj, path8) {
37055
- if (!path8)
37055
+ function getElementAtPath(obj, path10) {
37056
+ if (!path10)
37056
37057
  return obj;
37057
- return path8.reduce((acc, key) => acc?.[key], obj);
37058
+ return path10.reduce((acc, key) => acc?.[key], obj);
37058
37059
  }
37059
37060
  function promiseAllObject(promisesObj) {
37060
37061
  const keys = Object.keys(promisesObj);
@@ -37374,11 +37375,11 @@ function aborted(x, startIndex = 0) {
37374
37375
  }
37375
37376
  return false;
37376
37377
  }
37377
- function prefixIssues(path8, issues) {
37378
+ function prefixIssues(path10, issues) {
37378
37379
  return issues.map((iss) => {
37379
37380
  var _a3;
37380
37381
  (_a3 = iss).path ?? (_a3.path = []);
37381
- iss.path.unshift(path8);
37382
+ iss.path.unshift(path10);
37382
37383
  return iss;
37383
37384
  });
37384
37385
  }
@@ -48902,12 +48903,12 @@ function encodeURIPath(str2) {
48902
48903
  return str2.replace(/[^A-Za-z0-9\-._~!$&'()*+,;=:@]+/g, encodeURIComponent);
48903
48904
  }
48904
48905
  var EMPTY = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.create(null));
48905
- var createPathTagFunction = (pathEncoder = encodeURIPath) => function path8(statics, ...params) {
48906
+ var createPathTagFunction = (pathEncoder = encodeURIPath) => function path10(statics, ...params) {
48906
48907
  if (statics.length === 1)
48907
48908
  return statics[0];
48908
48909
  let postPath = false;
48909
48910
  const invalidSegments = [];
48910
- const path9 = statics.reduce((previousValue, currentValue, index) => {
48911
+ const path11 = statics.reduce((previousValue, currentValue, index) => {
48911
48912
  if (/[?#]/.test(currentValue)) {
48912
48913
  postPath = true;
48913
48914
  }
@@ -48924,7 +48925,7 @@ var createPathTagFunction = (pathEncoder = encodeURIPath) => function path8(stat
48924
48925
  }
48925
48926
  return previousValue + currentValue + (index === params.length ? "" : encoded);
48926
48927
  }, "");
48927
- const pathOnly = path9.split(/[?#]/, 1)[0];
48928
+ const pathOnly = path11.split(/[?#]/, 1)[0];
48928
48929
  const invalidSegmentPattern = /(?<=^|\/)(?:\.|%2e){1,2}(?=\/|$)/gi;
48929
48930
  let match;
48930
48931
  while ((match = invalidSegmentPattern.exec(pathOnly)) !== null) {
@@ -48945,10 +48946,10 @@ var createPathTagFunction = (pathEncoder = encodeURIPath) => function path8(stat
48945
48946
  }, "");
48946
48947
  throw new OpenAIError(`Path parameters result in path with invalid segments:
48947
48948
  ${invalidSegments.map((e) => e.error).join("\n")}
48948
- ${path9}
48949
+ ${path11}
48949
48950
  ${underline}`);
48950
48951
  }
48951
- return path9;
48952
+ return path11;
48952
48953
  };
48953
48954
  var path2 = /* @__PURE__ */ createPathTagFunction(encodeURIPath);
48954
48955
 
@@ -53921,9 +53922,9 @@ var OpenAI = class {
53921
53922
  this.apiKey = token;
53922
53923
  return true;
53923
53924
  }
53924
- buildURL(path8, query, defaultBaseURL) {
53925
+ buildURL(path10, query, defaultBaseURL) {
53925
53926
  const baseURL = !__classPrivateFieldGet(this, _OpenAI_instances, "m", _OpenAI_baseURLOverridden).call(this) && defaultBaseURL || this.baseURL;
53926
- const url = isAbsoluteURL(path8) ? new URL(path8) : new URL(baseURL + (baseURL.endsWith("/") && path8.startsWith("/") ? path8.slice(1) : path8));
53927
+ const url = isAbsoluteURL(path10) ? new URL(path10) : new URL(baseURL + (baseURL.endsWith("/") && path10.startsWith("/") ? path10.slice(1) : path10));
53927
53928
  const defaultQuery = this.defaultQuery();
53928
53929
  const pathQuery = Object.fromEntries(url.searchParams);
53929
53930
  if (!isEmptyObj(defaultQuery) || !isEmptyObj(pathQuery)) {
@@ -53948,24 +53949,24 @@ var OpenAI = class {
53948
53949
  */
53949
53950
  async prepareRequest(request, { url, options }) {
53950
53951
  }
53951
- get(path8, opts) {
53952
- return this.methodRequest("get", path8, opts);
53952
+ get(path10, opts) {
53953
+ return this.methodRequest("get", path10, opts);
53953
53954
  }
53954
- post(path8, opts) {
53955
- return this.methodRequest("post", path8, opts);
53955
+ post(path10, opts) {
53956
+ return this.methodRequest("post", path10, opts);
53956
53957
  }
53957
- patch(path8, opts) {
53958
- return this.methodRequest("patch", path8, opts);
53958
+ patch(path10, opts) {
53959
+ return this.methodRequest("patch", path10, opts);
53959
53960
  }
53960
- put(path8, opts) {
53961
- return this.methodRequest("put", path8, opts);
53961
+ put(path10, opts) {
53962
+ return this.methodRequest("put", path10, opts);
53962
53963
  }
53963
- delete(path8, opts) {
53964
- return this.methodRequest("delete", path8, opts);
53964
+ delete(path10, opts) {
53965
+ return this.methodRequest("delete", path10, opts);
53965
53966
  }
53966
- methodRequest(method, path8, opts) {
53967
+ methodRequest(method, path10, opts) {
53967
53968
  return this.request(Promise.resolve(opts).then((opts2) => {
53968
- return { method, path: path8, ...opts2 };
53969
+ return { method, path: path10, ...opts2 };
53969
53970
  }));
53970
53971
  }
53971
53972
  request(options, remainingRetries = null) {
@@ -54083,8 +54084,8 @@ var OpenAI = class {
54083
54084
  }));
54084
54085
  return { response, options, controller, requestLogID, retryOfRequestLogID, startTime };
54085
54086
  }
54086
- getAPIList(path8, Page2, opts) {
54087
- return this.requestAPIList(Page2, opts && "then" in opts ? opts.then((opts2) => ({ method: "get", path: path8, ...opts2 })) : { method: "get", path: path8, ...opts });
54087
+ getAPIList(path10, Page2, opts) {
54088
+ return this.requestAPIList(Page2, opts && "then" in opts ? opts.then((opts2) => ({ method: "get", path: path10, ...opts2 })) : { method: "get", path: path10, ...opts });
54088
54089
  }
54089
54090
  requestAPIList(Page2, options) {
54090
54091
  const request = this.makeRequest(options, null, void 0);
@@ -54175,8 +54176,8 @@ var OpenAI = class {
54175
54176
  }
54176
54177
  async buildRequest(inputOptions, { retryCount = 0 } = {}) {
54177
54178
  const options = { ...inputOptions };
54178
- const { method, path: path8, query, defaultBaseURL } = options;
54179
- const url = this.buildURL(path8, query, defaultBaseURL);
54179
+ const { method, path: path10, query, defaultBaseURL } = options;
54180
+ const url = this.buildURL(path10, query, defaultBaseURL);
54180
54181
  if ("timeout" in options)
54181
54182
  validatePositiveInteger("timeout", options.timeout);
54182
54183
  options.timeout = options.timeout ?? this.timeout;
@@ -55316,9 +55317,12 @@ var GoogleGenerativeAI = class {
55316
55317
  var KNOWN_PREFIXES = {
55317
55318
  openai: ["gpt-", "o1-", "o3-", "text-embedding-"],
55318
55319
  google: ["gemini-"],
55319
- xai: ["grok-"]
55320
+ xai: ["grok-"],
55321
+ local: []
55322
+ // 모델명 제한 없음 (ollama 태그 형식: qwen3.5:9b 등)
55320
55323
  };
55321
55324
  function assertModelProvider(spec) {
55325
+ if (spec.provider === "local") return;
55322
55326
  const m = spec.model_name.toLowerCase();
55323
55327
  const valid = KNOWN_PREFIXES[spec.provider];
55324
55328
  if (!valid.some((p) => m.startsWith(p))) {
@@ -55337,7 +55341,8 @@ function inferProvider(modelName) {
55337
55341
  var DEFAULTS = {
55338
55342
  tagger: { provider: "xai", model_name: "grok-4-1-fast-non-reasoning" },
55339
55343
  librarian: { provider: "xai", model_name: "grok-4-1-fast-non-reasoning" },
55340
- clusterer: { provider: "xai", model_name: "grok-4-1-fast-non-reasoning" }
55344
+ clusterer: { provider: "xai", model_name: "grok-4-1-fast-non-reasoning" },
55345
+ project_alias_judge: { provider: "local", model_name: "gemma4:26b-a4b-it-q4_K_M" }
55341
55346
  };
55342
55347
  function envEnvelope(role) {
55343
55348
  const upper = role.toUpperCase();
@@ -55364,7 +55369,8 @@ function envEnvelope(role) {
55364
55369
  var ROLE_REGISTRY = {
55365
55370
  tagger: envEnvelope("tagger"),
55366
55371
  librarian: envEnvelope("librarian"),
55367
- clusterer: envEnvelope("clusterer")
55372
+ clusterer: envEnvelope("clusterer"),
55373
+ project_alias_judge: envEnvelope("project_alias_judge")
55368
55374
  };
55369
55375
  for (const [role, spec] of Object.entries(ROLE_REGISTRY)) {
55370
55376
  try {
@@ -55378,6 +55384,7 @@ var EMBEDDING_MODEL = process.env.EMBEDDING_MODEL ?? "text-embedding-3-large";
55378
55384
  var _openaiClient = null;
55379
55385
  var _googleClient = null;
55380
55386
  var _xaiClient = null;
55387
+ var _localClient = null;
55381
55388
  function getOpenAIClient() {
55382
55389
  if (!_openaiClient) {
55383
55390
  if (!process.env.OPENAI_API_KEY) throw new Error("OPENAI_API_KEY missing");
@@ -55399,8 +55406,14 @@ function getGoogleClient() {
55399
55406
  }
55400
55407
  return _googleClient;
55401
55408
  }
55402
- async function callRole(role, opts) {
55403
- const spec = ROLE_REGISTRY[role];
55409
+ function getLocalClient() {
55410
+ if (!_localClient) {
55411
+ const baseURL = process.env.LOCAL_LLM_BASE_URL ?? "http://localhost:11434/v1";
55412
+ _localClient = new OpenAI({ apiKey: "local", baseURL });
55413
+ }
55414
+ return _localClient;
55415
+ }
55416
+ async function callSpec(spec, opts) {
55404
55417
  assertModelProvider(spec);
55405
55418
  const maxTokens = opts.maxTokens ?? 4096;
55406
55419
  const useJson = opts.responseFormat === "json";
@@ -55443,8 +55456,27 @@ async function callRole(role, opts) {
55443
55456
  });
55444
55457
  return (res.choices[0]?.message?.content || "").replace(/```json|```/g, "").trim();
55445
55458
  }
55459
+ case "local": {
55460
+ const client2 = getLocalClient();
55461
+ const res = await client2.chat.completions.create({
55462
+ model: spec.model_name,
55463
+ messages: [
55464
+ { role: "system", content: opts.system },
55465
+ { role: "user", content: opts.user }
55466
+ ],
55467
+ ...useJson ? { response_format: { type: "json_object" } } : {},
55468
+ temperature: 0.1,
55469
+ max_tokens: maxTokens
55470
+ });
55471
+ const raw = res.choices[0]?.message?.content || "";
55472
+ return raw.replace(/<think>[\s\S]*?<\/think>/g, "").replace(/```json|```/g, "").trim();
55473
+ }
55446
55474
  }
55447
55475
  }
55476
+ async function callRole(role, opts) {
55477
+ const spec = ROLE_REGISTRY[role];
55478
+ return callSpec(spec, opts);
55479
+ }
55448
55480
 
55449
55481
  // src/embeddings.ts
55450
55482
  var client = null;
@@ -55479,6 +55511,8 @@ async function generateEmbedding(text) {
55479
55511
 
55480
55512
  // src/cold_path/tagger.ts
55481
55513
  init_db();
55514
+ var GROK_FALLBACK_SPEC = { provider: "xai", model_name: "grok-4-1-fast-non-reasoning" };
55515
+ var localFallbackEnabled = process.env.LOCAL_GROK_FALLBACK !== "false";
55482
55516
  var _candidateCache = null;
55483
55517
  var CANDIDATE_CACHE_TTL_MS = 5 * 60 * 1e3;
55484
55518
  async function listProjectTagCandidates(limit2 = 20) {
@@ -55557,11 +55591,33 @@ async function getOrCreateProjectTag(name) {
55557
55591
  async function tagMessage(input) {
55558
55592
  const candidates = await listProjectTagCandidates();
55559
55593
  const userPrompt = buildUserPrompt(input, candidates);
55560
- const raw = await callRole("tagger", {
55561
- system: SYSTEM_PROMPT,
55562
- user: userPrompt,
55563
- responseFormat: "json"
55564
- });
55594
+ const isLocal = ROLE_REGISTRY.tagger.provider === "local";
55595
+ let raw;
55596
+ if (isLocal) {
55597
+ try {
55598
+ raw = await callRole("tagger", {
55599
+ system: SYSTEM_PROMPT,
55600
+ user: userPrompt,
55601
+ responseFormat: "json"
55602
+ // thinking: false — 태거는 단순 매핑 작업. thinking 켜면 reasoning이 모든 토큰 소비해 content 비어버림.
55603
+ // Librarian 등 복잡한 분석 역할에서만 thinking: true 사용.
55604
+ });
55605
+ } catch (err2) {
55606
+ if (!localFallbackEnabled) throw err2;
55607
+ console.warn(`\u26A0\uFE0F [Tagger] Local model \uC2E4\uD328, grok fallback: ${err2.message?.slice(0, 80)}`);
55608
+ raw = await callSpec(GROK_FALLBACK_SPEC, {
55609
+ system: SYSTEM_PROMPT,
55610
+ user: userPrompt,
55611
+ responseFormat: "json"
55612
+ });
55613
+ }
55614
+ } else {
55615
+ raw = await callRole("tagger", {
55616
+ system: SYSTEM_PROMPT,
55617
+ user: userPrompt,
55618
+ responseFormat: "json"
55619
+ });
55620
+ }
55565
55621
  let parsed;
55566
55622
  try {
55567
55623
  parsed = JSON.parse(raw);
@@ -55849,6 +55905,7 @@ function vectorToHalfvecSql(embedding) {
55849
55905
  }
55850
55906
 
55851
55907
  // src/tools/search_memory.ts
55908
+ import os3 from "node:os";
55852
55909
  var DEFAULT_LIMIT = 10;
55853
55910
  var DEFAULT_FALLBACK_THRESHOLD = 0.3;
55854
55911
  function parseDateRange(s) {
@@ -55917,7 +55974,10 @@ date_range \uC778\uC2DD \uD615\uC2DD:
55917
55974
  date_range: external_exports.string().optional().describe("\uAE30\uAC04 \uD55C\uC815 (today / last_week / last_month / 7_days_ago / YYYY-MM-DD)"),
55918
55975
  role: external_exports.enum(["user", "assistant"]).optional().describe("\uBC1C\uD654\uC790 \uD55C\uC815 (\uC0DD\uB7B5 \uC2DC \uB458 \uB2E4)"),
55919
55976
  agent_platform: external_exports.string().optional().describe(
55920
- "agent platform \uD55C\uC815 (\uC608: 'claude-code', 'gemini-cli-mcp-client'). \uC0DD\uB7B5 \uC2DC cross-platform."
55977
+ "agent platform \uD55C\uC815 (\uC608: 'claude-code', 'gemini-cli-mcp-client'). \uC0DD\uB7B5 \uB610\uB294 '*' \uC774\uBA74 cross-platform(\uC804 \uD50C\uB7AB\uD3FC)."
55978
+ ),
55979
+ device_scope: external_exports.enum(["local", "global"]).optional().describe(
55980
+ "\uAE30\uAE30 \uD55C\uC815. 'global'(\uAE30\uBCF8): \uC804 \uAE30\uAE30 \uAC80\uC0C9(\uB113\uAC8C). 'local': \uD604\uC7AC \uAE30\uAE30(hostname)\uB85C \uD55C\uC815(\uC774\uC5B4\uBC1B\uAE30\uC6A9, \uC881\uAC8C). zero-config \u2014 \uC11C\uBC84\uAC00 hostname \uC790\uB3D9 \uD574\uC11D."
55921
55981
  ),
55922
55982
  limit: external_exports.number().int().min(1).max(50).optional().describe(`\uCD5C\uB300 \uACB0\uACFC \uC218 (default ${DEFAULT_LIMIT})`),
55923
55983
  include_archived: external_exports.boolean().optional().describe("archived \uBA54\uBAA8\uB9AC\uB3C4 \uD3EC\uD568 (default false)")
@@ -55945,6 +56005,7 @@ date_range \uC778\uC2DD \uD615\uC2DD:
55945
56005
  }
55946
56006
  const sinceDate = parseDateRange(args.date_range);
55947
56007
  const includeArchived = args.include_archived === true;
56008
+ const localDevice = os3.hostname();
55948
56009
  const filters = [`m.user_id = $1`];
55949
56010
  const params = [userId];
55950
56011
  let p = 2;
@@ -55952,7 +56013,7 @@ date_range \uC778\uC2DD \uD615\uC2DD:
55952
56013
  filters.push(`m.is_active = TRUE`);
55953
56014
  }
55954
56015
  if (p_tag_id !== null) {
55955
- filters.push(`m.p_tag_id = $${p++}`);
56016
+ filters.push(`canonical_project_tag_id(m.p_tag_id) = $${p++}`);
55956
56017
  params.push(p_tag_id);
55957
56018
  }
55958
56019
  if (sinceDate) {
@@ -55963,10 +56024,14 @@ date_range \uC778\uC2DD \uD615\uC2DD:
55963
56024
  filters.push(`m.role = $${p++}`);
55964
56025
  params.push(args.role);
55965
56026
  }
55966
- if (args.agent_platform) {
56027
+ if (args.agent_platform && args.agent_platform !== "*") {
55967
56028
  filters.push(`m.agent_platform = $${p++}`);
55968
56029
  params.push(args.agent_platform);
55969
56030
  }
56031
+ if (args.device_scope === "local") {
56032
+ filters.push(`(m.device_name = $${p++} OR m.is_pinned = TRUE)`);
56033
+ params.push(localDevice);
56034
+ }
55970
56035
  const whereSql = filters.join(" AND ");
55971
56036
  let used = "recency";
55972
56037
  let topSimilarity = void 0;
@@ -55980,11 +56045,11 @@ date_range \uC778\uC2DD \uD615\uC2DD:
55980
56045
  const limitParam = p++;
55981
56046
  params.push(limit2);
55982
56047
  const r = await db.query(
55983
- `SELECT m.id, m.role, m.message, m.agent_platform, m.agent_model,
56048
+ `SELECT m.id, m.role, m.message, m.agent_platform, m.agent_model, m.device_name,
55984
56049
  pt.name AS p_tag_name, m.d_tag, m.is_pinned, m.created_at,
55985
56050
  1 - (m.embedding <=> $${vecParam}::halfvec) AS similarity
55986
56051
  FROM memory m
55987
- LEFT JOIN project_tags pt ON pt.id = m.p_tag_id
56052
+ LEFT JOIN project_tags pt ON pt.id = canonical_project_tag_id(m.p_tag_id)
55988
56053
  WHERE ${whereSql}
55989
56054
  AND m.embedding IS NOT NULL
55990
56055
  ORDER BY m.embedding <=> $${vecParam}::halfvec
@@ -55997,6 +56062,7 @@ date_range \uC778\uC2DD \uD615\uC2DD:
55997
56062
  message: row.message,
55998
56063
  agent_platform: row.agent_platform,
55999
56064
  agent_model: row.agent_model,
56065
+ device_name: row.device_name ?? null,
56000
56066
  p_tag_name: row.p_tag_name,
56001
56067
  d_tag: row.d_tag ?? [],
56002
56068
  is_pinned: row.is_pinned,
@@ -56016,7 +56082,7 @@ date_range \uC778\uC2DD \uD615\uC2DD:
56016
56082
  const ilikeFilters = [`m.user_id = $1`];
56017
56083
  if (!includeArchived) ilikeFilters.push(`m.is_active = TRUE`);
56018
56084
  if (p_tag_id !== null) {
56019
- ilikeFilters.push(`m.p_tag_id = $${q++}`);
56085
+ ilikeFilters.push(`canonical_project_tag_id(m.p_tag_id) = $${q++}`);
56020
56086
  ilikeParams.push(p_tag_id);
56021
56087
  }
56022
56088
  if (sinceDate) {
@@ -56027,18 +56093,22 @@ date_range \uC778\uC2DD \uD615\uC2DD:
56027
56093
  ilikeFilters.push(`m.role = $${q++}`);
56028
56094
  ilikeParams.push(args.role);
56029
56095
  }
56030
- if (args.agent_platform) {
56096
+ if (args.agent_platform && args.agent_platform !== "*") {
56031
56097
  ilikeFilters.push(`m.agent_platform = $${q++}`);
56032
56098
  ilikeParams.push(args.agent_platform);
56033
56099
  }
56100
+ if (args.device_scope === "local") {
56101
+ ilikeFilters.push(`(m.device_name = $${q++} OR m.is_pinned = TRUE)`);
56102
+ ilikeParams.push(localDevice);
56103
+ }
56034
56104
  ilikeFilters.push(`m.message ILIKE $${q++}`);
56035
56105
  ilikeParams.push(`%${args.query}%`);
56036
56106
  ilikeParams.push(limit2);
56037
56107
  const r = await db.query(
56038
- `SELECT m.id, m.role, m.message, m.agent_platform, m.agent_model,
56108
+ `SELECT m.id, m.role, m.message, m.agent_platform, m.agent_model, m.device_name,
56039
56109
  pt.name AS p_tag_name, m.d_tag, m.is_pinned, m.created_at
56040
56110
  FROM memory m
56041
- LEFT JOIN project_tags pt ON pt.id = m.p_tag_id
56111
+ LEFT JOIN project_tags pt ON pt.id = canonical_project_tag_id(m.p_tag_id)
56042
56112
  WHERE ${ilikeFilters.join(" AND ")}
56043
56113
  ORDER BY m.created_at DESC
56044
56114
  LIMIT $${q}`,
@@ -56051,6 +56121,7 @@ date_range \uC778\uC2DD \uD615\uC2DD:
56051
56121
  message: row.message,
56052
56122
  agent_platform: row.agent_platform,
56053
56123
  agent_model: row.agent_model,
56124
+ device_name: row.device_name ?? null,
56054
56125
  p_tag_name: row.p_tag_name,
56055
56126
  d_tag: row.d_tag ?? [],
56056
56127
  is_pinned: row.is_pinned,
@@ -56063,10 +56134,10 @@ date_range \uC778\uC2DD \uD615\uC2DD:
56063
56134
  } else {
56064
56135
  params.push(limit2);
56065
56136
  const r = await db.query(
56066
- `SELECT m.id, m.role, m.message, m.agent_platform, m.agent_model,
56137
+ `SELECT m.id, m.role, m.message, m.agent_platform, m.agent_model, m.device_name,
56067
56138
  pt.name AS p_tag_name, m.d_tag, m.is_pinned, m.created_at
56068
56139
  FROM memory m
56069
- LEFT JOIN project_tags pt ON pt.id = m.p_tag_id
56140
+ LEFT JOIN project_tags pt ON pt.id = canonical_project_tag_id(m.p_tag_id)
56070
56141
  WHERE ${whereSql}
56071
56142
  ORDER BY m.created_at DESC
56072
56143
  LIMIT $${p}`,
@@ -56078,6 +56149,7 @@ date_range \uC778\uC2DD \uD615\uC2DD:
56078
56149
  message: row.message,
56079
56150
  agent_platform: row.agent_platform,
56080
56151
  agent_model: row.agent_model,
56152
+ device_name: row.device_name ?? null,
56081
56153
  p_tag_name: row.p_tag_name,
56082
56154
  d_tag: row.d_tag ?? [],
56083
56155
  is_pinned: row.is_pinned,
@@ -56101,32 +56173,1357 @@ date_range \uC778\uC2DD \uD615\uC2DD:
56101
56173
  );
56102
56174
  }
56103
56175
 
56176
+ // src/tools/manage_project_tags.ts
56177
+ init_db();
56178
+
56179
+ // src/cold_path/project_alias_promoter.ts
56180
+ init_db();
56181
+ var QUALIFYING_RELATIONS = /* @__PURE__ */ new Set([
56182
+ "rename",
56183
+ "alias",
56184
+ "same_project"
56185
+ ]);
56186
+ var PROJECT_ALIAS_PROMOTER_DEFAULT_INTERVAL_HOURS = 24;
56187
+ var ALL_RELATIONS = /* @__PURE__ */ new Set([
56188
+ "rename",
56189
+ "alias",
56190
+ "same_project",
56191
+ "different",
56192
+ "misfile_suspected",
56193
+ "insufficient"
56194
+ ]);
56195
+ var EXPLICIT_PATTERNS = [
56196
+ "%\uC774\uB984 \uBC14\uAFC8%",
56197
+ "%\uC774\uB984\uBC14\uAFC8%",
56198
+ "%\uC774\uB984\uC744 \uBC14\uAFC8%",
56199
+ "%\uAC1C\uBA85%",
56200
+ "%\uC774\uC81C%\uB85C%",
56201
+ "%\uAC19\uC740 \uD504\uB85C\uC81D\uD2B8%",
56202
+ "%\uB3D9\uC77C%",
56203
+ "%\uD569\uCCD0%",
56204
+ "%\uD569\uCE58%",
56205
+ "%\uBCC4\uCE6D%",
56206
+ "%\uB3D9\uC758\uC5B4%",
56207
+ "%rename%",
56208
+ "%renamed%",
56209
+ "%now called%",
56210
+ "%same as%",
56211
+ "%alias%"
56212
+ ];
56213
+ var projectAliasPromoterRunning = false;
56214
+ var SOURCE_WEIGHT = {
56215
+ explicit_user_statement: 100,
56216
+ vector_neighbor: 30,
56217
+ d_tag_overlap: 18,
56218
+ name_similarity: 16,
56219
+ low_frequency_tag: 8
56220
+ };
56221
+ var JUDGE_SYSTEM_PROMPT = `You are a project-tag alias judge for one user's personal memory system.
56222
+
56223
+ Decide whether two CANONICAL project tags refer to the same project identity.
56224
+
56225
+ OUTPUT strict JSON only:
56226
+ {
56227
+ "relation": "rename" | "alias" | "same_project" | "different" | "misfile_suspected" | "insufficient",
56228
+ "same_project": true | false,
56229
+ "source_should_alias_target": true | false,
56230
+ "confidence": 0.0,
56231
+ "evidence_memory_ids": [],
56232
+ "conflict_memory_ids": [],
56233
+ "rationale": ""
56234
+ }
56235
+
56236
+ Rules:
56237
+ - "Related" is not the same as "same project". Shared topic, tech stack, or client is not enough.
56238
+ - Vector similarity is recall evidence only. Never conclude same_project from vector similarity alone.
56239
+ - A colloquial nickname, renamed label, Korean/English spelling variant, or old/new project name can be alias/rename.
56240
+ - If only a few memories are filed under the wrong tag, relation must be "misfile_suspected"; do not recommend aliasing the whole tag.
56241
+ - If same_project is true, choose direction. source_should_alias_target=true means Tag A should alias Tag B. false means Tag B should alias Tag A.
56242
+ - If direction is unclear, use relation="insufficient" or lower confidence.
56243
+ - evidence_memory_ids and conflict_memory_ids must only use supplied memory ids.`;
56244
+ function envInt(name, fallback) {
56245
+ const raw = process.env[name];
56246
+ if (!raw) return fallback;
56247
+ const n = Number(raw);
56248
+ return Number.isFinite(n) && n >= 1 ? Math.floor(n) : fallback;
56249
+ }
56250
+ function envFloat(name, fallback) {
56251
+ const raw = process.env[name];
56252
+ if (!raw) return fallback;
56253
+ const n = Number(raw);
56254
+ return Number.isFinite(n) ? n : fallback;
56255
+ }
56256
+ function clamp01(value) {
56257
+ if (!Number.isFinite(value)) return 0;
56258
+ return Math.max(0, Math.min(1, value));
56259
+ }
56260
+ function toDate(value) {
56261
+ return value instanceof Date ? value : new Date(String(value));
56262
+ }
56263
+ function toNullableDate(value) {
56264
+ if (value === null || value === void 0) return null;
56265
+ return toDate(value);
56266
+ }
56267
+ function toStringArray(value) {
56268
+ if (!Array.isArray(value)) return [];
56269
+ return value.filter((v) => typeof v === "string" || typeof v === "number").map((v) => String(v).trim()).filter(Boolean);
56270
+ }
56271
+ function toNumberArray(value) {
56272
+ if (!Array.isArray(value)) return [];
56273
+ return value.map((v) => Number(v)).filter((v) => Number.isInteger(v) && v > 0);
56274
+ }
56275
+ function truncate(text, max) {
56276
+ return text.length <= max ? text : `${text.slice(0, max - 1)}\u2026`;
56277
+ }
56278
+ function normalizeText(text) {
56279
+ return text.toLowerCase().normalize("NFKC").replace(/[^\p{L}\p{N}]+/gu, " ").trim().replace(/\s+/g, " ");
56280
+ }
56281
+ function compactText(text) {
56282
+ return text.toLowerCase().normalize("NFKC").replace(/[^\p{L}\p{N}]+/gu, "");
56283
+ }
56284
+ function tokenSet(name) {
56285
+ return new Set(normalizeText(name).split(" ").filter((t) => t.length > 0));
56286
+ }
56287
+ function jaccard(a, b) {
56288
+ const left = new Set(a);
56289
+ const right = new Set(b);
56290
+ if (left.size === 0 || right.size === 0) return 0;
56291
+ let intersection2 = 0;
56292
+ for (const item of left) {
56293
+ if (right.has(item)) intersection2++;
56294
+ }
56295
+ return intersection2 / (left.size + right.size - intersection2);
56296
+ }
56297
+ function bigramDice(a, b) {
56298
+ const left = compactText(a);
56299
+ const right = compactText(b);
56300
+ if (left.length < 3 || right.length < 3) return 0;
56301
+ if (left === right) return 1;
56302
+ if (left.includes(right) || right.includes(left)) return 0.78;
56303
+ const grams = (s) => {
56304
+ const out = /* @__PURE__ */ new Map();
56305
+ for (let i = 0; i < s.length - 1; i++) {
56306
+ const g = s.slice(i, i + 2);
56307
+ out.set(g, (out.get(g) ?? 0) + 1);
56308
+ }
56309
+ return out;
56310
+ };
56311
+ const g1 = grams(left);
56312
+ const g2 = grams(right);
56313
+ let overlap = 0;
56314
+ for (const [g, count] of g1) {
56315
+ overlap += Math.min(count, g2.get(g) ?? 0);
56316
+ }
56317
+ return 2 * overlap / Math.max(1, left.length + right.length - 2);
56318
+ }
56319
+ function nameSimilarity(a, b) {
56320
+ const tokenScore = jaccard(tokenSet(a), tokenSet(b));
56321
+ const dice = bigramDice(a, b);
56322
+ return Math.max(tokenScore, dice);
56323
+ }
56324
+ function pairKey(a, b) {
56325
+ return a < b ? `${a}:${b}` : `${b}:${a}`;
56326
+ }
56327
+ function uniqueSortedNumbers(values) {
56328
+ return Array.from(new Set(values)).sort((a, b) => a - b);
56329
+ }
56330
+ function tagPayload(tag) {
56331
+ return {
56332
+ id: tag.id,
56333
+ name: tag.name,
56334
+ description: tag.description,
56335
+ memory_count: tag.memoryCount,
56336
+ first_seen: tag.firstSeen?.toISOString() ?? null,
56337
+ last_seen: tag.lastSeen?.toISOString() ?? null,
56338
+ d_tags: tag.dTags.slice(0, 20)
56339
+ };
56340
+ }
56341
+ function isNewOrLowFrequency(tag) {
56342
+ const lowMax = envInt("PROJECT_ALIAS_LOW_COUNT_MAX", 3);
56343
+ const newDays = envInt("PROJECT_ALIAS_NEW_TAG_DAYS", 14);
56344
+ const ageMs = Date.now() - tag.createdAt.getTime();
56345
+ return tag.memoryCount <= lowMax || ageMs <= newDays * 24 * 60 * 60 * 1e3;
56346
+ }
56347
+ function chooseDirection(a, b) {
56348
+ if (a.memoryCount !== b.memoryCount) {
56349
+ return a.memoryCount < b.memoryCount ? { sourceId: a.id, targetId: b.id } : { sourceId: b.id, targetId: a.id };
56350
+ }
56351
+ const aFirst = a.firstSeen?.getTime() ?? a.createdAt.getTime();
56352
+ const bFirst = b.firstSeen?.getTime() ?? b.createdAt.getTime();
56353
+ if (aFirst !== bFirst) {
56354
+ return aFirst > bFirst ? { sourceId: a.id, targetId: b.id } : { sourceId: b.id, targetId: a.id };
56355
+ }
56356
+ return a.id > b.id ? { sourceId: a.id, targetId: b.id } : { sourceId: b.id, targetId: a.id };
56357
+ }
56358
+ function getOrAddPair(pairs, tagsById, aId, bId) {
56359
+ if (aId === bId) return null;
56360
+ const a = tagsById.get(aId);
56361
+ const b = tagsById.get(bId);
56362
+ if (!a || !b) return null;
56363
+ const key = pairKey(aId, bId);
56364
+ const existing = pairs.get(key);
56365
+ if (existing) return existing;
56366
+ const direction = chooseDirection(a, b);
56367
+ const pair = {
56368
+ sourceId: direction.sourceId,
56369
+ targetId: direction.targetId,
56370
+ sources: /* @__PURE__ */ new Set(),
56371
+ explicitStatements: [],
56372
+ score: 0,
56373
+ signals: {}
56374
+ };
56375
+ pairs.set(key, pair);
56376
+ return pair;
56377
+ }
56378
+ function addSource(pair, source) {
56379
+ if (!pair.sources.has(source)) {
56380
+ pair.sources.add(source);
56381
+ pair.score += SOURCE_WEIGHT[source];
56382
+ }
56383
+ }
56384
+ function addExplicitStatement(pair, memory) {
56385
+ addSource(pair, "explicit_user_statement");
56386
+ if (!pair.explicitStatements.some((m) => m.id === memory.id)) {
56387
+ pair.explicitStatements.push(memory);
56388
+ }
56389
+ pair.signals.explicit_statement_ids = pair.explicitStatements.map((m) => m.id);
56390
+ pair.signals.explicit_statement_previews = pair.explicitStatements.map((m) => ({
56391
+ id: m.id,
56392
+ preview: truncate(m.message, 240)
56393
+ }));
56394
+ }
56395
+ function addLowFrequencySignal(pair, tagId) {
56396
+ addSource(pair, "low_frequency_tag");
56397
+ const current = toNumberArray(pair.signals.low_frequency_anchor_ids);
56398
+ pair.signals.low_frequency_anchor_ids = uniqueSortedNumbers([...current, tagId]);
56399
+ }
56400
+ function messageMentionsTag(message, tag) {
56401
+ const lowerMessage = message.toLowerCase();
56402
+ const lowerName = tag.name.toLowerCase();
56403
+ if (lowerName.length >= 3 && lowerMessage.includes(lowerName)) return true;
56404
+ const normalizedName = normalizeText(tag.name);
56405
+ const normalizedMessage = normalizeText(message);
56406
+ if (normalizedName.length >= 3 && normalizedMessage.includes(normalizedName)) return true;
56407
+ const compactName = compactText(tag.name);
56408
+ const compactMessage = compactText(message);
56409
+ return compactName.length >= 3 && compactMessage.includes(compactName);
56410
+ }
56411
+ async function listCanonicalTags(userId) {
56412
+ const maxTags = envInt("PROJECT_ALIAS_MAX_TAGS", 120);
56413
+ const result = await db.query(
56414
+ `SELECT pt.id,
56415
+ pt.name,
56416
+ pt.description,
56417
+ pt.created_at,
56418
+ pt.updated_at,
56419
+ COUNT(m.id)::int AS memory_count,
56420
+ MIN(m.created_at) AS first_seen,
56421
+ MAX(m.created_at) AS last_seen
56422
+ FROM project_tags pt
56423
+ LEFT JOIN memory m
56424
+ ON m.user_id = $1
56425
+ AND m.is_active = TRUE
56426
+ AND m.p_tag_id IS NOT NULL
56427
+ AND canonical_project_tag_id(m.p_tag_id) = pt.id
56428
+ WHERE pt.alias_of IS NULL
56429
+ GROUP BY pt.id
56430
+ ORDER BY MAX(m.created_at) DESC NULLS LAST, pt.created_at DESC
56431
+ LIMIT $2`,
56432
+ [userId, maxTags]
56433
+ );
56434
+ const tags = result.rows.map((row) => ({
56435
+ id: Number(row.id),
56436
+ name: String(row.name),
56437
+ description: row.description === null ? null : String(row.description),
56438
+ createdAt: toDate(row.created_at),
56439
+ updatedAt: toDate(row.updated_at),
56440
+ memoryCount: Number(row.memory_count ?? 0),
56441
+ firstSeen: toNullableDate(row.first_seen),
56442
+ lastSeen: toNullableDate(row.last_seen),
56443
+ dTags: []
56444
+ }));
56445
+ if (tags.length === 0) return tags;
56446
+ const tagIds = tags.map((t) => t.id);
56447
+ const dtagResult = await db.query(
56448
+ `SELECT tag_id, ARRAY_AGG(DISTINCT tag ORDER BY tag) AS d_tags
56449
+ FROM (
56450
+ SELECT canonical_project_tag_id(m.p_tag_id) AS tag_id,
56451
+ dt.tag AS tag
56452
+ FROM memory m
56453
+ CROSS JOIN LATERAL unnest(m.d_tag) AS dt(tag)
56454
+ WHERE m.user_id = $1
56455
+ AND m.is_active = TRUE
56456
+ AND m.p_tag_id IS NOT NULL
56457
+ AND canonical_project_tag_id(m.p_tag_id) = ANY($2::bigint[])
56458
+ AND dt.tag <> ''
56459
+ ) s
56460
+ GROUP BY tag_id`,
56461
+ [userId, tagIds]
56462
+ );
56463
+ const dTagsById = /* @__PURE__ */ new Map();
56464
+ for (const row of dtagResult.rows) {
56465
+ dTagsById.set(Number(row.tag_id), toStringArray(row.d_tags));
56466
+ }
56467
+ for (const tag of tags) {
56468
+ tag.dTags = dTagsById.get(tag.id) ?? [];
56469
+ }
56470
+ return tags;
56471
+ }
56472
+ async function listRepresentativeMemories(userId, tagIds, perTag = 3) {
56473
+ const out = /* @__PURE__ */ new Map();
56474
+ if (tagIds.length === 0) return out;
56475
+ const result = await db.query(
56476
+ `WITH ranked AS (
56477
+ SELECT canonical_project_tag_id(m.p_tag_id) AS tag_id,
56478
+ m.id,
56479
+ m.role,
56480
+ m.message,
56481
+ m.d_tag,
56482
+ m.created_at,
56483
+ ROW_NUMBER() OVER (
56484
+ PARTITION BY canonical_project_tag_id(m.p_tag_id)
56485
+ ORDER BY m.is_pinned DESC, m.created_at DESC
56486
+ ) AS rn
56487
+ FROM memory m
56488
+ WHERE m.user_id = $1
56489
+ AND m.is_active = TRUE
56490
+ AND m.p_tag_id IS NOT NULL
56491
+ AND canonical_project_tag_id(m.p_tag_id) = ANY($2::bigint[])
56492
+ )
56493
+ SELECT tag_id, id, role, message, d_tag, created_at
56494
+ FROM ranked
56495
+ WHERE rn <= $3
56496
+ ORDER BY tag_id, rn`,
56497
+ [userId, tagIds, perTag]
56498
+ );
56499
+ for (const row of result.rows) {
56500
+ const tagId = Number(row.tag_id);
56501
+ const list = out.get(tagId) ?? [];
56502
+ list.push({
56503
+ id: Number(row.id),
56504
+ role: row.role === "assistant" ? "assistant" : "user",
56505
+ message: String(row.message),
56506
+ dTags: toStringArray(row.d_tag),
56507
+ createdAt: toDate(row.created_at)
56508
+ });
56509
+ out.set(tagId, list);
56510
+ }
56511
+ return out;
56512
+ }
56513
+ async function addExplicitStatementCandidates(userId, tags, tagsById, pairs) {
56514
+ const scanLimit = envInt("PROJECT_ALIAS_EXPLICIT_SCAN_LIMIT", 200);
56515
+ const clauses = EXPLICIT_PATTERNS.map((_, i) => `message ILIKE $${i + 2}`).join(" OR ");
56516
+ const limitParam = EXPLICIT_PATTERNS.length + 2;
56517
+ const result = await db.query(
56518
+ `SELECT id,
56519
+ message,
56520
+ d_tag,
56521
+ created_at,
56522
+ canonical_project_tag_id(p_tag_id) AS memory_tag_id
56523
+ FROM memory
56524
+ WHERE user_id = $1
56525
+ AND role = 'user'
56526
+ AND is_active = TRUE
56527
+ AND (${clauses})
56528
+ ORDER BY created_at DESC
56529
+ LIMIT $${limitParam}`,
56530
+ [userId, ...EXPLICIT_PATTERNS, scanLimit]
56531
+ );
56532
+ for (const row of result.rows) {
56533
+ const memory = {
56534
+ id: Number(row.id),
56535
+ role: "user",
56536
+ message: String(row.message),
56537
+ dTags: toStringArray(row.d_tag),
56538
+ createdAt: toDate(row.created_at)
56539
+ };
56540
+ const mentioned = /* @__PURE__ */ new Set();
56541
+ for (const tag of tags) {
56542
+ if (messageMentionsTag(memory.message, tag)) {
56543
+ mentioned.add(tag.id);
56544
+ }
56545
+ }
56546
+ const memoryTagId = row.memory_tag_id === null ? null : Number(row.memory_tag_id);
56547
+ if (memoryTagId && tagsById.has(memoryTagId)) {
56548
+ mentioned.add(memoryTagId);
56549
+ }
56550
+ const ids = Array.from(mentioned);
56551
+ if (ids.length < 2) continue;
56552
+ for (let i = 0; i < ids.length; i++) {
56553
+ for (let j = i + 1; j < ids.length; j++) {
56554
+ const pair = getOrAddPair(pairs, tagsById, ids[i], ids[j]);
56555
+ if (pair) addExplicitStatement(pair, memory);
56556
+ }
56557
+ }
56558
+ }
56559
+ }
56560
+ function addNameAndDtagCandidates(tags, tagsById, pairs) {
56561
+ for (let i = 0; i < tags.length; i++) {
56562
+ for (let j = i + 1; j < tags.length; j++) {
56563
+ const a = tags[i];
56564
+ const b = tags[j];
56565
+ const lowAnchor = isNewOrLowFrequency(a) || isNewOrLowFrequency(b);
56566
+ const nScore = nameSimilarity(a.name, b.name);
56567
+ const dScore = jaccard(a.dTags, b.dTags);
56568
+ const nameThreshold = lowAnchor ? 0.45 : 0.55;
56569
+ const dtagThreshold = lowAnchor ? 0.18 : 0.25;
56570
+ if (nScore >= nameThreshold) {
56571
+ const pair = getOrAddPair(pairs, tagsById, a.id, b.id);
56572
+ if (pair) {
56573
+ addSource(pair, "name_similarity");
56574
+ pair.signals.name_similarity = Math.max(
56575
+ Number(pair.signals.name_similarity ?? 0),
56576
+ Number(nScore.toFixed(4))
56577
+ );
56578
+ if (isNewOrLowFrequency(a)) addLowFrequencySignal(pair, a.id);
56579
+ if (isNewOrLowFrequency(b)) addLowFrequencySignal(pair, b.id);
56580
+ }
56581
+ }
56582
+ if (dScore >= dtagThreshold) {
56583
+ const pair = getOrAddPair(pairs, tagsById, a.id, b.id);
56584
+ if (pair) {
56585
+ addSource(pair, "d_tag_overlap");
56586
+ pair.signals.d_tag_jaccard = Math.max(
56587
+ Number(pair.signals.d_tag_jaccard ?? 0),
56588
+ Number(dScore.toFixed(4))
56589
+ );
56590
+ pair.signals.shared_d_tags = a.dTags.filter((t) => b.dTags.includes(t)).slice(0, 20);
56591
+ if (isNewOrLowFrequency(a)) addLowFrequencySignal(pair, a.id);
56592
+ if (isNewOrLowFrequency(b)) addLowFrequencySignal(pair, b.id);
56593
+ }
56594
+ }
56595
+ }
56596
+ }
56597
+ }
56598
+ async function addVectorCandidates(userId, tags, tagsById, pairs) {
56599
+ const anchorLimit = envInt("PROJECT_ALIAS_VECTOR_ANCHOR_LIMIT", 12);
56600
+ const representativeLimit = envInt("PROJECT_ALIAS_VECTOR_REPRESENTATIVE_LIMIT", 5);
56601
+ const perMemoryLimit = envInt("PROJECT_ALIAS_VECTOR_PER_MEMORY_LIMIT", 8);
56602
+ const perTagLimit = envInt("PROJECT_ALIAS_VECTOR_TARGET_LIMIT", 3);
56603
+ const maxDistance = envFloat("PROJECT_ALIAS_VECTOR_MAX_DISTANCE", 0.28);
56604
+ const anchors = tags.slice().sort((a, b) => {
56605
+ const aLow = isNewOrLowFrequency(a) ? 1 : 0;
56606
+ const bLow = isNewOrLowFrequency(b) ? 1 : 0;
56607
+ if (aLow !== bLow) return bLow - aLow;
56608
+ return (b.lastSeen?.getTime() ?? b.createdAt.getTime()) - (a.lastSeen?.getTime() ?? a.createdAt.getTime());
56609
+ }).slice(0, anchorLimit);
56610
+ for (const anchor of anchors) {
56611
+ const result = await db.query(
56612
+ `WITH src AS (
56613
+ SELECT id, embedding
56614
+ FROM memory
56615
+ WHERE user_id = $1
56616
+ AND is_active = TRUE
56617
+ AND p_tag_id IS NOT NULL
56618
+ AND embedding IS NOT NULL
56619
+ AND canonical_project_tag_id(p_tag_id) = $2
56620
+ ORDER BY is_pinned DESC, created_at DESC
56621
+ LIMIT $3
56622
+ ),
56623
+ neighbors AS (
56624
+ SELECT canonical_project_tag_id(m.p_tag_id) AS target_tag_id,
56625
+ m.id AS memory_id,
56626
+ src.id AS source_memory_id,
56627
+ (m.embedding <=> src.embedding) AS distance
56628
+ FROM src
56629
+ JOIN LATERAL (
56630
+ SELECT id, p_tag_id, embedding
56631
+ FROM memory
56632
+ WHERE user_id = $1
56633
+ AND is_active = TRUE
56634
+ AND p_tag_id IS NOT NULL
56635
+ AND embedding IS NOT NULL
56636
+ AND canonical_project_tag_id(p_tag_id) <> $2
56637
+ ORDER BY embedding <=> src.embedding
56638
+ LIMIT $4
56639
+ ) m ON TRUE
56640
+ )
56641
+ SELECT target_tag_id,
56642
+ MIN(distance)::float8 AS min_distance,
56643
+ (ARRAY_AGG(memory_id ORDER BY distance))[1:5] AS evidence_memory_ids,
56644
+ (ARRAY_AGG(source_memory_id ORDER BY distance))[1:5] AS source_memory_ids
56645
+ FROM neighbors
56646
+ WHERE target_tag_id IS NOT NULL
56647
+ GROUP BY target_tag_id
56648
+ HAVING MIN(distance) <= $5
56649
+ ORDER BY MIN(distance)
56650
+ LIMIT $6`,
56651
+ [userId, anchor.id, representativeLimit, perMemoryLimit, maxDistance, perTagLimit]
56652
+ );
56653
+ for (const row of result.rows) {
56654
+ const targetId = Number(row.target_tag_id);
56655
+ if (!tagsById.has(targetId)) continue;
56656
+ const pair = getOrAddPair(pairs, tagsById, anchor.id, targetId);
56657
+ if (!pair) continue;
56658
+ addSource(pair, "vector_neighbor");
56659
+ if (isNewOrLowFrequency(anchor)) addLowFrequencySignal(pair, anchor.id);
56660
+ const vectorSignals = Array.isArray(pair.signals.vector_neighbors) ? pair.signals.vector_neighbors : [];
56661
+ vectorSignals.push({
56662
+ source_tag_id: anchor.id,
56663
+ target_tag_id: targetId,
56664
+ min_distance: Number(Number(row.min_distance).toFixed(6)),
56665
+ evidence_memory_ids: toNumberArray(row.evidence_memory_ids),
56666
+ source_memory_ids: toNumberArray(row.source_memory_ids)
56667
+ });
56668
+ pair.signals.vector_neighbors = vectorSignals;
56669
+ const previous = Number(pair.signals.vector_min_distance ?? Number.POSITIVE_INFINITY);
56670
+ pair.signals.vector_min_distance = Math.min(previous, Number(row.min_distance));
56671
+ }
56672
+ }
56673
+ }
56674
+ async function buildCandidatePairs(userId, tags, limit2) {
56675
+ const tagsById = new Map(tags.map((tag) => [tag.id, tag]));
56676
+ const pairs = /* @__PURE__ */ new Map();
56677
+ await addExplicitStatementCandidates(userId, tags, tagsById, pairs);
56678
+ addNameAndDtagCandidates(tags, tagsById, pairs);
56679
+ await addVectorCandidates(userId, tags, tagsById, pairs);
56680
+ for (const pair of pairs.values()) {
56681
+ const source = tagsById.get(pair.sourceId);
56682
+ const target = tagsById.get(pair.targetId);
56683
+ if (source && target) {
56684
+ pair.signals.source_tag = tagPayload(source);
56685
+ pair.signals.target_tag = tagPayload(target);
56686
+ pair.signals.candidate_sources = Array.from(pair.sources);
56687
+ }
56688
+ }
56689
+ return Array.from(pairs.values()).sort((a, b) => {
56690
+ const aExplicit = a.sources.has("explicit_user_statement") ? 1 : 0;
56691
+ const bExplicit = b.sources.has("explicit_user_statement") ? 1 : 0;
56692
+ if (aExplicit !== bExplicit) return bExplicit - aExplicit;
56693
+ return b.score - a.score;
56694
+ }).slice(0, limit2);
56695
+ }
56696
+ function formatMemoryForPrompt(memory) {
56697
+ return {
56698
+ id: memory.id,
56699
+ role: memory.role,
56700
+ created_at: memory.createdAt.toISOString(),
56701
+ d_tags: memory.dTags.slice(0, 8),
56702
+ message: truncate(memory.message, 700)
56703
+ };
56704
+ }
56705
+ function collectAllowedMemoryIds(pair, sourceMemories, targetMemories) {
56706
+ return /* @__PURE__ */ new Set([
56707
+ ...pair.explicitStatements.map((m) => m.id),
56708
+ ...sourceMemories.map((m) => m.id),
56709
+ ...targetMemories.map((m) => m.id)
56710
+ ]);
56711
+ }
56712
+ function parseJsonObject(raw) {
56713
+ const cleaned = raw.replace(/<think>[\s\S]*?<\/think>/g, "").replace(/```json|```/g, "").trim();
56714
+ try {
56715
+ return JSON.parse(cleaned);
56716
+ } catch {
56717
+ const start = cleaned.indexOf("{");
56718
+ const end = cleaned.lastIndexOf("}");
56719
+ if (start >= 0 && end > start) {
56720
+ return JSON.parse(cleaned.slice(start, end + 1));
56721
+ }
56722
+ throw new Error(`invalid JSON: ${cleaned.slice(0, 240)}`);
56723
+ }
56724
+ }
56725
+ function normalizeJudgment(raw, allowedIds) {
56726
+ const parsed = parseJsonObject(raw);
56727
+ let relation = String(parsed.relation ?? "insufficient").trim();
56728
+ if (!ALL_RELATIONS.has(relation)) relation = "insufficient";
56729
+ const directionIsBoolean = typeof parsed.source_should_alias_target === "boolean";
56730
+ if (QUALIFYING_RELATIONS.has(relation) && !directionIsBoolean) {
56731
+ relation = "insufficient";
56732
+ }
56733
+ const evidence = toNumberArray(parsed.evidence_memory_ids).filter((id) => allowedIds.has(id));
56734
+ const conflicts = toNumberArray(parsed.conflict_memory_ids).filter((id) => allowedIds.has(id));
56735
+ const confidence = clamp01(Number(parsed.confidence ?? 0));
56736
+ const sameProject = typeof parsed.same_project === "boolean" ? parsed.same_project : QUALIFYING_RELATIONS.has(relation);
56737
+ return {
56738
+ relation,
56739
+ same_project: sameProject,
56740
+ source_should_alias_target: directionIsBoolean ? Boolean(parsed.source_should_alias_target) : true,
56741
+ confidence,
56742
+ evidence_memory_ids: evidence,
56743
+ conflict_memory_ids: conflicts,
56744
+ rationale: typeof parsed.rationale === "string" ? parsed.rationale : ""
56745
+ };
56746
+ }
56747
+ async function judgePair(pair, tagsById, memoriesByTag) {
56748
+ const source = tagsById.get(pair.sourceId);
56749
+ const target = tagsById.get(pair.targetId);
56750
+ if (!source || !target) {
56751
+ throw new Error("candidate pair references missing tag");
56752
+ }
56753
+ const sourceMemories = memoriesByTag.get(source.id) ?? [];
56754
+ const targetMemories = memoriesByTag.get(target.id) ?? [];
56755
+ const allowedIds = collectAllowedMemoryIds(pair, sourceMemories, targetMemories);
56756
+ const userPrompt = JSON.stringify(
56757
+ {
56758
+ task: "Judge whether Tag A and Tag B are the same project identity.",
56759
+ candidate_sources: Array.from(pair.sources),
56760
+ signals: pair.signals,
56761
+ tag_a_source_candidate: tagPayload(source),
56762
+ tag_b_target_candidate: tagPayload(target),
56763
+ tag_a_representative_memories: sourceMemories.map(formatMemoryForPrompt),
56764
+ tag_b_representative_memories: targetMemories.map(formatMemoryForPrompt),
56765
+ explicit_user_statements: pair.explicitStatements.map(formatMemoryForPrompt)
56766
+ },
56767
+ null,
56768
+ 2
56769
+ );
56770
+ const spec = ROLE_REGISTRY.project_alias_judge;
56771
+ const raw = await callSpec(spec, {
56772
+ system: JUDGE_SYSTEM_PROMPT,
56773
+ user: userPrompt,
56774
+ ...spec.provider === "local" ? {} : { responseFormat: "json" },
56775
+ maxTokens: envInt("PROJECT_ALIAS_JUDGE_MAX_TOKENS", 8192)
56776
+ });
56777
+ if (!raw) throw new Error("project_alias_judge returned empty content");
56778
+ return normalizeJudgment(raw, allowedIds);
56779
+ }
56780
+ async function areCanonicalRoots(tagIds) {
56781
+ if (tagIds.length === 0) return false;
56782
+ const result = await db.query(
56783
+ `SELECT id, alias_of
56784
+ FROM project_tags
56785
+ WHERE id = ANY($1::bigint[])`,
56786
+ [tagIds]
56787
+ );
56788
+ if (result.rows.length !== tagIds.length) return false;
56789
+ return result.rows.every((row) => row.alias_of === null);
56790
+ }
56791
+ async function hasRecentRejectedPair(userId, sourceId, targetId) {
56792
+ const rejectWindowDays = envInt("PROJECT_ALIAS_REJECT_WINDOW_DAYS", 30);
56793
+ const result = await db.query(
56794
+ `SELECT 1
56795
+ FROM project_tag_alias_suggestions
56796
+ WHERE user_id = $1
56797
+ AND status = 'rejected'
56798
+ AND LEAST(source_tag_id, target_tag_id) = LEAST($2::bigint, $3::bigint)
56799
+ AND GREATEST(source_tag_id, target_tag_id) = GREATEST($2::bigint, $3::bigint)
56800
+ AND COALESCE(decided_at, updated_at, created_at) >= NOW() - ($4 || ' days')::interval
56801
+ LIMIT 1`,
56802
+ [userId, sourceId, targetId, String(rejectWindowDays)]
56803
+ );
56804
+ return result.rows.length > 0;
56805
+ }
56806
+ async function computeAutoApplyEligible(userId, sourceId, targetId, pair, judgment) {
56807
+ if (!pair.sources.has("explicit_user_statement")) return false;
56808
+ if (!QUALIFYING_RELATIONS.has(judgment.relation)) return false;
56809
+ if (!judgment.same_project) return false;
56810
+ if (judgment.confidence < 0.98) return false;
56811
+ if (judgment.conflict_memory_ids.length > 0) return false;
56812
+ if (!await areCanonicalRoots([sourceId, targetId])) return false;
56813
+ if (await hasRecentRejectedPair(userId, sourceId, targetId)) return false;
56814
+ return true;
56815
+ }
56816
+ async function upsertSuggestion(userId, sourceId, targetId, pair, judgment, autoApplyEligible) {
56817
+ const spec = ROLE_REGISTRY.project_alias_judge;
56818
+ const candidateSources = Array.from(pair.sources);
56819
+ const signals = {
56820
+ ...pair.signals,
56821
+ llm: {
56822
+ relation: judgment.relation,
56823
+ same_project: judgment.same_project,
56824
+ source_should_alias_target: judgment.source_should_alias_target,
56825
+ stored_source_tag_id: sourceId,
56826
+ stored_target_tag_id: targetId,
56827
+ direction_reversed_from_candidate: sourceId !== pair.sourceId || targetId !== pair.targetId
56828
+ }
56829
+ };
56830
+ const result = await db.query(
56831
+ `INSERT INTO project_tag_alias_suggestions (
56832
+ user_id,
56833
+ source_tag_id,
56834
+ target_tag_id,
56835
+ relation,
56836
+ status,
56837
+ confidence,
56838
+ candidate_sources,
56839
+ evidence_memory_ids,
56840
+ conflict_memory_ids,
56841
+ signals,
56842
+ model_provider,
56843
+ model_name,
56844
+ rationale,
56845
+ auto_apply_eligible
56846
+ )
56847
+ VALUES (
56848
+ $1, $2, $3, $4, 'pending', $5,
56849
+ $6::text[], $7::bigint[], $8::bigint[], $9::jsonb,
56850
+ $10, $11, $12, $13
56851
+ )
56852
+ ON CONFLICT (
56853
+ user_id,
56854
+ (LEAST(source_tag_id, target_tag_id)),
56855
+ (GREATEST(source_tag_id, target_tag_id))
56856
+ )
56857
+ WHERE status = 'pending'
56858
+ DO UPDATE SET
56859
+ source_tag_id = EXCLUDED.source_tag_id,
56860
+ target_tag_id = EXCLUDED.target_tag_id,
56861
+ relation = EXCLUDED.relation,
56862
+ confidence = EXCLUDED.confidence,
56863
+ candidate_sources = EXCLUDED.candidate_sources,
56864
+ evidence_memory_ids = EXCLUDED.evidence_memory_ids,
56865
+ conflict_memory_ids = EXCLUDED.conflict_memory_ids,
56866
+ signals = EXCLUDED.signals,
56867
+ model_provider = EXCLUDED.model_provider,
56868
+ model_name = EXCLUDED.model_name,
56869
+ rationale = EXCLUDED.rationale,
56870
+ auto_apply_eligible = EXCLUDED.auto_apply_eligible,
56871
+ updated_at = NOW()
56872
+ RETURNING id`,
56873
+ [
56874
+ userId,
56875
+ sourceId,
56876
+ targetId,
56877
+ judgment.relation,
56878
+ Number(judgment.confidence.toFixed(4)),
56879
+ candidateSources,
56880
+ judgment.evidence_memory_ids,
56881
+ judgment.conflict_memory_ids,
56882
+ JSON.stringify(signals),
56883
+ spec.provider,
56884
+ spec.model_name,
56885
+ truncate(judgment.rationale, 4e3),
56886
+ autoApplyEligible
56887
+ ]
56888
+ );
56889
+ return Number(result.rows[0].id);
56890
+ }
56891
+ function qualifiesForPending(judgment, minConfidence) {
56892
+ return QUALIFYING_RELATIONS.has(judgment.relation) && judgment.same_project && judgment.confidence >= minConfidence;
56893
+ }
56894
+ async function runProjectAliasPromoter(opts = {}) {
56895
+ const userId = opts.userId ?? await getDefaultUserId();
56896
+ const candidateLimit = opts.limit ?? envInt("PROJECT_ALIAS_CANDIDATE_LIMIT", 12);
56897
+ const pendingMin = envFloat("PROJECT_ALIAS_PENDING_MIN", 0.85);
56898
+ const summary = {
56899
+ userId,
56900
+ candidates: 0,
56901
+ judged: 0,
56902
+ inserted: 0,
56903
+ autoApplied: 0,
56904
+ skipped: 0,
56905
+ errors: 0
56906
+ };
56907
+ const tags = await listCanonicalTags(userId);
56908
+ if (tags.length < 2) return summary;
56909
+ const tagsById = new Map(tags.map((tag) => [tag.id, tag]));
56910
+ const pairs = await buildCandidatePairs(userId, tags, candidateLimit);
56911
+ summary.candidates = pairs.length;
56912
+ if (pairs.length === 0) return summary;
56913
+ const tagIdsForPrompt = uniqueSortedNumbers(
56914
+ pairs.flatMap((pair) => [pair.sourceId, pair.targetId])
56915
+ );
56916
+ const memoriesByTag = await listRepresentativeMemories(userId, tagIdsForPrompt, 3);
56917
+ for (const pair of pairs) {
56918
+ try {
56919
+ const judgment = await judgePair(pair, tagsById, memoriesByTag);
56920
+ summary.judged++;
56921
+ if (!qualifiesForPending(judgment, pendingMin)) {
56922
+ summary.skipped++;
56923
+ continue;
56924
+ }
56925
+ let sourceId = pair.sourceId;
56926
+ let targetId = pair.targetId;
56927
+ if (!judgment.source_should_alias_target) {
56928
+ sourceId = pair.targetId;
56929
+ targetId = pair.sourceId;
56930
+ }
56931
+ if (sourceId === targetId) {
56932
+ summary.skipped++;
56933
+ continue;
56934
+ }
56935
+ const autoApplyEligible = await computeAutoApplyEligible(
56936
+ userId,
56937
+ sourceId,
56938
+ targetId,
56939
+ pair,
56940
+ judgment
56941
+ );
56942
+ const suggestionId = await upsertSuggestion(
56943
+ userId,
56944
+ sourceId,
56945
+ targetId,
56946
+ pair,
56947
+ judgment,
56948
+ autoApplyEligible
56949
+ );
56950
+ summary.inserted++;
56951
+ if (process.env.PROJECT_ALIAS_AUTO_APPLY === "true" && autoApplyEligible) {
56952
+ const applied = await applyAliasSuggestion(suggestionId, {
56953
+ decidedBy: "system",
56954
+ reason: "auto-apply eligible: explicit user rename/alias statement and high-confidence judge result"
56955
+ });
56956
+ if (applied.applied) summary.autoApplied++;
56957
+ }
56958
+ } catch (err2) {
56959
+ summary.errors++;
56960
+ console.error("[ProjectAliasPromoter] pair failed:", err2);
56961
+ }
56962
+ }
56963
+ return summary;
56964
+ }
56965
+ async function maybeRunProjectAliasPromoter() {
56966
+ if (process.env.PROJECT_ALIAS_PROMOTER_ENABLED !== "true") return;
56967
+ if (projectAliasPromoterRunning) return;
56968
+ projectAliasPromoterRunning = true;
56969
+ const intervalHours = envInt(
56970
+ "PROJECT_ALIAS_PROMOTER_INTERVAL_HOURS",
56971
+ PROJECT_ALIAS_PROMOTER_DEFAULT_INTERVAL_HOURS
56972
+ );
56973
+ try {
56974
+ const userId = await getDefaultUserId();
56975
+ const result = await db.query(
56976
+ `SELECT project_alias_promoter_last_run_at
56977
+ FROM users
56978
+ WHERE user_id = $1`,
56979
+ [userId]
56980
+ );
56981
+ const lastRunAt = result.rows[0]?.project_alias_promoter_last_run_at ?? null;
56982
+ const cooldownMs = intervalHours * 60 * 60 * 1e3;
56983
+ const cooldownPassed = lastRunAt === null || Date.now() - new Date(lastRunAt).getTime() >= cooldownMs;
56984
+ if (!cooldownPassed) return;
56985
+ await db.query(
56986
+ `UPDATE users
56987
+ SET project_alias_promoter_last_run_at = NOW()
56988
+ WHERE user_id = $1`,
56989
+ [userId]
56990
+ );
56991
+ const summary = await runProjectAliasPromoter({ userId });
56992
+ if (summary.candidates > 0 || summary.inserted > 0 || summary.errors > 0) {
56993
+ console.error(
56994
+ `\u{1F501} [ProjectAliasPromoter] done \u2014 candidates=${summary.candidates}, judged=${summary.judged}, inserted=${summary.inserted}, autoApplied=${summary.autoApplied}, skipped=${summary.skipped}, errors=${summary.errors}`
56995
+ );
56996
+ }
56997
+ } catch (err2) {
56998
+ console.error(
56999
+ `\u26A0\uFE0F [ProjectAliasPromoter] run failed (retries in ${intervalHours}h):`,
57000
+ err2
57001
+ );
57002
+ } finally {
57003
+ projectAliasPromoterRunning = false;
57004
+ }
57005
+ }
57006
+ async function markSuggestionTerminal(client2, suggestionId, status, decidedBy, reason) {
57007
+ await client2.query(
57008
+ `UPDATE project_tag_alias_suggestions
57009
+ SET status = $2,
57010
+ decided_by = $3,
57011
+ decision_reason = $4,
57012
+ decided_at = NOW(),
57013
+ updated_at = NOW()
57014
+ WHERE id = $1`,
57015
+ [suggestionId, status, decidedBy, reason]
57016
+ );
57017
+ }
57018
+ async function targetChainContainsSource(client2, sourceId, targetId) {
57019
+ const result = await client2.query(
57020
+ `WITH RECURSIVE chain(id, alias_of, path) AS (
57021
+ SELECT id, alias_of, ARRAY[id]::bigint[]
57022
+ FROM project_tags
57023
+ WHERE id = $2
57024
+ UNION ALL
57025
+ SELECT pt.id, pt.alias_of, chain.path || pt.id
57026
+ FROM project_tags pt
57027
+ JOIN chain ON pt.id = chain.alias_of
57028
+ WHERE NOT pt.id = ANY(chain.path)
57029
+ )
57030
+ SELECT 1
57031
+ FROM chain
57032
+ WHERE id = $1
57033
+ LIMIT 1`,
57034
+ [sourceId, targetId]
57035
+ );
57036
+ return result.rows.length > 0;
57037
+ }
57038
+ async function applyAliasSuggestion(suggestionId, opts = {}) {
57039
+ const decidedBy = opts.decidedBy ?? "system";
57040
+ const reason = opts.reason ?? (decidedBy === "user" ? "confirmed by user" : "auto-applied by system");
57041
+ const appliedStatus = decidedBy === "system" ? "auto_applied" : "confirmed";
57042
+ const client2 = await db.getClient();
57043
+ try {
57044
+ await client2.query("BEGIN");
57045
+ const suggestionResult = await client2.query(
57046
+ `SELECT *
57047
+ FROM project_tag_alias_suggestions
57048
+ WHERE id = $1
57049
+ FOR UPDATE`,
57050
+ [suggestionId]
57051
+ );
57052
+ if (suggestionResult.rows.length === 0) {
57053
+ await client2.query("ROLLBACK");
57054
+ return { suggestionId, applied: false, reason: "suggestion_not_found" };
57055
+ }
57056
+ const suggestion = suggestionResult.rows[0];
57057
+ if (suggestion.status !== "pending") {
57058
+ await client2.query("ROLLBACK");
57059
+ return {
57060
+ suggestionId,
57061
+ applied: false,
57062
+ status: String(suggestion.status),
57063
+ reason: "already_decided"
57064
+ };
57065
+ }
57066
+ const sourceId = Number(suggestion.source_tag_id);
57067
+ const targetId = Number(suggestion.target_tag_id);
57068
+ const roots = await client2.query(
57069
+ `SELECT canonical_project_tag_id($1::bigint) AS source_root,
57070
+ canonical_project_tag_id($2::bigint) AS target_root`,
57071
+ [sourceId, targetId]
57072
+ );
57073
+ const sourceRoot = Number(roots.rows[0]?.source_root);
57074
+ const targetRoot = Number(roots.rows[0]?.target_root);
57075
+ await client2.query(
57076
+ `SELECT id
57077
+ FROM project_tags
57078
+ WHERE id = ANY($1::bigint[])
57079
+ FOR UPDATE`,
57080
+ [[sourceId, targetRoot]]
57081
+ );
57082
+ if (!Number.isInteger(sourceRoot) || sourceRoot !== sourceId) {
57083
+ const msg = "source tag is no longer a canonical root";
57084
+ await markSuggestionTerminal(client2, suggestionId, "superseded", "system", msg);
57085
+ await client2.query("COMMIT");
57086
+ return { suggestionId, applied: false, status: "superseded", reason: msg };
57087
+ }
57088
+ if (!Number.isInteger(targetRoot) || targetRoot === sourceId) {
57089
+ const msg = "alias would point source to itself";
57090
+ await markSuggestionTerminal(client2, suggestionId, "rejected", decidedBy, msg);
57091
+ await client2.query("COMMIT");
57092
+ return { suggestionId, applied: false, status: "rejected", reason: msg };
57093
+ }
57094
+ if (await targetChainContainsSource(client2, sourceId, targetId)) {
57095
+ const msg = "alias would create a project_tag alias cycle";
57096
+ await markSuggestionTerminal(client2, suggestionId, "rejected", decidedBy, msg);
57097
+ await client2.query("COMMIT");
57098
+ return { suggestionId, applied: false, status: "rejected", reason: msg };
57099
+ }
57100
+ await client2.query(
57101
+ `UPDATE project_tags
57102
+ SET alias_of = $1,
57103
+ updated_at = NOW()
57104
+ WHERE id = $2`,
57105
+ [targetRoot, sourceId]
57106
+ );
57107
+ await client2.query(
57108
+ `UPDATE project_tag_alias_suggestions
57109
+ SET status = $2,
57110
+ decided_by = $3,
57111
+ decision_reason = $4,
57112
+ decided_at = NOW(),
57113
+ applied_at = NOW(),
57114
+ updated_at = NOW()
57115
+ WHERE id = $1`,
57116
+ [suggestionId, appliedStatus, decidedBy, reason]
57117
+ );
57118
+ await client2.query(
57119
+ `UPDATE project_tag_alias_suggestions
57120
+ SET status = 'superseded',
57121
+ decided_by = 'system',
57122
+ decision_reason = $4,
57123
+ decided_at = NOW(),
57124
+ updated_at = NOW()
57125
+ WHERE user_id = $1
57126
+ AND status = 'pending'
57127
+ AND id <> $2
57128
+ AND (source_tag_id = $3 OR target_tag_id = $3)`,
57129
+ [
57130
+ Number(suggestion.user_id),
57131
+ suggestionId,
57132
+ sourceId,
57133
+ `source tag ${sourceId} was aliased by suggestion ${suggestionId}`
57134
+ ]
57135
+ );
57136
+ await client2.query("COMMIT");
57137
+ invalidateCandidateCache();
57138
+ return { suggestionId, applied: true, status: appliedStatus };
57139
+ } catch (err2) {
57140
+ try {
57141
+ await client2.query("ROLLBACK");
57142
+ } catch {
57143
+ }
57144
+ throw err2;
57145
+ } finally {
57146
+ client2.release();
57147
+ }
57148
+ }
57149
+
57150
+ // src/tools/manage_project_tags.ts
57151
+ var DEFAULT_LIMIT2 = 20;
57152
+ var MAX_LIMIT = 100;
57153
+ var statusSchema = external_exports.enum([
57154
+ "pending",
57155
+ "confirmed",
57156
+ "rejected",
57157
+ "auto_applied",
57158
+ "superseded",
57159
+ "expired"
57160
+ ]);
57161
+ function ok2(payload) {
57162
+ return {
57163
+ content: [{ type: "text", text: JSON.stringify(payload, null, 2) }]
57164
+ };
57165
+ }
57166
+ function toolError(message, extra) {
57167
+ return {
57168
+ content: [{ type: "text", text: JSON.stringify({ error: message, ...extra }, null, 2) }]
57169
+ };
57170
+ }
57171
+ function normalizeTagName(name) {
57172
+ const normalized = name?.toLowerCase().trim();
57173
+ return normalized ? normalized : null;
57174
+ }
57175
+ function errorDetail(err2) {
57176
+ return err2 instanceof Error ? err2.message : String(err2);
57177
+ }
57178
+ async function lookupProjectTagByName(name) {
57179
+ const result = await db.query(
57180
+ `SELECT id, name, alias_of
57181
+ FROM project_tags
57182
+ WHERE name = $1
57183
+ LIMIT 1`,
57184
+ [name]
57185
+ );
57186
+ if (result.rows.length === 0) return null;
57187
+ const row = result.rows[0];
57188
+ return {
57189
+ id: Number(row.id),
57190
+ name: String(row.name),
57191
+ aliasOf: row.alias_of == null ? null : Number(row.alias_of)
57192
+ };
57193
+ }
57194
+ async function lookupSuggestionForUser(userId, suggestionId) {
57195
+ const result = await db.query(
57196
+ `SELECT id, status
57197
+ FROM project_tag_alias_suggestions
57198
+ WHERE id = $1
57199
+ AND user_id = $2
57200
+ LIMIT 1`,
57201
+ [suggestionId, userId]
57202
+ );
57203
+ if (result.rows.length === 0) return null;
57204
+ return {
57205
+ id: Number(result.rows[0].id),
57206
+ status: String(result.rows[0].status)
57207
+ };
57208
+ }
57209
+ async function listSuggestions(userId, status, limit2) {
57210
+ const result = await db.query(
57211
+ `SELECT s.id,
57212
+ source.name AS source_tag_name,
57213
+ target.name AS target_tag_name,
57214
+ s.relation,
57215
+ s.confidence,
57216
+ s.rationale,
57217
+ s.created_at
57218
+ FROM project_tag_alias_suggestions s
57219
+ JOIN project_tags source ON source.id = s.source_tag_id
57220
+ JOIN project_tags target ON target.id = s.target_tag_id
57221
+ WHERE s.user_id = $1
57222
+ AND s.status = $2
57223
+ ORDER BY s.created_at DESC
57224
+ LIMIT $3`,
57225
+ [userId, status, limit2]
57226
+ );
57227
+ const suggestions = result.rows.map((row) => ({
57228
+ suggestion_id: Number(row.id),
57229
+ source_tag: String(row.source_tag_name),
57230
+ target_tag: String(row.target_tag_name),
57231
+ relation: String(row.relation),
57232
+ confidence: Number(row.confidence),
57233
+ rationale: String(row.rationale ?? ""),
57234
+ created_at: row.created_at
57235
+ }));
57236
+ return ok2({
57237
+ action: "list_suggestions",
57238
+ status,
57239
+ limit: limit2,
57240
+ count: suggestions.length,
57241
+ suggestions
57242
+ });
57243
+ }
57244
+ async function confirmAlias(userId, suggestionId, reason) {
57245
+ const suggestion = await lookupSuggestionForUser(userId, suggestionId);
57246
+ if (!suggestion) {
57247
+ return toolError("suggestion_id not found for current user", { suggestion_id: suggestionId });
57248
+ }
57249
+ if (suggestion.status !== "pending") {
57250
+ return toolError("suggestion is not pending", {
57251
+ suggestion_id: suggestionId,
57252
+ status: suggestion.status
57253
+ });
57254
+ }
57255
+ const result = await applyAliasSuggestion(suggestionId, {
57256
+ decidedBy: "user",
57257
+ reason
57258
+ });
57259
+ return ok2({
57260
+ action: "confirm_alias",
57261
+ ...result
57262
+ });
57263
+ }
57264
+ async function rejectAlias(userId, suggestionId, reason) {
57265
+ const suggestion = await lookupSuggestionForUser(userId, suggestionId);
57266
+ if (!suggestion) {
57267
+ return toolError("suggestion_id not found for current user", { suggestion_id: suggestionId });
57268
+ }
57269
+ if (suggestion.status !== "pending") {
57270
+ return toolError("suggestion is not pending", {
57271
+ suggestion_id: suggestionId,
57272
+ status: suggestion.status
57273
+ });
57274
+ }
57275
+ const result = await db.query(
57276
+ `UPDATE project_tag_alias_suggestions
57277
+ SET status = 'rejected',
57278
+ decided_by = 'user',
57279
+ decision_reason = $3,
57280
+ decided_at = NOW(),
57281
+ updated_at = NOW()
57282
+ WHERE id = $1
57283
+ AND user_id = $2
57284
+ AND status = 'pending'
57285
+ RETURNING id, status, decision_reason, decided_at`,
57286
+ [suggestionId, userId, reason ?? null]
57287
+ );
57288
+ if (result.rows.length === 0) {
57289
+ return toolError("suggestion could not be rejected because it is no longer pending", {
57290
+ suggestion_id: suggestionId
57291
+ });
57292
+ }
57293
+ const row = result.rows[0];
57294
+ return ok2({
57295
+ action: "reject_alias",
57296
+ suggestion_id: Number(row.id),
57297
+ rejected: true,
57298
+ status: String(row.status),
57299
+ reason: row.decision_reason,
57300
+ decided_at: row.decided_at
57301
+ });
57302
+ }
57303
+ async function setAlias(userId, sourceTagName, targetTagName, reason) {
57304
+ const sourceName = normalizeTagName(sourceTagName);
57305
+ const targetName = normalizeTagName(targetTagName);
57306
+ if (!sourceName) return toolError("source_tag is required for set_alias");
57307
+ if (!targetName) return toolError("target_tag is required for set_alias");
57308
+ const sourceTag = await lookupProjectTagByName(sourceName);
57309
+ if (!sourceTag) {
57310
+ return toolError("source_tag not found in project_tags", { source_tag: sourceName });
57311
+ }
57312
+ const targetTag = await lookupProjectTagByName(targetName);
57313
+ if (!targetTag) {
57314
+ return toolError("target_tag not found in project_tags", { target_tag: targetName });
57315
+ }
57316
+ if (sourceTag.id === targetTag.id) {
57317
+ return toolError("source_tag and target_tag must be different", {
57318
+ source_tag: sourceTag.name,
57319
+ target_tag: targetTag.name
57320
+ });
57321
+ }
57322
+ const insertResult = await db.query(
57323
+ `INSERT INTO project_tag_alias_suggestions (
57324
+ user_id,
57325
+ source_tag_id,
57326
+ target_tag_id,
57327
+ relation,
57328
+ status,
57329
+ confidence,
57330
+ candidate_sources,
57331
+ signals,
57332
+ rationale,
57333
+ decided_by
57334
+ )
57335
+ VALUES (
57336
+ $1, $2, $3, 'alias', 'pending', 1.0,
57337
+ ARRAY['manual_set_alias']::text[],
57338
+ $4::jsonb,
57339
+ 'manual set_alias',
57340
+ 'user'
57341
+ )
57342
+ ON CONFLICT (
57343
+ user_id,
57344
+ (LEAST(source_tag_id, target_tag_id)),
57345
+ (GREATEST(source_tag_id, target_tag_id))
57346
+ )
57347
+ WHERE status = 'pending'
57348
+ DO UPDATE SET
57349
+ source_tag_id = EXCLUDED.source_tag_id,
57350
+ target_tag_id = EXCLUDED.target_tag_id,
57351
+ relation = EXCLUDED.relation,
57352
+ confidence = EXCLUDED.confidence,
57353
+ candidate_sources = EXCLUDED.candidate_sources,
57354
+ signals = EXCLUDED.signals,
57355
+ rationale = EXCLUDED.rationale,
57356
+ decided_by = EXCLUDED.decided_by,
57357
+ decision_reason = NULL,
57358
+ decided_at = NULL,
57359
+ applied_at = NULL,
57360
+ updated_at = NOW()
57361
+ RETURNING id`,
57362
+ [
57363
+ userId,
57364
+ sourceTag.id,
57365
+ targetTag.id,
57366
+ JSON.stringify({
57367
+ manual: true,
57368
+ action: "set_alias",
57369
+ source_tag: sourceTag.name,
57370
+ target_tag: targetTag.name
57371
+ })
57372
+ ]
57373
+ );
57374
+ const suggestionId = Number(insertResult.rows[0].id);
57375
+ const result = await applyAliasSuggestion(suggestionId, {
57376
+ decidedBy: "user",
57377
+ reason: reason ?? "manual set_alias"
57378
+ });
57379
+ return ok2({
57380
+ action: "set_alias",
57381
+ source_tag: sourceTag.name,
57382
+ target_tag: targetTag.name,
57383
+ ...result
57384
+ });
57385
+ }
57386
+ async function unsetAlias(userId, tagName) {
57387
+ const normalized = normalizeTagName(tagName);
57388
+ if (!normalized) return toolError("tag is required for unset_alias");
57389
+ const tag = await lookupProjectTagByName(normalized);
57390
+ if (!tag) {
57391
+ return toolError("tag not found in project_tags", { tag: normalized });
57392
+ }
57393
+ const result = await db.query(
57394
+ `UPDATE project_tags
57395
+ SET alias_of = NULL,
57396
+ updated_at = NOW()
57397
+ WHERE id = $1
57398
+ AND alias_of IS NOT NULL
57399
+ RETURNING id, name`,
57400
+ [tag.id]
57401
+ );
57402
+ if (result.rows.length === 0) {
57403
+ return ok2({
57404
+ action: "unset_alias",
57405
+ tag: tag.name,
57406
+ alias_removed: false,
57407
+ reason: "tag is not currently an alias",
57408
+ user_id: userId
57409
+ });
57410
+ }
57411
+ invalidateCandidateCache();
57412
+ return ok2({
57413
+ action: "unset_alias",
57414
+ tag: String(result.rows[0].name),
57415
+ alias_removed: true,
57416
+ user_id: userId
57417
+ });
57418
+ }
57419
+ function registerManageProjectTags(server) {
57420
+ server.registerTool(
57421
+ "manage_project_tags",
57422
+ {
57423
+ description: `Project tag alias suggestion management tool (Stage 2 / DEVLOG \xA719).
57424
+
57425
+ Actions:
57426
+ - list_suggestions: list alias suggestions by status for the default user.
57427
+ - confirm_alias: confirm a pending suggestion through applyAliasSuggestion().
57428
+ - reject_alias: reject a pending suggestion.
57429
+ - set_alias: manually set source_tag as an alias of target_tag through a pending suggestion + applyAliasSuggestion().
57430
+ - unset_alias: clear alias_of for one project tag.`,
57431
+ inputSchema: {
57432
+ action: external_exports.enum([
57433
+ "list_suggestions",
57434
+ "confirm_alias",
57435
+ "reject_alias",
57436
+ "set_alias",
57437
+ "unset_alias"
57438
+ ]).describe("\uC791\uC5C5 \uC885\uB958"),
57439
+ status: statusSchema.optional().describe("list_suggestions status filter (default pending)"),
57440
+ limit: external_exports.number().int().min(1).max(MAX_LIMIT).optional().describe(`list_suggestions limit (default ${DEFAULT_LIMIT2})`),
57441
+ suggestion_id: external_exports.number().int().positive().optional().describe("confirm_alias/reject_alias \uB300\uC0C1 suggestion id"),
57442
+ reason: external_exports.string().optional().describe("confirm/reject/set decision reason"),
57443
+ source_tag: external_exports.string().optional().describe("set_alias source project_tags.name"),
57444
+ target_tag: external_exports.string().optional().describe("set_alias target project_tags.name"),
57445
+ tag: external_exports.string().optional().describe("unset_alias \uB300\uC0C1 project_tags.name")
57446
+ }
57447
+ },
57448
+ async (args) => {
57449
+ try {
57450
+ const userId = await getDefaultUserId();
57451
+ if (args.action === "list_suggestions") {
57452
+ return listSuggestions(
57453
+ userId,
57454
+ args.status ?? "pending",
57455
+ args.limit ?? DEFAULT_LIMIT2
57456
+ );
57457
+ }
57458
+ if (args.action === "confirm_alias") {
57459
+ if (args.suggestion_id == null) {
57460
+ return toolError("suggestion_id is required for confirm_alias");
57461
+ }
57462
+ return confirmAlias(userId, args.suggestion_id, args.reason);
57463
+ }
57464
+ if (args.action === "reject_alias") {
57465
+ if (args.suggestion_id == null) {
57466
+ return toolError("suggestion_id is required for reject_alias");
57467
+ }
57468
+ return rejectAlias(userId, args.suggestion_id, args.reason);
57469
+ }
57470
+ if (args.action === "set_alias") {
57471
+ return setAlias(userId, args.source_tag, args.target_tag, args.reason);
57472
+ }
57473
+ if (args.action === "unset_alias") {
57474
+ return unsetAlias(userId, args.tag);
57475
+ }
57476
+ return toolError("unsupported action", { action: args.action });
57477
+ } catch (err2) {
57478
+ return toolError("manage_project_tags failed", { detail: errorDetail(err2) });
57479
+ }
57480
+ }
57481
+ );
57482
+ }
57483
+
56104
57484
  // src/briefing.ts
56105
57485
  init_db();
56106
- import * as os3 from "node:os";
57486
+ import * as os4 from "node:os";
56107
57487
  var RECENT_CURRENT_LIMIT = 8;
56108
57488
  var RECENT_OTHERS_LIMIT = 4;
56109
- var RECENT_MESSAGE_PREVIEW = 100;
57489
+ var MAX_PREVIEW_STORE = 500;
57490
+ var PREVIEW_COMPACT = 100;
57491
+ var PREVIEW_RECENT = 300;
56110
57492
  var ACTIVE_PTAG_LIMIT = 5;
57493
+ var PENDING_ALIAS_SUGGESTION_LIMIT = 2;
57494
+ var PINNED_LIMIT = 10;
57495
+ var INJECT_PINNED_LIMIT = 5;
57496
+ var BRIEF_MAX_CHARS = Number(process.env.BRIEF_MAX_CHARS ?? 8e3);
56111
57497
  async function collectBrief(opts = {}) {
56112
57498
  const userId = opts.userId ?? await getDefaultUserId();
56113
57499
  const shortTermDays = opts.shortTermDays ?? Number(process.env.SHORT_TERM_DAYS ?? 3);
56114
57500
  const currentPlatform = opts.currentPlatform ?? null;
57501
+ const deviceName = opts.deviceName ?? os4.hostname();
56115
57502
  const u = await db.query(
56116
57503
  `SELECT user_name, core_profile, sub_profile FROM users WHERE user_id = $1`,
56117
57504
  [userId]
56118
57505
  );
56119
57506
  const user = u.rows[0] ?? { user_name: "unknown", core_profile: null, sub_profile: null };
57507
+ const pinnedMsgs = await db.query(
57508
+ `SELECT role, agent_platform, device_name, message, created_at
57509
+ FROM memory
57510
+ WHERE user_id = $1
57511
+ AND is_active = TRUE
57512
+ AND is_pinned = TRUE
57513
+ ORDER BY created_at DESC
57514
+ LIMIT $2`,
57515
+ [userId, PINNED_LIMIT]
57516
+ );
56120
57517
  const ptags = await db.query(
56121
- `SELECT pt.name,
57518
+ `SELECT cpt.name,
56122
57519
  COUNT(*)::int AS cnt,
56123
57520
  MAX(m.created_at) AS last_used
56124
57521
  FROM memory m
56125
- JOIN project_tags pt ON pt.id = m.p_tag_id
57522
+ JOIN project_tags cpt ON cpt.id = canonical_project_tag_id(m.p_tag_id)
56126
57523
  WHERE m.user_id = $1
56127
57524
  AND m.is_active = TRUE
56128
57525
  AND m.created_at >= NOW() - ($2 || ' days')::INTERVAL
56129
- GROUP BY pt.name
57526
+ GROUP BY cpt.name
56130
57527
  ORDER BY MAX(m.created_at) DESC
56131
57528
  LIMIT $3`,
56132
57529
  [userId, String(shortTermDays), ACTIVE_PTAG_LIMIT]
@@ -56141,9 +57538,11 @@ async function collectBrief(opts = {}) {
56141
57538
  AND is_active = TRUE
56142
57539
  AND created_at >= NOW() - ($2 || ' days')::INTERVAL
56143
57540
  AND agent_platform = $3
57541
+ AND device_name = $4
57542
+ AND is_pinned = FALSE
56144
57543
  ORDER BY created_at DESC
56145
- LIMIT $4`,
56146
- [userId, String(shortTermDays), currentPlatform, RECENT_CURRENT_LIMIT]
57544
+ LIMIT $5`,
57545
+ [userId, String(shortTermDays), currentPlatform, deviceName, RECENT_CURRENT_LIMIT]
56147
57546
  );
56148
57547
  recentCurrent = currentMsgs.rows.reverse().map(rowToMsg);
56149
57548
  const othersMsgs = await db.query(
@@ -56153,6 +57552,7 @@ async function collectBrief(opts = {}) {
56153
57552
  AND is_active = TRUE
56154
57553
  AND created_at >= NOW() - ($2 || ' days')::INTERVAL
56155
57554
  AND agent_platform != $3
57555
+ AND is_pinned = FALSE
56156
57556
  ORDER BY created_at DESC
56157
57557
  LIMIT $4`,
56158
57558
  [userId, String(shortTermDays), currentPlatform, RECENT_OTHERS_LIMIT]
@@ -56165,16 +57565,35 @@ async function collectBrief(opts = {}) {
56165
57565
  WHERE user_id = $1
56166
57566
  AND is_active = TRUE
56167
57567
  AND created_at >= NOW() - ($2 || ' days')::INTERVAL
57568
+ AND is_pinned = FALSE
56168
57569
  ORDER BY created_at DESC
56169
57570
  LIMIT $3`,
56170
57571
  [userId, String(shortTermDays), RECENT_CURRENT_LIMIT]
56171
57572
  );
56172
57573
  recentCurrent = msgs.rows.reverse().map(rowToMsg);
56173
57574
  }
57575
+ const aliasSugg = await db.query(
57576
+ `SELECT s.id, src.name AS source, tgt.name AS target, s.relation, s.confidence
57577
+ FROM project_tag_alias_suggestions s
57578
+ JOIN project_tags src ON src.id = s.source_tag_id
57579
+ JOIN project_tags tgt ON tgt.id = s.target_tag_id
57580
+ WHERE s.user_id = $1 AND s.status = 'pending'
57581
+ ORDER BY s.confidence DESC, s.created_at DESC
57582
+ LIMIT $2`,
57583
+ [userId, PENDING_ALIAS_SUGGESTION_LIMIT]
57584
+ );
56174
57585
  return {
56175
57586
  user_name: user.user_name,
56176
57587
  core_profile: user.core_profile,
56177
57588
  sub_profile: user.sub_profile,
57589
+ pinned_memories: pinnedMsgs.rows.map(rowToMsg),
57590
+ pending_alias_suggestions: aliasSugg.rows.map((r) => ({
57591
+ id: Number(r.id),
57592
+ source: String(r.source),
57593
+ target: String(r.target),
57594
+ relation: String(r.relation),
57595
+ confidence: Number(r.confidence)
57596
+ })),
56178
57597
  active_p_tags: ptags.rows.map((r) => ({
56179
57598
  name: r.name,
56180
57599
  count: r.cnt,
@@ -56191,64 +57610,140 @@ function rowToMsg(r) {
56191
57610
  role: r.role,
56192
57611
  agent_platform: r.agent_platform,
56193
57612
  device_name: r.device_name ?? null,
56194
- preview: String(r.message ?? "").slice(0, RECENT_MESSAGE_PREVIEW),
57613
+ preview: String(r.message ?? "").slice(0, MAX_PREVIEW_STORE),
56195
57614
  created_at: r.created_at
56196
57615
  };
56197
57616
  }
56198
- function formatBriefMarkdown(brief) {
56199
- const lines = [];
56200
- lines.push(`# Memory Briefing (user: ${brief.user_name})`);
57617
+ function formatBriefMarkdown(brief, opts = {}) {
57618
+ if ((opts.mode ?? "full") === "inject") {
57619
+ return formatBriefInject(brief, opts.maxChars ?? BRIEF_MAX_CHARS);
57620
+ }
57621
+ return formatBriefFull(brief);
57622
+ }
57623
+ function formatBriefFull(brief) {
57624
+ const gLines = [];
57625
+ gLines.push(`# Memory Briefing (user: ${brief.user_name})`);
56201
57626
  if (brief.current_platform) {
56202
- lines.push(`Current platform: \`${brief.current_platform} @ ${os3.hostname()}\``);
57627
+ gLines.push(`Current platform: \`${brief.current_platform} @ ${os4.hostname()}\``);
56203
57628
  }
56204
- lines.push("");
57629
+ gLines.push("");
56205
57630
  if (brief.core_profile) {
56206
- lines.push(`## Core Profile`);
56207
- lines.push(brief.core_profile);
56208
- lines.push("");
57631
+ gLines.push("## Core Profile");
57632
+ gLines.push(brief.core_profile);
57633
+ gLines.push("");
56209
57634
  }
56210
57635
  if (brief.sub_profile) {
56211
- lines.push(`## Sub Profile`);
56212
- lines.push(brief.sub_profile);
56213
- lines.push("");
56214
- }
57636
+ gLines.push("## Sub Profile");
57637
+ gLines.push(brief.sub_profile);
57638
+ gLines.push("");
57639
+ }
57640
+ if (brief.pinned_memories.length > 0) {
57641
+ gLines.push("## Pinned Memories (Important Facts)");
57642
+ for (const m of brief.pinned_memories) {
57643
+ gLines.push(formatMsgLine(m, true));
57644
+ }
57645
+ gLines.push("");
57646
+ }
57647
+ if (brief.pending_alias_suggestions.length > 0) {
57648
+ gLines.push("## Project Tag Suggestions (\uC0AC\uC6A9\uC790 \uD655\uC778 \uD544\uC694)");
57649
+ for (const s of brief.pending_alias_suggestions) {
57650
+ gLines.push(`- [${s.id}] \`${s.source}\` \u2192 \`${s.target}\` \uAC19\uC740 \uD504\uB85C\uC81D\uD2B8\uB85C \uBCF4\uC784 (${s.relation}, conf ${s.confidence}). \uB9DE\uC73C\uBA74 \`manage_project_tags({action:"confirm_alias",suggestion_id:${s.id}})\`, \uC544\uB2C8\uBA74 \`reject_alias\`.`);
57651
+ }
57652
+ gLines.push("");
57653
+ }
57654
+ const guaranteed = gLines.join("\n");
57655
+ const footerLines = [
57656
+ "---",
57657
+ `Use \`search_memory({ query, p_tag, date_range, role, agent_platform, include_archived })\` to retrieve more.`,
57658
+ ...brief.current_platform ? [`Cross-platform search: \`search_memory({ query, agent_platform: "*" })\`.`] : [],
57659
+ `Use \`memory_startup\` tool for a refreshed brief mid-session.`
57660
+ ];
57661
+ const footer = footerLines.join("\n");
57662
+ const remaining = Math.max(0, BRIEF_MAX_CHARS - guaranteed.length - footer.length - 2);
57663
+ const optionalSections = [];
56215
57664
  if (brief.active_p_tags.length > 0) {
56216
- lines.push(`## Active Projects (last ${brief.short_term_window_days} days)`);
57665
+ const lines = [`## Active Projects (last ${brief.short_term_window_days} days)`];
56217
57666
  for (const t of brief.active_p_tags) {
56218
57667
  const dt = t.last_used ? t.last_used.toISOString().slice(0, 10) : "?";
56219
57668
  lines.push(`- **${t.name}** \u2014 ${t.count} memories (last: ${dt})`);
56220
57669
  }
56221
57670
  lines.push("");
57671
+ optionalSections.push(lines.join("\n"));
56222
57672
  }
56223
57673
  if (brief.recent_messages_current.length > 0) {
56224
57674
  const heading = brief.current_platform ? `## Recent on ${brief.current_platform} (last ${brief.recent_messages_current.length}, oldest \u2192 newest)` : `## Recent Memory (last ${brief.recent_messages_current.length}, oldest \u2192 newest)`;
56225
- lines.push(heading);
57675
+ const lines = [heading];
56226
57676
  for (const m of brief.recent_messages_current) {
56227
- lines.push(formatMsgLine(m, brief.current_platform === null));
57677
+ lines.push(formatMsgLine(m, brief.current_platform === null, PREVIEW_RECENT));
56228
57678
  }
56229
57679
  lines.push("");
57680
+ optionalSections.push(lines.join("\n"));
56230
57681
  }
56231
57682
  if (brief.recent_messages_others.length > 0) {
56232
- lines.push(`## Cross-platform Whispers (other agents, last ${brief.recent_messages_others.length})`);
57683
+ const lines = [`## Cross-platform Whispers (other agents, last ${brief.recent_messages_others.length})`];
56233
57684
  for (const m of brief.recent_messages_others) {
56234
57685
  lines.push(formatMsgLine(m, true));
56235
57686
  }
56236
57687
  lines.push("");
57688
+ optionalSections.push(lines.join("\n"));
56237
57689
  }
56238
- lines.push(`---`);
56239
- lines.push(`Use \`search_memory({ query, p_tag, date_range, role, agent_platform, include_archived })\` to retrieve more.`);
56240
- if (brief.current_platform) {
56241
- lines.push(`Cross-platform search: \`search_memory({ query, agent_platform: "*" })\`.`);
57690
+ let optionalText = "";
57691
+ for (const section of optionalSections) {
57692
+ if (optionalText.length + section.length > remaining) break;
57693
+ optionalText += section;
56242
57694
  }
56243
- lines.push(`Use \`memory_startup\` tool for a refreshed brief mid-session.`);
56244
- return lines.join("\n");
57695
+ return guaranteed + "\n" + optionalText + "\n" + footer;
56245
57696
  }
56246
- function formatMsgLine(m, showPlatform) {
57697
+ function formatBriefInject(brief, maxChars) {
57698
+ const headLines = [`# Memory Briefing (user: ${brief.user_name})`];
57699
+ if (brief.current_platform) {
57700
+ headLines.push(`Current platform: \`${brief.current_platform} @ ${os4.hostname()}\``);
57701
+ }
57702
+ headLines.push("");
57703
+ if (brief.core_profile) {
57704
+ headLines.push("## Core Profile", brief.core_profile, "");
57705
+ }
57706
+ const header = headLines.join("\n");
57707
+ const pointer = [
57708
+ "---",
57709
+ "\u26A1 \uC555\uCD95\uBCF8\uC785\uB2C8\uB2E4. **\uC138\uC158 \uC2DC\uC791 \uC2DC `memory_startup`\uC744 \uD55C \uBC88 \uD638\uCD9C**\uD574 \uCD5C\uADFC \uB300\uD654\xB7\uD65C\uC131 \uD504\uB85C\uC81D\uD2B8\xB7\uC0C1\uC138 \uD504\uB85C\uD544\uC744 \uC774\uC5B4\uBC1B\uC73C\uC138\uC694.",
57710
+ "Use `search_memory({ query, p_tag, date_range, role, agent_platform })` to retrieve more."
57711
+ ].join("\n");
57712
+ const budget = { remaining: Math.max(0, maxChars - header.length - pointer.length - 2) };
57713
+ const pinnedLines = brief.pinned_memories.slice(0, INJECT_PINNED_LIMIT).map((m) => formatMsgLine(m, true));
57714
+ const pinnedSection = fitSection("## Pinned Memories (Important Facts)", pinnedLines, budget);
57715
+ const activeLines = brief.active_p_tags.map((t) => {
57716
+ const dt = t.last_used ? t.last_used.toISOString().slice(0, 10) : "?";
57717
+ return `- **${t.name}** \u2014 ${t.count} memories (last: ${dt})`;
57718
+ });
57719
+ const activeSection = fitSection(
57720
+ `## Active Projects (last ${brief.short_term_window_days} days)`,
57721
+ activeLines,
57722
+ budget
57723
+ );
57724
+ return [header.trimEnd(), pinnedSection, activeSection, pointer].filter((s) => s.length > 0).join("\n\n");
57725
+ }
57726
+ function fitSection(heading, lines, budget) {
57727
+ if (lines.length === 0) return "";
57728
+ const headingCost = heading.length + 1;
57729
+ if (budget.remaining < headingCost + lines[0].length + 1) return "";
57730
+ const out = [heading];
57731
+ let used = headingCost;
57732
+ for (const ln of lines) {
57733
+ if (used + ln.length + 1 > budget.remaining) break;
57734
+ out.push(ln);
57735
+ used += ln.length + 1;
57736
+ }
57737
+ budget.remaining -= used;
57738
+ return out.join("\n");
57739
+ }
57740
+ function formatMsgLine(m, showPlatform, maxPreview = PREVIEW_COMPACT) {
56247
57741
  const dt = m.created_at?.toISOString?.().slice(11, 16) ?? "";
56248
57742
  const device = m.device_name ? `@${m.device_name} ` : "";
56249
57743
  const platformTag = showPlatform ? `${m.agent_platform} ${device}` : device;
56250
- const truncated = m.preview.length >= RECENT_MESSAGE_PREVIEW ? "\u2026" : "";
56251
- return `- [${dt} ${platformTag}${m.role}] ${m.preview}${truncated}`;
57744
+ const truncated = m.preview.length > maxPreview;
57745
+ const text = truncated ? m.preview.slice(0, maxPreview) : m.preview;
57746
+ return `- [${dt} ${platformTag}${m.role}] ${text}${truncated ? "\u2026" : ""}`;
56252
57747
  }
56253
57748
 
56254
57749
  // src/tools/memory_startup.ts
@@ -56300,16 +57795,16 @@ agent_platform_filter=false.`,
56300
57795
  }
56301
57796
 
56302
57797
  // src/auto_save/save_message_tool.ts
56303
- import * as os5 from "node:os";
57798
+ import * as os8 from "node:os";
56304
57799
 
56305
57800
  // src/auto_save/gemini_capture.ts
56306
57801
  import * as fs2 from "node:fs";
56307
57802
  import * as path3 from "node:path";
56308
- import * as os4 from "node:os";
57803
+ import * as os5 from "node:os";
56309
57804
  import * as crypto2 from "node:crypto";
56310
57805
  var CLIENT_PLATFORM = "gemini-cli-mcp-client";
56311
- var DEVICE_NAME2 = os4.hostname();
56312
- var GEMINI_TMP_ROOT = path3.join(os4.homedir(), ".gemini", "tmp");
57806
+ var DEVICE_NAME2 = os5.hostname();
57807
+ var GEMINI_TMP_ROOT = path3.join(os5.homedir(), ".gemini", "tmp");
56313
57808
  var FLUSH_DEBOUNCE_MS = 200;
56314
57809
  var PARSE_RETRY_MS = 80;
56315
57810
  var POLL_INTERVAL_MS = 3e3;
@@ -56686,8 +58181,593 @@ async function captureSessionEnd() {
56686
58181
  }
56687
58182
  }
56688
58183
 
58184
+ // src/auto_save/grok_capture.ts
58185
+ import * as fs3 from "node:fs";
58186
+ import * as path4 from "node:path";
58187
+ import * as os6 from "node:os";
58188
+ var DEVICE_NAME3 = os6.hostname();
58189
+ var SESSIONS_ROOT = path4.join(os6.homedir(), ".grok", "sessions");
58190
+ var CHAT_FILE = "chat_history.jsonl";
58191
+ var AGENT_PLATFORM = "grok-cli";
58192
+ var FLUSH_DEBOUNCE_MS2 = 200;
58193
+ var POLL_INTERVAL_MS2 = 3e3;
58194
+ var _state2 = null;
58195
+ var SERVER_START_MS = Date.now();
58196
+ var _watcher = null;
58197
+ var _pollTimer2 = null;
58198
+ var _flushInProgress2 = false;
58199
+ var _flushPending2 = false;
58200
+ var _flushDebounceTimer2 = null;
58201
+ function extractSessionId(filePath) {
58202
+ return path4.basename(path4.dirname(filePath));
58203
+ }
58204
+ function countCompleteLines(filePath) {
58205
+ try {
58206
+ const content = fs3.readFileSync(filePath, "utf-8");
58207
+ let n = 0;
58208
+ for (let i = 0; i < content.length; i++) if (content[i] === "\n") n++;
58209
+ return n;
58210
+ } catch {
58211
+ return 0;
58212
+ }
58213
+ }
58214
+ function* walkChatHistories(root) {
58215
+ if (!fs3.existsSync(root)) return;
58216
+ const stack = [root];
58217
+ while (stack.length) {
58218
+ const dir = stack.pop();
58219
+ let entries;
58220
+ try {
58221
+ entries = fs3.readdirSync(dir, { withFileTypes: true });
58222
+ } catch {
58223
+ continue;
58224
+ }
58225
+ for (const e of entries) {
58226
+ const p = path4.join(dir, e.name);
58227
+ if (e.isDirectory()) {
58228
+ stack.push(p);
58229
+ } else if (e.isFile() && e.name === CHAT_FILE) {
58230
+ yield p;
58231
+ }
58232
+ }
58233
+ }
58234
+ }
58235
+ function captureSessionStart2(_cwd) {
58236
+ const rootExists = fs3.existsSync(SESSIONS_ROOT);
58237
+ _state2 = {
58238
+ rootExists,
58239
+ files: /* @__PURE__ */ new Map()
58240
+ };
58241
+ if (!rootExists) {
58242
+ return;
58243
+ }
58244
+ let count = 0;
58245
+ for (const filePath of walkChatHistories(SESSIONS_ROOT)) {
58246
+ let stat;
58247
+ try {
58248
+ stat = fs3.statSync(filePath);
58249
+ } catch {
58250
+ continue;
58251
+ }
58252
+ _state2.files.set(filePath, {
58253
+ cursorLines: countCompleteLines(filePath),
58254
+ lastSize: stat.size,
58255
+ sessionId: extractSessionId(filePath)
58256
+ });
58257
+ count++;
58258
+ }
58259
+ console.error(`\u{1F4DD} [Grok] capture armed: ${count} chat_history(s)`);
58260
+ armDirWatcher();
58261
+ }
58262
+ function extractText(content) {
58263
+ if (typeof content === "string") return content;
58264
+ if (Array.isArray(content)) {
58265
+ return content.filter((b) => b && typeof b === "object" && b.type === "text" && typeof b.text === "string").map((b) => b.text).join("\n");
58266
+ }
58267
+ return "";
58268
+ }
58269
+ function extractUserQuery(text) {
58270
+ const m = text.match(/<user_query>([\s\S]*?)<\/user_query>/);
58271
+ return m ? m[1].trim() : null;
58272
+ }
58273
+ function parseEntry(line, lineIndex) {
58274
+ let entry;
58275
+ try {
58276
+ entry = JSON.parse(line);
58277
+ } catch {
58278
+ return null;
58279
+ }
58280
+ const type = entry.type;
58281
+ if (type === "user") {
58282
+ if (entry.synthetic_reason) return null;
58283
+ const text = extractText(entry.content);
58284
+ const query = extractUserQuery(text);
58285
+ if (!query) return null;
58286
+ return { lineIndex, role: "user", message: query, agentModel: null };
58287
+ }
58288
+ if (type === "assistant") {
58289
+ const message = extractText(entry.content).trim();
58290
+ if (!message) return null;
58291
+ const model = typeof entry.model_id === "string" && entry.model_id ? entry.model_id : "unknown";
58292
+ return { lineIndex, role: "assistant", message, agentModel: model };
58293
+ }
58294
+ return null;
58295
+ }
58296
+ async function flushDeltaForFile2(filePath, fileState) {
58297
+ let stat;
58298
+ try {
58299
+ stat = fs3.statSync(filePath);
58300
+ } catch {
58301
+ return { inserted: 0, skipped: 0, dedup: 0 };
58302
+ }
58303
+ if (stat.size === fileState.lastSize) return { inserted: 0, skipped: 0, dedup: 0 };
58304
+ let content;
58305
+ try {
58306
+ content = fs3.readFileSync(filePath, "utf-8");
58307
+ } catch {
58308
+ return { inserted: 0, skipped: 0, dedup: 0 };
58309
+ }
58310
+ fileState.lastSize = stat.size;
58311
+ const completeLines = content.split("\n").slice(0, -1);
58312
+ const total = completeLines.length;
58313
+ if (total <= fileState.cursorLines) return { inserted: 0, skipped: 0, dedup: 0 };
58314
+ const userId = await getDefaultUserId();
58315
+ let inserted = 0, skipped = 0, dedup = 0;
58316
+ for (let i = fileState.cursorLines; i < total; i++) {
58317
+ const line = completeLines[i].trim();
58318
+ if (!line) continue;
58319
+ const parsed = parseEntry(line, i);
58320
+ if (!parsed) {
58321
+ skipped++;
58322
+ continue;
58323
+ }
58324
+ const externalUuid = `grok:${fileState.sessionId}:${parsed.lineIndex}`;
58325
+ try {
58326
+ const result = await insertRawMemory({
58327
+ user_id: userId,
58328
+ agent_platform: AGENT_PLATFORM,
58329
+ agent_model: parsed.agentModel,
58330
+ role: parsed.role,
58331
+ message: parsed.message,
58332
+ external_uuid: externalUuid,
58333
+ device_name: DEVICE_NAME3
58334
+ });
58335
+ if (result.inserted) inserted++;
58336
+ else dedup++;
58337
+ } catch (err2) {
58338
+ console.error(`\u26A0\uFE0F [Grok] insert failed at line ${i}:`, err2);
58339
+ skipped++;
58340
+ }
58341
+ }
58342
+ fileState.cursorLines = total;
58343
+ return { inserted, skipped, dedup };
58344
+ }
58345
+ async function flushAllFiles2() {
58346
+ if (!_state2 || !_state2.rootExists) return { inserted: 0, skipped: 0, dedup: 0 };
58347
+ let totalI = 0, totalS = 0, totalD = 0;
58348
+ for (const filePath of walkChatHistories(SESSIONS_ROOT)) {
58349
+ let fileState = _state2.files.get(filePath);
58350
+ if (!fileState) {
58351
+ let stat = null;
58352
+ try {
58353
+ stat = fs3.statSync(filePath);
58354
+ } catch {
58355
+ }
58356
+ const fileBirthMs = stat ? stat.birthtimeMs || stat.ctimeMs : SERVER_START_MS;
58357
+ const isPreExisting = fileBirthMs < SERVER_START_MS - 5e3;
58358
+ const initialCursor = isPreExisting ? countCompleteLines(filePath) : 0;
58359
+ const initialSize = isPreExisting ? stat?.size ?? 0 : 0;
58360
+ if (isPreExisting) {
58361
+ console.error(`\u{1F4DD} [Grok] pre-existing session skipped (lines=${initialCursor}): ${extractSessionId(filePath)}`);
58362
+ } else {
58363
+ console.error(`\u{1F4DD} [Grok] new session detected: ${extractSessionId(filePath)}`);
58364
+ }
58365
+ fileState = {
58366
+ cursorLines: initialCursor,
58367
+ lastSize: initialSize,
58368
+ sessionId: extractSessionId(filePath)
58369
+ };
58370
+ _state2.files.set(filePath, fileState);
58371
+ }
58372
+ const r = await flushDeltaForFile2(filePath, fileState);
58373
+ totalI += r.inserted;
58374
+ totalS += r.skipped;
58375
+ totalD += r.dedup;
58376
+ }
58377
+ return { inserted: totalI, skipped: totalS, dedup: totalD };
58378
+ }
58379
+ async function flushWithMutex2() {
58380
+ if (_flushInProgress2) {
58381
+ _flushPending2 = true;
58382
+ return;
58383
+ }
58384
+ _flushInProgress2 = true;
58385
+ try {
58386
+ const r = await flushAllFiles2();
58387
+ if (r.inserted > 0 || r.skipped > 0 || r.dedup > 0) {
58388
+ console.error(`\u{1F4DD} [Grok] live flush: inserted=${r.inserted}, dedup=${r.dedup}, skipped=${r.skipped}`);
58389
+ }
58390
+ } catch (err2) {
58391
+ console.error("\u26A0\uFE0F [Grok] live flush error:", err2);
58392
+ } finally {
58393
+ _flushInProgress2 = false;
58394
+ if (_flushPending2) {
58395
+ _flushPending2 = false;
58396
+ setImmediate(() => {
58397
+ void flushWithMutex2();
58398
+ });
58399
+ }
58400
+ }
58401
+ }
58402
+ function scheduleFlush2() {
58403
+ if (_flushDebounceTimer2) clearTimeout(_flushDebounceTimer2);
58404
+ _flushDebounceTimer2 = setTimeout(() => {
58405
+ _flushDebounceTimer2 = null;
58406
+ void flushWithMutex2();
58407
+ }, FLUSH_DEBOUNCE_MS2);
58408
+ }
58409
+ function armDirWatcher() {
58410
+ if (!_state2 || !_state2.rootExists) return;
58411
+ if (_watcher) return;
58412
+ try {
58413
+ _watcher = fs3.watch(SESSIONS_ROOT, { recursive: true }, (_evt, filename) => {
58414
+ if (!filename) return;
58415
+ if (path4.basename(filename) !== CHAT_FILE) return;
58416
+ scheduleFlush2();
58417
+ });
58418
+ console.error(`\u{1F4DD} [Grok] dir watcher armed (recursive)`);
58419
+ } catch (err2) {
58420
+ console.error("\u26A0\uFE0F [Grok] fs.watch failed \u2014 polling only:", err2);
58421
+ }
58422
+ _pollTimer2 = setInterval(() => {
58423
+ void flushWithMutex2();
58424
+ }, POLL_INTERVAL_MS2);
58425
+ }
58426
+ function disarmDirWatcher() {
58427
+ if (_flushDebounceTimer2) {
58428
+ clearTimeout(_flushDebounceTimer2);
58429
+ _flushDebounceTimer2 = null;
58430
+ }
58431
+ if (_pollTimer2) {
58432
+ clearInterval(_pollTimer2);
58433
+ _pollTimer2 = null;
58434
+ }
58435
+ if (_watcher) {
58436
+ try {
58437
+ _watcher.close();
58438
+ } catch {
58439
+ }
58440
+ _watcher = null;
58441
+ }
58442
+ }
58443
+ async function captureSessionEnd2() {
58444
+ disarmDirWatcher();
58445
+ const waitStart = Date.now();
58446
+ while (_flushInProgress2 && Date.now() - waitStart < 2e3) {
58447
+ await new Promise((r) => setTimeout(r, 50));
58448
+ }
58449
+ if (!_state2 || !_state2.rootExists) {
58450
+ return { inserted: 0, skipped: 0, error: "session not armed" };
58451
+ }
58452
+ _flushInProgress2 = true;
58453
+ try {
58454
+ const r = await flushAllFiles2();
58455
+ if (r.inserted > 0 || r.skipped > 0 || r.dedup > 0) {
58456
+ console.error(`\u{1F4DD} [Grok] final flush: inserted=${r.inserted}, dedup=${r.dedup}, skipped=${r.skipped}`);
58457
+ }
58458
+ return { inserted: r.inserted, skipped: r.skipped };
58459
+ } finally {
58460
+ _flushInProgress2 = false;
58461
+ }
58462
+ }
58463
+ function isCaptureArmed2() {
58464
+ return _state2 !== null && _state2.rootExists;
58465
+ }
58466
+
58467
+ // src/auto_save/antigravity_capture.ts
58468
+ import * as fs4 from "node:fs";
58469
+ import * as path5 from "node:path";
58470
+ import * as os7 from "node:os";
58471
+ var DEVICE_NAME4 = os7.hostname();
58472
+ var GEMINI_ROOT = path5.join(os7.homedir(), ".gemini");
58473
+ var CHAT_FILE2 = "transcript_full.jsonl";
58474
+ var FLUSH_DEBOUNCE_MS3 = 200;
58475
+ var POLL_INTERVAL_MS3 = 3e3;
58476
+ var ROOTS = [
58477
+ { dir: path5.join(GEMINI_ROOT, "antigravity", "brain"), platform: "antigravity" },
58478
+ { dir: path5.join(GEMINI_ROOT, "antigravity-cli", "brain"), platform: "antigravity-cli" },
58479
+ { dir: path5.join(GEMINI_ROOT, "antigravity-ide", "brain"), platform: "antigravity-ide" }
58480
+ ];
58481
+ var _state3 = null;
58482
+ var SERVER_START_MS2 = Date.now();
58483
+ var _watchers2 = [];
58484
+ var _pollTimer3 = null;
58485
+ var _flushInProgress3 = false;
58486
+ var _flushPending3 = false;
58487
+ var _flushDebounceTimer3 = null;
58488
+ function extractSid(filePath) {
58489
+ return path5.basename(path5.dirname(path5.dirname(path5.dirname(filePath))));
58490
+ }
58491
+ function countCompleteLines2(filePath) {
58492
+ try {
58493
+ const content = fs4.readFileSync(filePath, "utf-8");
58494
+ let n = 0;
58495
+ for (let i = 0; i < content.length; i++) if (content[i] === "\n") n++;
58496
+ return n;
58497
+ } catch {
58498
+ return 0;
58499
+ }
58500
+ }
58501
+ function* walkTranscripts(root) {
58502
+ if (!fs4.existsSync(root)) return;
58503
+ const stack = [root];
58504
+ while (stack.length) {
58505
+ const dir = stack.pop();
58506
+ let entries;
58507
+ try {
58508
+ entries = fs4.readdirSync(dir, { withFileTypes: true });
58509
+ } catch {
58510
+ continue;
58511
+ }
58512
+ for (const e of entries) {
58513
+ const p = path5.join(dir, e.name);
58514
+ if (e.isDirectory()) {
58515
+ stack.push(p);
58516
+ } else if (e.isFile() && e.name === CHAT_FILE2) {
58517
+ yield p;
58518
+ }
58519
+ }
58520
+ }
58521
+ }
58522
+ function* walkAllTranscripts() {
58523
+ for (const { dir, platform } of ROOTS) {
58524
+ for (const filePath of walkTranscripts(dir)) {
58525
+ yield { filePath, platform };
58526
+ }
58527
+ }
58528
+ }
58529
+ function captureSessionStart3(_cwd) {
58530
+ const rootExists = ROOTS.some((r) => fs4.existsSync(r.dir));
58531
+ _state3 = {
58532
+ rootExists,
58533
+ files: /* @__PURE__ */ new Map()
58534
+ };
58535
+ if (!rootExists) {
58536
+ return;
58537
+ }
58538
+ let count = 0;
58539
+ for (const { filePath, platform } of walkAllTranscripts()) {
58540
+ let stat;
58541
+ try {
58542
+ stat = fs4.statSync(filePath);
58543
+ } catch {
58544
+ continue;
58545
+ }
58546
+ _state3.files.set(filePath, {
58547
+ cursorLines: countCompleteLines2(filePath),
58548
+ lastSize: stat.size,
58549
+ sessionId: extractSid(filePath),
58550
+ agentPlatform: platform,
58551
+ currentModel: null
58552
+ });
58553
+ count++;
58554
+ }
58555
+ const liveRoots = ROOTS.filter((r) => fs4.existsSync(r.dir)).map((r) => r.platform);
58556
+ console.error(`\u{1F4DD} [Antigravity] capture armed: ${count} transcript(s) across [${liveRoots.join(", ")}]`);
58557
+ armDirWatcher2();
58558
+ }
58559
+ function extractUserRequest(text) {
58560
+ const m = text.match(/<USER_REQUEST>([\s\S]*?)<\/USER_REQUEST>/);
58561
+ return m ? m[1].trim() : null;
58562
+ }
58563
+ function extractModelSelection(text) {
58564
+ const m = text.match(/Model Selection[^\n]*?\bto\s+(.+?)\.(?:\s|$)/);
58565
+ return m ? m[1].trim() : null;
58566
+ }
58567
+ function parseEntry2(line) {
58568
+ let entry;
58569
+ try {
58570
+ entry = JSON.parse(line);
58571
+ } catch {
58572
+ return null;
58573
+ }
58574
+ const type = entry.type;
58575
+ const stepIndex = typeof entry.step_index === "number" ? entry.step_index : -1;
58576
+ if (stepIndex < 0) return null;
58577
+ if (type === "USER_INPUT") {
58578
+ const raw = typeof entry.content === "string" ? entry.content : "";
58579
+ const query = extractUserRequest(raw);
58580
+ if (!query) return null;
58581
+ return { stepIndex, role: "user", message: query, modelHint: extractModelSelection(raw) };
58582
+ }
58583
+ if (type === "PLANNER_RESPONSE") {
58584
+ const message = (entry.content || "").toString().trim();
58585
+ if (!message) return null;
58586
+ return { stepIndex, role: "assistant", message, modelHint: null };
58587
+ }
58588
+ return null;
58589
+ }
58590
+ async function flushDeltaForFile3(filePath, fileState) {
58591
+ let stat;
58592
+ try {
58593
+ stat = fs4.statSync(filePath);
58594
+ } catch {
58595
+ return { inserted: 0, skipped: 0, dedup: 0 };
58596
+ }
58597
+ if (stat.size === fileState.lastSize) return { inserted: 0, skipped: 0, dedup: 0 };
58598
+ let content;
58599
+ try {
58600
+ content = fs4.readFileSync(filePath, "utf-8");
58601
+ } catch {
58602
+ return { inserted: 0, skipped: 0, dedup: 0 };
58603
+ }
58604
+ fileState.lastSize = stat.size;
58605
+ const completeLines = content.split("\n").slice(0, -1);
58606
+ const total = completeLines.length;
58607
+ if (total <= fileState.cursorLines) return { inserted: 0, skipped: 0, dedup: 0 };
58608
+ const userId = await getDefaultUserId();
58609
+ let inserted = 0, skipped = 0, dedup = 0;
58610
+ for (let i = fileState.cursorLines; i < total; i++) {
58611
+ const line = completeLines[i].trim();
58612
+ if (!line) continue;
58613
+ const parsed = parseEntry2(line);
58614
+ if (!parsed) {
58615
+ skipped++;
58616
+ continue;
58617
+ }
58618
+ if (parsed.modelHint) fileState.currentModel = parsed.modelHint;
58619
+ const agentModel = parsed.role === "user" ? null : fileState.currentModel ?? "unknown";
58620
+ const externalUuid = `antigravity:${fileState.sessionId}:${parsed.stepIndex}`;
58621
+ try {
58622
+ const result = await insertRawMemory({
58623
+ user_id: userId,
58624
+ agent_platform: fileState.agentPlatform,
58625
+ agent_model: agentModel,
58626
+ role: parsed.role,
58627
+ message: parsed.message,
58628
+ external_uuid: externalUuid,
58629
+ device_name: DEVICE_NAME4
58630
+ });
58631
+ if (result.inserted) inserted++;
58632
+ else dedup++;
58633
+ } catch (err2) {
58634
+ console.error(`\u26A0\uFE0F [Antigravity] insert failed at step ${parsed.stepIndex}:`, err2);
58635
+ skipped++;
58636
+ }
58637
+ }
58638
+ fileState.cursorLines = total;
58639
+ return { inserted, skipped, dedup };
58640
+ }
58641
+ async function flushAllFiles3() {
58642
+ if (!_state3 || !_state3.rootExists) return { inserted: 0, skipped: 0, dedup: 0 };
58643
+ let totalI = 0, totalS = 0, totalD = 0;
58644
+ for (const { filePath, platform } of walkAllTranscripts()) {
58645
+ let fileState = _state3.files.get(filePath);
58646
+ if (!fileState) {
58647
+ let stat = null;
58648
+ try {
58649
+ stat = fs4.statSync(filePath);
58650
+ } catch {
58651
+ }
58652
+ const fileBirthMs = stat ? stat.birthtimeMs || stat.ctimeMs : SERVER_START_MS2;
58653
+ const isPreExisting = fileBirthMs < SERVER_START_MS2 - 5e3;
58654
+ const initialCursor = isPreExisting ? countCompleteLines2(filePath) : 0;
58655
+ const initialSize = isPreExisting ? stat?.size ?? 0 : 0;
58656
+ if (isPreExisting) {
58657
+ console.error(`\u{1F4DD} [Antigravity] pre-existing session skipped (lines=${initialCursor}): ${platform}/${extractSid(filePath)}`);
58658
+ } else {
58659
+ console.error(`\u{1F4DD} [Antigravity] new session detected: ${platform}/${extractSid(filePath)}`);
58660
+ }
58661
+ fileState = {
58662
+ cursorLines: initialCursor,
58663
+ lastSize: initialSize,
58664
+ sessionId: extractSid(filePath),
58665
+ agentPlatform: platform,
58666
+ currentModel: null
58667
+ };
58668
+ _state3.files.set(filePath, fileState);
58669
+ }
58670
+ const r = await flushDeltaForFile3(filePath, fileState);
58671
+ totalI += r.inserted;
58672
+ totalS += r.skipped;
58673
+ totalD += r.dedup;
58674
+ }
58675
+ return { inserted: totalI, skipped: totalS, dedup: totalD };
58676
+ }
58677
+ async function flushWithMutex3() {
58678
+ if (_flushInProgress3) {
58679
+ _flushPending3 = true;
58680
+ return;
58681
+ }
58682
+ _flushInProgress3 = true;
58683
+ try {
58684
+ const r = await flushAllFiles3();
58685
+ if (r.inserted > 0 || r.skipped > 0 || r.dedup > 0) {
58686
+ console.error(`\u{1F4DD} [Antigravity] live flush: inserted=${r.inserted}, dedup=${r.dedup}, skipped=${r.skipped}`);
58687
+ }
58688
+ } catch (err2) {
58689
+ console.error("\u26A0\uFE0F [Antigravity] live flush error:", err2);
58690
+ } finally {
58691
+ _flushInProgress3 = false;
58692
+ if (_flushPending3) {
58693
+ _flushPending3 = false;
58694
+ setImmediate(() => {
58695
+ void flushWithMutex3();
58696
+ });
58697
+ }
58698
+ }
58699
+ }
58700
+ function scheduleFlush3() {
58701
+ if (_flushDebounceTimer3) clearTimeout(_flushDebounceTimer3);
58702
+ _flushDebounceTimer3 = setTimeout(() => {
58703
+ _flushDebounceTimer3 = null;
58704
+ void flushWithMutex3();
58705
+ }, FLUSH_DEBOUNCE_MS3);
58706
+ }
58707
+ function armDirWatcher2() {
58708
+ if (!_state3 || !_state3.rootExists) return;
58709
+ if (_watchers2.length) return;
58710
+ for (const { dir } of ROOTS) {
58711
+ if (!fs4.existsSync(dir)) continue;
58712
+ try {
58713
+ const w = fs4.watch(dir, { recursive: true }, (_evt, filename) => {
58714
+ if (!filename) return;
58715
+ if (path5.basename(filename) !== CHAT_FILE2) return;
58716
+ scheduleFlush3();
58717
+ });
58718
+ _watchers2.push(w);
58719
+ } catch (err2) {
58720
+ console.error(`\u26A0\uFE0F [Antigravity] fs.watch failed for ${dir} \u2014 polling only:`, err2);
58721
+ }
58722
+ }
58723
+ console.error(`\u{1F4DD} [Antigravity] dir watchers armed: ${_watchers2.length} root(s)`);
58724
+ _pollTimer3 = setInterval(() => {
58725
+ void flushWithMutex3();
58726
+ }, POLL_INTERVAL_MS3);
58727
+ }
58728
+ function disarmDirWatcher2() {
58729
+ if (_flushDebounceTimer3) {
58730
+ clearTimeout(_flushDebounceTimer3);
58731
+ _flushDebounceTimer3 = null;
58732
+ }
58733
+ if (_pollTimer3) {
58734
+ clearInterval(_pollTimer3);
58735
+ _pollTimer3 = null;
58736
+ }
58737
+ for (const w of _watchers2) {
58738
+ try {
58739
+ w.close();
58740
+ } catch {
58741
+ }
58742
+ }
58743
+ _watchers2 = [];
58744
+ }
58745
+ async function captureSessionEnd3() {
58746
+ disarmDirWatcher2();
58747
+ const waitStart = Date.now();
58748
+ while (_flushInProgress3 && Date.now() - waitStart < 2e3) {
58749
+ await new Promise((r) => setTimeout(r, 50));
58750
+ }
58751
+ if (!_state3 || !_state3.rootExists) {
58752
+ return { inserted: 0, skipped: 0, error: "session not armed" };
58753
+ }
58754
+ _flushInProgress3 = true;
58755
+ try {
58756
+ const r = await flushAllFiles3();
58757
+ if (r.inserted > 0 || r.skipped > 0 || r.dedup > 0) {
58758
+ console.error(`\u{1F4DD} [Antigravity] final flush: inserted=${r.inserted}, dedup=${r.dedup}, skipped=${r.skipped}`);
58759
+ }
58760
+ return { inserted: r.inserted, skipped: r.skipped };
58761
+ } finally {
58762
+ _flushInProgress3 = false;
58763
+ }
58764
+ }
58765
+ function isCaptureArmed3() {
58766
+ return _state3 !== null && _state3.rootExists;
58767
+ }
58768
+
56689
58769
  // src/auto_save/save_message_tool.ts
56690
- var DEVICE_NAME3 = os5.hostname();
58770
+ var DEVICE_NAME5 = os8.hostname();
56691
58771
  function registerSaveMessage(server) {
56692
58772
  server.registerTool(
56693
58773
  "save_message",
@@ -56728,6 +58808,22 @@ subagent \uCEE8\uD14D\uC2A4\uD2B8\uB77C\uBA74 subagent=true + subagent_model + s
56728
58808
  }]
56729
58809
  };
56730
58810
  }
58811
+ if (id.agent_platform?.startsWith("grok") && isCaptureArmed2()) {
58812
+ return {
58813
+ content: [{
58814
+ type: "text",
58815
+ text: JSON.stringify({ stored: false, skipped: "passive capture active" }, null, 2)
58816
+ }]
58817
+ };
58818
+ }
58819
+ if (id.agent_platform?.startsWith("antigravity") && isCaptureArmed3()) {
58820
+ return {
58821
+ content: [{
58822
+ type: "text",
58823
+ text: JSON.stringify({ stored: false, skipped: "passive capture active" }, null, 2)
58824
+ }]
58825
+ };
58826
+ }
56731
58827
  const agentModel = args.role === "user" ? null : id.agent_model;
56732
58828
  const inserted = await insertRawMemory({
56733
58829
  user_id: userId,
@@ -56738,7 +58834,7 @@ subagent \uCEE8\uD14D\uC2A4\uD2B8\uB77C\uBA74 subagent=true + subagent_model + s
56738
58834
  subagent_role: id.subagent_role,
56739
58835
  role: args.role,
56740
58836
  message: args.message,
56741
- device_name: DEVICE_NAME3
58837
+ device_name: DEVICE_NAME5
56742
58838
  // tag/embed NULL — Cold Path가 background 처리 (Hot Path latency 보장)
56743
58839
  });
56744
58840
  return {
@@ -56760,6 +58856,7 @@ subagent \uCEE8\uD14D\uC2A4\uD2B8\uB77C\uBA74 subagent=true + subagent_model + s
56760
58856
  function registerTools(server) {
56761
58857
  registerManageKnowledge(server);
56762
58858
  registerSearchMemory(server);
58859
+ registerManageProjectTags(server);
56763
58860
  registerMemoryStartup(server);
56764
58861
  registerSaveMessage(server);
56765
58862
  }
@@ -56769,7 +58866,7 @@ init_db();
56769
58866
 
56770
58867
  // src/cold_path/dtag_promoter.ts
56771
58868
  init_db();
56772
- function envInt(name, fallback) {
58869
+ function envInt2(name, fallback) {
56773
58870
  const raw = process.env[name];
56774
58871
  if (!raw) return fallback;
56775
58872
  const n = Number(raw);
@@ -56826,8 +58923,8 @@ ${tagList}`;
56826
58923
  });
56827
58924
  }
56828
58925
  async function runDtagPromotion() {
56829
- const minCount = envInt("DTAG_PROMOTE_MIN_COUNT", 10);
56830
- const windowDays = envInt("DTAG_PROMOTE_WINDOW_DAYS", 30);
58926
+ const minCount = envInt2("DTAG_PROMOTE_MIN_COUNT", 10);
58927
+ const windowDays = envInt2("DTAG_PROMOTE_WINDOW_DAYS", 30);
56831
58928
  const userId = await getDefaultUserId();
56832
58929
  const freqResult = await db.query(
56833
58930
  `SELECT unnest(d_tag) AS tag, COUNT(*)::int AS cnt
@@ -56894,6 +58991,163 @@ async function runDtagPromotion() {
56894
58991
  return summary;
56895
58992
  }
56896
58993
 
58994
+ // src/librarian.ts
58995
+ init_db();
58996
+ var LIBRARIAN_MSG_THRESHOLD = 30;
58997
+ var LIBRARIAN_COOLDOWN_MS = 24 * 60 * 60 * 1e3;
58998
+ var LIBRARIAN_RECENT_LIMIT = 50;
58999
+ var LIBRARIAN_MAX_TOKENS = 32768;
59000
+ var SYSTEM_PROMPT2 = `You are the Librarian for one user's personal memory system.
59001
+
59002
+ YOUR JOB
59003
+ Look at the user's recent first-person messages (role='user' only \u2014 ignore
59004
+ assistant replies). Identify any STABLE, MEMORABLE facts about WHO THE USER IS
59005
+ or HOW THEY WORK that should be promoted to their long-term profile.
59006
+
59007
+ OUTPUT TWO SECTIONS:
59008
+ 1. core_profile: critically important user identity (name, role, expertise,
59009
+ strong preferences). Should be SHORT (5-10 lines max) and high-signal.
59010
+ 2. sub_profile: secondary memorable info (tools, environment quirks,
59011
+ ongoing project focus, working style preferences). Can be longer but
59012
+ still curated.
59013
+
59014
+ RULES
59015
+ - The user is one person. Output is the WHOLE profile (replacing existing,
59016
+ not appending) \u2014 so include relevant existing facts that are still true.
59017
+ - DO NOT invent facts not supported by the messages.
59018
+ - DO NOT promote temporary state ("debugging X", "frustrated with Y").
59019
+ Only stable identity / preferences.
59020
+ - DO NOT promote third-party advice or system hints that show up in
59021
+ messages \u2014 only what the user is saying ABOUT THEMSELVES.
59022
+ - Korean is fine. Match the language of the user's writing.
59023
+ - If recent messages don't add anything new and existing profile is fine,
59024
+ output the existing profile unchanged.
59025
+
59026
+ FORMAT RULES FOR THE VALUES:
59027
+ - Both fields must be PLAIN PROSE TEXT \u2014 no nested JSON, no {}, [], key-value blobs.
59028
+ Write in sentences or short bullet lines, not serialized objects.
59029
+ - If you're tempted to write {"key": "value"} inside the string, write prose instead.
59030
+
59031
+ OUTPUT JSON STRICTLY:
59032
+ {
59033
+ "core_profile": "<concise high-signal prose or null>",
59034
+ "sub_profile": "<longer secondary prose or null>"
59035
+ }`;
59036
+ var librarianRunning = false;
59037
+ async function checkGate() {
59038
+ const userId = await getDefaultUserId();
59039
+ const r = await db.query(
59040
+ `SELECT
59041
+ u.librarian_last_run_at,
59042
+ u.librarian_msg_count_at_run,
59043
+ (SELECT COUNT(*)::bigint
59044
+ FROM memory
59045
+ WHERE user_id = u.user_id
59046
+ AND role = 'user'
59047
+ AND is_active = TRUE) AS current_msg_count
59048
+ FROM users u
59049
+ WHERE u.user_id = $1`,
59050
+ [userId]
59051
+ );
59052
+ const row = r.rows[0];
59053
+ const lastRunAt = row?.librarian_last_run_at ?? null;
59054
+ const msgCountAtRun = Number(row?.librarian_msg_count_at_run ?? 0);
59055
+ const currentMsgCount = Number(row?.current_msg_count ?? 0);
59056
+ const neverRan = lastRunAt === null;
59057
+ const cooldownPassed = lastRunAt !== null && Date.now() - new Date(lastRunAt).getTime() >= LIBRARIAN_COOLDOWN_MS;
59058
+ const enoughNewMessages = currentMsgCount - msgCountAtRun >= LIBRARIAN_MSG_THRESHOLD;
59059
+ const shouldRun = enoughNewMessages && (neverRan || cooldownPassed);
59060
+ return { userId, shouldRun, currentMsgCount, lastRunAt };
59061
+ }
59062
+ async function runLibrarian() {
59063
+ if (librarianRunning) return;
59064
+ const gate = await checkGate();
59065
+ if (!gate.shouldRun) return;
59066
+ librarianRunning = true;
59067
+ await db.query(
59068
+ `UPDATE users SET librarian_last_run_at = NOW() WHERE user_id = $1`,
59069
+ [gate.userId]
59070
+ );
59071
+ try {
59072
+ const beforeR = await db.query(
59073
+ `SELECT core_profile, sub_profile FROM users WHERE user_id = $1`,
59074
+ [gate.userId]
59075
+ );
59076
+ const before = beforeR.rows[0] ?? { core_profile: null, sub_profile: null };
59077
+ const recentR = await db.query(
59078
+ `SELECT message, created_at
59079
+ FROM memory
59080
+ WHERE user_id = $1
59081
+ AND role = 'user'
59082
+ AND is_active = TRUE
59083
+ ORDER BY created_at DESC
59084
+ LIMIT $2`,
59085
+ [gate.userId, LIBRARIAN_RECENT_LIMIT]
59086
+ );
59087
+ if (recentR.rows.length === 0) return;
59088
+ const messagesText = recentR.rows.reverse().map(
59089
+ (r, i) => `[#${i + 1} @ ${r.created_at?.toISOString().slice(0, 19) ?? ""}] ${r.message}`
59090
+ ).join("\n\n");
59091
+ const userPrompt = `EXISTING PROFILE (subject to update):
59092
+ core_profile:
59093
+ ${before.core_profile ?? "(empty)"}
59094
+
59095
+ sub_profile:
59096
+ ${before.sub_profile ?? "(empty)"}
59097
+
59098
+ RECENT USER MESSAGES (most recent ${recentR.rows.length}, role='user'):
59099
+ ${messagesText}
59100
+
59101
+ Task: produce updated core_profile and sub_profile JSON per the system prompt.`;
59102
+ const raw = await callRole("librarian", {
59103
+ system: SYSTEM_PROMPT2,
59104
+ user: userPrompt,
59105
+ maxTokens: LIBRARIAN_MAX_TOKENS
59106
+ });
59107
+ if (!raw) {
59108
+ throw new Error("Librarian returned empty content (reasoning token budget exceeded?)");
59109
+ }
59110
+ let parsed;
59111
+ try {
59112
+ parsed = JSON.parse(raw);
59113
+ } catch {
59114
+ throw new Error(`Librarian returned unparseable content: ${raw.slice(0, 300)}`);
59115
+ }
59116
+ for (const [field, val] of [["core_profile", parsed.core_profile], ["sub_profile", parsed.sub_profile]]) {
59117
+ if (typeof val === "string") {
59118
+ const t = val.trim();
59119
+ if (t.startsWith("{") || t.startsWith("[")) {
59120
+ throw new Error(`Librarian returned JSON-stuffed ${field} (prose required): ${t.slice(0, 120)}`);
59121
+ }
59122
+ }
59123
+ }
59124
+ const newCore = parsed.core_profile ?? before.core_profile;
59125
+ const newSub = parsed.sub_profile ?? before.sub_profile;
59126
+ const changed = (newCore ?? "") !== (before.core_profile ?? "") || (newSub ?? "") !== (before.sub_profile ?? "");
59127
+ if (changed) {
59128
+ await db.query(
59129
+ `UPDATE users
59130
+ SET core_profile = $1,
59131
+ sub_profile = $2,
59132
+ updated_at = NOW()
59133
+ WHERE user_id = $3`,
59134
+ [newCore, newSub, gate.userId]
59135
+ );
59136
+ }
59137
+ await db.query(
59138
+ `UPDATE users SET librarian_msg_count_at_run = $1 WHERE user_id = $2`,
59139
+ [gate.currentMsgCount, gate.userId]
59140
+ );
59141
+ console.error(
59142
+ `\u{1F4DA} [Librarian] done \u2014 ${recentR.rows.length} msgs, profile ${changed ? "updated" : "unchanged"}`
59143
+ );
59144
+ } catch (err2) {
59145
+ console.error("\u26A0\uFE0F [Librarian] run failed (retries in 24h):", err2);
59146
+ } finally {
59147
+ librarianRunning = false;
59148
+ }
59149
+ }
59150
+
56897
59151
  // src/cold_path/worker.ts
56898
59152
  var intervalTimer = null;
56899
59153
  var warmupTimer = null;
@@ -56902,7 +59156,7 @@ var tickCount = 0;
56902
59156
  var DEFAULT_INTERVAL_SEC = 60;
56903
59157
  var DEFAULT_BATCH = 5;
56904
59158
  var DEFAULT_WARMUP_SEC = 30;
56905
- function envInt2(name, fallback) {
59159
+ function envInt3(name, fallback) {
56906
59160
  const raw = process.env[name];
56907
59161
  if (!raw) return fallback;
56908
59162
  const n = Number(raw);
@@ -57020,7 +59274,7 @@ async function recordError(client2, rowId, err2) {
57020
59274
  async function tick() {
57021
59275
  if (running) return 0;
57022
59276
  running = true;
57023
- const batchSize = envInt2("COLD_PATH_BATCH_SIZE", DEFAULT_BATCH);
59277
+ const batchSize = envInt3("COLD_PATH_BATCH_SIZE", DEFAULT_BATCH);
57024
59278
  const client2 = await db.getClient();
57025
59279
  try {
57026
59280
  await client2.query("BEGIN");
@@ -57068,21 +59322,33 @@ async function maybeRunPromotion() {
57068
59322
  console.error("\u26A0\uFE0F [DTagPromoter] promotion error (non-blocking):", err2);
57069
59323
  }
57070
59324
  }
59325
+ async function maybeRunLibrarian() {
59326
+ if (process.env.LIBRARIAN_ENABLED !== "true") return;
59327
+ try {
59328
+ await runLibrarian();
59329
+ } catch (err2) {
59330
+ console.error("\u26A0\uFE0F [Librarian] unhandled error (non-blocking):", err2);
59331
+ }
59332
+ }
57071
59333
  function startColdPathWorker() {
57072
59334
  if (process.env.COLD_PATH_ENABLED === "false") {
57073
59335
  console.error("\u{1F535} [ColdPath] disabled (COLD_PATH_ENABLED=false)");
57074
59336
  return;
57075
59337
  }
57076
- const intervalSec = envInt2("COLD_PATH_INTERVAL_SEC", DEFAULT_INTERVAL_SEC);
57077
- const warmupSec = envInt2("COLD_PATH_WARMUP_SEC", DEFAULT_WARMUP_SEC);
59338
+ const intervalSec = envInt3("COLD_PATH_INTERVAL_SEC", DEFAULT_INTERVAL_SEC);
59339
+ const warmupSec = envInt3("COLD_PATH_WARMUP_SEC", DEFAULT_WARMUP_SEC);
57078
59340
  console.error(`\u{1F535} [ColdPath] starting \u2014 warmup ${warmupSec}s, interval ${intervalSec}s`);
57079
59341
  warmupTimer = setTimeout(() => {
57080
59342
  intervalTimer = setInterval(() => {
57081
59343
  tick().catch((err2) => console.error("\u274C [ColdPath] unhandled tick error:", err2));
57082
59344
  maybeRunPromotion().catch((err2) => console.error("\u274C [DTagPromoter] unhandled error:", err2));
59345
+ maybeRunLibrarian().catch((err2) => console.error("\u274C [Librarian] unhandled error:", err2));
59346
+ maybeRunProjectAliasPromoter().catch((err2) => console.error("\u274C [ProjectAliasPromoter] unhandled error:", err2));
57083
59347
  }, intervalSec * 1e3);
57084
59348
  tick().catch((err2) => console.error("\u274C [ColdPath] unhandled first tick:", err2));
57085
59349
  maybeRunPromotion().catch((err2) => console.error("\u274C [DTagPromoter] unhandled error:", err2));
59350
+ maybeRunLibrarian().catch((err2) => console.error("\u274C [Librarian] unhandled error:", err2));
59351
+ maybeRunProjectAliasPromoter().catch((err2) => console.error("\u274C [ProjectAliasPromoter] unhandled error:", err2));
57086
59352
  }, warmupSec * 1e3);
57087
59353
  }
57088
59354
  function stopColdPathWorker() {
@@ -57111,43 +59377,46 @@ async function drainColdPath(maxTicks = 12) {
57111
59377
  }
57112
59378
 
57113
59379
  // src/auto_save/jsonl_capture.ts
57114
- import * as fs3 from "node:fs";
57115
- import * as path4 from "node:path";
57116
- import * as os6 from "node:os";
59380
+ import * as fs5 from "node:fs";
59381
+ import * as path6 from "node:path";
59382
+ import * as os9 from "node:os";
57117
59383
  var CLIENT_PLATFORM2 = "claude-code";
57118
- var DEVICE_NAME4 = os6.hostname();
57119
- var PROJECTS_ROOT = path4.join(os6.homedir(), ".claude", "projects");
57120
- var FLUSH_DEBOUNCE_MS2 = 200;
57121
- var _state2 = null;
57122
- var _watcher = null;
57123
- var _flushInProgress2 = false;
57124
- var _flushPending2 = false;
57125
- var _flushDebounceTimer2 = null;
59384
+ var DEVICE_NAME6 = os9.hostname();
59385
+ var PROJECTS_ROOT = path6.join(os9.homedir(), ".claude", "projects");
59386
+ var FLUSH_DEBOUNCE_MS4 = 200;
59387
+ var _state4 = null;
59388
+ var _watcher2 = null;
59389
+ var _flushInProgress4 = false;
59390
+ var _flushPending4 = false;
59391
+ var _flushDebounceTimer4 = null;
59392
+ function cwdToSlug(cwd) {
59393
+ return cwd.replace(/[^a-zA-Z0-9]/g, "-");
59394
+ }
57126
59395
  function findProjectDir(cwd) {
57127
- const slug = cwd.replace(/\//g, "-");
57128
- const slugPath = path4.join(PROJECTS_ROOT, slug);
59396
+ const slug = cwdToSlug(cwd);
59397
+ const slugPath = path6.join(PROJECTS_ROOT, slug);
57129
59398
  try {
57130
- if (fs3.statSync(slugPath).isDirectory()) return slugPath;
59399
+ if (fs5.statSync(slugPath).isDirectory()) return slugPath;
57131
59400
  } catch {
57132
59401
  }
57133
59402
  let subdirs;
57134
59403
  try {
57135
- subdirs = fs3.readdirSync(PROJECTS_ROOT, { withFileTypes: true });
59404
+ subdirs = fs5.readdirSync(PROJECTS_ROOT, { withFileTypes: true });
57136
59405
  } catch {
57137
59406
  return null;
57138
59407
  }
57139
59408
  for (const subdir of subdirs) {
57140
59409
  if (!subdir.isDirectory()) continue;
57141
- const dirPath = path4.join(PROJECTS_ROOT, subdir.name);
59410
+ const dirPath = path6.join(PROJECTS_ROOT, subdir.name);
57142
59411
  let files;
57143
59412
  try {
57144
- files = fs3.readdirSync(dirPath, { withFileTypes: true });
59413
+ files = fs5.readdirSync(dirPath, { withFileTypes: true });
57145
59414
  } catch {
57146
59415
  continue;
57147
59416
  }
57148
59417
  for (const f of files) {
57149
59418
  if (!f.isFile() || !f.name.endsWith(".jsonl")) continue;
57150
- const foundCwd = readCwdFromJsonl(path4.join(dirPath, f.name));
59419
+ const foundCwd = readCwdFromJsonl(path6.join(dirPath, f.name));
57151
59420
  if (foundCwd === cwd) return dirPath;
57152
59421
  }
57153
59422
  }
@@ -57156,13 +59425,13 @@ function findProjectDir(cwd) {
57156
59425
  function readCwdFromJsonl(jsonlPath) {
57157
59426
  let fd;
57158
59427
  try {
57159
- fd = fs3.openSync(jsonlPath, "r");
59428
+ fd = fs5.openSync(jsonlPath, "r");
57160
59429
  } catch {
57161
59430
  return null;
57162
59431
  }
57163
59432
  try {
57164
59433
  const buf = Buffer.alloc(2048);
57165
- const bytesRead = fs3.readSync(fd, buf, 0, 2048, 0);
59434
+ const bytesRead = fs5.readSync(fd, buf, 0, 2048, 0);
57166
59435
  const raw = buf.slice(0, bytesRead).toString("utf-8");
57167
59436
  for (const line of raw.split("\n")) {
57168
59437
  if (!line.trim()) continue;
@@ -57174,38 +59443,59 @@ function readCwdFromJsonl(jsonlPath) {
57174
59443
  }
57175
59444
  return null;
57176
59445
  } finally {
57177
- fs3.closeSync(fd);
59446
+ fs5.closeSync(fd);
57178
59447
  }
57179
59448
  }
57180
- function captureSessionStart2(cwd) {
57181
- _state2 = { cwd, projectDir: null, files: /* @__PURE__ */ new Map() };
59449
+ function captureSessionStart4(cwd) {
59450
+ _state4 = { cwd, projectDir: null, files: /* @__PURE__ */ new Map() };
59451
+ _tryArm(cwd, 0);
59452
+ }
59453
+ var _MAX_ARM_RETRIES = 20;
59454
+ function _tryArm(cwd, attempt) {
57182
59455
  const projectDir = findProjectDir(cwd);
57183
59456
  if (!projectDir) {
59457
+ if (attempt < _MAX_ARM_RETRIES) {
59458
+ setTimeout(() => {
59459
+ if (!_state4 || _state4.cwd !== cwd) return;
59460
+ _tryArm(cwd, attempt + 1);
59461
+ }, 500);
59462
+ } else {
59463
+ console.error(
59464
+ `\u26A0\uFE0F [JSONL] project dir not found after ${_MAX_ARM_RETRIES} retries \u2014 capture \uAC74\uB108\uB700.
59465
+ cwd="${cwd}"
59466
+ PROJECTS_ROOT="${PROJECTS_ROOT}"
59467
+ expected slug="${cwdToSlug(cwd)}"`
59468
+ );
59469
+ }
57184
59470
  return;
57185
59471
  }
57186
- _state2.projectDir = projectDir;
59472
+ if (!_state4 || _state4.cwd !== cwd) return;
59473
+ _state4.projectDir = projectDir;
57187
59474
  let entries;
57188
59475
  try {
57189
- entries = fs3.readdirSync(projectDir, { withFileTypes: true });
59476
+ entries = fs5.readdirSync(projectDir, { withFileTypes: true });
57190
59477
  } catch {
57191
59478
  return;
57192
59479
  }
57193
59480
  for (const entry of entries) {
57194
59481
  if (!entry.isFile() || !entry.name.endsWith(".jsonl")) continue;
57195
- const jsonlPath = path4.join(projectDir, entry.name);
59482
+ const jsonlPath = path6.join(projectDir, entry.name);
57196
59483
  let stat;
57197
59484
  try {
57198
- stat = fs3.statSync(jsonlPath);
59485
+ stat = fs5.statSync(jsonlPath);
57199
59486
  } catch {
57200
59487
  continue;
57201
59488
  }
57202
59489
  const sessionId = entry.name.replace(/\.jsonl$/, "");
57203
- _state2.files.set(jsonlPath, { cursorBytes: stat.size, sessionId, contentSeen: /* @__PURE__ */ new Set() });
59490
+ _state4.files.set(jsonlPath, { cursorBytes: stat.size, sessionId, contentSeen: /* @__PURE__ */ new Set() });
57204
59491
  }
57205
- console.error(`\u{1F4DD} [JSONL] capture armed: ${_state2.files.size} jsonl(s) in ${path4.basename(projectDir)}/`);
57206
- armDirWatcher();
59492
+ if (attempt > 0) {
59493
+ console.error(`\u{1F4DD} [JSONL] project dir found on retry #${attempt}: ${path6.basename(projectDir)}/`);
59494
+ }
59495
+ console.error(`\u{1F4DD} [JSONL] capture armed: ${_state4.files.size} jsonl(s) in ${path6.basename(projectDir)}/`);
59496
+ armDirWatcher3();
57207
59497
  }
57208
- function parseEntry(line) {
59498
+ function parseEntry3(line) {
57209
59499
  let entry;
57210
59500
  try {
57211
59501
  entry = JSON.parse(line);
@@ -57241,24 +59531,24 @@ function parseEntry(line) {
57241
59531
  agent_model
57242
59532
  };
57243
59533
  }
57244
- async function flushDeltaForFile2(jsonlPath, fileState) {
57245
- if (!fs3.existsSync(jsonlPath)) return { inserted: 0, skipped: 0, dedup: 0 };
59534
+ async function flushDeltaForFile4(jsonlPath, fileState) {
59535
+ if (!fs5.existsSync(jsonlPath)) return { inserted: 0, skipped: 0, dedup: 0 };
57246
59536
  let stat;
57247
59537
  try {
57248
- stat = fs3.statSync(jsonlPath);
59538
+ stat = fs5.statSync(jsonlPath);
57249
59539
  } catch {
57250
59540
  return { inserted: 0, skipped: 0, dedup: 0 };
57251
59541
  }
57252
59542
  if (stat.size <= fileState.cursorBytes) return { inserted: 0, skipped: 0, dedup: 0 };
57253
59543
  const length = stat.size - fileState.cursorBytes;
57254
- const fd = fs3.openSync(jsonlPath, "r");
59544
+ const fd = fs5.openSync(jsonlPath, "r");
57255
59545
  let raw;
57256
59546
  try {
57257
59547
  const buf = Buffer.alloc(length);
57258
- fs3.readSync(fd, buf, 0, length, fileState.cursorBytes);
59548
+ fs5.readSync(fd, buf, 0, length, fileState.cursorBytes);
57259
59549
  raw = buf.toString("utf-8");
57260
59550
  } finally {
57261
- fs3.closeSync(fd);
59551
+ fs5.closeSync(fd);
57262
59552
  }
57263
59553
  const lastNl = raw.lastIndexOf("\n");
57264
59554
  if (lastNl === -1) return { inserted: 0, skipped: 0, dedup: 0 };
@@ -57271,7 +59561,7 @@ async function flushDeltaForFile2(jsonlPath, fileState) {
57271
59561
  for (const line of lines) {
57272
59562
  const trimmed = line.trim();
57273
59563
  if (!trimmed) continue;
57274
- const parsed = parseEntry(trimmed);
59564
+ const parsed = parseEntry3(trimmed);
57275
59565
  if (!parsed) {
57276
59566
  skipped++;
57277
59567
  continue;
@@ -57292,7 +59582,7 @@ async function flushDeltaForFile2(jsonlPath, fileState) {
57292
59582
  role: parsed.role,
57293
59583
  message: parsed.message,
57294
59584
  external_uuid: externalUuid,
57295
- device_name: DEVICE_NAME4
59585
+ device_name: DEVICE_NAME6
57296
59586
  });
57297
59587
  if (result.inserted) inserted++;
57298
59588
  else dedup++;
@@ -57304,121 +59594,121 @@ async function flushDeltaForFile2(jsonlPath, fileState) {
57304
59594
  fileState.cursorBytes = newCursor;
57305
59595
  return { inserted, skipped, dedup };
57306
59596
  }
57307
- async function flushAllFiles2() {
57308
- if (!_state2 || !_state2.projectDir) return { inserted: 0, skipped: 0, dedup: 0 };
57309
- const projectDir = _state2.projectDir;
57310
- if (!fs3.existsSync(projectDir)) return { inserted: 0, skipped: 0, dedup: 0 };
59597
+ async function flushAllFiles4() {
59598
+ if (!_state4 || !_state4.projectDir) return { inserted: 0, skipped: 0, dedup: 0 };
59599
+ const projectDir = _state4.projectDir;
59600
+ if (!fs5.existsSync(projectDir)) return { inserted: 0, skipped: 0, dedup: 0 };
57311
59601
  let entries;
57312
59602
  try {
57313
- entries = fs3.readdirSync(projectDir, { withFileTypes: true });
59603
+ entries = fs5.readdirSync(projectDir, { withFileTypes: true });
57314
59604
  } catch {
57315
59605
  return { inserted: 0, skipped: 0, dedup: 0 };
57316
59606
  }
57317
59607
  let totalI = 0, totalS = 0, totalD = 0;
57318
59608
  for (const entry of entries) {
57319
59609
  if (!entry.isFile() || !entry.name.endsWith(".jsonl")) continue;
57320
- const jsonlPath = path4.join(projectDir, entry.name);
57321
- let fileState = _state2.files.get(jsonlPath);
59610
+ const jsonlPath = path6.join(projectDir, entry.name);
59611
+ let fileState = _state4.files.get(jsonlPath);
57322
59612
  if (!fileState) {
57323
59613
  const sessionId = entry.name.replace(/\.jsonl$/, "");
57324
59614
  fileState = { cursorBytes: 0, sessionId, contentSeen: /* @__PURE__ */ new Set() };
57325
- _state2.files.set(jsonlPath, fileState);
59615
+ _state4.files.set(jsonlPath, fileState);
57326
59616
  console.error(`\u{1F4DD} [JSONL] new session detected: ${entry.name}`);
57327
59617
  }
57328
- const r = await flushDeltaForFile2(jsonlPath, fileState);
59618
+ const r = await flushDeltaForFile4(jsonlPath, fileState);
57329
59619
  totalI += r.inserted;
57330
59620
  totalS += r.skipped;
57331
59621
  totalD += r.dedup;
57332
59622
  }
57333
59623
  return { inserted: totalI, skipped: totalS, dedup: totalD };
57334
59624
  }
57335
- async function flushWithMutex2() {
57336
- if (_flushInProgress2) {
57337
- _flushPending2 = true;
59625
+ async function flushWithMutex4() {
59626
+ if (_flushInProgress4) {
59627
+ _flushPending4 = true;
57338
59628
  return;
57339
59629
  }
57340
- _flushInProgress2 = true;
59630
+ _flushInProgress4 = true;
57341
59631
  try {
57342
- const r = await flushAllFiles2();
59632
+ const r = await flushAllFiles4();
57343
59633
  if (r.inserted > 0 || r.skipped > 0 || r.dedup > 0) {
57344
59634
  console.error(`\u{1F4DD} [JSONL] live flush: inserted=${r.inserted}, dedup=${r.dedup}, skipped=${r.skipped}`);
57345
59635
  }
57346
59636
  } catch (err2) {
57347
59637
  console.error("\u26A0\uFE0F [JSONL] live flush error:", err2);
57348
59638
  } finally {
57349
- _flushInProgress2 = false;
57350
- if (_flushPending2) {
57351
- _flushPending2 = false;
59639
+ _flushInProgress4 = false;
59640
+ if (_flushPending4) {
59641
+ _flushPending4 = false;
57352
59642
  setImmediate(() => {
57353
- void flushWithMutex2();
59643
+ void flushWithMutex4();
57354
59644
  });
57355
59645
  }
57356
59646
  }
57357
59647
  }
57358
- function scheduleFlush2() {
57359
- if (_flushDebounceTimer2) clearTimeout(_flushDebounceTimer2);
57360
- _flushDebounceTimer2 = setTimeout(() => {
57361
- _flushDebounceTimer2 = null;
57362
- void flushWithMutex2();
57363
- }, FLUSH_DEBOUNCE_MS2);
59648
+ function scheduleFlush4() {
59649
+ if (_flushDebounceTimer4) clearTimeout(_flushDebounceTimer4);
59650
+ _flushDebounceTimer4 = setTimeout(() => {
59651
+ _flushDebounceTimer4 = null;
59652
+ void flushWithMutex4();
59653
+ }, FLUSH_DEBOUNCE_MS4);
57364
59654
  }
57365
- function armDirWatcher() {
57366
- if (!_state2 || !_state2.projectDir) return;
57367
- if (_watcher) return;
59655
+ function armDirWatcher3() {
59656
+ if (!_state4 || !_state4.projectDir) return;
59657
+ if (_watcher2) return;
57368
59658
  try {
57369
- _watcher = fs3.watch(_state2.projectDir, (_eventType, filename) => {
59659
+ _watcher2 = fs5.watch(_state4.projectDir, (_eventType, filename) => {
57370
59660
  if (filename && !filename.endsWith(".jsonl")) return;
57371
- scheduleFlush2();
59661
+ scheduleFlush4();
57372
59662
  });
57373
59663
  console.error(`\u{1F4DD} [JSONL] dir watcher armed`);
57374
59664
  } catch (err2) {
57375
59665
  console.error("\u26A0\uFE0F [JSONL] fs.watch dir failed (shutdown-only flush \uD3F4\uBC31):", err2);
57376
59666
  }
57377
59667
  }
57378
- function disarmDirWatcher() {
57379
- if (_flushDebounceTimer2) {
57380
- clearTimeout(_flushDebounceTimer2);
57381
- _flushDebounceTimer2 = null;
59668
+ function disarmDirWatcher3() {
59669
+ if (_flushDebounceTimer4) {
59670
+ clearTimeout(_flushDebounceTimer4);
59671
+ _flushDebounceTimer4 = null;
57382
59672
  }
57383
- if (_watcher) {
59673
+ if (_watcher2) {
57384
59674
  try {
57385
- _watcher.close();
59675
+ _watcher2.close();
57386
59676
  } catch {
57387
59677
  }
57388
- _watcher = null;
59678
+ _watcher2 = null;
57389
59679
  }
57390
59680
  }
57391
- async function captureSessionEnd2() {
57392
- disarmDirWatcher();
59681
+ async function captureSessionEnd4() {
59682
+ disarmDirWatcher3();
57393
59683
  const waitStart = Date.now();
57394
- while (_flushInProgress2 && Date.now() - waitStart < 2e3) {
59684
+ while (_flushInProgress4 && Date.now() - waitStart < 2e3) {
57395
59685
  await new Promise((r) => setTimeout(r, 50));
57396
59686
  }
57397
- if (!_state2 || !_state2.projectDir) {
59687
+ if (!_state4 || !_state4.projectDir) {
57398
59688
  return { inserted: 0, skipped: 0, error: "session not armed" };
57399
59689
  }
57400
- _flushInProgress2 = true;
59690
+ _flushInProgress4 = true;
57401
59691
  try {
57402
- const r = await flushAllFiles2();
59692
+ const r = await flushAllFiles4();
57403
59693
  if (r.inserted > 0 || r.skipped > 0 || r.dedup > 0) {
57404
59694
  console.error(`\u{1F4DD} [JSONL] final flush: inserted=${r.inserted}, dedup=${r.dedup}, skipped=${r.skipped}`);
57405
59695
  }
57406
59696
  return { inserted: r.inserted, skipped: r.skipped };
57407
59697
  } finally {
57408
- _flushInProgress2 = false;
59698
+ _flushInProgress4 = false;
57409
59699
  }
57410
59700
  }
57411
59701
 
57412
59702
  // src/auto_save/codex_capture.ts
57413
- import * as fs4 from "node:fs";
57414
- import * as path5 from "node:path";
57415
- import * as os7 from "node:os";
59703
+ import * as fs6 from "node:fs";
59704
+ import * as path7 from "node:path";
59705
+ import * as os10 from "node:os";
57416
59706
  import { spawnSync } from "node:child_process";
57417
- var DEVICE_NAME5 = os7.hostname();
57418
- var SESSIONS_ROOT = path5.join(os7.homedir(), ".codex", "sessions");
57419
- var STATE_DB = path5.join(os7.homedir(), ".codex", "state_5.sqlite");
57420
- var FLUSH_DEBOUNCE_MS3 = 200;
57421
- var POLL_INTERVAL_MS2 = 3e3;
59707
+ var DEVICE_NAME7 = os10.hostname();
59708
+ var SESSIONS_ROOT2 = path7.join(os10.homedir(), ".codex", "sessions");
59709
+ var STATE_DB = path7.join(os10.homedir(), ".codex", "state_5.sqlite");
59710
+ var FLUSH_DEBOUNCE_MS5 = 200;
59711
+ var POLL_INTERVAL_MS4 = 3e3;
57422
59712
  function sourceToPlatform(source) {
57423
59713
  switch (source) {
57424
59714
  case "cli":
@@ -57446,30 +59736,30 @@ function lookupPlatform(sessionId) {
57446
59736
  }
57447
59737
  return "codex-mcp-client";
57448
59738
  }
57449
- var _state3 = null;
57450
- var SERVER_START_MS = Date.now();
57451
- var _watcher2 = null;
57452
- var _pollTimer2 = null;
57453
- var _flushInProgress3 = false;
57454
- var _flushPending3 = false;
57455
- var _flushDebounceTimer3 = null;
57456
- function extractSessionId(filename) {
59739
+ var _state5 = null;
59740
+ var SERVER_START_MS3 = Date.now();
59741
+ var _watcher3 = null;
59742
+ var _pollTimer4 = null;
59743
+ var _flushInProgress5 = false;
59744
+ var _flushPending5 = false;
59745
+ var _flushDebounceTimer5 = null;
59746
+ function extractSessionId2(filename) {
57457
59747
  const m = filename.match(/^rollout-.+?-([0-9a-f-]{36})\.jsonl$/);
57458
59748
  return m ? m[1] : null;
57459
59749
  }
57460
59750
  function* walkRollouts(root) {
57461
- if (!fs4.existsSync(root)) return;
59751
+ if (!fs6.existsSync(root)) return;
57462
59752
  const stack = [root];
57463
59753
  while (stack.length) {
57464
59754
  const dir = stack.pop();
57465
59755
  let entries;
57466
59756
  try {
57467
- entries = fs4.readdirSync(dir, { withFileTypes: true });
59757
+ entries = fs6.readdirSync(dir, { withFileTypes: true });
57468
59758
  } catch {
57469
59759
  continue;
57470
59760
  }
57471
59761
  for (const e of entries) {
57472
- const p = path5.join(dir, e.name);
59762
+ const p = path7.join(dir, e.name);
57473
59763
  if (e.isDirectory()) {
57474
59764
  stack.push(p);
57475
59765
  } else if (e.isFile() && e.name.startsWith("rollout-") && e.name.endsWith(".jsonl")) {
@@ -57478,9 +59768,9 @@ function* walkRollouts(root) {
57478
59768
  }
57479
59769
  }
57480
59770
  }
57481
- function captureSessionStart3(_cwd) {
57482
- const rootExists = fs4.existsSync(SESSIONS_ROOT);
57483
- _state3 = {
59771
+ function captureSessionStart5(_cwd) {
59772
+ const rootExists = fs6.existsSync(SESSIONS_ROOT2);
59773
+ _state5 = {
57484
59774
  rootExists,
57485
59775
  files: /* @__PURE__ */ new Map()
57486
59776
  };
@@ -57488,16 +59778,16 @@ function captureSessionStart3(_cwd) {
57488
59778
  return;
57489
59779
  }
57490
59780
  let count = 0;
57491
- for (const filePath of walkRollouts(SESSIONS_ROOT)) {
59781
+ for (const filePath of walkRollouts(SESSIONS_ROOT2)) {
57492
59782
  let stat;
57493
59783
  try {
57494
- stat = fs4.statSync(filePath);
59784
+ stat = fs6.statSync(filePath);
57495
59785
  } catch {
57496
59786
  continue;
57497
59787
  }
57498
- const sessionId = extractSessionId(path5.basename(filePath));
59788
+ const sessionId = extractSessionId2(path7.basename(filePath));
57499
59789
  if (!sessionId) continue;
57500
- _state3.files.set(filePath, {
59790
+ _state5.files.set(filePath, {
57501
59791
  cursorBytes: stat.size,
57502
59792
  sessionId,
57503
59793
  agentPlatform: lookupPlatform(sessionId),
@@ -57506,9 +59796,9 @@ function captureSessionStart3(_cwd) {
57506
59796
  count++;
57507
59797
  }
57508
59798
  console.error(`\u{1F4DD} [Codex] capture armed: ${count} rollout(s)`);
57509
- armDirWatcher2();
59799
+ armDirWatcher4();
57510
59800
  }
57511
- function parseEntry2(line, byteOffset) {
59801
+ function parseEntry4(line, byteOffset) {
57512
59802
  let entry;
57513
59803
  try {
57514
59804
  entry = JSON.parse(line);
@@ -57544,24 +59834,24 @@ function extractModelFromTurnContext(line) {
57544
59834
  return null;
57545
59835
  }
57546
59836
  }
57547
- async function flushDeltaForFile3(filePath, fileState) {
57548
- if (!fs4.existsSync(filePath)) return { inserted: 0, skipped: 0, dedup: 0 };
59837
+ async function flushDeltaForFile5(filePath, fileState) {
59838
+ if (!fs6.existsSync(filePath)) return { inserted: 0, skipped: 0, dedup: 0 };
57549
59839
  let stat;
57550
59840
  try {
57551
- stat = fs4.statSync(filePath);
59841
+ stat = fs6.statSync(filePath);
57552
59842
  } catch {
57553
59843
  return { inserted: 0, skipped: 0, dedup: 0 };
57554
59844
  }
57555
59845
  if (stat.size <= fileState.cursorBytes) return { inserted: 0, skipped: 0, dedup: 0 };
57556
59846
  const length = stat.size - fileState.cursorBytes;
57557
- const fd = fs4.openSync(filePath, "r");
59847
+ const fd = fs6.openSync(filePath, "r");
57558
59848
  let raw;
57559
59849
  try {
57560
59850
  const buf = Buffer.alloc(length);
57561
- fs4.readSync(fd, buf, 0, length, fileState.cursorBytes);
59851
+ fs6.readSync(fd, buf, 0, length, fileState.cursorBytes);
57562
59852
  raw = buf.toString("utf-8");
57563
59853
  } finally {
57564
- fs4.closeSync(fd);
59854
+ fs6.closeSync(fd);
57565
59855
  }
57566
59856
  const lastNl = raw.lastIndexOf("\n");
57567
59857
  if (lastNl === -1) return { inserted: 0, skipped: 0, dedup: 0 };
@@ -57586,7 +59876,7 @@ async function flushDeltaForFile3(filePath, fileState) {
57586
59876
  continue;
57587
59877
  }
57588
59878
  const byteOffset = startBase + lineStartInRaw;
57589
- const parsed = parseEntry2(trimmed, byteOffset);
59879
+ const parsed = parseEntry4(trimmed, byteOffset);
57590
59880
  if (!parsed) {
57591
59881
  skipped++;
57592
59882
  lineStartInRaw += Buffer.byteLength(line, "utf-8") + 1;
@@ -57602,7 +59892,7 @@ async function flushDeltaForFile3(filePath, fileState) {
57602
59892
  role: parsed.role,
57603
59893
  message: parsed.message,
57604
59894
  external_uuid: externalUuid,
57605
- device_name: DEVICE_NAME5
59895
+ device_name: DEVICE_NAME7
57606
59896
  });
57607
59897
  if (result.inserted) inserted++;
57608
59898
  else dedup++;
@@ -57615,26 +59905,26 @@ async function flushDeltaForFile3(filePath, fileState) {
57615
59905
  fileState.cursorBytes = newCursor;
57616
59906
  return { inserted, skipped, dedup };
57617
59907
  }
57618
- async function flushAllFiles3() {
57619
- if (!_state3 || !_state3.rootExists) return { inserted: 0, skipped: 0, dedup: 0 };
59908
+ async function flushAllFiles5() {
59909
+ if (!_state5 || !_state5.rootExists) return { inserted: 0, skipped: 0, dedup: 0 };
57620
59910
  let totalI = 0, totalS = 0, totalD = 0;
57621
- for (const filePath of walkRollouts(SESSIONS_ROOT)) {
57622
- let fileState = _state3.files.get(filePath);
59911
+ for (const filePath of walkRollouts(SESSIONS_ROOT2)) {
59912
+ let fileState = _state5.files.get(filePath);
57623
59913
  if (!fileState) {
57624
- const sessionId = extractSessionId(path5.basename(filePath));
59914
+ const sessionId = extractSessionId2(path7.basename(filePath));
57625
59915
  if (!sessionId) continue;
57626
59916
  let stat = null;
57627
59917
  try {
57628
- stat = fs4.statSync(filePath);
59918
+ stat = fs6.statSync(filePath);
57629
59919
  } catch {
57630
59920
  }
57631
- const fileBirthMs = stat ? stat.birthtimeMs || stat.ctimeMs : SERVER_START_MS;
57632
- const isPreExisting = fileBirthMs < SERVER_START_MS - 5e3;
59921
+ const fileBirthMs = stat ? stat.birthtimeMs || stat.ctimeMs : SERVER_START_MS3;
59922
+ const isPreExisting = fileBirthMs < SERVER_START_MS3 - 5e3;
57633
59923
  const initialCursor = isPreExisting ? stat?.size ?? 0 : 0;
57634
59924
  if (isPreExisting) {
57635
- console.error(`\u{1F4DD} [Codex] pre-existing rollout skipped (cursor=${initialCursor}): ${path5.basename(filePath)}`);
59925
+ console.error(`\u{1F4DD} [Codex] pre-existing rollout skipped (cursor=${initialCursor}): ${path7.basename(filePath)}`);
57636
59926
  } else {
57637
- console.error(`\u{1F4DD} [Codex] new rollout detected: ${path5.basename(filePath)}`);
59927
+ console.error(`\u{1F4DD} [Codex] new rollout detected: ${path7.basename(filePath)}`);
57638
59928
  }
57639
59929
  fileState = {
57640
59930
  cursorBytes: initialCursor,
@@ -57642,129 +59932,126 @@ async function flushAllFiles3() {
57642
59932
  agentPlatform: lookupPlatform(sessionId),
57643
59933
  currentModel: null
57644
59934
  };
57645
- _state3.files.set(filePath, fileState);
59935
+ _state5.files.set(filePath, fileState);
57646
59936
  }
57647
- const r = await flushDeltaForFile3(filePath, fileState);
59937
+ const r = await flushDeltaForFile5(filePath, fileState);
57648
59938
  totalI += r.inserted;
57649
59939
  totalS += r.skipped;
57650
59940
  totalD += r.dedup;
57651
59941
  }
57652
59942
  return { inserted: totalI, skipped: totalS, dedup: totalD };
57653
59943
  }
57654
- async function flushWithMutex3() {
57655
- if (_flushInProgress3) {
57656
- _flushPending3 = true;
59944
+ async function flushWithMutex5() {
59945
+ if (_flushInProgress5) {
59946
+ _flushPending5 = true;
57657
59947
  return;
57658
59948
  }
57659
- _flushInProgress3 = true;
59949
+ _flushInProgress5 = true;
57660
59950
  try {
57661
- const r = await flushAllFiles3();
59951
+ const r = await flushAllFiles5();
57662
59952
  if (r.inserted > 0 || r.skipped > 0 || r.dedup > 0) {
57663
59953
  console.error(`\u{1F4DD} [Codex] live flush: inserted=${r.inserted}, dedup=${r.dedup}, skipped=${r.skipped}`);
57664
59954
  }
57665
59955
  } catch (err2) {
57666
59956
  console.error("\u26A0\uFE0F [Codex] live flush error:", err2);
57667
59957
  } finally {
57668
- _flushInProgress3 = false;
57669
- if (_flushPending3) {
57670
- _flushPending3 = false;
59958
+ _flushInProgress5 = false;
59959
+ if (_flushPending5) {
59960
+ _flushPending5 = false;
57671
59961
  setImmediate(() => {
57672
- void flushWithMutex3();
59962
+ void flushWithMutex5();
57673
59963
  });
57674
59964
  }
57675
59965
  }
57676
59966
  }
57677
- function scheduleFlush3() {
57678
- if (_flushDebounceTimer3) clearTimeout(_flushDebounceTimer3);
57679
- _flushDebounceTimer3 = setTimeout(() => {
57680
- _flushDebounceTimer3 = null;
57681
- void flushWithMutex3();
57682
- }, FLUSH_DEBOUNCE_MS3);
59967
+ function scheduleFlush5() {
59968
+ if (_flushDebounceTimer5) clearTimeout(_flushDebounceTimer5);
59969
+ _flushDebounceTimer5 = setTimeout(() => {
59970
+ _flushDebounceTimer5 = null;
59971
+ void flushWithMutex5();
59972
+ }, FLUSH_DEBOUNCE_MS5);
57683
59973
  }
57684
- function armDirWatcher2() {
57685
- if (!_state3 || !_state3.rootExists) return;
57686
- if (_watcher2) return;
59974
+ function armDirWatcher4() {
59975
+ if (!_state5 || !_state5.rootExists) return;
59976
+ if (_watcher3) return;
57687
59977
  try {
57688
- _watcher2 = fs4.watch(SESSIONS_ROOT, { recursive: true }, (_evt, filename) => {
59978
+ _watcher3 = fs6.watch(SESSIONS_ROOT2, { recursive: true }, (_evt, filename) => {
57689
59979
  if (!filename) return;
57690
- const base = path5.basename(filename);
59980
+ const base = path7.basename(filename);
57691
59981
  if (!base.startsWith("rollout-") || !base.endsWith(".jsonl")) return;
57692
- scheduleFlush3();
59982
+ scheduleFlush5();
57693
59983
  });
57694
59984
  console.error(`\u{1F4DD} [Codex] dir watcher armed (recursive)`);
57695
59985
  } catch (err2) {
57696
59986
  console.error("\u26A0\uFE0F [Codex] fs.watch failed \u2014 polling only:", err2);
57697
59987
  }
57698
- _pollTimer2 = setInterval(() => {
57699
- void flushWithMutex3();
57700
- }, POLL_INTERVAL_MS2);
59988
+ _pollTimer4 = setInterval(() => {
59989
+ void flushWithMutex5();
59990
+ }, POLL_INTERVAL_MS4);
57701
59991
  }
57702
- function disarmDirWatcher2() {
57703
- if (_flushDebounceTimer3) {
57704
- clearTimeout(_flushDebounceTimer3);
57705
- _flushDebounceTimer3 = null;
59992
+ function disarmDirWatcher4() {
59993
+ if (_flushDebounceTimer5) {
59994
+ clearTimeout(_flushDebounceTimer5);
59995
+ _flushDebounceTimer5 = null;
57706
59996
  }
57707
- if (_pollTimer2) {
57708
- clearInterval(_pollTimer2);
57709
- _pollTimer2 = null;
59997
+ if (_pollTimer4) {
59998
+ clearInterval(_pollTimer4);
59999
+ _pollTimer4 = null;
57710
60000
  }
57711
- if (_watcher2) {
60001
+ if (_watcher3) {
57712
60002
  try {
57713
- _watcher2.close();
60003
+ _watcher3.close();
57714
60004
  } catch {
57715
60005
  }
57716
- _watcher2 = null;
60006
+ _watcher3 = null;
57717
60007
  }
57718
60008
  }
57719
- async function captureSessionEnd3() {
57720
- disarmDirWatcher2();
60009
+ async function captureSessionEnd5() {
60010
+ disarmDirWatcher4();
57721
60011
  const waitStart = Date.now();
57722
- while (_flushInProgress3 && Date.now() - waitStart < 2e3) {
60012
+ while (_flushInProgress5 && Date.now() - waitStart < 2e3) {
57723
60013
  await new Promise((r) => setTimeout(r, 50));
57724
60014
  }
57725
- if (!_state3 || !_state3.rootExists) {
60015
+ if (!_state5 || !_state5.rootExists) {
57726
60016
  return { inserted: 0, skipped: 0, error: "session not armed" };
57727
60017
  }
57728
- _flushInProgress3 = true;
60018
+ _flushInProgress5 = true;
57729
60019
  try {
57730
- const r = await flushAllFiles3();
60020
+ const r = await flushAllFiles5();
57731
60021
  if (r.inserted > 0 || r.skipped > 0 || r.dedup > 0) {
57732
60022
  console.error(`\u{1F4DD} [Codex] final flush: inserted=${r.inserted}, dedup=${r.dedup}, skipped=${r.skipped}`);
57733
60023
  }
57734
60024
  return { inserted: r.inserted, skipped: r.skipped };
57735
60025
  } finally {
57736
- _flushInProgress3 = false;
60026
+ _flushInProgress5 = false;
57737
60027
  }
57738
60028
  }
57739
60029
 
57740
60030
  // src/version.ts
57741
- import fs5 from "fs";
57742
- import path6 from "path";
60031
+ import fs7 from "fs";
60032
+ import path8 from "path";
57743
60033
  import { fileURLToPath as fileURLToPath2 } from "url";
57744
- var __dirname3 = path6.dirname(fileURLToPath2(import.meta.url));
57745
- var packageJsonPath = path6.resolve(__dirname3, "..", "package.json");
57746
- var packageJson = JSON.parse(fs5.readFileSync(packageJsonPath, "utf8"));
60034
+ var __dirname3 = path8.dirname(fileURLToPath2(import.meta.url));
60035
+ var packageJsonPath = path8.resolve(__dirname3, "..", "package.json");
60036
+ var packageJson = JSON.parse(fs7.readFileSync(packageJsonPath, "utf8"));
57747
60037
  var PACKAGE_VERSION2 = packageJson.version || "0.0.0";
57748
60038
 
57749
60039
  // src/index.ts
57750
- import fs7 from "fs";
60040
+ import fs9 from "fs";
57751
60041
  var BRIEF_DB_TIMEOUT_MS = 5e3;
60042
+ var INSTRUCTIONS_MAX_CHARS = Number(process.env.INSTRUCTIONS_MAX_CHARS ?? 1900);
60043
+ var INSTRUCTIONS_SEP = "\n\n---\n\n";
57752
60044
  var STATIC_INSTRUCTIONS = `Long-term memory MCP server (RESPEC v1).
57753
60045
 
57754
- Tools:
57755
- - memory_startup : \uC2DC\uC791 brief (user profile + \uCD5C\uADFC \uD65C\uC131 \uD504\uB85C\uC81D\uD2B8 + \uCD5C\uADFC \uBA54\uBAA8\uB9AC). \uC138\uC158 \uC2DC\uC791 \uC2DC \uCCAB \uD638\uCD9C \uAD8C\uC7A5.
57756
- - search_memory : \uACFC\uAC70 \uAE30\uC5B5 \uC870\uD68C/\uAC80\uC0C9 \uD1B5\uD569 (\uC758\uBBF8 + \uD0A4\uC6CC\uB4DC fallback)
57757
- - manage_knowledge : \uBA85\uC2DC \uC800\uC7A5/\uC218\uC815/\uC0AD\uC81C \uD1B5\uD569 (\uAC15\uC81C \uAE30\uC5B5\uC740 is_pinned, archive \uBA74\uC81C)
57758
- - save_message : transcript \uCEA1\uCC98 \uBBF8\uC9C0\uC6D0 platform \uD55C\uC815 fallback (Cursor \uB4F1). Hot Path \uC9C1\uC811 INSERT.
60046
+ \u25B6 \uC138\uC158 \uC2DC\uC791 \uC2DC \`memory_startup\`\uC744 \uD55C \uBC88 \uD638\uCD9C\uD574 \uCD5C\uADFC \uB300\uD654\xB7\uD65C\uC131 \uD504\uB85C\uC81D\uD2B8\xB7\uC0C1\uC138 \uD504\uB85C\uD544 \uB9E5\uB77D\uC744 \uC774\uC5B4\uBC1B\uC73C\uC138\uC694.
60047
+
60048
+ Tools: memory_startup(\uC2DC\uC791 brief) \xB7 search_memory(\uACFC\uAC70 \uC870\uD68C/\uAC80\uC0C9) \xB7 manage_knowledge(\uC800\uC7A5/\uC218\uC815/\uC0AD\uC81C; \uAC15\uC81C\uAE30\uC5B5 is_pinned) \xB7 save_message(transcript \uBBF8\uC9C0\uC6D0 platform fallback).
57759
60049
 
57760
- Hot Path (\uC790\uB3D9 \uC800\uC7A5) \uB8F0:
57761
- - **Claude Code / Codex CLI / Gemini CLI**: server\uAC00 transcript \uD30C\uC77C\uC744 passive read\uD574\uC11C \uC790\uB3D9 \uCEA1\uCC98.
57762
- \uBCC4\uB3C4 save_message \uD638\uCD9C X. \uD638\uCD9C\uD558\uBA74 \uB3D9\uC77C \uBA54\uC2DC\uC9C0 \uC911\uBCF5 row \uBC1C\uC0DD.
57763
- - **\uADF8 \uC678 platform** (Cursor, Antigravity \uB4F1 transcript \uBE44\uACF5\uAC1C): \uB9E4 turn save_message \uD638\uCD9C \u2014 fallback.
60050
+ \uC790\uB3D9 \uC800\uC7A5: Claude Code / Codex CLI / Gemini CLI / Grok Build / Antigravity\uB294 transcript \uC790\uB3D9 \uCEA1\uCC98 \u2014 save_message \uD638\uCD9C \uAE08\uC9C0(\uC911\uBCF5 row). \uADF8 \uC678 platform\uB9CC \uB9E4 turn save_message.
57764
60051
 
57765
- caller convention:
57766
- - manage_knowledge / save_message \uD638\uCD9C \uC2DC agent_model \uBA85\uC2DC (\uC0DD\uB7B5 \uC2DC 'unknown' \uC800\uC7A5)
57767
- - subagent context\uBA74 subagent: true + subagent_model + subagent_role \uD568\uAED8`;
60052
+ \uB2A5\uB3D9 \uADDC\uCE59(mandatory): named entity(\uD504\uB85C\uC81D\uD2B8\xB7repo\xB7\uC778\uBB3C) \uC5B8\uAE09 \uC2DC, \uB610\uB294 \uACFC\uAC70 \uC120\uD638\xB7\uACB0\uC815\uC744 \uAC00\uC815\uD558\uAE30 \uC804 \uBA3C\uC800 search_memory. \uC791\uC5C5\uB2F9 1-2\uD68C.
60053
+
60054
+ \uD638\uCD9C \uC2DC agent_model \uBA85\uC2DC (subagent\uBA74 subagent:true + subagent_model/role \uB3D9\uBD09).`;
57768
60055
  var STATIC_INSTRUCTIONS_BRIEF_UNAVAILABLE = `
57769
60056
 
57770
60057
  ---
@@ -57784,7 +60071,11 @@ async function buildInstructions() {
57784
60071
  await db2.connect();
57785
60072
  const currentPlatform = detectBootPlatformFromEnv();
57786
60073
  const brief = await collectBrief({ currentPlatform });
57787
- return formatBriefMarkdown(brief);
60074
+ const briefBudget = Math.max(
60075
+ 0,
60076
+ INSTRUCTIONS_MAX_CHARS - STATIC_INSTRUCTIONS.length - INSTRUCTIONS_SEP.length
60077
+ );
60078
+ return formatBriefMarkdown(brief, { mode: "inject", maxChars: briefBudget });
57788
60079
  })();
57789
60080
  const briefMd = await Promise.race([
57790
60081
  work,
@@ -57792,11 +60083,13 @@ async function buildInstructions() {
57792
60083
  (_, reject) => setTimeout(() => reject(new Error("buildInstructions timeout")), BRIEF_DB_TIMEOUT_MS)
57793
60084
  )
57794
60085
  ]);
57795
- return `${STATIC_INSTRUCTIONS}
57796
-
57797
- ---
57798
-
57799
- ${briefMd}`;
60086
+ const assembled = `${STATIC_INSTRUCTIONS}${INSTRUCTIONS_SEP}${briefMd}`;
60087
+ if (assembled.length > INSTRUCTIONS_MAX_CHARS) {
60088
+ console.error(
60089
+ `\u26A0\uFE0F instructions ${assembled.length}\uC790 > cap ${INSTRUCTIONS_MAX_CHARS} \u2014 Core Profile \uAE38\uC774 \uC810\uAC80 \uD544\uC694`
60090
+ );
60091
+ }
60092
+ return assembled;
57800
60093
  } catch (err2) {
57801
60094
  console.error("\u26A0\uFE0F Brief \uB3D9\uC801 \uC8FC\uC785 \uC2E4\uD328 (DB connect \uB610\uB294 brief \uCFFC\uB9AC timeout):", err2 instanceof Error ? err2.message : err2);
57802
60095
  console.error(" static fallback + 'memory_startup \uBA85\uC2DC \uD638\uCD9C \uAD8C\uC7A5' \uC548\uB0B4 \uD3EC\uD568.");
@@ -57813,9 +60106,11 @@ async function shutdown(reason) {
57813
60106
  try {
57814
60107
  await Promise.race([
57815
60108
  Promise.allSettled([
60109
+ captureSessionEnd4(),
60110
+ captureSessionEnd5(),
60111
+ captureSessionEnd(),
57816
60112
  captureSessionEnd2(),
57817
- captureSessionEnd3(),
57818
- captureSessionEnd()
60113
+ captureSessionEnd3()
57819
60114
  ]),
57820
60115
  new Promise((resolve) => setTimeout(resolve, 3e3))
57821
60116
  ]);
@@ -57921,14 +60216,16 @@ async function runMcpServer() {
57921
60216
  registerTools(server);
57922
60217
  installShutdownHandlers();
57923
60218
  startParentWatchdog();
60219
+ captureSessionStart4(process.cwd());
60220
+ captureSessionStart5(process.cwd());
60221
+ captureSessionStart(process.cwd());
57924
60222
  captureSessionStart2(process.cwd());
57925
60223
  captureSessionStart3(process.cwd());
57926
- captureSessionStart(process.cwd());
57927
60224
  console.error("\u{1F680} Starting Memory MCP Server...");
57928
60225
  if (process.env.SSH_ENABLED === "true" && !process.env.SSH_KEY_PATH) {
57929
60226
  throw new Error("\u274C SSH_KEY_PATH is required when SSH is enabled");
57930
60227
  }
57931
- if (process.env.SSH_KEY_PATH && !fs7.existsSync(process.env.SSH_KEY_PATH)) {
60228
+ if (process.env.SSH_KEY_PATH && !fs9.existsSync(process.env.SSH_KEY_PATH)) {
57932
60229
  console.error(`\u26A0\uFE0F [WARNING] SSH key not found at: ${process.env.SSH_KEY_PATH}`);
57933
60230
  }
57934
60231
  try {