crispy-recall 0.2.0 → 0.3.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/dist/stop-hook.js CHANGED
@@ -153,21 +153,21 @@ var require_file_uri_to_path = __commonJS({
153
153
  var rest = decodeURI(uri.substring(7));
154
154
  var firstSlash = rest.indexOf("/");
155
155
  var host = rest.substring(0, firstSlash);
156
- var path2 = rest.substring(firstSlash + 1);
156
+ var path = rest.substring(firstSlash + 1);
157
157
  if ("localhost" == host)
158
158
  host = "";
159
159
  if (host) {
160
160
  host = sep + sep + host;
161
161
  }
162
- path2 = path2.replace(/^(.+)\|/, "$1:");
162
+ path = path.replace(/^(.+)\|/, "$1:");
163
163
  if (sep == "\\") {
164
- path2 = path2.replace(/\//g, "\\");
164
+ path = path.replace(/\//g, "\\");
165
165
  }
166
- if (/^.+\:/.test(path2)) {
166
+ if (/^.+\:/.test(path)) {
167
167
  } else {
168
- path2 = sep + path2;
168
+ path = sep + path;
169
169
  }
170
- return host + path2;
170
+ return host + path;
171
171
  }
172
172
  }
173
173
  });
@@ -176,18 +176,18 @@ var require_file_uri_to_path = __commonJS({
176
176
  var require_bindings = __commonJS({
177
177
  "node_modules/bindings/bindings.js"(exports2, module2) {
178
178
  var fs3 = require("fs");
179
- var path2 = require("path");
179
+ var path = require("path");
180
180
  var fileURLToPath = require_file_uri_to_path();
181
- var join5 = path2.join;
182
- var dirname2 = path2.dirname;
183
- var exists = fs3.accessSync && function(path3) {
181
+ var join4 = path.join;
182
+ var dirname2 = path.dirname;
183
+ var exists = fs3.accessSync && function(path2) {
184
184
  try {
185
- fs3.accessSync(path3);
185
+ fs3.accessSync(path2);
186
186
  } catch (e) {
187
187
  return false;
188
188
  }
189
189
  return true;
190
- } || fs3.existsSync || path2.existsSync;
190
+ } || fs3.existsSync || path.existsSync;
191
191
  var defaults = {
192
192
  arrow: process.env.NODE_BINDINGS_ARROW || " \u2192 ",
193
193
  compiled: process.env.NODE_BINDINGS_COMPILED_DIR || "compiled",
@@ -233,13 +233,13 @@ var require_bindings = __commonJS({
233
233
  if (!opts.module_root) {
234
234
  opts.module_root = exports2.getRoot(exports2.getFileName());
235
235
  }
236
- if (path2.extname(opts.bindings) != ".node") {
236
+ if (path.extname(opts.bindings) != ".node") {
237
237
  opts.bindings += ".node";
238
238
  }
239
239
  var requireFunc = typeof __webpack_require__ === "function" ? __non_webpack_require__ : require;
240
240
  var tries = [], i = 0, l = opts.try.length, n, b, err;
241
241
  for (; i < l; i++) {
242
- n = join5.apply(
242
+ n = join4.apply(
243
243
  null,
244
244
  opts.try[i].map(function(p) {
245
245
  return opts[p] || p;
@@ -300,7 +300,7 @@ var require_bindings = __commonJS({
300
300
  if (dir === ".") {
301
301
  dir = process.cwd();
302
302
  }
303
- if (exists(join5(dir, "package.json")) || exists(join5(dir, "node_modules"))) {
303
+ if (exists(join4(dir, "package.json")) || exists(join4(dir, "node_modules"))) {
304
304
  return dir;
305
305
  }
306
306
  if (prev === dir) {
@@ -309,7 +309,7 @@ var require_bindings = __commonJS({
309
309
  );
310
310
  }
311
311
  prev = dir;
312
- dir = join5(dir, "..");
312
+ dir = join4(dir, "..");
313
313
  }
314
314
  };
315
315
  }
@@ -477,7 +477,7 @@ var require_backup = __commonJS({
477
477
  "node_modules/better-sqlite3/lib/methods/backup.js"(exports2, module2) {
478
478
  "use strict";
479
479
  var fs3 = require("fs");
480
- var path2 = require("path");
480
+ var path = require("path");
481
481
  var { promisify } = require("util");
482
482
  var { cppdb } = require_util();
483
483
  var fsAccess = promisify(fs3.access);
@@ -501,7 +501,7 @@ var require_backup = __commonJS({
501
501
  throw new TypeError('The "attached" option cannot be an empty string');
502
502
  if (handler != null && typeof handler !== "function")
503
503
  throw new TypeError('Expected the "progress" option to be a function');
504
- await fsAccess(path2.dirname(filename)).catch(() => {
504
+ await fsAccess(path.dirname(filename)).catch(() => {
505
505
  throw new TypeError("Cannot save backup because the directory does not exist");
506
506
  });
507
507
  const isNewFile = await fsAccess(filename).then(() => false, () => true);
@@ -832,7 +832,7 @@ var require_database = __commonJS({
832
832
  "node_modules/better-sqlite3/lib/database.js"(exports2, module2) {
833
833
  "use strict";
834
834
  var fs3 = require("fs");
835
- var path2 = require("path");
835
+ var path = require("path");
836
836
  var util = require_util();
837
837
  var SqliteError = require_sqlite_error();
838
838
  var DEFAULT_ADDON;
@@ -879,7 +879,7 @@ var require_database = __commonJS({
879
879
  addon = DEFAULT_ADDON || (DEFAULT_ADDON = require_bindings()("better_sqlite3.node"));
880
880
  } else if (typeof nativeBinding === "string") {
881
881
  const requireFunc = typeof __non_webpack_require__ === "function" ? __non_webpack_require__ : require;
882
- addon = requireFunc(path2.resolve(nativeBinding).replace(/(\.node)?$/, ".node"));
882
+ addon = requireFunc(path.resolve(nativeBinding).replace(/(\.node)?$/, ".node"));
883
883
  } else {
884
884
  addon = nativeBinding;
885
885
  }
@@ -887,7 +887,7 @@ var require_database = __commonJS({
887
887
  addon.setErrorConstructor(SqliteError);
888
888
  addon.isInitialized = true;
889
889
  }
890
- if (!anonymous && !filename.startsWith("file:") && !fs3.existsSync(path2.dirname(filename))) {
890
+ if (!anonymous && !filename.startsWith("file:") && !fs3.existsSync(path.dirname(filename))) {
891
891
  throw new TypeError("Cannot open database because the directory does not exist");
892
892
  }
893
893
  Object.defineProperties(this, {
@@ -933,6 +933,7 @@ var init_quantize = __esm({
933
933
  // src/hooks/stop-hook.ts
934
934
  var stop_hook_exports = {};
935
935
  __export(stop_hook_exports, {
936
+ resolveIngestTarget: () => resolveIngestTarget,
936
937
  vendorForTranscript: () => vendorForTranscript
937
938
  });
938
939
  module.exports = __toCommonJS(stop_hook_exports);
@@ -976,13 +977,25 @@ var BindingLoadError = class extends Error {
976
977
  constructor(dbPath2, cause) {
977
978
  super(
978
979
  `recall: failed to load the better-sqlite3 native binding while opening ${dbPath2}: ${cause.message}
979
- The binding is ABI-locked to the Node it was built for \u2014 run \`recall doctor\` (usually fixed by re-running \`recall install\`).`
980
+ The native SQLite binding is ABI-locked to the Node it was built for. This is usually one of:
981
+ \u2022 recall is running under a different Node than the one npm installed it with (e.g. Homebrew node vs nvm) \u2014 reinstall with \`npm install -g crispy-recall\` using the node on your PATH, then \`recall install\`.
982
+ \u2022 no prebuilt binary matched your Node version and it could not compile \u2014 use Node 22 LTS or 24+ (Node 23 has no prebuilt SQLite binding), and on macOS make sure Xcode Command Line Tools are installed (\`xcode-select --install\`).
983
+ Run \`recall doctor\` for details.`
980
984
  );
981
985
  this.dbPath = dbPath2;
982
986
  this.cause = cause;
983
987
  this.name = "BindingLoadError";
984
988
  }
985
989
  };
990
+ var MigrationPendingError = class extends Error {
991
+ constructor(dbPath2) {
992
+ 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.)"
994
+ );
995
+ this.dbPath = dbPath2;
996
+ this.name = "MigrationPendingError";
997
+ }
998
+ };
986
999
  function isBindingLoadError(e) {
987
1000
  if (!e || typeof e !== "object")
988
1001
  return false;
@@ -997,7 +1010,7 @@ function isBindingLoadError(e) {
997
1010
  }
998
1011
  var db = null;
999
1012
  var currentDbPath = null;
1000
- function getDb(dbPath2) {
1013
+ function getDb(dbPath2, opts) {
1001
1014
  if (db && currentDbPath === dbPath2)
1002
1015
  return db;
1003
1016
  if (db) {
@@ -1012,12 +1025,45 @@ function getDb(dbPath2) {
1012
1025
  raw.close();
1013
1026
  throw e;
1014
1027
  }
1015
- db = createAdapter(raw);
1028
+ const adapter = createAdapter(raw);
1029
+ if (isRetrievalMigrationPending(adapter)) {
1030
+ if (!opts?.allowPendingMigration) {
1031
+ raw.close();
1032
+ throw new MigrationPendingError(dbPath2);
1033
+ }
1034
+ db = adapter;
1035
+ currentDbPath = dbPath2;
1036
+ log({ source: "db", level: "info", summary: `DB: opened pending-migration DB at ${dbPath2} (installer mode)` });
1037
+ return db;
1038
+ }
1039
+ db = adapter;
1016
1040
  currentDbPath = dbPath2;
1017
1041
  ensureSchema(db);
1018
1042
  log({ source: "db", level: "info", summary: `DB: initialized at ${dbPath2}` });
1019
1043
  return db;
1020
1044
  }
1045
+ var RETRIEVAL_MIGRATION_KEY = "retrieval_class_migration";
1046
+ function isRetrievalMigrationPending(d) {
1047
+ try {
1048
+ const hasMessages = d.get(
1049
+ `SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'messages'`
1050
+ );
1051
+ if (!hasMessages)
1052
+ return false;
1053
+ const hasMeta = d.get(
1054
+ `SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'schema_meta'`
1055
+ );
1056
+ if (!hasMeta)
1057
+ return true;
1058
+ const row = d.get(
1059
+ `SELECT value FROM schema_meta WHERE key = ?`,
1060
+ [RETRIEVAL_MIGRATION_KEY]
1061
+ );
1062
+ return row?.value !== "complete";
1063
+ } catch {
1064
+ return true;
1065
+ }
1066
+ }
1021
1067
  function closeDb() {
1022
1068
  if (db) {
1023
1069
  db.close();
@@ -1128,16 +1174,18 @@ function cleanupWasmArtifacts(dbPath2) {
1128
1174
  } catch {
1129
1175
  }
1130
1176
  }
1131
- function ensureSchema(db3) {
1132
- db3.exec(`
1177
+ var RETRIEVAL_SCHEMA_DDL = {
1178
+ /** messages + provenance tables + indexes (no FTS objects). */
1179
+ tables: `
1133
1180
  CREATE TABLE IF NOT EXISTS messages (
1134
- message_id TEXT PRIMARY KEY,
1135
- session_id TEXT NOT NULL,
1136
- message_seq INTEGER NOT NULL,
1137
- message_text TEXT NOT NULL,
1138
- project_id TEXT,
1139
- created_at INTEGER NOT NULL,
1140
- message_role TEXT,
1181
+ message_id TEXT PRIMARY KEY,
1182
+ session_id TEXT NOT NULL,
1183
+ message_seq INTEGER NOT NULL,
1184
+ message_text TEXT NOT NULL,
1185
+ project_id TEXT,
1186
+ created_at INTEGER NOT NULL,
1187
+ message_role TEXT,
1188
+ retrieval_class TEXT NOT NULL DEFAULT 'hot',
1141
1189
  UNIQUE(session_id, message_id)
1142
1190
  );
1143
1191
 
@@ -1149,68 +1197,134 @@ function ensureSchema(db3) {
1149
1197
  -- embed drain and every Stop-hook catch-up. This index serves the ORDER BY so
1150
1198
  -- the planner walks it and early-terminates at LIMIT instead.
1151
1199
  CREATE INDEX IF NOT EXISTS idx_messages_created_at ON messages(created_at);
1152
- `);
1153
- db3.exec(`
1200
+
1201
+ -- session_provenance \u2014 durable per-session classification evidence:
1202
+ -- canonical id, vendor, root-vs-agent kind, parent thread, hook/agent
1203
+ -- metadata, and the transcript path \u2192 canonical id mapping that lets
1204
+ -- later T1/mtime scans resolve a child to ONE identity.
1205
+ CREATE TABLE IF NOT EXISTS session_provenance (
1206
+ session_id TEXT PRIMARY KEY,
1207
+ vendor TEXT NOT NULL,
1208
+ kind TEXT NOT NULL,
1209
+ parent_session_id TEXT,
1210
+ agent_depth INTEGER,
1211
+ agent_meta TEXT,
1212
+ transcript_path TEXT,
1213
+ updated_at INTEGER NOT NULL
1214
+ );
1215
+ CREATE INDEX IF NOT EXISTS idx_provenance_path ON session_provenance(transcript_path);
1216
+
1217
+ -- session_aliases \u2014 alternate identifiers (e.g. a hook agent_id that
1218
+ -- differs from the child rollout's session-meta UUID) \u2192 canonical id.
1219
+ CREATE TABLE IF NOT EXISTS session_aliases (
1220
+ alias_id TEXT PRIMARY KEY,
1221
+ session_id TEXT NOT NULL,
1222
+ source TEXT,
1223
+ created_at INTEGER NOT NULL
1224
+ );
1225
+
1226
+ CREATE TABLE IF NOT EXISTS schema_meta (
1227
+ key TEXT PRIMARY KEY,
1228
+ value TEXT NOT NULL
1229
+ );
1230
+ `,
1231
+ /** Filtered external-content view + FTS + four-state triggers + vocab. */
1232
+ fts: `
1233
+ -- Filtered external-content source: FTS5 rebuild/integrity-check read
1234
+ -- THIS view, so 'rebuild' repopulates only hot rows and the (rank-1)
1235
+ -- integrity check compares against the filtered corpus.
1236
+ CREATE VIEW IF NOT EXISTS searchable_messages AS
1237
+ SELECT rowid, message_text FROM messages WHERE retrieval_class = 'hot';
1238
+
1154
1239
  CREATE VIRTUAL TABLE IF NOT EXISTS messages_fts USING fts5(
1155
1240
  message_text,
1156
- content=messages,
1241
+ content=searchable_messages,
1157
1242
  content_rowid=rowid,
1158
1243
  tokenize='porter unicode61'
1159
1244
  );
1160
1245
 
1161
- CREATE TRIGGER IF NOT EXISTS messages_fts_ai AFTER INSERT ON messages BEGIN
1246
+ -- Four-state trigger behavior:
1247
+ -- insert hot \u2192 add | insert agent \u2192 no-op
1248
+ -- delete hot \u2192 delete | delete agent \u2192 no-op
1249
+ -- update hot\u2192hot \u2192 delete old + add new
1250
+ -- update hot\u2192agent \u2192 delete old only
1251
+ -- update agent\u2192hot \u2192 add new only
1252
+ -- update agent\u2192agent \u2192 no-op
1253
+ CREATE TRIGGER IF NOT EXISTS messages_fts_ai AFTER INSERT ON messages
1254
+ WHEN new.retrieval_class = 'hot'
1255
+ BEGIN
1162
1256
  INSERT INTO messages_fts(rowid, message_text) VALUES (new.rowid, new.message_text);
1163
1257
  END;
1164
1258
 
1165
- CREATE TRIGGER IF NOT EXISTS messages_fts_ad AFTER DELETE ON messages BEGIN
1259
+ CREATE TRIGGER IF NOT EXISTS messages_fts_ad AFTER DELETE ON messages
1260
+ WHEN old.retrieval_class = 'hot'
1261
+ BEGIN
1166
1262
  INSERT INTO messages_fts(messages_fts, rowid, message_text)
1167
1263
  VALUES ('delete', old.rowid, old.message_text);
1168
1264
  END;
1169
1265
 
1170
1266
  CREATE TRIGGER IF NOT EXISTS messages_fts_au AFTER UPDATE ON messages BEGIN
1171
1267
  INSERT INTO messages_fts(messages_fts, rowid, message_text)
1172
- VALUES ('delete', old.rowid, old.message_text);
1173
- INSERT INTO messages_fts(rowid, message_text) VALUES (new.rowid, new.message_text);
1268
+ SELECT 'delete', old.rowid, old.message_text WHERE old.retrieval_class = 'hot';
1269
+ INSERT INTO messages_fts(rowid, message_text)
1270
+ SELECT new.rowid, new.message_text WHERE new.retrieval_class = 'hot';
1174
1271
  END;
1175
- `);
1176
- db3.exec(`
1272
+
1177
1273
  CREATE VIRTUAL TABLE IF NOT EXISTS messages_fts_vocab
1178
1274
  USING fts5vocab(messages_fts, 'row');
1179
- `);
1180
- db3.exec(`
1181
- CREATE VIRTUAL TABLE IF NOT EXISTS _stem USING fts5(
1182
- t, tokenize='porter unicode61'
1183
- );
1184
- CREATE VIRTUAL TABLE IF NOT EXISTS _stem_vocab
1185
- USING fts5vocab(_stem, 'row');
1186
- `);
1187
- db3.exec(`
1188
- CREATE TABLE IF NOT EXISTS message_vectors (
1189
- message_id TEXT PRIMARY KEY REFERENCES messages(message_id) ON DELETE CASCADE,
1190
- embedding_q8 BLOB NOT NULL,
1191
- norm REAL NOT NULL,
1192
- quant_scale REAL NOT NULL,
1193
- embed_version INTEGER NOT NULL DEFAULT 1
1194
- );
1195
- `);
1196
- const hasEmbedVersion = () => db3.all(`PRAGMA table_info(message_vectors)`).some((c) => c.name === "embed_version");
1197
- if (!hasEmbedVersion()) {
1275
+ `
1276
+ };
1277
+ function ensureSchema(db3) {
1278
+ db3.exec("BEGIN IMMEDIATE");
1279
+ try {
1280
+ db3.exec(RETRIEVAL_SCHEMA_DDL.tables);
1281
+ db3.exec(RETRIEVAL_SCHEMA_DDL.fts);
1282
+ db3.exec(`
1283
+ CREATE VIRTUAL TABLE IF NOT EXISTS _stem USING fts5(
1284
+ t, tokenize='porter unicode61'
1285
+ );
1286
+ CREATE VIRTUAL TABLE IF NOT EXISTS _stem_vocab
1287
+ USING fts5vocab(_stem, 'row');
1288
+ `);
1289
+ db3.exec(`
1290
+ CREATE TABLE IF NOT EXISTS message_vectors (
1291
+ message_id TEXT PRIMARY KEY REFERENCES messages(message_id) ON DELETE CASCADE,
1292
+ embedding_q8 BLOB NOT NULL,
1293
+ norm REAL NOT NULL,
1294
+ quant_scale REAL NOT NULL,
1295
+ embed_version INTEGER NOT NULL DEFAULT 1
1296
+ );
1297
+ `);
1298
+ const hasEmbedVersion = () => db3.all(`PRAGMA table_info(message_vectors)`).some((c) => c.name === "embed_version");
1299
+ if (!hasEmbedVersion()) {
1300
+ try {
1301
+ db3.exec(`ALTER TABLE message_vectors ADD COLUMN embed_version INTEGER NOT NULL DEFAULT 1`);
1302
+ } catch (e) {
1303
+ if (!hasEmbedVersion())
1304
+ throw e;
1305
+ }
1306
+ }
1307
+ db3.exec(`
1308
+ CREATE TABLE IF NOT EXISTS ingest_watermark (
1309
+ transcript_path TEXT PRIMARY KEY,
1310
+ last_mtime INTEGER NOT NULL,
1311
+ last_size INTEGER NOT NULL,
1312
+ vendor TEXT NOT NULL
1313
+ );
1314
+ CREATE INDEX IF NOT EXISTS idx_watermark_vendor ON ingest_watermark(vendor);
1315
+ `);
1316
+ db3.exec(`
1317
+ INSERT OR IGNORE INTO schema_meta(key, value)
1318
+ VALUES ('${RETRIEVAL_MIGRATION_KEY}', 'complete');
1319
+ `);
1320
+ db3.exec("COMMIT");
1321
+ } catch (e) {
1198
1322
  try {
1199
- db3.exec(`ALTER TABLE message_vectors ADD COLUMN embed_version INTEGER NOT NULL DEFAULT 1`);
1200
- } catch (e) {
1201
- if (!hasEmbedVersion())
1202
- throw e;
1323
+ db3.exec("ROLLBACK");
1324
+ } catch {
1203
1325
  }
1326
+ throw e;
1204
1327
  }
1205
- db3.exec(`
1206
- CREATE TABLE IF NOT EXISTS ingest_watermark (
1207
- transcript_path TEXT PRIMARY KEY,
1208
- last_mtime INTEGER NOT NULL,
1209
- last_size INTEGER NOT NULL,
1210
- vendor TEXT NOT NULL
1211
- );
1212
- CREATE INDEX IF NOT EXISTS idx_watermark_vendor ON ingest_watermark(vendor);
1213
- `);
1214
1328
  }
1215
1329
 
1216
1330
  // src/recall/message-store.ts
@@ -1233,7 +1347,7 @@ function ensureDir2() {
1233
1347
  }
1234
1348
  }
1235
1349
  function insertMessages(messages, opts) {
1236
- if (messages.length === 0 && !opts?.replaceSessionId)
1350
+ if (messages.length === 0 && !opts?.replaceSessionId && !opts?.provenance)
1237
1351
  return;
1238
1352
  ensureDir2();
1239
1353
  const d = db2();
@@ -1249,8 +1363,8 @@ function insertMessages(messages, opts) {
1249
1363
  }
1250
1364
  const stmt = d.prepare(
1251
1365
  `INSERT OR IGNORE INTO messages
1252
- (message_id, session_id, message_seq, message_text, project_id, created_at, message_role)
1253
- VALUES (?, ?, ?, ?, ?, ?, ?)`
1366
+ (message_id, session_id, message_seq, message_text, project_id, created_at, message_role, retrieval_class)
1367
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)`
1254
1368
  );
1255
1369
  for (const m of messages) {
1256
1370
  stmt.run([
@@ -1260,9 +1374,55 @@ function insertMessages(messages, opts) {
1260
1374
  m.message_text,
1261
1375
  m.project_id,
1262
1376
  m.created_at,
1263
- m.message_role
1377
+ m.message_role,
1378
+ m.retrieval_class ?? "hot"
1264
1379
  ]);
1265
1380
  }
1381
+ if (opts?.provenance) {
1382
+ const p = opts.provenance;
1383
+ d.run(
1384
+ `INSERT INTO session_provenance
1385
+ (session_id, vendor, kind, parent_session_id, agent_depth, agent_meta, transcript_path, updated_at)
1386
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
1387
+ ON CONFLICT(session_id) DO UPDATE SET
1388
+ vendor = excluded.vendor,
1389
+ kind = excluded.kind,
1390
+ parent_session_id = COALESCE(excluded.parent_session_id, session_provenance.parent_session_id),
1391
+ agent_depth = COALESCE(excluded.agent_depth, session_provenance.agent_depth),
1392
+ agent_meta = COALESCE(excluded.agent_meta, session_provenance.agent_meta),
1393
+ transcript_path = COALESCE(excluded.transcript_path, session_provenance.transcript_path),
1394
+ updated_at = excluded.updated_at`,
1395
+ [
1396
+ p.sessionId,
1397
+ p.vendor,
1398
+ p.kind,
1399
+ p.parentSessionId,
1400
+ p.agentDepth,
1401
+ p.agentMeta ? JSON.stringify(p.agentMeta) : null,
1402
+ p.transcriptPath,
1403
+ Date.now()
1404
+ ]
1405
+ );
1406
+ if (p.kind === "agent") {
1407
+ d.run(
1408
+ `UPDATE messages SET retrieval_class = 'agent'
1409
+ WHERE session_id = ? AND retrieval_class != 'agent'`,
1410
+ [p.sessionId]
1411
+ );
1412
+ d.run(
1413
+ `DELETE FROM message_vectors WHERE message_id IN
1414
+ (SELECT message_id FROM messages WHERE session_id = ?)`,
1415
+ [p.sessionId]
1416
+ );
1417
+ }
1418
+ }
1419
+ for (const a of opts?.aliases ?? []) {
1420
+ d.run(
1421
+ `INSERT OR REPLACE INTO session_aliases (alias_id, session_id, source, created_at)
1422
+ VALUES (?, ?, ?, ?)`,
1423
+ [a.aliasId, a.sessionId, a.source, Date.now()]
1424
+ );
1425
+ }
1266
1426
  d.exec("COMMIT");
1267
1427
  } catch (e) {
1268
1428
  d.exec("ROLLBACK");
@@ -1270,6 +1430,260 @@ function insertMessages(messages, opts) {
1270
1430
  }
1271
1431
  }
1272
1432
 
1433
+ // src/recall/session-classifier.ts
1434
+ init_paths();
1435
+ init_log();
1436
+
1437
+ // src/adapters/codex/codex-jsonl-reader.ts
1438
+ var fs = __toESM(require("fs"));
1439
+ init_log();
1440
+ function parseCodexJsonlFile(filepath) {
1441
+ try {
1442
+ const content = fs.readFileSync(filepath, "utf-8");
1443
+ const lines = content.split("\n");
1444
+ const records = [];
1445
+ for (const line of lines) {
1446
+ const trimmed = line.trim();
1447
+ if (!trimmed)
1448
+ continue;
1449
+ try {
1450
+ const record = JSON.parse(trimmed);
1451
+ records.push(record);
1452
+ } catch (err) {
1453
+ log({ level: "warn", source: "codex-jsonl-reader", summary: `Skipping unparseable line: ${err.message}` });
1454
+ }
1455
+ }
1456
+ return records;
1457
+ } catch (error) {
1458
+ 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
+ return [];
1460
+ }
1461
+ }
1462
+ function extractCodexSessionMeta(filepath) {
1463
+ let fd = null;
1464
+ try {
1465
+ fd = fs.openSync(filepath, "r");
1466
+ const buffer = Buffer.alloc(8192);
1467
+ const bytesRead = fs.readSync(fd, buffer, 0, 8192, 0);
1468
+ if (bytesRead === 0)
1469
+ return null;
1470
+ const content = buffer.toString("utf-8", 0, bytesRead);
1471
+ const newlineIdx = content.indexOf("\n");
1472
+ const firstLine = newlineIdx >= 0 ? content.slice(0, newlineIdx) : content;
1473
+ const record = JSON.parse(firstLine.trim());
1474
+ if (record.type !== "session_meta")
1475
+ return null;
1476
+ const payload = record.payload;
1477
+ return {
1478
+ id: payload.id,
1479
+ cwd: payload.cwd,
1480
+ cli_version: payload.cli_version,
1481
+ git: payload.git,
1482
+ ...payload.source && typeof payload.source === "object" ? { source: payload.source } : {}
1483
+ };
1484
+ } catch {
1485
+ return null;
1486
+ } finally {
1487
+ if (fd !== null) {
1488
+ try {
1489
+ fs.closeSync(fd);
1490
+ } catch {
1491
+ }
1492
+ }
1493
+ }
1494
+ }
1495
+ var READ_BUFFER_SIZE = 64 * 1024;
1496
+ var TURN_CONTENT_CAP = 8 * 1024;
1497
+
1498
+ // src/recall/session-classifier.ts
1499
+ var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
1500
+ var ROLLOUT_UUID_RE = /rollout-.*-([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\.jsonl$/i;
1501
+ function normalize(p) {
1502
+ return p.replace(/\\/g, "/");
1503
+ }
1504
+ function basenameNoExt(p) {
1505
+ return normalize(p).split("/").pop().replace(/\.jsonl$/i, "");
1506
+ }
1507
+ function lookupStoredProvenance(transcriptPath) {
1508
+ try {
1509
+ const row = getDb(dbPath()).get(
1510
+ `SELECT session_id, kind, parent_session_id FROM session_provenance
1511
+ WHERE transcript_path = ? LIMIT 1`,
1512
+ [normalize(transcriptPath)]
1513
+ );
1514
+ return row ?? null;
1515
+ } catch {
1516
+ return null;
1517
+ }
1518
+ }
1519
+ function claudeSubagentParent(normPath) {
1520
+ const segments = normPath.split("/");
1521
+ const idx = segments.lastIndexOf("subagents");
1522
+ if (idx <= 0)
1523
+ return null;
1524
+ const parent = segments[idx - 1];
1525
+ return parent || null;
1526
+ }
1527
+ function classifySession(args) {
1528
+ const { sessionId, transcriptPath, vendor, hook } = args;
1529
+ const normPath = normalize(transcriptPath);
1530
+ const base = basenameNoExt(transcriptPath);
1531
+ const stored = lookupStoredProvenance(transcriptPath);
1532
+ if (stored && !(stored.kind !== "agent" && hook?.isSubagent)) {
1533
+ return {
1534
+ kind: stored.kind === "agent" ? "agent" : "root",
1535
+ canonicalSessionId: stored.session_id,
1536
+ parentSessionId: stored.parent_session_id,
1537
+ agentDepth: null,
1538
+ agentMeta: null,
1539
+ aliases: aliasCandidates(stored.session_id, sessionId, hook),
1540
+ evidence: "stored"
1541
+ };
1542
+ }
1543
+ if (vendor === "claude")
1544
+ return classifyClaude(sessionId, normPath, base, hook);
1545
+ return classifyCodex(sessionId, transcriptPath, base, hook);
1546
+ }
1547
+ function aliasCandidates(canonical, proposed, hook) {
1548
+ const out = /* @__PURE__ */ new Set();
1549
+ if (hook?.agentId && hook.agentId !== canonical)
1550
+ out.add(hook.agentId);
1551
+ if (proposed && proposed !== canonical)
1552
+ out.add(proposed);
1553
+ return [...out];
1554
+ }
1555
+ function classifyClaude(sessionId, normPath, base, hook) {
1556
+ if (normPath.includes("/subagents/") && base.startsWith("agent-")) {
1557
+ return {
1558
+ kind: "agent",
1559
+ canonicalSessionId: base,
1560
+ parentSessionId: claudeSubagentParent(normPath) ?? hook?.payloadSessionId ?? null,
1561
+ agentDepth: null,
1562
+ agentMeta: hook?.agentId ? { hookAgentId: hook.agentId } : null,
1563
+ aliases: aliasCandidates(base, sessionId, hook),
1564
+ evidence: "claude-path"
1565
+ };
1566
+ }
1567
+ if (base.startsWith("agent-") || sessionId.startsWith("agent-")) {
1568
+ const canonical = base.startsWith("agent-") ? base : sessionId;
1569
+ return {
1570
+ kind: "agent",
1571
+ canonicalSessionId: canonical,
1572
+ parentSessionId: hook?.payloadSessionId ?? null,
1573
+ agentDepth: null,
1574
+ agentMeta: hook?.agentId ? { hookAgentId: hook.agentId } : null,
1575
+ aliases: aliasCandidates(canonical, sessionId, hook),
1576
+ evidence: "claude-name"
1577
+ };
1578
+ }
1579
+ if (hook?.isSubagent) {
1580
+ return {
1581
+ kind: "agent",
1582
+ canonicalSessionId: base || sessionId,
1583
+ parentSessionId: hook.payloadSessionId ?? null,
1584
+ agentDepth: null,
1585
+ agentMeta: hook.agentId ? { hookAgentId: hook.agentId } : null,
1586
+ aliases: aliasCandidates(base || sessionId, sessionId, hook),
1587
+ evidence: "hook"
1588
+ };
1589
+ }
1590
+ return {
1591
+ kind: "root",
1592
+ canonicalSessionId: sessionId,
1593
+ parentSessionId: null,
1594
+ agentDepth: null,
1595
+ agentMeta: null,
1596
+ aliases: [],
1597
+ evidence: "default-root"
1598
+ };
1599
+ }
1600
+ function classifyCodex(sessionId, transcriptPath, base, hook) {
1601
+ const meta = extractCodexSessionMeta(transcriptPath);
1602
+ const filenameUuid = ROLLOUT_UUID_RE.exec(normalize(transcriptPath))?.[1] ?? null;
1603
+ const canonical = meta?.id && typeof meta.id === "string" && meta.id.length > 0 ? meta.id : filenameUuid ?? (UUID_RE.test(sessionId) ? sessionId : null);
1604
+ const sub = parseSubagentSource(meta?.source, transcriptPath);
1605
+ if (sub.malformed) {
1606
+ log({
1607
+ source: "recall:classify",
1608
+ level: "warn",
1609
+ summary: `codex session_meta.source looks subagent-like but is malformed \u2014 leaving session hot (conservative): ${transcriptPath}`
1610
+ });
1611
+ }
1612
+ if (sub.isSubagent || hook?.isSubagent) {
1613
+ if (!canonical) {
1614
+ log({
1615
+ source: "recall:classify",
1616
+ level: "warn",
1617
+ summary: `codex subagent transcript has no derivable canonical id \u2014 skipping ingest (conservative): ${transcriptPath}`
1618
+ });
1619
+ return {
1620
+ kind: "agent",
1621
+ canonicalSessionId: base || sessionId,
1622
+ parentSessionId: sub.parentThreadId ?? hook?.payloadSessionId ?? null,
1623
+ agentDepth: sub.depth,
1624
+ agentMeta: sub.meta,
1625
+ aliases: [],
1626
+ evidence: sub.isSubagent ? "codex-meta" : "hook",
1627
+ unresolvable: true
1628
+ };
1629
+ }
1630
+ return {
1631
+ kind: "agent",
1632
+ canonicalSessionId: canonical,
1633
+ parentSessionId: sub.parentThreadId ?? hook?.payloadSessionId ?? null,
1634
+ agentDepth: sub.depth,
1635
+ agentMeta: mergeMeta(sub.meta, hook?.agentId),
1636
+ aliases: aliasCandidates(canonical, sessionId, hook),
1637
+ evidence: sub.isSubagent ? "codex-meta" : "hook"
1638
+ };
1639
+ }
1640
+ return {
1641
+ kind: "root",
1642
+ canonicalSessionId: canonical ?? sessionId,
1643
+ parentSessionId: null,
1644
+ agentDepth: null,
1645
+ agentMeta: null,
1646
+ aliases: canonical && canonical !== sessionId ? [sessionId] : [],
1647
+ evidence: "default-root"
1648
+ };
1649
+ }
1650
+ function mergeMeta(meta, hookAgentId) {
1651
+ if (!hookAgentId)
1652
+ return meta;
1653
+ return { ...meta ?? {}, hookAgentId };
1654
+ }
1655
+ function parseSubagentSource(source, _transcriptPath) {
1656
+ const none = { isSubagent: false, malformed: false, parentThreadId: null, depth: null, meta: null };
1657
+ if (!source || typeof source !== "object")
1658
+ return none;
1659
+ const subRaw = source.subagent;
1660
+ const typeTag = source.type;
1661
+ const looksSubagent = subRaw !== void 0 || typeTag === "subagent";
1662
+ if (!looksSubagent)
1663
+ return none;
1664
+ if (subRaw !== void 0 && (typeof subRaw !== "object" || subRaw === null)) {
1665
+ return { ...none, malformed: true };
1666
+ }
1667
+ const sub = subRaw ?? {};
1668
+ const spawnRaw = sub.thread_spawn ?? source.thread_spawn;
1669
+ const spawn2 = spawnRaw && typeof spawnRaw === "object" ? spawnRaw : {};
1670
+ const parentThreadId = typeof spawn2.parent_thread_id === "string" && spawn2.parent_thread_id ? spawn2.parent_thread_id : null;
1671
+ const depth = typeof spawn2.depth === "number" && Number.isFinite(spawn2.depth) ? spawn2.depth : null;
1672
+ const meta = {};
1673
+ for (const k of ["agent_path", "agent_type", "agent_name", "path", "type", "name"]) {
1674
+ const v = spawn2[k] ?? sub[k];
1675
+ if (typeof v === "string" || typeof v === "number")
1676
+ meta[k] = v;
1677
+ }
1678
+ return {
1679
+ isSubagent: true,
1680
+ malformed: false,
1681
+ parentThreadId,
1682
+ depth,
1683
+ meta: Object.keys(meta).length > 0 ? meta : null
1684
+ };
1685
+ }
1686
+
1273
1687
  // src/recall/message-ingest.ts
1274
1688
  init_paths();
1275
1689
 
@@ -1288,13 +1702,13 @@ function normalizePath(p) {
1288
1702
  init_log();
1289
1703
 
1290
1704
  // src/adapters/claude/jsonl-reader.ts
1291
- var fs = __toESM(require("fs"));
1705
+ var fs2 = __toESM(require("fs"));
1292
1706
  init_log();
1293
- var READ_BUFFER_SIZE = 64 * 1024;
1294
- var TURN_CONTENT_CAP = 8 * 1024;
1707
+ var READ_BUFFER_SIZE2 = 64 * 1024;
1708
+ var TURN_CONTENT_CAP2 = 8 * 1024;
1295
1709
  function parseJsonlFile(filepath) {
1296
1710
  try {
1297
- const content = fs.readFileSync(filepath, "utf-8");
1711
+ const content = fs2.readFileSync(filepath, "utf-8");
1298
1712
  const lines = content.split("\n");
1299
1713
  const entries = [];
1300
1714
  for (const line of lines) {
@@ -1483,37 +1897,6 @@ function adaptClaudeEntries(rawEntries) {
1483
1897
  return rawEntries.map(adaptClaudeEntry).filter((entry) => entry !== null);
1484
1898
  }
1485
1899
 
1486
- // src/adapters/codex/codex-jsonl-reader.ts
1487
- var fs2 = __toESM(require("fs"));
1488
- init_log();
1489
- var path = __toESM(require("path"));
1490
- var os = __toESM(require("os"));
1491
- var CODEX_SESSIONS_DIR = path.join(os.homedir(), ".codex", "sessions");
1492
- function parseCodexJsonlFile(filepath) {
1493
- try {
1494
- const content = fs2.readFileSync(filepath, "utf-8");
1495
- const lines = content.split("\n");
1496
- const records = [];
1497
- for (const line of lines) {
1498
- const trimmed = line.trim();
1499
- if (!trimmed)
1500
- continue;
1501
- try {
1502
- const record = JSON.parse(trimmed);
1503
- records.push(record);
1504
- } catch (err) {
1505
- log({ level: "warn", source: "codex-jsonl-reader", summary: `Skipping unparseable line: ${err.message}` });
1506
- }
1507
- }
1508
- return records;
1509
- } catch (error) {
1510
- log({ level: "error", source: "codex-jsonl-reader", summary: `Failed to read ${filepath}: ${error instanceof Error ? error.message : String(error)}`, data: { filepath, error: String(error) } });
1511
- return [];
1512
- }
1513
- }
1514
- var READ_BUFFER_SIZE2 = 64 * 1024;
1515
- var TURN_CONTENT_CAP2 = 8 * 1024;
1516
-
1517
1900
  // src/adapters/codex/codex-jsonl-adapter.ts
1518
1901
  function adaptCodexJsonlRecords(records, sessionId) {
1519
1902
  const outputIndex = /* @__PURE__ */ new Map();
@@ -2148,6 +2531,36 @@ function entriesCwd(entries) {
2148
2531
  return void 0;
2149
2532
  }
2150
2533
  async function ingestSessionMessages(sessionId, transcriptPath, vendor, options) {
2534
+ const classification = classifySession({
2535
+ sessionId,
2536
+ transcriptPath,
2537
+ vendor,
2538
+ ...options?.hook ? { hook: options.hook } : {}
2539
+ });
2540
+ if (classification.unresolvable) {
2541
+ return {
2542
+ sessionId,
2543
+ chunksCreated: 0,
2544
+ skipped: true,
2545
+ error: "subagent transcript has no derivable canonical identity \u2014 skipped (conservative)"
2546
+ };
2547
+ }
2548
+ const canonicalId = classification.canonicalSessionId;
2549
+ const retrievalClass = classification.kind === "agent" ? "agent" : "hot";
2550
+ const provenance = {
2551
+ sessionId: canonicalId,
2552
+ vendor,
2553
+ kind: classification.kind,
2554
+ parentSessionId: classification.parentSessionId,
2555
+ agentDepth: classification.agentDepth,
2556
+ agentMeta: classification.agentMeta,
2557
+ transcriptPath: transcriptPath.replace(/\\/g, "/")
2558
+ };
2559
+ const aliases = classification.aliases.map((aliasId) => ({
2560
+ aliasId,
2561
+ sessionId: canonicalId,
2562
+ source: options?.hook?.agentId === aliasId ? "hook-agent-id" : "ingest-request-id"
2563
+ }));
2151
2564
  let rawEntries;
2152
2565
  try {
2153
2566
  if (vendor === "claude") {
@@ -2155,18 +2568,25 @@ async function ingestSessionMessages(sessionId, transcriptPath, vendor, options)
2155
2568
  rawEntries = adaptClaudeEntries(raw);
2156
2569
  } else {
2157
2570
  const envelopes = parseCodexJsonlFile(transcriptPath);
2158
- rawEntries = adaptCodexJsonlRecords(envelopes, sessionId);
2571
+ rawEntries = adaptCodexJsonlRecords(envelopes, canonicalId);
2159
2572
  }
2160
2573
  } catch (err) {
2161
2574
  return {
2162
- sessionId,
2575
+ sessionId: canonicalId,
2163
2576
  chunksCreated: 0,
2164
2577
  skipped: false,
2165
- error: `Failed to load session: ${err instanceof Error ? err.message : String(err)}`
2578
+ error: `Failed to load session: ${err instanceof Error ? err.message : String(err)}`,
2579
+ retrievalClass
2166
2580
  };
2167
2581
  }
2168
2582
  if (rawEntries.length === 0) {
2169
- return { sessionId, chunksCreated: 0, skipped: true };
2583
+ if (options?.force) {
2584
+ try {
2585
+ insertMessages([], { replaceSessionId: canonicalId, provenance, aliases });
2586
+ } catch {
2587
+ }
2588
+ }
2589
+ return { sessionId: canonicalId, chunksCreated: 0, skipped: true, retrievalClass };
2170
2590
  }
2171
2591
  const rawProjectId = options?.projectId ?? entriesCwd(rawEntries);
2172
2592
  const projectId = rawProjectId ? normalizePath(rawProjectId) : null;
@@ -2183,31 +2603,44 @@ async function ingestSessionMessages(sessionId, transcriptPath, vendor, options)
2183
2603
  const createdAt = entry.timestamp ? new Date(entry.timestamp).getTime() : Date.now();
2184
2604
  records.push({
2185
2605
  message_id: entry.uuid,
2186
- session_id: sessionId,
2606
+ session_id: canonicalId,
2187
2607
  message_seq: i,
2188
2608
  message_text: text,
2189
2609
  project_id: projectId,
2190
2610
  created_at: createdAt,
2191
- message_role: entry.message?.role ?? entry.type ?? null
2611
+ message_role: entry.message?.role ?? entry.type ?? null,
2612
+ retrieval_class: retrievalClass
2192
2613
  });
2193
2614
  }
2194
2615
  if (records.length === 0) {
2195
- return { sessionId, chunksCreated: 0, skipped: true };
2616
+ if (options?.force) {
2617
+ try {
2618
+ insertMessages([], { replaceSessionId: canonicalId, provenance, aliases });
2619
+ } catch {
2620
+ }
2621
+ }
2622
+ return { sessionId: canonicalId, chunksCreated: 0, skipped: true, retrievalClass };
2196
2623
  }
2197
2624
  try {
2198
- insertMessages(records, options?.force ? { replaceSessionId: sessionId } : void 0);
2625
+ insertMessages(records, {
2626
+ ...options?.force ? { replaceSessionId: canonicalId } : {},
2627
+ provenance,
2628
+ aliases
2629
+ });
2199
2630
  } catch (err) {
2200
2631
  return {
2201
- sessionId,
2632
+ sessionId: canonicalId,
2202
2633
  chunksCreated: 0,
2203
2634
  skipped: false,
2204
- error: `DB insert failed: ${err instanceof Error ? err.message : String(err)}`
2635
+ error: `DB insert failed: ${err instanceof Error ? err.message : String(err)}`,
2636
+ retrievalClass
2205
2637
  };
2206
2638
  }
2207
2639
  return {
2208
- sessionId,
2640
+ sessionId: canonicalId,
2209
2641
  chunksCreated: records.length,
2210
- skipped: false
2642
+ skipped: false,
2643
+ retrievalClass
2211
2644
  };
2212
2645
  }
2213
2646
 
@@ -2216,9 +2649,35 @@ init_paths();
2216
2649
  var import_fs = require("fs");
2217
2650
  var import_child_process = require("child_process");
2218
2651
  var import_path = require("path");
2219
- function vendorForTranscript(transcriptPath) {
2652
+ function vendorForTranscript(transcriptPath, codexHome = process.env["CODEX_HOME"]) {
2220
2653
  const p = transcriptPath.replace(/\\/g, "/");
2221
- return p.includes("/.codex/") ? "codex" : "claude";
2654
+ const root = codexHome?.replace(/\\/g, "/").replace(/\/$/, "");
2655
+ const underCodexHome = root ? p === root || p.startsWith(`${root}/`) : false;
2656
+ return p.includes("/.codex/") || underCodexHome ? "codex" : "claude";
2657
+ }
2658
+ function resolveIngestTarget(payload) {
2659
+ if (payload.agent_transcript_path) {
2660
+ const transcriptPath = payload.agent_transcript_path;
2661
+ const base = transcriptPath.replace(/\\/g, "/").split("/").pop().replace(/\.jsonl$/i, "");
2662
+ if (!base)
2663
+ return null;
2664
+ return {
2665
+ sessionId: base,
2666
+ transcriptPath,
2667
+ hook: {
2668
+ ...payload.session_id ? { payloadSessionId: payload.session_id } : {},
2669
+ ...payload.agent_id ? { agentId: payload.agent_id } : {},
2670
+ isSubagent: true
2671
+ }
2672
+ };
2673
+ }
2674
+ if (!payload.session_id || !payload.transcript_path)
2675
+ return null;
2676
+ return {
2677
+ sessionId: payload.session_id,
2678
+ transcriptPath: payload.transcript_path,
2679
+ hook: { payloadSessionId: payload.session_id, isSubagent: false }
2680
+ };
2222
2681
  }
2223
2682
  function logStopHook(line) {
2224
2683
  try {
@@ -2237,40 +2696,47 @@ async function runStopHook() {
2237
2696
  } catch {
2238
2697
  process.exit(0);
2239
2698
  }
2240
- if (!payload?.session_id || !payload?.transcript_path)
2241
- process.exit(0);
2242
2699
  if (payload.stop_hook_active)
2243
2700
  process.exit(0);
2701
+ const target = resolveIngestTarget(payload);
2702
+ if (!target)
2703
+ process.exit(0);
2704
+ let ingestedClass;
2705
+ let canonicalId = target.sessionId;
2244
2706
  try {
2245
2707
  getDb(dbPath()).exec("PRAGMA busy_timeout = 5000;");
2246
- const vendor = vendorForTranscript(payload.transcript_path);
2708
+ const vendor = vendorForTranscript(target.transcriptPath);
2247
2709
  const result = await ingestSessionMessages(
2248
- payload.session_id,
2249
- payload.transcript_path,
2710
+ target.sessionId,
2711
+ target.transcriptPath,
2250
2712
  vendor,
2251
- { projectId: payload.cwd ?? void 0 }
2713
+ { projectId: payload.cwd ?? void 0, hook: target.hook }
2252
2714
  );
2715
+ ingestedClass = result?.retrievalClass;
2716
+ canonicalId = result?.sessionId ?? target.sessionId;
2253
2717
  if (result?.error) {
2254
2718
  logStopHook(
2255
- `${(/* @__PURE__ */ new Date()).toISOString()} ingest-failed sid=${payload.session_id} err=${result.error}
2719
+ `${(/* @__PURE__ */ new Date()).toISOString()} ingest-failed sid=${target.sessionId} err=${result.error}
2256
2720
  `
2257
2721
  );
2258
2722
  }
2259
2723
  } catch (e) {
2260
2724
  logStopHook(
2261
- `${(/* @__PURE__ */ new Date()).toISOString()} ingest-failed sid=${payload.session_id} err=${e.message}
2725
+ `${(/* @__PURE__ */ new Date()).toISOString()} ingest-failed sid=${target.sessionId} err=${e.message}
2262
2726
  `
2263
2727
  );
2264
2728
  }
2265
- (0, import_child_process.spawn)(
2266
- process.execPath,
2267
- [(0, import_path.join)(binDir(), "embed-pending.js"), payload.session_id],
2268
- {
2269
- detached: true,
2270
- stdio: "ignore",
2271
- windowsHide: true
2272
- }
2273
- ).unref();
2729
+ if (!target.hook.isSubagent && ingestedClass !== "agent") {
2730
+ (0, import_child_process.spawn)(
2731
+ process.execPath,
2732
+ [(0, import_path.join)(binDir(), "embed-pending.js"), canonicalId],
2733
+ {
2734
+ detached: true,
2735
+ stdio: "ignore",
2736
+ windowsHide: true
2737
+ }
2738
+ ).unref();
2739
+ }
2274
2740
  process.exit(0);
2275
2741
  }
2276
2742
  if (typeof require !== "undefined" && typeof module !== "undefined" && require.main === module) {
@@ -2278,5 +2744,6 @@ if (typeof require !== "undefined" && typeof module !== "undefined" && require.m
2278
2744
  }
2279
2745
  // Annotate the CommonJS export names for ESM import in node:
2280
2746
  0 && (module.exports = {
2747
+ resolveIngestTarget,
2281
2748
  vendorForTranscript
2282
2749
  });