mcp-agents-memory 0.9.2 → 0.9.5

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 hostname2 = dummyHost ? "" : result.hostname;
1658
+ const hostname8 = dummyHost ? "" : result.hostname;
1659
1659
  if (!config2.host) {
1660
- config2.host = decodeURIComponent(hostname2);
1661
- } else if (hostname2 && /^%2f/i.test(hostname2)) {
1662
- result.pathname = hostname2 + result.pathname;
1660
+ config2.host = decodeURIComponent(hostname8);
1661
+ } else if (hostname8 && /^%2f/i.test(hostname8)) {
1662
+ result.pathname = hostname8 + result.pathname;
1663
1663
  }
1664
1664
  if (!config2.port) {
1665
1665
  config2.port = result.port;
@@ -3079,7 +3079,7 @@ var require_dist = __commonJS({
3079
3079
  function parse3(stream, callback) {
3080
3080
  const parser = new parser_1.Parser();
3081
3081
  stream.on("data", (buffer) => parser.parse(buffer, callback));
3082
- return new Promise((resolve2) => stream.on("end", () => resolve2()));
3082
+ return new Promise((resolve) => stream.on("end", () => resolve()));
3083
3083
  }
3084
3084
  exports.parse = parse3;
3085
3085
  }
@@ -3810,12 +3810,12 @@ var require_client = __commonJS({
3810
3810
  this._connect(callback);
3811
3811
  return;
3812
3812
  }
3813
- return new this._Promise((resolve2, reject) => {
3813
+ return new this._Promise((resolve, reject) => {
3814
3814
  this._connect((error2) => {
3815
3815
  if (error2) {
3816
3816
  reject(error2);
3817
3817
  } else {
3818
- resolve2(this);
3818
+ resolve(this);
3819
3819
  }
3820
3820
  });
3821
3821
  });
@@ -4161,8 +4161,8 @@ var require_client = __commonJS({
4161
4161
  readTimeout = config2.query_timeout || this.connectionParameters.query_timeout;
4162
4162
  query = new Query2(config2, values, callback);
4163
4163
  if (!query.callback) {
4164
- result = new this._Promise((resolve2, reject) => {
4165
- query.callback = (err2, res) => err2 ? reject(err2) : resolve2(res);
4164
+ result = new this._Promise((resolve, reject) => {
4165
+ query.callback = (err2, res) => err2 ? reject(err2) : resolve(res);
4166
4166
  }).catch((err2) => {
4167
4167
  Error.captureStackTrace(err2);
4168
4168
  throw err2;
@@ -4239,8 +4239,8 @@ var require_client = __commonJS({
4239
4239
  if (cb) {
4240
4240
  this.connection.once("end", cb);
4241
4241
  } else {
4242
- return new this._Promise((resolve2) => {
4243
- this.connection.once("end", resolve2);
4242
+ return new this._Promise((resolve) => {
4243
+ this.connection.once("end", resolve);
4244
4244
  });
4245
4245
  }
4246
4246
  }
@@ -4289,8 +4289,8 @@ var require_pg_pool = __commonJS({
4289
4289
  const cb = function(err2, client2) {
4290
4290
  err2 ? rej(err2) : res(client2);
4291
4291
  };
4292
- const result = new Promise2(function(resolve2, reject) {
4293
- res = resolve2;
4292
+ const result = new Promise2(function(resolve, reject) {
4293
+ res = resolve;
4294
4294
  rej = reject;
4295
4295
  }).catch((err2) => {
4296
4296
  Error.captureStackTrace(err2);
@@ -4351,7 +4351,7 @@ var require_pg_pool = __commonJS({
4351
4351
  if (typeof Promise2.try === "function") {
4352
4352
  return Promise2.try(f);
4353
4353
  }
4354
- return new Promise2((resolve2) => resolve2(f()));
4354
+ return new Promise2((resolve) => resolve(f()));
4355
4355
  }
4356
4356
  _isFull() {
4357
4357
  return this._clients.length >= this.options.max;
@@ -4744,8 +4744,8 @@ var require_query2 = __commonJS({
4744
4744
  NativeQuery.prototype._getPromise = function() {
4745
4745
  if (this._promise) return this._promise;
4746
4746
  this._promise = new Promise(
4747
- function(resolve2, reject) {
4748
- this._once("end", resolve2);
4747
+ function(resolve, reject) {
4748
+ this._once("end", resolve);
4749
4749
  this._once("error", reject);
4750
4750
  }.bind(this)
4751
4751
  );
@@ -4922,12 +4922,12 @@ var require_client2 = __commonJS({
4922
4922
  this._connect(callback);
4923
4923
  return;
4924
4924
  }
4925
- return new this._Promise((resolve2, reject) => {
4925
+ return new this._Promise((resolve, reject) => {
4926
4926
  this._connect((error2) => {
4927
4927
  if (error2) {
4928
4928
  reject(error2);
4929
4929
  } else {
4930
- resolve2(this);
4930
+ resolve(this);
4931
4931
  }
4932
4932
  });
4933
4933
  });
@@ -4951,8 +4951,8 @@ var require_client2 = __commonJS({
4951
4951
  query = new NativeQuery(config2, values, callback);
4952
4952
  if (!query.callback) {
4953
4953
  let resolveOut, rejectOut;
4954
- result = new this._Promise((resolve2, reject) => {
4955
- resolveOut = resolve2;
4954
+ result = new this._Promise((resolve, reject) => {
4955
+ resolveOut = resolve;
4956
4956
  rejectOut = reject;
4957
4957
  }).catch((err2) => {
4958
4958
  Error.captureStackTrace(err2);
@@ -5012,8 +5012,8 @@ var require_client2 = __commonJS({
5012
5012
  }
5013
5013
  let result;
5014
5014
  if (!cb) {
5015
- result = new this._Promise(function(resolve2, reject) {
5016
- cb = (err2) => err2 ? reject(err2) : resolve2();
5015
+ result = new this._Promise(function(resolve, reject) {
5016
+ cb = (err2) => err2 ? reject(err2) : resolve();
5017
5017
  });
5018
5018
  }
5019
5019
  this.native.end(function() {
@@ -11652,7 +11652,7 @@ var require_crypto = __commonJS({
11652
11652
  MAC_INFO,
11653
11653
  bindingAvailable: !!binding,
11654
11654
  init: (() => {
11655
- return new Promise(async (resolve2, reject) => {
11655
+ return new Promise(async (resolve, reject) => {
11656
11656
  try {
11657
11657
  POLY1305_WASM_MODULE = await require_poly1305()();
11658
11658
  POLY1305_RESULT_MALLOC = POLY1305_WASM_MODULE._malloc(16);
@@ -11664,7 +11664,7 @@ var require_crypto = __commonJS({
11664
11664
  } catch (ex) {
11665
11665
  return reject(ex);
11666
11666
  }
11667
- resolve2();
11667
+ resolve();
11668
11668
  });
11669
11669
  })(),
11670
11670
  NullCipher,
@@ -12922,7 +12922,7 @@ var require_agent = __commonJS({
12922
12922
  "use strict";
12923
12923
  var { Socket } = __require("net");
12924
12924
  var { Duplex } = __require("stream");
12925
- var { resolve: resolve2 } = __require("path");
12925
+ var { resolve } = __require("path");
12926
12926
  var { readFile } = __require("fs");
12927
12927
  var { execFile, spawn: spawn2 } = __require("child_process");
12928
12928
  var { isParsedKey, parseKey } = require_keyParser();
@@ -13058,7 +13058,7 @@ var require_agent = __commonJS({
13058
13058
  const RET_ERR_BINSTDIN = 13;
13059
13059
  const RET_ERR_BINSTDOUT = 14;
13060
13060
  const RET_ERR_BADLEN = 15;
13061
- const EXEPATH = resolve2(__dirname, "..", "util/pagent.exe");
13061
+ const EXEPATH = resolve(__dirname, "..", "util/pagent.exe");
13062
13062
  const ERROR = {
13063
13063
  [RET_ERR_BADARGS]: new Error("Invalid pagent.exe arguments"),
13064
13064
  [RET_ERR_UNAVAILABLE]: new Error("Pageant is not running"),
@@ -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, hostname2, userlocal, keyAlgo, cbSign) {
17106
+ authHostbased(username, pubKey, hostname8, 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(hostname2);
17125
+ const hostnameLen = Buffer.byteLength(hostname8);
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(hostname2, p += 4, hostnameLen);
17145
+ data.utf8Write(hostname8, p += 4, hostnameLen);
17146
17146
  writeUInt32BE(data, userlocalLen, p += hostnameLen);
17147
17147
  data.utf8Write(userlocal, p += 4, userlocalLen);
17148
17148
  cbSign(data, (signature) => {
@@ -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 os5 = __require("os");
25303
+ var os8 = __require("os");
25304
25304
  function autoClose(server, connection) {
25305
25305
  connection.on("close", () => {
25306
25306
  server.getConnections((error2, count) => {
@@ -25315,7 +25315,7 @@ var require_tunnel_ssh = __commonJS({
25315
25315
  if (!serverOptions.port && !serverOptions.path) {
25316
25316
  serverOptions = null;
25317
25317
  }
25318
- return new Promise((resolve2, reject) => {
25318
+ return new Promise((resolve, reject) => {
25319
25319
  let server = net.createServer();
25320
25320
  let errorHandler = function(error2) {
25321
25321
  reject(error2);
@@ -25325,14 +25325,14 @@ var require_tunnel_ssh = __commonJS({
25325
25325
  server.listen(serverOptions);
25326
25326
  server.on("listening", () => {
25327
25327
  process.removeListener("uncaughtException", errorHandler);
25328
- resolve2(server);
25328
+ resolve(server);
25329
25329
  });
25330
25330
  });
25331
25331
  }
25332
25332
  async function createSSHConnection(config2) {
25333
- return new Promise(function(resolve2, reject) {
25333
+ return new Promise(function(resolve, reject) {
25334
25334
  let conn = new Client2();
25335
- conn.on("ready", () => resolve2(conn));
25335
+ conn.on("ready", () => resolve(conn));
25336
25336
  conn.on("error", reject);
25337
25337
  conn.connect(config2);
25338
25338
  });
@@ -25342,7 +25342,7 @@ var require_tunnel_ssh = __commonJS({
25342
25342
  let forwardOptionsLocal = Object.assign({ dstAddr: "0.0.0.0" }, forwardOptions);
25343
25343
  let tunnelOptionsLocal = Object.assign({ autoClose: false, reconnectOnError: false }, tunnelOptions || {});
25344
25344
  let server, sshConnection;
25345
- return new Promise(async function(resolve2, reject) {
25345
+ return new Promise(async function(resolve, reject) {
25346
25346
  try {
25347
25347
  sshConnection = await createSSHConnection(sshOptionslocal);
25348
25348
  addListenerSshConnection(sshConnection);
@@ -25407,7 +25407,7 @@ var require_tunnel_ssh = __commonJS({
25407
25407
  }
25408
25408
  );
25409
25409
  }
25410
- resolve2([server, sshConnection]);
25410
+ resolve([server, sshConnection]);
25411
25411
  });
25412
25412
  }
25413
25413
  exports.createTunnel = createTunnel2;
@@ -25487,7 +25487,7 @@ var require_main = __commonJS({
25487
25487
  "node_modules/dotenv/lib/main.js"(exports, module) {
25488
25488
  var fs8 = __require("fs");
25489
25489
  var path8 = __require("path");
25490
- var os5 = __require("os");
25490
+ var os8 = __require("os");
25491
25491
  var crypto3 = __require("crypto");
25492
25492
  var packageJson2 = require_package2();
25493
25493
  var version2 = packageJson2.version;
@@ -25610,7 +25610,7 @@ var require_main = __commonJS({
25610
25610
  return null;
25611
25611
  }
25612
25612
  function _resolveHome(envPath) {
25613
- return envPath[0] === "~" ? path8.join(os5.homedir(), envPath.slice(1)) : envPath;
25613
+ return envPath[0] === "~" ? path8.join(os8.homedir(), envPath.slice(1)) : envPath;
25614
25614
  }
25615
25615
  function _configVault(options) {
25616
25616
  const debug = Boolean(options && options.debug);
@@ -25882,7 +25882,7 @@ var init_db = __esm({
25882
25882
  dstAddr: process.env.DB_HOST || "localhost",
25883
25883
  dstPort: parseInt(process.env.DB_PORT || "5432")
25884
25884
  };
25885
- return new Promise((resolve2, reject) => {
25885
+ return new Promise((resolve, reject) => {
25886
25886
  (0, import_tunnel_ssh.createTunnel)(tunnelOptions, serverOptions, sshOptions, forwardOptions).then(async ([server, conn]) => {
25887
25887
  this.tunnelServer = server;
25888
25888
  this.tunnelConn = conn;
@@ -25903,7 +25903,7 @@ var init_db = __esm({
25903
25903
  await pool2.query("SELECT 1");
25904
25904
  console.error("\u2705 Database connection verified.");
25905
25905
  this.pool = pool2;
25906
- resolve2(pool2);
25906
+ resolve(pool2);
25907
25907
  } catch (err2) {
25908
25908
  console.error("\u274C Database connection probe failed:", err2);
25909
25909
  await pool2.end().catch(() => {
@@ -25955,11 +25955,11 @@ var init_db = __esm({
25955
25955
  this.tunnelConn = null;
25956
25956
  }
25957
25957
  if (this.tunnelServer) {
25958
- await new Promise((resolve2) => {
25958
+ await new Promise((resolve) => {
25959
25959
  this.tunnelServer.close(() => {
25960
25960
  this.tunnelServer = null;
25961
25961
  console.error("\u{1F512} SSH Tunnel closed.");
25962
- resolve2();
25962
+ resolve();
25963
25963
  });
25964
25964
  });
25965
25965
  }
@@ -28920,7 +28920,7 @@ var require_compile = __commonJS({
28920
28920
  const schOrFunc = root.refs[ref];
28921
28921
  if (schOrFunc)
28922
28922
  return schOrFunc;
28923
- let _sch = resolve2.call(this, root, ref);
28923
+ let _sch = resolve.call(this, root, ref);
28924
28924
  if (_sch === void 0) {
28925
28925
  const schema = (_a3 = root.localRefs) === null || _a3 === void 0 ? void 0 : _a3[ref];
28926
28926
  const { schemaId } = this.opts;
@@ -28947,7 +28947,7 @@ var require_compile = __commonJS({
28947
28947
  function sameSchemaEnv(s1, s2) {
28948
28948
  return s1.schema === s2.schema && s1.root === s2.root && s1.baseId === s2.baseId;
28949
28949
  }
28950
- function resolve2(root, ref) {
28950
+ function resolve(root, ref) {
28951
28951
  let sch;
28952
28952
  while (typeof (sch = this.refs[ref]) == "string")
28953
28953
  ref = sch;
@@ -29522,7 +29522,7 @@ var require_fast_uri = __commonJS({
29522
29522
  }
29523
29523
  return uri;
29524
29524
  }
29525
- function resolve2(baseURI, relativeURI, options) {
29525
+ function resolve(baseURI, relativeURI, options) {
29526
29526
  const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
29527
29527
  const resolved = resolveComponent(parse3(baseURI, schemelessOptions), parse3(relativeURI, schemelessOptions), schemelessOptions, true);
29528
29528
  schemelessOptions.skipEscape = true;
@@ -29749,7 +29749,7 @@ var require_fast_uri = __commonJS({
29749
29749
  var fastUri = {
29750
29750
  SCHEMES,
29751
29751
  normalize,
29752
- resolve: resolve2,
29752
+ resolve,
29753
29753
  resolveComponent,
29754
29754
  equal,
29755
29755
  serialize,
@@ -32777,13 +32777,13 @@ function listMigrationFiles() {
32777
32777
  return fs6.readdirSync(migrationsDir).filter((f) => MIGRATION_FILE_RE.test(f)).sort();
32778
32778
  }
32779
32779
  function runOne(file) {
32780
- return new Promise((resolve2, reject) => {
32780
+ return new Promise((resolve, reject) => {
32781
32781
  const child = spawn(process.execPath, [file], {
32782
32782
  stdio: "inherit",
32783
32783
  env: process.env
32784
32784
  });
32785
32785
  child.on("exit", (code) => {
32786
- if (code === 0) return resolve2();
32786
+ if (code === 0) return resolve();
32787
32787
  reject(new Error(`Migration ${path7.basename(file)} exited with code ${code}`));
32788
32788
  });
32789
32789
  child.on("error", reject);
@@ -44906,7 +44906,7 @@ var Protocol = class {
44906
44906
  return;
44907
44907
  }
44908
44908
  const pollInterval = task2.pollInterval ?? this._options?.defaultTaskPollInterval ?? 1e3;
44909
- await new Promise((resolve2) => setTimeout(resolve2, pollInterval));
44909
+ await new Promise((resolve) => setTimeout(resolve, pollInterval));
44910
44910
  options?.signal?.throwIfAborted();
44911
44911
  }
44912
44912
  } catch (error2) {
@@ -44923,7 +44923,7 @@ var Protocol = class {
44923
44923
  */
44924
44924
  request(request, resultSchema, options) {
44925
44925
  const { relatedRequestId, resumptionToken, onresumptiontoken, task, relatedTask } = options ?? {};
44926
- return new Promise((resolve2, reject) => {
44926
+ return new Promise((resolve, reject) => {
44927
44927
  const earlyReject = (error2) => {
44928
44928
  reject(error2);
44929
44929
  };
@@ -45001,7 +45001,7 @@ var Protocol = class {
45001
45001
  if (!parseResult.success) {
45002
45002
  reject(parseResult.error);
45003
45003
  } else {
45004
- resolve2(parseResult.data);
45004
+ resolve(parseResult.data);
45005
45005
  }
45006
45006
  } catch (error2) {
45007
45007
  reject(error2);
@@ -45262,12 +45262,12 @@ var Protocol = class {
45262
45262
  }
45263
45263
  } catch {
45264
45264
  }
45265
- return new Promise((resolve2, reject) => {
45265
+ return new Promise((resolve, reject) => {
45266
45266
  if (signal.aborted) {
45267
45267
  reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
45268
45268
  return;
45269
45269
  }
45270
- const timeoutId = setTimeout(resolve2, interval);
45270
+ const timeoutId = setTimeout(resolve, interval);
45271
45271
  signal.addEventListener("abort", () => {
45272
45272
  clearTimeout(timeoutId);
45273
45273
  reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
@@ -46367,7 +46367,7 @@ var McpServer = class {
46367
46367
  let task = createTaskResult.task;
46368
46368
  const pollInterval = task.pollInterval ?? 5e3;
46369
46369
  while (task.status !== "completed" && task.status !== "failed" && task.status !== "cancelled") {
46370
- await new Promise((resolve2) => setTimeout(resolve2, pollInterval));
46370
+ await new Promise((resolve) => setTimeout(resolve, pollInterval));
46371
46371
  const updatedTask = await extra.taskStore.getTask(taskId);
46372
46372
  if (!updatedTask) {
46373
46373
  throw new McpError(ErrorCode.InternalError, `Task ${taskId} not found during polling`);
@@ -47016,12 +47016,12 @@ var StdioServerTransport = class {
47016
47016
  this.onclose?.();
47017
47017
  }
47018
47018
  send(message) {
47019
- return new Promise((resolve2) => {
47019
+ return new Promise((resolve) => {
47020
47020
  const json = serializeMessage(message);
47021
47021
  if (this._stdout.write(json)) {
47022
- resolve2();
47022
+ resolve();
47023
47023
  } else {
47024
- this._stdout.once("drain", resolve2);
47024
+ this._stdout.once("drain", resolve);
47025
47025
  }
47026
47026
  });
47027
47027
  }
@@ -47029,6 +47029,7 @@ var StdioServerTransport = class {
47029
47029
 
47030
47030
  // src/tools/manage_knowledge.ts
47031
47031
  init_db();
47032
+ import * as os2 from "node:os";
47032
47033
 
47033
47034
  // src/hot_path.ts
47034
47035
  init_db();
@@ -47046,7 +47047,8 @@ async function insertRawMemory(params) {
47046
47047
  p_tag_id = null,
47047
47048
  d_tag = [],
47048
47049
  embedding = null,
47049
- external_uuid = null
47050
+ external_uuid = null,
47051
+ device_name = null
47050
47052
  } = params;
47051
47053
  const embeddingSql = embedding && embedding.length > 0 ? `[${embedding.join(",")}]` : null;
47052
47054
  const tagProcessed = p_tag_id !== null;
@@ -47056,13 +47058,15 @@ async function insertRawMemory(params) {
47056
47058
  subagent, subagent_model, subagent_role,
47057
47059
  role, message,
47058
47060
  p_tag_id, d_tag, embedding,
47059
- is_pinned, tag_processed, external_uuid
47061
+ is_pinned, tag_processed, external_uuid,
47062
+ device_name
47060
47063
  ) VALUES (
47061
47064
  $1, $2, $3,
47062
47065
  $4, $5, $6,
47063
47066
  $7, $8,
47064
47067
  $9, $10::text[], $11::halfvec,
47065
- $12, $13, $14
47068
+ $12, $13, $14,
47069
+ $15
47066
47070
  )
47067
47071
  ON CONFLICT (external_uuid) WHERE external_uuid IS NOT NULL
47068
47072
  DO NOTHING
@@ -47081,7 +47085,8 @@ async function insertRawMemory(params) {
47081
47085
  embeddingSql,
47082
47086
  is_pinned,
47083
47087
  tagProcessed,
47084
- external_uuid
47088
+ external_uuid,
47089
+ device_name
47085
47090
  ]
47086
47091
  );
47087
47092
  if (result.rows.length === 0) {
@@ -47388,7 +47393,7 @@ var safeJSON = (text) => {
47388
47393
  };
47389
47394
 
47390
47395
  // node_modules/openai/internal/utils/sleep.mjs
47391
- var sleep = (ms) => new Promise((resolve2) => setTimeout(resolve2, ms));
47396
+ var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
47392
47397
 
47393
47398
  // node_modules/openai/version.mjs
47394
47399
  var VERSION = "6.34.0";
@@ -48467,8 +48472,8 @@ function addRequestID(value, response) {
48467
48472
  var _APIPromise_client;
48468
48473
  var APIPromise = class _APIPromise extends Promise {
48469
48474
  constructor(client2, responsePromise, parseResponse2 = defaultParseResponse) {
48470
- super((resolve2) => {
48471
- resolve2(null);
48475
+ super((resolve) => {
48476
+ resolve(null);
48472
48477
  });
48473
48478
  this.responsePromise = responsePromise;
48474
48479
  this.parseResponse = parseResponse2;
@@ -49116,12 +49121,12 @@ var EventStream = class {
49116
49121
  _EventStream_errored.set(this, false);
49117
49122
  _EventStream_aborted.set(this, false);
49118
49123
  _EventStream_catchingPromiseCreated.set(this, false);
49119
- __classPrivateFieldSet(this, _EventStream_connectedPromise, new Promise((resolve2, reject) => {
49120
- __classPrivateFieldSet(this, _EventStream_resolveConnectedPromise, resolve2, "f");
49124
+ __classPrivateFieldSet(this, _EventStream_connectedPromise, new Promise((resolve, reject) => {
49125
+ __classPrivateFieldSet(this, _EventStream_resolveConnectedPromise, resolve, "f");
49121
49126
  __classPrivateFieldSet(this, _EventStream_rejectConnectedPromise, reject, "f");
49122
49127
  }), "f");
49123
- __classPrivateFieldSet(this, _EventStream_endPromise, new Promise((resolve2, reject) => {
49124
- __classPrivateFieldSet(this, _EventStream_resolveEndPromise, resolve2, "f");
49128
+ __classPrivateFieldSet(this, _EventStream_endPromise, new Promise((resolve, reject) => {
49129
+ __classPrivateFieldSet(this, _EventStream_resolveEndPromise, resolve, "f");
49125
49130
  __classPrivateFieldSet(this, _EventStream_rejectEndPromise, reject, "f");
49126
49131
  }), "f");
49127
49132
  __classPrivateFieldGet(this, _EventStream_connectedPromise, "f").catch(() => {
@@ -49205,11 +49210,11 @@ var EventStream = class {
49205
49210
  * const message = await stream.emitted('message') // rejects if the stream errors
49206
49211
  */
49207
49212
  emitted(event) {
49208
- return new Promise((resolve2, reject) => {
49213
+ return new Promise((resolve, reject) => {
49209
49214
  __classPrivateFieldSet(this, _EventStream_catchingPromiseCreated, true, "f");
49210
49215
  if (event !== "error")
49211
49216
  this.once("error", reject);
49212
- this.once(event, resolve2);
49217
+ this.once(event, resolve);
49213
49218
  });
49214
49219
  }
49215
49220
  async done() {
@@ -50148,7 +50153,7 @@ var ChatCompletionStream = class _ChatCompletionStream extends AbstractChatCompl
50148
50153
  if (done) {
50149
50154
  return { value: void 0, done: true };
50150
50155
  }
50151
- return new Promise((resolve2, reject) => readQueue.push({ resolve: resolve2, reject })).then((chunk2) => chunk2 ? { value: chunk2, done: false } : { value: void 0, done: true });
50156
+ return new Promise((resolve, reject) => readQueue.push({ resolve, reject })).then((chunk2) => chunk2 ? { value: chunk2, done: false } : { value: void 0, done: true });
50152
50157
  }
50153
50158
  const chunk = pushQueue.shift();
50154
50159
  return { value: chunk, done: false };
@@ -50980,7 +50985,7 @@ var AssistantStream = class extends EventStream {
50980
50985
  if (done) {
50981
50986
  return { value: void 0, done: true };
50982
50987
  }
50983
- return new Promise((resolve2, reject) => readQueue.push({ resolve: resolve2, reject })).then((chunk2) => chunk2 ? { value: chunk2, done: false } : { value: void 0, done: true });
50988
+ return new Promise((resolve, reject) => readQueue.push({ resolve, reject })).then((chunk2) => chunk2 ? { value: chunk2, done: false } : { value: void 0, done: true });
50984
50989
  }
50985
50990
  const chunk = pushQueue.shift();
50986
50991
  return { value: chunk, done: false };
@@ -52929,7 +52934,7 @@ var ResponseStream = class _ResponseStream extends EventStream {
52929
52934
  if (done) {
52930
52935
  return { value: void 0, done: true };
52931
52936
  }
52932
- return new Promise((resolve2, reject) => readQueue.push({ resolve: resolve2, reject })).then((event2) => event2 ? { value: event2, done: false } : { value: void 0, done: true });
52937
+ return new Promise((resolve, reject) => readQueue.push({ resolve, reject })).then((event2) => event2 ? { value: event2, done: false } : { value: void 0, done: true });
52933
52938
  }
52934
52939
  const event = pushQueue.shift();
52935
52940
  return { value: event, done: false };
@@ -55331,7 +55336,8 @@ function inferProvider(modelName) {
55331
55336
  }
55332
55337
  var DEFAULTS = {
55333
55338
  tagger: { provider: "xai", model_name: "grok-4-1-fast-non-reasoning" },
55334
- librarian: { provider: "xai", model_name: "grok-4-1-fast-non-reasoning" }
55339
+ librarian: { provider: "xai", model_name: "grok-4-1-fast-non-reasoning" },
55340
+ clusterer: { provider: "xai", model_name: "grok-4-1-fast-non-reasoning" }
55335
55341
  };
55336
55342
  function envEnvelope(role) {
55337
55343
  const upper = role.toUpperCase();
@@ -55357,7 +55363,8 @@ function envEnvelope(role) {
55357
55363
  }
55358
55364
  var ROLE_REGISTRY = {
55359
55365
  tagger: envEnvelope("tagger"),
55360
- librarian: envEnvelope("librarian")
55366
+ librarian: envEnvelope("librarian"),
55367
+ clusterer: envEnvelope("clusterer")
55361
55368
  };
55362
55369
  for (const [role, spec] of Object.entries(ROLE_REGISTRY)) {
55363
55370
  try {
@@ -55627,6 +55634,7 @@ function resolveAgentIdentity(server, args) {
55627
55634
  }
55628
55635
 
55629
55636
  // src/tools/manage_knowledge.ts
55637
+ var DEVICE_NAME = os2.hostname();
55630
55638
  function ok(payload) {
55631
55639
  return {
55632
55640
  content: [{ type: "text", text: JSON.stringify(payload, null, 2) }]
@@ -55791,7 +55799,8 @@ ${args.content}` : args.content;
55791
55799
  // 강제 기억은 archive 면제
55792
55800
  p_tag_id,
55793
55801
  d_tag,
55794
- embedding
55802
+ embedding,
55803
+ device_name: DEVICE_NAME
55795
55804
  });
55796
55805
  return ok({
55797
55806
  stored: true,
@@ -56094,6 +56103,7 @@ date_range \uC778\uC2DD \uD615\uC2DD:
56094
56103
 
56095
56104
  // src/briefing.ts
56096
56105
  init_db();
56106
+ import * as os3 from "node:os";
56097
56107
  var RECENT_CURRENT_LIMIT = 8;
56098
56108
  var RECENT_OTHERS_LIMIT = 4;
56099
56109
  var RECENT_MESSAGE_PREVIEW = 100;
@@ -56125,7 +56135,7 @@ async function collectBrief(opts = {}) {
56125
56135
  let recentOthers = [];
56126
56136
  if (currentPlatform) {
56127
56137
  const currentMsgs = await db.query(
56128
- `SELECT role, agent_platform, message, created_at
56138
+ `SELECT role, agent_platform, device_name, message, created_at
56129
56139
  FROM memory
56130
56140
  WHERE user_id = $1
56131
56141
  AND is_active = TRUE
@@ -56137,7 +56147,7 @@ async function collectBrief(opts = {}) {
56137
56147
  );
56138
56148
  recentCurrent = currentMsgs.rows.reverse().map(rowToMsg);
56139
56149
  const othersMsgs = await db.query(
56140
- `SELECT role, agent_platform, message, created_at
56150
+ `SELECT role, agent_platform, device_name, message, created_at
56141
56151
  FROM memory
56142
56152
  WHERE user_id = $1
56143
56153
  AND is_active = TRUE
@@ -56150,7 +56160,7 @@ async function collectBrief(opts = {}) {
56150
56160
  recentOthers = othersMsgs.rows.reverse().map(rowToMsg);
56151
56161
  } else {
56152
56162
  const msgs = await db.query(
56153
- `SELECT role, agent_platform, message, created_at
56163
+ `SELECT role, agent_platform, device_name, message, created_at
56154
56164
  FROM memory
56155
56165
  WHERE user_id = $1
56156
56166
  AND is_active = TRUE
@@ -56180,6 +56190,7 @@ function rowToMsg(r) {
56180
56190
  return {
56181
56191
  role: r.role,
56182
56192
  agent_platform: r.agent_platform,
56193
+ device_name: r.device_name ?? null,
56183
56194
  preview: String(r.message ?? "").slice(0, RECENT_MESSAGE_PREVIEW),
56184
56195
  created_at: r.created_at
56185
56196
  };
@@ -56188,7 +56199,7 @@ function formatBriefMarkdown(brief) {
56188
56199
  const lines = [];
56189
56200
  lines.push(`# Memory Briefing (user: ${brief.user_name})`);
56190
56201
  if (brief.current_platform) {
56191
- lines.push(`Current platform: \`${brief.current_platform}\``);
56202
+ lines.push(`Current platform: \`${brief.current_platform} @ ${os3.hostname()}\``);
56192
56203
  }
56193
56204
  lines.push("");
56194
56205
  if (brief.core_profile) {
@@ -56234,7 +56245,8 @@ function formatBriefMarkdown(brief) {
56234
56245
  }
56235
56246
  function formatMsgLine(m, showPlatform) {
56236
56247
  const dt = m.created_at?.toISOString?.().slice(11, 16) ?? "";
56237
- const platformTag = showPlatform ? `${m.agent_platform} ` : "";
56248
+ const device = m.device_name ? `@${m.device_name} ` : "";
56249
+ const platformTag = showPlatform ? `${m.agent_platform} ${device}` : device;
56238
56250
  const truncated = m.preview.length >= RECENT_MESSAGE_PREVIEW ? "\u2026" : "";
56239
56251
  return `- [${dt} ${platformTag}${m.role}] ${m.preview}${truncated}`;
56240
56252
  }
@@ -56288,6 +56300,394 @@ agent_platform_filter=false.`,
56288
56300
  }
56289
56301
 
56290
56302
  // src/auto_save/save_message_tool.ts
56303
+ import * as os5 from "node:os";
56304
+
56305
+ // src/auto_save/gemini_capture.ts
56306
+ import * as fs2 from "node:fs";
56307
+ import * as path3 from "node:path";
56308
+ import * as os4 from "node:os";
56309
+ import * as crypto2 from "node:crypto";
56310
+ var CLIENT_PLATFORM = "gemini-cli-mcp-client";
56311
+ var DEVICE_NAME2 = os4.hostname();
56312
+ var GEMINI_TMP_ROOT = path3.join(os4.homedir(), ".gemini", "tmp");
56313
+ var FLUSH_DEBOUNCE_MS = 200;
56314
+ var PARSE_RETRY_MS = 80;
56315
+ var POLL_INTERVAL_MS = 3e3;
56316
+ var _state = null;
56317
+ var _watchers = [];
56318
+ var _pollTimer = null;
56319
+ var _flushInProgress = false;
56320
+ var _flushPending = false;
56321
+ var _flushDebounceTimer = null;
56322
+ function extractShortId(filename) {
56323
+ const m = filename.match(/^session-.+?-([0-9a-f]+)\.json$/);
56324
+ return m ? m[1] : filename.replace(/\.json$/, "");
56325
+ }
56326
+ function deriveChatsDirCandidates(cwd) {
56327
+ const cwdHash = crypto2.createHash("sha256").update(cwd).digest("hex");
56328
+ const candidates = [
56329
+ path3.join(GEMINI_TMP_ROOT, cwdHash, "chats"),
56330
+ path3.join(GEMINI_TMP_ROOT, path3.basename(cwd), "chats")
56331
+ ];
56332
+ return candidates.filter((d) => fs2.existsSync(d));
56333
+ }
56334
+ async function readSessionFile(filePath) {
56335
+ for (let attempt = 0; attempt < 2; attempt++) {
56336
+ try {
56337
+ const raw = fs2.readFileSync(filePath, "utf-8");
56338
+ if (!raw.trim()) return null;
56339
+ return JSON.parse(raw);
56340
+ } catch {
56341
+ if (attempt === 0) {
56342
+ await new Promise((r) => setTimeout(r, PARSE_RETRY_MS));
56343
+ continue;
56344
+ }
56345
+ return null;
56346
+ }
56347
+ }
56348
+ return null;
56349
+ }
56350
+ function parseMessage(msg, fallbackId) {
56351
+ let role;
56352
+ if (msg.type === "user") role = "user";
56353
+ else if (msg.type === "gemini") role = "assistant";
56354
+ else return null;
56355
+ let text = "";
56356
+ const c = msg.content;
56357
+ if (typeof c === "string") {
56358
+ text = c;
56359
+ } else if (Array.isArray(c)) {
56360
+ for (const block of c) {
56361
+ if (block && typeof block === "object" && typeof block.text === "string") {
56362
+ text += (text ? "\n" : "") + block.text;
56363
+ }
56364
+ }
56365
+ }
56366
+ text = text.trim();
56367
+ if (!text) return null;
56368
+ if (text.startsWith("System:") || text === "Please continue.") return null;
56369
+ const msgId = typeof msg.id === "string" && msg.id ? msg.id : fallbackId;
56370
+ const agent_model = role === "assistant" && typeof msg.model === "string" ? msg.model : null;
56371
+ return { msgId, role, message: text, agent_model };
56372
+ }
56373
+ async function flushDeltaForFile(filePath, fileState, contentSeen) {
56374
+ return fileState.format === "jsonl" ? flushJsonlFile(filePath, fileState, contentSeen) : flushJsonFile(filePath, fileState, contentSeen);
56375
+ }
56376
+ async function flushJsonFile(filePath, fileState, contentSeen) {
56377
+ const session = await readSessionFile(filePath);
56378
+ if (!session || !Array.isArray(session.messages)) {
56379
+ return { inserted: 0, skipped: 0, dedup: 0 };
56380
+ }
56381
+ const messages = session.messages;
56382
+ if (messages.length <= fileState.cursor) {
56383
+ return { inserted: 0, skipped: 0, dedup: 0 };
56384
+ }
56385
+ if (typeof session.sessionId === "string" && session.sessionId) {
56386
+ fileState.sessionId = session.sessionId;
56387
+ }
56388
+ const userId = await getDefaultUserId();
56389
+ let inserted = 0, skipped = 0, dedup = 0;
56390
+ for (let i = fileState.cursor; i < messages.length; i++) {
56391
+ const msg = messages[i];
56392
+ const fallbackId = `${fileState.sessionId}-${i}`;
56393
+ const parsed = parseMessage(msg, fallbackId);
56394
+ if (!parsed) {
56395
+ skipped++;
56396
+ continue;
56397
+ }
56398
+ const ck = `${parsed.role}::${parsed.message}`;
56399
+ if (contentSeen.has(ck)) {
56400
+ dedup++;
56401
+ continue;
56402
+ }
56403
+ contentSeen.add(ck);
56404
+ const externalUuid = `gemini:${fileState.sessionId}:${parsed.msgId}`;
56405
+ const agentModel = parsed.role === "user" ? null : parsed.agent_model ?? "unknown";
56406
+ try {
56407
+ const result = await insertRawMemory({
56408
+ user_id: userId,
56409
+ agent_platform: CLIENT_PLATFORM,
56410
+ agent_model: agentModel,
56411
+ role: parsed.role,
56412
+ message: parsed.message,
56413
+ external_uuid: externalUuid,
56414
+ device_name: DEVICE_NAME2
56415
+ });
56416
+ if (result.inserted) inserted++;
56417
+ else dedup++;
56418
+ } catch (err2) {
56419
+ console.error(`\u26A0\uFE0F [Gemini] insert failed at index ${i}:`, err2);
56420
+ skipped++;
56421
+ }
56422
+ }
56423
+ fileState.cursor = messages.length;
56424
+ return { inserted, skipped, dedup };
56425
+ }
56426
+ async function flushJsonlFile(filePath, fileState, contentSeen) {
56427
+ if (!fs2.existsSync(filePath)) return { inserted: 0, skipped: 0, dedup: 0 };
56428
+ let stat;
56429
+ try {
56430
+ stat = fs2.statSync(filePath);
56431
+ } catch {
56432
+ return { inserted: 0, skipped: 0, dedup: 0 };
56433
+ }
56434
+ if (stat.size <= fileState.cursor) return { inserted: 0, skipped: 0, dedup: 0 };
56435
+ const length = stat.size - fileState.cursor;
56436
+ const fd = fs2.openSync(filePath, "r");
56437
+ let raw;
56438
+ try {
56439
+ const buf = Buffer.alloc(length);
56440
+ fs2.readSync(fd, buf, 0, length, fileState.cursor);
56441
+ raw = buf.toString("utf-8");
56442
+ } finally {
56443
+ fs2.closeSync(fd);
56444
+ }
56445
+ const lastNl = raw.lastIndexOf("\n");
56446
+ if (lastNl === -1) return { inserted: 0, skipped: 0, dedup: 0 };
56447
+ const parsable = raw.slice(0, lastNl);
56448
+ const advanceBy = Buffer.byteLength(parsable, "utf-8") + 1;
56449
+ const newCursor = fileState.cursor + advanceBy;
56450
+ const userId = await getDefaultUserId();
56451
+ let inserted = 0, skipped = 0, dedup = 0;
56452
+ let lineIdx = fileState.cursor === 0 ? 0 : -1;
56453
+ for (const line of parsable.split("\n")) {
56454
+ const trimmed = line.trim();
56455
+ if (!trimmed) {
56456
+ lineIdx++;
56457
+ continue;
56458
+ }
56459
+ let obj;
56460
+ try {
56461
+ obj = JSON.parse(trimmed);
56462
+ } catch {
56463
+ lineIdx++;
56464
+ continue;
56465
+ }
56466
+ if (lineIdx === 0 && typeof obj.sessionId === "string" && obj.sessionId) {
56467
+ fileState.sessionId = obj.sessionId;
56468
+ lineIdx++;
56469
+ continue;
56470
+ }
56471
+ lineIdx++;
56472
+ if (obj.$set !== void 0) continue;
56473
+ const parsed = parseMessage(obj, `${fileState.sessionId}-${lineIdx}`);
56474
+ if (!parsed) {
56475
+ skipped++;
56476
+ continue;
56477
+ }
56478
+ const ck = `${parsed.role}::${parsed.message}`;
56479
+ if (contentSeen.has(ck)) {
56480
+ dedup++;
56481
+ continue;
56482
+ }
56483
+ contentSeen.add(ck);
56484
+ const externalUuid = `gemini:${fileState.sessionId}:${parsed.msgId}`;
56485
+ const agentModel = parsed.role === "user" ? null : parsed.agent_model ?? "unknown";
56486
+ try {
56487
+ const result = await insertRawMemory({
56488
+ user_id: userId,
56489
+ agent_platform: CLIENT_PLATFORM,
56490
+ agent_model: agentModel,
56491
+ role: parsed.role,
56492
+ message: parsed.message,
56493
+ external_uuid: externalUuid,
56494
+ device_name: DEVICE_NAME2
56495
+ });
56496
+ if (result.inserted) inserted++;
56497
+ else dedup++;
56498
+ } catch (err2) {
56499
+ console.error(`\u26A0\uFE0F [Gemini] jsonl insert failed:`, err2);
56500
+ skipped++;
56501
+ }
56502
+ }
56503
+ fileState.cursor = newCursor;
56504
+ return { inserted, skipped, dedup };
56505
+ }
56506
+ function isCaptureArmed() {
56507
+ return _state !== null && _state.chatsDirs.length > 0;
56508
+ }
56509
+ async function flushAllFiles() {
56510
+ if (!_state) return { inserted: 0, skipped: 0, dedup: 0 };
56511
+ let totalI = 0, totalS = 0, totalD = 0;
56512
+ const contentSeen = /* @__PURE__ */ new Set();
56513
+ for (const dir of _state.chatsDirs) {
56514
+ if (!fs2.existsSync(dir)) continue;
56515
+ let entries;
56516
+ try {
56517
+ entries = fs2.readdirSync(dir, { withFileTypes: true });
56518
+ } catch {
56519
+ continue;
56520
+ }
56521
+ for (const entry of entries) {
56522
+ if (!entry.isFile()) continue;
56523
+ if (!entry.name.startsWith("session-")) continue;
56524
+ if (!entry.name.endsWith(".json") && !entry.name.endsWith(".jsonl")) continue;
56525
+ const filePath = path3.join(dir, entry.name);
56526
+ let fileState = _state.files.get(filePath);
56527
+ if (!fileState) {
56528
+ const sessionId = extractShortId(entry.name);
56529
+ const format = entry.name.endsWith(".jsonl") ? "jsonl" : "json";
56530
+ fileState = { cursor: 0, format, sessionId };
56531
+ _state.files.set(filePath, fileState);
56532
+ console.error(`\u{1F4DD} [Gemini] new session detected: ${entry.name} (${format})`);
56533
+ }
56534
+ const r = await flushDeltaForFile(filePath, fileState, contentSeen);
56535
+ totalI += r.inserted;
56536
+ totalS += r.skipped;
56537
+ totalD += r.dedup;
56538
+ }
56539
+ }
56540
+ return { inserted: totalI, skipped: totalS, dedup: totalD };
56541
+ }
56542
+ async function flushWithMutex() {
56543
+ if (_flushInProgress) {
56544
+ _flushPending = true;
56545
+ return;
56546
+ }
56547
+ _flushInProgress = true;
56548
+ try {
56549
+ const r = await flushAllFiles();
56550
+ if (r.inserted > 0 || r.skipped > 0 || r.dedup > 0) {
56551
+ console.error(`\u{1F4DD} [Gemini] live flush: inserted=${r.inserted}, dedup=${r.dedup}, skipped=${r.skipped}`);
56552
+ }
56553
+ } catch (err2) {
56554
+ console.error("\u26A0\uFE0F [Gemini] live flush error:", err2);
56555
+ } finally {
56556
+ _flushInProgress = false;
56557
+ if (_flushPending) {
56558
+ _flushPending = false;
56559
+ setImmediate(() => {
56560
+ void flushWithMutex();
56561
+ });
56562
+ }
56563
+ }
56564
+ }
56565
+ function scheduleFlush() {
56566
+ if (_flushDebounceTimer) clearTimeout(_flushDebounceTimer);
56567
+ _flushDebounceTimer = setTimeout(() => {
56568
+ _flushDebounceTimer = null;
56569
+ void flushWithMutex();
56570
+ }, FLUSH_DEBOUNCE_MS);
56571
+ }
56572
+ function captureSessionStart(cwd) {
56573
+ const chatsDirs = deriveChatsDirCandidates(cwd);
56574
+ _state = { cwd, chatsDirs, files: /* @__PURE__ */ new Map() };
56575
+ if (chatsDirs.length === 0) {
56576
+ return;
56577
+ }
56578
+ let count = 0;
56579
+ for (const dir of chatsDirs) {
56580
+ let entries;
56581
+ try {
56582
+ entries = fs2.readdirSync(dir, { withFileTypes: true });
56583
+ } catch {
56584
+ continue;
56585
+ }
56586
+ for (const entry of entries) {
56587
+ if (!entry.isFile()) continue;
56588
+ if (!entry.name.startsWith("session-")) continue;
56589
+ if (!entry.name.endsWith(".json") && !entry.name.endsWith(".jsonl")) continue;
56590
+ const filePath = path3.join(dir, entry.name);
56591
+ const format = entry.name.endsWith(".jsonl") ? "jsonl" : "json";
56592
+ const sessionId = extractShortId(entry.name);
56593
+ let cursor = 0;
56594
+ let realSessionId = sessionId;
56595
+ if (format === "json") {
56596
+ let session = null;
56597
+ try {
56598
+ session = JSON.parse(fs2.readFileSync(filePath, "utf-8"));
56599
+ } catch {
56600
+ }
56601
+ cursor = session && Array.isArray(session.messages) ? session.messages.length : 0;
56602
+ if (session?.sessionId) realSessionId = session.sessionId;
56603
+ } else {
56604
+ try {
56605
+ cursor = fs2.statSync(filePath).size;
56606
+ } catch {
56607
+ }
56608
+ try {
56609
+ const fd = fs2.openSync(filePath, "r");
56610
+ const buf = Buffer.alloc(512);
56611
+ const n = fs2.readSync(fd, buf, 0, 512, 0);
56612
+ fs2.closeSync(fd);
56613
+ const firstLine = buf.slice(0, n).toString("utf-8").split("\n")[0].trim();
56614
+ const header = JSON.parse(firstLine);
56615
+ if (typeof header.sessionId === "string" && header.sessionId) {
56616
+ realSessionId = header.sessionId;
56617
+ }
56618
+ } catch {
56619
+ }
56620
+ }
56621
+ _state.files.set(filePath, { cursor, format, sessionId: realSessionId });
56622
+ count++;
56623
+ }
56624
+ }
56625
+ console.error(
56626
+ `\u{1F4DD} [Gemini] capture armed: ${count} session(s) across ${chatsDirs.length} chats dir(s)`
56627
+ );
56628
+ armDirWatchers();
56629
+ }
56630
+ function armDirWatchers() {
56631
+ if (!_state) return;
56632
+ if (_watchers.length > 0) return;
56633
+ for (const dir of _state.chatsDirs) {
56634
+ try {
56635
+ const w = fs2.watch(dir, (_evt, filename) => {
56636
+ if (filename && !filename.endsWith(".json") && !filename.endsWith(".jsonl")) return;
56637
+ scheduleFlush();
56638
+ });
56639
+ _watchers.push(w);
56640
+ } catch (err2) {
56641
+ console.error(`\u26A0\uFE0F [Gemini] fs.watch ${dir} failed:`, err2);
56642
+ }
56643
+ }
56644
+ if (_watchers.length > 0) {
56645
+ console.error(`\u{1F4DD} [Gemini] dir watcher(s) armed: ${_watchers.length}`);
56646
+ }
56647
+ _pollTimer = setInterval(() => {
56648
+ void flushWithMutex();
56649
+ }, POLL_INTERVAL_MS);
56650
+ }
56651
+ function disarmDirWatchers() {
56652
+ if (_flushDebounceTimer) {
56653
+ clearTimeout(_flushDebounceTimer);
56654
+ _flushDebounceTimer = null;
56655
+ }
56656
+ if (_pollTimer) {
56657
+ clearInterval(_pollTimer);
56658
+ _pollTimer = null;
56659
+ }
56660
+ for (const w of _watchers) {
56661
+ try {
56662
+ w.close();
56663
+ } catch {
56664
+ }
56665
+ }
56666
+ _watchers.length = 0;
56667
+ }
56668
+ async function captureSessionEnd() {
56669
+ disarmDirWatchers();
56670
+ const waitStart = Date.now();
56671
+ while (_flushInProgress && Date.now() - waitStart < 2e3) {
56672
+ await new Promise((r) => setTimeout(r, 50));
56673
+ }
56674
+ if (!_state || _state.chatsDirs.length === 0) {
56675
+ return { inserted: 0, skipped: 0, error: "session not armed" };
56676
+ }
56677
+ _flushInProgress = true;
56678
+ try {
56679
+ const r = await flushAllFiles();
56680
+ if (r.inserted > 0 || r.skipped > 0 || r.dedup > 0) {
56681
+ console.error(`\u{1F4DD} [Gemini] final flush: inserted=${r.inserted}, dedup=${r.dedup}, skipped=${r.skipped}`);
56682
+ }
56683
+ return { inserted: r.inserted, skipped: r.skipped };
56684
+ } finally {
56685
+ _flushInProgress = false;
56686
+ }
56687
+ }
56688
+
56689
+ // src/auto_save/save_message_tool.ts
56690
+ var DEVICE_NAME3 = os5.hostname();
56291
56691
  function registerSaveMessage(server) {
56292
56692
  server.registerTool(
56293
56693
  "save_message",
@@ -56320,6 +56720,14 @@ subagent \uCEE8\uD14D\uC2A4\uD2B8\uB77C\uBA74 subagent=true + subagent_model + s
56320
56720
  async (args) => {
56321
56721
  const userId = await getDefaultUserId();
56322
56722
  const id = resolveAgentIdentity(server, args);
56723
+ if (id.agent_platform === "gemini-cli-mcp-client" && isCaptureArmed()) {
56724
+ return {
56725
+ content: [{
56726
+ type: "text",
56727
+ text: JSON.stringify({ stored: false, skipped: "passive capture active" }, null, 2)
56728
+ }]
56729
+ };
56730
+ }
56323
56731
  const agentModel = args.role === "user" ? null : id.agent_model;
56324
56732
  const inserted = await insertRawMemory({
56325
56733
  user_id: userId,
@@ -56329,7 +56737,8 @@ subagent \uCEE8\uD14D\uC2A4\uD2B8\uB77C\uBA74 subagent=true + subagent_model + s
56329
56737
  subagent_model: id.subagent_model,
56330
56738
  subagent_role: id.subagent_role,
56331
56739
  role: args.role,
56332
- message: args.message
56740
+ message: args.message,
56741
+ device_name: DEVICE_NAME3
56333
56742
  // tag/embed NULL — Cold Path가 background 처리 (Hot Path latency 보장)
56334
56743
  });
56335
56744
  return {
@@ -56357,13 +56766,143 @@ function registerTools(server) {
56357
56766
 
56358
56767
  // src/cold_path/worker.ts
56359
56768
  init_db();
56769
+
56770
+ // src/cold_path/dtag_promoter.ts
56771
+ init_db();
56772
+ function envInt(name, fallback) {
56773
+ const raw = process.env[name];
56774
+ if (!raw) return fallback;
56775
+ const n = Number(raw);
56776
+ return Number.isFinite(n) && n >= 1 ? n : fallback;
56777
+ }
56778
+ var CLUSTER_SYSTEM = `You are a keyword clustering assistant for a personal memory system.
56779
+
56780
+ Given a list of d_tags (short hyphenated keywords) with their occurrence counts,
56781
+ group semantically similar tags that refer to the same project or topic.
56782
+
56783
+ OUTPUT strict JSON array:
56784
+ [
56785
+ { "canonical": "<best-slug>", "members": ["<tag1>", "<tag2>", ...] },
56786
+ ...
56787
+ ]
56788
+
56789
+ Rules:
56790
+ - canonical must be one of the input tags (pick the most descriptive one) or a clean slug if none fit
56791
+ - canonical must be lowercase, hyphenated (e.g. "yt-signal-finder")
56792
+ - Only group tags that clearly refer to the same project/topic
56793
+ - Tags with no similar counterparts become their own single-member cluster
56794
+ - Do NOT merge unrelated topics just because they share one word`;
56795
+ async function clusterDTags(tags) {
56796
+ if (tags.length === 0) return [];
56797
+ const tagList = tags.map((t) => `${t.tag} (${t.cnt}x)`).join(", ");
56798
+ const userPrompt = `Cluster these d_tags by project/topic:
56799
+ ${tagList}`;
56800
+ let raw;
56801
+ try {
56802
+ raw = await callRole("clusterer", {
56803
+ system: CLUSTER_SYSTEM,
56804
+ user: userPrompt,
56805
+ responseFormat: "json",
56806
+ maxTokens: 512
56807
+ });
56808
+ } catch (err2) {
56809
+ console.error("\u26A0\uFE0F [DTagPromoter] clusterer call failed, falling back to no clustering:", err2);
56810
+ return tags.map((t) => ({ canonical: t.tag, members: [t.tag], total: t.cnt }));
56811
+ }
56812
+ let parsed;
56813
+ try {
56814
+ parsed = JSON.parse(raw);
56815
+ if (!Array.isArray(parsed)) throw new Error("not an array");
56816
+ } catch {
56817
+ console.error("\u26A0\uFE0F [DTagPromoter] clusterer returned invalid JSON, falling back:", raw.slice(0, 200));
56818
+ return tags.map((t) => ({ canonical: t.tag, members: [t.tag], total: t.cnt }));
56819
+ }
56820
+ const freqMap = new Map(tags.map((t) => [t.tag, t.cnt]));
56821
+ return parsed.map((c) => {
56822
+ const canonical = String(c.canonical || "").toLowerCase().trim().replace(/\s+/g, "-");
56823
+ const members = Array.isArray(c.members) ? c.members.map((m) => String(m).toLowerCase().trim()).filter(Boolean) : [canonical];
56824
+ const total = members.reduce((sum, m) => sum + (freqMap.get(m) ?? 0), 0);
56825
+ return { canonical, members, total };
56826
+ });
56827
+ }
56828
+ async function runDtagPromotion() {
56829
+ const minCount = envInt("DTAG_PROMOTE_MIN_COUNT", 10);
56830
+ const windowDays = envInt("DTAG_PROMOTE_WINDOW_DAYS", 30);
56831
+ const userId = await getDefaultUserId();
56832
+ const freqResult = await db.query(
56833
+ `SELECT unnest(d_tag) AS tag, COUNT(*)::int AS cnt
56834
+ FROM memory
56835
+ WHERE user_id = $1
56836
+ AND tag_processed = TRUE
56837
+ AND is_active = TRUE
56838
+ AND created_at >= NOW() - ($2 || ' days')::INTERVAL
56839
+ GROUP BY tag
56840
+ HAVING COUNT(*) >= 2
56841
+ ORDER BY cnt DESC
56842
+ LIMIT 50`,
56843
+ [userId, String(windowDays)]
56844
+ );
56845
+ if (freqResult.rows.length === 0) return { promoted: [], retrotagged: 0 };
56846
+ const tags = freqResult.rows.map((r) => ({
56847
+ tag: String(r.tag).toLowerCase().trim(),
56848
+ cnt: Number(r.cnt)
56849
+ })).filter((t) => t.tag.length > 0);
56850
+ const clusters = await clusterDTags(tags);
56851
+ const toPromote = clusters.filter((c) => c.total >= minCount);
56852
+ if (toPromote.length === 0) return { promoted: [], retrotagged: 0 };
56853
+ const summary = { promoted: [], retrotagged: 0 };
56854
+ for (const cluster of toPromote) {
56855
+ if (!cluster.canonical) continue;
56856
+ const upsert = await db.query(
56857
+ `INSERT INTO project_tags (name)
56858
+ VALUES ($1)
56859
+ ON CONFLICT (name) DO NOTHING
56860
+ RETURNING id`,
56861
+ [cluster.canonical]
56862
+ );
56863
+ let pTagId;
56864
+ if (upsert.rows.length > 0) {
56865
+ pTagId = Number(upsert.rows[0].id);
56866
+ summary.promoted.push(cluster.canonical);
56867
+ console.error(`\u{1F3F7}\uFE0F [DTagPromoter] promoted "${cluster.canonical}" (total=${cluster.total}, members=${cluster.members.join(", ")})`);
56868
+ invalidateCandidateCache();
56869
+ } else {
56870
+ const existing = await db.query(
56871
+ `SELECT id FROM project_tags WHERE name = $1 LIMIT 1`,
56872
+ [cluster.canonical]
56873
+ );
56874
+ if (existing.rows.length === 0) continue;
56875
+ pTagId = Number(existing.rows[0].id);
56876
+ }
56877
+ for (const memberTag of cluster.members) {
56878
+ const updated = await db.query(
56879
+ `UPDATE memory
56880
+ SET p_tag_id = $1, updated_at = NOW()
56881
+ WHERE user_id = $2
56882
+ AND tag_processed = TRUE
56883
+ AND p_tag_id IS NULL
56884
+ AND $3 = ANY(d_tag)
56885
+ RETURNING id`,
56886
+ [pTagId, userId, memberTag]
56887
+ );
56888
+ summary.retrotagged += updated.rows.length;
56889
+ }
56890
+ if (summary.retrotagged > 0) {
56891
+ console.error(`\u{1F3F7}\uFE0F [DTagPromoter] retrotagged ${summary.retrotagged} rows with "${cluster.canonical}"`);
56892
+ }
56893
+ }
56894
+ return summary;
56895
+ }
56896
+
56897
+ // src/cold_path/worker.ts
56360
56898
  var intervalTimer = null;
56361
56899
  var warmupTimer = null;
56362
56900
  var running = false;
56901
+ var tickCount = 0;
56363
56902
  var DEFAULT_INTERVAL_SEC = 60;
56364
56903
  var DEFAULT_BATCH = 5;
56365
56904
  var DEFAULT_WARMUP_SEC = 30;
56366
- function envInt(name, fallback) {
56905
+ function envInt2(name, fallback) {
56367
56906
  const raw = process.env[name];
56368
56907
  if (!raw) return fallback;
56369
56908
  const n = Number(raw);
@@ -56481,7 +57020,7 @@ async function recordError(client2, rowId, err2) {
56481
57020
  async function tick() {
56482
57021
  if (running) return 0;
56483
57022
  running = true;
56484
- const batchSize = envInt("COLD_PATH_BATCH_SIZE", DEFAULT_BATCH);
57023
+ const batchSize = envInt2("COLD_PATH_BATCH_SIZE", DEFAULT_BATCH);
56485
57024
  const client2 = await db.getClient();
56486
57025
  try {
56487
57026
  await client2.query("BEGIN");
@@ -56515,19 +57054,35 @@ async function tick() {
56515
57054
  running = false;
56516
57055
  }
56517
57056
  }
57057
+ var DTAG_PROMOTE_EVERY_N_TICKS = 10;
57058
+ async function maybeRunPromotion() {
57059
+ if (process.env.DTAG_PROMOTE_ENABLED === "false") return;
57060
+ tickCount++;
57061
+ if (tickCount % DTAG_PROMOTE_EVERY_N_TICKS !== 0) return;
57062
+ try {
57063
+ const result = await runDtagPromotion();
57064
+ if (result.promoted.length > 0 || result.retrotagged > 0) {
57065
+ console.error(`\u{1F3F7}\uFE0F [DTagPromoter] promoted=${result.promoted.length} tags, retrotagged=${result.retrotagged} rows`);
57066
+ }
57067
+ } catch (err2) {
57068
+ console.error("\u26A0\uFE0F [DTagPromoter] promotion error (non-blocking):", err2);
57069
+ }
57070
+ }
56518
57071
  function startColdPathWorker() {
56519
57072
  if (process.env.COLD_PATH_ENABLED === "false") {
56520
57073
  console.error("\u{1F535} [ColdPath] disabled (COLD_PATH_ENABLED=false)");
56521
57074
  return;
56522
57075
  }
56523
- const intervalSec = envInt("COLD_PATH_INTERVAL_SEC", DEFAULT_INTERVAL_SEC);
56524
- const warmupSec = envInt("COLD_PATH_WARMUP_SEC", DEFAULT_WARMUP_SEC);
57076
+ const intervalSec = envInt2("COLD_PATH_INTERVAL_SEC", DEFAULT_INTERVAL_SEC);
57077
+ const warmupSec = envInt2("COLD_PATH_WARMUP_SEC", DEFAULT_WARMUP_SEC);
56525
57078
  console.error(`\u{1F535} [ColdPath] starting \u2014 warmup ${warmupSec}s, interval ${intervalSec}s`);
56526
57079
  warmupTimer = setTimeout(() => {
56527
57080
  intervalTimer = setInterval(() => {
56528
57081
  tick().catch((err2) => console.error("\u274C [ColdPath] unhandled tick error:", err2));
57082
+ maybeRunPromotion().catch((err2) => console.error("\u274C [DTagPromoter] unhandled error:", err2));
56529
57083
  }, intervalSec * 1e3);
56530
57084
  tick().catch((err2) => console.error("\u274C [ColdPath] unhandled first tick:", err2));
57085
+ maybeRunPromotion().catch((err2) => console.error("\u274C [DTagPromoter] unhandled error:", err2));
56531
57086
  }, warmupSec * 1e3);
56532
57087
  }
56533
57088
  function stopColdPathWorker() {
@@ -56556,50 +57111,58 @@ async function drainColdPath(maxTicks = 12) {
56556
57111
  }
56557
57112
 
56558
57113
  // src/auto_save/jsonl_capture.ts
56559
- import * as fs2 from "node:fs";
56560
- import * as path3 from "node:path";
56561
- import * as os2 from "node:os";
56562
- var CLIENT_PLATFORM = "claude-code";
56563
- var PROJECTS_ROOT = path3.join(os2.homedir(), ".claude", "projects");
56564
- var FLUSH_DEBOUNCE_MS = 200;
56565
- var _state = null;
57114
+ import * as fs3 from "node:fs";
57115
+ import * as path4 from "node:path";
57116
+ import * as os6 from "node:os";
57117
+ 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;
56566
57122
  var _watcher = null;
56567
- var _flushInProgress = false;
56568
- var _flushPending = false;
56569
- var _flushDebounceTimer = null;
57123
+ var _flushInProgress2 = false;
57124
+ var _flushPending2 = false;
57125
+ var _flushDebounceTimer2 = null;
56570
57126
  function findProjectDir(cwd) {
57127
+ const slug = cwd.replace(/\//g, "-");
57128
+ const slugPath = path4.join(PROJECTS_ROOT, slug);
57129
+ try {
57130
+ if (fs3.statSync(slugPath).isDirectory()) return slugPath;
57131
+ } catch {
57132
+ }
56571
57133
  let subdirs;
56572
57134
  try {
56573
- subdirs = fs2.readdirSync(PROJECTS_ROOT, { withFileTypes: true });
57135
+ subdirs = fs3.readdirSync(PROJECTS_ROOT, { withFileTypes: true });
56574
57136
  } catch {
56575
57137
  return null;
56576
57138
  }
56577
57139
  for (const subdir of subdirs) {
56578
57140
  if (!subdir.isDirectory()) continue;
56579
- const dirPath = path3.join(PROJECTS_ROOT, subdir.name);
57141
+ const dirPath = path4.join(PROJECTS_ROOT, subdir.name);
56580
57142
  let files;
56581
57143
  try {
56582
- files = fs2.readdirSync(dirPath, { withFileTypes: true });
57144
+ files = fs3.readdirSync(dirPath, { withFileTypes: true });
56583
57145
  } catch {
56584
57146
  continue;
56585
57147
  }
56586
- const jsonlFile = files.find((f) => f.isFile() && f.name.endsWith(".jsonl"));
56587
- if (!jsonlFile) continue;
56588
- const foundCwd = readCwdFromJsonl(path3.join(dirPath, jsonlFile.name));
56589
- if (foundCwd === cwd) return dirPath;
57148
+ for (const f of files) {
57149
+ if (!f.isFile() || !f.name.endsWith(".jsonl")) continue;
57150
+ const foundCwd = readCwdFromJsonl(path4.join(dirPath, f.name));
57151
+ if (foundCwd === cwd) return dirPath;
57152
+ }
56590
57153
  }
56591
57154
  return null;
56592
57155
  }
56593
57156
  function readCwdFromJsonl(jsonlPath) {
56594
57157
  let fd;
56595
57158
  try {
56596
- fd = fs2.openSync(jsonlPath, "r");
57159
+ fd = fs3.openSync(jsonlPath, "r");
56597
57160
  } catch {
56598
57161
  return null;
56599
57162
  }
56600
57163
  try {
56601
57164
  const buf = Buffer.alloc(2048);
56602
- const bytesRead = fs2.readSync(fd, buf, 0, 2048, 0);
57165
+ const bytesRead = fs3.readSync(fd, buf, 0, 2048, 0);
56603
57166
  const raw = buf.slice(0, bytesRead).toString("utf-8");
56604
57167
  for (const line of raw.split("\n")) {
56605
57168
  if (!line.trim()) continue;
@@ -56611,35 +57174,35 @@ function readCwdFromJsonl(jsonlPath) {
56611
57174
  }
56612
57175
  return null;
56613
57176
  } finally {
56614
- fs2.closeSync(fd);
57177
+ fs3.closeSync(fd);
56615
57178
  }
56616
57179
  }
56617
- function captureSessionStart(cwd) {
56618
- _state = { cwd, projectDir: null, files: /* @__PURE__ */ new Map() };
57180
+ function captureSessionStart2(cwd) {
57181
+ _state2 = { cwd, projectDir: null, files: /* @__PURE__ */ new Map() };
56619
57182
  const projectDir = findProjectDir(cwd);
56620
57183
  if (!projectDir) {
56621
57184
  return;
56622
57185
  }
56623
- _state.projectDir = projectDir;
57186
+ _state2.projectDir = projectDir;
56624
57187
  let entries;
56625
57188
  try {
56626
- entries = fs2.readdirSync(projectDir, { withFileTypes: true });
57189
+ entries = fs3.readdirSync(projectDir, { withFileTypes: true });
56627
57190
  } catch {
56628
57191
  return;
56629
57192
  }
56630
57193
  for (const entry of entries) {
56631
57194
  if (!entry.isFile() || !entry.name.endsWith(".jsonl")) continue;
56632
- const jsonlPath = path3.join(projectDir, entry.name);
57195
+ const jsonlPath = path4.join(projectDir, entry.name);
56633
57196
  let stat;
56634
57197
  try {
56635
- stat = fs2.statSync(jsonlPath);
57198
+ stat = fs3.statSync(jsonlPath);
56636
57199
  } catch {
56637
57200
  continue;
56638
57201
  }
56639
57202
  const sessionId = entry.name.replace(/\.jsonl$/, "");
56640
- _state.files.set(jsonlPath, { cursorBytes: stat.size, sessionId });
57203
+ _state2.files.set(jsonlPath, { cursorBytes: stat.size, sessionId, contentSeen: /* @__PURE__ */ new Set() });
56641
57204
  }
56642
- console.error(`\u{1F4DD} [JSONL] capture armed: ${_state.files.size} jsonl(s) in ${path3.basename(projectDir)}/`);
57205
+ console.error(`\u{1F4DD} [JSONL] capture armed: ${_state2.files.size} jsonl(s) in ${path4.basename(projectDir)}/`);
56643
57206
  armDirWatcher();
56644
57207
  }
56645
57208
  function parseEntry(line) {
@@ -56678,24 +57241,24 @@ function parseEntry(line) {
56678
57241
  agent_model
56679
57242
  };
56680
57243
  }
56681
- async function flushDeltaForFile(jsonlPath, fileState) {
56682
- if (!fs2.existsSync(jsonlPath)) return { inserted: 0, skipped: 0, dedup: 0 };
57244
+ async function flushDeltaForFile2(jsonlPath, fileState) {
57245
+ if (!fs3.existsSync(jsonlPath)) return { inserted: 0, skipped: 0, dedup: 0 };
56683
57246
  let stat;
56684
57247
  try {
56685
- stat = fs2.statSync(jsonlPath);
57248
+ stat = fs3.statSync(jsonlPath);
56686
57249
  } catch {
56687
57250
  return { inserted: 0, skipped: 0, dedup: 0 };
56688
57251
  }
56689
57252
  if (stat.size <= fileState.cursorBytes) return { inserted: 0, skipped: 0, dedup: 0 };
56690
57253
  const length = stat.size - fileState.cursorBytes;
56691
- const fd = fs2.openSync(jsonlPath, "r");
57254
+ const fd = fs3.openSync(jsonlPath, "r");
56692
57255
  let raw;
56693
57256
  try {
56694
57257
  const buf = Buffer.alloc(length);
56695
- fs2.readSync(fd, buf, 0, length, fileState.cursorBytes);
57258
+ fs3.readSync(fd, buf, 0, length, fileState.cursorBytes);
56696
57259
  raw = buf.toString("utf-8");
56697
57260
  } finally {
56698
- fs2.closeSync(fd);
57261
+ fs3.closeSync(fd);
56699
57262
  }
56700
57263
  const lastNl = raw.lastIndexOf("\n");
56701
57264
  if (lastNl === -1) return { inserted: 0, skipped: 0, dedup: 0 };
@@ -56713,16 +57276,23 @@ async function flushDeltaForFile(jsonlPath, fileState) {
56713
57276
  skipped++;
56714
57277
  continue;
56715
57278
  }
57279
+ const ck = `${parsed.role}::${parsed.message}`;
57280
+ if (fileState.contentSeen.has(ck)) {
57281
+ dedup++;
57282
+ continue;
57283
+ }
57284
+ fileState.contentSeen.add(ck);
56716
57285
  const externalUuid = `claude-code:${fileState.sessionId}:${parsed.uuid}`;
56717
57286
  const agentModel = parsed.role === "user" ? null : parsed.agent_model ?? "unknown";
56718
57287
  try {
56719
57288
  const result = await insertRawMemory({
56720
57289
  user_id: userId,
56721
- agent_platform: CLIENT_PLATFORM,
57290
+ agent_platform: CLIENT_PLATFORM2,
56722
57291
  agent_model: agentModel,
56723
57292
  role: parsed.role,
56724
57293
  message: parsed.message,
56725
- external_uuid: externalUuid
57294
+ external_uuid: externalUuid,
57295
+ device_name: DEVICE_NAME4
56726
57296
  });
56727
57297
  if (result.inserted) inserted++;
56728
57298
  else dedup++;
@@ -56734,71 +57304,71 @@ async function flushDeltaForFile(jsonlPath, fileState) {
56734
57304
  fileState.cursorBytes = newCursor;
56735
57305
  return { inserted, skipped, dedup };
56736
57306
  }
56737
- async function flushAllFiles() {
56738
- if (!_state || !_state.projectDir) return { inserted: 0, skipped: 0, dedup: 0 };
56739
- const projectDir = _state.projectDir;
56740
- if (!fs2.existsSync(projectDir)) return { inserted: 0, skipped: 0, dedup: 0 };
57307
+ async function flushAllFiles2() {
57308
+ if (!_state2 || !_state2.projectDir) return { inserted: 0, skipped: 0, dedup: 0 };
57309
+ const projectDir = _state2.projectDir;
57310
+ if (!fs3.existsSync(projectDir)) return { inserted: 0, skipped: 0, dedup: 0 };
56741
57311
  let entries;
56742
57312
  try {
56743
- entries = fs2.readdirSync(projectDir, { withFileTypes: true });
57313
+ entries = fs3.readdirSync(projectDir, { withFileTypes: true });
56744
57314
  } catch {
56745
57315
  return { inserted: 0, skipped: 0, dedup: 0 };
56746
57316
  }
56747
57317
  let totalI = 0, totalS = 0, totalD = 0;
56748
57318
  for (const entry of entries) {
56749
57319
  if (!entry.isFile() || !entry.name.endsWith(".jsonl")) continue;
56750
- const jsonlPath = path3.join(projectDir, entry.name);
56751
- let fileState = _state.files.get(jsonlPath);
57320
+ const jsonlPath = path4.join(projectDir, entry.name);
57321
+ let fileState = _state2.files.get(jsonlPath);
56752
57322
  if (!fileState) {
56753
57323
  const sessionId = entry.name.replace(/\.jsonl$/, "");
56754
- fileState = { cursorBytes: 0, sessionId };
56755
- _state.files.set(jsonlPath, fileState);
57324
+ fileState = { cursorBytes: 0, sessionId, contentSeen: /* @__PURE__ */ new Set() };
57325
+ _state2.files.set(jsonlPath, fileState);
56756
57326
  console.error(`\u{1F4DD} [JSONL] new session detected: ${entry.name}`);
56757
57327
  }
56758
- const r = await flushDeltaForFile(jsonlPath, fileState);
57328
+ const r = await flushDeltaForFile2(jsonlPath, fileState);
56759
57329
  totalI += r.inserted;
56760
57330
  totalS += r.skipped;
56761
57331
  totalD += r.dedup;
56762
57332
  }
56763
57333
  return { inserted: totalI, skipped: totalS, dedup: totalD };
56764
57334
  }
56765
- async function flushWithMutex() {
56766
- if (_flushInProgress) {
56767
- _flushPending = true;
57335
+ async function flushWithMutex2() {
57336
+ if (_flushInProgress2) {
57337
+ _flushPending2 = true;
56768
57338
  return;
56769
57339
  }
56770
- _flushInProgress = true;
57340
+ _flushInProgress2 = true;
56771
57341
  try {
56772
- const r = await flushAllFiles();
57342
+ const r = await flushAllFiles2();
56773
57343
  if (r.inserted > 0 || r.skipped > 0 || r.dedup > 0) {
56774
57344
  console.error(`\u{1F4DD} [JSONL] live flush: inserted=${r.inserted}, dedup=${r.dedup}, skipped=${r.skipped}`);
56775
57345
  }
56776
57346
  } catch (err2) {
56777
57347
  console.error("\u26A0\uFE0F [JSONL] live flush error:", err2);
56778
57348
  } finally {
56779
- _flushInProgress = false;
56780
- if (_flushPending) {
56781
- _flushPending = false;
57349
+ _flushInProgress2 = false;
57350
+ if (_flushPending2) {
57351
+ _flushPending2 = false;
56782
57352
  setImmediate(() => {
56783
- void flushWithMutex();
57353
+ void flushWithMutex2();
56784
57354
  });
56785
57355
  }
56786
57356
  }
56787
57357
  }
56788
- function scheduleFlush() {
56789
- if (_flushDebounceTimer) clearTimeout(_flushDebounceTimer);
56790
- _flushDebounceTimer = setTimeout(() => {
56791
- _flushDebounceTimer = null;
56792
- void flushWithMutex();
56793
- }, FLUSH_DEBOUNCE_MS);
57358
+ function scheduleFlush2() {
57359
+ if (_flushDebounceTimer2) clearTimeout(_flushDebounceTimer2);
57360
+ _flushDebounceTimer2 = setTimeout(() => {
57361
+ _flushDebounceTimer2 = null;
57362
+ void flushWithMutex2();
57363
+ }, FLUSH_DEBOUNCE_MS2);
56794
57364
  }
56795
57365
  function armDirWatcher() {
56796
- if (!_state || !_state.projectDir) return;
57366
+ if (!_state2 || !_state2.projectDir) return;
56797
57367
  if (_watcher) return;
56798
57368
  try {
56799
- _watcher = fs2.watch(_state.projectDir, (_eventType, filename) => {
57369
+ _watcher = fs3.watch(_state2.projectDir, (_eventType, filename) => {
56800
57370
  if (filename && !filename.endsWith(".jsonl")) return;
56801
- scheduleFlush();
57371
+ scheduleFlush2();
56802
57372
  });
56803
57373
  console.error(`\u{1F4DD} [JSONL] dir watcher armed`);
56804
57374
  } catch (err2) {
@@ -56806,9 +57376,9 @@ function armDirWatcher() {
56806
57376
  }
56807
57377
  }
56808
57378
  function disarmDirWatcher() {
56809
- if (_flushDebounceTimer) {
56810
- clearTimeout(_flushDebounceTimer);
56811
- _flushDebounceTimer = null;
57379
+ if (_flushDebounceTimer2) {
57380
+ clearTimeout(_flushDebounceTimer2);
57381
+ _flushDebounceTimer2 = null;
56812
57382
  }
56813
57383
  if (_watcher) {
56814
57384
  try {
@@ -56818,85 +57388,88 @@ function disarmDirWatcher() {
56818
57388
  _watcher = null;
56819
57389
  }
56820
57390
  }
56821
- async function captureSessionEnd() {
57391
+ async function captureSessionEnd2() {
56822
57392
  disarmDirWatcher();
56823
57393
  const waitStart = Date.now();
56824
- while (_flushInProgress && Date.now() - waitStart < 2e3) {
57394
+ while (_flushInProgress2 && Date.now() - waitStart < 2e3) {
56825
57395
  await new Promise((r) => setTimeout(r, 50));
56826
57396
  }
56827
- if (!_state || !_state.projectDir) {
57397
+ if (!_state2 || !_state2.projectDir) {
56828
57398
  return { inserted: 0, skipped: 0, error: "session not armed" };
56829
57399
  }
56830
- _flushInProgress = true;
57400
+ _flushInProgress2 = true;
56831
57401
  try {
56832
- const r = await flushAllFiles();
57402
+ const r = await flushAllFiles2();
56833
57403
  if (r.inserted > 0 || r.skipped > 0 || r.dedup > 0) {
56834
57404
  console.error(`\u{1F4DD} [JSONL] final flush: inserted=${r.inserted}, dedup=${r.dedup}, skipped=${r.skipped}`);
56835
57405
  }
56836
57406
  return { inserted: r.inserted, skipped: r.skipped };
56837
57407
  } finally {
56838
- _flushInProgress = false;
57408
+ _flushInProgress2 = false;
56839
57409
  }
56840
57410
  }
56841
57411
 
56842
57412
  // src/auto_save/codex_capture.ts
56843
- import * as fs3 from "node:fs";
56844
- import * as path4 from "node:path";
56845
- import * as os3 from "node:os";
56846
- var CLIENT_PLATFORM2 = "codex-mcp-client";
56847
- var SESSIONS_ROOT = path4.join(os3.homedir(), ".codex", "sessions");
56848
- var FLUSH_DEBOUNCE_MS2 = 200;
56849
- var _state2 = null;
56850
- var _watcher2 = null;
56851
- var _flushInProgress2 = false;
56852
- var _flushPending2 = false;
56853
- var _flushDebounceTimer2 = null;
56854
- function extractSessionId(filename) {
56855
- const m = filename.match(/^rollout-.+?-([0-9a-f-]{36})\.jsonl$/);
56856
- return m ? m[1] : null;
56857
- }
56858
- function probeSessionMeta(filePath) {
56859
- let fd;
56860
- try {
56861
- fd = fs3.openSync(filePath, "r");
56862
- } catch {
56863
- return null;
57413
+ import * as fs4 from "node:fs";
57414
+ import * as path5 from "node:path";
57415
+ import * as os7 from "node:os";
57416
+ import { spawnSync } from "node:child_process";
57417
+ var DEVICE_NAME5 = os7.hostname();
57418
+ var SESSIONS_ROOT = path5.join(os7.homedir(), ".codex", "sessions");
57419
+ var STATE_DB = path5.join(os7.homedir(), ".codex", "state_5.sqlite");
57420
+ var FLUSH_DEBOUNCE_MS3 = 200;
57421
+ var POLL_INTERVAL_MS2 = 3e3;
57422
+ function sourceToPlatform(source) {
57423
+ switch (source) {
57424
+ case "cli":
57425
+ return "codex-cli";
57426
+ case "vscode":
57427
+ return "codex-desktop";
57428
+ case "mcp":
57429
+ return "codex-mcp";
57430
+ case "exec":
57431
+ return "codex-exec";
57432
+ default:
57433
+ return "codex-mcp-client";
56864
57434
  }
57435
+ }
57436
+ function lookupPlatform(sessionId) {
56865
57437
  try {
56866
- const buf = Buffer.alloc(8192);
56867
- const n = fs3.readSync(fd, buf, 0, 8192, 0);
56868
- if (n === 0) return null;
56869
- const slice = buf.slice(0, n).toString("utf-8");
56870
- const nl = slice.indexOf("\n");
56871
- if (nl === -1) return null;
56872
- const entry = JSON.parse(slice.slice(0, nl));
56873
- if (entry.type !== "session_meta") return null;
56874
- const sid = entry.payload?.id;
56875
- const cwd = entry.payload?.cwd;
56876
- if (typeof sid !== "string" || typeof cwd !== "string") return null;
56877
- return { sessionId: sid, cwd };
57438
+ const res = spawnSync(
57439
+ "sqlite3",
57440
+ [STATE_DB, `SELECT source FROM threads WHERE id='${sessionId}' LIMIT 1;`],
57441
+ { encoding: "utf-8", timeout: 500 }
57442
+ );
57443
+ const source = (res.stdout ?? "").trim();
57444
+ if (source) return sourceToPlatform(source);
56878
57445
  } catch {
56879
- return null;
56880
- } finally {
56881
- try {
56882
- fs3.closeSync(fd);
56883
- } catch {
56884
- }
56885
57446
  }
57447
+ return "codex-mcp-client";
57448
+ }
57449
+ var _state3 = null;
57450
+ var SERVER_START_MS = Date.now();
57451
+ var _watcher2 = null;
57452
+ var _pollTimer2 = null;
57453
+ var _flushInProgress3 = false;
57454
+ var _flushPending3 = false;
57455
+ var _flushDebounceTimer3 = null;
57456
+ function extractSessionId(filename) {
57457
+ const m = filename.match(/^rollout-.+?-([0-9a-f-]{36})\.jsonl$/);
57458
+ return m ? m[1] : null;
56886
57459
  }
56887
57460
  function* walkRollouts(root) {
56888
- if (!fs3.existsSync(root)) return;
57461
+ if (!fs4.existsSync(root)) return;
56889
57462
  const stack = [root];
56890
57463
  while (stack.length) {
56891
57464
  const dir = stack.pop();
56892
57465
  let entries;
56893
57466
  try {
56894
- entries = fs3.readdirSync(dir, { withFileTypes: true });
57467
+ entries = fs4.readdirSync(dir, { withFileTypes: true });
56895
57468
  } catch {
56896
57469
  continue;
56897
57470
  }
56898
57471
  for (const e of entries) {
56899
- const p = path4.join(dir, e.name);
57472
+ const p = path5.join(dir, e.name);
56900
57473
  if (e.isDirectory()) {
56901
57474
  stack.push(p);
56902
57475
  } else if (e.isFile() && e.name.startsWith("rollout-") && e.name.endsWith(".jsonl")) {
@@ -56905,43 +57478,34 @@ function* walkRollouts(root) {
56905
57478
  }
56906
57479
  }
56907
57480
  }
56908
- function captureSessionStart2(cwd) {
56909
- const cwdNormalized = path4.resolve(cwd);
56910
- const rootExists = fs3.existsSync(SESSIONS_ROOT);
56911
- _state2 = {
56912
- cwd,
56913
- cwdNormalized,
57481
+ function captureSessionStart3(_cwd) {
57482
+ const rootExists = fs4.existsSync(SESSIONS_ROOT);
57483
+ _state3 = {
56914
57484
  rootExists,
56915
57485
  files: /* @__PURE__ */ new Map()
56916
57486
  };
56917
57487
  if (!rootExists) {
56918
57488
  return;
56919
57489
  }
56920
- let count = 0, matched = 0;
57490
+ let count = 0;
56921
57491
  for (const filePath of walkRollouts(SESSIONS_ROOT)) {
56922
57492
  let stat;
56923
57493
  try {
56924
- stat = fs3.statSync(filePath);
57494
+ stat = fs4.statSync(filePath);
56925
57495
  } catch {
56926
57496
  continue;
56927
57497
  }
56928
- const sessionId = extractSessionId(path4.basename(filePath));
57498
+ const sessionId = extractSessionId(path5.basename(filePath));
56929
57499
  if (!sessionId) continue;
56930
- const meta = probeSessionMeta(filePath);
56931
- let cwdMatched = null;
56932
- if (meta) {
56933
- cwdMatched = path4.resolve(meta.cwd) === cwdNormalized;
56934
- }
56935
- _state2.files.set(filePath, {
57500
+ _state3.files.set(filePath, {
56936
57501
  cursorBytes: stat.size,
56937
57502
  sessionId,
56938
- cwdMatched,
57503
+ agentPlatform: lookupPlatform(sessionId),
56939
57504
  currentModel: null
56940
57505
  });
56941
57506
  count++;
56942
- if (cwdMatched) matched++;
56943
57507
  }
56944
- console.error(`\u{1F4DD} [Codex] capture armed: ${count} rollout(s), ${matched} cwd-matched`);
57508
+ console.error(`\u{1F4DD} [Codex] capture armed: ${count} rollout(s)`);
56945
57509
  armDirWatcher2();
56946
57510
  }
56947
57511
  function parseEntry2(line, byteOffset) {
@@ -56965,6 +57529,9 @@ function parseEntry2(line, byteOffset) {
56965
57529
  }
56966
57530
  const message = typeof payload.message === "string" ? payload.message.trim() : "";
56967
57531
  if (!message) return null;
57532
+ if (message.startsWith("[external_agent_tool_call:") || message.startsWith("[external_agent_tool_call_response:")) {
57533
+ return null;
57534
+ }
56968
57535
  return { byteOffset, role, message };
56969
57536
  }
56970
57537
  function extractModelFromTurnContext(line) {
@@ -56977,35 +57544,24 @@ function extractModelFromTurnContext(line) {
56977
57544
  return null;
56978
57545
  }
56979
57546
  }
56980
- function applySessionMetaIfPresent(line, fileState, cwdNormalized) {
56981
- if (fileState.cwdMatched !== null) return;
56982
- try {
56983
- const entry = JSON.parse(line);
56984
- if (entry.type !== "session_meta") return;
56985
- const cwd = entry.payload?.cwd;
56986
- if (typeof cwd !== "string") return;
56987
- fileState.cwdMatched = path4.resolve(cwd) === cwdNormalized;
56988
- } catch {
56989
- }
56990
- }
56991
- async function flushDeltaForFile2(filePath, fileState, cwdNormalized) {
56992
- if (!fs3.existsSync(filePath)) return { inserted: 0, skipped: 0, dedup: 0 };
57547
+ async function flushDeltaForFile3(filePath, fileState) {
57548
+ if (!fs4.existsSync(filePath)) return { inserted: 0, skipped: 0, dedup: 0 };
56993
57549
  let stat;
56994
57550
  try {
56995
- stat = fs3.statSync(filePath);
57551
+ stat = fs4.statSync(filePath);
56996
57552
  } catch {
56997
57553
  return { inserted: 0, skipped: 0, dedup: 0 };
56998
57554
  }
56999
57555
  if (stat.size <= fileState.cursorBytes) return { inserted: 0, skipped: 0, dedup: 0 };
57000
57556
  const length = stat.size - fileState.cursorBytes;
57001
- const fd = fs3.openSync(filePath, "r");
57557
+ const fd = fs4.openSync(filePath, "r");
57002
57558
  let raw;
57003
57559
  try {
57004
57560
  const buf = Buffer.alloc(length);
57005
- fs3.readSync(fd, buf, 0, length, fileState.cursorBytes);
57561
+ fs4.readSync(fd, buf, 0, length, fileState.cursorBytes);
57006
57562
  raw = buf.toString("utf-8");
57007
57563
  } finally {
57008
- fs3.closeSync(fd);
57564
+ fs4.closeSync(fd);
57009
57565
  }
57010
57566
  const lastNl = raw.lastIndexOf("\n");
57011
57567
  if (lastNl === -1) return { inserted: 0, skipped: 0, dedup: 0 };
@@ -57023,17 +57579,12 @@ async function flushDeltaForFile2(filePath, fileState, cwdNormalized) {
57023
57579
  lineStartInRaw += Buffer.byteLength(line, "utf-8") + 1;
57024
57580
  continue;
57025
57581
  }
57026
- applySessionMetaIfPresent(trimmed, fileState, cwdNormalized);
57027
57582
  const m = extractModelFromTurnContext(trimmed);
57028
57583
  if (m !== null) {
57029
57584
  fileState.currentModel = m;
57030
57585
  lineStartInRaw += Buffer.byteLength(line, "utf-8") + 1;
57031
57586
  continue;
57032
57587
  }
57033
- if (fileState.cwdMatched === false) {
57034
- lineStartInRaw += Buffer.byteLength(line, "utf-8") + 1;
57035
- continue;
57036
- }
57037
57588
  const byteOffset = startBase + lineStartInRaw;
57038
57589
  const parsed = parseEntry2(trimmed, byteOffset);
57039
57590
  if (!parsed) {
@@ -57041,21 +57592,17 @@ async function flushDeltaForFile2(filePath, fileState, cwdNormalized) {
57041
57592
  lineStartInRaw += Buffer.byteLength(line, "utf-8") + 1;
57042
57593
  continue;
57043
57594
  }
57044
- if (fileState.cwdMatched !== true) {
57045
- skipped++;
57046
- lineStartInRaw += Buffer.byteLength(line, "utf-8") + 1;
57047
- continue;
57048
- }
57049
57595
  const externalUuid = `codex:${fileState.sessionId}:${parsed.byteOffset}`;
57050
57596
  const agentModel = parsed.role === "user" ? null : fileState.currentModel ?? "unknown";
57051
57597
  try {
57052
57598
  const result = await insertRawMemory({
57053
57599
  user_id: userId,
57054
- agent_platform: CLIENT_PLATFORM2,
57600
+ agent_platform: fileState.agentPlatform,
57055
57601
  agent_model: agentModel,
57056
57602
  role: parsed.role,
57057
57603
  message: parsed.message,
57058
- external_uuid: externalUuid
57604
+ external_uuid: externalUuid,
57605
+ device_name: DEVICE_NAME5
57059
57606
  });
57060
57607
  if (result.inserted) inserted++;
57061
57608
  else dedup++;
@@ -57068,247 +57615,42 @@ async function flushDeltaForFile2(filePath, fileState, cwdNormalized) {
57068
57615
  fileState.cursorBytes = newCursor;
57069
57616
  return { inserted, skipped, dedup };
57070
57617
  }
57071
- async function flushAllFiles2() {
57072
- if (!_state2 || !_state2.rootExists) return { inserted: 0, skipped: 0, dedup: 0 };
57073
- const cwdNormalized = _state2.cwdNormalized;
57618
+ async function flushAllFiles3() {
57619
+ if (!_state3 || !_state3.rootExists) return { inserted: 0, skipped: 0, dedup: 0 };
57074
57620
  let totalI = 0, totalS = 0, totalD = 0;
57075
57621
  for (const filePath of walkRollouts(SESSIONS_ROOT)) {
57076
- let fileState = _state2.files.get(filePath);
57622
+ let fileState = _state3.files.get(filePath);
57077
57623
  if (!fileState) {
57078
- const sessionId = extractSessionId(path4.basename(filePath));
57624
+ const sessionId = extractSessionId(path5.basename(filePath));
57079
57625
  if (!sessionId) continue;
57626
+ let stat = null;
57627
+ try {
57628
+ stat = fs4.statSync(filePath);
57629
+ } catch {
57630
+ }
57631
+ const fileBirthMs = stat ? stat.birthtimeMs || stat.ctimeMs : SERVER_START_MS;
57632
+ const isPreExisting = fileBirthMs < SERVER_START_MS - 5e3;
57633
+ const initialCursor = isPreExisting ? stat?.size ?? 0 : 0;
57634
+ if (isPreExisting) {
57635
+ console.error(`\u{1F4DD} [Codex] pre-existing rollout skipped (cursor=${initialCursor}): ${path5.basename(filePath)}`);
57636
+ } else {
57637
+ console.error(`\u{1F4DD} [Codex] new rollout detected: ${path5.basename(filePath)}`);
57638
+ }
57080
57639
  fileState = {
57081
- cursorBytes: 0,
57640
+ cursorBytes: initialCursor,
57082
57641
  sessionId,
57083
- cwdMatched: null,
57642
+ agentPlatform: lookupPlatform(sessionId),
57084
57643
  currentModel: null
57085
57644
  };
57086
- _state2.files.set(filePath, fileState);
57087
- console.error(`\u{1F4DD} [Codex] new rollout detected: ${path4.basename(filePath)}`);
57645
+ _state3.files.set(filePath, fileState);
57088
57646
  }
57089
- const r = await flushDeltaForFile2(filePath, fileState, cwdNormalized);
57647
+ const r = await flushDeltaForFile3(filePath, fileState);
57090
57648
  totalI += r.inserted;
57091
57649
  totalS += r.skipped;
57092
57650
  totalD += r.dedup;
57093
57651
  }
57094
57652
  return { inserted: totalI, skipped: totalS, dedup: totalD };
57095
57653
  }
57096
- async function flushWithMutex2() {
57097
- if (_flushInProgress2) {
57098
- _flushPending2 = true;
57099
- return;
57100
- }
57101
- _flushInProgress2 = true;
57102
- try {
57103
- const r = await flushAllFiles2();
57104
- if (r.inserted > 0 || r.skipped > 0 || r.dedup > 0) {
57105
- console.error(`\u{1F4DD} [Codex] live flush: inserted=${r.inserted}, dedup=${r.dedup}, skipped=${r.skipped}`);
57106
- }
57107
- } catch (err2) {
57108
- console.error("\u26A0\uFE0F [Codex] live flush error:", err2);
57109
- } finally {
57110
- _flushInProgress2 = false;
57111
- if (_flushPending2) {
57112
- _flushPending2 = false;
57113
- setImmediate(() => {
57114
- void flushWithMutex2();
57115
- });
57116
- }
57117
- }
57118
- }
57119
- function scheduleFlush2() {
57120
- if (_flushDebounceTimer2) clearTimeout(_flushDebounceTimer2);
57121
- _flushDebounceTimer2 = setTimeout(() => {
57122
- _flushDebounceTimer2 = null;
57123
- void flushWithMutex2();
57124
- }, FLUSH_DEBOUNCE_MS2);
57125
- }
57126
- function armDirWatcher2() {
57127
- if (!_state2 || !_state2.rootExists) return;
57128
- if (_watcher2) return;
57129
- try {
57130
- _watcher2 = fs3.watch(SESSIONS_ROOT, { recursive: true }, (_evt, filename) => {
57131
- if (!filename) return;
57132
- const base = path4.basename(filename);
57133
- if (!base.startsWith("rollout-") || !base.endsWith(".jsonl")) return;
57134
- scheduleFlush2();
57135
- });
57136
- console.error(`\u{1F4DD} [Codex] dir watcher armed (recursive)`);
57137
- } catch (err2) {
57138
- console.error("\u26A0\uFE0F [Codex] fs.watch failed (shutdown-only flush \uD3F4\uBC31):", err2);
57139
- }
57140
- }
57141
- function disarmDirWatcher2() {
57142
- if (_flushDebounceTimer2) {
57143
- clearTimeout(_flushDebounceTimer2);
57144
- _flushDebounceTimer2 = null;
57145
- }
57146
- if (_watcher2) {
57147
- try {
57148
- _watcher2.close();
57149
- } catch {
57150
- }
57151
- _watcher2 = null;
57152
- }
57153
- }
57154
- async function captureSessionEnd2() {
57155
- disarmDirWatcher2();
57156
- const waitStart = Date.now();
57157
- while (_flushInProgress2 && Date.now() - waitStart < 2e3) {
57158
- await new Promise((r) => setTimeout(r, 50));
57159
- }
57160
- if (!_state2 || !_state2.rootExists) {
57161
- return { inserted: 0, skipped: 0, error: "session not armed" };
57162
- }
57163
- _flushInProgress2 = true;
57164
- try {
57165
- const r = await flushAllFiles2();
57166
- if (r.inserted > 0 || r.skipped > 0 || r.dedup > 0) {
57167
- console.error(`\u{1F4DD} [Codex] final flush: inserted=${r.inserted}, dedup=${r.dedup}, skipped=${r.skipped}`);
57168
- }
57169
- return { inserted: r.inserted, skipped: r.skipped };
57170
- } finally {
57171
- _flushInProgress2 = false;
57172
- }
57173
- }
57174
-
57175
- // src/auto_save/gemini_capture.ts
57176
- import * as fs4 from "node:fs";
57177
- import * as path5 from "node:path";
57178
- import * as os4 from "node:os";
57179
- import * as crypto2 from "node:crypto";
57180
- var CLIENT_PLATFORM3 = "gemini-cli-mcp-client";
57181
- var GEMINI_TMP_ROOT = path5.join(os4.homedir(), ".gemini", "tmp");
57182
- var FLUSH_DEBOUNCE_MS3 = 200;
57183
- var PARSE_RETRY_MS = 80;
57184
- var _state3 = null;
57185
- var _watchers = [];
57186
- var _flushInProgress3 = false;
57187
- var _flushPending3 = false;
57188
- var _flushDebounceTimer3 = null;
57189
- function extractShortId(filename) {
57190
- const m = filename.match(/^session-.+?-([0-9a-f]+)\.json$/);
57191
- return m ? m[1] : filename.replace(/\.json$/, "");
57192
- }
57193
- function deriveChatsDirCandidates(cwd) {
57194
- const cwdHash = crypto2.createHash("sha256").update(cwd).digest("hex");
57195
- const candidates = [
57196
- path5.join(GEMINI_TMP_ROOT, cwdHash, "chats"),
57197
- path5.join(GEMINI_TMP_ROOT, path5.basename(cwd), "chats")
57198
- ];
57199
- return candidates.filter((d) => fs4.existsSync(d));
57200
- }
57201
- async function readSessionFile(filePath) {
57202
- for (let attempt = 0; attempt < 2; attempt++) {
57203
- try {
57204
- const raw = fs4.readFileSync(filePath, "utf-8");
57205
- if (!raw.trim()) return null;
57206
- return JSON.parse(raw);
57207
- } catch {
57208
- if (attempt === 0) {
57209
- await new Promise((r) => setTimeout(r, PARSE_RETRY_MS));
57210
- continue;
57211
- }
57212
- return null;
57213
- }
57214
- }
57215
- return null;
57216
- }
57217
- function parseMessage(msg, fallbackId) {
57218
- let role;
57219
- if (msg.type === "user") role = "user";
57220
- else if (msg.type === "gemini") role = "assistant";
57221
- else return null;
57222
- let text = "";
57223
- const c = msg.content;
57224
- if (typeof c === "string") {
57225
- text = c;
57226
- } else if (Array.isArray(c)) {
57227
- for (const block of c) {
57228
- if (block && typeof block === "object" && typeof block.text === "string") {
57229
- text += (text ? "\n" : "") + block.text;
57230
- }
57231
- }
57232
- }
57233
- text = text.trim();
57234
- if (!text) return null;
57235
- const msgId = typeof msg.id === "string" && msg.id ? msg.id : fallbackId;
57236
- const agent_model = role === "assistant" && typeof msg.model === "string" ? msg.model : null;
57237
- return { msgId, role, message: text, agent_model };
57238
- }
57239
- async function flushDeltaForFile3(filePath, fileState) {
57240
- const session = await readSessionFile(filePath);
57241
- if (!session || !Array.isArray(session.messages)) {
57242
- return { inserted: 0, skipped: 0, dedup: 0 };
57243
- }
57244
- const messages = session.messages;
57245
- if (messages.length <= fileState.cursorIndex) {
57246
- return { inserted: 0, skipped: 0, dedup: 0 };
57247
- }
57248
- if (typeof session.sessionId === "string" && session.sessionId) {
57249
- fileState.sessionId = session.sessionId;
57250
- }
57251
- const userId = await getDefaultUserId();
57252
- let inserted = 0, skipped = 0, dedup = 0;
57253
- for (let i = fileState.cursorIndex; i < messages.length; i++) {
57254
- const msg = messages[i];
57255
- const fallbackId = `${fileState.sessionId}-${i}`;
57256
- const parsed = parseMessage(msg, fallbackId);
57257
- if (!parsed) {
57258
- skipped++;
57259
- continue;
57260
- }
57261
- const externalUuid = `gemini:${fileState.sessionId}:${parsed.msgId}`;
57262
- const agentModel = parsed.role === "user" ? null : parsed.agent_model ?? "unknown";
57263
- try {
57264
- const result = await insertRawMemory({
57265
- user_id: userId,
57266
- agent_platform: CLIENT_PLATFORM3,
57267
- agent_model: agentModel,
57268
- role: parsed.role,
57269
- message: parsed.message,
57270
- external_uuid: externalUuid
57271
- });
57272
- if (result.inserted) inserted++;
57273
- else dedup++;
57274
- } catch (err2) {
57275
- console.error(`\u26A0\uFE0F [Gemini] insert failed at index ${i}:`, err2);
57276
- skipped++;
57277
- }
57278
- }
57279
- fileState.cursorIndex = messages.length;
57280
- return { inserted, skipped, dedup };
57281
- }
57282
- async function flushAllFiles3() {
57283
- if (!_state3) return { inserted: 0, skipped: 0, dedup: 0 };
57284
- let totalI = 0, totalS = 0, totalD = 0;
57285
- for (const dir of _state3.chatsDirs) {
57286
- if (!fs4.existsSync(dir)) continue;
57287
- let entries;
57288
- try {
57289
- entries = fs4.readdirSync(dir, { withFileTypes: true });
57290
- } catch {
57291
- continue;
57292
- }
57293
- for (const entry of entries) {
57294
- if (!entry.isFile() || !entry.name.endsWith(".json")) continue;
57295
- if (!entry.name.startsWith("session-")) continue;
57296
- const filePath = path5.join(dir, entry.name);
57297
- let fileState = _state3.files.get(filePath);
57298
- if (!fileState) {
57299
- const sessionId = extractShortId(entry.name);
57300
- fileState = { cursorIndex: 0, sessionId };
57301
- _state3.files.set(filePath, fileState);
57302
- console.error(`\u{1F4DD} [Gemini] new session detected: ${entry.name}`);
57303
- }
57304
- const r = await flushDeltaForFile3(filePath, fileState);
57305
- totalI += r.inserted;
57306
- totalS += r.skipped;
57307
- totalD += r.dedup;
57308
- }
57309
- }
57310
- return { inserted: totalI, skipped: totalS, dedup: totalD };
57311
- }
57312
57654
  async function flushWithMutex3() {
57313
57655
  if (_flushInProgress3) {
57314
57656
  _flushPending3 = true;
@@ -57318,10 +57660,10 @@ async function flushWithMutex3() {
57318
57660
  try {
57319
57661
  const r = await flushAllFiles3();
57320
57662
  if (r.inserted > 0 || r.skipped > 0 || r.dedup > 0) {
57321
- console.error(`\u{1F4DD} [Gemini] live flush: inserted=${r.inserted}, dedup=${r.dedup}, skipped=${r.skipped}`);
57663
+ console.error(`\u{1F4DD} [Codex] live flush: inserted=${r.inserted}, dedup=${r.dedup}, skipped=${r.skipped}`);
57322
57664
  }
57323
57665
  } catch (err2) {
57324
- console.error("\u26A0\uFE0F [Gemini] live flush error:", err2);
57666
+ console.error("\u26A0\uFE0F [Codex] live flush error:", err2);
57325
57667
  } finally {
57326
57668
  _flushInProgress3 = false;
57327
57669
  if (_flushPending3) {
@@ -57339,87 +57681,55 @@ function scheduleFlush3() {
57339
57681
  void flushWithMutex3();
57340
57682
  }, FLUSH_DEBOUNCE_MS3);
57341
57683
  }
57342
- function captureSessionStart3(cwd) {
57343
- const chatsDirs = deriveChatsDirCandidates(cwd);
57344
- _state3 = { cwd, chatsDirs, files: /* @__PURE__ */ new Map() };
57345
- if (chatsDirs.length === 0) {
57346
- return;
57347
- }
57348
- let count = 0;
57349
- for (const dir of chatsDirs) {
57350
- let entries;
57351
- try {
57352
- entries = fs4.readdirSync(dir, { withFileTypes: true });
57353
- } catch {
57354
- continue;
57355
- }
57356
- for (const entry of entries) {
57357
- if (!entry.isFile() || !entry.name.endsWith(".json")) continue;
57358
- if (!entry.name.startsWith("session-")) continue;
57359
- const filePath = path5.join(dir, entry.name);
57360
- let session = null;
57361
- try {
57362
- const raw = fs4.readFileSync(filePath, "utf-8");
57363
- session = JSON.parse(raw);
57364
- } catch {
57365
- session = null;
57366
- }
57367
- const sessionId = session && typeof session.sessionId === "string" && session.sessionId || extractShortId(entry.name);
57368
- const cursorIndex = session && Array.isArray(session.messages) ? session.messages.length : 0;
57369
- _state3.files.set(filePath, { cursorIndex, sessionId });
57370
- count++;
57371
- }
57372
- }
57373
- console.error(
57374
- `\u{1F4DD} [Gemini] capture armed: ${count} session(s) across ${chatsDirs.length} chats dir(s)`
57375
- );
57376
- armDirWatchers();
57377
- }
57378
- function armDirWatchers() {
57379
- if (!_state3) return;
57380
- if (_watchers.length > 0) return;
57381
- for (const dir of _state3.chatsDirs) {
57382
- try {
57383
- const w = fs4.watch(dir, (_evt, filename) => {
57384
- if (filename && !filename.endsWith(".json")) return;
57385
- scheduleFlush3();
57386
- });
57387
- _watchers.push(w);
57388
- } catch (err2) {
57389
- console.error(`\u26A0\uFE0F [Gemini] fs.watch ${dir} failed:`, err2);
57390
- }
57391
- }
57392
- if (_watchers.length > 0) {
57393
- console.error(`\u{1F4DD} [Gemini] dir watcher(s) armed: ${_watchers.length}`);
57684
+ function armDirWatcher2() {
57685
+ if (!_state3 || !_state3.rootExists) return;
57686
+ if (_watcher2) return;
57687
+ try {
57688
+ _watcher2 = fs4.watch(SESSIONS_ROOT, { recursive: true }, (_evt, filename) => {
57689
+ if (!filename) return;
57690
+ const base = path5.basename(filename);
57691
+ if (!base.startsWith("rollout-") || !base.endsWith(".jsonl")) return;
57692
+ scheduleFlush3();
57693
+ });
57694
+ console.error(`\u{1F4DD} [Codex] dir watcher armed (recursive)`);
57695
+ } catch (err2) {
57696
+ console.error("\u26A0\uFE0F [Codex] fs.watch failed \u2014 polling only:", err2);
57394
57697
  }
57698
+ _pollTimer2 = setInterval(() => {
57699
+ void flushWithMutex3();
57700
+ }, POLL_INTERVAL_MS2);
57395
57701
  }
57396
- function disarmDirWatchers() {
57702
+ function disarmDirWatcher2() {
57397
57703
  if (_flushDebounceTimer3) {
57398
57704
  clearTimeout(_flushDebounceTimer3);
57399
57705
  _flushDebounceTimer3 = null;
57400
57706
  }
57401
- for (const w of _watchers) {
57707
+ if (_pollTimer2) {
57708
+ clearInterval(_pollTimer2);
57709
+ _pollTimer2 = null;
57710
+ }
57711
+ if (_watcher2) {
57402
57712
  try {
57403
- w.close();
57713
+ _watcher2.close();
57404
57714
  } catch {
57405
57715
  }
57716
+ _watcher2 = null;
57406
57717
  }
57407
- _watchers.length = 0;
57408
57718
  }
57409
57719
  async function captureSessionEnd3() {
57410
- disarmDirWatchers();
57720
+ disarmDirWatcher2();
57411
57721
  const waitStart = Date.now();
57412
57722
  while (_flushInProgress3 && Date.now() - waitStart < 2e3) {
57413
57723
  await new Promise((r) => setTimeout(r, 50));
57414
57724
  }
57415
- if (!_state3 || _state3.chatsDirs.length === 0) {
57725
+ if (!_state3 || !_state3.rootExists) {
57416
57726
  return { inserted: 0, skipped: 0, error: "session not armed" };
57417
57727
  }
57418
57728
  _flushInProgress3 = true;
57419
57729
  try {
57420
57730
  const r = await flushAllFiles3();
57421
57731
  if (r.inserted > 0 || r.skipped > 0 || r.dedup > 0) {
57422
- console.error(`\u{1F4DD} [Gemini] final flush: inserted=${r.inserted}, dedup=${r.dedup}, skipped=${r.skipped}`);
57732
+ console.error(`\u{1F4DD} [Codex] final flush: inserted=${r.inserted}, dedup=${r.dedup}, skipped=${r.skipped}`);
57423
57733
  }
57424
57734
  return { inserted: r.inserted, skipped: r.skipped };
57425
57735
  } finally {
@@ -57503,11 +57813,11 @@ async function shutdown(reason) {
57503
57813
  try {
57504
57814
  await Promise.race([
57505
57815
  Promise.allSettled([
57506
- captureSessionEnd(),
57507
57816
  captureSessionEnd2(),
57508
- captureSessionEnd3()
57817
+ captureSessionEnd3(),
57818
+ captureSessionEnd()
57509
57819
  ]),
57510
- new Promise((resolve2) => setTimeout(resolve2, 3e3))
57820
+ new Promise((resolve) => setTimeout(resolve, 3e3))
57511
57821
  ]);
57512
57822
  } catch (err2) {
57513
57823
  console.error("\u{1F4DD} capture error (non-blocking):", err2);
@@ -57519,7 +57829,7 @@ async function shutdown(reason) {
57519
57829
  try {
57520
57830
  await Promise.race([
57521
57831
  drainColdPath(),
57522
- new Promise((resolve2) => setTimeout(resolve2, 2e4))
57832
+ new Promise((resolve) => setTimeout(resolve, 2e4))
57523
57833
  ]);
57524
57834
  } catch (err2) {
57525
57835
  console.error("\u{1F535} [ColdPath] drain error (non-blocking):", err2);
@@ -57527,7 +57837,7 @@ async function shutdown(reason) {
57527
57837
  try {
57528
57838
  await Promise.race([
57529
57839
  db.close(),
57530
- new Promise((resolve2) => setTimeout(resolve2, 3e3))
57840
+ new Promise((resolve) => setTimeout(resolve, 3e3))
57531
57841
  ]);
57532
57842
  } catch (err2) {
57533
57843
  console.error("Error during shutdown:", err2);
@@ -57611,9 +57921,9 @@ async function runMcpServer() {
57611
57921
  registerTools(server);
57612
57922
  installShutdownHandlers();
57613
57923
  startParentWatchdog();
57614
- captureSessionStart(process.cwd());
57615
57924
  captureSessionStart2(process.cwd());
57616
57925
  captureSessionStart3(process.cwd());
57926
+ captureSessionStart(process.cwd());
57617
57927
  console.error("\u{1F680} Starting Memory MCP Server...");
57618
57928
  if (process.env.SSH_ENABLED === "true" && !process.env.SSH_KEY_PATH) {
57619
57929
  throw new Error("\u274C SSH_KEY_PATH is required when SSH is enabled");