@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
|
@@ -6,13 +6,13 @@
|
|
|
6
6
|
},
|
|
7
7
|
"metadata": {
|
|
8
8
|
"description": "Cloud-backed persistent shared memory for AI agents powered by Deeplake",
|
|
9
|
-
"version": "0.7.
|
|
9
|
+
"version": "0.7.36"
|
|
10
10
|
},
|
|
11
11
|
"plugins": [
|
|
12
12
|
{
|
|
13
13
|
"name": "hivemind",
|
|
14
14
|
"description": "Persistent shared memory powered by Deeplake — captures all session activity and provides cross-session, cross-agent memory search",
|
|
15
|
-
"version": "0.7.
|
|
15
|
+
"version": "0.7.36",
|
|
16
16
|
"source": "./claude-code",
|
|
17
17
|
"homepage": "https://github.com/activeloopai/hivemind"
|
|
18
18
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hivemind",
|
|
3
3
|
"description": "Cloud-backed persistent memory powered by Deeplake — read, write, and share memory across Claude Code sessions and agents",
|
|
4
|
-
"version": "0.7.
|
|
4
|
+
"version": "0.7.36",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Activeloop",
|
|
7
7
|
"url": "https://deeplake.ai"
|
package/bundle/cli.js
CHANGED
|
@@ -4341,7 +4341,123 @@ function sqlIdent(name) {
|
|
|
4341
4341
|
|
|
4342
4342
|
// dist/src/embeddings/columns.js
|
|
4343
4343
|
var SUMMARY_EMBEDDING_COL = "summary_embedding";
|
|
4344
|
-
|
|
4344
|
+
|
|
4345
|
+
// dist/src/deeplake-schema.js
|
|
4346
|
+
var MEMORY_COLUMNS = Object.freeze([
|
|
4347
|
+
{ name: "id", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
4348
|
+
{ name: "path", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
4349
|
+
{ name: "filename", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
4350
|
+
{ name: "summary", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
4351
|
+
{ name: "summary_embedding", sql: "FLOAT4[]" },
|
|
4352
|
+
{ name: "author", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
4353
|
+
{ name: "mime_type", sql: "TEXT NOT NULL DEFAULT 'text/plain'" },
|
|
4354
|
+
{ name: "size_bytes", sql: "BIGINT NOT NULL DEFAULT 0" },
|
|
4355
|
+
{ name: "project", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
4356
|
+
{ name: "description", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
4357
|
+
{ name: "agent", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
4358
|
+
{ name: "plugin_version", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
4359
|
+
{ name: "creation_date", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
4360
|
+
{ name: "last_update_date", sql: "TEXT NOT NULL DEFAULT ''" }
|
|
4361
|
+
]);
|
|
4362
|
+
var SESSIONS_COLUMNS = Object.freeze([
|
|
4363
|
+
{ name: "id", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
4364
|
+
{ name: "path", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
4365
|
+
{ name: "filename", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
4366
|
+
{ name: "message", sql: "JSONB" },
|
|
4367
|
+
{ name: "message_embedding", sql: "FLOAT4[]" },
|
|
4368
|
+
{ name: "author", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
4369
|
+
{ name: "mime_type", sql: "TEXT NOT NULL DEFAULT 'application/json'" },
|
|
4370
|
+
{ name: "size_bytes", sql: "BIGINT NOT NULL DEFAULT 0" },
|
|
4371
|
+
{ name: "project", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
4372
|
+
{ name: "description", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
4373
|
+
{ name: "agent", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
4374
|
+
{ name: "plugin_version", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
4375
|
+
{ name: "creation_date", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
4376
|
+
{ name: "last_update_date", sql: "TEXT NOT NULL DEFAULT ''" }
|
|
4377
|
+
]);
|
|
4378
|
+
var SKILLS_COLUMNS = Object.freeze([
|
|
4379
|
+
{ name: "id", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
4380
|
+
{ name: "name", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
4381
|
+
{ name: "project", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
4382
|
+
{ name: "project_key", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
4383
|
+
{ name: "local_path", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
4384
|
+
{ name: "install", sql: "TEXT NOT NULL DEFAULT 'project'" },
|
|
4385
|
+
{ name: "source_sessions", sql: "TEXT NOT NULL DEFAULT '[]'" },
|
|
4386
|
+
{ name: "source_agent", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
4387
|
+
{ name: "scope", sql: "TEXT NOT NULL DEFAULT 'me'" },
|
|
4388
|
+
{ name: "author", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
4389
|
+
{ name: "contributors", sql: "TEXT NOT NULL DEFAULT '[]'" },
|
|
4390
|
+
{ name: "description", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
4391
|
+
{ name: "trigger_text", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
4392
|
+
{ name: "body", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
4393
|
+
{ name: "version", sql: "BIGINT NOT NULL DEFAULT 1" },
|
|
4394
|
+
{ name: "created_at", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
4395
|
+
{ name: "updated_at", sql: "TEXT NOT NULL DEFAULT ''" }
|
|
4396
|
+
]);
|
|
4397
|
+
function validateSchema(label, cols) {
|
|
4398
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4399
|
+
for (const col of cols) {
|
|
4400
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(col.name)) {
|
|
4401
|
+
throw new Error(`${label}: column name "${col.name}" is not a valid SQL identifier`);
|
|
4402
|
+
}
|
|
4403
|
+
if (seen.has(col.name)) {
|
|
4404
|
+
throw new Error(`${label}: duplicate column "${col.name}"`);
|
|
4405
|
+
}
|
|
4406
|
+
seen.add(col.name);
|
|
4407
|
+
const notNull = /\bNOT\s+NULL\b/i.test(col.sql);
|
|
4408
|
+
const hasDefault = /\bDEFAULT\b/i.test(col.sql);
|
|
4409
|
+
if (notNull && !hasDefault) {
|
|
4410
|
+
throw new Error(`${label}: column "${col.name}" is NOT NULL but has no DEFAULT \u2014 ALTER TABLE ADD COLUMN on a populated table would fail.`);
|
|
4411
|
+
}
|
|
4412
|
+
}
|
|
4413
|
+
}
|
|
4414
|
+
validateSchema("MEMORY_COLUMNS", MEMORY_COLUMNS);
|
|
4415
|
+
validateSchema("SESSIONS_COLUMNS", SESSIONS_COLUMNS);
|
|
4416
|
+
validateSchema("SKILLS_COLUMNS", SKILLS_COLUMNS);
|
|
4417
|
+
function buildCreateTableSql(tableName, cols) {
|
|
4418
|
+
const safe = sqlIdent(tableName);
|
|
4419
|
+
const colSql = cols.map((c) => `${c.name} ${c.sql}`).join(", ");
|
|
4420
|
+
return `CREATE TABLE IF NOT EXISTS "${safe}" (${colSql}) USING deeplake`;
|
|
4421
|
+
}
|
|
4422
|
+
function buildIntrospectionSql(tableName, workspaceId) {
|
|
4423
|
+
return `SELECT column_name FROM information_schema.columns WHERE table_name = '${sqlStr(tableName)}' AND table_schema = '${sqlStr(workspaceId)}'`;
|
|
4424
|
+
}
|
|
4425
|
+
async function healMissingColumns(args) {
|
|
4426
|
+
const safeTable = sqlIdent(args.tableName);
|
|
4427
|
+
const introspectSql = buildIntrospectionSql(args.tableName, args.workspaceId);
|
|
4428
|
+
const rows = await args.query(introspectSql);
|
|
4429
|
+
const existing = /* @__PURE__ */ new Set();
|
|
4430
|
+
for (const row of rows) {
|
|
4431
|
+
const v = row?.column_name;
|
|
4432
|
+
if (typeof v === "string")
|
|
4433
|
+
existing.add(v.toLowerCase());
|
|
4434
|
+
}
|
|
4435
|
+
const missingCols = args.columns.filter((c) => !existing.has(c.name.toLowerCase()));
|
|
4436
|
+
const missing = missingCols.map((c) => c.name);
|
|
4437
|
+
if (missingCols.length === 0)
|
|
4438
|
+
return { missing, altered: [] };
|
|
4439
|
+
const altered = [];
|
|
4440
|
+
for (const col of missingCols) {
|
|
4441
|
+
try {
|
|
4442
|
+
await args.query(`ALTER TABLE "${safeTable}" ADD COLUMN ${col.name} ${col.sql}`);
|
|
4443
|
+
altered.push(col.name);
|
|
4444
|
+
args.log?.(`schema-heal: added "${args.tableName}"."${col.name}"`);
|
|
4445
|
+
} catch (e) {
|
|
4446
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
4447
|
+
if (!/already exists/i.test(msg))
|
|
4448
|
+
throw e;
|
|
4449
|
+
const recheck = await args.query(introspectSql);
|
|
4450
|
+
const present = recheck.some((r) => {
|
|
4451
|
+
const v = r?.column_name;
|
|
4452
|
+
return typeof v === "string" && v.toLowerCase() === col.name.toLowerCase();
|
|
4453
|
+
});
|
|
4454
|
+
if (!present)
|
|
4455
|
+
throw e;
|
|
4456
|
+
args.log?.(`schema-heal: "${args.tableName}"."${col.name}" appeared via race, treating as success`);
|
|
4457
|
+
}
|
|
4458
|
+
}
|
|
4459
|
+
return { missing, altered };
|
|
4460
|
+
}
|
|
4345
4461
|
|
|
4346
4462
|
// dist/src/notifications/queue.js
|
|
4347
4463
|
import { readFileSync as readFileSync12, writeFileSync as writeFileSync8, renameSync as renameSync3, mkdirSync as mkdirSync4, openSync, closeSync, unlinkSync as unlinkSync7, statSync as statSync2 } from "node:fs";
|
|
@@ -4707,64 +4823,33 @@ var DeeplakeApi = class {
|
|
|
4707
4823
|
}
|
|
4708
4824
|
}
|
|
4709
4825
|
/**
|
|
4710
|
-
*
|
|
4711
|
-
*
|
|
4712
|
-
*
|
|
4713
|
-
*
|
|
4714
|
-
*
|
|
4715
|
-
* in the log and a wasted round-trip. Worse, the very first call after the
|
|
4716
|
-
* column is genuinely added triggers Deeplake's post-ALTER `vector::at`
|
|
4717
|
-
* window (~30s) during which subsequent INSERTs fail; minimising the
|
|
4718
|
-
* number of ALTER calls minimises exposure to that window.
|
|
4826
|
+
* Heal any missing columns on a table so it matches one of the schema
|
|
4827
|
+
* definitions in `deeplake-schema.ts`. One SELECT against
|
|
4828
|
+
* `information_schema.columns` per call, then `ALTER TABLE ADD COLUMN`
|
|
4829
|
+
* only the genuinely missing ones — never blanket, never `IF NOT
|
|
4830
|
+
* EXISTS`.
|
|
4719
4831
|
*
|
|
4720
|
-
*
|
|
4721
|
-
*
|
|
4722
|
-
*
|
|
4723
|
-
*
|
|
4724
|
-
*
|
|
4725
|
-
*
|
|
4726
|
-
*
|
|
4727
|
-
*
|
|
4728
|
-
*
|
|
4729
|
-
*
|
|
4730
|
-
*
|
|
4731
|
-
*
|
|
4732
|
-
* caches share an opt-out (HIVEMIND_INDEX_MARKER_DIR) and a TTL knob.
|
|
4832
|
+
* History: an earlier path used a local marker file (`col_<name>` under
|
|
4833
|
+
* the index-marker dir) to skip even the SELECT after the first
|
|
4834
|
+
* confirmation, plus per-column ALTERs for `summary_embedding`,
|
|
4835
|
+
* `message_embedding`, `agent`, `plugin_version`. The marker existed
|
|
4836
|
+
* because Deeplake used to expose a ~30s post-ALTER bug where
|
|
4837
|
+
* subsequent INSERTs failed, so we wanted to keep ALTER traffic to a
|
|
4838
|
+
* minimum. The bug was re-verified on 2026-05-18 against
|
|
4839
|
+
* `api.deeplake.ai` (`test_plugin` org) and no longer reproduces
|
|
4840
|
+
* (71/71 INSERTs OK, first success 2ms after ALTER). The single SELECT
|
|
4841
|
+
* + targeted ALTER pattern survives the marker removal because: each
|
|
4842
|
+
* ALTER still costs ~800ms (so blanket sweeps are wasteful) and the
|
|
4843
|
+
* diff produces clearer logs than "ALTER all with IF NOT EXISTS".
|
|
4733
4844
|
*/
|
|
4734
|
-
async
|
|
4735
|
-
await
|
|
4736
|
-
|
|
4737
|
-
|
|
4738
|
-
|
|
4739
|
-
|
|
4740
|
-
|
|
4741
|
-
|
|
4742
|
-
* the `agent` column (added 2026-04-11) — the latter has no fallback if
|
|
4743
|
-
* a user upgraded over a pre-2026-04-11 table, so every INSERT fails
|
|
4744
|
-
* with `column "agent" does not exist`.
|
|
4745
|
-
*/
|
|
4746
|
-
async ensureColumn(table, column, sqlType) {
|
|
4747
|
-
const markers = await getIndexMarkerStore();
|
|
4748
|
-
const markerPath = markers.buildIndexMarkerPath(this.workspaceId, this.orgId, table, `col_${column}`);
|
|
4749
|
-
if (markers.hasFreshIndexMarker(markerPath))
|
|
4750
|
-
return;
|
|
4751
|
-
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`;
|
|
4752
|
-
const rows = await this.query(colCheck);
|
|
4753
|
-
if (rows.length > 0) {
|
|
4754
|
-
markers.writeIndexMarker(markerPath);
|
|
4755
|
-
return;
|
|
4756
|
-
}
|
|
4757
|
-
try {
|
|
4758
|
-
await this.query(`ALTER TABLE "${table}" ADD COLUMN ${column} ${sqlType}`);
|
|
4759
|
-
} catch (e) {
|
|
4760
|
-
const msg = e instanceof Error ? e.message : String(e);
|
|
4761
|
-
if (!/already exists/i.test(msg))
|
|
4762
|
-
throw e;
|
|
4763
|
-
const recheck = await this.query(colCheck);
|
|
4764
|
-
if (recheck.length === 0)
|
|
4765
|
-
throw e;
|
|
4766
|
-
}
|
|
4767
|
-
markers.writeIndexMarker(markerPath);
|
|
4845
|
+
async healSchema(table, columns) {
|
|
4846
|
+
await healMissingColumns({
|
|
4847
|
+
query: (sql) => this.query(sql),
|
|
4848
|
+
tableName: table,
|
|
4849
|
+
workspaceId: this.workspaceId,
|
|
4850
|
+
columns,
|
|
4851
|
+
log: log4
|
|
4852
|
+
});
|
|
4768
4853
|
}
|
|
4769
4854
|
/** List all tables in the workspace (with retry). */
|
|
4770
4855
|
async listTables(forceRefresh = false) {
|
|
@@ -4835,20 +4920,21 @@ var DeeplakeApi = class {
|
|
|
4835
4920
|
}
|
|
4836
4921
|
throw lastErr;
|
|
4837
4922
|
}
|
|
4838
|
-
/** Create the memory table if it doesn't already exist.
|
|
4923
|
+
/** Create the memory table if it doesn't already exist. Heal missing columns on existing tables. */
|
|
4839
4924
|
async ensureTable(name) {
|
|
4925
|
+
if (!MEMORY_COLUMNS.some((c) => c.name === SUMMARY_EMBEDDING_COL)) {
|
|
4926
|
+
throw new Error(`MEMORY_COLUMNS missing "${SUMMARY_EMBEDDING_COL}" (embeddings/columns.ts drift)`);
|
|
4927
|
+
}
|
|
4840
4928
|
const tbl = sqlIdent(name ?? this.tableName);
|
|
4841
4929
|
const tables = await this.listTables();
|
|
4842
4930
|
if (!tables.includes(tbl)) {
|
|
4843
4931
|
log4(`table "${tbl}" not found, creating`);
|
|
4844
|
-
await this.createTableWithRetry(
|
|
4932
|
+
await this.createTableWithRetry(buildCreateTableSql(tbl, MEMORY_COLUMNS), tbl);
|
|
4845
4933
|
log4(`table "${tbl}" created`);
|
|
4846
4934
|
if (!tables.includes(tbl))
|
|
4847
4935
|
this._tablesCache = [...tables, tbl];
|
|
4848
4936
|
}
|
|
4849
|
-
await this.
|
|
4850
|
-
await this.ensureColumn(tbl, "agent", "TEXT NOT NULL DEFAULT ''");
|
|
4851
|
-
await this.ensureColumn(tbl, "plugin_version", "TEXT NOT NULL DEFAULT ''");
|
|
4937
|
+
await this.healSchema(tbl, MEMORY_COLUMNS);
|
|
4852
4938
|
}
|
|
4853
4939
|
/** Create the sessions table (uses JSONB for message since every row is a JSON event). */
|
|
4854
4940
|
async ensureSessionsTable(name) {
|
|
@@ -4856,14 +4942,12 @@ var DeeplakeApi = class {
|
|
|
4856
4942
|
const tables = await this.listTables();
|
|
4857
4943
|
if (!tables.includes(safe)) {
|
|
4858
4944
|
log4(`table "${safe}" not found, creating`);
|
|
4859
|
-
await this.createTableWithRetry(
|
|
4945
|
+
await this.createTableWithRetry(buildCreateTableSql(safe, SESSIONS_COLUMNS), safe);
|
|
4860
4946
|
log4(`table "${safe}" created`);
|
|
4861
4947
|
if (!tables.includes(safe))
|
|
4862
4948
|
this._tablesCache = [...tables, safe];
|
|
4863
4949
|
}
|
|
4864
|
-
await this.
|
|
4865
|
-
await this.ensureColumn(safe, "agent", "TEXT NOT NULL DEFAULT ''");
|
|
4866
|
-
await this.ensureColumn(safe, "plugin_version", "TEXT NOT NULL DEFAULT ''");
|
|
4950
|
+
await this.healSchema(safe, SESSIONS_COLUMNS);
|
|
4867
4951
|
await this.ensureLookupIndex(safe, "path_creation_date", `("path", "creation_date")`);
|
|
4868
4952
|
}
|
|
4869
4953
|
/**
|
|
@@ -4881,11 +4965,12 @@ var DeeplakeApi = class {
|
|
|
4881
4965
|
const tables = await this.listTables();
|
|
4882
4966
|
if (!tables.includes(safe)) {
|
|
4883
4967
|
log4(`table "${safe}" not found, creating`);
|
|
4884
|
-
await this.createTableWithRetry(
|
|
4968
|
+
await this.createTableWithRetry(buildCreateTableSql(safe, SKILLS_COLUMNS), safe);
|
|
4885
4969
|
log4(`table "${safe}" created`);
|
|
4886
4970
|
if (!tables.includes(safe))
|
|
4887
4971
|
this._tablesCache = [...tables, safe];
|
|
4888
4972
|
}
|
|
4973
|
+
await this.healSchema(safe, SKILLS_COLUMNS);
|
|
4889
4974
|
await this.ensureLookupIndex(safe, "project_key_name", `("project_key", "name")`);
|
|
4890
4975
|
}
|
|
4891
4976
|
};
|
package/codex/bundle/capture.js
CHANGED
|
@@ -136,7 +136,6 @@ function sqlIdent(name) {
|
|
|
136
136
|
|
|
137
137
|
// dist/src/embeddings/columns.js
|
|
138
138
|
var SUMMARY_EMBEDDING_COL = "summary_embedding";
|
|
139
|
-
var MESSAGE_EMBEDDING_COL = "message_embedding";
|
|
140
139
|
|
|
141
140
|
// dist/src/utils/client-header.js
|
|
142
141
|
var DEEPLAKE_CLIENT_HEADER = "X-Deeplake-Client";
|
|
@@ -147,6 +146,123 @@ function deeplakeClientHeader() {
|
|
|
147
146
|
return { [DEEPLAKE_CLIENT_HEADER]: deeplakeClientValue() };
|
|
148
147
|
}
|
|
149
148
|
|
|
149
|
+
// dist/src/deeplake-schema.js
|
|
150
|
+
var MEMORY_COLUMNS = Object.freeze([
|
|
151
|
+
{ name: "id", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
152
|
+
{ name: "path", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
153
|
+
{ name: "filename", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
154
|
+
{ name: "summary", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
155
|
+
{ name: "summary_embedding", sql: "FLOAT4[]" },
|
|
156
|
+
{ name: "author", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
157
|
+
{ name: "mime_type", sql: "TEXT NOT NULL DEFAULT 'text/plain'" },
|
|
158
|
+
{ name: "size_bytes", sql: "BIGINT NOT NULL DEFAULT 0" },
|
|
159
|
+
{ name: "project", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
160
|
+
{ name: "description", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
161
|
+
{ name: "agent", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
162
|
+
{ name: "plugin_version", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
163
|
+
{ name: "creation_date", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
164
|
+
{ name: "last_update_date", sql: "TEXT NOT NULL DEFAULT ''" }
|
|
165
|
+
]);
|
|
166
|
+
var SESSIONS_COLUMNS = Object.freeze([
|
|
167
|
+
{ name: "id", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
168
|
+
{ name: "path", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
169
|
+
{ name: "filename", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
170
|
+
{ name: "message", sql: "JSONB" },
|
|
171
|
+
{ name: "message_embedding", sql: "FLOAT4[]" },
|
|
172
|
+
{ name: "author", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
173
|
+
{ name: "mime_type", sql: "TEXT NOT NULL DEFAULT 'application/json'" },
|
|
174
|
+
{ name: "size_bytes", sql: "BIGINT NOT NULL DEFAULT 0" },
|
|
175
|
+
{ name: "project", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
176
|
+
{ name: "description", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
177
|
+
{ name: "agent", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
178
|
+
{ name: "plugin_version", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
179
|
+
{ name: "creation_date", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
180
|
+
{ name: "last_update_date", sql: "TEXT NOT NULL DEFAULT ''" }
|
|
181
|
+
]);
|
|
182
|
+
var SKILLS_COLUMNS = Object.freeze([
|
|
183
|
+
{ name: "id", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
184
|
+
{ name: "name", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
185
|
+
{ name: "project", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
186
|
+
{ name: "project_key", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
187
|
+
{ name: "local_path", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
188
|
+
{ name: "install", sql: "TEXT NOT NULL DEFAULT 'project'" },
|
|
189
|
+
{ name: "source_sessions", sql: "TEXT NOT NULL DEFAULT '[]'" },
|
|
190
|
+
{ name: "source_agent", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
191
|
+
{ name: "scope", sql: "TEXT NOT NULL DEFAULT 'me'" },
|
|
192
|
+
{ name: "author", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
193
|
+
{ name: "contributors", sql: "TEXT NOT NULL DEFAULT '[]'" },
|
|
194
|
+
{ name: "description", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
195
|
+
{ name: "trigger_text", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
196
|
+
{ name: "body", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
197
|
+
{ name: "version", sql: "BIGINT NOT NULL DEFAULT 1" },
|
|
198
|
+
{ name: "created_at", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
199
|
+
{ name: "updated_at", sql: "TEXT NOT NULL DEFAULT ''" }
|
|
200
|
+
]);
|
|
201
|
+
function validateSchema(label, cols) {
|
|
202
|
+
const seen = /* @__PURE__ */ new Set();
|
|
203
|
+
for (const col of cols) {
|
|
204
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(col.name)) {
|
|
205
|
+
throw new Error(`${label}: column name "${col.name}" is not a valid SQL identifier`);
|
|
206
|
+
}
|
|
207
|
+
if (seen.has(col.name)) {
|
|
208
|
+
throw new Error(`${label}: duplicate column "${col.name}"`);
|
|
209
|
+
}
|
|
210
|
+
seen.add(col.name);
|
|
211
|
+
const notNull = /\bNOT\s+NULL\b/i.test(col.sql);
|
|
212
|
+
const hasDefault = /\bDEFAULT\b/i.test(col.sql);
|
|
213
|
+
if (notNull && !hasDefault) {
|
|
214
|
+
throw new Error(`${label}: column "${col.name}" is NOT NULL but has no DEFAULT \u2014 ALTER TABLE ADD COLUMN on a populated table would fail.`);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
validateSchema("MEMORY_COLUMNS", MEMORY_COLUMNS);
|
|
219
|
+
validateSchema("SESSIONS_COLUMNS", SESSIONS_COLUMNS);
|
|
220
|
+
validateSchema("SKILLS_COLUMNS", SKILLS_COLUMNS);
|
|
221
|
+
function buildCreateTableSql(tableName, cols) {
|
|
222
|
+
const safe = sqlIdent(tableName);
|
|
223
|
+
const colSql = cols.map((c) => `${c.name} ${c.sql}`).join(", ");
|
|
224
|
+
return `CREATE TABLE IF NOT EXISTS "${safe}" (${colSql}) USING deeplake`;
|
|
225
|
+
}
|
|
226
|
+
function buildIntrospectionSql(tableName, workspaceId) {
|
|
227
|
+
return `SELECT column_name FROM information_schema.columns WHERE table_name = '${sqlStr(tableName)}' AND table_schema = '${sqlStr(workspaceId)}'`;
|
|
228
|
+
}
|
|
229
|
+
async function healMissingColumns(args) {
|
|
230
|
+
const safeTable = sqlIdent(args.tableName);
|
|
231
|
+
const introspectSql = buildIntrospectionSql(args.tableName, args.workspaceId);
|
|
232
|
+
const rows = await args.query(introspectSql);
|
|
233
|
+
const existing = /* @__PURE__ */ new Set();
|
|
234
|
+
for (const row of rows) {
|
|
235
|
+
const v = row?.column_name;
|
|
236
|
+
if (typeof v === "string")
|
|
237
|
+
existing.add(v.toLowerCase());
|
|
238
|
+
}
|
|
239
|
+
const missingCols = args.columns.filter((c) => !existing.has(c.name.toLowerCase()));
|
|
240
|
+
const missing = missingCols.map((c) => c.name);
|
|
241
|
+
if (missingCols.length === 0)
|
|
242
|
+
return { missing, altered: [] };
|
|
243
|
+
const altered = [];
|
|
244
|
+
for (const col of missingCols) {
|
|
245
|
+
try {
|
|
246
|
+
await args.query(`ALTER TABLE "${safeTable}" ADD COLUMN ${col.name} ${col.sql}`);
|
|
247
|
+
altered.push(col.name);
|
|
248
|
+
args.log?.(`schema-heal: added "${args.tableName}"."${col.name}"`);
|
|
249
|
+
} catch (e) {
|
|
250
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
251
|
+
if (!/already exists/i.test(msg))
|
|
252
|
+
throw e;
|
|
253
|
+
const recheck = await args.query(introspectSql);
|
|
254
|
+
const present = recheck.some((r) => {
|
|
255
|
+
const v = r?.column_name;
|
|
256
|
+
return typeof v === "string" && v.toLowerCase() === col.name.toLowerCase();
|
|
257
|
+
});
|
|
258
|
+
if (!present)
|
|
259
|
+
throw e;
|
|
260
|
+
args.log?.(`schema-heal: "${args.tableName}"."${col.name}" appeared via race, treating as success`);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
return { missing, altered };
|
|
264
|
+
}
|
|
265
|
+
|
|
150
266
|
// dist/src/notifications/queue.js
|
|
151
267
|
import { readFileSync as readFileSync2, writeFileSync, renameSync, mkdirSync, openSync, closeSync, unlinkSync, statSync } from "node:fs";
|
|
152
268
|
import { join as join3, resolve } from "node:path";
|
|
@@ -529,64 +645,33 @@ var DeeplakeApi = class {
|
|
|
529
645
|
}
|
|
530
646
|
}
|
|
531
647
|
/**
|
|
532
|
-
*
|
|
533
|
-
*
|
|
534
|
-
*
|
|
535
|
-
*
|
|
536
|
-
*
|
|
537
|
-
* in the log and a wasted round-trip. Worse, the very first call after the
|
|
538
|
-
* column is genuinely added triggers Deeplake's post-ALTER `vector::at`
|
|
539
|
-
* window (~30s) during which subsequent INSERTs fail; minimising the
|
|
540
|
-
* number of ALTER calls minimises exposure to that window.
|
|
541
|
-
*
|
|
542
|
-
* New flow:
|
|
543
|
-
* 1. Check the local marker file (mirrors ensureLookupIndex). If fresh,
|
|
544
|
-
* return — zero network calls.
|
|
545
|
-
* 2. SELECT 1 FROM information_schema.columns WHERE table_name = T AND
|
|
546
|
-
* column_name = C. Read-only, idempotent, can't tickle the post-ALTER
|
|
547
|
-
* bug. If the column is present → mark + return.
|
|
548
|
-
* 3. Only if step 2 says the column is missing, fall back to ALTER ADD
|
|
549
|
-
* COLUMN IF NOT EXISTS. Mark on success, also mark if Deeplake reports
|
|
550
|
-
* "already exists" (race: another client added it between our SELECT
|
|
551
|
-
* and ALTER).
|
|
648
|
+
* Heal any missing columns on a table so it matches one of the schema
|
|
649
|
+
* definitions in `deeplake-schema.ts`. One SELECT against
|
|
650
|
+
* `information_schema.columns` per call, then `ALTER TABLE ADD COLUMN`
|
|
651
|
+
* only the genuinely missing ones — never blanket, never `IF NOT
|
|
652
|
+
* EXISTS`.
|
|
552
653
|
*
|
|
553
|
-
*
|
|
554
|
-
*
|
|
654
|
+
* History: an earlier path used a local marker file (`col_<name>` under
|
|
655
|
+
* the index-marker dir) to skip even the SELECT after the first
|
|
656
|
+
* confirmation, plus per-column ALTERs for `summary_embedding`,
|
|
657
|
+
* `message_embedding`, `agent`, `plugin_version`. The marker existed
|
|
658
|
+
* because Deeplake used to expose a ~30s post-ALTER bug where
|
|
659
|
+
* subsequent INSERTs failed, so we wanted to keep ALTER traffic to a
|
|
660
|
+
* minimum. The bug was re-verified on 2026-05-18 against
|
|
661
|
+
* `api.deeplake.ai` (`test_plugin` org) and no longer reproduces
|
|
662
|
+
* (71/71 INSERTs OK, first success 2ms after ALTER). The single SELECT
|
|
663
|
+
* + targeted ALTER pattern survives the marker removal because: each
|
|
664
|
+
* ALTER still costs ~800ms (so blanket sweeps are wasteful) and the
|
|
665
|
+
* diff produces clearer logs than "ALTER all with IF NOT EXISTS".
|
|
555
666
|
*/
|
|
556
|
-
async
|
|
557
|
-
await
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
* the `agent` column (added 2026-04-11) — the latter has no fallback if
|
|
565
|
-
* a user upgraded over a pre-2026-04-11 table, so every INSERT fails
|
|
566
|
-
* with `column "agent" does not exist`.
|
|
567
|
-
*/
|
|
568
|
-
async ensureColumn(table, column, sqlType) {
|
|
569
|
-
const markers = await getIndexMarkerStore();
|
|
570
|
-
const markerPath = markers.buildIndexMarkerPath(this.workspaceId, this.orgId, table, `col_${column}`);
|
|
571
|
-
if (markers.hasFreshIndexMarker(markerPath))
|
|
572
|
-
return;
|
|
573
|
-
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`;
|
|
574
|
-
const rows = await this.query(colCheck);
|
|
575
|
-
if (rows.length > 0) {
|
|
576
|
-
markers.writeIndexMarker(markerPath);
|
|
577
|
-
return;
|
|
578
|
-
}
|
|
579
|
-
try {
|
|
580
|
-
await this.query(`ALTER TABLE "${table}" ADD COLUMN ${column} ${sqlType}`);
|
|
581
|
-
} catch (e) {
|
|
582
|
-
const msg = e instanceof Error ? e.message : String(e);
|
|
583
|
-
if (!/already exists/i.test(msg))
|
|
584
|
-
throw e;
|
|
585
|
-
const recheck = await this.query(colCheck);
|
|
586
|
-
if (recheck.length === 0)
|
|
587
|
-
throw e;
|
|
588
|
-
}
|
|
589
|
-
markers.writeIndexMarker(markerPath);
|
|
667
|
+
async healSchema(table, columns) {
|
|
668
|
+
await healMissingColumns({
|
|
669
|
+
query: (sql) => this.query(sql),
|
|
670
|
+
tableName: table,
|
|
671
|
+
workspaceId: this.workspaceId,
|
|
672
|
+
columns,
|
|
673
|
+
log: log3
|
|
674
|
+
});
|
|
590
675
|
}
|
|
591
676
|
/** List all tables in the workspace (with retry). */
|
|
592
677
|
async listTables(forceRefresh = false) {
|
|
@@ -657,20 +742,21 @@ var DeeplakeApi = class {
|
|
|
657
742
|
}
|
|
658
743
|
throw lastErr;
|
|
659
744
|
}
|
|
660
|
-
/** Create the memory table if it doesn't already exist.
|
|
745
|
+
/** Create the memory table if it doesn't already exist. Heal missing columns on existing tables. */
|
|
661
746
|
async ensureTable(name) {
|
|
747
|
+
if (!MEMORY_COLUMNS.some((c) => c.name === SUMMARY_EMBEDDING_COL)) {
|
|
748
|
+
throw new Error(`MEMORY_COLUMNS missing "${SUMMARY_EMBEDDING_COL}" (embeddings/columns.ts drift)`);
|
|
749
|
+
}
|
|
662
750
|
const tbl = sqlIdent(name ?? this.tableName);
|
|
663
751
|
const tables = await this.listTables();
|
|
664
752
|
if (!tables.includes(tbl)) {
|
|
665
753
|
log3(`table "${tbl}" not found, creating`);
|
|
666
|
-
await this.createTableWithRetry(
|
|
754
|
+
await this.createTableWithRetry(buildCreateTableSql(tbl, MEMORY_COLUMNS), tbl);
|
|
667
755
|
log3(`table "${tbl}" created`);
|
|
668
756
|
if (!tables.includes(tbl))
|
|
669
757
|
this._tablesCache = [...tables, tbl];
|
|
670
758
|
}
|
|
671
|
-
await this.
|
|
672
|
-
await this.ensureColumn(tbl, "agent", "TEXT NOT NULL DEFAULT ''");
|
|
673
|
-
await this.ensureColumn(tbl, "plugin_version", "TEXT NOT NULL DEFAULT ''");
|
|
759
|
+
await this.healSchema(tbl, MEMORY_COLUMNS);
|
|
674
760
|
}
|
|
675
761
|
/** Create the sessions table (uses JSONB for message since every row is a JSON event). */
|
|
676
762
|
async ensureSessionsTable(name) {
|
|
@@ -678,14 +764,12 @@ var DeeplakeApi = class {
|
|
|
678
764
|
const tables = await this.listTables();
|
|
679
765
|
if (!tables.includes(safe)) {
|
|
680
766
|
log3(`table "${safe}" not found, creating`);
|
|
681
|
-
await this.createTableWithRetry(
|
|
767
|
+
await this.createTableWithRetry(buildCreateTableSql(safe, SESSIONS_COLUMNS), safe);
|
|
682
768
|
log3(`table "${safe}" created`);
|
|
683
769
|
if (!tables.includes(safe))
|
|
684
770
|
this._tablesCache = [...tables, safe];
|
|
685
771
|
}
|
|
686
|
-
await this.
|
|
687
|
-
await this.ensureColumn(safe, "agent", "TEXT NOT NULL DEFAULT ''");
|
|
688
|
-
await this.ensureColumn(safe, "plugin_version", "TEXT NOT NULL DEFAULT ''");
|
|
772
|
+
await this.healSchema(safe, SESSIONS_COLUMNS);
|
|
689
773
|
await this.ensureLookupIndex(safe, "path_creation_date", `("path", "creation_date")`);
|
|
690
774
|
}
|
|
691
775
|
/**
|
|
@@ -703,11 +787,12 @@ var DeeplakeApi = class {
|
|
|
703
787
|
const tables = await this.listTables();
|
|
704
788
|
if (!tables.includes(safe)) {
|
|
705
789
|
log3(`table "${safe}" not found, creating`);
|
|
706
|
-
await this.createTableWithRetry(
|
|
790
|
+
await this.createTableWithRetry(buildCreateTableSql(safe, SKILLS_COLUMNS), safe);
|
|
707
791
|
log3(`table "${safe}" created`);
|
|
708
792
|
if (!tables.includes(safe))
|
|
709
793
|
this._tablesCache = [...tables, safe];
|
|
710
794
|
}
|
|
795
|
+
await this.healSchema(safe, SKILLS_COLUMNS);
|
|
711
796
|
await this.ensureLookupIndex(safe, "project_key_name", `("project_key", "name")`);
|
|
712
797
|
}
|
|
713
798
|
};
|