crispy-recall 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +146 -39
- package/dist/SKILL.md.template +28 -237
- package/dist/embed-pending.js +936 -269
- package/dist/push-pending.js +7958 -0
- package/dist/recall.js +7124 -1201
- package/dist/stop-hook.js +825 -134
- package/package.json +11 -5
package/dist/stop-hook.js
CHANGED
|
@@ -34,6 +34,47 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
34
34
|
));
|
|
35
35
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
36
36
|
|
|
37
|
+
// src/paths.ts
|
|
38
|
+
function recallRoot() {
|
|
39
|
+
if (rootOverride)
|
|
40
|
+
return rootOverride;
|
|
41
|
+
const envRoot = process.env["RECALL_HOME"];
|
|
42
|
+
if (envRoot && envRoot.length > 0)
|
|
43
|
+
return envRoot;
|
|
44
|
+
return (0, import_node_path.join)((0, import_node_os.homedir)(), ".recall");
|
|
45
|
+
}
|
|
46
|
+
function remoteRoot() {
|
|
47
|
+
const env = process.env["RECALL_REMOTE_ROOT"];
|
|
48
|
+
if (env && env.length > 0)
|
|
49
|
+
return env;
|
|
50
|
+
return (0, import_node_path.join)(recallRoot(), "remote");
|
|
51
|
+
}
|
|
52
|
+
function dbPath() {
|
|
53
|
+
return (0, import_node_path.join)(recallRoot(), "recall.db");
|
|
54
|
+
}
|
|
55
|
+
function binDir() {
|
|
56
|
+
return (0, import_node_path.join)(recallRoot(), "bin");
|
|
57
|
+
}
|
|
58
|
+
function runDir() {
|
|
59
|
+
return (0, import_node_path.join)(recallRoot(), "run");
|
|
60
|
+
}
|
|
61
|
+
function logsDir() {
|
|
62
|
+
return (0, import_node_path.join)(recallRoot(), "logs");
|
|
63
|
+
}
|
|
64
|
+
function ensureDir() {
|
|
65
|
+
(0, import_node_fs.mkdirSync)(recallRoot(), { recursive: true });
|
|
66
|
+
}
|
|
67
|
+
var import_node_fs, import_node_os, import_node_path, rootOverride;
|
|
68
|
+
var init_paths = __esm({
|
|
69
|
+
"src/paths.ts"() {
|
|
70
|
+
"use strict";
|
|
71
|
+
import_node_fs = require("node:fs");
|
|
72
|
+
import_node_os = require("node:os");
|
|
73
|
+
import_node_path = require("node:path");
|
|
74
|
+
rootOverride = null;
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
|
|
37
78
|
// src/log.ts
|
|
38
79
|
function log(entry) {
|
|
39
80
|
if (LEVEL_ORDER[entry.level] < _threshold)
|
|
@@ -68,38 +109,6 @@ var init_log = __esm({
|
|
|
68
109
|
}
|
|
69
110
|
});
|
|
70
111
|
|
|
71
|
-
// src/paths.ts
|
|
72
|
-
function recallRoot() {
|
|
73
|
-
if (rootOverride)
|
|
74
|
-
return rootOverride;
|
|
75
|
-
const envRoot = process.env["RECALL_HOME"];
|
|
76
|
-
if (envRoot && envRoot.length > 0)
|
|
77
|
-
return envRoot;
|
|
78
|
-
return (0, import_node_path.join)((0, import_node_os.homedir)(), ".recall");
|
|
79
|
-
}
|
|
80
|
-
function dbPath() {
|
|
81
|
-
return (0, import_node_path.join)(recallRoot(), "recall.db");
|
|
82
|
-
}
|
|
83
|
-
function binDir() {
|
|
84
|
-
return (0, import_node_path.join)(recallRoot(), "bin");
|
|
85
|
-
}
|
|
86
|
-
function logsDir() {
|
|
87
|
-
return (0, import_node_path.join)(recallRoot(), "logs");
|
|
88
|
-
}
|
|
89
|
-
function ensureDir() {
|
|
90
|
-
(0, import_node_fs.mkdirSync)(recallRoot(), { recursive: true });
|
|
91
|
-
}
|
|
92
|
-
var import_node_fs, import_node_os, import_node_path, rootOverride;
|
|
93
|
-
var init_paths = __esm({
|
|
94
|
-
"src/paths.ts"() {
|
|
95
|
-
"use strict";
|
|
96
|
-
import_node_fs = require("node:fs");
|
|
97
|
-
import_node_os = require("node:os");
|
|
98
|
-
import_node_path = require("node:path");
|
|
99
|
-
rootOverride = null;
|
|
100
|
-
}
|
|
101
|
-
});
|
|
102
|
-
|
|
103
112
|
// node_modules/better-sqlite3/lib/util.js
|
|
104
113
|
var require_util = __commonJS({
|
|
105
114
|
"node_modules/better-sqlite3/lib/util.js"(exports2) {
|
|
@@ -144,7 +153,7 @@ var require_sqlite_error = __commonJS({
|
|
|
144
153
|
// node_modules/file-uri-to-path/index.js
|
|
145
154
|
var require_file_uri_to_path = __commonJS({
|
|
146
155
|
"node_modules/file-uri-to-path/index.js"(exports2, module2) {
|
|
147
|
-
var
|
|
156
|
+
var sep2 = require("path").sep || "/";
|
|
148
157
|
module2.exports = fileUriToPath;
|
|
149
158
|
function fileUriToPath(uri) {
|
|
150
159
|
if ("string" != typeof uri || uri.length <= 7 || "file://" != uri.substring(0, 7)) {
|
|
@@ -157,15 +166,15 @@ var require_file_uri_to_path = __commonJS({
|
|
|
157
166
|
if ("localhost" == host)
|
|
158
167
|
host = "";
|
|
159
168
|
if (host) {
|
|
160
|
-
host =
|
|
169
|
+
host = sep2 + sep2 + host;
|
|
161
170
|
}
|
|
162
171
|
path = path.replace(/^(.+)\|/, "$1:");
|
|
163
|
-
if (
|
|
172
|
+
if (sep2 == "\\") {
|
|
164
173
|
path = path.replace(/\//g, "\\");
|
|
165
174
|
}
|
|
166
175
|
if (/^.+\:/.test(path)) {
|
|
167
176
|
} else {
|
|
168
|
-
path =
|
|
177
|
+
path = sep2 + path;
|
|
169
178
|
}
|
|
170
179
|
return host + path;
|
|
171
180
|
}
|
|
@@ -178,8 +187,8 @@ var require_bindings = __commonJS({
|
|
|
178
187
|
var fs3 = require("fs");
|
|
179
188
|
var path = require("path");
|
|
180
189
|
var fileURLToPath = require_file_uri_to_path();
|
|
181
|
-
var
|
|
182
|
-
var
|
|
190
|
+
var join7 = path.join;
|
|
191
|
+
var dirname4 = path.dirname;
|
|
183
192
|
var exists = fs3.accessSync && function(path2) {
|
|
184
193
|
try {
|
|
185
194
|
fs3.accessSync(path2);
|
|
@@ -239,7 +248,7 @@ var require_bindings = __commonJS({
|
|
|
239
248
|
var requireFunc = typeof __webpack_require__ === "function" ? __non_webpack_require__ : require;
|
|
240
249
|
var tries = [], i = 0, l = opts.try.length, n, b, err;
|
|
241
250
|
for (; i < l; i++) {
|
|
242
|
-
n =
|
|
251
|
+
n = join7.apply(
|
|
243
252
|
null,
|
|
244
253
|
opts.try[i].map(function(p) {
|
|
245
254
|
return opts[p] || p;
|
|
@@ -295,12 +304,12 @@ var require_bindings = __commonJS({
|
|
|
295
304
|
return fileName;
|
|
296
305
|
};
|
|
297
306
|
exports2.getRoot = function getRoot(file) {
|
|
298
|
-
var dir =
|
|
307
|
+
var dir = dirname4(file), prev;
|
|
299
308
|
while (true) {
|
|
300
309
|
if (dir === ".") {
|
|
301
310
|
dir = process.cwd();
|
|
302
311
|
}
|
|
303
|
-
if (exists(
|
|
312
|
+
if (exists(join7(dir, "package.json")) || exists(join7(dir, "node_modules"))) {
|
|
304
313
|
return dir;
|
|
305
314
|
}
|
|
306
315
|
if (prev === dir) {
|
|
@@ -309,7 +318,7 @@ var require_bindings = __commonJS({
|
|
|
309
318
|
);
|
|
310
319
|
}
|
|
311
320
|
prev = dir;
|
|
312
|
-
dir =
|
|
321
|
+
dir = join7(dir, "..");
|
|
313
322
|
}
|
|
314
323
|
};
|
|
315
324
|
}
|
|
@@ -510,13 +519,13 @@ var require_backup = __commonJS({
|
|
|
510
519
|
var runBackup = (backup, handler) => {
|
|
511
520
|
let rate = 0;
|
|
512
521
|
let useDefault = true;
|
|
513
|
-
return new Promise((
|
|
522
|
+
return new Promise((resolve3, reject) => {
|
|
514
523
|
setImmediate(function step() {
|
|
515
524
|
try {
|
|
516
525
|
const progress = backup.transfer(rate);
|
|
517
526
|
if (!progress.remainingPages) {
|
|
518
527
|
backup.close();
|
|
519
|
-
|
|
528
|
+
resolve3(progress);
|
|
520
529
|
return;
|
|
521
530
|
}
|
|
522
531
|
if (useDefault) {
|
|
@@ -930,15 +939,141 @@ var init_quantize = __esm({
|
|
|
930
939
|
}
|
|
931
940
|
});
|
|
932
941
|
|
|
942
|
+
// src/installer/stable-node.ts
|
|
943
|
+
var init_stable_node = __esm({
|
|
944
|
+
"src/installer/stable-node.ts"() {
|
|
945
|
+
"use strict";
|
|
946
|
+
}
|
|
947
|
+
});
|
|
948
|
+
|
|
949
|
+
// src/installer/settings-merge.ts
|
|
950
|
+
var init_settings_merge = __esm({
|
|
951
|
+
"src/installer/settings-merge.ts"() {
|
|
952
|
+
"use strict";
|
|
953
|
+
init_stable_node();
|
|
954
|
+
init_config();
|
|
955
|
+
init_paths();
|
|
956
|
+
}
|
|
957
|
+
});
|
|
958
|
+
|
|
959
|
+
// src/installer/config.ts
|
|
960
|
+
function configPath() {
|
|
961
|
+
return (0, import_node_path6.join)(recallRoot(), "config.json");
|
|
962
|
+
}
|
|
963
|
+
function satelliteTokenPath() {
|
|
964
|
+
return (0, import_node_path6.join)(recallRoot(), "satellite-token");
|
|
965
|
+
}
|
|
966
|
+
function readConfig() {
|
|
967
|
+
const p = configPath();
|
|
968
|
+
if (!(0, import_node_fs6.existsSync)(p))
|
|
969
|
+
return null;
|
|
970
|
+
try {
|
|
971
|
+
return JSON.parse((0, import_node_fs6.readFileSync)(p, "utf-8"));
|
|
972
|
+
} catch (err) {
|
|
973
|
+
log({
|
|
974
|
+
source: "installer/config",
|
|
975
|
+
level: "warn",
|
|
976
|
+
summary: `config.json unparseable (${err.message}) \u2014 treating as absent`
|
|
977
|
+
});
|
|
978
|
+
return null;
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
function readSatelliteConfig() {
|
|
982
|
+
const sat = readConfig()?.satellite;
|
|
983
|
+
if (!sat || typeof sat.hubUrl !== "string" || sat.hubUrl.length === 0)
|
|
984
|
+
return null;
|
|
985
|
+
let token = null;
|
|
986
|
+
try {
|
|
987
|
+
const raw = (0, import_node_fs6.readFileSync)(satelliteTokenPath(), "utf-8").trim();
|
|
988
|
+
if (raw.length > 0)
|
|
989
|
+
token = raw;
|
|
990
|
+
} catch {
|
|
991
|
+
}
|
|
992
|
+
return { hubUrl: sat.hubUrl, host: typeof sat.host === "string" ? sat.host : "", token };
|
|
993
|
+
}
|
|
994
|
+
var import_node_fs6, import_node_path6;
|
|
995
|
+
var init_config = __esm({
|
|
996
|
+
"src/installer/config.ts"() {
|
|
997
|
+
"use strict";
|
|
998
|
+
import_node_fs6 = require("node:fs");
|
|
999
|
+
import_node_path6 = require("node:path");
|
|
1000
|
+
init_paths();
|
|
1001
|
+
init_log();
|
|
1002
|
+
init_settings_merge();
|
|
1003
|
+
}
|
|
1004
|
+
});
|
|
1005
|
+
|
|
933
1006
|
// src/hooks/stop-hook.ts
|
|
934
1007
|
var stop_hook_exports = {};
|
|
935
1008
|
__export(stop_hook_exports, {
|
|
936
1009
|
resolveIngestTarget: () => resolveIngestTarget,
|
|
1010
|
+
retryBusyIngest: () => retryBusyIngest,
|
|
937
1011
|
vendorForTranscript: () => vendorForTranscript
|
|
938
1012
|
});
|
|
939
1013
|
module.exports = __toCommonJS(stop_hook_exports);
|
|
940
1014
|
|
|
1015
|
+
// src/recall/embed-failures.ts
|
|
1016
|
+
init_paths();
|
|
1017
|
+
|
|
1018
|
+
// src/adapters/system-context.ts
|
|
1019
|
+
function firstTextContent(message) {
|
|
1020
|
+
if (!message)
|
|
1021
|
+
return void 0;
|
|
1022
|
+
const content = message.content;
|
|
1023
|
+
if (typeof content === "string")
|
|
1024
|
+
return content;
|
|
1025
|
+
if (Array.isArray(content)) {
|
|
1026
|
+
const firstText = content.find(
|
|
1027
|
+
(b) => typeof b === "object" && b !== null && b.type === "text"
|
|
1028
|
+
);
|
|
1029
|
+
if (firstText && "text" in firstText) {
|
|
1030
|
+
return firstText.text;
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
return void 0;
|
|
1034
|
+
}
|
|
1035
|
+
function isSystemContextContent(message) {
|
|
1036
|
+
if (!message || message.role !== "user")
|
|
1037
|
+
return false;
|
|
1038
|
+
const text = firstTextContent(message);
|
|
1039
|
+
if (!text)
|
|
1040
|
+
return false;
|
|
1041
|
+
if (text.startsWith("<system-reminder>"))
|
|
1042
|
+
return true;
|
|
1043
|
+
if (text.startsWith("<environment_context>"))
|
|
1044
|
+
return true;
|
|
1045
|
+
if (text.startsWith("<INSTRUCTIONS>"))
|
|
1046
|
+
return true;
|
|
1047
|
+
if (text.startsWith("# AGENTS.md instructions for"))
|
|
1048
|
+
return true;
|
|
1049
|
+
if (text.startsWith("<context>"))
|
|
1050
|
+
return true;
|
|
1051
|
+
if (text.startsWith("<task-notification>"))
|
|
1052
|
+
return true;
|
|
1053
|
+
if (text.startsWith("<command-name>"))
|
|
1054
|
+
return true;
|
|
1055
|
+
if (text.startsWith("<local-command-stdout>"))
|
|
1056
|
+
return true;
|
|
1057
|
+
if (text.startsWith("<local-command-caveat>"))
|
|
1058
|
+
return true;
|
|
1059
|
+
return false;
|
|
1060
|
+
}
|
|
1061
|
+
|
|
941
1062
|
// src/recall/transcript-utils.ts
|
|
1063
|
+
var META_KEEP_PREFIXES = [
|
|
1064
|
+
"<task-notification>",
|
|
1065
|
+
"[SYSTEM NOTIFICATION",
|
|
1066
|
+
"Another Claude session sent a message"
|
|
1067
|
+
];
|
|
1068
|
+
function shouldDropAsMeta(entry) {
|
|
1069
|
+
if (entry.isMeta !== true)
|
|
1070
|
+
return false;
|
|
1071
|
+
const text = firstTextContent(entry.message);
|
|
1072
|
+
if (text && META_KEEP_PREFIXES.some((prefix) => text.startsWith(prefix))) {
|
|
1073
|
+
return false;
|
|
1074
|
+
}
|
|
1075
|
+
return true;
|
|
1076
|
+
}
|
|
942
1077
|
function stripToolContent(entries) {
|
|
943
1078
|
const out = [];
|
|
944
1079
|
for (const entry of entries) {
|
|
@@ -988,11 +1123,12 @@ Run \`recall doctor\` for details.`
|
|
|
988
1123
|
}
|
|
989
1124
|
};
|
|
990
1125
|
var MigrationPendingError = class extends Error {
|
|
991
|
-
constructor(dbPath2) {
|
|
1126
|
+
constructor(dbPath2, kind = "retrieval-class") {
|
|
992
1127
|
super(
|
|
993
|
-
"recall: this database needs a one-time schema migration \u2014 run `recall install` to finish it. (Normal commands refuse to rewrite the index unattended.)"
|
|
1128
|
+
kind === "codex-rekey" ? "recall: this database needs a one-time Codex message-id migration \u2014 run `recall install` (or `recall repair --rekey-codex`) to finish it. (Normal commands refuse to rewrite the index unattended.)" : "recall: this database needs a one-time schema migration \u2014 run `recall install` to finish it. (Normal commands refuse to rewrite the index unattended.)"
|
|
994
1129
|
);
|
|
995
1130
|
this.dbPath = dbPath2;
|
|
1131
|
+
this.kind = kind;
|
|
996
1132
|
this.name = "MigrationPendingError";
|
|
997
1133
|
}
|
|
998
1134
|
};
|
|
@@ -1033,16 +1169,43 @@ function getDb(dbPath2, opts) {
|
|
|
1033
1169
|
}
|
|
1034
1170
|
db = adapter;
|
|
1035
1171
|
currentDbPath = dbPath2;
|
|
1172
|
+
ensureStemScratch(db);
|
|
1036
1173
|
log({ source: "db", level: "info", summary: `DB: opened pending-migration DB at ${dbPath2} (installer mode)` });
|
|
1037
1174
|
return db;
|
|
1038
1175
|
}
|
|
1176
|
+
if (isCodexRekeyPending(adapter) && !opts?.allowPendingMigration) {
|
|
1177
|
+
raw.close();
|
|
1178
|
+
throw new MigrationPendingError(dbPath2, "codex-rekey");
|
|
1179
|
+
}
|
|
1039
1180
|
db = adapter;
|
|
1040
1181
|
currentDbPath = dbPath2;
|
|
1041
|
-
|
|
1182
|
+
try {
|
|
1183
|
+
ensureSchema(db);
|
|
1184
|
+
ensureStemScratch(db);
|
|
1185
|
+
} catch (error) {
|
|
1186
|
+
closeDb();
|
|
1187
|
+
throw error;
|
|
1188
|
+
}
|
|
1042
1189
|
log({ source: "db", level: "info", summary: `DB: initialized at ${dbPath2}` });
|
|
1043
1190
|
return db;
|
|
1044
1191
|
}
|
|
1192
|
+
function ensureStemScratch(db3) {
|
|
1193
|
+
db3.exec(`
|
|
1194
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS temp._stem USING fts5(
|
|
1195
|
+
t, tokenize='porter unicode61'
|
|
1196
|
+
);
|
|
1197
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS temp._stem_vocab
|
|
1198
|
+
USING fts5vocab(temp, _stem, 'row');
|
|
1199
|
+
`);
|
|
1200
|
+
}
|
|
1045
1201
|
var RETRIEVAL_MIGRATION_KEY = "retrieval_class_migration";
|
|
1202
|
+
function rethrowIfBusy(err) {
|
|
1203
|
+
const code = err?.code;
|
|
1204
|
+
const message = err?.message;
|
|
1205
|
+
if (typeof code === "string" && code.startsWith("SQLITE_BUSY") || typeof message === "string" && /database is locked|SQLITE_BUSY/i.test(message))
|
|
1206
|
+
throw err;
|
|
1207
|
+
}
|
|
1208
|
+
var PROJECT_KEY_BACKFILL_KEY = "project_key_backfill";
|
|
1046
1209
|
function isRetrievalMigrationPending(d) {
|
|
1047
1210
|
try {
|
|
1048
1211
|
const hasMessages = d.get(
|
|
@@ -1060,13 +1223,40 @@ function isRetrievalMigrationPending(d) {
|
|
|
1060
1223
|
[RETRIEVAL_MIGRATION_KEY]
|
|
1061
1224
|
);
|
|
1062
1225
|
return row?.value !== "complete";
|
|
1063
|
-
} catch {
|
|
1226
|
+
} catch (err) {
|
|
1227
|
+
rethrowIfBusy(err);
|
|
1228
|
+
return true;
|
|
1229
|
+
}
|
|
1230
|
+
}
|
|
1231
|
+
var CODEX_REKEY_MIGRATION_KEY = "codex_message_id_v2";
|
|
1232
|
+
function isCodexRekeyPending(d) {
|
|
1233
|
+
try {
|
|
1234
|
+
const hasMessages = d.get(
|
|
1235
|
+
`SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'messages'`
|
|
1236
|
+
);
|
|
1237
|
+
if (!hasMessages)
|
|
1238
|
+
return false;
|
|
1239
|
+
const hasMeta = d.get(
|
|
1240
|
+
`SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'schema_meta'`
|
|
1241
|
+
);
|
|
1242
|
+
if (!hasMeta)
|
|
1243
|
+
return true;
|
|
1244
|
+
const row = d.get(
|
|
1245
|
+
`SELECT value FROM schema_meta WHERE key = ?`,
|
|
1246
|
+
[CODEX_REKEY_MIGRATION_KEY]
|
|
1247
|
+
);
|
|
1248
|
+
return row?.value !== "complete";
|
|
1249
|
+
} catch (err) {
|
|
1250
|
+
rethrowIfBusy(err);
|
|
1064
1251
|
return true;
|
|
1065
1252
|
}
|
|
1066
1253
|
}
|
|
1067
1254
|
function closeDb() {
|
|
1068
|
-
if (db)
|
|
1255
|
+
if (!db)
|
|
1256
|
+
return;
|
|
1257
|
+
try {
|
|
1069
1258
|
db.close();
|
|
1259
|
+
} finally {
|
|
1070
1260
|
db = null;
|
|
1071
1261
|
currentDbPath = null;
|
|
1072
1262
|
}
|
|
@@ -1186,6 +1376,7 @@ var RETRIEVAL_SCHEMA_DDL = {
|
|
|
1186
1376
|
created_at INTEGER NOT NULL,
|
|
1187
1377
|
message_role TEXT,
|
|
1188
1378
|
retrieval_class TEXT NOT NULL DEFAULT 'hot',
|
|
1379
|
+
project_key TEXT,
|
|
1189
1380
|
UNIQUE(session_id, message_id)
|
|
1190
1381
|
);
|
|
1191
1382
|
|
|
@@ -1275,15 +1466,18 @@ var RETRIEVAL_SCHEMA_DDL = {
|
|
|
1275
1466
|
`
|
|
1276
1467
|
};
|
|
1277
1468
|
function ensureSchema(db3) {
|
|
1469
|
+
const fresh = !db3.get(
|
|
1470
|
+
`SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'messages'`
|
|
1471
|
+
);
|
|
1278
1472
|
db3.exec("BEGIN IMMEDIATE");
|
|
1279
1473
|
try {
|
|
1280
1474
|
db3.exec(RETRIEVAL_SCHEMA_DDL.tables);
|
|
1281
1475
|
db3.exec(RETRIEVAL_SCHEMA_DDL.fts);
|
|
1282
1476
|
db3.exec(`
|
|
1283
|
-
CREATE VIRTUAL TABLE IF NOT EXISTS _stem USING fts5(
|
|
1477
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS main._stem USING fts5(
|
|
1284
1478
|
t, tokenize='porter unicode61'
|
|
1285
1479
|
);
|
|
1286
|
-
CREATE VIRTUAL TABLE IF NOT EXISTS _stem_vocab
|
|
1480
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS main._stem_vocab
|
|
1287
1481
|
USING fts5vocab(_stem, 'row');
|
|
1288
1482
|
`);
|
|
1289
1483
|
db3.exec(`
|
|
@@ -1304,6 +1498,16 @@ function ensureSchema(db3) {
|
|
|
1304
1498
|
throw e;
|
|
1305
1499
|
}
|
|
1306
1500
|
}
|
|
1501
|
+
const hasProjectKey = () => db3.all(`PRAGMA table_info(messages)`).some((c) => c.name === "project_key");
|
|
1502
|
+
if (!hasProjectKey()) {
|
|
1503
|
+
try {
|
|
1504
|
+
db3.exec(`ALTER TABLE messages ADD COLUMN project_key TEXT`);
|
|
1505
|
+
} catch (e) {
|
|
1506
|
+
if (!hasProjectKey())
|
|
1507
|
+
throw e;
|
|
1508
|
+
}
|
|
1509
|
+
}
|
|
1510
|
+
db3.exec(`CREATE INDEX IF NOT EXISTS idx_messages_project_key ON messages(project_key)`);
|
|
1307
1511
|
db3.exec(`
|
|
1308
1512
|
CREATE TABLE IF NOT EXISTS ingest_watermark (
|
|
1309
1513
|
transcript_path TEXT PRIMARY KEY,
|
|
@@ -1317,6 +1521,18 @@ function ensureSchema(db3) {
|
|
|
1317
1521
|
INSERT OR IGNORE INTO schema_meta(key, value)
|
|
1318
1522
|
VALUES ('${RETRIEVAL_MIGRATION_KEY}', 'complete');
|
|
1319
1523
|
`);
|
|
1524
|
+
if (fresh) {
|
|
1525
|
+
db3.exec(`
|
|
1526
|
+
INSERT OR IGNORE INTO schema_meta(key, value)
|
|
1527
|
+
VALUES ('${CODEX_REKEY_MIGRATION_KEY}', 'complete');
|
|
1528
|
+
`);
|
|
1529
|
+
}
|
|
1530
|
+
if (fresh) {
|
|
1531
|
+
db3.exec(`
|
|
1532
|
+
INSERT OR IGNORE INTO schema_meta(key, value)
|
|
1533
|
+
VALUES ('${PROJECT_KEY_BACKFILL_KEY}', 'complete');
|
|
1534
|
+
`);
|
|
1535
|
+
}
|
|
1320
1536
|
db3.exec("COMMIT");
|
|
1321
1537
|
} catch (e) {
|
|
1322
1538
|
try {
|
|
@@ -1348,10 +1564,11 @@ function ensureDir2() {
|
|
|
1348
1564
|
}
|
|
1349
1565
|
function insertMessages(messages, opts) {
|
|
1350
1566
|
if (messages.length === 0 && !opts?.replaceSessionId && !opts?.provenance)
|
|
1351
|
-
return;
|
|
1567
|
+
return 0;
|
|
1352
1568
|
ensureDir2();
|
|
1353
1569
|
const d = db2();
|
|
1354
1570
|
d.exec("BEGIN IMMEDIATE");
|
|
1571
|
+
let inserted = 0;
|
|
1355
1572
|
try {
|
|
1356
1573
|
if (opts?.replaceSessionId) {
|
|
1357
1574
|
d.run(
|
|
@@ -1363,20 +1580,65 @@ function insertMessages(messages, opts) {
|
|
|
1363
1580
|
}
|
|
1364
1581
|
const stmt = d.prepare(
|
|
1365
1582
|
`INSERT OR IGNORE INTO messages
|
|
1366
|
-
(message_id, session_id, message_seq, message_text, project_id, created_at, message_role, retrieval_class)
|
|
1367
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`
|
|
1583
|
+
(message_id, session_id, message_seq, message_text, project_id, created_at, message_role, retrieval_class, project_key)
|
|
1584
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
1368
1585
|
);
|
|
1586
|
+
const invalidatedSessions = /* @__PURE__ */ new Set();
|
|
1587
|
+
const sourceIds = /* @__PURE__ */ new Map();
|
|
1369
1588
|
for (const m of messages) {
|
|
1370
|
-
|
|
1371
|
-
|
|
1589
|
+
const ids = sourceIds.get(m.session_id) ?? /* @__PURE__ */ new Set();
|
|
1590
|
+
ids.add(m.message_id);
|
|
1591
|
+
ids.add(`session:${m.session_id}:${m.message_id}`);
|
|
1592
|
+
sourceIds.set(m.session_id, ids);
|
|
1593
|
+
}
|
|
1594
|
+
if (opts?.provenance && opts.sourceMessageIds) {
|
|
1595
|
+
const sid = opts.provenance.sessionId;
|
|
1596
|
+
const ids = sourceIds.get(sid);
|
|
1597
|
+
for (const id of opts.sourceMessageIds) {
|
|
1598
|
+
ids?.add(id);
|
|
1599
|
+
ids?.add(`session:${sid}:${id}`);
|
|
1600
|
+
}
|
|
1601
|
+
}
|
|
1602
|
+
const partialSessions = /* @__PURE__ */ new Map();
|
|
1603
|
+
const maxStoredSeq = /* @__PURE__ */ new Map();
|
|
1604
|
+
for (const [sid, ids] of sourceIds) {
|
|
1605
|
+
const stored = d.all("SELECT message_id, message_seq FROM messages WHERE session_id = ?", [sid]);
|
|
1606
|
+
const max = stored.reduce((acc, r) => Math.max(acc, r.message_seq), -1);
|
|
1607
|
+
maxStoredSeq.set(sid, max);
|
|
1608
|
+
if (stored.some((r) => !ids.has(r.message_id)))
|
|
1609
|
+
partialSessions.set(sid, max);
|
|
1610
|
+
}
|
|
1611
|
+
for (const m of messages) {
|
|
1612
|
+
const scopedId = `session:${m.session_id}:${m.message_id}`;
|
|
1613
|
+
const scoped = d.get("SELECT message_id FROM messages WHERE message_id = ? AND session_id = ?", [scopedId, m.session_id]);
|
|
1614
|
+
const owner = d.get("SELECT session_id FROM messages WHERE message_id = ?", [m.message_id]);
|
|
1615
|
+
const id = scoped || owner && owner.session_id !== m.session_id ? scopedId : m.message_id;
|
|
1616
|
+
const existing = d.get("SELECT message_seq FROM messages WHERE message_id = ? AND session_id = ?", [id, m.session_id]);
|
|
1617
|
+
let sequence = m.message_seq;
|
|
1618
|
+
if (partialSessions.has(m.session_id)) {
|
|
1619
|
+
sequence = existing?.message_seq ?? partialSessions.get(m.session_id) + 1;
|
|
1620
|
+
partialSessions.set(m.session_id, Math.max(partialSessions.get(m.session_id), sequence));
|
|
1621
|
+
}
|
|
1622
|
+
const insertsPredecessor = !existing && sequence <= (maxStoredSeq.get(m.session_id) ?? -1);
|
|
1623
|
+
if (existing && existing.message_seq !== sequence || insertsPredecessor) {
|
|
1624
|
+
if (!invalidatedSessions.has(m.session_id)) {
|
|
1625
|
+
d.run("DELETE FROM message_vectors WHERE message_id IN (SELECT message_id FROM messages WHERE session_id = ?)", [m.session_id]);
|
|
1626
|
+
invalidatedSessions.add(m.session_id);
|
|
1627
|
+
}
|
|
1628
|
+
d.run("UPDATE messages SET message_seq = ? WHERE message_id = ? AND session_id = ?", [sequence, id, m.session_id]);
|
|
1629
|
+
}
|
|
1630
|
+
maxStoredSeq.set(m.session_id, Math.max(maxStoredSeq.get(m.session_id) ?? -1, sequence));
|
|
1631
|
+
inserted += stmt.run([
|
|
1632
|
+
id,
|
|
1372
1633
|
m.session_id,
|
|
1373
|
-
|
|
1634
|
+
sequence,
|
|
1374
1635
|
m.message_text,
|
|
1375
1636
|
m.project_id,
|
|
1376
1637
|
m.created_at,
|
|
1377
1638
|
m.message_role,
|
|
1378
|
-
m.retrieval_class ?? "hot"
|
|
1379
|
-
|
|
1639
|
+
m.retrieval_class ?? "hot",
|
|
1640
|
+
m.project_key ?? null
|
|
1641
|
+
]).changes;
|
|
1380
1642
|
}
|
|
1381
1643
|
if (opts?.provenance) {
|
|
1382
1644
|
const p = opts.provenance;
|
|
@@ -1416,6 +1678,15 @@ function insertMessages(messages, opts) {
|
|
|
1416
1678
|
);
|
|
1417
1679
|
}
|
|
1418
1680
|
}
|
|
1681
|
+
if (opts?.adopt) {
|
|
1682
|
+
const { sessionId, projectKey, projectId } = opts.adopt;
|
|
1683
|
+
if (projectKey !== null) {
|
|
1684
|
+
d.run("UPDATE messages SET project_key = ? WHERE session_id = ? AND project_key IS NULL", [projectKey, sessionId]);
|
|
1685
|
+
}
|
|
1686
|
+
if (projectId !== null && projectId !== "") {
|
|
1687
|
+
d.run("UPDATE messages SET project_id = ? WHERE session_id = ? AND (project_id IS NULL OR project_id = '')", [projectId, sessionId]);
|
|
1688
|
+
}
|
|
1689
|
+
}
|
|
1419
1690
|
for (const a of opts?.aliases ?? []) {
|
|
1420
1691
|
d.run(
|
|
1421
1692
|
`INSERT OR REPLACE INTO session_aliases (alias_id, session_id, source, created_at)
|
|
@@ -1424,11 +1695,23 @@ function insertMessages(messages, opts) {
|
|
|
1424
1695
|
);
|
|
1425
1696
|
}
|
|
1426
1697
|
d.exec("COMMIT");
|
|
1698
|
+
return inserted;
|
|
1427
1699
|
} catch (e) {
|
|
1428
1700
|
d.exec("ROLLBACK");
|
|
1429
1701
|
throw e;
|
|
1430
1702
|
}
|
|
1431
1703
|
}
|
|
1704
|
+
function hasSessionMessages(sessionId) {
|
|
1705
|
+
try {
|
|
1706
|
+
const row = db2().get(
|
|
1707
|
+
"SELECT 1 FROM messages WHERE session_id = ? LIMIT 1",
|
|
1708
|
+
[sessionId]
|
|
1709
|
+
);
|
|
1710
|
+
return row != null;
|
|
1711
|
+
} catch {
|
|
1712
|
+
return false;
|
|
1713
|
+
}
|
|
1714
|
+
}
|
|
1432
1715
|
|
|
1433
1716
|
// src/recall/session-classifier.ts
|
|
1434
1717
|
init_paths();
|
|
@@ -1437,6 +1720,83 @@ init_log();
|
|
|
1437
1720
|
// src/adapters/codex/codex-jsonl-reader.ts
|
|
1438
1721
|
var fs = __toESM(require("fs"));
|
|
1439
1722
|
init_log();
|
|
1723
|
+
|
|
1724
|
+
// src/recall/transcript-roots.ts
|
|
1725
|
+
var import_node_os2 = require("node:os");
|
|
1726
|
+
var import_node_path4 = require("node:path");
|
|
1727
|
+
|
|
1728
|
+
// src/hub/runtime.ts
|
|
1729
|
+
var import_node_fs3 = require("node:fs");
|
|
1730
|
+
var import_node_path3 = require("node:path");
|
|
1731
|
+
init_paths();
|
|
1732
|
+
var STALE_RECORD_MS = 60 * 60 * 1e3;
|
|
1733
|
+
var REFUSED_RECENT_MAX = 5;
|
|
1734
|
+
function hubHostsPath() {
|
|
1735
|
+
return (0, import_node_path3.join)(runDir(), "hub-hosts.json");
|
|
1736
|
+
}
|
|
1737
|
+
function readHostRecords() {
|
|
1738
|
+
try {
|
|
1739
|
+
const parsed = JSON.parse((0, import_node_fs3.readFileSync)(hubHostsPath(), "utf-8"));
|
|
1740
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
1741
|
+
return {};
|
|
1742
|
+
const out = {};
|
|
1743
|
+
for (const [host, rec] of Object.entries(parsed)) {
|
|
1744
|
+
if (!rec || typeof rec !== "object")
|
|
1745
|
+
continue;
|
|
1746
|
+
const r = rec;
|
|
1747
|
+
out[host] = {
|
|
1748
|
+
...typeof r["lastPushAt"] === "string" ? { lastPushAt: r["lastPushAt"] } : {},
|
|
1749
|
+
...typeof r["lastQueryAt"] === "string" ? { lastQueryAt: r["lastQueryAt"] } : {},
|
|
1750
|
+
...typeof r["lastFullManifestAt"] === "string" ? { lastFullManifestAt: r["lastFullManifestAt"] } : {},
|
|
1751
|
+
refusedCollisions: typeof r["refusedCollisions"] === "number" ? r["refusedCollisions"] : 0,
|
|
1752
|
+
refusedRecent: Array.isArray(r["refusedRecent"]) ? r["refusedRecent"].filter((v) => typeof v === "string").slice(0, REFUSED_RECENT_MAX) : []
|
|
1753
|
+
};
|
|
1754
|
+
}
|
|
1755
|
+
return out;
|
|
1756
|
+
} catch {
|
|
1757
|
+
return {};
|
|
1758
|
+
}
|
|
1759
|
+
}
|
|
1760
|
+
|
|
1761
|
+
// src/recall/transcript-roots.ts
|
|
1762
|
+
init_log();
|
|
1763
|
+
var DRVFS = /^\/mnt\/[a-zA-Z]\//;
|
|
1764
|
+
var warned = /* @__PURE__ */ new Set();
|
|
1765
|
+
function defaultCodexRoot() {
|
|
1766
|
+
return (0, import_node_path4.join)((0, import_node_os2.homedir)(), ".codex");
|
|
1767
|
+
}
|
|
1768
|
+
function guardedHosts(override) {
|
|
1769
|
+
if (process.platform !== "linux")
|
|
1770
|
+
return [];
|
|
1771
|
+
const abs = (0, import_node_path4.resolve)(override).replace(/\\/g, "/");
|
|
1772
|
+
if (!DRVFS.test(abs.endsWith("/") ? abs : `${abs}/`))
|
|
1773
|
+
return [];
|
|
1774
|
+
return Object.keys(readHostRecords());
|
|
1775
|
+
}
|
|
1776
|
+
function resolveRoot(envVar, fallback) {
|
|
1777
|
+
const override = process.env[envVar];
|
|
1778
|
+
if (!override || override.length === 0)
|
|
1779
|
+
return fallback;
|
|
1780
|
+
const hosts = guardedHosts(override);
|
|
1781
|
+
if (hosts.length === 0)
|
|
1782
|
+
return override;
|
|
1783
|
+
if (!warned.has(envVar)) {
|
|
1784
|
+
warned.add(envVar);
|
|
1785
|
+
log({
|
|
1786
|
+
level: "warn",
|
|
1787
|
+
source: "transcript-roots",
|
|
1788
|
+
summary: `ignoring ${envVar}=${override}: this hub serves satellite ${hosts.join(", ")} and that tree is pushed by a satellite; set the variable elsewhere or unset it`
|
|
1789
|
+
});
|
|
1790
|
+
}
|
|
1791
|
+
return fallback;
|
|
1792
|
+
}
|
|
1793
|
+
function codexRoot() {
|
|
1794
|
+
return resolveRoot("CODEX_HOME", defaultCodexRoot());
|
|
1795
|
+
}
|
|
1796
|
+
|
|
1797
|
+
// src/adapters/codex/codex-jsonl-reader.ts
|
|
1798
|
+
var META_READ_LIMIT = 256 * 1024;
|
|
1799
|
+
var META_CHUNK_SIZE = 8192;
|
|
1440
1800
|
function parseCodexJsonlFile(filepath) {
|
|
1441
1801
|
try {
|
|
1442
1802
|
const content = fs.readFileSync(filepath, "utf-8");
|
|
@@ -1456,20 +1816,39 @@ function parseCodexJsonlFile(filepath) {
|
|
|
1456
1816
|
return records;
|
|
1457
1817
|
} catch (error) {
|
|
1458
1818
|
log({ level: "error", source: "codex-jsonl-reader", summary: `Failed to read ${filepath}: ${error instanceof Error ? error.message : String(error)}`, data: { filepath, error: String(error) } });
|
|
1459
|
-
|
|
1819
|
+
throw error;
|
|
1460
1820
|
}
|
|
1461
1821
|
}
|
|
1462
1822
|
function extractCodexSessionMeta(filepath) {
|
|
1463
1823
|
let fd = null;
|
|
1464
1824
|
try {
|
|
1465
1825
|
fd = fs.openSync(filepath, "r");
|
|
1466
|
-
const
|
|
1467
|
-
|
|
1468
|
-
|
|
1826
|
+
const chunks = [];
|
|
1827
|
+
let position = 0;
|
|
1828
|
+
let newlineIdx = -1;
|
|
1829
|
+
let atEof = false;
|
|
1830
|
+
while (position < META_READ_LIMIT) {
|
|
1831
|
+
const chunk = Buffer.alloc(Math.min(META_CHUNK_SIZE, META_READ_LIMIT - position));
|
|
1832
|
+
const bytesRead = fs.readSync(fd, chunk, 0, chunk.length, position);
|
|
1833
|
+
if (bytesRead === 0) {
|
|
1834
|
+
atEof = true;
|
|
1835
|
+
break;
|
|
1836
|
+
}
|
|
1837
|
+
const slice = chunk.subarray(0, bytesRead);
|
|
1838
|
+
const idx = slice.indexOf(10);
|
|
1839
|
+
chunks.push(slice);
|
|
1840
|
+
if (idx >= 0) {
|
|
1841
|
+
newlineIdx = position + idx;
|
|
1842
|
+
break;
|
|
1843
|
+
}
|
|
1844
|
+
position += bytesRead;
|
|
1845
|
+
}
|
|
1846
|
+
if (chunks.length === 0)
|
|
1847
|
+
return null;
|
|
1848
|
+
if (newlineIdx < 0 && !atEof)
|
|
1469
1849
|
return null;
|
|
1470
|
-
const
|
|
1471
|
-
const
|
|
1472
|
-
const firstLine = newlineIdx >= 0 ? content.slice(0, newlineIdx) : content;
|
|
1850
|
+
const joined = Buffer.concat(chunks);
|
|
1851
|
+
const firstLine = (newlineIdx >= 0 ? joined.subarray(0, newlineIdx) : joined).toString("utf-8");
|
|
1473
1852
|
const record = JSON.parse(firstLine.trim());
|
|
1474
1853
|
if (record.type !== "session_meta")
|
|
1475
1854
|
return null;
|
|
@@ -1661,7 +2040,10 @@ function parseSubagentSource(source, _transcriptPath) {
|
|
|
1661
2040
|
const looksSubagent = subRaw !== void 0 || typeTag === "subagent";
|
|
1662
2041
|
if (!looksSubagent)
|
|
1663
2042
|
return none;
|
|
1664
|
-
if (subRaw
|
|
2043
|
+
if (typeof subRaw === "string" && ["review", "compact", "memory_consolidation"].includes(subRaw)) {
|
|
2044
|
+
return { ...none, isSubagent: true, meta: { type: subRaw } };
|
|
2045
|
+
}
|
|
2046
|
+
if (subRaw !== void 0 && (typeof subRaw !== "object" || subRaw === null || Array.isArray(subRaw))) {
|
|
1665
2047
|
return { ...none, malformed: true };
|
|
1666
2048
|
}
|
|
1667
2049
|
const sub = subRaw ?? {};
|
|
@@ -1670,7 +2052,7 @@ function parseSubagentSource(source, _transcriptPath) {
|
|
|
1670
2052
|
const parentThreadId = typeof spawn2.parent_thread_id === "string" && spawn2.parent_thread_id ? spawn2.parent_thread_id : null;
|
|
1671
2053
|
const depth = typeof spawn2.depth === "number" && Number.isFinite(spawn2.depth) ? spawn2.depth : null;
|
|
1672
2054
|
const meta = {};
|
|
1673
|
-
for (const k of ["agent_path", "agent_type", "agent_name", "path", "type", "name"]) {
|
|
2055
|
+
for (const k of ["agent_path", "agent_type", "agent_name", "agent_nickname", "agent_role", "path", "type", "name"]) {
|
|
1674
2056
|
const v = spawn2[k] ?? sub[k];
|
|
1675
2057
|
if (typeof v === "string" || typeof v === "number")
|
|
1676
2058
|
meta[k] = v;
|
|
@@ -1698,6 +2080,255 @@ function normalizePath(p) {
|
|
|
1698
2080
|
return normalized;
|
|
1699
2081
|
}
|
|
1700
2082
|
|
|
2083
|
+
// src/recall/project-key.ts
|
|
2084
|
+
var import_node_fs4 = require("node:fs");
|
|
2085
|
+
var import_node_os3 = require("node:os");
|
|
2086
|
+
var import_node_child_process = require("node:child_process");
|
|
2087
|
+
var cache = /* @__PURE__ */ new Map();
|
|
2088
|
+
var GIT_TIMEOUT_MS = 3e3;
|
|
2089
|
+
function runGit(cwd, args, timeout) {
|
|
2090
|
+
return (0, import_node_child_process.spawnSync)("git", args, {
|
|
2091
|
+
cwd,
|
|
2092
|
+
encoding: "utf8",
|
|
2093
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
2094
|
+
...timeout === void 0 ? {} : { timeout },
|
|
2095
|
+
windowsHide: true
|
|
2096
|
+
});
|
|
2097
|
+
}
|
|
2098
|
+
function isTransient(r) {
|
|
2099
|
+
const code = r.error?.code;
|
|
2100
|
+
if (code !== void 0 && code !== "ENOENT")
|
|
2101
|
+
return true;
|
|
2102
|
+
return r.signal != null;
|
|
2103
|
+
}
|
|
2104
|
+
function foldKeyPath(p, platform = process.platform) {
|
|
2105
|
+
if (/^[A-Za-z]:\//.test(p))
|
|
2106
|
+
return p.toLowerCase();
|
|
2107
|
+
if (/^\/(?!\/)/.test(p))
|
|
2108
|
+
return p;
|
|
2109
|
+
return platform === "win32" ? p.toLowerCase() : p;
|
|
2110
|
+
}
|
|
2111
|
+
function wslUncToPosix(p) {
|
|
2112
|
+
let s = p.trim();
|
|
2113
|
+
if (/^[\\/]{2}\?[\\/]UNC[\\/]/i.test(s))
|
|
2114
|
+
s = "//" + s.slice(8);
|
|
2115
|
+
else if (/^[\\/]{2}\?[\\/]/.test(s))
|
|
2116
|
+
s = s.slice(4);
|
|
2117
|
+
const m = /^[\\/]{2}(?:wsl\$|wsl\.localhost)[\\/]+([^\\/]+)(?:[\\/]+(.*))?$/i.exec(s);
|
|
2118
|
+
if (!m)
|
|
2119
|
+
return void 0;
|
|
2120
|
+
const rest = (m[2] ?? "").replace(/[\\/]+/g, "/").replace(/^\/+/, "").replace(/\/+$/, "");
|
|
2121
|
+
return { distro: m[1], posix: "/" + rest };
|
|
2122
|
+
}
|
|
2123
|
+
var MAX_RESOLVE_DEPTH = 64;
|
|
2124
|
+
function resolveCaseInsensitive(p) {
|
|
2125
|
+
if ((0, import_node_fs4.existsSync)(p))
|
|
2126
|
+
return p;
|
|
2127
|
+
const home = (0, import_node_os3.homedir)();
|
|
2128
|
+
if (!home || home === "/")
|
|
2129
|
+
return void 0;
|
|
2130
|
+
const prefix = home.replace(/\/+$/, "") + "/";
|
|
2131
|
+
if (!p.toLowerCase().startsWith(prefix.toLowerCase()))
|
|
2132
|
+
return void 0;
|
|
2133
|
+
let current = prefix.slice(0, -1);
|
|
2134
|
+
if (!(0, import_node_fs4.existsSync)(current))
|
|
2135
|
+
return void 0;
|
|
2136
|
+
const segments = p.slice(prefix.length).split("/").filter((seg) => seg.length > 0);
|
|
2137
|
+
if (segments.length > MAX_RESOLVE_DEPTH)
|
|
2138
|
+
return void 0;
|
|
2139
|
+
for (const seg of segments) {
|
|
2140
|
+
const exact = current + "/" + seg;
|
|
2141
|
+
if ((0, import_node_fs4.existsSync)(exact)) {
|
|
2142
|
+
current = exact;
|
|
2143
|
+
continue;
|
|
2144
|
+
}
|
|
2145
|
+
let entries;
|
|
2146
|
+
try {
|
|
2147
|
+
entries = (0, import_node_fs4.readdirSync)(current);
|
|
2148
|
+
} catch {
|
|
2149
|
+
return void 0;
|
|
2150
|
+
}
|
|
2151
|
+
const lower = seg.toLowerCase();
|
|
2152
|
+
const hits = entries.filter((e) => e.toLowerCase() === lower);
|
|
2153
|
+
if (hits.length !== 1)
|
|
2154
|
+
return void 0;
|
|
2155
|
+
current = current + "/" + hits[0];
|
|
2156
|
+
}
|
|
2157
|
+
return (0, import_node_fs4.existsSync)(current) ? current : void 0;
|
|
2158
|
+
}
|
|
2159
|
+
function pathKey(p) {
|
|
2160
|
+
const unc = wslUncToPosix(p);
|
|
2161
|
+
if (unc)
|
|
2162
|
+
return "path:" + unc.posix;
|
|
2163
|
+
return "path:" + foldKeyPath(normalizePath(p));
|
|
2164
|
+
}
|
|
2165
|
+
var upgradeCache = /* @__PURE__ */ new Map();
|
|
2166
|
+
function upgradeLocalPathKey(key) {
|
|
2167
|
+
if (!key.startsWith("path:"))
|
|
2168
|
+
return key;
|
|
2169
|
+
const hit = upgradeCache.get(key);
|
|
2170
|
+
if (hit !== void 0)
|
|
2171
|
+
return hit;
|
|
2172
|
+
const answer = (() => {
|
|
2173
|
+
const raw = key.slice("path:".length);
|
|
2174
|
+
const unc = wslUncToPosix(raw);
|
|
2175
|
+
const p = unc ? unc.posix : raw;
|
|
2176
|
+
if (!/^\/(?!\/)/.test(p))
|
|
2177
|
+
return key;
|
|
2178
|
+
const dir = resolveCaseInsensitive(p);
|
|
2179
|
+
if (dir === void 0)
|
|
2180
|
+
return key;
|
|
2181
|
+
const r = deriveProjectKey(dir);
|
|
2182
|
+
if (r.key && (r.kind === "git" || r.kind === "origin"))
|
|
2183
|
+
return r.key;
|
|
2184
|
+
return key;
|
|
2185
|
+
})();
|
|
2186
|
+
upgradeCache.set(key, answer);
|
|
2187
|
+
return answer;
|
|
2188
|
+
}
|
|
2189
|
+
function normalizeOrigin(url) {
|
|
2190
|
+
let s = url.trim();
|
|
2191
|
+
if (!s)
|
|
2192
|
+
return void 0;
|
|
2193
|
+
if (/^file:\/\//i.test(s))
|
|
2194
|
+
return void 0;
|
|
2195
|
+
if (/^[A-Za-z]:/.test(s))
|
|
2196
|
+
return void 0;
|
|
2197
|
+
const scheme = /^([A-Za-z][A-Za-z0-9+.-]*:)?\/\//.exec(s);
|
|
2198
|
+
if (scheme) {
|
|
2199
|
+
s = s.slice(scheme[0].length);
|
|
2200
|
+
} else if (s.includes("://")) {
|
|
2201
|
+
return void 0;
|
|
2202
|
+
}
|
|
2203
|
+
const firstSlash = s.indexOf("/");
|
|
2204
|
+
const authEnd = firstSlash < 0 ? s.length : firstSlash;
|
|
2205
|
+
const at = s.lastIndexOf("@", authEnd - 1);
|
|
2206
|
+
if (at >= 0)
|
|
2207
|
+
s = s.slice(at + 1);
|
|
2208
|
+
if (!scheme) {
|
|
2209
|
+
if (s.startsWith("/") || s.startsWith("."))
|
|
2210
|
+
return void 0;
|
|
2211
|
+
const colon = s.indexOf(":");
|
|
2212
|
+
if (colon < 0)
|
|
2213
|
+
return void 0;
|
|
2214
|
+
s = s.slice(0, colon) + "/" + s.slice(colon + 1).replace(/^\/+/, "");
|
|
2215
|
+
}
|
|
2216
|
+
const slash = s.indexOf("/");
|
|
2217
|
+
let host = slash < 0 ? s : s.slice(0, slash);
|
|
2218
|
+
const rest = slash < 0 ? "" : s.slice(slash);
|
|
2219
|
+
const port = /^(.*):(\d+)$/.exec(host);
|
|
2220
|
+
if (port)
|
|
2221
|
+
host = port[1];
|
|
2222
|
+
if (!host)
|
|
2223
|
+
return void 0;
|
|
2224
|
+
s = host + rest;
|
|
2225
|
+
s = s.replace(/\/+$/, "");
|
|
2226
|
+
s = s.replace(/\.git$/i, "");
|
|
2227
|
+
s = s.replace(/\/+$/, "");
|
|
2228
|
+
if (!s.includes("/"))
|
|
2229
|
+
return void 0;
|
|
2230
|
+
return s.toLowerCase();
|
|
2231
|
+
}
|
|
2232
|
+
function transient() {
|
|
2233
|
+
return { key: void 0, kind: "transient", transientFailure: true };
|
|
2234
|
+
}
|
|
2235
|
+
function deriveProjectKey(cwd) {
|
|
2236
|
+
const cacheKey = normalizePath(cwd);
|
|
2237
|
+
const hit = cache.get(cacheKey);
|
|
2238
|
+
if (hit)
|
|
2239
|
+
return hit;
|
|
2240
|
+
const result = derive(cwd);
|
|
2241
|
+
if (!result.transientFailure)
|
|
2242
|
+
cache.set(cacheKey, result);
|
|
2243
|
+
return result;
|
|
2244
|
+
}
|
|
2245
|
+
function derive(cwd) {
|
|
2246
|
+
if (!(0, import_node_fs4.existsSync)(cwd))
|
|
2247
|
+
return { kind: "path", key: pathKey(cwd) };
|
|
2248
|
+
let rev = runGit(cwd, ["rev-parse", "--is-shallow-repository", "--show-toplevel"], GIT_TIMEOUT_MS);
|
|
2249
|
+
if (rev.error?.code === "ENOENT") {
|
|
2250
|
+
return { kind: "path", key: pathKey(cwd), gitMissing: true };
|
|
2251
|
+
}
|
|
2252
|
+
if (isTransient(rev)) {
|
|
2253
|
+
rev = runGit(cwd, ["rev-parse", "--is-shallow-repository", "--show-toplevel"]);
|
|
2254
|
+
if (rev.error?.code === "ENOENT") {
|
|
2255
|
+
return { kind: "path", key: pathKey(cwd), gitMissing: true };
|
|
2256
|
+
}
|
|
2257
|
+
if (isTransient(rev))
|
|
2258
|
+
return transient();
|
|
2259
|
+
}
|
|
2260
|
+
if (rev.status === 128)
|
|
2261
|
+
return { kind: "path", key: pathKey(cwd) };
|
|
2262
|
+
if (rev.status !== 0)
|
|
2263
|
+
return { kind: "path", key: pathKey(cwd) };
|
|
2264
|
+
const lines = (rev.stdout ?? "").split("\n").map((l) => l.trim()).filter((l) => l.length > 0);
|
|
2265
|
+
const shallow = lines[0] === "true";
|
|
2266
|
+
const toplevel = lines[1];
|
|
2267
|
+
if (!shallow) {
|
|
2268
|
+
const args = ["--no-replace-objects", "rev-list", "--max-parents=0", "--format=%ct %H", "HEAD"];
|
|
2269
|
+
let roots = runGit(cwd, args, GIT_TIMEOUT_MS);
|
|
2270
|
+
if (isTransient(roots)) {
|
|
2271
|
+
roots = runGit(cwd, args);
|
|
2272
|
+
if (isTransient(roots))
|
|
2273
|
+
return transient();
|
|
2274
|
+
}
|
|
2275
|
+
if (roots.status === 0) {
|
|
2276
|
+
const parsed = [];
|
|
2277
|
+
for (const line of (roots.stdout ?? "").split("\n")) {
|
|
2278
|
+
const t = line.trim();
|
|
2279
|
+
if (!t || t.startsWith("commit "))
|
|
2280
|
+
continue;
|
|
2281
|
+
const m = /^(\d+)\s+([0-9a-f]{40})$/.exec(t);
|
|
2282
|
+
if (m)
|
|
2283
|
+
parsed.push({ ct: Number(m[1]), hash: m[2] });
|
|
2284
|
+
}
|
|
2285
|
+
parsed.sort((a, b) => a.ct - b.ct || (a.hash < b.hash ? -1 : a.hash > b.hash ? 1 : 0));
|
|
2286
|
+
const first = parsed[0];
|
|
2287
|
+
if (first) {
|
|
2288
|
+
return { kind: "git", key: "git:" + first.hash, ...toplevel ? { toplevel } : {} };
|
|
2289
|
+
}
|
|
2290
|
+
}
|
|
2291
|
+
}
|
|
2292
|
+
let origin = runGit(cwd, ["config", "--get", "remote.origin.url"], GIT_TIMEOUT_MS);
|
|
2293
|
+
if (isTransient(origin)) {
|
|
2294
|
+
origin = runGit(cwd, ["config", "--get", "remote.origin.url"]);
|
|
2295
|
+
if (isTransient(origin))
|
|
2296
|
+
return transient();
|
|
2297
|
+
}
|
|
2298
|
+
if (origin.status === 0) {
|
|
2299
|
+
const value = normalizeOrigin(origin.stdout ?? "");
|
|
2300
|
+
if (value)
|
|
2301
|
+
return { kind: "origin", key: "origin:" + value, ...toplevel ? { toplevel } : {} };
|
|
2302
|
+
}
|
|
2303
|
+
return { kind: "path", key: pathKey(toplevel ?? cwd), ...toplevel ? { toplevel } : {} };
|
|
2304
|
+
}
|
|
2305
|
+
|
|
2306
|
+
// src/recall/mirror-meta.ts
|
|
2307
|
+
var import_node_fs5 = require("node:fs");
|
|
2308
|
+
var import_node_path5 = require("node:path");
|
|
2309
|
+
init_paths();
|
|
2310
|
+
function isUnderRemoteRoot(p) {
|
|
2311
|
+
return (0, import_node_path5.resolve)(p).startsWith((0, import_node_path5.resolve)(remoteRoot()) + import_node_path5.sep);
|
|
2312
|
+
}
|
|
2313
|
+
function readMirrorMeta(transcriptPath) {
|
|
2314
|
+
let parsed;
|
|
2315
|
+
try {
|
|
2316
|
+
parsed = JSON.parse((0, import_node_fs5.readFileSync)(`${transcriptPath}.meta.json`, "utf-8"));
|
|
2317
|
+
} catch {
|
|
2318
|
+
return null;
|
|
2319
|
+
}
|
|
2320
|
+
if (!parsed || typeof parsed !== "object")
|
|
2321
|
+
return null;
|
|
2322
|
+
const m = parsed;
|
|
2323
|
+
if (m["v"] !== 1)
|
|
2324
|
+
return null;
|
|
2325
|
+
if (m["key"] !== void 0 && typeof m["key"] !== "string")
|
|
2326
|
+
return null;
|
|
2327
|
+
if (m["cwd"] !== void 0 && typeof m["cwd"] !== "string")
|
|
2328
|
+
return null;
|
|
2329
|
+
return parsed;
|
|
2330
|
+
}
|
|
2331
|
+
|
|
1701
2332
|
// src/recall/message-ingest.ts
|
|
1702
2333
|
init_log();
|
|
1703
2334
|
|
|
@@ -1727,48 +2358,11 @@ function parseJsonlFile(filepath) {
|
|
|
1727
2358
|
return entries;
|
|
1728
2359
|
} catch (error) {
|
|
1729
2360
|
log({ level: "error", source: "jsonl-reader", summary: `Failed to read JSONL file ${filepath}: ${error instanceof Error ? error.message : String(error)}`, data: { filepath, error: String(error) } });
|
|
1730
|
-
|
|
2361
|
+
throw error;
|
|
1731
2362
|
}
|
|
1732
2363
|
}
|
|
1733
2364
|
|
|
1734
2365
|
// src/adapters/claude/claude-entry-adapter.ts
|
|
1735
|
-
function isSystemContextContent(message) {
|
|
1736
|
-
if (!message || message.role !== "user")
|
|
1737
|
-
return false;
|
|
1738
|
-
const content = message.content;
|
|
1739
|
-
let text;
|
|
1740
|
-
if (typeof content === "string") {
|
|
1741
|
-
text = content;
|
|
1742
|
-
} else if (Array.isArray(content)) {
|
|
1743
|
-
const firstText = content.find(
|
|
1744
|
-
(b) => typeof b === "object" && b !== null && b.type === "text"
|
|
1745
|
-
);
|
|
1746
|
-
if (firstText && "text" in firstText) {
|
|
1747
|
-
text = firstText.text;
|
|
1748
|
-
}
|
|
1749
|
-
}
|
|
1750
|
-
if (!text)
|
|
1751
|
-
return false;
|
|
1752
|
-
if (text.startsWith("<system-reminder>"))
|
|
1753
|
-
return true;
|
|
1754
|
-
if (text.startsWith("<environment_context>"))
|
|
1755
|
-
return true;
|
|
1756
|
-
if (text.startsWith("<INSTRUCTIONS>"))
|
|
1757
|
-
return true;
|
|
1758
|
-
if (text.startsWith("# AGENTS.md instructions for"))
|
|
1759
|
-
return true;
|
|
1760
|
-
if (text.startsWith("<context>"))
|
|
1761
|
-
return true;
|
|
1762
|
-
if (text.startsWith("<task-notification>"))
|
|
1763
|
-
return true;
|
|
1764
|
-
if (text.startsWith("<command-name>"))
|
|
1765
|
-
return true;
|
|
1766
|
-
if (text.startsWith("<local-command-stdout>"))
|
|
1767
|
-
return true;
|
|
1768
|
-
if (text.startsWith("<local-command-caveat>"))
|
|
1769
|
-
return true;
|
|
1770
|
-
return false;
|
|
1771
|
-
}
|
|
1772
2366
|
function sanitizeContentBlocks(content) {
|
|
1773
2367
|
if (typeof content === "string")
|
|
1774
2368
|
return content;
|
|
@@ -1988,15 +2582,17 @@ function emitMessage(payload, base, model, counter) {
|
|
|
1988
2582
|
if (role === "developer")
|
|
1989
2583
|
return [];
|
|
1990
2584
|
if (role === "user") {
|
|
2585
|
+
const message = {
|
|
2586
|
+
role: "user",
|
|
2587
|
+
content: adaptContentItems(contentItems)
|
|
2588
|
+
};
|
|
1991
2589
|
return [
|
|
1992
2590
|
{
|
|
1993
2591
|
type: "user",
|
|
1994
2592
|
uuid: generateId(base.sessionId, counter),
|
|
1995
2593
|
...base,
|
|
1996
|
-
message
|
|
1997
|
-
|
|
1998
|
-
content: adaptContentItems(contentItems)
|
|
1999
|
-
}
|
|
2594
|
+
message,
|
|
2595
|
+
...isSystemContextContent(message) && { isMeta: true }
|
|
2000
2596
|
}
|
|
2001
2597
|
];
|
|
2002
2598
|
}
|
|
@@ -2473,6 +3069,11 @@ function parseCodexPatch(input) {
|
|
|
2473
3069
|
currentKind = "update";
|
|
2474
3070
|
continue;
|
|
2475
3071
|
}
|
|
3072
|
+
const moveMatch = line.match(/^\*\*\* Move to:\s*(.+)$/);
|
|
3073
|
+
if (moveMatch && currentKind === "update") {
|
|
3074
|
+
currentPath = moveMatch[1].trim();
|
|
3075
|
+
continue;
|
|
3076
|
+
}
|
|
2476
3077
|
const addMatch = line.match(/^\*\*\* Add File:\s*(.+)$/);
|
|
2477
3078
|
if (addMatch) {
|
|
2478
3079
|
flushCurrent();
|
|
@@ -2508,7 +3109,7 @@ function parseCodexPatch(input) {
|
|
|
2508
3109
|
return changes;
|
|
2509
3110
|
}
|
|
2510
3111
|
function generateId(sessionId, counter) {
|
|
2511
|
-
return `codex-jsonl-${sessionId
|
|
3112
|
+
return `codex-jsonl-${sessionId}-${counter}`;
|
|
2512
3113
|
}
|
|
2513
3114
|
|
|
2514
3115
|
// src/recall/message-ingest.ts
|
|
@@ -2517,12 +3118,19 @@ function extractEntryText(entry) {
|
|
|
2517
3118
|
if (!msg)
|
|
2518
3119
|
return "";
|
|
2519
3120
|
if (typeof msg.content === "string")
|
|
2520
|
-
return msg.content.trim();
|
|
3121
|
+
return msg.content.replaceAll("\0", "").trim();
|
|
2521
3122
|
if (Array.isArray(msg.content)) {
|
|
2522
|
-
return msg.content.filter((b) => b.type === "text").map((b) => b.text?.trim()).filter(Boolean).join("\n\n");
|
|
3123
|
+
return msg.content.filter((b) => b.type === "text").map((b) => b.text?.replaceAll("\0", "").trim()).filter(Boolean).join("\n\n");
|
|
2523
3124
|
}
|
|
2524
3125
|
return "";
|
|
2525
3126
|
}
|
|
3127
|
+
function loadTranscriptEntries(transcriptPath, vendor, canonicalSessionId) {
|
|
3128
|
+
if (vendor === "claude") {
|
|
3129
|
+
const raw = parseJsonlFile(transcriptPath);
|
|
3130
|
+
return adaptClaudeEntries(raw);
|
|
3131
|
+
}
|
|
3132
|
+
return adaptCodexJsonlRecords(parseCodexJsonlFile(transcriptPath), canonicalSessionId);
|
|
3133
|
+
}
|
|
2526
3134
|
function entriesCwd(entries) {
|
|
2527
3135
|
for (const e of entries) {
|
|
2528
3136
|
if (e.cwd)
|
|
@@ -2563,13 +3171,7 @@ async function ingestSessionMessages(sessionId, transcriptPath, vendor, options)
|
|
|
2563
3171
|
}));
|
|
2564
3172
|
let rawEntries;
|
|
2565
3173
|
try {
|
|
2566
|
-
|
|
2567
|
-
const raw = parseJsonlFile(transcriptPath);
|
|
2568
|
-
rawEntries = adaptClaudeEntries(raw);
|
|
2569
|
-
} else {
|
|
2570
|
-
const envelopes = parseCodexJsonlFile(transcriptPath);
|
|
2571
|
-
rawEntries = adaptCodexJsonlRecords(envelopes, canonicalId);
|
|
2572
|
-
}
|
|
3174
|
+
rawEntries = loadTranscriptEntries(transcriptPath, vendor, canonicalId);
|
|
2573
3175
|
} catch (err) {
|
|
2574
3176
|
return {
|
|
2575
3177
|
sessionId: canonicalId,
|
|
@@ -2590,11 +3192,22 @@ async function ingestSessionMessages(sessionId, transcriptPath, vendor, options)
|
|
|
2590
3192
|
}
|
|
2591
3193
|
const rawProjectId = options?.projectId ?? entriesCwd(rawEntries);
|
|
2592
3194
|
const projectId = rawProjectId ? normalizePath(rawProjectId) : null;
|
|
3195
|
+
const localKey = () => {
|
|
3196
|
+
const k = deriveProjectKey(rawProjectId).key ?? null;
|
|
3197
|
+
return k !== null && wslUncToPosix(rawProjectId) ? upgradeLocalPathKey(k) : k;
|
|
3198
|
+
};
|
|
3199
|
+
const sidecarKey = () => {
|
|
3200
|
+
const k = readMirrorMeta(transcriptPath)?.key;
|
|
3201
|
+
return k === void 0 ? null : upgradeLocalPathKey(k);
|
|
3202
|
+
};
|
|
3203
|
+
const projectKey = rawProjectId ? options?.projectKey !== void 0 ? options.projectKey : isUnderRemoteRoot(transcriptPath) ? sidecarKey() : localKey() : null;
|
|
2593
3204
|
const filtered = stripToolContent(rawEntries);
|
|
2594
3205
|
const topLevel = filtered.filter((e) => !e.parentToolUseID);
|
|
2595
3206
|
const records = [];
|
|
2596
3207
|
for (let i = 0; i < topLevel.length; i++) {
|
|
2597
3208
|
const entry = topLevel[i];
|
|
3209
|
+
if (shouldDropAsMeta(entry))
|
|
3210
|
+
continue;
|
|
2598
3211
|
if (!entry.uuid)
|
|
2599
3212
|
continue;
|
|
2600
3213
|
const text = extractEntryText(entry);
|
|
@@ -2609,7 +3222,8 @@ async function ingestSessionMessages(sessionId, transcriptPath, vendor, options)
|
|
|
2609
3222
|
project_id: projectId,
|
|
2610
3223
|
created_at: createdAt,
|
|
2611
3224
|
message_role: entry.message?.role ?? entry.type ?? null,
|
|
2612
|
-
retrieval_class: retrievalClass
|
|
3225
|
+
retrieval_class: retrievalClass,
|
|
3226
|
+
project_key: projectKey
|
|
2613
3227
|
});
|
|
2614
3228
|
}
|
|
2615
3229
|
if (records.length === 0) {
|
|
@@ -2621,11 +3235,19 @@ async function ingestSessionMessages(sessionId, transcriptPath, vendor, options)
|
|
|
2621
3235
|
}
|
|
2622
3236
|
return { sessionId: canonicalId, chunksCreated: 0, skipped: true, retrievalClass };
|
|
2623
3237
|
}
|
|
3238
|
+
let inserted = 0;
|
|
3239
|
+
const previouslyIndexed = hasSessionMessages(canonicalId);
|
|
2624
3240
|
try {
|
|
2625
|
-
insertMessages(records, {
|
|
3241
|
+
inserted = insertMessages(records, {
|
|
3242
|
+
sourceMessageIds: topLevel.flatMap((e) => e.uuid ? [e.uuid] : []),
|
|
2626
3243
|
...options?.force ? { replaceSessionId: canonicalId } : {},
|
|
2627
3244
|
provenance,
|
|
2628
|
-
aliases
|
|
3245
|
+
aliases,
|
|
3246
|
+
// Same identity this chunk's own rows get; back-fills the session's
|
|
3247
|
+
// NULL-identity rows atomically with the insert (M2). Runs on every
|
|
3248
|
+
// ingest path — push, sweep, backfill — so a sweep of a mirror whose
|
|
3249
|
+
// sidecar now carries the key repairs rows without a new push.
|
|
3250
|
+
adopt: { sessionId: canonicalId, projectKey, projectId }
|
|
2629
3251
|
});
|
|
2630
3252
|
} catch (err) {
|
|
2631
3253
|
return {
|
|
@@ -2636,26 +3258,58 @@ async function ingestSessionMessages(sessionId, transcriptPath, vendor, options)
|
|
|
2636
3258
|
retrievalClass
|
|
2637
3259
|
};
|
|
2638
3260
|
}
|
|
3261
|
+
if (!previouslyIndexed && inserted < records.length) {
|
|
3262
|
+
log({ source: "recall:ingest", level: "warn", summary: `Session ${canonicalId}: ${records.length} records offered, ${inserted} inserted (duplicate message IDs)` });
|
|
3263
|
+
}
|
|
2639
3264
|
return {
|
|
2640
3265
|
sessionId: canonicalId,
|
|
2641
|
-
chunksCreated:
|
|
3266
|
+
chunksCreated: inserted,
|
|
3267
|
+
recordsOffered: records.length,
|
|
2642
3268
|
skipped: false,
|
|
2643
3269
|
retrievalClass
|
|
2644
3270
|
};
|
|
2645
3271
|
}
|
|
2646
3272
|
|
|
2647
3273
|
// src/hooks/stop-hook.ts
|
|
3274
|
+
init_config();
|
|
2648
3275
|
init_paths();
|
|
2649
3276
|
var import_fs = require("fs");
|
|
2650
3277
|
var import_child_process = require("child_process");
|
|
2651
3278
|
var import_path = require("path");
|
|
2652
|
-
|
|
3279
|
+
var import_promises = require("node:timers/promises");
|
|
3280
|
+
async function retryBusyIngest(operation) {
|
|
3281
|
+
for (let attempt = 0; ; attempt++) {
|
|
3282
|
+
try {
|
|
3283
|
+
const result = await operation();
|
|
3284
|
+
if (attempt < 2 && result.error && /database is locked|SQLITE_BUSY/i.test(result.error)) {
|
|
3285
|
+
await (0, import_promises.setTimeout)(25 * (attempt + 1));
|
|
3286
|
+
continue;
|
|
3287
|
+
}
|
|
3288
|
+
return result;
|
|
3289
|
+
} catch (error) {
|
|
3290
|
+
if (attempt >= 2 || !/database is locked|SQLITE_BUSY/i.test(String(error)))
|
|
3291
|
+
throw error;
|
|
3292
|
+
await (0, import_promises.setTimeout)(25 * (attempt + 1));
|
|
3293
|
+
}
|
|
3294
|
+
}
|
|
3295
|
+
}
|
|
3296
|
+
function vendorForTranscript(transcriptPath, codexHome = codexRoot()) {
|
|
2653
3297
|
const p = transcriptPath.replace(/\\/g, "/");
|
|
2654
3298
|
const root = codexHome?.replace(/\\/g, "/").replace(/\/$/, "");
|
|
2655
3299
|
const underCodexHome = root ? p === root || p.startsWith(`${root}/`) : false;
|
|
2656
3300
|
return p.includes("/.codex/") || underCodexHome ? "codex" : "claude";
|
|
2657
3301
|
}
|
|
2658
3302
|
function resolveIngestTarget(payload) {
|
|
3303
|
+
if (!payload || typeof payload !== "object" || Array.isArray(payload))
|
|
3304
|
+
return null;
|
|
3305
|
+
if (payload.agent_transcript_path != null && typeof payload.agent_transcript_path !== "string")
|
|
3306
|
+
return null;
|
|
3307
|
+
if (payload.transcript_path != null && typeof payload.transcript_path !== "string")
|
|
3308
|
+
return null;
|
|
3309
|
+
if (payload.session_id != null && typeof payload.session_id !== "string")
|
|
3310
|
+
return null;
|
|
3311
|
+
if (payload.agent_id != null && typeof payload.agent_id !== "string")
|
|
3312
|
+
return null;
|
|
2659
3313
|
if (payload.agent_transcript_path) {
|
|
2660
3314
|
const transcriptPath = payload.agent_transcript_path;
|
|
2661
3315
|
const base = transcriptPath.replace(/\\/g, "/").split("/").pop().replace(/\.jsonl$/i, "");
|
|
@@ -2696,6 +3350,8 @@ async function runStopHook() {
|
|
|
2696
3350
|
} catch {
|
|
2697
3351
|
process.exit(0);
|
|
2698
3352
|
}
|
|
3353
|
+
if (!payload || typeof payload !== "object" || Array.isArray(payload))
|
|
3354
|
+
process.exit(0);
|
|
2699
3355
|
if (payload.stop_hook_active)
|
|
2700
3356
|
process.exit(0);
|
|
2701
3357
|
const target = resolveIngestTarget(payload);
|
|
@@ -2703,15 +3359,45 @@ async function runStopHook() {
|
|
|
2703
3359
|
process.exit(0);
|
|
2704
3360
|
let ingestedClass;
|
|
2705
3361
|
let canonicalId = target.sessionId;
|
|
3362
|
+
let satellite = false;
|
|
2706
3363
|
try {
|
|
2707
|
-
|
|
3364
|
+
const sat = readSatelliteConfig();
|
|
3365
|
+
if (sat) {
|
|
3366
|
+
satellite = true;
|
|
3367
|
+
(0, import_child_process.spawn)(
|
|
3368
|
+
process.execPath,
|
|
3369
|
+
[
|
|
3370
|
+
(0, import_path.join)(binDir(), "push-pending.js"),
|
|
3371
|
+
"--named",
|
|
3372
|
+
target.transcriptPath,
|
|
3373
|
+
"--hook",
|
|
3374
|
+
JSON.stringify(target.hook),
|
|
3375
|
+
"--cwd",
|
|
3376
|
+
payload.cwd ?? ""
|
|
3377
|
+
],
|
|
3378
|
+
{ detached: true, stdio: "ignore", windowsHide: true }
|
|
3379
|
+
).unref();
|
|
3380
|
+
process.exit(0);
|
|
3381
|
+
}
|
|
2708
3382
|
const vendor = vendorForTranscript(target.transcriptPath);
|
|
2709
|
-
const
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
3383
|
+
const projectKey = payload.cwd ? deriveProjectKey(payload.cwd).key ?? null : void 0;
|
|
3384
|
+
const result = await retryBusyIngest(async () => {
|
|
3385
|
+
getDb(dbPath()).exec("PRAGMA busy_timeout = 5000;");
|
|
3386
|
+
return ingestSessionMessages(
|
|
3387
|
+
target.sessionId,
|
|
3388
|
+
target.transcriptPath,
|
|
3389
|
+
vendor,
|
|
3390
|
+
{
|
|
3391
|
+
projectId: payload.cwd ?? void 0,
|
|
3392
|
+
// Derive ONCE per turn (spec §4.2): two git spawns, ~4-7 ms. A throw
|
|
3393
|
+
// is caught by the enclosing try, so the hook still exits 0. A
|
|
3394
|
+
// transient failure becomes an explicit null — ingest must not repeat
|
|
3395
|
+
// the derivation that just failed.
|
|
3396
|
+
projectKey,
|
|
3397
|
+
hook: target.hook
|
|
3398
|
+
}
|
|
3399
|
+
);
|
|
3400
|
+
});
|
|
2715
3401
|
ingestedClass = result?.retrievalClass;
|
|
2716
3402
|
canonicalId = result?.sessionId ?? target.sessionId;
|
|
2717
3403
|
if (result?.error) {
|
|
@@ -2726,7 +3412,7 @@ async function runStopHook() {
|
|
|
2726
3412
|
`
|
|
2727
3413
|
);
|
|
2728
3414
|
}
|
|
2729
|
-
if (!target.hook.isSubagent && ingestedClass !== "agent") {
|
|
3415
|
+
if (!satellite && !target.hook.isSubagent && ingestedClass !== "agent") {
|
|
2730
3416
|
(0, import_child_process.spawn)(
|
|
2731
3417
|
process.execPath,
|
|
2732
3418
|
[(0, import_path.join)(binDir(), "embed-pending.js"), canonicalId],
|
|
@@ -2740,10 +3426,15 @@ async function runStopHook() {
|
|
|
2740
3426
|
process.exit(0);
|
|
2741
3427
|
}
|
|
2742
3428
|
if (typeof require !== "undefined" && typeof module !== "undefined" && require.main === module) {
|
|
2743
|
-
void runStopHook()
|
|
3429
|
+
void runStopHook().catch((error) => {
|
|
3430
|
+
logStopHook(`${(/* @__PURE__ */ new Date()).toISOString()} hook-failed err=${String(error)}
|
|
3431
|
+
`);
|
|
3432
|
+
process.exit(0);
|
|
3433
|
+
});
|
|
2744
3434
|
}
|
|
2745
3435
|
// Annotate the CommonJS export names for ESM import in node:
|
|
2746
3436
|
0 && (module.exports = {
|
|
2747
3437
|
resolveIngestTarget,
|
|
3438
|
+
retryBusyIngest,
|
|
2748
3439
|
vendorForTranscript
|
|
2749
3440
|
});
|