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