mcp-agents-memory 0.9.6 → 0.9.11

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,49 @@ 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
+ let effectiveThinking = opts.enableThinking;
55462
+ if (opts.enableThinking === true && opts.jsonSchema) {
55463
+ console.warn(
55464
+ "[ModelRegistry] enableThinking + jsonSchema both set \u2014 forcing thinking OFF (llama.cpp #20345 silently bypasses grammar when thinking is on)."
55465
+ );
55466
+ effectiveThinking = false;
55467
+ }
55468
+ const responseFormat = opts.jsonSchema ? {
55469
+ type: "json_schema",
55470
+ json_schema: {
55471
+ name: "librarian_profile",
55472
+ strict: true,
55473
+ schema: opts.jsonSchema
55474
+ }
55475
+ } : useJson ? { type: "json_object" } : void 0;
55476
+ const params = {
55477
+ model: spec.model_name,
55478
+ messages: [
55479
+ { role: "system", content: opts.system },
55480
+ { role: "user", content: opts.user }
55481
+ ],
55482
+ ...responseFormat ? { response_format: responseFormat } : {},
55483
+ temperature: 0.1,
55484
+ max_tokens: maxTokens
55485
+ };
55486
+ const res = await client2.chat.completions.create(
55487
+ // narrow cast: only this call site is widened to accommodate chat_template_kwargs
55488
+ {
55489
+ ...params,
55490
+ ...effectiveThinking !== void 0 ? { chat_template_kwargs: { enable_thinking: effectiveThinking } } : {}
55491
+ }
55492
+ );
55493
+ const raw = res.choices[0]?.message?.content || "";
55494
+ return raw.replace(/<think>[\s\S]*?<\/think>/g, "").replace(/```json|```/g, "").trim();
55495
+ }
55446
55496
  }
55447
55497
  }
55498
+ async function callRole(role, opts) {
55499
+ const spec = ROLE_REGISTRY[role];
55500
+ return callSpec(spec, opts);
55501
+ }
55448
55502
 
55449
55503
  // src/embeddings.ts
55450
55504
  var client = null;
@@ -55479,6 +55533,8 @@ async function generateEmbedding(text) {
55479
55533
 
55480
55534
  // src/cold_path/tagger.ts
55481
55535
  init_db();
55536
+ var GROK_FALLBACK_SPEC = { provider: "xai", model_name: "grok-4-1-fast-non-reasoning" };
55537
+ var localFallbackEnabled = process.env.LOCAL_GROK_FALLBACK !== "false";
55482
55538
  var _candidateCache = null;
55483
55539
  var CANDIDATE_CACHE_TTL_MS = 5 * 60 * 1e3;
55484
55540
  async function listProjectTagCandidates(limit2 = 20) {
@@ -55505,6 +55561,15 @@ async function listProjectTagCandidates(limit2 = 20) {
55505
55561
  function invalidateCandidateCache() {
55506
55562
  _candidateCache = null;
55507
55563
  }
55564
+ var TAGGER_SCHEMA = {
55565
+ type: "object",
55566
+ properties: {
55567
+ p_tag: { type: ["string", "null"] },
55568
+ d_tag: { type: "array", items: { type: "string" }, maxItems: 5 }
55569
+ },
55570
+ required: ["p_tag", "d_tag"],
55571
+ additionalProperties: false
55572
+ };
55508
55573
  var SYSTEM_PROMPT = `Tagger for one user's personal long-term memory across AI agents.
55509
55574
 
55510
55575
  OUTPUT (strict JSON):
@@ -55557,11 +55622,35 @@ async function getOrCreateProjectTag(name) {
55557
55622
  async function tagMessage(input) {
55558
55623
  const candidates = await listProjectTagCandidates();
55559
55624
  const userPrompt = buildUserPrompt(input, candidates);
55560
- const raw = await callRole("tagger", {
55561
- system: SYSTEM_PROMPT,
55562
- user: userPrompt,
55563
- responseFormat: "json"
55564
- });
55625
+ const isLocal = ROLE_REGISTRY.tagger.provider === "local";
55626
+ let raw;
55627
+ if (isLocal) {
55628
+ try {
55629
+ raw = await callRole("tagger", {
55630
+ system: SYSTEM_PROMPT,
55631
+ user: userPrompt,
55632
+ responseFormat: "json",
55633
+ jsonSchema: TAGGER_SCHEMA,
55634
+ enableThinking: false
55635
+ // thinking off — 태거는 단순 매핑 작업. thinking 켜면 reasoning이 모든 토큰 소비해 content 비어버림.
55636
+ // jsonSchema → llama.cpp grammar로 <think> bleed 차단 (Qwen3 bug #20345).
55637
+ });
55638
+ } catch (err2) {
55639
+ if (!localFallbackEnabled) throw err2;
55640
+ console.warn(`\u26A0\uFE0F [Tagger] Local model \uC2E4\uD328, grok fallback: ${err2.message?.slice(0, 80)}`);
55641
+ raw = await callSpec(GROK_FALLBACK_SPEC, {
55642
+ system: SYSTEM_PROMPT,
55643
+ user: userPrompt,
55644
+ responseFormat: "json"
55645
+ });
55646
+ }
55647
+ } else {
55648
+ raw = await callRole("tagger", {
55649
+ system: SYSTEM_PROMPT,
55650
+ user: userPrompt,
55651
+ responseFormat: "json"
55652
+ });
55653
+ }
55565
55654
  let parsed;
55566
55655
  try {
55567
55656
  parsed = JSON.parse(raw);
@@ -55849,6 +55938,7 @@ function vectorToHalfvecSql(embedding) {
55849
55938
  }
55850
55939
 
55851
55940
  // src/tools/search_memory.ts
55941
+ import os3 from "node:os";
55852
55942
  var DEFAULT_LIMIT = 10;
55853
55943
  var DEFAULT_FALLBACK_THRESHOLD = 0.3;
55854
55944
  function parseDateRange(s) {
@@ -55917,7 +56007,10 @@ date_range \uC778\uC2DD \uD615\uC2DD:
55917
56007
  date_range: external_exports.string().optional().describe("\uAE30\uAC04 \uD55C\uC815 (today / last_week / last_month / 7_days_ago / YYYY-MM-DD)"),
55918
56008
  role: external_exports.enum(["user", "assistant"]).optional().describe("\uBC1C\uD654\uC790 \uD55C\uC815 (\uC0DD\uB7B5 \uC2DC \uB458 \uB2E4)"),
55919
56009
  agent_platform: external_exports.string().optional().describe(
55920
- "agent platform \uD55C\uC815 (\uC608: 'claude-code', 'gemini-cli-mcp-client'). \uC0DD\uB7B5 \uC2DC cross-platform."
56010
+ "agent platform \uD55C\uC815 (\uC608: 'claude-code', 'gemini-cli-mcp-client'). \uC0DD\uB7B5 \uB610\uB294 '*' \uC774\uBA74 cross-platform(\uC804 \uD50C\uB7AB\uD3FC)."
56011
+ ),
56012
+ device_scope: external_exports.enum(["local", "global"]).optional().describe(
56013
+ "\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
56014
  ),
55922
56015
  limit: external_exports.number().int().min(1).max(50).optional().describe(`\uCD5C\uB300 \uACB0\uACFC \uC218 (default ${DEFAULT_LIMIT})`),
55923
56016
  include_archived: external_exports.boolean().optional().describe("archived \uBA54\uBAA8\uB9AC\uB3C4 \uD3EC\uD568 (default false)")
@@ -55945,6 +56038,7 @@ date_range \uC778\uC2DD \uD615\uC2DD:
55945
56038
  }
55946
56039
  const sinceDate = parseDateRange(args.date_range);
55947
56040
  const includeArchived = args.include_archived === true;
56041
+ const localDevice = os3.hostname();
55948
56042
  const filters = [`m.user_id = $1`];
55949
56043
  const params = [userId];
55950
56044
  let p = 2;
@@ -55952,7 +56046,7 @@ date_range \uC778\uC2DD \uD615\uC2DD:
55952
56046
  filters.push(`m.is_active = TRUE`);
55953
56047
  }
55954
56048
  if (p_tag_id !== null) {
55955
- filters.push(`m.p_tag_id = $${p++}`);
56049
+ filters.push(`canonical_project_tag_id(m.p_tag_id) = $${p++}`);
55956
56050
  params.push(p_tag_id);
55957
56051
  }
55958
56052
  if (sinceDate) {
@@ -55963,10 +56057,14 @@ date_range \uC778\uC2DD \uD615\uC2DD:
55963
56057
  filters.push(`m.role = $${p++}`);
55964
56058
  params.push(args.role);
55965
56059
  }
55966
- if (args.agent_platform) {
56060
+ if (args.agent_platform && args.agent_platform !== "*") {
55967
56061
  filters.push(`m.agent_platform = $${p++}`);
55968
56062
  params.push(args.agent_platform);
55969
56063
  }
56064
+ if (args.device_scope === "local") {
56065
+ filters.push(`(m.device_name = $${p++} OR m.is_pinned = TRUE)`);
56066
+ params.push(localDevice);
56067
+ }
55970
56068
  const whereSql = filters.join(" AND ");
55971
56069
  let used = "recency";
55972
56070
  let topSimilarity = void 0;
@@ -55980,11 +56078,11 @@ date_range \uC778\uC2DD \uD615\uC2DD:
55980
56078
  const limitParam = p++;
55981
56079
  params.push(limit2);
55982
56080
  const r = await db.query(
55983
- `SELECT m.id, m.role, m.message, m.agent_platform, m.agent_model,
56081
+ `SELECT m.id, m.role, m.message, m.agent_platform, m.agent_model, m.device_name,
55984
56082
  pt.name AS p_tag_name, m.d_tag, m.is_pinned, m.created_at,
55985
56083
  1 - (m.embedding <=> $${vecParam}::halfvec) AS similarity
55986
56084
  FROM memory m
55987
- LEFT JOIN project_tags pt ON pt.id = m.p_tag_id
56085
+ LEFT JOIN project_tags pt ON pt.id = canonical_project_tag_id(m.p_tag_id)
55988
56086
  WHERE ${whereSql}
55989
56087
  AND m.embedding IS NOT NULL
55990
56088
  ORDER BY m.embedding <=> $${vecParam}::halfvec
@@ -55997,6 +56095,7 @@ date_range \uC778\uC2DD \uD615\uC2DD:
55997
56095
  message: row.message,
55998
56096
  agent_platform: row.agent_platform,
55999
56097
  agent_model: row.agent_model,
56098
+ device_name: row.device_name ?? null,
56000
56099
  p_tag_name: row.p_tag_name,
56001
56100
  d_tag: row.d_tag ?? [],
56002
56101
  is_pinned: row.is_pinned,
@@ -56016,7 +56115,7 @@ date_range \uC778\uC2DD \uD615\uC2DD:
56016
56115
  const ilikeFilters = [`m.user_id = $1`];
56017
56116
  if (!includeArchived) ilikeFilters.push(`m.is_active = TRUE`);
56018
56117
  if (p_tag_id !== null) {
56019
- ilikeFilters.push(`m.p_tag_id = $${q++}`);
56118
+ ilikeFilters.push(`canonical_project_tag_id(m.p_tag_id) = $${q++}`);
56020
56119
  ilikeParams.push(p_tag_id);
56021
56120
  }
56022
56121
  if (sinceDate) {
@@ -56027,18 +56126,22 @@ date_range \uC778\uC2DD \uD615\uC2DD:
56027
56126
  ilikeFilters.push(`m.role = $${q++}`);
56028
56127
  ilikeParams.push(args.role);
56029
56128
  }
56030
- if (args.agent_platform) {
56129
+ if (args.agent_platform && args.agent_platform !== "*") {
56031
56130
  ilikeFilters.push(`m.agent_platform = $${q++}`);
56032
56131
  ilikeParams.push(args.agent_platform);
56033
56132
  }
56133
+ if (args.device_scope === "local") {
56134
+ ilikeFilters.push(`(m.device_name = $${q++} OR m.is_pinned = TRUE)`);
56135
+ ilikeParams.push(localDevice);
56136
+ }
56034
56137
  ilikeFilters.push(`m.message ILIKE $${q++}`);
56035
56138
  ilikeParams.push(`%${args.query}%`);
56036
56139
  ilikeParams.push(limit2);
56037
56140
  const r = await db.query(
56038
- `SELECT m.id, m.role, m.message, m.agent_platform, m.agent_model,
56141
+ `SELECT m.id, m.role, m.message, m.agent_platform, m.agent_model, m.device_name,
56039
56142
  pt.name AS p_tag_name, m.d_tag, m.is_pinned, m.created_at
56040
56143
  FROM memory m
56041
- LEFT JOIN project_tags pt ON pt.id = m.p_tag_id
56144
+ LEFT JOIN project_tags pt ON pt.id = canonical_project_tag_id(m.p_tag_id)
56042
56145
  WHERE ${ilikeFilters.join(" AND ")}
56043
56146
  ORDER BY m.created_at DESC
56044
56147
  LIMIT $${q}`,
@@ -56051,6 +56154,7 @@ date_range \uC778\uC2DD \uD615\uC2DD:
56051
56154
  message: row.message,
56052
56155
  agent_platform: row.agent_platform,
56053
56156
  agent_model: row.agent_model,
56157
+ device_name: row.device_name ?? null,
56054
56158
  p_tag_name: row.p_tag_name,
56055
56159
  d_tag: row.d_tag ?? [],
56056
56160
  is_pinned: row.is_pinned,
@@ -56063,10 +56167,10 @@ date_range \uC778\uC2DD \uD615\uC2DD:
56063
56167
  } else {
56064
56168
  params.push(limit2);
56065
56169
  const r = await db.query(
56066
- `SELECT m.id, m.role, m.message, m.agent_platform, m.agent_model,
56170
+ `SELECT m.id, m.role, m.message, m.agent_platform, m.agent_model, m.device_name,
56067
56171
  pt.name AS p_tag_name, m.d_tag, m.is_pinned, m.created_at
56068
56172
  FROM memory m
56069
- LEFT JOIN project_tags pt ON pt.id = m.p_tag_id
56173
+ LEFT JOIN project_tags pt ON pt.id = canonical_project_tag_id(m.p_tag_id)
56070
56174
  WHERE ${whereSql}
56071
56175
  ORDER BY m.created_at DESC
56072
56176
  LIMIT $${p}`,
@@ -56078,6 +56182,7 @@ date_range \uC778\uC2DD \uD615\uC2DD:
56078
56182
  message: row.message,
56079
56183
  agent_platform: row.agent_platform,
56080
56184
  agent_model: row.agent_model,
56185
+ device_name: row.device_name ?? null,
56081
56186
  p_tag_name: row.p_tag_name,
56082
56187
  d_tag: row.d_tag ?? [],
56083
56188
  is_pinned: row.is_pinned,
@@ -56101,32 +56206,1371 @@ date_range \uC778\uC2DD \uD615\uC2DD:
56101
56206
  );
56102
56207
  }
56103
56208
 
56209
+ // src/tools/manage_project_tags.ts
56210
+ init_db();
56211
+
56212
+ // src/cold_path/project_alias_promoter.ts
56213
+ init_db();
56214
+ var QUALIFYING_RELATIONS = /* @__PURE__ */ new Set([
56215
+ "rename",
56216
+ "alias",
56217
+ "same_project"
56218
+ ]);
56219
+ var PROJECT_ALIAS_PROMOTER_DEFAULT_INTERVAL_HOURS = 24;
56220
+ var ALL_RELATIONS = /* @__PURE__ */ new Set([
56221
+ "rename",
56222
+ "alias",
56223
+ "same_project",
56224
+ "different",
56225
+ "misfile_suspected",
56226
+ "insufficient"
56227
+ ]);
56228
+ var EXPLICIT_PATTERNS = [
56229
+ "%\uC774\uB984 \uBC14\uAFC8%",
56230
+ "%\uC774\uB984\uBC14\uAFC8%",
56231
+ "%\uC774\uB984\uC744 \uBC14\uAFC8%",
56232
+ "%\uAC1C\uBA85%",
56233
+ "%\uC774\uC81C%\uB85C%",
56234
+ "%\uAC19\uC740 \uD504\uB85C\uC81D\uD2B8%",
56235
+ "%\uB3D9\uC77C%",
56236
+ "%\uD569\uCCD0%",
56237
+ "%\uD569\uCE58%",
56238
+ "%\uBCC4\uCE6D%",
56239
+ "%\uB3D9\uC758\uC5B4%",
56240
+ "%rename%",
56241
+ "%renamed%",
56242
+ "%now called%",
56243
+ "%same as%",
56244
+ "%alias%"
56245
+ ];
56246
+ var projectAliasPromoterRunning = false;
56247
+ var SOURCE_WEIGHT = {
56248
+ explicit_user_statement: 100,
56249
+ vector_neighbor: 30,
56250
+ d_tag_overlap: 18,
56251
+ name_similarity: 16,
56252
+ low_frequency_tag: 8
56253
+ };
56254
+ var JUDGE_SCHEMA = {
56255
+ type: "object",
56256
+ properties: {
56257
+ relation: { type: "string", enum: ["rename", "alias", "same_project", "different", "misfile_suspected", "insufficient"] },
56258
+ same_project: { type: "boolean" },
56259
+ source_should_alias_target: { type: "boolean" },
56260
+ confidence: { type: "number" },
56261
+ evidence_memory_ids: { type: "array", items: { type: "integer" } },
56262
+ conflict_memory_ids: { type: "array", items: { type: "integer" } },
56263
+ rationale: { type: "string" }
56264
+ },
56265
+ required: ["relation", "same_project", "source_should_alias_target", "confidence", "evidence_memory_ids", "conflict_memory_ids", "rationale"],
56266
+ additionalProperties: false
56267
+ };
56268
+ var JUDGE_SYSTEM_PROMPT = `You are a project-tag alias judge for one user's personal memory system.
56269
+
56270
+ Decide whether two CANONICAL project tags refer to the same project identity.
56271
+
56272
+ OUTPUT strict JSON only:
56273
+ {
56274
+ "relation": "rename" | "alias" | "same_project" | "different" | "misfile_suspected" | "insufficient",
56275
+ "same_project": true | false,
56276
+ "source_should_alias_target": true | false,
56277
+ "confidence": 0.0,
56278
+ "evidence_memory_ids": [],
56279
+ "conflict_memory_ids": [],
56280
+ "rationale": ""
56281
+ }
56282
+
56283
+ Rules:
56284
+ - "Related" is not the same as "same project". Shared topic, tech stack, or client is not enough.
56285
+ - Vector similarity is recall evidence only. Never conclude same_project from vector similarity alone.
56286
+ - A colloquial nickname, renamed label, Korean/English spelling variant, or old/new project name can be alias/rename.
56287
+ - If only a few memories are filed under the wrong tag, relation must be "misfile_suspected"; do not recommend aliasing the whole tag.
56288
+ - 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.
56289
+ - If direction is unclear, use relation="insufficient" or lower confidence.
56290
+ - evidence_memory_ids and conflict_memory_ids must only use supplied memory ids.`;
56291
+ function envInt(name, fallback) {
56292
+ const raw = process.env[name];
56293
+ if (!raw) return fallback;
56294
+ const n = Number(raw);
56295
+ return Number.isFinite(n) && n >= 1 ? Math.floor(n) : fallback;
56296
+ }
56297
+ function envFloat(name, fallback) {
56298
+ const raw = process.env[name];
56299
+ if (!raw) return fallback;
56300
+ const n = Number(raw);
56301
+ return Number.isFinite(n) ? n : fallback;
56302
+ }
56303
+ function clamp01(value) {
56304
+ if (!Number.isFinite(value)) return 0;
56305
+ return Math.max(0, Math.min(1, value));
56306
+ }
56307
+ function toDate(value) {
56308
+ return value instanceof Date ? value : new Date(String(value));
56309
+ }
56310
+ function toNullableDate(value) {
56311
+ if (value === null || value === void 0) return null;
56312
+ return toDate(value);
56313
+ }
56314
+ function toStringArray(value) {
56315
+ if (!Array.isArray(value)) return [];
56316
+ return value.filter((v) => typeof v === "string" || typeof v === "number").map((v) => String(v).trim()).filter(Boolean);
56317
+ }
56318
+ function toNumberArray(value) {
56319
+ if (!Array.isArray(value)) return [];
56320
+ return value.map((v) => Number(v)).filter((v) => Number.isInteger(v) && v > 0);
56321
+ }
56322
+ function truncate(text, max) {
56323
+ return text.length <= max ? text : `${text.slice(0, max - 1)}\u2026`;
56324
+ }
56325
+ function normalizeText(text) {
56326
+ return text.toLowerCase().normalize("NFKC").replace(/[^\p{L}\p{N}]+/gu, " ").trim().replace(/\s+/g, " ");
56327
+ }
56328
+ function compactText(text) {
56329
+ return text.toLowerCase().normalize("NFKC").replace(/[^\p{L}\p{N}]+/gu, "");
56330
+ }
56331
+ function tokenSet(name) {
56332
+ return new Set(normalizeText(name).split(" ").filter((t) => t.length > 0));
56333
+ }
56334
+ function jaccard(a, b) {
56335
+ const left = new Set(a);
56336
+ const right = new Set(b);
56337
+ if (left.size === 0 || right.size === 0) return 0;
56338
+ let intersection2 = 0;
56339
+ for (const item of left) {
56340
+ if (right.has(item)) intersection2++;
56341
+ }
56342
+ return intersection2 / (left.size + right.size - intersection2);
56343
+ }
56344
+ function bigramDice(a, b) {
56345
+ const left = compactText(a);
56346
+ const right = compactText(b);
56347
+ if (left.length < 3 || right.length < 3) return 0;
56348
+ if (left === right) return 1;
56349
+ if (left.includes(right) || right.includes(left)) return 0.78;
56350
+ const grams = (s) => {
56351
+ const out = /* @__PURE__ */ new Map();
56352
+ for (let i = 0; i < s.length - 1; i++) {
56353
+ const g = s.slice(i, i + 2);
56354
+ out.set(g, (out.get(g) ?? 0) + 1);
56355
+ }
56356
+ return out;
56357
+ };
56358
+ const g1 = grams(left);
56359
+ const g2 = grams(right);
56360
+ let overlap = 0;
56361
+ for (const [g, count] of g1) {
56362
+ overlap += Math.min(count, g2.get(g) ?? 0);
56363
+ }
56364
+ return 2 * overlap / Math.max(1, left.length + right.length - 2);
56365
+ }
56366
+ function nameSimilarity(a, b) {
56367
+ const tokenScore = jaccard(tokenSet(a), tokenSet(b));
56368
+ const dice = bigramDice(a, b);
56369
+ return Math.max(tokenScore, dice);
56370
+ }
56371
+ function pairKey(a, b) {
56372
+ return a < b ? `${a}:${b}` : `${b}:${a}`;
56373
+ }
56374
+ function uniqueSortedNumbers(values) {
56375
+ return Array.from(new Set(values)).sort((a, b) => a - b);
56376
+ }
56377
+ function tagPayload(tag) {
56378
+ return {
56379
+ id: tag.id,
56380
+ name: tag.name,
56381
+ description: tag.description,
56382
+ memory_count: tag.memoryCount,
56383
+ first_seen: tag.firstSeen?.toISOString() ?? null,
56384
+ last_seen: tag.lastSeen?.toISOString() ?? null,
56385
+ d_tags: tag.dTags.slice(0, 20)
56386
+ };
56387
+ }
56388
+ function isNewOrLowFrequency(tag) {
56389
+ const lowMax = envInt("PROJECT_ALIAS_LOW_COUNT_MAX", 3);
56390
+ const newDays = envInt("PROJECT_ALIAS_NEW_TAG_DAYS", 14);
56391
+ const ageMs = Date.now() - tag.createdAt.getTime();
56392
+ return tag.memoryCount <= lowMax || ageMs <= newDays * 24 * 60 * 60 * 1e3;
56393
+ }
56394
+ function chooseDirection(a, b) {
56395
+ if (a.memoryCount !== b.memoryCount) {
56396
+ return a.memoryCount < b.memoryCount ? { sourceId: a.id, targetId: b.id } : { sourceId: b.id, targetId: a.id };
56397
+ }
56398
+ const aFirst = a.firstSeen?.getTime() ?? a.createdAt.getTime();
56399
+ const bFirst = b.firstSeen?.getTime() ?? b.createdAt.getTime();
56400
+ if (aFirst !== bFirst) {
56401
+ return aFirst > bFirst ? { sourceId: a.id, targetId: b.id } : { sourceId: b.id, targetId: a.id };
56402
+ }
56403
+ return a.id > b.id ? { sourceId: a.id, targetId: b.id } : { sourceId: b.id, targetId: a.id };
56404
+ }
56405
+ function getOrAddPair(pairs, tagsById, aId, bId) {
56406
+ if (aId === bId) return null;
56407
+ const a = tagsById.get(aId);
56408
+ const b = tagsById.get(bId);
56409
+ if (!a || !b) return null;
56410
+ const key = pairKey(aId, bId);
56411
+ const existing = pairs.get(key);
56412
+ if (existing) return existing;
56413
+ const direction = chooseDirection(a, b);
56414
+ const pair = {
56415
+ sourceId: direction.sourceId,
56416
+ targetId: direction.targetId,
56417
+ sources: /* @__PURE__ */ new Set(),
56418
+ explicitStatements: [],
56419
+ score: 0,
56420
+ signals: {}
56421
+ };
56422
+ pairs.set(key, pair);
56423
+ return pair;
56424
+ }
56425
+ function addSource(pair, source) {
56426
+ if (!pair.sources.has(source)) {
56427
+ pair.sources.add(source);
56428
+ pair.score += SOURCE_WEIGHT[source];
56429
+ }
56430
+ }
56431
+ function addExplicitStatement(pair, memory) {
56432
+ addSource(pair, "explicit_user_statement");
56433
+ if (!pair.explicitStatements.some((m) => m.id === memory.id)) {
56434
+ pair.explicitStatements.push(memory);
56435
+ }
56436
+ pair.signals.explicit_statement_ids = pair.explicitStatements.map((m) => m.id);
56437
+ pair.signals.explicit_statement_previews = pair.explicitStatements.map((m) => ({
56438
+ id: m.id,
56439
+ preview: truncate(m.message, 240)
56440
+ }));
56441
+ }
56442
+ function addLowFrequencySignal(pair, tagId) {
56443
+ addSource(pair, "low_frequency_tag");
56444
+ const current = toNumberArray(pair.signals.low_frequency_anchor_ids);
56445
+ pair.signals.low_frequency_anchor_ids = uniqueSortedNumbers([...current, tagId]);
56446
+ }
56447
+ function messageMentionsTag(message, tag) {
56448
+ const lowerMessage = message.toLowerCase();
56449
+ const lowerName = tag.name.toLowerCase();
56450
+ if (lowerName.length >= 3 && lowerMessage.includes(lowerName)) return true;
56451
+ const normalizedName = normalizeText(tag.name);
56452
+ const normalizedMessage = normalizeText(message);
56453
+ if (normalizedName.length >= 3 && normalizedMessage.includes(normalizedName)) return true;
56454
+ const compactName = compactText(tag.name);
56455
+ const compactMessage = compactText(message);
56456
+ return compactName.length >= 3 && compactMessage.includes(compactName);
56457
+ }
56458
+ async function listCanonicalTags(userId) {
56459
+ const maxTags = envInt("PROJECT_ALIAS_MAX_TAGS", 120);
56460
+ const result = await db.query(
56461
+ `SELECT pt.id,
56462
+ pt.name,
56463
+ pt.description,
56464
+ pt.created_at,
56465
+ pt.updated_at,
56466
+ COUNT(m.id)::int AS memory_count,
56467
+ MIN(m.created_at) AS first_seen,
56468
+ MAX(m.created_at) AS last_seen
56469
+ FROM project_tags pt
56470
+ LEFT JOIN memory m
56471
+ ON m.user_id = $1
56472
+ AND m.is_active = TRUE
56473
+ AND m.p_tag_id IS NOT NULL
56474
+ AND canonical_project_tag_id(m.p_tag_id) = pt.id
56475
+ WHERE pt.alias_of IS NULL
56476
+ GROUP BY pt.id
56477
+ ORDER BY MAX(m.created_at) DESC NULLS LAST, pt.created_at DESC
56478
+ LIMIT $2`,
56479
+ [userId, maxTags]
56480
+ );
56481
+ const tags = result.rows.map((row) => ({
56482
+ id: Number(row.id),
56483
+ name: String(row.name),
56484
+ description: row.description === null ? null : String(row.description),
56485
+ createdAt: toDate(row.created_at),
56486
+ updatedAt: toDate(row.updated_at),
56487
+ memoryCount: Number(row.memory_count ?? 0),
56488
+ firstSeen: toNullableDate(row.first_seen),
56489
+ lastSeen: toNullableDate(row.last_seen),
56490
+ dTags: []
56491
+ }));
56492
+ if (tags.length === 0) return tags;
56493
+ const tagIds = tags.map((t) => t.id);
56494
+ const dtagResult = await db.query(
56495
+ `SELECT tag_id, ARRAY_AGG(DISTINCT tag ORDER BY tag) AS d_tags
56496
+ FROM (
56497
+ SELECT canonical_project_tag_id(m.p_tag_id) AS tag_id,
56498
+ dt.tag AS tag
56499
+ FROM memory m
56500
+ CROSS JOIN LATERAL unnest(m.d_tag) AS dt(tag)
56501
+ WHERE m.user_id = $1
56502
+ AND m.is_active = TRUE
56503
+ AND m.p_tag_id IS NOT NULL
56504
+ AND canonical_project_tag_id(m.p_tag_id) = ANY($2::bigint[])
56505
+ AND dt.tag <> ''
56506
+ ) s
56507
+ GROUP BY tag_id`,
56508
+ [userId, tagIds]
56509
+ );
56510
+ const dTagsById = /* @__PURE__ */ new Map();
56511
+ for (const row of dtagResult.rows) {
56512
+ dTagsById.set(Number(row.tag_id), toStringArray(row.d_tags));
56513
+ }
56514
+ for (const tag of tags) {
56515
+ tag.dTags = dTagsById.get(tag.id) ?? [];
56516
+ }
56517
+ return tags;
56518
+ }
56519
+ async function listRepresentativeMemories(userId, tagIds, perTag = 3) {
56520
+ const out = /* @__PURE__ */ new Map();
56521
+ if (tagIds.length === 0) return out;
56522
+ const result = await db.query(
56523
+ `WITH ranked AS (
56524
+ SELECT canonical_project_tag_id(m.p_tag_id) AS tag_id,
56525
+ m.id,
56526
+ m.role,
56527
+ m.message,
56528
+ m.d_tag,
56529
+ m.created_at,
56530
+ ROW_NUMBER() OVER (
56531
+ PARTITION BY canonical_project_tag_id(m.p_tag_id)
56532
+ ORDER BY m.is_pinned DESC, m.created_at DESC
56533
+ ) AS rn
56534
+ FROM memory m
56535
+ WHERE m.user_id = $1
56536
+ AND m.is_active = TRUE
56537
+ AND m.p_tag_id IS NOT NULL
56538
+ AND canonical_project_tag_id(m.p_tag_id) = ANY($2::bigint[])
56539
+ )
56540
+ SELECT tag_id, id, role, message, d_tag, created_at
56541
+ FROM ranked
56542
+ WHERE rn <= $3
56543
+ ORDER BY tag_id, rn`,
56544
+ [userId, tagIds, perTag]
56545
+ );
56546
+ for (const row of result.rows) {
56547
+ const tagId = Number(row.tag_id);
56548
+ const list = out.get(tagId) ?? [];
56549
+ list.push({
56550
+ id: Number(row.id),
56551
+ role: row.role === "assistant" ? "assistant" : "user",
56552
+ message: String(row.message),
56553
+ dTags: toStringArray(row.d_tag),
56554
+ createdAt: toDate(row.created_at)
56555
+ });
56556
+ out.set(tagId, list);
56557
+ }
56558
+ return out;
56559
+ }
56560
+ async function addExplicitStatementCandidates(userId, tags, tagsById, pairs) {
56561
+ const scanLimit = envInt("PROJECT_ALIAS_EXPLICIT_SCAN_LIMIT", 200);
56562
+ const clauses = EXPLICIT_PATTERNS.map((_, i) => `message ILIKE $${i + 2}`).join(" OR ");
56563
+ const limitParam = EXPLICIT_PATTERNS.length + 2;
56564
+ const result = await db.query(
56565
+ `SELECT id,
56566
+ message,
56567
+ d_tag,
56568
+ created_at,
56569
+ canonical_project_tag_id(p_tag_id) AS memory_tag_id
56570
+ FROM memory
56571
+ WHERE user_id = $1
56572
+ AND role = 'user'
56573
+ AND is_active = TRUE
56574
+ AND (${clauses})
56575
+ ORDER BY created_at DESC
56576
+ LIMIT $${limitParam}`,
56577
+ [userId, ...EXPLICIT_PATTERNS, scanLimit]
56578
+ );
56579
+ for (const row of result.rows) {
56580
+ const memory = {
56581
+ id: Number(row.id),
56582
+ role: "user",
56583
+ message: String(row.message),
56584
+ dTags: toStringArray(row.d_tag),
56585
+ createdAt: toDate(row.created_at)
56586
+ };
56587
+ const mentioned = /* @__PURE__ */ new Set();
56588
+ for (const tag of tags) {
56589
+ if (messageMentionsTag(memory.message, tag)) {
56590
+ mentioned.add(tag.id);
56591
+ }
56592
+ }
56593
+ const memoryTagId = row.memory_tag_id === null ? null : Number(row.memory_tag_id);
56594
+ if (memoryTagId && tagsById.has(memoryTagId)) {
56595
+ mentioned.add(memoryTagId);
56596
+ }
56597
+ const ids = Array.from(mentioned);
56598
+ if (ids.length < 2) continue;
56599
+ for (let i = 0; i < ids.length; i++) {
56600
+ for (let j = i + 1; j < ids.length; j++) {
56601
+ const pair = getOrAddPair(pairs, tagsById, ids[i], ids[j]);
56602
+ if (pair) addExplicitStatement(pair, memory);
56603
+ }
56604
+ }
56605
+ }
56606
+ }
56607
+ function addNameAndDtagCandidates(tags, tagsById, pairs) {
56608
+ for (let i = 0; i < tags.length; i++) {
56609
+ for (let j = i + 1; j < tags.length; j++) {
56610
+ const a = tags[i];
56611
+ const b = tags[j];
56612
+ const lowAnchor = isNewOrLowFrequency(a) || isNewOrLowFrequency(b);
56613
+ const nScore = nameSimilarity(a.name, b.name);
56614
+ const dScore = jaccard(a.dTags, b.dTags);
56615
+ const nameThreshold = lowAnchor ? 0.45 : 0.55;
56616
+ const dtagThreshold = lowAnchor ? 0.18 : 0.25;
56617
+ if (nScore >= nameThreshold) {
56618
+ const pair = getOrAddPair(pairs, tagsById, a.id, b.id);
56619
+ if (pair) {
56620
+ addSource(pair, "name_similarity");
56621
+ pair.signals.name_similarity = Math.max(
56622
+ Number(pair.signals.name_similarity ?? 0),
56623
+ Number(nScore.toFixed(4))
56624
+ );
56625
+ if (isNewOrLowFrequency(a)) addLowFrequencySignal(pair, a.id);
56626
+ if (isNewOrLowFrequency(b)) addLowFrequencySignal(pair, b.id);
56627
+ }
56628
+ }
56629
+ if (dScore >= dtagThreshold) {
56630
+ const pair = getOrAddPair(pairs, tagsById, a.id, b.id);
56631
+ if (pair) {
56632
+ addSource(pair, "d_tag_overlap");
56633
+ pair.signals.d_tag_jaccard = Math.max(
56634
+ Number(pair.signals.d_tag_jaccard ?? 0),
56635
+ Number(dScore.toFixed(4))
56636
+ );
56637
+ pair.signals.shared_d_tags = a.dTags.filter((t) => b.dTags.includes(t)).slice(0, 20);
56638
+ if (isNewOrLowFrequency(a)) addLowFrequencySignal(pair, a.id);
56639
+ if (isNewOrLowFrequency(b)) addLowFrequencySignal(pair, b.id);
56640
+ }
56641
+ }
56642
+ }
56643
+ }
56644
+ }
56645
+ async function addVectorCandidates(userId, tags, tagsById, pairs) {
56646
+ const anchorLimit = envInt("PROJECT_ALIAS_VECTOR_ANCHOR_LIMIT", 12);
56647
+ const representativeLimit = envInt("PROJECT_ALIAS_VECTOR_REPRESENTATIVE_LIMIT", 5);
56648
+ const perMemoryLimit = envInt("PROJECT_ALIAS_VECTOR_PER_MEMORY_LIMIT", 8);
56649
+ const perTagLimit = envInt("PROJECT_ALIAS_VECTOR_TARGET_LIMIT", 3);
56650
+ const maxDistance = envFloat("PROJECT_ALIAS_VECTOR_MAX_DISTANCE", 0.28);
56651
+ const anchors = tags.slice().sort((a, b) => {
56652
+ const aLow = isNewOrLowFrequency(a) ? 1 : 0;
56653
+ const bLow = isNewOrLowFrequency(b) ? 1 : 0;
56654
+ if (aLow !== bLow) return bLow - aLow;
56655
+ return (b.lastSeen?.getTime() ?? b.createdAt.getTime()) - (a.lastSeen?.getTime() ?? a.createdAt.getTime());
56656
+ }).slice(0, anchorLimit);
56657
+ for (const anchor of anchors) {
56658
+ const result = await db.query(
56659
+ `WITH src AS (
56660
+ SELECT id, embedding
56661
+ FROM memory
56662
+ WHERE user_id = $1
56663
+ AND is_active = TRUE
56664
+ AND p_tag_id IS NOT NULL
56665
+ AND embedding IS NOT NULL
56666
+ AND canonical_project_tag_id(p_tag_id) = $2
56667
+ ORDER BY is_pinned DESC, created_at DESC
56668
+ LIMIT $3
56669
+ ),
56670
+ neighbors AS (
56671
+ SELECT canonical_project_tag_id(m.p_tag_id) AS target_tag_id,
56672
+ m.id AS memory_id,
56673
+ src.id AS source_memory_id,
56674
+ (m.embedding <=> src.embedding) AS distance
56675
+ FROM src
56676
+ JOIN LATERAL (
56677
+ SELECT id, p_tag_id, embedding
56678
+ FROM memory
56679
+ WHERE user_id = $1
56680
+ AND is_active = TRUE
56681
+ AND p_tag_id IS NOT NULL
56682
+ AND embedding IS NOT NULL
56683
+ AND canonical_project_tag_id(p_tag_id) <> $2
56684
+ ORDER BY embedding <=> src.embedding
56685
+ LIMIT $4
56686
+ ) m ON TRUE
56687
+ )
56688
+ SELECT target_tag_id,
56689
+ MIN(distance)::float8 AS min_distance,
56690
+ (ARRAY_AGG(memory_id ORDER BY distance))[1:5] AS evidence_memory_ids,
56691
+ (ARRAY_AGG(source_memory_id ORDER BY distance))[1:5] AS source_memory_ids
56692
+ FROM neighbors
56693
+ WHERE target_tag_id IS NOT NULL
56694
+ GROUP BY target_tag_id
56695
+ HAVING MIN(distance) <= $5
56696
+ ORDER BY MIN(distance)
56697
+ LIMIT $6`,
56698
+ [userId, anchor.id, representativeLimit, perMemoryLimit, maxDistance, perTagLimit]
56699
+ );
56700
+ for (const row of result.rows) {
56701
+ const targetId = Number(row.target_tag_id);
56702
+ if (!tagsById.has(targetId)) continue;
56703
+ const pair = getOrAddPair(pairs, tagsById, anchor.id, targetId);
56704
+ if (!pair) continue;
56705
+ addSource(pair, "vector_neighbor");
56706
+ if (isNewOrLowFrequency(anchor)) addLowFrequencySignal(pair, anchor.id);
56707
+ const vectorSignals = Array.isArray(pair.signals.vector_neighbors) ? pair.signals.vector_neighbors : [];
56708
+ vectorSignals.push({
56709
+ source_tag_id: anchor.id,
56710
+ target_tag_id: targetId,
56711
+ min_distance: Number(Number(row.min_distance).toFixed(6)),
56712
+ evidence_memory_ids: toNumberArray(row.evidence_memory_ids),
56713
+ source_memory_ids: toNumberArray(row.source_memory_ids)
56714
+ });
56715
+ pair.signals.vector_neighbors = vectorSignals;
56716
+ const previous = Number(pair.signals.vector_min_distance ?? Number.POSITIVE_INFINITY);
56717
+ pair.signals.vector_min_distance = Math.min(previous, Number(row.min_distance));
56718
+ }
56719
+ }
56720
+ }
56721
+ async function buildCandidatePairs(userId, tags, limit2) {
56722
+ const tagsById = new Map(tags.map((tag) => [tag.id, tag]));
56723
+ const pairs = /* @__PURE__ */ new Map();
56724
+ await addExplicitStatementCandidates(userId, tags, tagsById, pairs);
56725
+ addNameAndDtagCandidates(tags, tagsById, pairs);
56726
+ await addVectorCandidates(userId, tags, tagsById, pairs);
56727
+ for (const pair of pairs.values()) {
56728
+ const source = tagsById.get(pair.sourceId);
56729
+ const target = tagsById.get(pair.targetId);
56730
+ if (source && target) {
56731
+ pair.signals.source_tag = tagPayload(source);
56732
+ pair.signals.target_tag = tagPayload(target);
56733
+ pair.signals.candidate_sources = Array.from(pair.sources);
56734
+ }
56735
+ }
56736
+ return Array.from(pairs.values()).sort((a, b) => {
56737
+ const aExplicit = a.sources.has("explicit_user_statement") ? 1 : 0;
56738
+ const bExplicit = b.sources.has("explicit_user_statement") ? 1 : 0;
56739
+ if (aExplicit !== bExplicit) return bExplicit - aExplicit;
56740
+ return b.score - a.score;
56741
+ }).slice(0, limit2);
56742
+ }
56743
+ function formatMemoryForPrompt(memory) {
56744
+ return {
56745
+ id: memory.id,
56746
+ role: memory.role,
56747
+ created_at: memory.createdAt.toISOString(),
56748
+ d_tags: memory.dTags.slice(0, 8),
56749
+ message: truncate(memory.message, 700)
56750
+ };
56751
+ }
56752
+ function collectAllowedMemoryIds(pair, sourceMemories, targetMemories) {
56753
+ return /* @__PURE__ */ new Set([
56754
+ ...pair.explicitStatements.map((m) => m.id),
56755
+ ...sourceMemories.map((m) => m.id),
56756
+ ...targetMemories.map((m) => m.id)
56757
+ ]);
56758
+ }
56759
+ function parseJsonObject(raw) {
56760
+ const cleaned = raw.replace(/<think>[\s\S]*?<\/think>/g, "").replace(/```json|```/g, "").trim();
56761
+ try {
56762
+ return JSON.parse(cleaned);
56763
+ } catch {
56764
+ const start = cleaned.indexOf("{");
56765
+ const end = cleaned.lastIndexOf("}");
56766
+ if (start >= 0 && end > start) {
56767
+ return JSON.parse(cleaned.slice(start, end + 1));
56768
+ }
56769
+ throw new Error(`invalid JSON: ${cleaned.slice(0, 240)}`);
56770
+ }
56771
+ }
56772
+ function normalizeJudgment(raw, allowedIds) {
56773
+ const parsed = parseJsonObject(raw);
56774
+ let relation = String(parsed.relation ?? "insufficient").trim();
56775
+ if (!ALL_RELATIONS.has(relation)) relation = "insufficient";
56776
+ const directionIsBoolean = typeof parsed.source_should_alias_target === "boolean";
56777
+ if (QUALIFYING_RELATIONS.has(relation) && !directionIsBoolean) {
56778
+ relation = "insufficient";
56779
+ }
56780
+ const evidence = toNumberArray(parsed.evidence_memory_ids).filter((id) => allowedIds.has(id));
56781
+ const conflicts = toNumberArray(parsed.conflict_memory_ids).filter((id) => allowedIds.has(id));
56782
+ const confidence = clamp01(Number(parsed.confidence ?? 0));
56783
+ const sameProject = typeof parsed.same_project === "boolean" ? parsed.same_project : QUALIFYING_RELATIONS.has(relation);
56784
+ return {
56785
+ relation,
56786
+ same_project: sameProject,
56787
+ source_should_alias_target: directionIsBoolean ? Boolean(parsed.source_should_alias_target) : true,
56788
+ confidence,
56789
+ evidence_memory_ids: evidence,
56790
+ conflict_memory_ids: conflicts,
56791
+ rationale: typeof parsed.rationale === "string" ? parsed.rationale : ""
56792
+ };
56793
+ }
56794
+ async function judgePair(pair, tagsById, memoriesByTag) {
56795
+ const source = tagsById.get(pair.sourceId);
56796
+ const target = tagsById.get(pair.targetId);
56797
+ if (!source || !target) {
56798
+ throw new Error("candidate pair references missing tag");
56799
+ }
56800
+ const sourceMemories = memoriesByTag.get(source.id) ?? [];
56801
+ const targetMemories = memoriesByTag.get(target.id) ?? [];
56802
+ const allowedIds = collectAllowedMemoryIds(pair, sourceMemories, targetMemories);
56803
+ const userPrompt = JSON.stringify(
56804
+ {
56805
+ task: "Judge whether Tag A and Tag B are the same project identity.",
56806
+ candidate_sources: Array.from(pair.sources),
56807
+ signals: pair.signals,
56808
+ tag_a_source_candidate: tagPayload(source),
56809
+ tag_b_target_candidate: tagPayload(target),
56810
+ tag_a_representative_memories: sourceMemories.map(formatMemoryForPrompt),
56811
+ tag_b_representative_memories: targetMemories.map(formatMemoryForPrompt),
56812
+ explicit_user_statements: pair.explicitStatements.map(formatMemoryForPrompt)
56813
+ },
56814
+ null,
56815
+ 2
56816
+ );
56817
+ const spec = ROLE_REGISTRY.project_alias_judge;
56818
+ const raw = await callSpec(spec, {
56819
+ system: JUDGE_SYSTEM_PROMPT,
56820
+ user: userPrompt,
56821
+ ...spec.provider === "local" ? { jsonSchema: JUDGE_SCHEMA, enableThinking: false } : { responseFormat: "json" },
56822
+ maxTokens: envInt("PROJECT_ALIAS_JUDGE_MAX_TOKENS", 8192)
56823
+ });
56824
+ if (!raw) throw new Error("project_alias_judge returned empty content");
56825
+ return normalizeJudgment(raw, allowedIds);
56826
+ }
56827
+ async function areCanonicalRoots(tagIds) {
56828
+ if (tagIds.length === 0) return false;
56829
+ const result = await db.query(
56830
+ `SELECT id, alias_of
56831
+ FROM project_tags
56832
+ WHERE id = ANY($1::bigint[])`,
56833
+ [tagIds]
56834
+ );
56835
+ if (result.rows.length !== tagIds.length) return false;
56836
+ return result.rows.every((row) => row.alias_of === null);
56837
+ }
56838
+ async function hasRecentRejectedPair(userId, sourceId, targetId) {
56839
+ const rejectWindowDays = envInt("PROJECT_ALIAS_REJECT_WINDOW_DAYS", 30);
56840
+ const result = await db.query(
56841
+ `SELECT 1
56842
+ FROM project_tag_alias_suggestions
56843
+ WHERE user_id = $1
56844
+ AND status = 'rejected'
56845
+ AND LEAST(source_tag_id, target_tag_id) = LEAST($2::bigint, $3::bigint)
56846
+ AND GREATEST(source_tag_id, target_tag_id) = GREATEST($2::bigint, $3::bigint)
56847
+ AND COALESCE(decided_at, updated_at, created_at) >= NOW() - ($4 || ' days')::interval
56848
+ LIMIT 1`,
56849
+ [userId, sourceId, targetId, String(rejectWindowDays)]
56850
+ );
56851
+ return result.rows.length > 0;
56852
+ }
56853
+ async function computeAutoApplyEligible(userId, sourceId, targetId, pair, judgment) {
56854
+ if (!pair.sources.has("explicit_user_statement")) return false;
56855
+ if (!QUALIFYING_RELATIONS.has(judgment.relation)) return false;
56856
+ if (!judgment.same_project) return false;
56857
+ if (judgment.confidence < 0.98) return false;
56858
+ if (judgment.conflict_memory_ids.length > 0) return false;
56859
+ if (!await areCanonicalRoots([sourceId, targetId])) return false;
56860
+ if (await hasRecentRejectedPair(userId, sourceId, targetId)) return false;
56861
+ return true;
56862
+ }
56863
+ async function upsertSuggestion(userId, sourceId, targetId, pair, judgment, autoApplyEligible) {
56864
+ const spec = ROLE_REGISTRY.project_alias_judge;
56865
+ const candidateSources = Array.from(pair.sources);
56866
+ const signals = {
56867
+ ...pair.signals,
56868
+ llm: {
56869
+ relation: judgment.relation,
56870
+ same_project: judgment.same_project,
56871
+ source_should_alias_target: judgment.source_should_alias_target,
56872
+ stored_source_tag_id: sourceId,
56873
+ stored_target_tag_id: targetId,
56874
+ direction_reversed_from_candidate: sourceId !== pair.sourceId || targetId !== pair.targetId
56875
+ }
56876
+ };
56877
+ const result = await db.query(
56878
+ `INSERT INTO project_tag_alias_suggestions (
56879
+ user_id,
56880
+ source_tag_id,
56881
+ target_tag_id,
56882
+ relation,
56883
+ status,
56884
+ confidence,
56885
+ candidate_sources,
56886
+ evidence_memory_ids,
56887
+ conflict_memory_ids,
56888
+ signals,
56889
+ model_provider,
56890
+ model_name,
56891
+ rationale,
56892
+ auto_apply_eligible
56893
+ )
56894
+ VALUES (
56895
+ $1, $2, $3, $4, 'pending', $5,
56896
+ $6::text[], $7::bigint[], $8::bigint[], $9::jsonb,
56897
+ $10, $11, $12, $13
56898
+ )
56899
+ ON CONFLICT (
56900
+ user_id,
56901
+ (LEAST(source_tag_id, target_tag_id)),
56902
+ (GREATEST(source_tag_id, target_tag_id))
56903
+ )
56904
+ WHERE status = 'pending'
56905
+ DO UPDATE SET
56906
+ source_tag_id = EXCLUDED.source_tag_id,
56907
+ target_tag_id = EXCLUDED.target_tag_id,
56908
+ relation = EXCLUDED.relation,
56909
+ confidence = EXCLUDED.confidence,
56910
+ candidate_sources = EXCLUDED.candidate_sources,
56911
+ evidence_memory_ids = EXCLUDED.evidence_memory_ids,
56912
+ conflict_memory_ids = EXCLUDED.conflict_memory_ids,
56913
+ signals = EXCLUDED.signals,
56914
+ model_provider = EXCLUDED.model_provider,
56915
+ model_name = EXCLUDED.model_name,
56916
+ rationale = EXCLUDED.rationale,
56917
+ auto_apply_eligible = EXCLUDED.auto_apply_eligible,
56918
+ updated_at = NOW()
56919
+ RETURNING id`,
56920
+ [
56921
+ userId,
56922
+ sourceId,
56923
+ targetId,
56924
+ judgment.relation,
56925
+ Number(judgment.confidence.toFixed(4)),
56926
+ candidateSources,
56927
+ judgment.evidence_memory_ids,
56928
+ judgment.conflict_memory_ids,
56929
+ JSON.stringify(signals),
56930
+ spec.provider,
56931
+ spec.model_name,
56932
+ truncate(judgment.rationale, 4e3),
56933
+ autoApplyEligible
56934
+ ]
56935
+ );
56936
+ return Number(result.rows[0].id);
56937
+ }
56938
+ function qualifiesForPending(judgment, minConfidence) {
56939
+ return QUALIFYING_RELATIONS.has(judgment.relation) && judgment.same_project && judgment.confidence >= minConfidence;
56940
+ }
56941
+ async function runProjectAliasPromoter(opts = {}) {
56942
+ const userId = opts.userId ?? await getDefaultUserId();
56943
+ const candidateLimit = opts.limit ?? envInt("PROJECT_ALIAS_CANDIDATE_LIMIT", 12);
56944
+ const pendingMin = envFloat("PROJECT_ALIAS_PENDING_MIN", 0.85);
56945
+ const summary = {
56946
+ userId,
56947
+ candidates: 0,
56948
+ judged: 0,
56949
+ inserted: 0,
56950
+ autoApplied: 0,
56951
+ skipped: 0,
56952
+ errors: 0
56953
+ };
56954
+ const tags = await listCanonicalTags(userId);
56955
+ if (tags.length < 2) return summary;
56956
+ const tagsById = new Map(tags.map((tag) => [tag.id, tag]));
56957
+ const pairs = await buildCandidatePairs(userId, tags, candidateLimit);
56958
+ summary.candidates = pairs.length;
56959
+ if (pairs.length === 0) return summary;
56960
+ const tagIdsForPrompt = uniqueSortedNumbers(
56961
+ pairs.flatMap((pair) => [pair.sourceId, pair.targetId])
56962
+ );
56963
+ const memoriesByTag = await listRepresentativeMemories(userId, tagIdsForPrompt, 3);
56964
+ for (const pair of pairs) {
56965
+ try {
56966
+ const judgment = await judgePair(pair, tagsById, memoriesByTag);
56967
+ summary.judged++;
56968
+ if (!qualifiesForPending(judgment, pendingMin)) {
56969
+ summary.skipped++;
56970
+ continue;
56971
+ }
56972
+ let sourceId = pair.sourceId;
56973
+ let targetId = pair.targetId;
56974
+ if (!judgment.source_should_alias_target) {
56975
+ sourceId = pair.targetId;
56976
+ targetId = pair.sourceId;
56977
+ }
56978
+ if (sourceId === targetId) {
56979
+ summary.skipped++;
56980
+ continue;
56981
+ }
56982
+ const autoApplyEligible = await computeAutoApplyEligible(
56983
+ userId,
56984
+ sourceId,
56985
+ targetId,
56986
+ pair,
56987
+ judgment
56988
+ );
56989
+ const suggestionId = await upsertSuggestion(
56990
+ userId,
56991
+ sourceId,
56992
+ targetId,
56993
+ pair,
56994
+ judgment,
56995
+ autoApplyEligible
56996
+ );
56997
+ summary.inserted++;
56998
+ if (process.env.PROJECT_ALIAS_AUTO_APPLY === "true" && autoApplyEligible) {
56999
+ const applied = await applyAliasSuggestion(suggestionId, {
57000
+ decidedBy: "system",
57001
+ reason: "auto-apply eligible: explicit user rename/alias statement and high-confidence judge result"
57002
+ });
57003
+ if (applied.applied) summary.autoApplied++;
57004
+ }
57005
+ } catch (err2) {
57006
+ summary.errors++;
57007
+ console.error("[ProjectAliasPromoter] pair failed:", err2);
57008
+ }
57009
+ }
57010
+ return summary;
57011
+ }
57012
+ async function maybeRunProjectAliasPromoter() {
57013
+ if (process.env.PROJECT_ALIAS_PROMOTER_ENABLED !== "true") return;
57014
+ if (projectAliasPromoterRunning) return;
57015
+ projectAliasPromoterRunning = true;
57016
+ const intervalHours = envInt(
57017
+ "PROJECT_ALIAS_PROMOTER_INTERVAL_HOURS",
57018
+ PROJECT_ALIAS_PROMOTER_DEFAULT_INTERVAL_HOURS
57019
+ );
57020
+ try {
57021
+ const userId = await getDefaultUserId();
57022
+ const result = await db.query(
57023
+ `SELECT project_alias_promoter_last_run_at
57024
+ FROM users
57025
+ WHERE user_id = $1`,
57026
+ [userId]
57027
+ );
57028
+ const lastRunAt = result.rows[0]?.project_alias_promoter_last_run_at ?? null;
57029
+ const cooldownMs = intervalHours * 60 * 60 * 1e3;
57030
+ const cooldownPassed = lastRunAt === null || Date.now() - new Date(lastRunAt).getTime() >= cooldownMs;
57031
+ if (!cooldownPassed) return;
57032
+ await db.query(
57033
+ `UPDATE users
57034
+ SET project_alias_promoter_last_run_at = NOW()
57035
+ WHERE user_id = $1`,
57036
+ [userId]
57037
+ );
57038
+ const summary = await runProjectAliasPromoter({ userId });
57039
+ if (summary.candidates > 0 || summary.inserted > 0 || summary.errors > 0) {
57040
+ console.error(
57041
+ `\u{1F501} [ProjectAliasPromoter] done \u2014 candidates=${summary.candidates}, judged=${summary.judged}, inserted=${summary.inserted}, autoApplied=${summary.autoApplied}, skipped=${summary.skipped}, errors=${summary.errors}`
57042
+ );
57043
+ }
57044
+ } catch (err2) {
57045
+ console.error(
57046
+ `\u26A0\uFE0F [ProjectAliasPromoter] run failed (retries in ${intervalHours}h):`,
57047
+ err2
57048
+ );
57049
+ } finally {
57050
+ projectAliasPromoterRunning = false;
57051
+ }
57052
+ }
57053
+ async function markSuggestionTerminal(client2, suggestionId, status, decidedBy, reason) {
57054
+ await client2.query(
57055
+ `UPDATE project_tag_alias_suggestions
57056
+ SET status = $2,
57057
+ decided_by = $3,
57058
+ decision_reason = $4,
57059
+ decided_at = NOW(),
57060
+ updated_at = NOW()
57061
+ WHERE id = $1`,
57062
+ [suggestionId, status, decidedBy, reason]
57063
+ );
57064
+ }
57065
+ async function targetChainContainsSource(client2, sourceId, targetId) {
57066
+ const result = await client2.query(
57067
+ `WITH RECURSIVE chain(id, alias_of, path) AS (
57068
+ SELECT id, alias_of, ARRAY[id]::bigint[]
57069
+ FROM project_tags
57070
+ WHERE id = $2
57071
+ UNION ALL
57072
+ SELECT pt.id, pt.alias_of, chain.path || pt.id
57073
+ FROM project_tags pt
57074
+ JOIN chain ON pt.id = chain.alias_of
57075
+ WHERE NOT pt.id = ANY(chain.path)
57076
+ )
57077
+ SELECT 1
57078
+ FROM chain
57079
+ WHERE id = $1
57080
+ LIMIT 1`,
57081
+ [sourceId, targetId]
57082
+ );
57083
+ return result.rows.length > 0;
57084
+ }
57085
+ async function applyAliasSuggestion(suggestionId, opts = {}) {
57086
+ const decidedBy = opts.decidedBy ?? "system";
57087
+ const reason = opts.reason ?? (decidedBy === "user" ? "confirmed by user" : "auto-applied by system");
57088
+ const appliedStatus = decidedBy === "system" ? "auto_applied" : "confirmed";
57089
+ const client2 = await db.getClient();
57090
+ try {
57091
+ await client2.query("BEGIN");
57092
+ const suggestionResult = await client2.query(
57093
+ `SELECT *
57094
+ FROM project_tag_alias_suggestions
57095
+ WHERE id = $1
57096
+ FOR UPDATE`,
57097
+ [suggestionId]
57098
+ );
57099
+ if (suggestionResult.rows.length === 0) {
57100
+ await client2.query("ROLLBACK");
57101
+ return { suggestionId, applied: false, reason: "suggestion_not_found" };
57102
+ }
57103
+ const suggestion = suggestionResult.rows[0];
57104
+ if (suggestion.status !== "pending") {
57105
+ await client2.query("ROLLBACK");
57106
+ return {
57107
+ suggestionId,
57108
+ applied: false,
57109
+ status: String(suggestion.status),
57110
+ reason: "already_decided"
57111
+ };
57112
+ }
57113
+ const sourceId = Number(suggestion.source_tag_id);
57114
+ const targetId = Number(suggestion.target_tag_id);
57115
+ const roots = await client2.query(
57116
+ `SELECT canonical_project_tag_id($1::bigint) AS source_root,
57117
+ canonical_project_tag_id($2::bigint) AS target_root`,
57118
+ [sourceId, targetId]
57119
+ );
57120
+ const sourceRoot = Number(roots.rows[0]?.source_root);
57121
+ const targetRoot = Number(roots.rows[0]?.target_root);
57122
+ await client2.query(
57123
+ `SELECT id
57124
+ FROM project_tags
57125
+ WHERE id = ANY($1::bigint[])
57126
+ FOR UPDATE`,
57127
+ [[sourceId, targetRoot]]
57128
+ );
57129
+ if (!Number.isInteger(sourceRoot) || sourceRoot !== sourceId) {
57130
+ const msg = "source tag is no longer a canonical root";
57131
+ await markSuggestionTerminal(client2, suggestionId, "superseded", "system", msg);
57132
+ await client2.query("COMMIT");
57133
+ return { suggestionId, applied: false, status: "superseded", reason: msg };
57134
+ }
57135
+ if (!Number.isInteger(targetRoot) || targetRoot === sourceId) {
57136
+ const msg = "alias would point source to itself";
57137
+ await markSuggestionTerminal(client2, suggestionId, "rejected", decidedBy, msg);
57138
+ await client2.query("COMMIT");
57139
+ return { suggestionId, applied: false, status: "rejected", reason: msg };
57140
+ }
57141
+ if (await targetChainContainsSource(client2, sourceId, targetId)) {
57142
+ const msg = "alias would create a project_tag alias cycle";
57143
+ await markSuggestionTerminal(client2, suggestionId, "rejected", decidedBy, msg);
57144
+ await client2.query("COMMIT");
57145
+ return { suggestionId, applied: false, status: "rejected", reason: msg };
57146
+ }
57147
+ await client2.query(
57148
+ `UPDATE project_tags
57149
+ SET alias_of = $1,
57150
+ updated_at = NOW()
57151
+ WHERE id = $2`,
57152
+ [targetRoot, sourceId]
57153
+ );
57154
+ await client2.query(
57155
+ `UPDATE project_tag_alias_suggestions
57156
+ SET status = $2,
57157
+ decided_by = $3,
57158
+ decision_reason = $4,
57159
+ decided_at = NOW(),
57160
+ applied_at = NOW(),
57161
+ updated_at = NOW()
57162
+ WHERE id = $1`,
57163
+ [suggestionId, appliedStatus, decidedBy, reason]
57164
+ );
57165
+ await client2.query(
57166
+ `UPDATE project_tag_alias_suggestions
57167
+ SET status = 'superseded',
57168
+ decided_by = 'system',
57169
+ decision_reason = $4,
57170
+ decided_at = NOW(),
57171
+ updated_at = NOW()
57172
+ WHERE user_id = $1
57173
+ AND status = 'pending'
57174
+ AND id <> $2
57175
+ AND (source_tag_id = $3 OR target_tag_id = $3)`,
57176
+ [
57177
+ Number(suggestion.user_id),
57178
+ suggestionId,
57179
+ sourceId,
57180
+ `source tag ${sourceId} was aliased by suggestion ${suggestionId}`
57181
+ ]
57182
+ );
57183
+ await client2.query("COMMIT");
57184
+ invalidateCandidateCache();
57185
+ return { suggestionId, applied: true, status: appliedStatus };
57186
+ } catch (err2) {
57187
+ try {
57188
+ await client2.query("ROLLBACK");
57189
+ } catch {
57190
+ }
57191
+ throw err2;
57192
+ } finally {
57193
+ client2.release();
57194
+ }
57195
+ }
57196
+
57197
+ // src/tools/manage_project_tags.ts
57198
+ var DEFAULT_LIMIT2 = 20;
57199
+ var MAX_LIMIT = 100;
57200
+ var statusSchema = external_exports.enum([
57201
+ "pending",
57202
+ "confirmed",
57203
+ "rejected",
57204
+ "auto_applied",
57205
+ "superseded",
57206
+ "expired"
57207
+ ]);
57208
+ function ok2(payload) {
57209
+ return {
57210
+ content: [{ type: "text", text: JSON.stringify(payload, null, 2) }]
57211
+ };
57212
+ }
57213
+ function toolError(message, extra) {
57214
+ return {
57215
+ content: [{ type: "text", text: JSON.stringify({ error: message, ...extra }, null, 2) }]
57216
+ };
57217
+ }
57218
+ function normalizeTagName(name) {
57219
+ const normalized = name?.toLowerCase().trim();
57220
+ return normalized ? normalized : null;
57221
+ }
57222
+ function errorDetail(err2) {
57223
+ return err2 instanceof Error ? err2.message : String(err2);
57224
+ }
57225
+ async function lookupProjectTagByName(name) {
57226
+ const result = await db.query(
57227
+ `SELECT id, name, alias_of
57228
+ FROM project_tags
57229
+ WHERE name = $1
57230
+ LIMIT 1`,
57231
+ [name]
57232
+ );
57233
+ if (result.rows.length === 0) return null;
57234
+ const row = result.rows[0];
57235
+ return {
57236
+ id: Number(row.id),
57237
+ name: String(row.name),
57238
+ aliasOf: row.alias_of == null ? null : Number(row.alias_of)
57239
+ };
57240
+ }
57241
+ async function lookupSuggestionForUser(userId, suggestionId) {
57242
+ const result = await db.query(
57243
+ `SELECT id, status
57244
+ FROM project_tag_alias_suggestions
57245
+ WHERE id = $1
57246
+ AND user_id = $2
57247
+ LIMIT 1`,
57248
+ [suggestionId, userId]
57249
+ );
57250
+ if (result.rows.length === 0) return null;
57251
+ return {
57252
+ id: Number(result.rows[0].id),
57253
+ status: String(result.rows[0].status)
57254
+ };
57255
+ }
57256
+ async function listSuggestions(userId, status, limit2) {
57257
+ const result = await db.query(
57258
+ `SELECT s.id,
57259
+ source.name AS source_tag_name,
57260
+ target.name AS target_tag_name,
57261
+ s.relation,
57262
+ s.confidence,
57263
+ s.rationale,
57264
+ s.created_at
57265
+ FROM project_tag_alias_suggestions s
57266
+ JOIN project_tags source ON source.id = s.source_tag_id
57267
+ JOIN project_tags target ON target.id = s.target_tag_id
57268
+ WHERE s.user_id = $1
57269
+ AND s.status = $2
57270
+ ORDER BY s.created_at DESC
57271
+ LIMIT $3`,
57272
+ [userId, status, limit2]
57273
+ );
57274
+ const suggestions = result.rows.map((row) => ({
57275
+ suggestion_id: Number(row.id),
57276
+ source_tag: String(row.source_tag_name),
57277
+ target_tag: String(row.target_tag_name),
57278
+ relation: String(row.relation),
57279
+ confidence: Number(row.confidence),
57280
+ rationale: String(row.rationale ?? ""),
57281
+ created_at: row.created_at
57282
+ }));
57283
+ return ok2({
57284
+ action: "list_suggestions",
57285
+ status,
57286
+ limit: limit2,
57287
+ count: suggestions.length,
57288
+ suggestions
57289
+ });
57290
+ }
57291
+ async function confirmAlias(userId, suggestionId, reason) {
57292
+ const suggestion = await lookupSuggestionForUser(userId, suggestionId);
57293
+ if (!suggestion) {
57294
+ return toolError("suggestion_id not found for current user", { suggestion_id: suggestionId });
57295
+ }
57296
+ if (suggestion.status !== "pending") {
57297
+ return toolError("suggestion is not pending", {
57298
+ suggestion_id: suggestionId,
57299
+ status: suggestion.status
57300
+ });
57301
+ }
57302
+ const result = await applyAliasSuggestion(suggestionId, {
57303
+ decidedBy: "user",
57304
+ reason
57305
+ });
57306
+ return ok2({
57307
+ action: "confirm_alias",
57308
+ ...result
57309
+ });
57310
+ }
57311
+ async function rejectAlias(userId, suggestionId, reason) {
57312
+ const suggestion = await lookupSuggestionForUser(userId, suggestionId);
57313
+ if (!suggestion) {
57314
+ return toolError("suggestion_id not found for current user", { suggestion_id: suggestionId });
57315
+ }
57316
+ if (suggestion.status !== "pending") {
57317
+ return toolError("suggestion is not pending", {
57318
+ suggestion_id: suggestionId,
57319
+ status: suggestion.status
57320
+ });
57321
+ }
57322
+ const result = await db.query(
57323
+ `UPDATE project_tag_alias_suggestions
57324
+ SET status = 'rejected',
57325
+ decided_by = 'user',
57326
+ decision_reason = $3,
57327
+ decided_at = NOW(),
57328
+ updated_at = NOW()
57329
+ WHERE id = $1
57330
+ AND user_id = $2
57331
+ AND status = 'pending'
57332
+ RETURNING id, status, decision_reason, decided_at`,
57333
+ [suggestionId, userId, reason ?? null]
57334
+ );
57335
+ if (result.rows.length === 0) {
57336
+ return toolError("suggestion could not be rejected because it is no longer pending", {
57337
+ suggestion_id: suggestionId
57338
+ });
57339
+ }
57340
+ const row = result.rows[0];
57341
+ return ok2({
57342
+ action: "reject_alias",
57343
+ suggestion_id: Number(row.id),
57344
+ rejected: true,
57345
+ status: String(row.status),
57346
+ reason: row.decision_reason,
57347
+ decided_at: row.decided_at
57348
+ });
57349
+ }
57350
+ async function setAlias(userId, sourceTagName, targetTagName, reason) {
57351
+ const sourceName = normalizeTagName(sourceTagName);
57352
+ const targetName = normalizeTagName(targetTagName);
57353
+ if (!sourceName) return toolError("source_tag is required for set_alias");
57354
+ if (!targetName) return toolError("target_tag is required for set_alias");
57355
+ const sourceTag = await lookupProjectTagByName(sourceName);
57356
+ if (!sourceTag) {
57357
+ return toolError("source_tag not found in project_tags", { source_tag: sourceName });
57358
+ }
57359
+ const targetTag = await lookupProjectTagByName(targetName);
57360
+ if (!targetTag) {
57361
+ return toolError("target_tag not found in project_tags", { target_tag: targetName });
57362
+ }
57363
+ if (sourceTag.id === targetTag.id) {
57364
+ return toolError("source_tag and target_tag must be different", {
57365
+ source_tag: sourceTag.name,
57366
+ target_tag: targetTag.name
57367
+ });
57368
+ }
57369
+ const insertResult = await db.query(
57370
+ `INSERT INTO project_tag_alias_suggestions (
57371
+ user_id,
57372
+ source_tag_id,
57373
+ target_tag_id,
57374
+ relation,
57375
+ status,
57376
+ confidence,
57377
+ candidate_sources,
57378
+ signals,
57379
+ rationale,
57380
+ decided_by
57381
+ )
57382
+ VALUES (
57383
+ $1, $2, $3, 'alias', 'pending', 1.0,
57384
+ ARRAY['manual_set_alias']::text[],
57385
+ $4::jsonb,
57386
+ 'manual set_alias',
57387
+ 'user'
57388
+ )
57389
+ ON CONFLICT (
57390
+ user_id,
57391
+ (LEAST(source_tag_id, target_tag_id)),
57392
+ (GREATEST(source_tag_id, target_tag_id))
57393
+ )
57394
+ WHERE status = 'pending'
57395
+ DO UPDATE SET
57396
+ source_tag_id = EXCLUDED.source_tag_id,
57397
+ target_tag_id = EXCLUDED.target_tag_id,
57398
+ relation = EXCLUDED.relation,
57399
+ confidence = EXCLUDED.confidence,
57400
+ candidate_sources = EXCLUDED.candidate_sources,
57401
+ signals = EXCLUDED.signals,
57402
+ rationale = EXCLUDED.rationale,
57403
+ decided_by = EXCLUDED.decided_by,
57404
+ decision_reason = NULL,
57405
+ decided_at = NULL,
57406
+ applied_at = NULL,
57407
+ updated_at = NOW()
57408
+ RETURNING id`,
57409
+ [
57410
+ userId,
57411
+ sourceTag.id,
57412
+ targetTag.id,
57413
+ JSON.stringify({
57414
+ manual: true,
57415
+ action: "set_alias",
57416
+ source_tag: sourceTag.name,
57417
+ target_tag: targetTag.name
57418
+ })
57419
+ ]
57420
+ );
57421
+ const suggestionId = Number(insertResult.rows[0].id);
57422
+ const result = await applyAliasSuggestion(suggestionId, {
57423
+ decidedBy: "user",
57424
+ reason: reason ?? "manual set_alias"
57425
+ });
57426
+ return ok2({
57427
+ action: "set_alias",
57428
+ source_tag: sourceTag.name,
57429
+ target_tag: targetTag.name,
57430
+ ...result
57431
+ });
57432
+ }
57433
+ async function unsetAlias(userId, tagName) {
57434
+ const normalized = normalizeTagName(tagName);
57435
+ if (!normalized) return toolError("tag is required for unset_alias");
57436
+ const tag = await lookupProjectTagByName(normalized);
57437
+ if (!tag) {
57438
+ return toolError("tag not found in project_tags", { tag: normalized });
57439
+ }
57440
+ const result = await db.query(
57441
+ `UPDATE project_tags
57442
+ SET alias_of = NULL,
57443
+ updated_at = NOW()
57444
+ WHERE id = $1
57445
+ AND alias_of IS NOT NULL
57446
+ RETURNING id, name`,
57447
+ [tag.id]
57448
+ );
57449
+ if (result.rows.length === 0) {
57450
+ return ok2({
57451
+ action: "unset_alias",
57452
+ tag: tag.name,
57453
+ alias_removed: false,
57454
+ reason: "tag is not currently an alias",
57455
+ user_id: userId
57456
+ });
57457
+ }
57458
+ invalidateCandidateCache();
57459
+ return ok2({
57460
+ action: "unset_alias",
57461
+ tag: String(result.rows[0].name),
57462
+ alias_removed: true,
57463
+ user_id: userId
57464
+ });
57465
+ }
57466
+ function registerManageProjectTags(server) {
57467
+ server.registerTool(
57468
+ "manage_project_tags",
57469
+ {
57470
+ description: `Project tag alias suggestion management tool (Stage 2 / DEVLOG \xA719).
57471
+
57472
+ Actions:
57473
+ - list_suggestions: list alias suggestions by status for the default user.
57474
+ - confirm_alias: confirm a pending suggestion through applyAliasSuggestion().
57475
+ - reject_alias: reject a pending suggestion.
57476
+ - set_alias: manually set source_tag as an alias of target_tag through a pending suggestion + applyAliasSuggestion().
57477
+ - unset_alias: clear alias_of for one project tag.`,
57478
+ inputSchema: {
57479
+ action: external_exports.enum([
57480
+ "list_suggestions",
57481
+ "confirm_alias",
57482
+ "reject_alias",
57483
+ "set_alias",
57484
+ "unset_alias"
57485
+ ]).describe("\uC791\uC5C5 \uC885\uB958"),
57486
+ status: statusSchema.optional().describe("list_suggestions status filter (default pending)"),
57487
+ limit: external_exports.number().int().min(1).max(MAX_LIMIT).optional().describe(`list_suggestions limit (default ${DEFAULT_LIMIT2})`),
57488
+ suggestion_id: external_exports.number().int().positive().optional().describe("confirm_alias/reject_alias \uB300\uC0C1 suggestion id"),
57489
+ reason: external_exports.string().optional().describe("confirm/reject/set decision reason"),
57490
+ source_tag: external_exports.string().optional().describe("set_alias source project_tags.name"),
57491
+ target_tag: external_exports.string().optional().describe("set_alias target project_tags.name"),
57492
+ tag: external_exports.string().optional().describe("unset_alias \uB300\uC0C1 project_tags.name")
57493
+ }
57494
+ },
57495
+ async (args) => {
57496
+ try {
57497
+ const userId = await getDefaultUserId();
57498
+ if (args.action === "list_suggestions") {
57499
+ return listSuggestions(
57500
+ userId,
57501
+ args.status ?? "pending",
57502
+ args.limit ?? DEFAULT_LIMIT2
57503
+ );
57504
+ }
57505
+ if (args.action === "confirm_alias") {
57506
+ if (args.suggestion_id == null) {
57507
+ return toolError("suggestion_id is required for confirm_alias");
57508
+ }
57509
+ return confirmAlias(userId, args.suggestion_id, args.reason);
57510
+ }
57511
+ if (args.action === "reject_alias") {
57512
+ if (args.suggestion_id == null) {
57513
+ return toolError("suggestion_id is required for reject_alias");
57514
+ }
57515
+ return rejectAlias(userId, args.suggestion_id, args.reason);
57516
+ }
57517
+ if (args.action === "set_alias") {
57518
+ return setAlias(userId, args.source_tag, args.target_tag, args.reason);
57519
+ }
57520
+ if (args.action === "unset_alias") {
57521
+ return unsetAlias(userId, args.tag);
57522
+ }
57523
+ return toolError("unsupported action", { action: args.action });
57524
+ } catch (err2) {
57525
+ return toolError("manage_project_tags failed", { detail: errorDetail(err2) });
57526
+ }
57527
+ }
57528
+ );
57529
+ }
57530
+
56104
57531
  // src/briefing.ts
56105
57532
  init_db();
56106
- import * as os3 from "node:os";
57533
+ import * as os4 from "node:os";
56107
57534
  var RECENT_CURRENT_LIMIT = 8;
56108
57535
  var RECENT_OTHERS_LIMIT = 4;
56109
- var RECENT_MESSAGE_PREVIEW = 100;
57536
+ var MAX_PREVIEW_STORE = 500;
57537
+ var PREVIEW_COMPACT = 100;
57538
+ var PREVIEW_RECENT = 300;
56110
57539
  var ACTIVE_PTAG_LIMIT = 5;
57540
+ var PENDING_ALIAS_SUGGESTION_LIMIT = 2;
57541
+ var PINNED_LIMIT = 10;
57542
+ var INJECT_PINNED_LIMIT = 5;
57543
+ var BRIEF_MAX_CHARS = Number(process.env.BRIEF_MAX_CHARS ?? 8e3);
56111
57544
  async function collectBrief(opts = {}) {
56112
57545
  const userId = opts.userId ?? await getDefaultUserId();
56113
57546
  const shortTermDays = opts.shortTermDays ?? Number(process.env.SHORT_TERM_DAYS ?? 3);
56114
57547
  const currentPlatform = opts.currentPlatform ?? null;
57548
+ const deviceName = opts.deviceName ?? os4.hostname();
56115
57549
  const u = await db.query(
56116
57550
  `SELECT user_name, core_profile, sub_profile FROM users WHERE user_id = $1`,
56117
57551
  [userId]
56118
57552
  );
56119
57553
  const user = u.rows[0] ?? { user_name: "unknown", core_profile: null, sub_profile: null };
57554
+ const pinnedMsgs = await db.query(
57555
+ `SELECT role, agent_platform, device_name, message, created_at
57556
+ FROM memory
57557
+ WHERE user_id = $1
57558
+ AND is_active = TRUE
57559
+ AND is_pinned = TRUE
57560
+ ORDER BY created_at DESC
57561
+ LIMIT $2`,
57562
+ [userId, PINNED_LIMIT]
57563
+ );
56120
57564
  const ptags = await db.query(
56121
- `SELECT pt.name,
57565
+ `SELECT cpt.name,
56122
57566
  COUNT(*)::int AS cnt,
56123
57567
  MAX(m.created_at) AS last_used
56124
57568
  FROM memory m
56125
- JOIN project_tags pt ON pt.id = m.p_tag_id
57569
+ JOIN project_tags cpt ON cpt.id = canonical_project_tag_id(m.p_tag_id)
56126
57570
  WHERE m.user_id = $1
56127
57571
  AND m.is_active = TRUE
56128
57572
  AND m.created_at >= NOW() - ($2 || ' days')::INTERVAL
56129
- GROUP BY pt.name
57573
+ GROUP BY cpt.name
56130
57574
  ORDER BY MAX(m.created_at) DESC
56131
57575
  LIMIT $3`,
56132
57576
  [userId, String(shortTermDays), ACTIVE_PTAG_LIMIT]
@@ -56141,9 +57585,11 @@ async function collectBrief(opts = {}) {
56141
57585
  AND is_active = TRUE
56142
57586
  AND created_at >= NOW() - ($2 || ' days')::INTERVAL
56143
57587
  AND agent_platform = $3
57588
+ AND device_name = $4
57589
+ AND is_pinned = FALSE
56144
57590
  ORDER BY created_at DESC
56145
- LIMIT $4`,
56146
- [userId, String(shortTermDays), currentPlatform, RECENT_CURRENT_LIMIT]
57591
+ LIMIT $5`,
57592
+ [userId, String(shortTermDays), currentPlatform, deviceName, RECENT_CURRENT_LIMIT]
56147
57593
  );
56148
57594
  recentCurrent = currentMsgs.rows.reverse().map(rowToMsg);
56149
57595
  const othersMsgs = await db.query(
@@ -56153,6 +57599,7 @@ async function collectBrief(opts = {}) {
56153
57599
  AND is_active = TRUE
56154
57600
  AND created_at >= NOW() - ($2 || ' days')::INTERVAL
56155
57601
  AND agent_platform != $3
57602
+ AND is_pinned = FALSE
56156
57603
  ORDER BY created_at DESC
56157
57604
  LIMIT $4`,
56158
57605
  [userId, String(shortTermDays), currentPlatform, RECENT_OTHERS_LIMIT]
@@ -56165,16 +57612,35 @@ async function collectBrief(opts = {}) {
56165
57612
  WHERE user_id = $1
56166
57613
  AND is_active = TRUE
56167
57614
  AND created_at >= NOW() - ($2 || ' days')::INTERVAL
57615
+ AND is_pinned = FALSE
56168
57616
  ORDER BY created_at DESC
56169
57617
  LIMIT $3`,
56170
57618
  [userId, String(shortTermDays), RECENT_CURRENT_LIMIT]
56171
57619
  );
56172
57620
  recentCurrent = msgs.rows.reverse().map(rowToMsg);
56173
57621
  }
57622
+ const aliasSugg = await db.query(
57623
+ `SELECT s.id, src.name AS source, tgt.name AS target, s.relation, s.confidence
57624
+ FROM project_tag_alias_suggestions s
57625
+ JOIN project_tags src ON src.id = s.source_tag_id
57626
+ JOIN project_tags tgt ON tgt.id = s.target_tag_id
57627
+ WHERE s.user_id = $1 AND s.status = 'pending'
57628
+ ORDER BY s.confidence DESC, s.created_at DESC
57629
+ LIMIT $2`,
57630
+ [userId, PENDING_ALIAS_SUGGESTION_LIMIT]
57631
+ );
56174
57632
  return {
56175
57633
  user_name: user.user_name,
56176
57634
  core_profile: user.core_profile,
56177
57635
  sub_profile: user.sub_profile,
57636
+ pinned_memories: pinnedMsgs.rows.map(rowToMsg),
57637
+ pending_alias_suggestions: aliasSugg.rows.map((r) => ({
57638
+ id: Number(r.id),
57639
+ source: String(r.source),
57640
+ target: String(r.target),
57641
+ relation: String(r.relation),
57642
+ confidence: Number(r.confidence)
57643
+ })),
56178
57644
  active_p_tags: ptags.rows.map((r) => ({
56179
57645
  name: r.name,
56180
57646
  count: r.cnt,
@@ -56191,64 +57657,140 @@ function rowToMsg(r) {
56191
57657
  role: r.role,
56192
57658
  agent_platform: r.agent_platform,
56193
57659
  device_name: r.device_name ?? null,
56194
- preview: String(r.message ?? "").slice(0, RECENT_MESSAGE_PREVIEW),
57660
+ preview: String(r.message ?? "").slice(0, MAX_PREVIEW_STORE),
56195
57661
  created_at: r.created_at
56196
57662
  };
56197
57663
  }
56198
- function formatBriefMarkdown(brief) {
56199
- const lines = [];
56200
- lines.push(`# Memory Briefing (user: ${brief.user_name})`);
57664
+ function formatBriefMarkdown(brief, opts = {}) {
57665
+ if ((opts.mode ?? "full") === "inject") {
57666
+ return formatBriefInject(brief, opts.maxChars ?? BRIEF_MAX_CHARS);
57667
+ }
57668
+ return formatBriefFull(brief);
57669
+ }
57670
+ function formatBriefFull(brief) {
57671
+ const gLines = [];
57672
+ gLines.push(`# Memory Briefing (user: ${brief.user_name})`);
56201
57673
  if (brief.current_platform) {
56202
- lines.push(`Current platform: \`${brief.current_platform} @ ${os3.hostname()}\``);
57674
+ gLines.push(`Current platform: \`${brief.current_platform} @ ${os4.hostname()}\``);
56203
57675
  }
56204
- lines.push("");
57676
+ gLines.push("");
56205
57677
  if (brief.core_profile) {
56206
- lines.push(`## Core Profile`);
56207
- lines.push(brief.core_profile);
56208
- lines.push("");
57678
+ gLines.push("## Core Profile");
57679
+ gLines.push(brief.core_profile);
57680
+ gLines.push("");
56209
57681
  }
56210
57682
  if (brief.sub_profile) {
56211
- lines.push(`## Sub Profile`);
56212
- lines.push(brief.sub_profile);
56213
- lines.push("");
56214
- }
57683
+ gLines.push("## Sub Profile");
57684
+ gLines.push(brief.sub_profile);
57685
+ gLines.push("");
57686
+ }
57687
+ if (brief.pinned_memories.length > 0) {
57688
+ gLines.push("## Pinned Memories (Important Facts)");
57689
+ for (const m of brief.pinned_memories) {
57690
+ gLines.push(formatMsgLine(m, true));
57691
+ }
57692
+ gLines.push("");
57693
+ }
57694
+ if (brief.pending_alias_suggestions.length > 0) {
57695
+ gLines.push("## Project Tag Suggestions (\uC0AC\uC6A9\uC790 \uD655\uC778 \uD544\uC694)");
57696
+ for (const s of brief.pending_alias_suggestions) {
57697
+ 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\`.`);
57698
+ }
57699
+ gLines.push("");
57700
+ }
57701
+ const guaranteed = gLines.join("\n");
57702
+ const footerLines = [
57703
+ "---",
57704
+ `Use \`search_memory({ query, p_tag, date_range, role, agent_platform, include_archived })\` to retrieve more.`,
57705
+ ...brief.current_platform ? [`Cross-platform search: \`search_memory({ query, agent_platform: "*" })\`.`] : [],
57706
+ `Use \`memory_startup\` tool for a refreshed brief mid-session.`
57707
+ ];
57708
+ const footer = footerLines.join("\n");
57709
+ const remaining = Math.max(0, BRIEF_MAX_CHARS - guaranteed.length - footer.length - 2);
57710
+ const optionalSections = [];
56215
57711
  if (brief.active_p_tags.length > 0) {
56216
- lines.push(`## Active Projects (last ${brief.short_term_window_days} days)`);
57712
+ const lines = [`## Active Projects (last ${brief.short_term_window_days} days)`];
56217
57713
  for (const t of brief.active_p_tags) {
56218
57714
  const dt = t.last_used ? t.last_used.toISOString().slice(0, 10) : "?";
56219
57715
  lines.push(`- **${t.name}** \u2014 ${t.count} memories (last: ${dt})`);
56220
57716
  }
56221
57717
  lines.push("");
57718
+ optionalSections.push(lines.join("\n"));
56222
57719
  }
56223
57720
  if (brief.recent_messages_current.length > 0) {
56224
57721
  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);
57722
+ const lines = [heading];
56226
57723
  for (const m of brief.recent_messages_current) {
56227
- lines.push(formatMsgLine(m, brief.current_platform === null));
57724
+ lines.push(formatMsgLine(m, brief.current_platform === null, PREVIEW_RECENT));
56228
57725
  }
56229
57726
  lines.push("");
57727
+ optionalSections.push(lines.join("\n"));
56230
57728
  }
56231
57729
  if (brief.recent_messages_others.length > 0) {
56232
- lines.push(`## Cross-platform Whispers (other agents, last ${brief.recent_messages_others.length})`);
57730
+ const lines = [`## Cross-platform Whispers (other agents, last ${brief.recent_messages_others.length})`];
56233
57731
  for (const m of brief.recent_messages_others) {
56234
57732
  lines.push(formatMsgLine(m, true));
56235
57733
  }
56236
57734
  lines.push("");
57735
+ optionalSections.push(lines.join("\n"));
56237
57736
  }
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: "*" })\`.`);
57737
+ let optionalText = "";
57738
+ for (const section of optionalSections) {
57739
+ if (optionalText.length + section.length > remaining) break;
57740
+ optionalText += section;
56242
57741
  }
56243
- lines.push(`Use \`memory_startup\` tool for a refreshed brief mid-session.`);
56244
- return lines.join("\n");
57742
+ return guaranteed + "\n" + optionalText + "\n" + footer;
56245
57743
  }
56246
- function formatMsgLine(m, showPlatform) {
57744
+ function formatBriefInject(brief, maxChars) {
57745
+ const headLines = [`# Memory Briefing (user: ${brief.user_name})`];
57746
+ if (brief.current_platform) {
57747
+ headLines.push(`Current platform: \`${brief.current_platform} @ ${os4.hostname()}\``);
57748
+ }
57749
+ headLines.push("");
57750
+ if (brief.core_profile) {
57751
+ headLines.push("## Core Profile", brief.core_profile, "");
57752
+ }
57753
+ const header = headLines.join("\n");
57754
+ const pointer = [
57755
+ "---",
57756
+ "\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.",
57757
+ "Use `search_memory({ query, p_tag, date_range, role, agent_platform })` to retrieve more."
57758
+ ].join("\n");
57759
+ const budget = { remaining: Math.max(0, maxChars - header.length - pointer.length - 2) };
57760
+ const pinnedLines = brief.pinned_memories.slice(0, INJECT_PINNED_LIMIT).map((m) => formatMsgLine(m, true));
57761
+ const pinnedSection = fitSection("## Pinned Memories (Important Facts)", pinnedLines, budget);
57762
+ const activeLines = brief.active_p_tags.map((t) => {
57763
+ const dt = t.last_used ? t.last_used.toISOString().slice(0, 10) : "?";
57764
+ return `- **${t.name}** \u2014 ${t.count} memories (last: ${dt})`;
57765
+ });
57766
+ const activeSection = fitSection(
57767
+ `## Active Projects (last ${brief.short_term_window_days} days)`,
57768
+ activeLines,
57769
+ budget
57770
+ );
57771
+ return [header.trimEnd(), pinnedSection, activeSection, pointer].filter((s) => s.length > 0).join("\n\n");
57772
+ }
57773
+ function fitSection(heading, lines, budget) {
57774
+ if (lines.length === 0) return "";
57775
+ const headingCost = heading.length + 1;
57776
+ if (budget.remaining < headingCost + lines[0].length + 1) return "";
57777
+ const out = [heading];
57778
+ let used = headingCost;
57779
+ for (const ln of lines) {
57780
+ if (used + ln.length + 1 > budget.remaining) break;
57781
+ out.push(ln);
57782
+ used += ln.length + 1;
57783
+ }
57784
+ budget.remaining -= used;
57785
+ return out.join("\n");
57786
+ }
57787
+ function formatMsgLine(m, showPlatform, maxPreview = PREVIEW_COMPACT) {
56247
57788
  const dt = m.created_at?.toISOString?.().slice(11, 16) ?? "";
56248
57789
  const device = m.device_name ? `@${m.device_name} ` : "";
56249
57790
  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}`;
57791
+ const truncated = m.preview.length > maxPreview;
57792
+ const text = truncated ? m.preview.slice(0, maxPreview) : m.preview;
57793
+ return `- [${dt} ${platformTag}${m.role}] ${text}${truncated ? "\u2026" : ""}`;
56252
57794
  }
56253
57795
 
56254
57796
  // src/tools/memory_startup.ts
@@ -56300,16 +57842,16 @@ agent_platform_filter=false.`,
56300
57842
  }
56301
57843
 
56302
57844
  // src/auto_save/save_message_tool.ts
56303
- import * as os5 from "node:os";
57845
+ import * as os8 from "node:os";
56304
57846
 
56305
57847
  // src/auto_save/gemini_capture.ts
56306
57848
  import * as fs2 from "node:fs";
56307
57849
  import * as path3 from "node:path";
56308
- import * as os4 from "node:os";
57850
+ import * as os5 from "node:os";
56309
57851
  import * as crypto2 from "node:crypto";
56310
57852
  var CLIENT_PLATFORM = "gemini-cli-mcp-client";
56311
- var DEVICE_NAME2 = os4.hostname();
56312
- var GEMINI_TMP_ROOT = path3.join(os4.homedir(), ".gemini", "tmp");
57853
+ var DEVICE_NAME2 = os5.hostname();
57854
+ var GEMINI_TMP_ROOT = path3.join(os5.homedir(), ".gemini", "tmp");
56313
57855
  var FLUSH_DEBOUNCE_MS = 200;
56314
57856
  var PARSE_RETRY_MS = 80;
56315
57857
  var POLL_INTERVAL_MS = 3e3;
@@ -56686,8 +58228,593 @@ async function captureSessionEnd() {
56686
58228
  }
56687
58229
  }
56688
58230
 
58231
+ // src/auto_save/grok_capture.ts
58232
+ import * as fs3 from "node:fs";
58233
+ import * as path4 from "node:path";
58234
+ import * as os6 from "node:os";
58235
+ var DEVICE_NAME3 = os6.hostname();
58236
+ var SESSIONS_ROOT = path4.join(os6.homedir(), ".grok", "sessions");
58237
+ var CHAT_FILE = "chat_history.jsonl";
58238
+ var AGENT_PLATFORM = "grok-cli";
58239
+ var FLUSH_DEBOUNCE_MS2 = 200;
58240
+ var POLL_INTERVAL_MS2 = 3e3;
58241
+ var _state2 = null;
58242
+ var SERVER_START_MS = Date.now();
58243
+ var _watcher = null;
58244
+ var _pollTimer2 = null;
58245
+ var _flushInProgress2 = false;
58246
+ var _flushPending2 = false;
58247
+ var _flushDebounceTimer2 = null;
58248
+ function extractSessionId(filePath) {
58249
+ return path4.basename(path4.dirname(filePath));
58250
+ }
58251
+ function countCompleteLines(filePath) {
58252
+ try {
58253
+ const content = fs3.readFileSync(filePath, "utf-8");
58254
+ let n = 0;
58255
+ for (let i = 0; i < content.length; i++) if (content[i] === "\n") n++;
58256
+ return n;
58257
+ } catch {
58258
+ return 0;
58259
+ }
58260
+ }
58261
+ function* walkChatHistories(root) {
58262
+ if (!fs3.existsSync(root)) return;
58263
+ const stack = [root];
58264
+ while (stack.length) {
58265
+ const dir = stack.pop();
58266
+ let entries;
58267
+ try {
58268
+ entries = fs3.readdirSync(dir, { withFileTypes: true });
58269
+ } catch {
58270
+ continue;
58271
+ }
58272
+ for (const e of entries) {
58273
+ const p = path4.join(dir, e.name);
58274
+ if (e.isDirectory()) {
58275
+ stack.push(p);
58276
+ } else if (e.isFile() && e.name === CHAT_FILE) {
58277
+ yield p;
58278
+ }
58279
+ }
58280
+ }
58281
+ }
58282
+ function captureSessionStart2(_cwd) {
58283
+ const rootExists = fs3.existsSync(SESSIONS_ROOT);
58284
+ _state2 = {
58285
+ rootExists,
58286
+ files: /* @__PURE__ */ new Map()
58287
+ };
58288
+ if (!rootExists) {
58289
+ return;
58290
+ }
58291
+ let count = 0;
58292
+ for (const filePath of walkChatHistories(SESSIONS_ROOT)) {
58293
+ let stat;
58294
+ try {
58295
+ stat = fs3.statSync(filePath);
58296
+ } catch {
58297
+ continue;
58298
+ }
58299
+ _state2.files.set(filePath, {
58300
+ cursorLines: countCompleteLines(filePath),
58301
+ lastSize: stat.size,
58302
+ sessionId: extractSessionId(filePath)
58303
+ });
58304
+ count++;
58305
+ }
58306
+ console.error(`\u{1F4DD} [Grok] capture armed: ${count} chat_history(s)`);
58307
+ armDirWatcher();
58308
+ }
58309
+ function extractText(content) {
58310
+ if (typeof content === "string") return content;
58311
+ if (Array.isArray(content)) {
58312
+ return content.filter((b) => b && typeof b === "object" && b.type === "text" && typeof b.text === "string").map((b) => b.text).join("\n");
58313
+ }
58314
+ return "";
58315
+ }
58316
+ function extractUserQuery(text) {
58317
+ const m = text.match(/<user_query>([\s\S]*?)<\/user_query>/);
58318
+ return m ? m[1].trim() : null;
58319
+ }
58320
+ function parseEntry(line, lineIndex) {
58321
+ let entry;
58322
+ try {
58323
+ entry = JSON.parse(line);
58324
+ } catch {
58325
+ return null;
58326
+ }
58327
+ const type = entry.type;
58328
+ if (type === "user") {
58329
+ if (entry.synthetic_reason) return null;
58330
+ const text = extractText(entry.content);
58331
+ const query = extractUserQuery(text);
58332
+ if (!query) return null;
58333
+ return { lineIndex, role: "user", message: query, agentModel: null };
58334
+ }
58335
+ if (type === "assistant") {
58336
+ const message = extractText(entry.content).trim();
58337
+ if (!message) return null;
58338
+ const model = typeof entry.model_id === "string" && entry.model_id ? entry.model_id : "unknown";
58339
+ return { lineIndex, role: "assistant", message, agentModel: model };
58340
+ }
58341
+ return null;
58342
+ }
58343
+ async function flushDeltaForFile2(filePath, fileState) {
58344
+ let stat;
58345
+ try {
58346
+ stat = fs3.statSync(filePath);
58347
+ } catch {
58348
+ return { inserted: 0, skipped: 0, dedup: 0 };
58349
+ }
58350
+ if (stat.size === fileState.lastSize) return { inserted: 0, skipped: 0, dedup: 0 };
58351
+ let content;
58352
+ try {
58353
+ content = fs3.readFileSync(filePath, "utf-8");
58354
+ } catch {
58355
+ return { inserted: 0, skipped: 0, dedup: 0 };
58356
+ }
58357
+ fileState.lastSize = stat.size;
58358
+ const completeLines = content.split("\n").slice(0, -1);
58359
+ const total = completeLines.length;
58360
+ if (total <= fileState.cursorLines) return { inserted: 0, skipped: 0, dedup: 0 };
58361
+ const userId = await getDefaultUserId();
58362
+ let inserted = 0, skipped = 0, dedup = 0;
58363
+ for (let i = fileState.cursorLines; i < total; i++) {
58364
+ const line = completeLines[i].trim();
58365
+ if (!line) continue;
58366
+ const parsed = parseEntry(line, i);
58367
+ if (!parsed) {
58368
+ skipped++;
58369
+ continue;
58370
+ }
58371
+ const externalUuid = `grok:${fileState.sessionId}:${parsed.lineIndex}`;
58372
+ try {
58373
+ const result = await insertRawMemory({
58374
+ user_id: userId,
58375
+ agent_platform: AGENT_PLATFORM,
58376
+ agent_model: parsed.agentModel,
58377
+ role: parsed.role,
58378
+ message: parsed.message,
58379
+ external_uuid: externalUuid,
58380
+ device_name: DEVICE_NAME3
58381
+ });
58382
+ if (result.inserted) inserted++;
58383
+ else dedup++;
58384
+ } catch (err2) {
58385
+ console.error(`\u26A0\uFE0F [Grok] insert failed at line ${i}:`, err2);
58386
+ skipped++;
58387
+ }
58388
+ }
58389
+ fileState.cursorLines = total;
58390
+ return { inserted, skipped, dedup };
58391
+ }
58392
+ async function flushAllFiles2() {
58393
+ if (!_state2 || !_state2.rootExists) return { inserted: 0, skipped: 0, dedup: 0 };
58394
+ let totalI = 0, totalS = 0, totalD = 0;
58395
+ for (const filePath of walkChatHistories(SESSIONS_ROOT)) {
58396
+ let fileState = _state2.files.get(filePath);
58397
+ if (!fileState) {
58398
+ let stat = null;
58399
+ try {
58400
+ stat = fs3.statSync(filePath);
58401
+ } catch {
58402
+ }
58403
+ const fileBirthMs = stat ? stat.birthtimeMs || stat.ctimeMs : SERVER_START_MS;
58404
+ const isPreExisting = fileBirthMs < SERVER_START_MS - 5e3;
58405
+ const initialCursor = isPreExisting ? countCompleteLines(filePath) : 0;
58406
+ const initialSize = isPreExisting ? stat?.size ?? 0 : 0;
58407
+ if (isPreExisting) {
58408
+ console.error(`\u{1F4DD} [Grok] pre-existing session skipped (lines=${initialCursor}): ${extractSessionId(filePath)}`);
58409
+ } else {
58410
+ console.error(`\u{1F4DD} [Grok] new session detected: ${extractSessionId(filePath)}`);
58411
+ }
58412
+ fileState = {
58413
+ cursorLines: initialCursor,
58414
+ lastSize: initialSize,
58415
+ sessionId: extractSessionId(filePath)
58416
+ };
58417
+ _state2.files.set(filePath, fileState);
58418
+ }
58419
+ const r = await flushDeltaForFile2(filePath, fileState);
58420
+ totalI += r.inserted;
58421
+ totalS += r.skipped;
58422
+ totalD += r.dedup;
58423
+ }
58424
+ return { inserted: totalI, skipped: totalS, dedup: totalD };
58425
+ }
58426
+ async function flushWithMutex2() {
58427
+ if (_flushInProgress2) {
58428
+ _flushPending2 = true;
58429
+ return;
58430
+ }
58431
+ _flushInProgress2 = true;
58432
+ try {
58433
+ const r = await flushAllFiles2();
58434
+ if (r.inserted > 0 || r.skipped > 0 || r.dedup > 0) {
58435
+ console.error(`\u{1F4DD} [Grok] live flush: inserted=${r.inserted}, dedup=${r.dedup}, skipped=${r.skipped}`);
58436
+ }
58437
+ } catch (err2) {
58438
+ console.error("\u26A0\uFE0F [Grok] live flush error:", err2);
58439
+ } finally {
58440
+ _flushInProgress2 = false;
58441
+ if (_flushPending2) {
58442
+ _flushPending2 = false;
58443
+ setImmediate(() => {
58444
+ void flushWithMutex2();
58445
+ });
58446
+ }
58447
+ }
58448
+ }
58449
+ function scheduleFlush2() {
58450
+ if (_flushDebounceTimer2) clearTimeout(_flushDebounceTimer2);
58451
+ _flushDebounceTimer2 = setTimeout(() => {
58452
+ _flushDebounceTimer2 = null;
58453
+ void flushWithMutex2();
58454
+ }, FLUSH_DEBOUNCE_MS2);
58455
+ }
58456
+ function armDirWatcher() {
58457
+ if (!_state2 || !_state2.rootExists) return;
58458
+ if (_watcher) return;
58459
+ try {
58460
+ _watcher = fs3.watch(SESSIONS_ROOT, { recursive: true }, (_evt, filename) => {
58461
+ if (!filename) return;
58462
+ if (path4.basename(filename) !== CHAT_FILE) return;
58463
+ scheduleFlush2();
58464
+ });
58465
+ console.error(`\u{1F4DD} [Grok] dir watcher armed (recursive)`);
58466
+ } catch (err2) {
58467
+ console.error("\u26A0\uFE0F [Grok] fs.watch failed \u2014 polling only:", err2);
58468
+ }
58469
+ _pollTimer2 = setInterval(() => {
58470
+ void flushWithMutex2();
58471
+ }, POLL_INTERVAL_MS2);
58472
+ }
58473
+ function disarmDirWatcher() {
58474
+ if (_flushDebounceTimer2) {
58475
+ clearTimeout(_flushDebounceTimer2);
58476
+ _flushDebounceTimer2 = null;
58477
+ }
58478
+ if (_pollTimer2) {
58479
+ clearInterval(_pollTimer2);
58480
+ _pollTimer2 = null;
58481
+ }
58482
+ if (_watcher) {
58483
+ try {
58484
+ _watcher.close();
58485
+ } catch {
58486
+ }
58487
+ _watcher = null;
58488
+ }
58489
+ }
58490
+ async function captureSessionEnd2() {
58491
+ disarmDirWatcher();
58492
+ const waitStart = Date.now();
58493
+ while (_flushInProgress2 && Date.now() - waitStart < 2e3) {
58494
+ await new Promise((r) => setTimeout(r, 50));
58495
+ }
58496
+ if (!_state2 || !_state2.rootExists) {
58497
+ return { inserted: 0, skipped: 0, error: "session not armed" };
58498
+ }
58499
+ _flushInProgress2 = true;
58500
+ try {
58501
+ const r = await flushAllFiles2();
58502
+ if (r.inserted > 0 || r.skipped > 0 || r.dedup > 0) {
58503
+ console.error(`\u{1F4DD} [Grok] final flush: inserted=${r.inserted}, dedup=${r.dedup}, skipped=${r.skipped}`);
58504
+ }
58505
+ return { inserted: r.inserted, skipped: r.skipped };
58506
+ } finally {
58507
+ _flushInProgress2 = false;
58508
+ }
58509
+ }
58510
+ function isCaptureArmed2() {
58511
+ return _state2 !== null && _state2.rootExists;
58512
+ }
58513
+
58514
+ // src/auto_save/antigravity_capture.ts
58515
+ import * as fs4 from "node:fs";
58516
+ import * as path5 from "node:path";
58517
+ import * as os7 from "node:os";
58518
+ var DEVICE_NAME4 = os7.hostname();
58519
+ var GEMINI_ROOT = path5.join(os7.homedir(), ".gemini");
58520
+ var CHAT_FILE2 = "transcript_full.jsonl";
58521
+ var FLUSH_DEBOUNCE_MS3 = 200;
58522
+ var POLL_INTERVAL_MS3 = 3e3;
58523
+ var ROOTS = [
58524
+ { dir: path5.join(GEMINI_ROOT, "antigravity", "brain"), platform: "antigravity" },
58525
+ { dir: path5.join(GEMINI_ROOT, "antigravity-cli", "brain"), platform: "antigravity-cli" },
58526
+ { dir: path5.join(GEMINI_ROOT, "antigravity-ide", "brain"), platform: "antigravity-ide" }
58527
+ ];
58528
+ var _state3 = null;
58529
+ var SERVER_START_MS2 = Date.now();
58530
+ var _watchers2 = [];
58531
+ var _pollTimer3 = null;
58532
+ var _flushInProgress3 = false;
58533
+ var _flushPending3 = false;
58534
+ var _flushDebounceTimer3 = null;
58535
+ function extractSid(filePath) {
58536
+ return path5.basename(path5.dirname(path5.dirname(path5.dirname(filePath))));
58537
+ }
58538
+ function countCompleteLines2(filePath) {
58539
+ try {
58540
+ const content = fs4.readFileSync(filePath, "utf-8");
58541
+ let n = 0;
58542
+ for (let i = 0; i < content.length; i++) if (content[i] === "\n") n++;
58543
+ return n;
58544
+ } catch {
58545
+ return 0;
58546
+ }
58547
+ }
58548
+ function* walkTranscripts(root) {
58549
+ if (!fs4.existsSync(root)) return;
58550
+ const stack = [root];
58551
+ while (stack.length) {
58552
+ const dir = stack.pop();
58553
+ let entries;
58554
+ try {
58555
+ entries = fs4.readdirSync(dir, { withFileTypes: true });
58556
+ } catch {
58557
+ continue;
58558
+ }
58559
+ for (const e of entries) {
58560
+ const p = path5.join(dir, e.name);
58561
+ if (e.isDirectory()) {
58562
+ stack.push(p);
58563
+ } else if (e.isFile() && e.name === CHAT_FILE2) {
58564
+ yield p;
58565
+ }
58566
+ }
58567
+ }
58568
+ }
58569
+ function* walkAllTranscripts() {
58570
+ for (const { dir, platform } of ROOTS) {
58571
+ for (const filePath of walkTranscripts(dir)) {
58572
+ yield { filePath, platform };
58573
+ }
58574
+ }
58575
+ }
58576
+ function captureSessionStart3(_cwd) {
58577
+ const rootExists = ROOTS.some((r) => fs4.existsSync(r.dir));
58578
+ _state3 = {
58579
+ rootExists,
58580
+ files: /* @__PURE__ */ new Map()
58581
+ };
58582
+ if (!rootExists) {
58583
+ return;
58584
+ }
58585
+ let count = 0;
58586
+ for (const { filePath, platform } of walkAllTranscripts()) {
58587
+ let stat;
58588
+ try {
58589
+ stat = fs4.statSync(filePath);
58590
+ } catch {
58591
+ continue;
58592
+ }
58593
+ _state3.files.set(filePath, {
58594
+ cursorLines: countCompleteLines2(filePath),
58595
+ lastSize: stat.size,
58596
+ sessionId: extractSid(filePath),
58597
+ agentPlatform: platform,
58598
+ currentModel: null
58599
+ });
58600
+ count++;
58601
+ }
58602
+ const liveRoots = ROOTS.filter((r) => fs4.existsSync(r.dir)).map((r) => r.platform);
58603
+ console.error(`\u{1F4DD} [Antigravity] capture armed: ${count} transcript(s) across [${liveRoots.join(", ")}]`);
58604
+ armDirWatcher2();
58605
+ }
58606
+ function extractUserRequest(text) {
58607
+ const m = text.match(/<USER_REQUEST>([\s\S]*?)<\/USER_REQUEST>/);
58608
+ return m ? m[1].trim() : null;
58609
+ }
58610
+ function extractModelSelection(text) {
58611
+ const m = text.match(/Model Selection[^\n]*?\bto\s+(.+?)\.(?:\s|$)/);
58612
+ return m ? m[1].trim() : null;
58613
+ }
58614
+ function parseEntry2(line) {
58615
+ let entry;
58616
+ try {
58617
+ entry = JSON.parse(line);
58618
+ } catch {
58619
+ return null;
58620
+ }
58621
+ const type = entry.type;
58622
+ const stepIndex = typeof entry.step_index === "number" ? entry.step_index : -1;
58623
+ if (stepIndex < 0) return null;
58624
+ if (type === "USER_INPUT") {
58625
+ const raw = typeof entry.content === "string" ? entry.content : "";
58626
+ const query = extractUserRequest(raw);
58627
+ if (!query) return null;
58628
+ return { stepIndex, role: "user", message: query, modelHint: extractModelSelection(raw) };
58629
+ }
58630
+ if (type === "PLANNER_RESPONSE") {
58631
+ const message = (entry.content || "").toString().trim();
58632
+ if (!message) return null;
58633
+ return { stepIndex, role: "assistant", message, modelHint: null };
58634
+ }
58635
+ return null;
58636
+ }
58637
+ async function flushDeltaForFile3(filePath, fileState) {
58638
+ let stat;
58639
+ try {
58640
+ stat = fs4.statSync(filePath);
58641
+ } catch {
58642
+ return { inserted: 0, skipped: 0, dedup: 0 };
58643
+ }
58644
+ if (stat.size === fileState.lastSize) return { inserted: 0, skipped: 0, dedup: 0 };
58645
+ let content;
58646
+ try {
58647
+ content = fs4.readFileSync(filePath, "utf-8");
58648
+ } catch {
58649
+ return { inserted: 0, skipped: 0, dedup: 0 };
58650
+ }
58651
+ fileState.lastSize = stat.size;
58652
+ const completeLines = content.split("\n").slice(0, -1);
58653
+ const total = completeLines.length;
58654
+ if (total <= fileState.cursorLines) return { inserted: 0, skipped: 0, dedup: 0 };
58655
+ const userId = await getDefaultUserId();
58656
+ let inserted = 0, skipped = 0, dedup = 0;
58657
+ for (let i = fileState.cursorLines; i < total; i++) {
58658
+ const line = completeLines[i].trim();
58659
+ if (!line) continue;
58660
+ const parsed = parseEntry2(line);
58661
+ if (!parsed) {
58662
+ skipped++;
58663
+ continue;
58664
+ }
58665
+ if (parsed.modelHint) fileState.currentModel = parsed.modelHint;
58666
+ const agentModel = parsed.role === "user" ? null : fileState.currentModel ?? "unknown";
58667
+ const externalUuid = `antigravity:${fileState.sessionId}:${parsed.stepIndex}`;
58668
+ try {
58669
+ const result = await insertRawMemory({
58670
+ user_id: userId,
58671
+ agent_platform: fileState.agentPlatform,
58672
+ agent_model: agentModel,
58673
+ role: parsed.role,
58674
+ message: parsed.message,
58675
+ external_uuid: externalUuid,
58676
+ device_name: DEVICE_NAME4
58677
+ });
58678
+ if (result.inserted) inserted++;
58679
+ else dedup++;
58680
+ } catch (err2) {
58681
+ console.error(`\u26A0\uFE0F [Antigravity] insert failed at step ${parsed.stepIndex}:`, err2);
58682
+ skipped++;
58683
+ }
58684
+ }
58685
+ fileState.cursorLines = total;
58686
+ return { inserted, skipped, dedup };
58687
+ }
58688
+ async function flushAllFiles3() {
58689
+ if (!_state3 || !_state3.rootExists) return { inserted: 0, skipped: 0, dedup: 0 };
58690
+ let totalI = 0, totalS = 0, totalD = 0;
58691
+ for (const { filePath, platform } of walkAllTranscripts()) {
58692
+ let fileState = _state3.files.get(filePath);
58693
+ if (!fileState) {
58694
+ let stat = null;
58695
+ try {
58696
+ stat = fs4.statSync(filePath);
58697
+ } catch {
58698
+ }
58699
+ const fileBirthMs = stat ? stat.birthtimeMs || stat.ctimeMs : SERVER_START_MS2;
58700
+ const isPreExisting = fileBirthMs < SERVER_START_MS2 - 5e3;
58701
+ const initialCursor = isPreExisting ? countCompleteLines2(filePath) : 0;
58702
+ const initialSize = isPreExisting ? stat?.size ?? 0 : 0;
58703
+ if (isPreExisting) {
58704
+ console.error(`\u{1F4DD} [Antigravity] pre-existing session skipped (lines=${initialCursor}): ${platform}/${extractSid(filePath)}`);
58705
+ } else {
58706
+ console.error(`\u{1F4DD} [Antigravity] new session detected: ${platform}/${extractSid(filePath)}`);
58707
+ }
58708
+ fileState = {
58709
+ cursorLines: initialCursor,
58710
+ lastSize: initialSize,
58711
+ sessionId: extractSid(filePath),
58712
+ agentPlatform: platform,
58713
+ currentModel: null
58714
+ };
58715
+ _state3.files.set(filePath, fileState);
58716
+ }
58717
+ const r = await flushDeltaForFile3(filePath, fileState);
58718
+ totalI += r.inserted;
58719
+ totalS += r.skipped;
58720
+ totalD += r.dedup;
58721
+ }
58722
+ return { inserted: totalI, skipped: totalS, dedup: totalD };
58723
+ }
58724
+ async function flushWithMutex3() {
58725
+ if (_flushInProgress3) {
58726
+ _flushPending3 = true;
58727
+ return;
58728
+ }
58729
+ _flushInProgress3 = true;
58730
+ try {
58731
+ const r = await flushAllFiles3();
58732
+ if (r.inserted > 0 || r.skipped > 0 || r.dedup > 0) {
58733
+ console.error(`\u{1F4DD} [Antigravity] live flush: inserted=${r.inserted}, dedup=${r.dedup}, skipped=${r.skipped}`);
58734
+ }
58735
+ } catch (err2) {
58736
+ console.error("\u26A0\uFE0F [Antigravity] live flush error:", err2);
58737
+ } finally {
58738
+ _flushInProgress3 = false;
58739
+ if (_flushPending3) {
58740
+ _flushPending3 = false;
58741
+ setImmediate(() => {
58742
+ void flushWithMutex3();
58743
+ });
58744
+ }
58745
+ }
58746
+ }
58747
+ function scheduleFlush3() {
58748
+ if (_flushDebounceTimer3) clearTimeout(_flushDebounceTimer3);
58749
+ _flushDebounceTimer3 = setTimeout(() => {
58750
+ _flushDebounceTimer3 = null;
58751
+ void flushWithMutex3();
58752
+ }, FLUSH_DEBOUNCE_MS3);
58753
+ }
58754
+ function armDirWatcher2() {
58755
+ if (!_state3 || !_state3.rootExists) return;
58756
+ if (_watchers2.length) return;
58757
+ for (const { dir } of ROOTS) {
58758
+ if (!fs4.existsSync(dir)) continue;
58759
+ try {
58760
+ const w = fs4.watch(dir, { recursive: true }, (_evt, filename) => {
58761
+ if (!filename) return;
58762
+ if (path5.basename(filename) !== CHAT_FILE2) return;
58763
+ scheduleFlush3();
58764
+ });
58765
+ _watchers2.push(w);
58766
+ } catch (err2) {
58767
+ console.error(`\u26A0\uFE0F [Antigravity] fs.watch failed for ${dir} \u2014 polling only:`, err2);
58768
+ }
58769
+ }
58770
+ console.error(`\u{1F4DD} [Antigravity] dir watchers armed: ${_watchers2.length} root(s)`);
58771
+ _pollTimer3 = setInterval(() => {
58772
+ void flushWithMutex3();
58773
+ }, POLL_INTERVAL_MS3);
58774
+ }
58775
+ function disarmDirWatcher2() {
58776
+ if (_flushDebounceTimer3) {
58777
+ clearTimeout(_flushDebounceTimer3);
58778
+ _flushDebounceTimer3 = null;
58779
+ }
58780
+ if (_pollTimer3) {
58781
+ clearInterval(_pollTimer3);
58782
+ _pollTimer3 = null;
58783
+ }
58784
+ for (const w of _watchers2) {
58785
+ try {
58786
+ w.close();
58787
+ } catch {
58788
+ }
58789
+ }
58790
+ _watchers2 = [];
58791
+ }
58792
+ async function captureSessionEnd3() {
58793
+ disarmDirWatcher2();
58794
+ const waitStart = Date.now();
58795
+ while (_flushInProgress3 && Date.now() - waitStart < 2e3) {
58796
+ await new Promise((r) => setTimeout(r, 50));
58797
+ }
58798
+ if (!_state3 || !_state3.rootExists) {
58799
+ return { inserted: 0, skipped: 0, error: "session not armed" };
58800
+ }
58801
+ _flushInProgress3 = true;
58802
+ try {
58803
+ const r = await flushAllFiles3();
58804
+ if (r.inserted > 0 || r.skipped > 0 || r.dedup > 0) {
58805
+ console.error(`\u{1F4DD} [Antigravity] final flush: inserted=${r.inserted}, dedup=${r.dedup}, skipped=${r.skipped}`);
58806
+ }
58807
+ return { inserted: r.inserted, skipped: r.skipped };
58808
+ } finally {
58809
+ _flushInProgress3 = false;
58810
+ }
58811
+ }
58812
+ function isCaptureArmed3() {
58813
+ return _state3 !== null && _state3.rootExists;
58814
+ }
58815
+
56689
58816
  // src/auto_save/save_message_tool.ts
56690
- var DEVICE_NAME3 = os5.hostname();
58817
+ var DEVICE_NAME5 = os8.hostname();
56691
58818
  function registerSaveMessage(server) {
56692
58819
  server.registerTool(
56693
58820
  "save_message",
@@ -56728,6 +58855,22 @@ subagent \uCEE8\uD14D\uC2A4\uD2B8\uB77C\uBA74 subagent=true + subagent_model + s
56728
58855
  }]
56729
58856
  };
56730
58857
  }
58858
+ if (id.agent_platform?.startsWith("grok") && isCaptureArmed2()) {
58859
+ return {
58860
+ content: [{
58861
+ type: "text",
58862
+ text: JSON.stringify({ stored: false, skipped: "passive capture active" }, null, 2)
58863
+ }]
58864
+ };
58865
+ }
58866
+ if (id.agent_platform?.startsWith("antigravity") && isCaptureArmed3()) {
58867
+ return {
58868
+ content: [{
58869
+ type: "text",
58870
+ text: JSON.stringify({ stored: false, skipped: "passive capture active" }, null, 2)
58871
+ }]
58872
+ };
58873
+ }
56731
58874
  const agentModel = args.role === "user" ? null : id.agent_model;
56732
58875
  const inserted = await insertRawMemory({
56733
58876
  user_id: userId,
@@ -56738,7 +58881,7 @@ subagent \uCEE8\uD14D\uC2A4\uD2B8\uB77C\uBA74 subagent=true + subagent_model + s
56738
58881
  subagent_role: id.subagent_role,
56739
58882
  role: args.role,
56740
58883
  message: args.message,
56741
- device_name: DEVICE_NAME3
58884
+ device_name: DEVICE_NAME5
56742
58885
  // tag/embed NULL — Cold Path가 background 처리 (Hot Path latency 보장)
56743
58886
  });
56744
58887
  return {
@@ -56760,6 +58903,7 @@ subagent \uCEE8\uD14D\uC2A4\uD2B8\uB77C\uBA74 subagent=true + subagent_model + s
56760
58903
  function registerTools(server) {
56761
58904
  registerManageKnowledge(server);
56762
58905
  registerSearchMemory(server);
58906
+ registerManageProjectTags(server);
56763
58907
  registerMemoryStartup(server);
56764
58908
  registerSaveMessage(server);
56765
58909
  }
@@ -56769,7 +58913,7 @@ init_db();
56769
58913
 
56770
58914
  // src/cold_path/dtag_promoter.ts
56771
58915
  init_db();
56772
- function envInt(name, fallback) {
58916
+ function envInt2(name, fallback) {
56773
58917
  const raw = process.env[name];
56774
58918
  if (!raw) return fallback;
56775
58919
  const n = Number(raw);
@@ -56803,6 +58947,7 @@ ${tagList}`;
56803
58947
  system: CLUSTER_SYSTEM,
56804
58948
  user: userPrompt,
56805
58949
  responseFormat: "json",
58950
+ enableThinking: false,
56806
58951
  maxTokens: 512
56807
58952
  });
56808
58953
  } catch (err2) {
@@ -56826,8 +58971,8 @@ ${tagList}`;
56826
58971
  });
56827
58972
  }
56828
58973
  async function runDtagPromotion() {
56829
- const minCount = envInt("DTAG_PROMOTE_MIN_COUNT", 10);
56830
- const windowDays = envInt("DTAG_PROMOTE_WINDOW_DAYS", 30);
58974
+ const minCount = envInt2("DTAG_PROMOTE_MIN_COUNT", 10);
58975
+ const windowDays = envInt2("DTAG_PROMOTE_WINDOW_DAYS", 30);
56831
58976
  const userId = await getDefaultUserId();
56832
58977
  const freqResult = await db.query(
56833
58978
  `SELECT unnest(d_tag) AS tag, COUNT(*)::int AS cnt
@@ -56894,6 +59039,246 @@ async function runDtagPromotion() {
56894
59039
  return summary;
56895
59040
  }
56896
59041
 
59042
+ // src/librarian.ts
59043
+ init_db();
59044
+ var LIBRARIAN_MSG_THRESHOLD = Number(process.env.LIBRARIAN_MSG_THRESHOLD ?? 30);
59045
+ var LIBRARIAN_COOLDOWN_MS = Number(process.env.LIBRARIAN_COOLDOWN_HOURS ?? 24) * 60 * 60 * 1e3;
59046
+ var LIBRARIAN_RECENT_SLICE = Number(process.env.LIBRARIAN_RECENT_SLICE ?? 25);
59047
+ var LIBRARIAN_HISTORY_SLICE = Number(process.env.LIBRARIAN_HISTORY_SLICE ?? 25);
59048
+ var LIBRARIAN_MAX_TOKENS = Number(process.env.LIBRARIAN_MAX_TOKENS ?? 2048);
59049
+ var LIBRARIAN_PROFILE_SCHEMA = {
59050
+ type: "object",
59051
+ properties: {
59052
+ core_profile: { type: ["string", "null"] },
59053
+ sub_profile: { type: ["string", "null"] }
59054
+ },
59055
+ required: ["core_profile", "sub_profile"],
59056
+ additionalProperties: false
59057
+ };
59058
+ var SYSTEM_PROMPT2 = `You are the Librarian for one user's personal memory system.
59059
+
59060
+ YOUR JOB
59061
+ Look at the user's first-person messages (role='user' only \u2014 ignore assistant
59062
+ replies). Identify any STABLE, DURABLE facts about WHO THE USER IS that should
59063
+ be promoted to their long-term profile.
59064
+
59065
+ OUTPUT TWO SECTIONS \u2014 these are STRICTLY SEPARATE categories:
59066
+
59067
+ 1. core_profile \u2014 DURABLE IDENTITY ONLY.
59068
+ Who the person IS: name, role, profession, expertise, stable long-term
59069
+ preferences. Should be SHORT (5-10 lines max) and high-signal.
59070
+ Example of the SHAPE only (fictional \u2014 never copy this content): "Backend
59071
+ engineer at a logistics startup; 10+ yrs Python; prefers terse,
59072
+ example-driven answers." Derive the actual content ONLY from the messages.
59073
+
59074
+ 2. sub_profile \u2014 CURRENT WORK AND ACTIVITY.
59075
+ What they are actively doing, building, or focused on: tools, environment
59076
+ details, ongoing projects, recent working style observations. Expected to
59077
+ change often. Can be longer but still curated.
59078
+
59079
+ CRITICAL IDENTITY vs. WORK DISTINCTION
59080
+ A user discussing, building, evaluating, debugging, or working on a topic \u2014
59081
+ including AI models, agent frameworks, or this memory system itself \u2014 is
59082
+ describing their WORK or CURRENT ACTIVITY, NOT their identity. A burst of
59083
+ messages about one subject means they are WORKING on it, not that it defines
59084
+ them. NEVER promote a work or project topic into core_profile. It belongs in
59085
+ sub_profile at most.
59086
+
59087
+ CONSERVATISM / NULL-PRESERVE RULE (most important rule)
59088
+ If the recent window contains NO new durable identity fact \u2014 only project work,
59089
+ meta-tooling, topic evaluation, or session-specific activity \u2014 return
59090
+ core_profile: null to PRESERVE the existing identity unchanged. DO NOT restate,
59091
+ rephrase, or "refresh" an existing core_profile just because you saw it. Null
59092
+ means "keep it as-is." Only set a non-null core_profile when there is an
59093
+ explicit, durable, first-person identity statement that is genuinely new.
59094
+ sub_profile may freely capture current projects and activity.
59095
+
59096
+ OTHER RULES
59097
+ - DO NOT invent facts not supported by the messages.
59098
+ - DO NOT promote third-party advice or system hints \u2014 only what the user is
59099
+ saying ABOUT THEMSELVES.
59100
+ - Korean is fine. Match the language of the user's writing.
59101
+
59102
+ FORMAT RULES FOR THE VALUES:
59103
+ - Both fields must be PLAIN PROSE TEXT \u2014 no nested JSON, no {}, [], key-value blobs.
59104
+ Write in sentences or short bullet lines, not serialized objects.
59105
+ - If you're tempted to write {"key": "value"} inside the string, write prose instead.
59106
+
59107
+ OUTPUT JSON STRICTLY:
59108
+ {
59109
+ "core_profile": "<concise high-signal prose or null>",
59110
+ "sub_profile": "<longer secondary prose or null>"
59111
+ }`;
59112
+ var librarianRunning = false;
59113
+ async function checkGate() {
59114
+ const userId = await getDefaultUserId();
59115
+ const r = await db.query(
59116
+ `SELECT
59117
+ u.librarian_last_run_at,
59118
+ u.librarian_msg_count_at_run,
59119
+ (SELECT COUNT(*)::bigint
59120
+ FROM memory
59121
+ WHERE user_id = u.user_id
59122
+ AND role = 'user'
59123
+ AND is_active = TRUE) AS current_msg_count
59124
+ FROM users u
59125
+ WHERE u.user_id = $1`,
59126
+ [userId]
59127
+ );
59128
+ const row = r.rows[0];
59129
+ const lastRunAt = row?.librarian_last_run_at ?? null;
59130
+ const msgCountAtRun = Number(row?.librarian_msg_count_at_run ?? 0);
59131
+ const currentMsgCount = Number(row?.current_msg_count ?? 0);
59132
+ const neverRan = lastRunAt === null;
59133
+ const cooldownPassed = lastRunAt !== null && Date.now() - new Date(lastRunAt).getTime() >= LIBRARIAN_COOLDOWN_MS;
59134
+ const enoughNewMessages = currentMsgCount - msgCountAtRun >= LIBRARIAN_MSG_THRESHOLD;
59135
+ const shouldRun = enoughNewMessages && (neverRan || cooldownPassed);
59136
+ return { userId, shouldRun, currentMsgCount, lastRunAt };
59137
+ }
59138
+ async function runLibrarianDeepPass(userPrompt, maxTokens) {
59139
+ const analysisSystemPrompt = `${SYSTEM_PROMPT2}
59140
+
59141
+ In this ANALYSIS PASS, think deeply and reason freely about what should go in each profile field.
59142
+ Do NOT output JSON yet \u2014 write your analysis as plain prose reasoning.`;
59143
+ const analysis = await callRole("librarian", {
59144
+ system: analysisSystemPrompt,
59145
+ user: userPrompt,
59146
+ maxTokens,
59147
+ enableThinking: true
59148
+ // jsonSchema 未指定 → thinking ON が有効になる
59149
+ });
59150
+ const pass2User = `${userPrompt}
59151
+
59152
+ ANALYSIS FROM PREVIOUS REASONING PASS (use as additional context):
59153
+ ${analysis}
59154
+
59155
+ Now emit the final JSON profile only.`;
59156
+ return callRole("librarian", {
59157
+ system: SYSTEM_PROMPT2,
59158
+ user: pass2User,
59159
+ maxTokens,
59160
+ jsonSchema: LIBRARIAN_PROFILE_SCHEMA,
59161
+ enableThinking: false
59162
+ });
59163
+ }
59164
+ async function runLibrarian() {
59165
+ if (librarianRunning) return;
59166
+ const gate = await checkGate();
59167
+ if (!gate.shouldRun) return;
59168
+ librarianRunning = true;
59169
+ await db.query(
59170
+ `UPDATE users SET librarian_last_run_at = NOW() WHERE user_id = $1`,
59171
+ [gate.userId]
59172
+ );
59173
+ try {
59174
+ const beforeR = await db.query(
59175
+ `SELECT core_profile, sub_profile FROM users WHERE user_id = $1`,
59176
+ [gate.userId]
59177
+ );
59178
+ const before = beforeR.rows[0] ?? { core_profile: null, sub_profile: null };
59179
+ const recentR = await db.query(
59180
+ `SELECT message, created_at
59181
+ FROM memory
59182
+ WHERE user_id = $1
59183
+ AND role = 'user'
59184
+ AND is_active = TRUE
59185
+ ORDER BY created_at DESC
59186
+ LIMIT $2`,
59187
+ [gate.userId, LIBRARIAN_RECENT_SLICE]
59188
+ );
59189
+ if (recentR.rows.length === 0) return;
59190
+ const historyR = await db.query(
59191
+ `SELECT message, created_at
59192
+ FROM memory
59193
+ WHERE user_id = $1
59194
+ AND role = 'user'
59195
+ AND is_active = TRUE
59196
+ ORDER BY created_at ASC
59197
+ LIMIT $2`,
59198
+ [gate.userId, LIBRARIAN_HISTORY_SLICE]
59199
+ );
59200
+ const recentKeys = new Set(
59201
+ recentR.rows.map(
59202
+ (r) => `${r.created_at?.toISOString() ?? ""}|${r.message}`
59203
+ )
59204
+ );
59205
+ const historicalRows = historyR.rows.filter(
59206
+ (r) => !recentKeys.has(`${r.created_at?.toISOString() ?? ""}|${r.message}`)
59207
+ );
59208
+ const recentSorted = [...recentR.rows].reverse();
59209
+ const historicalSorted = [...historicalRows];
59210
+ const formatRows = (rows, startIdx) => rows.map(
59211
+ (r, i) => `[#${startIdx + i + 1} @ ${r.created_at?.toISOString().slice(0, 19) ?? ""}] ${r.message}`
59212
+ ).join("\n\n");
59213
+ const historicalText = historicalSorted.length > 0 ? `
59214
+ EARLIER MESSAGES \u2014 historical context (identity anchors, NOT current activity):
59215
+ ${formatRows(historicalSorted, 0)}
59216
+ ` : "";
59217
+ const recentText = `
59218
+ RECENT MESSAGES \u2014 most recent ${recentSorted.length} messages:
59219
+ ${formatRows(recentSorted, historicalSorted.length)}
59220
+ `;
59221
+ const userPrompt = `EXISTING PROFILE (preserve unless a new durable identity fact appears):
59222
+ core_profile:
59223
+ ${before.core_profile ?? "(empty)"}
59224
+
59225
+ sub_profile:
59226
+ ${before.sub_profile ?? "(empty)"}
59227
+ ${historicalText}${recentText}
59228
+ Task: produce updated core_profile and sub_profile JSON per the system prompt.`;
59229
+ const deepMode = process.env.LIBRARIAN_DEEP_THINKING === "true";
59230
+ const raw = deepMode ? await runLibrarianDeepPass(userPrompt, LIBRARIAN_MAX_TOKENS) : await callRole("librarian", {
59231
+ system: SYSTEM_PROMPT2,
59232
+ user: userPrompt,
59233
+ maxTokens: LIBRARIAN_MAX_TOKENS,
59234
+ jsonSchema: LIBRARIAN_PROFILE_SCHEMA,
59235
+ enableThinking: false
59236
+ });
59237
+ if (!raw) {
59238
+ throw new Error("Librarian returned empty content (reasoning token budget exceeded?)");
59239
+ }
59240
+ let parsed;
59241
+ try {
59242
+ parsed = JSON.parse(raw);
59243
+ } catch {
59244
+ throw new Error(`Librarian returned unparseable content: ${raw.slice(0, 300)}`);
59245
+ }
59246
+ for (const [field, val] of [["core_profile", parsed.core_profile], ["sub_profile", parsed.sub_profile]]) {
59247
+ if (typeof val === "string") {
59248
+ const t = val.trim();
59249
+ if (t.startsWith("{") || t.startsWith("[")) {
59250
+ throw new Error(`Librarian returned JSON-stuffed ${field} (prose required): ${t.slice(0, 120)}`);
59251
+ }
59252
+ }
59253
+ }
59254
+ const newCore = parsed.core_profile ?? before.core_profile;
59255
+ const newSub = parsed.sub_profile ?? before.sub_profile;
59256
+ const changed = (newCore ?? "") !== (before.core_profile ?? "") || (newSub ?? "") !== (before.sub_profile ?? "");
59257
+ if (changed) {
59258
+ await db.query(
59259
+ `UPDATE users
59260
+ SET core_profile = $1,
59261
+ sub_profile = $2,
59262
+ updated_at = NOW()
59263
+ WHERE user_id = $3`,
59264
+ [newCore, newSub, gate.userId]
59265
+ );
59266
+ }
59267
+ await db.query(
59268
+ `UPDATE users SET librarian_msg_count_at_run = $1 WHERE user_id = $2`,
59269
+ [gate.currentMsgCount, gate.userId]
59270
+ );
59271
+ const totalMsgs = recentSorted.length + historicalSorted.length;
59272
+ console.error(
59273
+ `\u{1F4DA} [Librarian] done \u2014 ${totalMsgs} msgs (${recentSorted.length} recent + ${historicalSorted.length} historical), profile ${changed ? "updated" : "unchanged"}`
59274
+ );
59275
+ } catch (err2) {
59276
+ console.error("\u26A0\uFE0F [Librarian] run failed (retries in 24h):", err2);
59277
+ } finally {
59278
+ librarianRunning = false;
59279
+ }
59280
+ }
59281
+
56897
59282
  // src/cold_path/worker.ts
56898
59283
  var intervalTimer = null;
56899
59284
  var warmupTimer = null;
@@ -56902,7 +59287,7 @@ var tickCount = 0;
56902
59287
  var DEFAULT_INTERVAL_SEC = 60;
56903
59288
  var DEFAULT_BATCH = 5;
56904
59289
  var DEFAULT_WARMUP_SEC = 30;
56905
- function envInt2(name, fallback) {
59290
+ function envInt3(name, fallback) {
56906
59291
  const raw = process.env[name];
56907
59292
  if (!raw) return fallback;
56908
59293
  const n = Number(raw);
@@ -57020,7 +59405,7 @@ async function recordError(client2, rowId, err2) {
57020
59405
  async function tick() {
57021
59406
  if (running) return 0;
57022
59407
  running = true;
57023
- const batchSize = envInt2("COLD_PATH_BATCH_SIZE", DEFAULT_BATCH);
59408
+ const batchSize = envInt3("COLD_PATH_BATCH_SIZE", DEFAULT_BATCH);
57024
59409
  const client2 = await db.getClient();
57025
59410
  try {
57026
59411
  await client2.query("BEGIN");
@@ -57068,21 +59453,78 @@ async function maybeRunPromotion() {
57068
59453
  console.error("\u26A0\uFE0F [DTagPromoter] promotion error (non-blocking):", err2);
57069
59454
  }
57070
59455
  }
57071
- function startColdPathWorker() {
59456
+ async function maybeRunLibrarian() {
59457
+ if (process.env.LIBRARIAN_ENABLED !== "true") return;
59458
+ try {
59459
+ await runLibrarian();
59460
+ } catch (err2) {
59461
+ console.error("\u26A0\uFE0F [Librarian] unhandled error (non-blocking):", err2);
59462
+ }
59463
+ }
59464
+ var COLDPATH_ADVISORY_LOCK_KEY = 4242000017;
59465
+ var lockClient = null;
59466
+ async function acquireColdPathLock() {
59467
+ if (lockClient) return true;
59468
+ const client2 = await db.getClient();
59469
+ try {
59470
+ const res = await client2.query("SELECT pg_try_advisory_lock($1) AS got", [COLDPATH_ADVISORY_LOCK_KEY]);
59471
+ if (res.rows[0]?.got === true) {
59472
+ lockClient = client2;
59473
+ return true;
59474
+ }
59475
+ client2.release();
59476
+ return false;
59477
+ } catch (err2) {
59478
+ try {
59479
+ client2.release();
59480
+ } catch {
59481
+ }
59482
+ throw err2;
59483
+ }
59484
+ }
59485
+ async function releaseColdPathLock() {
59486
+ const c = lockClient;
59487
+ if (!c) return;
59488
+ lockClient = null;
59489
+ try {
59490
+ await c.query("SELECT pg_advisory_unlock($1)", [COLDPATH_ADVISORY_LOCK_KEY]);
59491
+ } catch {
59492
+ }
59493
+ try {
59494
+ c.release();
59495
+ } catch {
59496
+ }
59497
+ }
59498
+ async function startColdPathWorker() {
57072
59499
  if (process.env.COLD_PATH_ENABLED === "false") {
57073
59500
  console.error("\u{1F535} [ColdPath] disabled (COLD_PATH_ENABLED=false)");
57074
59501
  return;
57075
59502
  }
57076
- const intervalSec = envInt2("COLD_PATH_INTERVAL_SEC", DEFAULT_INTERVAL_SEC);
57077
- const warmupSec = envInt2("COLD_PATH_WARMUP_SEC", DEFAULT_WARMUP_SEC);
59503
+ let got = false;
59504
+ try {
59505
+ got = await acquireColdPathLock();
59506
+ } catch (err2) {
59507
+ console.error("\u274C [ColdPath] advisory lock acquire failed \u2014 not starting:", err2);
59508
+ return;
59509
+ }
59510
+ if (!got) {
59511
+ console.error("\u{1F512} [ColdPath] another instance holds the lock \u2014 this process will not process");
59512
+ return;
59513
+ }
59514
+ const intervalSec = envInt3("COLD_PATH_INTERVAL_SEC", DEFAULT_INTERVAL_SEC);
59515
+ const warmupSec = envInt3("COLD_PATH_WARMUP_SEC", DEFAULT_WARMUP_SEC);
57078
59516
  console.error(`\u{1F535} [ColdPath] starting \u2014 warmup ${warmupSec}s, interval ${intervalSec}s`);
57079
59517
  warmupTimer = setTimeout(() => {
57080
59518
  intervalTimer = setInterval(() => {
57081
59519
  tick().catch((err2) => console.error("\u274C [ColdPath] unhandled tick error:", err2));
57082
59520
  maybeRunPromotion().catch((err2) => console.error("\u274C [DTagPromoter] unhandled error:", err2));
59521
+ maybeRunLibrarian().catch((err2) => console.error("\u274C [Librarian] unhandled error:", err2));
59522
+ maybeRunProjectAliasPromoter().catch((err2) => console.error("\u274C [ProjectAliasPromoter] unhandled error:", err2));
57083
59523
  }, intervalSec * 1e3);
57084
59524
  tick().catch((err2) => console.error("\u274C [ColdPath] unhandled first tick:", err2));
57085
59525
  maybeRunPromotion().catch((err2) => console.error("\u274C [DTagPromoter] unhandled error:", err2));
59526
+ maybeRunLibrarian().catch((err2) => console.error("\u274C [Librarian] unhandled error:", err2));
59527
+ maybeRunProjectAliasPromoter().catch((err2) => console.error("\u274C [ProjectAliasPromoter] unhandled error:", err2));
57086
59528
  }, warmupSec * 1e3);
57087
59529
  }
57088
59530
  function stopColdPathWorker() {
@@ -57090,6 +59532,7 @@ function stopColdPathWorker() {
57090
59532
  if (intervalTimer) clearInterval(intervalTimer);
57091
59533
  warmupTimer = null;
57092
59534
  intervalTimer = null;
59535
+ void releaseColdPathLock();
57093
59536
  console.error("\u{1F535} [ColdPath] stopped");
57094
59537
  }
57095
59538
  async function drainColdPath(maxTicks = 12) {
@@ -57111,46 +59554,46 @@ async function drainColdPath(maxTicks = 12) {
57111
59554
  }
57112
59555
 
57113
59556
  // 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";
59557
+ import * as fs5 from "node:fs";
59558
+ import * as path6 from "node:path";
59559
+ import * as os9 from "node:os";
57117
59560
  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;
59561
+ var DEVICE_NAME6 = os9.hostname();
59562
+ var PROJECTS_ROOT = path6.join(os9.homedir(), ".claude", "projects");
59563
+ var FLUSH_DEBOUNCE_MS4 = 200;
59564
+ var _state4 = null;
59565
+ var _watcher2 = null;
59566
+ var _flushInProgress4 = false;
59567
+ var _flushPending4 = false;
59568
+ var _flushDebounceTimer4 = null;
57126
59569
  function cwdToSlug(cwd) {
57127
59570
  return cwd.replace(/[^a-zA-Z0-9]/g, "-");
57128
59571
  }
57129
59572
  function findProjectDir(cwd) {
57130
59573
  const slug = cwdToSlug(cwd);
57131
- const slugPath = path4.join(PROJECTS_ROOT, slug);
59574
+ const slugPath = path6.join(PROJECTS_ROOT, slug);
57132
59575
  try {
57133
- if (fs3.statSync(slugPath).isDirectory()) return slugPath;
59576
+ if (fs5.statSync(slugPath).isDirectory()) return slugPath;
57134
59577
  } catch {
57135
59578
  }
57136
59579
  let subdirs;
57137
59580
  try {
57138
- subdirs = fs3.readdirSync(PROJECTS_ROOT, { withFileTypes: true });
59581
+ subdirs = fs5.readdirSync(PROJECTS_ROOT, { withFileTypes: true });
57139
59582
  } catch {
57140
59583
  return null;
57141
59584
  }
57142
59585
  for (const subdir of subdirs) {
57143
59586
  if (!subdir.isDirectory()) continue;
57144
- const dirPath = path4.join(PROJECTS_ROOT, subdir.name);
59587
+ const dirPath = path6.join(PROJECTS_ROOT, subdir.name);
57145
59588
  let files;
57146
59589
  try {
57147
- files = fs3.readdirSync(dirPath, { withFileTypes: true });
59590
+ files = fs5.readdirSync(dirPath, { withFileTypes: true });
57148
59591
  } catch {
57149
59592
  continue;
57150
59593
  }
57151
59594
  for (const f of files) {
57152
59595
  if (!f.isFile() || !f.name.endsWith(".jsonl")) continue;
57153
- const foundCwd = readCwdFromJsonl(path4.join(dirPath, f.name));
59596
+ const foundCwd = readCwdFromJsonl(path6.join(dirPath, f.name));
57154
59597
  if (foundCwd === cwd) return dirPath;
57155
59598
  }
57156
59599
  }
@@ -57159,13 +59602,13 @@ function findProjectDir(cwd) {
57159
59602
  function readCwdFromJsonl(jsonlPath) {
57160
59603
  let fd;
57161
59604
  try {
57162
- fd = fs3.openSync(jsonlPath, "r");
59605
+ fd = fs5.openSync(jsonlPath, "r");
57163
59606
  } catch {
57164
59607
  return null;
57165
59608
  }
57166
59609
  try {
57167
59610
  const buf = Buffer.alloc(2048);
57168
- const bytesRead = fs3.readSync(fd, buf, 0, 2048, 0);
59611
+ const bytesRead = fs5.readSync(fd, buf, 0, 2048, 0);
57169
59612
  const raw = buf.slice(0, bytesRead).toString("utf-8");
57170
59613
  for (const line of raw.split("\n")) {
57171
59614
  if (!line.trim()) continue;
@@ -57177,11 +59620,11 @@ function readCwdFromJsonl(jsonlPath) {
57177
59620
  }
57178
59621
  return null;
57179
59622
  } finally {
57180
- fs3.closeSync(fd);
59623
+ fs5.closeSync(fd);
57181
59624
  }
57182
59625
  }
57183
- function captureSessionStart2(cwd) {
57184
- _state2 = { cwd, projectDir: null, files: /* @__PURE__ */ new Map() };
59626
+ function captureSessionStart4(cwd) {
59627
+ _state4 = { cwd, projectDir: null, files: /* @__PURE__ */ new Map() };
57185
59628
  _tryArm(cwd, 0);
57186
59629
  }
57187
59630
  var _MAX_ARM_RETRIES = 20;
@@ -57190,7 +59633,7 @@ function _tryArm(cwd, attempt) {
57190
59633
  if (!projectDir) {
57191
59634
  if (attempt < _MAX_ARM_RETRIES) {
57192
59635
  setTimeout(() => {
57193
- if (!_state2 || _state2.cwd !== cwd) return;
59636
+ if (!_state4 || _state4.cwd !== cwd) return;
57194
59637
  _tryArm(cwd, attempt + 1);
57195
59638
  }, 500);
57196
59639
  } else {
@@ -57203,33 +59646,33 @@ function _tryArm(cwd, attempt) {
57203
59646
  }
57204
59647
  return;
57205
59648
  }
57206
- if (!_state2 || _state2.cwd !== cwd) return;
57207
- _state2.projectDir = projectDir;
59649
+ if (!_state4 || _state4.cwd !== cwd) return;
59650
+ _state4.projectDir = projectDir;
57208
59651
  let entries;
57209
59652
  try {
57210
- entries = fs3.readdirSync(projectDir, { withFileTypes: true });
59653
+ entries = fs5.readdirSync(projectDir, { withFileTypes: true });
57211
59654
  } catch {
57212
59655
  return;
57213
59656
  }
57214
59657
  for (const entry of entries) {
57215
59658
  if (!entry.isFile() || !entry.name.endsWith(".jsonl")) continue;
57216
- const jsonlPath = path4.join(projectDir, entry.name);
59659
+ const jsonlPath = path6.join(projectDir, entry.name);
57217
59660
  let stat;
57218
59661
  try {
57219
- stat = fs3.statSync(jsonlPath);
59662
+ stat = fs5.statSync(jsonlPath);
57220
59663
  } catch {
57221
59664
  continue;
57222
59665
  }
57223
59666
  const sessionId = entry.name.replace(/\.jsonl$/, "");
57224
- _state2.files.set(jsonlPath, { cursorBytes: stat.size, sessionId, contentSeen: /* @__PURE__ */ new Set() });
59667
+ _state4.files.set(jsonlPath, { cursorBytes: stat.size, sessionId, contentSeen: /* @__PURE__ */ new Set() });
57225
59668
  }
57226
59669
  if (attempt > 0) {
57227
- console.error(`\u{1F4DD} [JSONL] project dir found on retry #${attempt}: ${path4.basename(projectDir)}/`);
59670
+ console.error(`\u{1F4DD} [JSONL] project dir found on retry #${attempt}: ${path6.basename(projectDir)}/`);
57228
59671
  }
57229
- console.error(`\u{1F4DD} [JSONL] capture armed: ${_state2.files.size} jsonl(s) in ${path4.basename(projectDir)}/`);
57230
- armDirWatcher();
59672
+ console.error(`\u{1F4DD} [JSONL] capture armed: ${_state4.files.size} jsonl(s) in ${path6.basename(projectDir)}/`);
59673
+ armDirWatcher3();
57231
59674
  }
57232
- function parseEntry(line) {
59675
+ function parseEntry3(line) {
57233
59676
  let entry;
57234
59677
  try {
57235
59678
  entry = JSON.parse(line);
@@ -57265,24 +59708,24 @@ function parseEntry(line) {
57265
59708
  agent_model
57266
59709
  };
57267
59710
  }
57268
- async function flushDeltaForFile2(jsonlPath, fileState) {
57269
- if (!fs3.existsSync(jsonlPath)) return { inserted: 0, skipped: 0, dedup: 0 };
59711
+ async function flushDeltaForFile4(jsonlPath, fileState) {
59712
+ if (!fs5.existsSync(jsonlPath)) return { inserted: 0, skipped: 0, dedup: 0 };
57270
59713
  let stat;
57271
59714
  try {
57272
- stat = fs3.statSync(jsonlPath);
59715
+ stat = fs5.statSync(jsonlPath);
57273
59716
  } catch {
57274
59717
  return { inserted: 0, skipped: 0, dedup: 0 };
57275
59718
  }
57276
59719
  if (stat.size <= fileState.cursorBytes) return { inserted: 0, skipped: 0, dedup: 0 };
57277
59720
  const length = stat.size - fileState.cursorBytes;
57278
- const fd = fs3.openSync(jsonlPath, "r");
59721
+ const fd = fs5.openSync(jsonlPath, "r");
57279
59722
  let raw;
57280
59723
  try {
57281
59724
  const buf = Buffer.alloc(length);
57282
- fs3.readSync(fd, buf, 0, length, fileState.cursorBytes);
59725
+ fs5.readSync(fd, buf, 0, length, fileState.cursorBytes);
57283
59726
  raw = buf.toString("utf-8");
57284
59727
  } finally {
57285
- fs3.closeSync(fd);
59728
+ fs5.closeSync(fd);
57286
59729
  }
57287
59730
  const lastNl = raw.lastIndexOf("\n");
57288
59731
  if (lastNl === -1) return { inserted: 0, skipped: 0, dedup: 0 };
@@ -57295,7 +59738,7 @@ async function flushDeltaForFile2(jsonlPath, fileState) {
57295
59738
  for (const line of lines) {
57296
59739
  const trimmed = line.trim();
57297
59740
  if (!trimmed) continue;
57298
- const parsed = parseEntry(trimmed);
59741
+ const parsed = parseEntry3(trimmed);
57299
59742
  if (!parsed) {
57300
59743
  skipped++;
57301
59744
  continue;
@@ -57316,7 +59759,7 @@ async function flushDeltaForFile2(jsonlPath, fileState) {
57316
59759
  role: parsed.role,
57317
59760
  message: parsed.message,
57318
59761
  external_uuid: externalUuid,
57319
- device_name: DEVICE_NAME4
59762
+ device_name: DEVICE_NAME6
57320
59763
  });
57321
59764
  if (result.inserted) inserted++;
57322
59765
  else dedup++;
@@ -57328,121 +59771,121 @@ async function flushDeltaForFile2(jsonlPath, fileState) {
57328
59771
  fileState.cursorBytes = newCursor;
57329
59772
  return { inserted, skipped, dedup };
57330
59773
  }
57331
- async function flushAllFiles2() {
57332
- if (!_state2 || !_state2.projectDir) return { inserted: 0, skipped: 0, dedup: 0 };
57333
- const projectDir = _state2.projectDir;
57334
- if (!fs3.existsSync(projectDir)) return { inserted: 0, skipped: 0, dedup: 0 };
59774
+ async function flushAllFiles4() {
59775
+ if (!_state4 || !_state4.projectDir) return { inserted: 0, skipped: 0, dedup: 0 };
59776
+ const projectDir = _state4.projectDir;
59777
+ if (!fs5.existsSync(projectDir)) return { inserted: 0, skipped: 0, dedup: 0 };
57335
59778
  let entries;
57336
59779
  try {
57337
- entries = fs3.readdirSync(projectDir, { withFileTypes: true });
59780
+ entries = fs5.readdirSync(projectDir, { withFileTypes: true });
57338
59781
  } catch {
57339
59782
  return { inserted: 0, skipped: 0, dedup: 0 };
57340
59783
  }
57341
59784
  let totalI = 0, totalS = 0, totalD = 0;
57342
59785
  for (const entry of entries) {
57343
59786
  if (!entry.isFile() || !entry.name.endsWith(".jsonl")) continue;
57344
- const jsonlPath = path4.join(projectDir, entry.name);
57345
- let fileState = _state2.files.get(jsonlPath);
59787
+ const jsonlPath = path6.join(projectDir, entry.name);
59788
+ let fileState = _state4.files.get(jsonlPath);
57346
59789
  if (!fileState) {
57347
59790
  const sessionId = entry.name.replace(/\.jsonl$/, "");
57348
59791
  fileState = { cursorBytes: 0, sessionId, contentSeen: /* @__PURE__ */ new Set() };
57349
- _state2.files.set(jsonlPath, fileState);
59792
+ _state4.files.set(jsonlPath, fileState);
57350
59793
  console.error(`\u{1F4DD} [JSONL] new session detected: ${entry.name}`);
57351
59794
  }
57352
- const r = await flushDeltaForFile2(jsonlPath, fileState);
59795
+ const r = await flushDeltaForFile4(jsonlPath, fileState);
57353
59796
  totalI += r.inserted;
57354
59797
  totalS += r.skipped;
57355
59798
  totalD += r.dedup;
57356
59799
  }
57357
59800
  return { inserted: totalI, skipped: totalS, dedup: totalD };
57358
59801
  }
57359
- async function flushWithMutex2() {
57360
- if (_flushInProgress2) {
57361
- _flushPending2 = true;
59802
+ async function flushWithMutex4() {
59803
+ if (_flushInProgress4) {
59804
+ _flushPending4 = true;
57362
59805
  return;
57363
59806
  }
57364
- _flushInProgress2 = true;
59807
+ _flushInProgress4 = true;
57365
59808
  try {
57366
- const r = await flushAllFiles2();
59809
+ const r = await flushAllFiles4();
57367
59810
  if (r.inserted > 0 || r.skipped > 0 || r.dedup > 0) {
57368
59811
  console.error(`\u{1F4DD} [JSONL] live flush: inserted=${r.inserted}, dedup=${r.dedup}, skipped=${r.skipped}`);
57369
59812
  }
57370
59813
  } catch (err2) {
57371
59814
  console.error("\u26A0\uFE0F [JSONL] live flush error:", err2);
57372
59815
  } finally {
57373
- _flushInProgress2 = false;
57374
- if (_flushPending2) {
57375
- _flushPending2 = false;
59816
+ _flushInProgress4 = false;
59817
+ if (_flushPending4) {
59818
+ _flushPending4 = false;
57376
59819
  setImmediate(() => {
57377
- void flushWithMutex2();
59820
+ void flushWithMutex4();
57378
59821
  });
57379
59822
  }
57380
59823
  }
57381
59824
  }
57382
- function scheduleFlush2() {
57383
- if (_flushDebounceTimer2) clearTimeout(_flushDebounceTimer2);
57384
- _flushDebounceTimer2 = setTimeout(() => {
57385
- _flushDebounceTimer2 = null;
57386
- void flushWithMutex2();
57387
- }, FLUSH_DEBOUNCE_MS2);
59825
+ function scheduleFlush4() {
59826
+ if (_flushDebounceTimer4) clearTimeout(_flushDebounceTimer4);
59827
+ _flushDebounceTimer4 = setTimeout(() => {
59828
+ _flushDebounceTimer4 = null;
59829
+ void flushWithMutex4();
59830
+ }, FLUSH_DEBOUNCE_MS4);
57388
59831
  }
57389
- function armDirWatcher() {
57390
- if (!_state2 || !_state2.projectDir) return;
57391
- if (_watcher) return;
59832
+ function armDirWatcher3() {
59833
+ if (!_state4 || !_state4.projectDir) return;
59834
+ if (_watcher2) return;
57392
59835
  try {
57393
- _watcher = fs3.watch(_state2.projectDir, (_eventType, filename) => {
59836
+ _watcher2 = fs5.watch(_state4.projectDir, (_eventType, filename) => {
57394
59837
  if (filename && !filename.endsWith(".jsonl")) return;
57395
- scheduleFlush2();
59838
+ scheduleFlush4();
57396
59839
  });
57397
59840
  console.error(`\u{1F4DD} [JSONL] dir watcher armed`);
57398
59841
  } catch (err2) {
57399
59842
  console.error("\u26A0\uFE0F [JSONL] fs.watch dir failed (shutdown-only flush \uD3F4\uBC31):", err2);
57400
59843
  }
57401
59844
  }
57402
- function disarmDirWatcher() {
57403
- if (_flushDebounceTimer2) {
57404
- clearTimeout(_flushDebounceTimer2);
57405
- _flushDebounceTimer2 = null;
59845
+ function disarmDirWatcher3() {
59846
+ if (_flushDebounceTimer4) {
59847
+ clearTimeout(_flushDebounceTimer4);
59848
+ _flushDebounceTimer4 = null;
57406
59849
  }
57407
- if (_watcher) {
59850
+ if (_watcher2) {
57408
59851
  try {
57409
- _watcher.close();
59852
+ _watcher2.close();
57410
59853
  } catch {
57411
59854
  }
57412
- _watcher = null;
59855
+ _watcher2 = null;
57413
59856
  }
57414
59857
  }
57415
- async function captureSessionEnd2() {
57416
- disarmDirWatcher();
59858
+ async function captureSessionEnd4() {
59859
+ disarmDirWatcher3();
57417
59860
  const waitStart = Date.now();
57418
- while (_flushInProgress2 && Date.now() - waitStart < 2e3) {
59861
+ while (_flushInProgress4 && Date.now() - waitStart < 2e3) {
57419
59862
  await new Promise((r) => setTimeout(r, 50));
57420
59863
  }
57421
- if (!_state2 || !_state2.projectDir) {
59864
+ if (!_state4 || !_state4.projectDir) {
57422
59865
  return { inserted: 0, skipped: 0, error: "session not armed" };
57423
59866
  }
57424
- _flushInProgress2 = true;
59867
+ _flushInProgress4 = true;
57425
59868
  try {
57426
- const r = await flushAllFiles2();
59869
+ const r = await flushAllFiles4();
57427
59870
  if (r.inserted > 0 || r.skipped > 0 || r.dedup > 0) {
57428
59871
  console.error(`\u{1F4DD} [JSONL] final flush: inserted=${r.inserted}, dedup=${r.dedup}, skipped=${r.skipped}`);
57429
59872
  }
57430
59873
  return { inserted: r.inserted, skipped: r.skipped };
57431
59874
  } finally {
57432
- _flushInProgress2 = false;
59875
+ _flushInProgress4 = false;
57433
59876
  }
57434
59877
  }
57435
59878
 
57436
59879
  // src/auto_save/codex_capture.ts
57437
- import * as fs4 from "node:fs";
57438
- import * as path5 from "node:path";
57439
- import * as os7 from "node:os";
59880
+ import * as fs6 from "node:fs";
59881
+ import * as path7 from "node:path";
59882
+ import * as os10 from "node:os";
57440
59883
  import { spawnSync } from "node:child_process";
57441
- var DEVICE_NAME5 = os7.hostname();
57442
- var SESSIONS_ROOT = path5.join(os7.homedir(), ".codex", "sessions");
57443
- var STATE_DB = path5.join(os7.homedir(), ".codex", "state_5.sqlite");
57444
- var FLUSH_DEBOUNCE_MS3 = 200;
57445
- var POLL_INTERVAL_MS2 = 3e3;
59884
+ var DEVICE_NAME7 = os10.hostname();
59885
+ var SESSIONS_ROOT2 = path7.join(os10.homedir(), ".codex", "sessions");
59886
+ var STATE_DB = path7.join(os10.homedir(), ".codex", "state_5.sqlite");
59887
+ var FLUSH_DEBOUNCE_MS5 = 200;
59888
+ var POLL_INTERVAL_MS4 = 3e3;
57446
59889
  function sourceToPlatform(source) {
57447
59890
  switch (source) {
57448
59891
  case "cli":
@@ -57470,30 +59913,30 @@ function lookupPlatform(sessionId) {
57470
59913
  }
57471
59914
  return "codex-mcp-client";
57472
59915
  }
57473
- var _state3 = null;
57474
- var SERVER_START_MS = Date.now();
57475
- var _watcher2 = null;
57476
- var _pollTimer2 = null;
57477
- var _flushInProgress3 = false;
57478
- var _flushPending3 = false;
57479
- var _flushDebounceTimer3 = null;
57480
- function extractSessionId(filename) {
59916
+ var _state5 = null;
59917
+ var SERVER_START_MS3 = Date.now();
59918
+ var _watcher3 = null;
59919
+ var _pollTimer4 = null;
59920
+ var _flushInProgress5 = false;
59921
+ var _flushPending5 = false;
59922
+ var _flushDebounceTimer5 = null;
59923
+ function extractSessionId2(filename) {
57481
59924
  const m = filename.match(/^rollout-.+?-([0-9a-f-]{36})\.jsonl$/);
57482
59925
  return m ? m[1] : null;
57483
59926
  }
57484
59927
  function* walkRollouts(root) {
57485
- if (!fs4.existsSync(root)) return;
59928
+ if (!fs6.existsSync(root)) return;
57486
59929
  const stack = [root];
57487
59930
  while (stack.length) {
57488
59931
  const dir = stack.pop();
57489
59932
  let entries;
57490
59933
  try {
57491
- entries = fs4.readdirSync(dir, { withFileTypes: true });
59934
+ entries = fs6.readdirSync(dir, { withFileTypes: true });
57492
59935
  } catch {
57493
59936
  continue;
57494
59937
  }
57495
59938
  for (const e of entries) {
57496
- const p = path5.join(dir, e.name);
59939
+ const p = path7.join(dir, e.name);
57497
59940
  if (e.isDirectory()) {
57498
59941
  stack.push(p);
57499
59942
  } else if (e.isFile() && e.name.startsWith("rollout-") && e.name.endsWith(".jsonl")) {
@@ -57502,9 +59945,9 @@ function* walkRollouts(root) {
57502
59945
  }
57503
59946
  }
57504
59947
  }
57505
- function captureSessionStart3(_cwd) {
57506
- const rootExists = fs4.existsSync(SESSIONS_ROOT);
57507
- _state3 = {
59948
+ function captureSessionStart5(_cwd) {
59949
+ const rootExists = fs6.existsSync(SESSIONS_ROOT2);
59950
+ _state5 = {
57508
59951
  rootExists,
57509
59952
  files: /* @__PURE__ */ new Map()
57510
59953
  };
@@ -57512,16 +59955,16 @@ function captureSessionStart3(_cwd) {
57512
59955
  return;
57513
59956
  }
57514
59957
  let count = 0;
57515
- for (const filePath of walkRollouts(SESSIONS_ROOT)) {
59958
+ for (const filePath of walkRollouts(SESSIONS_ROOT2)) {
57516
59959
  let stat;
57517
59960
  try {
57518
- stat = fs4.statSync(filePath);
59961
+ stat = fs6.statSync(filePath);
57519
59962
  } catch {
57520
59963
  continue;
57521
59964
  }
57522
- const sessionId = extractSessionId(path5.basename(filePath));
59965
+ const sessionId = extractSessionId2(path7.basename(filePath));
57523
59966
  if (!sessionId) continue;
57524
- _state3.files.set(filePath, {
59967
+ _state5.files.set(filePath, {
57525
59968
  cursorBytes: stat.size,
57526
59969
  sessionId,
57527
59970
  agentPlatform: lookupPlatform(sessionId),
@@ -57530,9 +59973,9 @@ function captureSessionStart3(_cwd) {
57530
59973
  count++;
57531
59974
  }
57532
59975
  console.error(`\u{1F4DD} [Codex] capture armed: ${count} rollout(s)`);
57533
- armDirWatcher2();
59976
+ armDirWatcher4();
57534
59977
  }
57535
- function parseEntry2(line, byteOffset) {
59978
+ function parseEntry4(line, byteOffset) {
57536
59979
  let entry;
57537
59980
  try {
57538
59981
  entry = JSON.parse(line);
@@ -57568,24 +60011,24 @@ function extractModelFromTurnContext(line) {
57568
60011
  return null;
57569
60012
  }
57570
60013
  }
57571
- async function flushDeltaForFile3(filePath, fileState) {
57572
- if (!fs4.existsSync(filePath)) return { inserted: 0, skipped: 0, dedup: 0 };
60014
+ async function flushDeltaForFile5(filePath, fileState) {
60015
+ if (!fs6.existsSync(filePath)) return { inserted: 0, skipped: 0, dedup: 0 };
57573
60016
  let stat;
57574
60017
  try {
57575
- stat = fs4.statSync(filePath);
60018
+ stat = fs6.statSync(filePath);
57576
60019
  } catch {
57577
60020
  return { inserted: 0, skipped: 0, dedup: 0 };
57578
60021
  }
57579
60022
  if (stat.size <= fileState.cursorBytes) return { inserted: 0, skipped: 0, dedup: 0 };
57580
60023
  const length = stat.size - fileState.cursorBytes;
57581
- const fd = fs4.openSync(filePath, "r");
60024
+ const fd = fs6.openSync(filePath, "r");
57582
60025
  let raw;
57583
60026
  try {
57584
60027
  const buf = Buffer.alloc(length);
57585
- fs4.readSync(fd, buf, 0, length, fileState.cursorBytes);
60028
+ fs6.readSync(fd, buf, 0, length, fileState.cursorBytes);
57586
60029
  raw = buf.toString("utf-8");
57587
60030
  } finally {
57588
- fs4.closeSync(fd);
60031
+ fs6.closeSync(fd);
57589
60032
  }
57590
60033
  const lastNl = raw.lastIndexOf("\n");
57591
60034
  if (lastNl === -1) return { inserted: 0, skipped: 0, dedup: 0 };
@@ -57610,7 +60053,7 @@ async function flushDeltaForFile3(filePath, fileState) {
57610
60053
  continue;
57611
60054
  }
57612
60055
  const byteOffset = startBase + lineStartInRaw;
57613
- const parsed = parseEntry2(trimmed, byteOffset);
60056
+ const parsed = parseEntry4(trimmed, byteOffset);
57614
60057
  if (!parsed) {
57615
60058
  skipped++;
57616
60059
  lineStartInRaw += Buffer.byteLength(line, "utf-8") + 1;
@@ -57626,7 +60069,7 @@ async function flushDeltaForFile3(filePath, fileState) {
57626
60069
  role: parsed.role,
57627
60070
  message: parsed.message,
57628
60071
  external_uuid: externalUuid,
57629
- device_name: DEVICE_NAME5
60072
+ device_name: DEVICE_NAME7
57630
60073
  });
57631
60074
  if (result.inserted) inserted++;
57632
60075
  else dedup++;
@@ -57639,26 +60082,26 @@ async function flushDeltaForFile3(filePath, fileState) {
57639
60082
  fileState.cursorBytes = newCursor;
57640
60083
  return { inserted, skipped, dedup };
57641
60084
  }
57642
- async function flushAllFiles3() {
57643
- if (!_state3 || !_state3.rootExists) return { inserted: 0, skipped: 0, dedup: 0 };
60085
+ async function flushAllFiles5() {
60086
+ if (!_state5 || !_state5.rootExists) return { inserted: 0, skipped: 0, dedup: 0 };
57644
60087
  let totalI = 0, totalS = 0, totalD = 0;
57645
- for (const filePath of walkRollouts(SESSIONS_ROOT)) {
57646
- let fileState = _state3.files.get(filePath);
60088
+ for (const filePath of walkRollouts(SESSIONS_ROOT2)) {
60089
+ let fileState = _state5.files.get(filePath);
57647
60090
  if (!fileState) {
57648
- const sessionId = extractSessionId(path5.basename(filePath));
60091
+ const sessionId = extractSessionId2(path7.basename(filePath));
57649
60092
  if (!sessionId) continue;
57650
60093
  let stat = null;
57651
60094
  try {
57652
- stat = fs4.statSync(filePath);
60095
+ stat = fs6.statSync(filePath);
57653
60096
  } catch {
57654
60097
  }
57655
- const fileBirthMs = stat ? stat.birthtimeMs || stat.ctimeMs : SERVER_START_MS;
57656
- const isPreExisting = fileBirthMs < SERVER_START_MS - 5e3;
60098
+ const fileBirthMs = stat ? stat.birthtimeMs || stat.ctimeMs : SERVER_START_MS3;
60099
+ const isPreExisting = fileBirthMs < SERVER_START_MS3 - 5e3;
57657
60100
  const initialCursor = isPreExisting ? stat?.size ?? 0 : 0;
57658
60101
  if (isPreExisting) {
57659
- console.error(`\u{1F4DD} [Codex] pre-existing rollout skipped (cursor=${initialCursor}): ${path5.basename(filePath)}`);
60102
+ console.error(`\u{1F4DD} [Codex] pre-existing rollout skipped (cursor=${initialCursor}): ${path7.basename(filePath)}`);
57660
60103
  } else {
57661
- console.error(`\u{1F4DD} [Codex] new rollout detected: ${path5.basename(filePath)}`);
60104
+ console.error(`\u{1F4DD} [Codex] new rollout detected: ${path7.basename(filePath)}`);
57662
60105
  }
57663
60106
  fileState = {
57664
60107
  cursorBytes: initialCursor,
@@ -57666,129 +60109,126 @@ async function flushAllFiles3() {
57666
60109
  agentPlatform: lookupPlatform(sessionId),
57667
60110
  currentModel: null
57668
60111
  };
57669
- _state3.files.set(filePath, fileState);
60112
+ _state5.files.set(filePath, fileState);
57670
60113
  }
57671
- const r = await flushDeltaForFile3(filePath, fileState);
60114
+ const r = await flushDeltaForFile5(filePath, fileState);
57672
60115
  totalI += r.inserted;
57673
60116
  totalS += r.skipped;
57674
60117
  totalD += r.dedup;
57675
60118
  }
57676
60119
  return { inserted: totalI, skipped: totalS, dedup: totalD };
57677
60120
  }
57678
- async function flushWithMutex3() {
57679
- if (_flushInProgress3) {
57680
- _flushPending3 = true;
60121
+ async function flushWithMutex5() {
60122
+ if (_flushInProgress5) {
60123
+ _flushPending5 = true;
57681
60124
  return;
57682
60125
  }
57683
- _flushInProgress3 = true;
60126
+ _flushInProgress5 = true;
57684
60127
  try {
57685
- const r = await flushAllFiles3();
60128
+ const r = await flushAllFiles5();
57686
60129
  if (r.inserted > 0 || r.skipped > 0 || r.dedup > 0) {
57687
60130
  console.error(`\u{1F4DD} [Codex] live flush: inserted=${r.inserted}, dedup=${r.dedup}, skipped=${r.skipped}`);
57688
60131
  }
57689
60132
  } catch (err2) {
57690
60133
  console.error("\u26A0\uFE0F [Codex] live flush error:", err2);
57691
60134
  } finally {
57692
- _flushInProgress3 = false;
57693
- if (_flushPending3) {
57694
- _flushPending3 = false;
60135
+ _flushInProgress5 = false;
60136
+ if (_flushPending5) {
60137
+ _flushPending5 = false;
57695
60138
  setImmediate(() => {
57696
- void flushWithMutex3();
60139
+ void flushWithMutex5();
57697
60140
  });
57698
60141
  }
57699
60142
  }
57700
60143
  }
57701
- function scheduleFlush3() {
57702
- if (_flushDebounceTimer3) clearTimeout(_flushDebounceTimer3);
57703
- _flushDebounceTimer3 = setTimeout(() => {
57704
- _flushDebounceTimer3 = null;
57705
- void flushWithMutex3();
57706
- }, FLUSH_DEBOUNCE_MS3);
60144
+ function scheduleFlush5() {
60145
+ if (_flushDebounceTimer5) clearTimeout(_flushDebounceTimer5);
60146
+ _flushDebounceTimer5 = setTimeout(() => {
60147
+ _flushDebounceTimer5 = null;
60148
+ void flushWithMutex5();
60149
+ }, FLUSH_DEBOUNCE_MS5);
57707
60150
  }
57708
- function armDirWatcher2() {
57709
- if (!_state3 || !_state3.rootExists) return;
57710
- if (_watcher2) return;
60151
+ function armDirWatcher4() {
60152
+ if (!_state5 || !_state5.rootExists) return;
60153
+ if (_watcher3) return;
57711
60154
  try {
57712
- _watcher2 = fs4.watch(SESSIONS_ROOT, { recursive: true }, (_evt, filename) => {
60155
+ _watcher3 = fs6.watch(SESSIONS_ROOT2, { recursive: true }, (_evt, filename) => {
57713
60156
  if (!filename) return;
57714
- const base = path5.basename(filename);
60157
+ const base = path7.basename(filename);
57715
60158
  if (!base.startsWith("rollout-") || !base.endsWith(".jsonl")) return;
57716
- scheduleFlush3();
60159
+ scheduleFlush5();
57717
60160
  });
57718
60161
  console.error(`\u{1F4DD} [Codex] dir watcher armed (recursive)`);
57719
60162
  } catch (err2) {
57720
60163
  console.error("\u26A0\uFE0F [Codex] fs.watch failed \u2014 polling only:", err2);
57721
60164
  }
57722
- _pollTimer2 = setInterval(() => {
57723
- void flushWithMutex3();
57724
- }, POLL_INTERVAL_MS2);
60165
+ _pollTimer4 = setInterval(() => {
60166
+ void flushWithMutex5();
60167
+ }, POLL_INTERVAL_MS4);
57725
60168
  }
57726
- function disarmDirWatcher2() {
57727
- if (_flushDebounceTimer3) {
57728
- clearTimeout(_flushDebounceTimer3);
57729
- _flushDebounceTimer3 = null;
60169
+ function disarmDirWatcher4() {
60170
+ if (_flushDebounceTimer5) {
60171
+ clearTimeout(_flushDebounceTimer5);
60172
+ _flushDebounceTimer5 = null;
57730
60173
  }
57731
- if (_pollTimer2) {
57732
- clearInterval(_pollTimer2);
57733
- _pollTimer2 = null;
60174
+ if (_pollTimer4) {
60175
+ clearInterval(_pollTimer4);
60176
+ _pollTimer4 = null;
57734
60177
  }
57735
- if (_watcher2) {
60178
+ if (_watcher3) {
57736
60179
  try {
57737
- _watcher2.close();
60180
+ _watcher3.close();
57738
60181
  } catch {
57739
60182
  }
57740
- _watcher2 = null;
60183
+ _watcher3 = null;
57741
60184
  }
57742
60185
  }
57743
- async function captureSessionEnd3() {
57744
- disarmDirWatcher2();
60186
+ async function captureSessionEnd5() {
60187
+ disarmDirWatcher4();
57745
60188
  const waitStart = Date.now();
57746
- while (_flushInProgress3 && Date.now() - waitStart < 2e3) {
60189
+ while (_flushInProgress5 && Date.now() - waitStart < 2e3) {
57747
60190
  await new Promise((r) => setTimeout(r, 50));
57748
60191
  }
57749
- if (!_state3 || !_state3.rootExists) {
60192
+ if (!_state5 || !_state5.rootExists) {
57750
60193
  return { inserted: 0, skipped: 0, error: "session not armed" };
57751
60194
  }
57752
- _flushInProgress3 = true;
60195
+ _flushInProgress5 = true;
57753
60196
  try {
57754
- const r = await flushAllFiles3();
60197
+ const r = await flushAllFiles5();
57755
60198
  if (r.inserted > 0 || r.skipped > 0 || r.dedup > 0) {
57756
60199
  console.error(`\u{1F4DD} [Codex] final flush: inserted=${r.inserted}, dedup=${r.dedup}, skipped=${r.skipped}`);
57757
60200
  }
57758
60201
  return { inserted: r.inserted, skipped: r.skipped };
57759
60202
  } finally {
57760
- _flushInProgress3 = false;
60203
+ _flushInProgress5 = false;
57761
60204
  }
57762
60205
  }
57763
60206
 
57764
60207
  // src/version.ts
57765
- import fs5 from "fs";
57766
- import path6 from "path";
60208
+ import fs7 from "fs";
60209
+ import path8 from "path";
57767
60210
  import { fileURLToPath as fileURLToPath2 } from "url";
57768
- var __dirname3 = path6.dirname(fileURLToPath2(import.meta.url));
57769
- var packageJsonPath = path6.resolve(__dirname3, "..", "package.json");
57770
- var packageJson = JSON.parse(fs5.readFileSync(packageJsonPath, "utf8"));
60211
+ var __dirname3 = path8.dirname(fileURLToPath2(import.meta.url));
60212
+ var packageJsonPath = path8.resolve(__dirname3, "..", "package.json");
60213
+ var packageJson = JSON.parse(fs7.readFileSync(packageJsonPath, "utf8"));
57771
60214
  var PACKAGE_VERSION2 = packageJson.version || "0.0.0";
57772
60215
 
57773
60216
  // src/index.ts
57774
- import fs7 from "fs";
60217
+ import fs9 from "fs";
57775
60218
  var BRIEF_DB_TIMEOUT_MS = 5e3;
60219
+ var INSTRUCTIONS_MAX_CHARS = Number(process.env.INSTRUCTIONS_MAX_CHARS ?? 1900);
60220
+ var INSTRUCTIONS_SEP = "\n\n---\n\n";
57776
60221
  var STATIC_INSTRUCTIONS = `Long-term memory MCP server (RESPEC v1).
57777
60222
 
57778
- Tools:
57779
- - 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.
57780
- - search_memory : \uACFC\uAC70 \uAE30\uC5B5 \uC870\uD68C/\uAC80\uC0C9 \uD1B5\uD569 (\uC758\uBBF8 + \uD0A4\uC6CC\uB4DC fallback)
57781
- - manage_knowledge : \uBA85\uC2DC \uC800\uC7A5/\uC218\uC815/\uC0AD\uC81C \uD1B5\uD569 (\uAC15\uC81C \uAE30\uC5B5\uC740 is_pinned, archive \uBA74\uC81C)
57782
- - save_message : transcript \uCEA1\uCC98 \uBBF8\uC9C0\uC6D0 platform \uD55C\uC815 fallback (Cursor \uB4F1). Hot Path \uC9C1\uC811 INSERT.
60223
+ \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.
60224
+
60225
+ 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).
60226
+
60227
+ \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.
57783
60228
 
57784
- Hot Path (\uC790\uB3D9 \uC800\uC7A5) \uB8F0:
57785
- - **Claude Code / Codex CLI / Gemini CLI**: server\uAC00 transcript \uD30C\uC77C\uC744 passive read\uD574\uC11C \uC790\uB3D9 \uCEA1\uCC98.
57786
- \uBCC4\uB3C4 save_message \uD638\uCD9C X. \uD638\uCD9C\uD558\uBA74 \uB3D9\uC77C \uBA54\uC2DC\uC9C0 \uC911\uBCF5 row \uBC1C\uC0DD.
57787
- - **\uADF8 \uC678 platform** (Cursor, Antigravity \uB4F1 transcript \uBE44\uACF5\uAC1C): \uB9E4 turn save_message \uD638\uCD9C \u2014 fallback.
60229
+ \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.
57788
60230
 
57789
- caller convention:
57790
- - manage_knowledge / save_message \uD638\uCD9C \uC2DC agent_model \uBA85\uC2DC (\uC0DD\uB7B5 \uC2DC 'unknown' \uC800\uC7A5)
57791
- - subagent context\uBA74 subagent: true + subagent_model + subagent_role \uD568\uAED8`;
60231
+ \uD638\uCD9C \uC2DC agent_model \uBA85\uC2DC (subagent\uBA74 subagent:true + subagent_model/role \uB3D9\uBD09).`;
57792
60232
  var STATIC_INSTRUCTIONS_BRIEF_UNAVAILABLE = `
57793
60233
 
57794
60234
  ---
@@ -57808,7 +60248,11 @@ async function buildInstructions() {
57808
60248
  await db2.connect();
57809
60249
  const currentPlatform = detectBootPlatformFromEnv();
57810
60250
  const brief = await collectBrief({ currentPlatform });
57811
- return formatBriefMarkdown(brief);
60251
+ const briefBudget = Math.max(
60252
+ 0,
60253
+ INSTRUCTIONS_MAX_CHARS - STATIC_INSTRUCTIONS.length - INSTRUCTIONS_SEP.length
60254
+ );
60255
+ return formatBriefMarkdown(brief, { mode: "inject", maxChars: briefBudget });
57812
60256
  })();
57813
60257
  const briefMd = await Promise.race([
57814
60258
  work,
@@ -57816,11 +60260,13 @@ async function buildInstructions() {
57816
60260
  (_, reject) => setTimeout(() => reject(new Error("buildInstructions timeout")), BRIEF_DB_TIMEOUT_MS)
57817
60261
  )
57818
60262
  ]);
57819
- return `${STATIC_INSTRUCTIONS}
57820
-
57821
- ---
57822
-
57823
- ${briefMd}`;
60263
+ const assembled = `${STATIC_INSTRUCTIONS}${INSTRUCTIONS_SEP}${briefMd}`;
60264
+ if (assembled.length > INSTRUCTIONS_MAX_CHARS) {
60265
+ console.error(
60266
+ `\u26A0\uFE0F instructions ${assembled.length}\uC790 > cap ${INSTRUCTIONS_MAX_CHARS} \u2014 Core Profile \uAE38\uC774 \uC810\uAC80 \uD544\uC694`
60267
+ );
60268
+ }
60269
+ return assembled;
57824
60270
  } catch (err2) {
57825
60271
  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);
57826
60272
  console.error(" static fallback + 'memory_startup \uBA85\uC2DC \uD638\uCD9C \uAD8C\uC7A5' \uC548\uB0B4 \uD3EC\uD568.");
@@ -57837,9 +60283,11 @@ async function shutdown(reason) {
57837
60283
  try {
57838
60284
  await Promise.race([
57839
60285
  Promise.allSettled([
60286
+ captureSessionEnd4(),
60287
+ captureSessionEnd5(),
60288
+ captureSessionEnd(),
57840
60289
  captureSessionEnd2(),
57841
- captureSessionEnd3(),
57842
- captureSessionEnd()
60290
+ captureSessionEnd3()
57843
60291
  ]),
57844
60292
  new Promise((resolve) => setTimeout(resolve, 3e3))
57845
60293
  ]);
@@ -57916,6 +60364,7 @@ function printHelp() {
57916
60364
 
57917
60365
  Usage:
57918
60366
  mcp-agents-memory Run the MCP server (stdio).
60367
+ mcp-agents-memory coldpath Run ONLY the cold-path worker as a standalone always-on daemon (no MCP server). For the processing/GPU machine via systemd.
57919
60368
  mcp-agents-memory setup Interactive setup \u2014 write config to ~/.config/mcp-agents-memory/.env and run migrations.
57920
60369
  mcp-agents-memory migrate Apply any pending DB migrations against the configured database.
57921
60370
  mcp-agents-memory help Show this message.
@@ -57945,14 +60394,16 @@ async function runMcpServer() {
57945
60394
  registerTools(server);
57946
60395
  installShutdownHandlers();
57947
60396
  startParentWatchdog();
60397
+ captureSessionStart4(process.cwd());
60398
+ captureSessionStart5(process.cwd());
60399
+ captureSessionStart(process.cwd());
57948
60400
  captureSessionStart2(process.cwd());
57949
60401
  captureSessionStart3(process.cwd());
57950
- captureSessionStart(process.cwd());
57951
60402
  console.error("\u{1F680} Starting Memory MCP Server...");
57952
60403
  if (process.env.SSH_ENABLED === "true" && !process.env.SSH_KEY_PATH) {
57953
60404
  throw new Error("\u274C SSH_KEY_PATH is required when SSH is enabled");
57954
60405
  }
57955
- if (process.env.SSH_KEY_PATH && !fs7.existsSync(process.env.SSH_KEY_PATH)) {
60406
+ if (process.env.SSH_KEY_PATH && !fs9.existsSync(process.env.SSH_KEY_PATH)) {
57956
60407
  console.error(`\u26A0\uFE0F [WARNING] SSH key not found at: ${process.env.SSH_KEY_PATH}`);
57957
60408
  }
57958
60409
  try {
@@ -57963,13 +60414,36 @@ async function runMcpServer() {
57963
60414
  console.error("\u274C Fatal error during startup:", err2);
57964
60415
  process.exit(1);
57965
60416
  }
57966
- startColdPathWorker();
60417
+ void startColdPathWorker().catch((err2) => console.error("\u274C [ColdPath] start failed:", err2));
60418
+ }
60419
+ async function runColdPathDaemon() {
60420
+ process.env.COLD_PATH_ENABLED = "true";
60421
+ process.on("SIGTERM", () => {
60422
+ void shutdown("SIGTERM");
60423
+ });
60424
+ process.on("SIGINT", () => {
60425
+ void shutdown("SIGINT");
60426
+ });
60427
+ process.on("SIGHUP", () => {
60428
+ void shutdown("SIGHUP");
60429
+ });
60430
+ try {
60431
+ await db.connect();
60432
+ } catch (err2) {
60433
+ console.error("\u274C [ColdPathDaemon] DB connect failed \u2014 exiting (systemd will retry):", err2);
60434
+ process.exit(1);
60435
+ }
60436
+ console.error(`\u{1F9CA} Cold-path daemon (v${PACKAGE_VERSION2}) started \u2014 no MCP server`);
60437
+ await startColdPathWorker();
57967
60438
  }
57968
60439
  async function cli() {
57969
60440
  const cmd = process.argv[2];
57970
60441
  if (!cmd || cmd === "serve") {
57971
60442
  return runMcpServer();
57972
60443
  }
60444
+ if (cmd === "coldpath") {
60445
+ return runColdPathDaemon();
60446
+ }
57973
60447
  if (cmd === "help" || cmd === "--help" || cmd === "-h") {
57974
60448
  printHelp();
57975
60449
  return;