@deeplake/hivemind 0.7.31 → 0.7.32
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/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/bundle/cli.js +427 -233
- package/codex/bundle/capture.js +550 -122
- package/codex/bundle/embeddings/embed-daemon.js +55 -4
- package/codex/bundle/pre-tool-use.js +447 -90
- package/codex/bundle/shell/deeplake-shell.js +431 -74
- package/codex/bundle/stop.js +437 -80
- package/codex/bundle/wiki-worker.js +429 -72
- package/cursor/bundle/capture.js +625 -197
- package/cursor/bundle/embeddings/embed-daemon.js +55 -4
- package/cursor/bundle/pre-tool-use.js +432 -75
- package/cursor/bundle/session-start.js +8 -1
- package/cursor/bundle/shell/deeplake-shell.js +431 -74
- package/cursor/bundle/wiki-worker.js +429 -72
- package/hermes/bundle/capture.js +626 -198
- package/hermes/bundle/embeddings/embed-daemon.js +55 -4
- package/hermes/bundle/pre-tool-use.js +431 -74
- package/hermes/bundle/session-start.js +8 -1
- package/hermes/bundle/shell/deeplake-shell.js +431 -74
- package/hermes/bundle/wiki-worker.js +429 -72
- package/openclaw/dist/index.js +1 -1
- package/openclaw/openclaw.plugin.json +1 -1
- package/openclaw/package.json +1 -1
- package/package.json +1 -1
package/codex/bundle/stop.js
CHANGED
|
@@ -53,19 +53,19 @@ var init_index_marker_store = __esm({
|
|
|
53
53
|
});
|
|
54
54
|
|
|
55
55
|
// dist/src/hooks/codex/stop.js
|
|
56
|
-
import { readFileSync as
|
|
56
|
+
import { readFileSync as readFileSync10, existsSync as existsSync10 } from "node:fs";
|
|
57
57
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
58
|
-
import { dirname as
|
|
58
|
+
import { dirname as dirname5, join as join17 } from "node:path";
|
|
59
59
|
|
|
60
60
|
// dist/src/utils/stdin.js
|
|
61
61
|
function readStdin() {
|
|
62
|
-
return new Promise((
|
|
62
|
+
return new Promise((resolve2, reject) => {
|
|
63
63
|
let data = "";
|
|
64
64
|
process.stdin.setEncoding("utf-8");
|
|
65
65
|
process.stdin.on("data", (chunk) => data += chunk);
|
|
66
66
|
process.stdin.on("end", () => {
|
|
67
67
|
try {
|
|
68
|
-
|
|
68
|
+
resolve2(JSON.parse(data));
|
|
69
69
|
} catch (err) {
|
|
70
70
|
reject(new Error(`Failed to parse hook input: ${err}`));
|
|
71
71
|
}
|
|
@@ -181,7 +181,7 @@ function getQueryTimeoutMs() {
|
|
|
181
181
|
return Number(process.env.HIVEMIND_QUERY_TIMEOUT_MS ?? 1e4);
|
|
182
182
|
}
|
|
183
183
|
function sleep(ms) {
|
|
184
|
-
return new Promise((
|
|
184
|
+
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
185
185
|
}
|
|
186
186
|
function isTimeoutError(error) {
|
|
187
187
|
const name = error instanceof Error ? error.name.toLowerCase() : "";
|
|
@@ -211,7 +211,7 @@ var Semaphore = class {
|
|
|
211
211
|
this.active++;
|
|
212
212
|
return;
|
|
213
213
|
}
|
|
214
|
-
await new Promise((
|
|
214
|
+
await new Promise((resolve2) => this.waiting.push(resolve2));
|
|
215
215
|
}
|
|
216
216
|
release() {
|
|
217
217
|
this.active--;
|
|
@@ -1175,9 +1175,9 @@ function buildSessionPath(config, sessionId) {
|
|
|
1175
1175
|
// dist/src/embeddings/client.js
|
|
1176
1176
|
import { connect } from "node:net";
|
|
1177
1177
|
import { spawn as spawn3 } from "node:child_process";
|
|
1178
|
-
import { openSync as
|
|
1179
|
-
import { homedir as
|
|
1180
|
-
import { join as
|
|
1178
|
+
import { openSync as openSync4, closeSync as closeSync4, writeSync as writeSync3, unlinkSync as unlinkSync4, existsSync as existsSync9, readFileSync as readFileSync9 } from "node:fs";
|
|
1179
|
+
import { homedir as homedir13 } from "node:os";
|
|
1180
|
+
import { join as join16 } from "node:path";
|
|
1181
1181
|
|
|
1182
1182
|
// dist/src/embeddings/protocol.js
|
|
1183
1183
|
var DEFAULT_SOCKET_DIR = "/tmp";
|
|
@@ -1190,13 +1190,234 @@ function pidPathFor(uid, dir = DEFAULT_SOCKET_DIR) {
|
|
|
1190
1190
|
return `${dir}/hivemind-embed-${uid}.pid`;
|
|
1191
1191
|
}
|
|
1192
1192
|
|
|
1193
|
+
// dist/src/notifications/queue.js
|
|
1194
|
+
import { readFileSync as readFileSync7, writeFileSync as writeFileSync7, renameSync as renameSync4, mkdirSync as mkdirSync8, openSync as openSync3, closeSync as closeSync3, unlinkSync as unlinkSync3, statSync } from "node:fs";
|
|
1195
|
+
import { join as join13, resolve } from "node:path";
|
|
1196
|
+
import { homedir as homedir10 } from "node:os";
|
|
1197
|
+
import { setTimeout as sleep2 } from "node:timers/promises";
|
|
1198
|
+
var log3 = (msg) => log("notifications-queue", msg);
|
|
1199
|
+
var LOCK_RETRY_MAX = 50;
|
|
1200
|
+
var LOCK_RETRY_BASE_MS = 5;
|
|
1201
|
+
var LOCK_STALE_MS = 5e3;
|
|
1202
|
+
function queuePath() {
|
|
1203
|
+
return join13(homedir10(), ".deeplake", "notifications-queue.json");
|
|
1204
|
+
}
|
|
1205
|
+
function lockPath3() {
|
|
1206
|
+
return `${queuePath()}.lock`;
|
|
1207
|
+
}
|
|
1208
|
+
function readQueue() {
|
|
1209
|
+
try {
|
|
1210
|
+
const raw = readFileSync7(queuePath(), "utf-8");
|
|
1211
|
+
const parsed = JSON.parse(raw);
|
|
1212
|
+
if (!parsed || !Array.isArray(parsed.queue)) {
|
|
1213
|
+
log3(`queue malformed \u2192 treating as empty`);
|
|
1214
|
+
return { queue: [] };
|
|
1215
|
+
}
|
|
1216
|
+
return { queue: parsed.queue };
|
|
1217
|
+
} catch {
|
|
1218
|
+
return { queue: [] };
|
|
1219
|
+
}
|
|
1220
|
+
}
|
|
1221
|
+
function _isQueuePathInsideHome(path, home) {
|
|
1222
|
+
const r = resolve(path);
|
|
1223
|
+
const h = resolve(home);
|
|
1224
|
+
return r.startsWith(h + "/") || r === h;
|
|
1225
|
+
}
|
|
1226
|
+
function writeQueue(q) {
|
|
1227
|
+
const path = queuePath();
|
|
1228
|
+
const home = resolve(homedir10());
|
|
1229
|
+
if (!_isQueuePathInsideHome(path, home)) {
|
|
1230
|
+
throw new Error(`notifications-queue write blocked: ${path} is outside ${home}`);
|
|
1231
|
+
}
|
|
1232
|
+
mkdirSync8(join13(home, ".deeplake"), { recursive: true, mode: 448 });
|
|
1233
|
+
const tmp = `${path}.${process.pid}.tmp`;
|
|
1234
|
+
writeFileSync7(tmp, JSON.stringify(q, null, 2), { mode: 384 });
|
|
1235
|
+
renameSync4(tmp, path);
|
|
1236
|
+
}
|
|
1237
|
+
async function withQueueLock(fn) {
|
|
1238
|
+
const path = lockPath3();
|
|
1239
|
+
mkdirSync8(join13(homedir10(), ".deeplake"), { recursive: true, mode: 448 });
|
|
1240
|
+
let fd = null;
|
|
1241
|
+
for (let attempt = 0; attempt < LOCK_RETRY_MAX; attempt++) {
|
|
1242
|
+
try {
|
|
1243
|
+
fd = openSync3(path, "wx", 384);
|
|
1244
|
+
break;
|
|
1245
|
+
} catch (e) {
|
|
1246
|
+
const code = e.code;
|
|
1247
|
+
if (code !== "EEXIST")
|
|
1248
|
+
throw e;
|
|
1249
|
+
try {
|
|
1250
|
+
const age = Date.now() - statSync(path).mtimeMs;
|
|
1251
|
+
if (age > LOCK_STALE_MS) {
|
|
1252
|
+
unlinkSync3(path);
|
|
1253
|
+
continue;
|
|
1254
|
+
}
|
|
1255
|
+
} catch {
|
|
1256
|
+
}
|
|
1257
|
+
const delay = LOCK_RETRY_BASE_MS * (attempt + 1);
|
|
1258
|
+
await sleep2(delay);
|
|
1259
|
+
}
|
|
1260
|
+
}
|
|
1261
|
+
if (fd === null) {
|
|
1262
|
+
log3(`lock acquisition gave up after ${LOCK_RETRY_MAX} attempts \u2014 proceeding unlocked (last-writer-wins)`);
|
|
1263
|
+
return fn();
|
|
1264
|
+
}
|
|
1265
|
+
try {
|
|
1266
|
+
return fn();
|
|
1267
|
+
} finally {
|
|
1268
|
+
try {
|
|
1269
|
+
closeSync3(fd);
|
|
1270
|
+
} catch {
|
|
1271
|
+
}
|
|
1272
|
+
try {
|
|
1273
|
+
unlinkSync3(path);
|
|
1274
|
+
} catch {
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
}
|
|
1278
|
+
function sameDedupKey(a, b) {
|
|
1279
|
+
if (a.id !== b.id)
|
|
1280
|
+
return false;
|
|
1281
|
+
return JSON.stringify(a.dedupKey) === JSON.stringify(b.dedupKey);
|
|
1282
|
+
}
|
|
1283
|
+
async function enqueueNotification(n) {
|
|
1284
|
+
await withQueueLock(() => {
|
|
1285
|
+
const q = readQueue();
|
|
1286
|
+
if (q.queue.some((existing) => sameDedupKey(existing, n))) {
|
|
1287
|
+
return;
|
|
1288
|
+
}
|
|
1289
|
+
q.queue.push(n);
|
|
1290
|
+
writeQueue(q);
|
|
1291
|
+
});
|
|
1292
|
+
}
|
|
1293
|
+
|
|
1294
|
+
// dist/src/embeddings/disable.js
|
|
1295
|
+
import { createRequire as createRequire2 } from "node:module";
|
|
1296
|
+
import { homedir as homedir12 } from "node:os";
|
|
1297
|
+
import { join as join15 } from "node:path";
|
|
1298
|
+
import { pathToFileURL } from "node:url";
|
|
1299
|
+
|
|
1300
|
+
// dist/src/user-config.js
|
|
1301
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync9, readFileSync as readFileSync8, renameSync as renameSync5, writeFileSync as writeFileSync8 } from "node:fs";
|
|
1302
|
+
import { homedir as homedir11 } from "node:os";
|
|
1303
|
+
import { dirname as dirname4, join as join14 } from "node:path";
|
|
1304
|
+
var _configPath = () => process.env.HIVEMIND_CONFIG_PATH ?? join14(homedir11(), ".deeplake", "config.json");
|
|
1305
|
+
var _cache = null;
|
|
1306
|
+
var _migrated = false;
|
|
1307
|
+
function readUserConfig() {
|
|
1308
|
+
if (_cache !== null)
|
|
1309
|
+
return _cache;
|
|
1310
|
+
const path = _configPath();
|
|
1311
|
+
if (!existsSync8(path)) {
|
|
1312
|
+
_cache = {};
|
|
1313
|
+
return _cache;
|
|
1314
|
+
}
|
|
1315
|
+
try {
|
|
1316
|
+
const raw = readFileSync8(path, "utf-8");
|
|
1317
|
+
const parsed = JSON.parse(raw);
|
|
1318
|
+
_cache = isPlainObject(parsed) ? parsed : {};
|
|
1319
|
+
} catch {
|
|
1320
|
+
_cache = {};
|
|
1321
|
+
}
|
|
1322
|
+
return _cache;
|
|
1323
|
+
}
|
|
1324
|
+
function writeUserConfig(patch) {
|
|
1325
|
+
const current = readUserConfig();
|
|
1326
|
+
const merged = deepMerge(current, patch);
|
|
1327
|
+
const path = _configPath();
|
|
1328
|
+
const dir = dirname4(path);
|
|
1329
|
+
if (!existsSync8(dir))
|
|
1330
|
+
mkdirSync9(dir, { recursive: true });
|
|
1331
|
+
const tmp = `${path}.tmp.${process.pid}`;
|
|
1332
|
+
writeFileSync8(tmp, JSON.stringify(merged, null, 2) + "\n", "utf-8");
|
|
1333
|
+
renameSync5(tmp, path);
|
|
1334
|
+
_cache = merged;
|
|
1335
|
+
return merged;
|
|
1336
|
+
}
|
|
1337
|
+
function getEmbeddingsEnabled() {
|
|
1338
|
+
const cfg = readUserConfig();
|
|
1339
|
+
if (cfg.embeddings && typeof cfg.embeddings.enabled === "boolean") {
|
|
1340
|
+
return cfg.embeddings.enabled;
|
|
1341
|
+
}
|
|
1342
|
+
if (_migrated) {
|
|
1343
|
+
return migrationValueFromEnv();
|
|
1344
|
+
}
|
|
1345
|
+
_migrated = true;
|
|
1346
|
+
const enabled = migrationValueFromEnv();
|
|
1347
|
+
try {
|
|
1348
|
+
writeUserConfig({ embeddings: { enabled } });
|
|
1349
|
+
} catch {
|
|
1350
|
+
_cache = { ...cfg ?? {}, embeddings: { ...cfg?.embeddings ?? {}, enabled } };
|
|
1351
|
+
}
|
|
1352
|
+
return enabled;
|
|
1353
|
+
}
|
|
1354
|
+
function migrationValueFromEnv() {
|
|
1355
|
+
const raw = process.env.HIVEMIND_EMBEDDINGS;
|
|
1356
|
+
if (raw === void 0)
|
|
1357
|
+
return false;
|
|
1358
|
+
if (raw === "false")
|
|
1359
|
+
return false;
|
|
1360
|
+
return true;
|
|
1361
|
+
}
|
|
1362
|
+
function isPlainObject(value) {
|
|
1363
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1364
|
+
}
|
|
1365
|
+
function deepMerge(base, patch) {
|
|
1366
|
+
const out = { ...base };
|
|
1367
|
+
for (const key of Object.keys(patch)) {
|
|
1368
|
+
const patchVal = patch[key];
|
|
1369
|
+
const baseVal = base[key];
|
|
1370
|
+
if (isPlainObject(patchVal) && isPlainObject(baseVal)) {
|
|
1371
|
+
out[key] = { ...baseVal, ...patchVal };
|
|
1372
|
+
} else if (patchVal !== void 0) {
|
|
1373
|
+
out[key] = patchVal;
|
|
1374
|
+
}
|
|
1375
|
+
}
|
|
1376
|
+
return out;
|
|
1377
|
+
}
|
|
1378
|
+
|
|
1379
|
+
// dist/src/embeddings/disable.js
|
|
1380
|
+
var cachedStatus = null;
|
|
1381
|
+
function defaultResolveTransformers() {
|
|
1382
|
+
const sharedDir = join15(homedir12(), ".hivemind", "embed-deps");
|
|
1383
|
+
try {
|
|
1384
|
+
createRequire2(pathToFileURL(`${sharedDir}/`).href).resolve("@huggingface/transformers");
|
|
1385
|
+
return;
|
|
1386
|
+
} catch {
|
|
1387
|
+
}
|
|
1388
|
+
createRequire2(import.meta.url).resolve("@huggingface/transformers");
|
|
1389
|
+
}
|
|
1390
|
+
var _resolve = defaultResolveTransformers;
|
|
1391
|
+
var _readEnabled = getEmbeddingsEnabled;
|
|
1392
|
+
function detectStatus() {
|
|
1393
|
+
if (!_readEnabled())
|
|
1394
|
+
return "user-disabled";
|
|
1395
|
+
try {
|
|
1396
|
+
_resolve();
|
|
1397
|
+
return "enabled";
|
|
1398
|
+
} catch {
|
|
1399
|
+
return "no-transformers";
|
|
1400
|
+
}
|
|
1401
|
+
}
|
|
1402
|
+
function embeddingsStatus() {
|
|
1403
|
+
if (cachedStatus !== null)
|
|
1404
|
+
return cachedStatus;
|
|
1405
|
+
cachedStatus = detectStatus();
|
|
1406
|
+
return cachedStatus;
|
|
1407
|
+
}
|
|
1408
|
+
function embeddingsDisabled() {
|
|
1409
|
+
return embeddingsStatus() !== "enabled";
|
|
1410
|
+
}
|
|
1411
|
+
|
|
1193
1412
|
// dist/src/embeddings/client.js
|
|
1194
|
-
var SHARED_DAEMON_PATH =
|
|
1195
|
-
var
|
|
1413
|
+
var SHARED_DAEMON_PATH = join16(homedir13(), ".hivemind", "embed-deps", "embed-daemon.js");
|
|
1414
|
+
var log4 = (m) => log("embed-client", m);
|
|
1196
1415
|
function getUid() {
|
|
1197
1416
|
const uid = typeof process.getuid === "function" ? process.getuid() : void 0;
|
|
1198
1417
|
return uid !== void 0 ? String(uid) : process.env.USER ?? "default";
|
|
1199
1418
|
}
|
|
1419
|
+
var _signalledMissingDeps = false;
|
|
1420
|
+
var _recycledStuckDaemon = false;
|
|
1200
1421
|
var EmbedClient = class {
|
|
1201
1422
|
socketPath;
|
|
1202
1423
|
pidPath;
|
|
@@ -1205,13 +1426,14 @@ var EmbedClient = class {
|
|
|
1205
1426
|
autoSpawn;
|
|
1206
1427
|
spawnWaitMs;
|
|
1207
1428
|
nextId = 0;
|
|
1429
|
+
helloVerified = false;
|
|
1208
1430
|
constructor(opts = {}) {
|
|
1209
1431
|
const uid = getUid();
|
|
1210
1432
|
const dir = opts.socketDir ?? "/tmp";
|
|
1211
1433
|
this.socketPath = socketPathFor(uid, dir);
|
|
1212
1434
|
this.pidPath = pidPathFor(uid, dir);
|
|
1213
1435
|
this.timeoutMs = opts.timeoutMs ?? DEFAULT_CLIENT_TIMEOUT_MS;
|
|
1214
|
-
this.daemonEntry = opts.daemonEntry ?? process.env.HIVEMIND_EMBED_DAEMON ?? (
|
|
1436
|
+
this.daemonEntry = opts.daemonEntry ?? process.env.HIVEMIND_EMBED_DAEMON ?? (existsSync9(SHARED_DAEMON_PATH) ? SHARED_DAEMON_PATH : void 0);
|
|
1215
1437
|
this.autoSpawn = opts.autoSpawn ?? true;
|
|
1216
1438
|
this.spawnWaitMs = opts.spawnWaitMs ?? 5e3;
|
|
1217
1439
|
}
|
|
@@ -1221,8 +1443,33 @@ var EmbedClient = class {
|
|
|
1221
1443
|
*
|
|
1222
1444
|
* Fire-and-forget spawn on miss: if the daemon isn't up, this call returns
|
|
1223
1445
|
* null AND kicks off a background spawn. The next call finds a ready daemon.
|
|
1446
|
+
*
|
|
1447
|
+
* Stuck-daemon recycle: if the daemon returns a transformers-missing
|
|
1448
|
+
* error (typical after a marketplace upgrade left an older daemon process
|
|
1449
|
+
* alive but with no node_modules accessible from its bundle path), we
|
|
1450
|
+
* SIGTERM it and clear its sock/pid so the very next call spawns a fresh
|
|
1451
|
+
* daemon from the current bundle. Without this, the stuck daemon would
|
|
1452
|
+
* keep poisoning every session until its 10-minute idle-out fires.
|
|
1224
1453
|
*/
|
|
1225
1454
|
async embed(text, kind = "document") {
|
|
1455
|
+
const v = await this.embedAttempt(text, kind);
|
|
1456
|
+
if (v !== "recycled")
|
|
1457
|
+
return v;
|
|
1458
|
+
if (!this.autoSpawn)
|
|
1459
|
+
return null;
|
|
1460
|
+
this.trySpawnDaemon();
|
|
1461
|
+
await this.waitForDaemonReady();
|
|
1462
|
+
const retry = await this.embedAttempt(text, kind);
|
|
1463
|
+
return retry === "recycled" ? null : retry;
|
|
1464
|
+
}
|
|
1465
|
+
/**
|
|
1466
|
+
* One round-trip: connect → verify → embed. Returns:
|
|
1467
|
+
* - number[] : embedding vector (happy path)
|
|
1468
|
+
* - null : timeout / daemon error / transformers-missing
|
|
1469
|
+
* - "recycled": verifyDaemonOnce killed the daemon mid-call;
|
|
1470
|
+
* caller should respawn and retry once.
|
|
1471
|
+
*/
|
|
1472
|
+
async embedAttempt(text, kind) {
|
|
1226
1473
|
let sock;
|
|
1227
1474
|
try {
|
|
1228
1475
|
sock = await this.connectOnce();
|
|
@@ -1232,17 +1479,25 @@ var EmbedClient = class {
|
|
|
1232
1479
|
return null;
|
|
1233
1480
|
}
|
|
1234
1481
|
try {
|
|
1482
|
+
const recycled = await this.verifyDaemonOnce(sock);
|
|
1483
|
+
if (recycled) {
|
|
1484
|
+
return "recycled";
|
|
1485
|
+
}
|
|
1235
1486
|
const id = String(++this.nextId);
|
|
1236
1487
|
const req = { op: "embed", id, kind, text };
|
|
1237
1488
|
const resp = await this.sendAndWait(sock, req);
|
|
1238
1489
|
if (resp.error || !("embedding" in resp) || !resp.embedding) {
|
|
1239
|
-
|
|
1490
|
+
const err = resp.error ?? "no embedding";
|
|
1491
|
+
log4(`embed err: ${err}`);
|
|
1492
|
+
if (isTransformersMissingError(err)) {
|
|
1493
|
+
this.handleTransformersMissing(err);
|
|
1494
|
+
}
|
|
1240
1495
|
return null;
|
|
1241
1496
|
}
|
|
1242
1497
|
return resp.embedding;
|
|
1243
1498
|
} catch (e) {
|
|
1244
1499
|
const err = e instanceof Error ? e.message : String(e);
|
|
1245
|
-
|
|
1500
|
+
log4(`embed failed: ${err}`);
|
|
1246
1501
|
return null;
|
|
1247
1502
|
} finally {
|
|
1248
1503
|
try {
|
|
@@ -1251,6 +1506,139 @@ var EmbedClient = class {
|
|
|
1251
1506
|
}
|
|
1252
1507
|
}
|
|
1253
1508
|
}
|
|
1509
|
+
/**
|
|
1510
|
+
* Poll for the sock file to come back after `trySpawnDaemon` — used by
|
|
1511
|
+
* the recycle retry path. Best-effort: caps at `spawnWaitMs` and
|
|
1512
|
+
* returns regardless so the retry attempt can run.
|
|
1513
|
+
*/
|
|
1514
|
+
async waitForDaemonReady() {
|
|
1515
|
+
const deadline = Date.now() + this.spawnWaitMs;
|
|
1516
|
+
while (Date.now() < deadline) {
|
|
1517
|
+
if (existsSync9(this.socketPath))
|
|
1518
|
+
return;
|
|
1519
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
1520
|
+
}
|
|
1521
|
+
}
|
|
1522
|
+
/**
|
|
1523
|
+
* Send a `hello` on first successful connect per EmbedClient instance.
|
|
1524
|
+
* If the daemon answers with a path that doesn't match our configured
|
|
1525
|
+
* daemonEntry — typical after a marketplace upgrade replaced the bundle
|
|
1526
|
+
* — SIGTERM the daemon + clear sock/pid so the next call spawns from the
|
|
1527
|
+
* current bundle.
|
|
1528
|
+
*
|
|
1529
|
+
* `helloVerified` is set ONLY after we've seen a compatible response,
|
|
1530
|
+
* so a transient probe failure or a recycle-triggering mismatch leaves
|
|
1531
|
+
* the flag false; the next reconnect re-runs verification against
|
|
1532
|
+
* whatever daemon is then live (typically the fresh spawn).
|
|
1533
|
+
*/
|
|
1534
|
+
async verifyDaemonOnce(sock) {
|
|
1535
|
+
if (this.helloVerified)
|
|
1536
|
+
return false;
|
|
1537
|
+
if (!this.daemonEntry) {
|
|
1538
|
+
this.helloVerified = true;
|
|
1539
|
+
return false;
|
|
1540
|
+
}
|
|
1541
|
+
const id = String(++this.nextId);
|
|
1542
|
+
const req = { op: "hello", id };
|
|
1543
|
+
let resp;
|
|
1544
|
+
try {
|
|
1545
|
+
resp = await this.sendAndWait(sock, req);
|
|
1546
|
+
} catch (e) {
|
|
1547
|
+
log4(`hello probe failed (inconclusive, will retry next connect): ${e instanceof Error ? e.message : String(e)}`);
|
|
1548
|
+
return false;
|
|
1549
|
+
}
|
|
1550
|
+
const hello = resp;
|
|
1551
|
+
if (_recycledStuckDaemon) {
|
|
1552
|
+
return false;
|
|
1553
|
+
}
|
|
1554
|
+
if (!hello.daemonPath) {
|
|
1555
|
+
_recycledStuckDaemon = true;
|
|
1556
|
+
log4(`daemon does not implement hello (older protocol); recycling`);
|
|
1557
|
+
this.recycleDaemon(hello.pid);
|
|
1558
|
+
return true;
|
|
1559
|
+
}
|
|
1560
|
+
if (hello.daemonPath !== this.daemonEntry && !existsSync9(hello.daemonPath)) {
|
|
1561
|
+
_recycledStuckDaemon = true;
|
|
1562
|
+
log4(`daemon path no longer on disk \u2014 running=${hello.daemonPath} (gone) expected=${this.daemonEntry}; recycling`);
|
|
1563
|
+
this.recycleDaemon(hello.pid);
|
|
1564
|
+
return true;
|
|
1565
|
+
}
|
|
1566
|
+
this.helloVerified = true;
|
|
1567
|
+
return false;
|
|
1568
|
+
}
|
|
1569
|
+
/**
|
|
1570
|
+
* On a transformers-missing error from the daemon, SIGTERM the stuck
|
|
1571
|
+
* daemon (the bundle daemon that can't find its deps) and clear
|
|
1572
|
+
* sock/pid so the next call spawns fresh. Also enqueue a one-time
|
|
1573
|
+
* notification telling the user to run `hivemind embeddings install`
|
|
1574
|
+
* — but only when the user has opted in. Suppressed when
|
|
1575
|
+
* embeddingsStatus() === "user-disabled" so we don't nag users who
|
|
1576
|
+
* explicitly chose to turn embeddings off.
|
|
1577
|
+
*/
|
|
1578
|
+
handleTransformersMissing(detail) {
|
|
1579
|
+
if (!_recycledStuckDaemon) {
|
|
1580
|
+
_recycledStuckDaemon = true;
|
|
1581
|
+
this.recycleDaemon(null);
|
|
1582
|
+
}
|
|
1583
|
+
if (_signalledMissingDeps)
|
|
1584
|
+
return;
|
|
1585
|
+
_signalledMissingDeps = true;
|
|
1586
|
+
let status;
|
|
1587
|
+
try {
|
|
1588
|
+
status = embeddingsStatus();
|
|
1589
|
+
} catch {
|
|
1590
|
+
status = "enabled";
|
|
1591
|
+
}
|
|
1592
|
+
if (status === "user-disabled")
|
|
1593
|
+
return;
|
|
1594
|
+
enqueueNotification({
|
|
1595
|
+
id: "embed-deps-missing",
|
|
1596
|
+
severity: "warn",
|
|
1597
|
+
title: "Hivemind embeddings disabled \u2014 deps missing",
|
|
1598
|
+
body: `Semantic memory search is off because @huggingface/transformers is not installed where the daemon can find it. Run \`hivemind embeddings install\` to enable.`,
|
|
1599
|
+
dedupKey: { reason: "transformers-missing", detail: detail.slice(0, 200) }
|
|
1600
|
+
}).catch((e) => {
|
|
1601
|
+
log4(`enqueue embed-deps-missing failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
1602
|
+
});
|
|
1603
|
+
}
|
|
1604
|
+
/**
|
|
1605
|
+
* Best-effort SIGTERM + sock/pid cleanup. Tolerant of every missing-file
|
|
1606
|
+
* combination and dead-PID cases.
|
|
1607
|
+
*
|
|
1608
|
+
* Identity check: gate the SIGTERM on the daemon's socket file still
|
|
1609
|
+
* existing. We know the daemon was alive moments ago (we either just
|
|
1610
|
+
* got a hello response or the caller saw a transformers-missing error
|
|
1611
|
+
* the daemon emitted), but if the socket file is gone by the time we
|
|
1612
|
+
* try to kill, the daemon process is also gone and the PID we
|
|
1613
|
+
* captured may already have been recycled by the OS to an unrelated
|
|
1614
|
+
* user process. Mirrors the gate added to `killEmbedDaemon` in the
|
|
1615
|
+
* CLI — same failure mode, rarer trigger.
|
|
1616
|
+
*/
|
|
1617
|
+
recycleDaemon(reportedPid) {
|
|
1618
|
+
let pid = reportedPid;
|
|
1619
|
+
if (pid === null) {
|
|
1620
|
+
try {
|
|
1621
|
+
pid = Number.parseInt(readFileSync9(this.pidPath, "utf-8").trim(), 10);
|
|
1622
|
+
} catch {
|
|
1623
|
+
}
|
|
1624
|
+
}
|
|
1625
|
+
if (Number.isFinite(pid) && pid !== null && pid > 0 && existsSync9(this.socketPath)) {
|
|
1626
|
+
try {
|
|
1627
|
+
process.kill(pid, "SIGTERM");
|
|
1628
|
+
} catch {
|
|
1629
|
+
}
|
|
1630
|
+
} else if (pid !== null) {
|
|
1631
|
+
log4(`recycle: socket gone, skipping SIGTERM on possibly-stale pid ${pid}`);
|
|
1632
|
+
}
|
|
1633
|
+
try {
|
|
1634
|
+
unlinkSync4(this.socketPath);
|
|
1635
|
+
} catch {
|
|
1636
|
+
}
|
|
1637
|
+
try {
|
|
1638
|
+
unlinkSync4(this.pidPath);
|
|
1639
|
+
} catch {
|
|
1640
|
+
}
|
|
1641
|
+
}
|
|
1254
1642
|
/**
|
|
1255
1643
|
* Wait up to spawnWaitMs for the daemon to accept connections, spawning if
|
|
1256
1644
|
* necessary. Meant for SessionStart / long-running batches — not the hot path.
|
|
@@ -1274,7 +1662,7 @@ var EmbedClient = class {
|
|
|
1274
1662
|
}
|
|
1275
1663
|
}
|
|
1276
1664
|
connectOnce() {
|
|
1277
|
-
return new Promise((
|
|
1665
|
+
return new Promise((resolve2, reject) => {
|
|
1278
1666
|
const sock = connect(this.socketPath);
|
|
1279
1667
|
const to = setTimeout(() => {
|
|
1280
1668
|
sock.destroy();
|
|
@@ -1282,7 +1670,7 @@ var EmbedClient = class {
|
|
|
1282
1670
|
}, this.timeoutMs);
|
|
1283
1671
|
sock.once("connect", () => {
|
|
1284
1672
|
clearTimeout(to);
|
|
1285
|
-
|
|
1673
|
+
resolve2(sock);
|
|
1286
1674
|
});
|
|
1287
1675
|
sock.once("error", (e) => {
|
|
1288
1676
|
clearTimeout(to);
|
|
@@ -1293,16 +1681,16 @@ var EmbedClient = class {
|
|
|
1293
1681
|
trySpawnDaemon() {
|
|
1294
1682
|
let fd;
|
|
1295
1683
|
try {
|
|
1296
|
-
fd =
|
|
1684
|
+
fd = openSync4(this.pidPath, "wx", 384);
|
|
1297
1685
|
writeSync3(fd, String(process.pid));
|
|
1298
1686
|
} catch (e) {
|
|
1299
1687
|
if (this.isPidFileStale()) {
|
|
1300
1688
|
try {
|
|
1301
|
-
|
|
1689
|
+
unlinkSync4(this.pidPath);
|
|
1302
1690
|
} catch {
|
|
1303
1691
|
}
|
|
1304
1692
|
try {
|
|
1305
|
-
fd =
|
|
1693
|
+
fd = openSync4(this.pidPath, "wx", 384);
|
|
1306
1694
|
writeSync3(fd, String(process.pid));
|
|
1307
1695
|
} catch {
|
|
1308
1696
|
return;
|
|
@@ -1311,11 +1699,11 @@ var EmbedClient = class {
|
|
|
1311
1699
|
return;
|
|
1312
1700
|
}
|
|
1313
1701
|
}
|
|
1314
|
-
if (!this.daemonEntry || !
|
|
1315
|
-
|
|
1702
|
+
if (!this.daemonEntry || !existsSync9(this.daemonEntry)) {
|
|
1703
|
+
log4(`daemonEntry not configured or missing: ${this.daemonEntry}`);
|
|
1316
1704
|
try {
|
|
1317
|
-
|
|
1318
|
-
|
|
1705
|
+
closeSync4(fd);
|
|
1706
|
+
unlinkSync4(this.pidPath);
|
|
1319
1707
|
} catch {
|
|
1320
1708
|
}
|
|
1321
1709
|
return;
|
|
@@ -1327,14 +1715,14 @@ var EmbedClient = class {
|
|
|
1327
1715
|
env: process.env
|
|
1328
1716
|
});
|
|
1329
1717
|
child.unref();
|
|
1330
|
-
|
|
1718
|
+
log4(`spawned daemon pid=${child.pid}`);
|
|
1331
1719
|
} finally {
|
|
1332
|
-
|
|
1720
|
+
closeSync4(fd);
|
|
1333
1721
|
}
|
|
1334
1722
|
}
|
|
1335
1723
|
isPidFileStale() {
|
|
1336
1724
|
try {
|
|
1337
|
-
const raw =
|
|
1725
|
+
const raw = readFileSync9(this.pidPath, "utf-8").trim();
|
|
1338
1726
|
const pid = Number(raw);
|
|
1339
1727
|
if (!pid || Number.isNaN(pid))
|
|
1340
1728
|
return true;
|
|
@@ -1352,9 +1740,9 @@ var EmbedClient = class {
|
|
|
1352
1740
|
const deadline = Date.now() + this.spawnWaitMs;
|
|
1353
1741
|
let delay = 30;
|
|
1354
1742
|
while (Date.now() < deadline) {
|
|
1355
|
-
await
|
|
1743
|
+
await sleep3(delay);
|
|
1356
1744
|
delay = Math.min(delay * 1.5, 300);
|
|
1357
|
-
if (!
|
|
1745
|
+
if (!existsSync9(this.socketPath))
|
|
1358
1746
|
continue;
|
|
1359
1747
|
try {
|
|
1360
1748
|
return await this.connectOnce();
|
|
@@ -1364,7 +1752,7 @@ var EmbedClient = class {
|
|
|
1364
1752
|
throw new Error("daemon did not become ready within spawnWaitMs");
|
|
1365
1753
|
}
|
|
1366
1754
|
sendAndWait(sock, req) {
|
|
1367
|
-
return new Promise((
|
|
1755
|
+
return new Promise((resolve2, reject) => {
|
|
1368
1756
|
let buf = "";
|
|
1369
1757
|
const to = setTimeout(() => {
|
|
1370
1758
|
sock.destroy();
|
|
@@ -1379,7 +1767,7 @@ var EmbedClient = class {
|
|
|
1379
1767
|
const line = buf.slice(0, nl);
|
|
1380
1768
|
clearTimeout(to);
|
|
1381
1769
|
try {
|
|
1382
|
-
|
|
1770
|
+
resolve2(JSON.parse(line));
|
|
1383
1771
|
} catch (e) {
|
|
1384
1772
|
reject(e);
|
|
1385
1773
|
}
|
|
@@ -1396,9 +1784,14 @@ var EmbedClient = class {
|
|
|
1396
1784
|
});
|
|
1397
1785
|
}
|
|
1398
1786
|
};
|
|
1399
|
-
function
|
|
1787
|
+
function sleep3(ms) {
|
|
1400
1788
|
return new Promise((r) => setTimeout(r, ms));
|
|
1401
1789
|
}
|
|
1790
|
+
function isTransformersMissingError(err) {
|
|
1791
|
+
if (/hivemind embeddings install/i.test(err))
|
|
1792
|
+
return true;
|
|
1793
|
+
return /@huggingface\/transformers/i.test(err);
|
|
1794
|
+
}
|
|
1402
1795
|
|
|
1403
1796
|
// dist/src/embeddings/sql.js
|
|
1404
1797
|
function embeddingSqlLiteral(vec) {
|
|
@@ -1413,48 +1806,12 @@ function embeddingSqlLiteral(vec) {
|
|
|
1413
1806
|
return `ARRAY[${parts.join(",")}]::float4[]`;
|
|
1414
1807
|
}
|
|
1415
1808
|
|
|
1416
|
-
// dist/src/embeddings/disable.js
|
|
1417
|
-
import { createRequire as createRequire2 } from "node:module";
|
|
1418
|
-
import { homedir as homedir11 } from "node:os";
|
|
1419
|
-
import { join as join14 } from "node:path";
|
|
1420
|
-
import { pathToFileURL } from "node:url";
|
|
1421
|
-
var cachedStatus = null;
|
|
1422
|
-
function defaultResolveTransformers() {
|
|
1423
|
-
try {
|
|
1424
|
-
createRequire2(import.meta.url).resolve("@huggingface/transformers");
|
|
1425
|
-
return;
|
|
1426
|
-
} catch {
|
|
1427
|
-
}
|
|
1428
|
-
const sharedDir = join14(homedir11(), ".hivemind", "embed-deps");
|
|
1429
|
-
createRequire2(pathToFileURL(`${sharedDir}/`).href).resolve("@huggingface/transformers");
|
|
1430
|
-
}
|
|
1431
|
-
var _resolve = defaultResolveTransformers;
|
|
1432
|
-
function detectStatus() {
|
|
1433
|
-
if (process.env.HIVEMIND_EMBEDDINGS === "false")
|
|
1434
|
-
return "env-disabled";
|
|
1435
|
-
try {
|
|
1436
|
-
_resolve();
|
|
1437
|
-
return "enabled";
|
|
1438
|
-
} catch {
|
|
1439
|
-
return "no-transformers";
|
|
1440
|
-
}
|
|
1441
|
-
}
|
|
1442
|
-
function embeddingsStatus() {
|
|
1443
|
-
if (cachedStatus !== null)
|
|
1444
|
-
return cachedStatus;
|
|
1445
|
-
cachedStatus = detectStatus();
|
|
1446
|
-
return cachedStatus;
|
|
1447
|
-
}
|
|
1448
|
-
function embeddingsDisabled() {
|
|
1449
|
-
return embeddingsStatus() !== "enabled";
|
|
1450
|
-
}
|
|
1451
|
-
|
|
1452
1809
|
// dist/src/hooks/codex/stop.js
|
|
1453
|
-
var
|
|
1810
|
+
var log5 = (msg) => log("codex-stop", msg);
|
|
1454
1811
|
function resolveEmbedDaemonPath() {
|
|
1455
|
-
return
|
|
1812
|
+
return join17(dirname5(fileURLToPath3(import.meta.url)), "embeddings", "embed-daemon.js");
|
|
1456
1813
|
}
|
|
1457
|
-
var __bundleDir =
|
|
1814
|
+
var __bundleDir = dirname5(fileURLToPath3(import.meta.url));
|
|
1458
1815
|
var PLUGIN_VERSION = getInstalledVersion(__bundleDir, ".codex-plugin") ?? "";
|
|
1459
1816
|
var CAPTURE = process.env.HIVEMIND_CAPTURE !== "false";
|
|
1460
1817
|
async function main() {
|
|
@@ -1466,7 +1823,7 @@ async function main() {
|
|
|
1466
1823
|
return;
|
|
1467
1824
|
const config = loadConfig();
|
|
1468
1825
|
if (!config) {
|
|
1469
|
-
|
|
1826
|
+
log5("no config");
|
|
1470
1827
|
return;
|
|
1471
1828
|
}
|
|
1472
1829
|
if (CAPTURE) {
|
|
@@ -1478,8 +1835,8 @@ async function main() {
|
|
|
1478
1835
|
if (input.transcript_path) {
|
|
1479
1836
|
try {
|
|
1480
1837
|
const transcriptPath = input.transcript_path;
|
|
1481
|
-
if (
|
|
1482
|
-
const transcript =
|
|
1838
|
+
if (existsSync10(transcriptPath)) {
|
|
1839
|
+
const transcript = readFileSync10(transcriptPath, "utf-8");
|
|
1483
1840
|
const lines = transcript.trim().split("\n").reverse();
|
|
1484
1841
|
for (const line2 of lines) {
|
|
1485
1842
|
try {
|
|
@@ -1496,10 +1853,10 @@ async function main() {
|
|
|
1496
1853
|
}
|
|
1497
1854
|
}
|
|
1498
1855
|
if (lastAssistantMessage)
|
|
1499
|
-
|
|
1856
|
+
log5(`extracted assistant message from transcript (${lastAssistantMessage.length} chars)`);
|
|
1500
1857
|
}
|
|
1501
1858
|
} catch (e) {
|
|
1502
|
-
|
|
1859
|
+
log5(`transcript read failed: ${e.message}`);
|
|
1503
1860
|
}
|
|
1504
1861
|
}
|
|
1505
1862
|
const entry = {
|
|
@@ -1522,9 +1879,9 @@ async function main() {
|
|
|
1522
1879
|
const embeddingSql = embeddingSqlLiteral(embedding);
|
|
1523
1880
|
const insertSql = `INSERT INTO "${sessionsTable}" (id, path, filename, message, message_embedding, author, size_bytes, project, description, agent, plugin_version, creation_date, last_update_date) VALUES ('${crypto.randomUUID()}', '${sqlStr(sessionPath)}', '${sqlStr(filename)}', '${jsonForSql}'::jsonb, ${embeddingSql}, '${sqlStr(config.userName)}', ${Buffer.byteLength(line, "utf-8")}, '${sqlStr(projectName)}', 'Stop', 'codex', '${sqlStr(PLUGIN_VERSION)}', '${ts}', '${ts}')`;
|
|
1524
1881
|
await api.query(insertSql);
|
|
1525
|
-
|
|
1882
|
+
log5("stop event captured");
|
|
1526
1883
|
} catch (e) {
|
|
1527
|
-
|
|
1884
|
+
log5(`capture failed: ${e.message}`);
|
|
1528
1885
|
}
|
|
1529
1886
|
}
|
|
1530
1887
|
if (!CAPTURE)
|
|
@@ -1543,11 +1900,11 @@ async function main() {
|
|
|
1543
1900
|
reason: "Stop"
|
|
1544
1901
|
});
|
|
1545
1902
|
} catch (e) {
|
|
1546
|
-
|
|
1903
|
+
log5(`spawn failed: ${e.message}`);
|
|
1547
1904
|
try {
|
|
1548
1905
|
releaseLock(sessionId);
|
|
1549
1906
|
} catch (releaseErr) {
|
|
1550
|
-
|
|
1907
|
+
log5(`releaseLock after spawn failure also failed: ${releaseErr.message}`);
|
|
1551
1908
|
}
|
|
1552
1909
|
throw e;
|
|
1553
1910
|
}
|
|
@@ -1560,6 +1917,6 @@ async function main() {
|
|
|
1560
1917
|
});
|
|
1561
1918
|
}
|
|
1562
1919
|
main().catch((e) => {
|
|
1563
|
-
|
|
1920
|
+
log5(`fatal: ${e.message}`);
|
|
1564
1921
|
process.exit(0);
|
|
1565
1922
|
});
|