@deeplake/hivemind 0.7.34 → 0.7.36
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 +152 -67
- package/codex/bundle/capture.js +152 -67
- package/codex/bundle/commands/auth-login.js +152 -67
- package/codex/bundle/pre-tool-use.js +152 -67
- package/codex/bundle/session-start-setup.js +152 -67
- package/codex/bundle/session-start.js +152 -67
- package/codex/bundle/shell/deeplake-shell.js +152 -67
- package/codex/bundle/skillify-worker.js +146 -14
- package/codex/bundle/stop.js +152 -67
- package/cursor/bundle/capture.js +152 -67
- package/cursor/bundle/commands/auth-login.js +152 -67
- package/cursor/bundle/pre-tool-use.js +152 -67
- package/cursor/bundle/session-start.js +152 -67
- package/cursor/bundle/shell/deeplake-shell.js +152 -67
- package/cursor/bundle/skillify-worker.js +146 -14
- package/hermes/bundle/capture.js +152 -67
- package/hermes/bundle/commands/auth-login.js +152 -67
- package/hermes/bundle/pre-tool-use.js +152 -67
- package/hermes/bundle/session-start.js +152 -67
- package/hermes/bundle/shell/deeplake-shell.js +152 -67
- package/hermes/bundle/skillify-worker.js +146 -14
- package/mcp/bundle/server.js +154 -69
- package/openclaw/dist/index.js +386 -80
- package/openclaw/dist/skillify-worker.js +146 -14
- package/openclaw/openclaw.plugin.json +1 -1
- package/openclaw/package.json +1 -1
- package/package.json +1 -1
- package/pi/extension-source/hivemind.ts +188 -62
package/openclaw/dist/index.js
CHANGED
|
@@ -4,6 +4,10 @@ import {
|
|
|
4
4
|
saveCredentials
|
|
5
5
|
} from "./chunks/chunk-OSD5GJJ5.js";
|
|
6
6
|
|
|
7
|
+
// stub:node:child_process
|
|
8
|
+
var spawn = () => {
|
|
9
|
+
};
|
|
10
|
+
|
|
7
11
|
// src/utils/client-header.ts
|
|
8
12
|
var DEEPLAKE_CLIENT_HEADER = "X-Deeplake-Client";
|
|
9
13
|
function deeplakeClientValue() {
|
|
@@ -102,7 +106,121 @@ function sqlIdent(name) {
|
|
|
102
106
|
|
|
103
107
|
// src/embeddings/columns.ts
|
|
104
108
|
var SUMMARY_EMBEDDING_COL = "summary_embedding";
|
|
105
|
-
|
|
109
|
+
|
|
110
|
+
// src/deeplake-schema.ts
|
|
111
|
+
var MEMORY_COLUMNS = Object.freeze([
|
|
112
|
+
{ name: "id", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
113
|
+
{ name: "path", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
114
|
+
{ name: "filename", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
115
|
+
{ name: "summary", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
116
|
+
{ name: "summary_embedding", sql: "FLOAT4[]" },
|
|
117
|
+
{ name: "author", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
118
|
+
{ name: "mime_type", sql: "TEXT NOT NULL DEFAULT 'text/plain'" },
|
|
119
|
+
{ name: "size_bytes", sql: "BIGINT NOT NULL DEFAULT 0" },
|
|
120
|
+
{ name: "project", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
121
|
+
{ name: "description", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
122
|
+
{ name: "agent", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
123
|
+
{ name: "plugin_version", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
124
|
+
{ name: "creation_date", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
125
|
+
{ name: "last_update_date", sql: "TEXT NOT NULL DEFAULT ''" }
|
|
126
|
+
]);
|
|
127
|
+
var SESSIONS_COLUMNS = Object.freeze([
|
|
128
|
+
{ name: "id", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
129
|
+
{ name: "path", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
130
|
+
{ name: "filename", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
131
|
+
{ name: "message", sql: "JSONB" },
|
|
132
|
+
{ name: "message_embedding", sql: "FLOAT4[]" },
|
|
133
|
+
{ name: "author", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
134
|
+
{ name: "mime_type", sql: "TEXT NOT NULL DEFAULT 'application/json'" },
|
|
135
|
+
{ name: "size_bytes", sql: "BIGINT NOT NULL DEFAULT 0" },
|
|
136
|
+
{ name: "project", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
137
|
+
{ name: "description", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
138
|
+
{ name: "agent", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
139
|
+
{ name: "plugin_version", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
140
|
+
{ name: "creation_date", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
141
|
+
{ name: "last_update_date", sql: "TEXT NOT NULL DEFAULT ''" }
|
|
142
|
+
]);
|
|
143
|
+
var SKILLS_COLUMNS = Object.freeze([
|
|
144
|
+
{ name: "id", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
145
|
+
{ name: "name", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
146
|
+
{ name: "project", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
147
|
+
{ name: "project_key", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
148
|
+
{ name: "local_path", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
149
|
+
{ name: "install", sql: "TEXT NOT NULL DEFAULT 'project'" },
|
|
150
|
+
{ name: "source_sessions", sql: "TEXT NOT NULL DEFAULT '[]'" },
|
|
151
|
+
{ name: "source_agent", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
152
|
+
{ name: "scope", sql: "TEXT NOT NULL DEFAULT 'me'" },
|
|
153
|
+
{ name: "author", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
154
|
+
{ name: "contributors", sql: "TEXT NOT NULL DEFAULT '[]'" },
|
|
155
|
+
{ name: "description", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
156
|
+
{ name: "trigger_text", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
157
|
+
{ name: "body", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
158
|
+
{ name: "version", sql: "BIGINT NOT NULL DEFAULT 1" },
|
|
159
|
+
{ name: "created_at", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
160
|
+
{ name: "updated_at", sql: "TEXT NOT NULL DEFAULT ''" }
|
|
161
|
+
]);
|
|
162
|
+
function validateSchema(label, cols) {
|
|
163
|
+
const seen = /* @__PURE__ */ new Set();
|
|
164
|
+
for (const col of cols) {
|
|
165
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(col.name)) {
|
|
166
|
+
throw new Error(`${label}: column name "${col.name}" is not a valid SQL identifier`);
|
|
167
|
+
}
|
|
168
|
+
if (seen.has(col.name)) {
|
|
169
|
+
throw new Error(`${label}: duplicate column "${col.name}"`);
|
|
170
|
+
}
|
|
171
|
+
seen.add(col.name);
|
|
172
|
+
const notNull = /\bNOT\s+NULL\b/i.test(col.sql);
|
|
173
|
+
const hasDefault = /\bDEFAULT\b/i.test(col.sql);
|
|
174
|
+
if (notNull && !hasDefault) {
|
|
175
|
+
throw new Error(
|
|
176
|
+
`${label}: column "${col.name}" is NOT NULL but has no DEFAULT \u2014 ALTER TABLE ADD COLUMN on a populated table would fail.`
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
validateSchema("MEMORY_COLUMNS", MEMORY_COLUMNS);
|
|
182
|
+
validateSchema("SESSIONS_COLUMNS", SESSIONS_COLUMNS);
|
|
183
|
+
validateSchema("SKILLS_COLUMNS", SKILLS_COLUMNS);
|
|
184
|
+
function buildCreateTableSql(tableName, cols) {
|
|
185
|
+
const safe = sqlIdent(tableName);
|
|
186
|
+
const colSql = cols.map((c) => `${c.name} ${c.sql}`).join(", ");
|
|
187
|
+
return `CREATE TABLE IF NOT EXISTS "${safe}" (${colSql}) USING deeplake`;
|
|
188
|
+
}
|
|
189
|
+
function buildIntrospectionSql(tableName, workspaceId) {
|
|
190
|
+
return `SELECT column_name FROM information_schema.columns WHERE table_name = '${sqlStr(tableName)}' AND table_schema = '${sqlStr(workspaceId)}'`;
|
|
191
|
+
}
|
|
192
|
+
async function healMissingColumns(args) {
|
|
193
|
+
const safeTable = sqlIdent(args.tableName);
|
|
194
|
+
const introspectSql = buildIntrospectionSql(args.tableName, args.workspaceId);
|
|
195
|
+
const rows = await args.query(introspectSql);
|
|
196
|
+
const existing = /* @__PURE__ */ new Set();
|
|
197
|
+
for (const row of rows) {
|
|
198
|
+
const v = row?.column_name;
|
|
199
|
+
if (typeof v === "string") existing.add(v.toLowerCase());
|
|
200
|
+
}
|
|
201
|
+
const missingCols = args.columns.filter((c) => !existing.has(c.name.toLowerCase()));
|
|
202
|
+
const missing = missingCols.map((c) => c.name);
|
|
203
|
+
if (missingCols.length === 0) return { missing, altered: [] };
|
|
204
|
+
const altered = [];
|
|
205
|
+
for (const col of missingCols) {
|
|
206
|
+
try {
|
|
207
|
+
await args.query(`ALTER TABLE "${safeTable}" ADD COLUMN ${col.name} ${col.sql}`);
|
|
208
|
+
altered.push(col.name);
|
|
209
|
+
args.log?.(`schema-heal: added "${args.tableName}"."${col.name}"`);
|
|
210
|
+
} catch (e) {
|
|
211
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
212
|
+
if (!/already exists/i.test(msg)) throw e;
|
|
213
|
+
const recheck = await args.query(introspectSql);
|
|
214
|
+
const present = recheck.some((r) => {
|
|
215
|
+
const v = r?.column_name;
|
|
216
|
+
return typeof v === "string" && v.toLowerCase() === col.name.toLowerCase();
|
|
217
|
+
});
|
|
218
|
+
if (!present) throw e;
|
|
219
|
+
args.log?.(`schema-heal: "${args.tableName}"."${col.name}" appeared via race, treating as success`);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
return { missing, altered };
|
|
223
|
+
}
|
|
106
224
|
|
|
107
225
|
// src/notifications/queue.ts
|
|
108
226
|
import { readFileSync, writeFileSync, renameSync, mkdirSync, openSync, closeSync, unlinkSync, statSync } from "node:fs";
|
|
@@ -465,61 +583,33 @@ var DeeplakeApi = class {
|
|
|
465
583
|
}
|
|
466
584
|
}
|
|
467
585
|
/**
|
|
468
|
-
*
|
|
586
|
+
* Heal any missing columns on a table so it matches one of the schema
|
|
587
|
+
* definitions in `deeplake-schema.ts`. One SELECT against
|
|
588
|
+
* `information_schema.columns` per call, then `ALTER TABLE ADD COLUMN`
|
|
589
|
+
* only the genuinely missing ones — never blanket, never `IF NOT
|
|
590
|
+
* EXISTS`.
|
|
469
591
|
*
|
|
470
|
-
*
|
|
471
|
-
*
|
|
472
|
-
*
|
|
473
|
-
*
|
|
474
|
-
*
|
|
475
|
-
*
|
|
476
|
-
*
|
|
477
|
-
*
|
|
478
|
-
*
|
|
479
|
-
*
|
|
480
|
-
*
|
|
481
|
-
*
|
|
482
|
-
* column_name = C. Read-only, idempotent, can't tickle the post-ALTER
|
|
483
|
-
* bug. If the column is present → mark + return.
|
|
484
|
-
* 3. Only if step 2 says the column is missing, fall back to ALTER ADD
|
|
485
|
-
* COLUMN IF NOT EXISTS. Mark on success, also mark if Deeplake reports
|
|
486
|
-
* "already exists" (race: another client added it between our SELECT
|
|
487
|
-
* and ALTER).
|
|
488
|
-
*
|
|
489
|
-
* Marker uses the same dir / TTL as ensureLookupIndex so both schema
|
|
490
|
-
* caches share an opt-out (HIVEMIND_INDEX_MARKER_DIR) and a TTL knob.
|
|
592
|
+
* History: an earlier path used a local marker file (`col_<name>` under
|
|
593
|
+
* the index-marker dir) to skip even the SELECT after the first
|
|
594
|
+
* confirmation, plus per-column ALTERs for `summary_embedding`,
|
|
595
|
+
* `message_embedding`, `agent`, `plugin_version`. The marker existed
|
|
596
|
+
* because Deeplake used to expose a ~30s post-ALTER bug where
|
|
597
|
+
* subsequent INSERTs failed, so we wanted to keep ALTER traffic to a
|
|
598
|
+
* minimum. The bug was re-verified on 2026-05-18 against
|
|
599
|
+
* `api.deeplake.ai` (`test_plugin` org) and no longer reproduces
|
|
600
|
+
* (71/71 INSERTs OK, first success 2ms after ALTER). The single SELECT
|
|
601
|
+
* + targeted ALTER pattern survives the marker removal because: each
|
|
602
|
+
* ALTER still costs ~800ms (so blanket sweeps are wasteful) and the
|
|
603
|
+
* diff produces clearer logs than "ALTER all with IF NOT EXISTS".
|
|
491
604
|
*/
|
|
492
|
-
async
|
|
493
|
-
await
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
* the `agent` column (added 2026-04-11) — the latter has no fallback if
|
|
501
|
-
* a user upgraded over a pre-2026-04-11 table, so every INSERT fails
|
|
502
|
-
* with `column "agent" does not exist`.
|
|
503
|
-
*/
|
|
504
|
-
async ensureColumn(table, column, sqlType) {
|
|
505
|
-
const markers = await getIndexMarkerStore();
|
|
506
|
-
const markerPath = markers.buildIndexMarkerPath(this.workspaceId, this.orgId, table, `col_${column}`);
|
|
507
|
-
if (markers.hasFreshIndexMarker(markerPath)) return;
|
|
508
|
-
const colCheck = `SELECT 1 FROM information_schema.columns WHERE table_name = '${sqlStr(table)}' AND column_name = '${sqlStr(column)}' AND table_schema = '${sqlStr(this.workspaceId)}' LIMIT 1`;
|
|
509
|
-
const rows = await this.query(colCheck);
|
|
510
|
-
if (rows.length > 0) {
|
|
511
|
-
markers.writeIndexMarker(markerPath);
|
|
512
|
-
return;
|
|
513
|
-
}
|
|
514
|
-
try {
|
|
515
|
-
await this.query(`ALTER TABLE "${table}" ADD COLUMN ${column} ${sqlType}`);
|
|
516
|
-
} catch (e) {
|
|
517
|
-
const msg = e instanceof Error ? e.message : String(e);
|
|
518
|
-
if (!/already exists/i.test(msg)) throw e;
|
|
519
|
-
const recheck = await this.query(colCheck);
|
|
520
|
-
if (recheck.length === 0) throw e;
|
|
521
|
-
}
|
|
522
|
-
markers.writeIndexMarker(markerPath);
|
|
605
|
+
async healSchema(table, columns) {
|
|
606
|
+
await healMissingColumns({
|
|
607
|
+
query: (sql) => this.query(sql),
|
|
608
|
+
tableName: table,
|
|
609
|
+
workspaceId: this.workspaceId,
|
|
610
|
+
columns,
|
|
611
|
+
log: log3
|
|
612
|
+
});
|
|
523
613
|
}
|
|
524
614
|
/** List all tables in the workspace (with retry). */
|
|
525
615
|
async listTables(forceRefresh = false) {
|
|
@@ -588,22 +678,20 @@ var DeeplakeApi = class {
|
|
|
588
678
|
}
|
|
589
679
|
throw lastErr;
|
|
590
680
|
}
|
|
591
|
-
/** Create the memory table if it doesn't already exist.
|
|
681
|
+
/** Create the memory table if it doesn't already exist. Heal missing columns on existing tables. */
|
|
592
682
|
async ensureTable(name) {
|
|
683
|
+
if (!MEMORY_COLUMNS.some((c) => c.name === SUMMARY_EMBEDDING_COL)) {
|
|
684
|
+
throw new Error(`MEMORY_COLUMNS missing "${SUMMARY_EMBEDDING_COL}" (embeddings/columns.ts drift)`);
|
|
685
|
+
}
|
|
593
686
|
const tbl = sqlIdent(name ?? this.tableName);
|
|
594
687
|
const tables = await this.listTables();
|
|
595
688
|
if (!tables.includes(tbl)) {
|
|
596
689
|
log3(`table "${tbl}" not found, creating`);
|
|
597
|
-
await this.createTableWithRetry(
|
|
598
|
-
`CREATE TABLE IF NOT EXISTS "${tbl}" (id TEXT NOT NULL DEFAULT '', path TEXT NOT NULL DEFAULT '', filename TEXT NOT NULL DEFAULT '', summary TEXT NOT NULL DEFAULT '', summary_embedding FLOAT4[], author TEXT NOT NULL DEFAULT '', mime_type TEXT NOT NULL DEFAULT 'text/plain', size_bytes BIGINT NOT NULL DEFAULT 0, project TEXT NOT NULL DEFAULT '', description TEXT NOT NULL DEFAULT '', agent TEXT NOT NULL DEFAULT '', plugin_version TEXT NOT NULL DEFAULT '', creation_date TEXT NOT NULL DEFAULT '', last_update_date TEXT NOT NULL DEFAULT '') USING deeplake`,
|
|
599
|
-
tbl
|
|
600
|
-
);
|
|
690
|
+
await this.createTableWithRetry(buildCreateTableSql(tbl, MEMORY_COLUMNS), tbl);
|
|
601
691
|
log3(`table "${tbl}" created`);
|
|
602
692
|
if (!tables.includes(tbl)) this._tablesCache = [...tables, tbl];
|
|
603
693
|
}
|
|
604
|
-
await this.
|
|
605
|
-
await this.ensureColumn(tbl, "agent", "TEXT NOT NULL DEFAULT ''");
|
|
606
|
-
await this.ensureColumn(tbl, "plugin_version", "TEXT NOT NULL DEFAULT ''");
|
|
694
|
+
await this.healSchema(tbl, MEMORY_COLUMNS);
|
|
607
695
|
}
|
|
608
696
|
/** Create the sessions table (uses JSONB for message since every row is a JSON event). */
|
|
609
697
|
async ensureSessionsTable(name) {
|
|
@@ -611,16 +699,11 @@ var DeeplakeApi = class {
|
|
|
611
699
|
const tables = await this.listTables();
|
|
612
700
|
if (!tables.includes(safe)) {
|
|
613
701
|
log3(`table "${safe}" not found, creating`);
|
|
614
|
-
await this.createTableWithRetry(
|
|
615
|
-
`CREATE TABLE IF NOT EXISTS "${safe}" (id TEXT NOT NULL DEFAULT '', path TEXT NOT NULL DEFAULT '', filename TEXT NOT NULL DEFAULT '', message JSONB, message_embedding FLOAT4[], author TEXT NOT NULL DEFAULT '', mime_type TEXT NOT NULL DEFAULT 'application/json', size_bytes BIGINT NOT NULL DEFAULT 0, project TEXT NOT NULL DEFAULT '', description TEXT NOT NULL DEFAULT '', agent TEXT NOT NULL DEFAULT '', plugin_version TEXT NOT NULL DEFAULT '', creation_date TEXT NOT NULL DEFAULT '', last_update_date TEXT NOT NULL DEFAULT '') USING deeplake`,
|
|
616
|
-
safe
|
|
617
|
-
);
|
|
702
|
+
await this.createTableWithRetry(buildCreateTableSql(safe, SESSIONS_COLUMNS), safe);
|
|
618
703
|
log3(`table "${safe}" created`);
|
|
619
704
|
if (!tables.includes(safe)) this._tablesCache = [...tables, safe];
|
|
620
705
|
}
|
|
621
|
-
await this.
|
|
622
|
-
await this.ensureColumn(safe, "agent", "TEXT NOT NULL DEFAULT ''");
|
|
623
|
-
await this.ensureColumn(safe, "plugin_version", "TEXT NOT NULL DEFAULT ''");
|
|
706
|
+
await this.healSchema(safe, SESSIONS_COLUMNS);
|
|
624
707
|
await this.ensureLookupIndex(safe, "path_creation_date", `("path", "creation_date")`);
|
|
625
708
|
}
|
|
626
709
|
/**
|
|
@@ -638,13 +721,11 @@ var DeeplakeApi = class {
|
|
|
638
721
|
const tables = await this.listTables();
|
|
639
722
|
if (!tables.includes(safe)) {
|
|
640
723
|
log3(`table "${safe}" not found, creating`);
|
|
641
|
-
await this.createTableWithRetry(
|
|
642
|
-
`CREATE TABLE IF NOT EXISTS "${safe}" (id TEXT NOT NULL DEFAULT '', name TEXT NOT NULL DEFAULT '', project TEXT NOT NULL DEFAULT '', project_key TEXT NOT NULL DEFAULT '', local_path TEXT NOT NULL DEFAULT '', install TEXT NOT NULL DEFAULT 'project', source_sessions TEXT NOT NULL DEFAULT '[]', source_agent TEXT NOT NULL DEFAULT '', scope TEXT NOT NULL DEFAULT 'me', author TEXT NOT NULL DEFAULT '', description TEXT NOT NULL DEFAULT '', trigger_text TEXT NOT NULL DEFAULT '', body TEXT NOT NULL DEFAULT '', version BIGINT NOT NULL DEFAULT 1, created_at TEXT NOT NULL DEFAULT '', updated_at TEXT NOT NULL DEFAULT '') USING deeplake`,
|
|
643
|
-
safe
|
|
644
|
-
);
|
|
724
|
+
await this.createTableWithRetry(buildCreateTableSql(safe, SKILLS_COLUMNS), safe);
|
|
645
725
|
log3(`table "${safe}" created`);
|
|
646
726
|
if (!tables.includes(safe)) this._tablesCache = [...tables, safe];
|
|
647
727
|
}
|
|
728
|
+
await this.healSchema(safe, SKILLS_COLUMNS);
|
|
648
729
|
await this.ensureLookupIndex(safe, "project_key_name", `("project_key", "name")`);
|
|
649
730
|
}
|
|
650
731
|
};
|
|
@@ -1153,10 +1234,232 @@ async function readVirtualPathContent(api2, memoryTable2, sessionsTable2, virtua
|
|
|
1153
1234
|
return (await readVirtualPathContents(api2, memoryTable2, sessionsTable2, [virtualPath])).get(virtualPath) ?? null;
|
|
1154
1235
|
}
|
|
1155
1236
|
|
|
1237
|
+
// src/embeddings/standalone-embed-client.ts
|
|
1238
|
+
import { connect } from "node:net";
|
|
1239
|
+
import {
|
|
1240
|
+
openSync as openSync2,
|
|
1241
|
+
closeSync as closeSync2,
|
|
1242
|
+
writeSync,
|
|
1243
|
+
unlinkSync as unlinkSync2,
|
|
1244
|
+
existsSync,
|
|
1245
|
+
readFileSync as readFileSync2
|
|
1246
|
+
} from "node:fs";
|
|
1247
|
+
import { homedir as homedir3 } from "node:os";
|
|
1248
|
+
import { join as join3 } from "node:path";
|
|
1249
|
+
|
|
1250
|
+
// src/embeddings/protocol.ts
|
|
1251
|
+
var DEFAULT_SOCKET_DIR = "/tmp";
|
|
1252
|
+
var DEFAULT_IDLE_TIMEOUT_MS = 10 * 60 * 1e3;
|
|
1253
|
+
var DEFAULT_CLIENT_TIMEOUT_MS = 2e3;
|
|
1254
|
+
function socketPathFor(uid, dir = DEFAULT_SOCKET_DIR) {
|
|
1255
|
+
return `${dir}/hivemind-embed-${uid}.sock`;
|
|
1256
|
+
}
|
|
1257
|
+
function pidPathFor(uid, dir = DEFAULT_SOCKET_DIR) {
|
|
1258
|
+
return `${dir}/hivemind-embed-${uid}.pid`;
|
|
1259
|
+
}
|
|
1260
|
+
|
|
1261
|
+
// src/embeddings/standalone-embed-client.ts
|
|
1262
|
+
var SHARED_DAEMON_PATH = join3(homedir3(), ".hivemind", "embed-deps", "embed-daemon.js");
|
|
1263
|
+
var _spawn = spawn;
|
|
1264
|
+
function getUid() {
|
|
1265
|
+
const uid = typeof process.getuid === "function" ? process.getuid() : void 0;
|
|
1266
|
+
return uid !== void 0 ? String(uid) : "default";
|
|
1267
|
+
}
|
|
1268
|
+
function isPidAlive(pid) {
|
|
1269
|
+
if (!Number.isFinite(pid) || pid <= 0) return false;
|
|
1270
|
+
try {
|
|
1271
|
+
process.kill(pid, 0);
|
|
1272
|
+
return true;
|
|
1273
|
+
} catch {
|
|
1274
|
+
return false;
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
function readPidFile(path) {
|
|
1278
|
+
let raw;
|
|
1279
|
+
try {
|
|
1280
|
+
raw = readFileSync2(path, "utf-8").trim();
|
|
1281
|
+
} catch {
|
|
1282
|
+
return null;
|
|
1283
|
+
}
|
|
1284
|
+
if (raw === "") return "empty";
|
|
1285
|
+
const pid = Number(raw);
|
|
1286
|
+
if (!pid || Number.isNaN(pid)) return null;
|
|
1287
|
+
return pid;
|
|
1288
|
+
}
|
|
1289
|
+
function connectOnce(socketPath, timeoutMs) {
|
|
1290
|
+
return new Promise((resolve2, reject) => {
|
|
1291
|
+
const sock = connect(socketPath);
|
|
1292
|
+
const to = setTimeout(() => {
|
|
1293
|
+
sock.destroy();
|
|
1294
|
+
reject(new Error("connect timeout"));
|
|
1295
|
+
}, timeoutMs);
|
|
1296
|
+
sock.once("connect", () => {
|
|
1297
|
+
clearTimeout(to);
|
|
1298
|
+
resolve2(sock);
|
|
1299
|
+
});
|
|
1300
|
+
sock.once("error", (e) => {
|
|
1301
|
+
clearTimeout(to);
|
|
1302
|
+
reject(e);
|
|
1303
|
+
});
|
|
1304
|
+
});
|
|
1305
|
+
}
|
|
1306
|
+
function sendAndWait(sock, req, timeoutMs) {
|
|
1307
|
+
return new Promise((resolve2, reject) => {
|
|
1308
|
+
let buf = "";
|
|
1309
|
+
const to = setTimeout(() => {
|
|
1310
|
+
sock.destroy();
|
|
1311
|
+
reject(new Error("request timeout"));
|
|
1312
|
+
}, timeoutMs);
|
|
1313
|
+
sock.setEncoding("utf-8");
|
|
1314
|
+
sock.on("data", (chunk) => {
|
|
1315
|
+
buf += chunk;
|
|
1316
|
+
const nl = buf.indexOf("\n");
|
|
1317
|
+
if (nl === -1) return;
|
|
1318
|
+
clearTimeout(to);
|
|
1319
|
+
try {
|
|
1320
|
+
resolve2(JSON.parse(buf.slice(0, nl)));
|
|
1321
|
+
} catch (e) {
|
|
1322
|
+
reject(e);
|
|
1323
|
+
}
|
|
1324
|
+
});
|
|
1325
|
+
sock.on("error", (e) => {
|
|
1326
|
+
clearTimeout(to);
|
|
1327
|
+
reject(e);
|
|
1328
|
+
});
|
|
1329
|
+
sock.on("end", () => {
|
|
1330
|
+
clearTimeout(to);
|
|
1331
|
+
reject(new Error("connection closed without response"));
|
|
1332
|
+
});
|
|
1333
|
+
sock.write(JSON.stringify(req) + "\n");
|
|
1334
|
+
});
|
|
1335
|
+
}
|
|
1336
|
+
function trySpawnDaemon(daemonEntry, pidPath) {
|
|
1337
|
+
let fd;
|
|
1338
|
+
try {
|
|
1339
|
+
fd = openSync2(pidPath, "wx", 384);
|
|
1340
|
+
writeSync(fd, String(process.pid));
|
|
1341
|
+
} catch {
|
|
1342
|
+
const existing = readPidFile(pidPath);
|
|
1343
|
+
if (existing === "empty") return false;
|
|
1344
|
+
if (existing !== null && isPidAlive(existing)) {
|
|
1345
|
+
return false;
|
|
1346
|
+
}
|
|
1347
|
+
try {
|
|
1348
|
+
unlinkSync2(pidPath);
|
|
1349
|
+
} catch {
|
|
1350
|
+
}
|
|
1351
|
+
try {
|
|
1352
|
+
fd = openSync2(pidPath, "wx", 384);
|
|
1353
|
+
writeSync(fd, String(process.pid));
|
|
1354
|
+
} catch {
|
|
1355
|
+
return false;
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1358
|
+
try {
|
|
1359
|
+
const child = _spawn(process.execPath, [daemonEntry], {
|
|
1360
|
+
detached: true,
|
|
1361
|
+
stdio: "ignore"
|
|
1362
|
+
});
|
|
1363
|
+
child.unref();
|
|
1364
|
+
return true;
|
|
1365
|
+
} catch {
|
|
1366
|
+
try {
|
|
1367
|
+
unlinkSync2(pidPath);
|
|
1368
|
+
} catch {
|
|
1369
|
+
}
|
|
1370
|
+
return false;
|
|
1371
|
+
} finally {
|
|
1372
|
+
try {
|
|
1373
|
+
closeSync2(fd);
|
|
1374
|
+
} catch {
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
1377
|
+
}
|
|
1378
|
+
function maybeCleanupOwnPlaceholder(pidPath) {
|
|
1379
|
+
const existing = readPidFile(pidPath);
|
|
1380
|
+
if (existing === process.pid || existing === "empty") {
|
|
1381
|
+
try {
|
|
1382
|
+
unlinkSync2(pidPath);
|
|
1383
|
+
} catch {
|
|
1384
|
+
}
|
|
1385
|
+
}
|
|
1386
|
+
}
|
|
1387
|
+
async function waitForSocket(socketPath, deadline, connectTimeoutMs) {
|
|
1388
|
+
let delay = 30;
|
|
1389
|
+
while (Date.now() < deadline) {
|
|
1390
|
+
await new Promise((r) => setTimeout(r, delay));
|
|
1391
|
+
delay = Math.min(delay * 1.5, 300);
|
|
1392
|
+
if (!existsSync(socketPath)) continue;
|
|
1393
|
+
try {
|
|
1394
|
+
return await connectOnce(socketPath, connectTimeoutMs);
|
|
1395
|
+
} catch {
|
|
1396
|
+
}
|
|
1397
|
+
}
|
|
1398
|
+
return null;
|
|
1399
|
+
}
|
|
1400
|
+
async function tryEmbedStandalone(text, kind, opts = {}) {
|
|
1401
|
+
const socketDir = opts.socketDir ?? "/tmp";
|
|
1402
|
+
const daemonEntry = opts.daemonEntry ?? SHARED_DAEMON_PATH;
|
|
1403
|
+
const requestTimeoutMs = opts.requestTimeoutMs ?? DEFAULT_CLIENT_TIMEOUT_MS;
|
|
1404
|
+
const spawnWaitMs = opts.spawnWaitMs ?? 5e3;
|
|
1405
|
+
const uid = getUid();
|
|
1406
|
+
const socketPath = socketPathFor(uid, socketDir);
|
|
1407
|
+
const pidPath = pidPathFor(uid, socketDir);
|
|
1408
|
+
let sock = null;
|
|
1409
|
+
try {
|
|
1410
|
+
sock = await connectOnce(socketPath, requestTimeoutMs);
|
|
1411
|
+
} catch {
|
|
1412
|
+
}
|
|
1413
|
+
if (!sock) {
|
|
1414
|
+
if (!existsSync(daemonEntry)) {
|
|
1415
|
+
return null;
|
|
1416
|
+
}
|
|
1417
|
+
trySpawnDaemon(daemonEntry, pidPath);
|
|
1418
|
+
const deadline = Date.now() + spawnWaitMs;
|
|
1419
|
+
sock = await waitForSocket(socketPath, deadline, requestTimeoutMs);
|
|
1420
|
+
if (!sock) {
|
|
1421
|
+
maybeCleanupOwnPlaceholder(pidPath);
|
|
1422
|
+
return null;
|
|
1423
|
+
}
|
|
1424
|
+
}
|
|
1425
|
+
try {
|
|
1426
|
+
const req = { op: "embed", id: "1", kind, text };
|
|
1427
|
+
const resp = await sendAndWait(sock, req, requestTimeoutMs);
|
|
1428
|
+
if (resp.error || !resp.embedding || !Array.isArray(resp.embedding)) {
|
|
1429
|
+
return null;
|
|
1430
|
+
}
|
|
1431
|
+
for (const v of resp.embedding) {
|
|
1432
|
+
if (typeof v !== "number" || !Number.isFinite(v)) return null;
|
|
1433
|
+
}
|
|
1434
|
+
return resp.embedding;
|
|
1435
|
+
} catch {
|
|
1436
|
+
return null;
|
|
1437
|
+
} finally {
|
|
1438
|
+
try {
|
|
1439
|
+
sock.end();
|
|
1440
|
+
} catch {
|
|
1441
|
+
}
|
|
1442
|
+
}
|
|
1443
|
+
}
|
|
1444
|
+
function _setSpawnImpl(fn) {
|
|
1445
|
+
_spawn = fn ?? spawn;
|
|
1446
|
+
}
|
|
1447
|
+
|
|
1448
|
+
// src/embeddings/sql.ts
|
|
1449
|
+
function embeddingSqlLiteral(vec) {
|
|
1450
|
+
if (!vec || vec.length === 0) return "NULL";
|
|
1451
|
+
const parts = [];
|
|
1452
|
+
for (const v of vec) {
|
|
1453
|
+
if (!Number.isFinite(v)) return "NULL";
|
|
1454
|
+
parts.push(String(v));
|
|
1455
|
+
}
|
|
1456
|
+
return `ARRAY[${parts.join(",")}]::float4[]`;
|
|
1457
|
+
}
|
|
1458
|
+
|
|
1156
1459
|
// openclaw/src/index.ts
|
|
1157
1460
|
import { fileURLToPath } from "node:url";
|
|
1158
1461
|
import { join as joinPath, dirname as dirnamePath } from "node:path";
|
|
1159
|
-
import { homedir as
|
|
1462
|
+
import { homedir as homedir4, tmpdir } from "node:os";
|
|
1160
1463
|
import {
|
|
1161
1464
|
existsSync as fsExists,
|
|
1162
1465
|
mkdirSync as fsMkdir,
|
|
@@ -1202,6 +1505,7 @@ async function loadConfig() {
|
|
|
1202
1505
|
}
|
|
1203
1506
|
var requireFromOpenclaw = createRequire(import.meta.url);
|
|
1204
1507
|
var { spawn: realSpawn, execFileSync: realExecFileSync } = requireFromOpenclaw("node:child_process");
|
|
1508
|
+
_setSpawnImpl(realSpawn);
|
|
1205
1509
|
var inheritedEnv = process;
|
|
1206
1510
|
function applyOpenclawTuning(pluginConfig) {
|
|
1207
1511
|
const cfg = pluginConfig ?? {};
|
|
@@ -1239,7 +1543,7 @@ function extractLatestVersion(body) {
|
|
|
1239
1543
|
return typeof v === "string" && v.length > 0 ? v : null;
|
|
1240
1544
|
}
|
|
1241
1545
|
function getInstalledVersion() {
|
|
1242
|
-
return "0.7.
|
|
1546
|
+
return "0.7.36".length > 0 ? "0.7.36" : null;
|
|
1243
1547
|
}
|
|
1244
1548
|
function isNewer(latest, current) {
|
|
1245
1549
|
const parse = (v) => v.replace(/-.*$/, "").split(".").map(Number);
|
|
@@ -1346,8 +1650,8 @@ var skillifySpawnedFor = /* @__PURE__ */ new Set();
|
|
|
1346
1650
|
var __openclaw_filename = fileURLToPath(import.meta.url);
|
|
1347
1651
|
var __openclaw_dirname = dirnamePath(__openclaw_filename);
|
|
1348
1652
|
var OPENCLAW_SKILLIFY_WORKER_PATH = joinPath(__openclaw_dirname, "skillify-worker.js");
|
|
1349
|
-
var OPENCLAW_SKILLIFY_STATE_DIR = joinPath(
|
|
1350
|
-
var OPENCLAW_SKILLIFY_LEGACY_STATE_DIR = joinPath(
|
|
1653
|
+
var OPENCLAW_SKILLIFY_STATE_DIR = joinPath(homedir4(), ".deeplake", "state", "skillify");
|
|
1654
|
+
var OPENCLAW_SKILLIFY_LEGACY_STATE_DIR = joinPath(homedir4(), ".deeplake", "state", "skilify");
|
|
1351
1655
|
var openclawSkillifyMigrationAttempted = false;
|
|
1352
1656
|
function migrateOpenclawSkillifyLegacyStateDir() {
|
|
1353
1657
|
if (openclawSkillifyMigrationAttempted) return;
|
|
@@ -1463,7 +1767,7 @@ function spawnOpenclawSkillifyWorker(a) {
|
|
|
1463
1767
|
sessionsTable,
|
|
1464
1768
|
skillsTable,
|
|
1465
1769
|
userName: a.userName,
|
|
1466
|
-
cwd:
|
|
1770
|
+
cwd: homedir4(),
|
|
1467
1771
|
// sentinel — only used by worker if install=project
|
|
1468
1772
|
projectKey,
|
|
1469
1773
|
project,
|
|
@@ -1479,7 +1783,7 @@ function spawnOpenclawSkillifyWorker(a) {
|
|
|
1479
1783
|
cursorModel: void 0,
|
|
1480
1784
|
hermesProvider: void 0,
|
|
1481
1785
|
hermesModel: void 0,
|
|
1482
|
-
skillifyLog: joinPath(
|
|
1786
|
+
skillifyLog: joinPath(homedir4(), ".deeplake", "hivemind-openclaw-skillify.log"),
|
|
1483
1787
|
currentSessionId: a.sessionId,
|
|
1484
1788
|
// Pass the tuning dispatch through so the worker can repopulate its
|
|
1485
1789
|
// own globalThis (each process has its own globalThis). The worker
|
|
@@ -2018,7 +2322,9 @@ One brain for every agent on your team.
|
|
|
2018
2322
|
};
|
|
2019
2323
|
const line = JSON.stringify(entry);
|
|
2020
2324
|
const jsonForSql = line.replace(/'/g, "''");
|
|
2021
|
-
const
|
|
2325
|
+
const embedding = await tryEmbedStandalone(line, "document");
|
|
2326
|
+
const embeddingSql = embeddingSqlLiteral(embedding);
|
|
2327
|
+
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(cfg.userName)}', ${Buffer.byteLength(line, "utf-8")}, '${sqlStr(projectName)}', '${sqlStr(msg.role)}', 'openclaw', '${sqlStr(getInstalledVersion() ?? "")}', '${ts}', '${ts}')`;
|
|
2022
2328
|
try {
|
|
2023
2329
|
await dl.query(insertSql);
|
|
2024
2330
|
} catch (e) {
|