@deeplake/hivemind 0.7.21 → 0.7.23

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.
Files changed (37) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/README.md +1 -1
  4. package/bundle/cli.js +66 -15
  5. package/codex/bundle/capture.js +60 -12
  6. package/codex/bundle/commands/auth-login.js +4 -2
  7. package/codex/bundle/pre-tool-use.js +4 -2
  8. package/codex/bundle/session-start-setup.js +52 -5
  9. package/codex/bundle/session-start.js +62 -11
  10. package/codex/bundle/shell/deeplake-shell.js +4 -2
  11. package/codex/bundle/skillify-worker.js +118 -29
  12. package/codex/bundle/stop.js +100 -52
  13. package/codex/bundle/wiki-worker.js +7 -3
  14. package/cursor/bundle/capture.js +87 -39
  15. package/cursor/bundle/commands/auth-login.js +4 -2
  16. package/cursor/bundle/pre-tool-use.js +4 -2
  17. package/cursor/bundle/session-end.js +78 -34
  18. package/cursor/bundle/session-start.js +67 -15
  19. package/cursor/bundle/shell/deeplake-shell.js +4 -2
  20. package/cursor/bundle/skillify-worker.js +118 -29
  21. package/cursor/bundle/wiki-worker.js +7 -3
  22. package/hermes/bundle/capture.js +87 -39
  23. package/hermes/bundle/commands/auth-login.js +4 -2
  24. package/hermes/bundle/pre-tool-use.js +4 -2
  25. package/hermes/bundle/session-end.js +78 -34
  26. package/hermes/bundle/session-start.js +67 -15
  27. package/hermes/bundle/shell/deeplake-shell.js +4 -2
  28. package/hermes/bundle/skillify-worker.js +118 -29
  29. package/hermes/bundle/wiki-worker.js +7 -3
  30. package/mcp/bundle/server.js +4 -2
  31. package/openclaw/dist/index.js +8 -6
  32. package/openclaw/dist/skillify-worker.js +118 -29
  33. package/openclaw/openclaw.plugin.json +1 -1
  34. package/openclaw/package.json +1 -1
  35. package/openclaw/skills/SKILL.md +1 -1
  36. package/package.json +1 -1
  37. package/pi/extension-source/hivemind.ts +18 -3
@@ -90,9 +90,16 @@ function renderFrontmatter(fm) {
90
90
  lines.push(`description: ${JSON.stringify(fm.description)}`);
91
91
  if (fm.trigger)
92
92
  lines.push(`trigger: ${JSON.stringify(fm.trigger)}`);
93
+ if (fm.author)
94
+ lines.push(`author: ${fm.author}`);
93
95
  lines.push(`source_sessions:`);
94
96
  for (const s of fm.source_sessions)
95
97
  lines.push(` - ${s}`);
98
+ if (fm.contributors && fm.contributors.length > 0) {
99
+ lines.push(`contributors:`);
100
+ for (const c of fm.contributors)
101
+ lines.push(` - ${c}`);
102
+ }
96
103
  lines.push(`version: ${fm.version}`);
97
104
  lines.push(`created_by_agent: ${fm.created_by_agent}`);
98
105
  lines.push(`created_at: ${fm.created_at}`);
@@ -109,18 +116,25 @@ function parseFrontmatter(text) {
109
116
  const head = text.slice(4, end).trim();
110
117
  const body = text.slice(end + 4).replace(/^\r?\n/, "");
111
118
  const fm = { source_sessions: [] };
112
- let mode = "kv";
119
+ let arrayKey = null;
113
120
  for (const raw of head.split(/\r?\n/)) {
114
- if (mode === "sources") {
121
+ if (arrayKey) {
115
122
  const m2 = raw.match(/^\s+-\s+(.+)$/);
116
123
  if (m2) {
117
- fm.source_sessions.push(m2[1].trim());
124
+ const arr = fm[arrayKey] ?? [];
125
+ arr.push(m2[1].trim());
126
+ fm[arrayKey] = arr;
118
127
  continue;
119
128
  }
120
- mode = "kv";
129
+ arrayKey = null;
121
130
  }
122
131
  if (raw.startsWith("source_sessions:")) {
123
- mode = "sources";
132
+ arrayKey = "source_sessions";
133
+ continue;
134
+ }
135
+ if (raw.startsWith("contributors:")) {
136
+ arrayKey = "contributors";
137
+ fm.contributors = [];
124
138
  continue;
125
139
  }
126
140
  const m = raw.match(/^([a-zA-Z_]+):\s*(.*)$/);
@@ -151,11 +165,15 @@ function writeNewSkill(args) {
151
165
  }
152
166
  mkdirSync(dir, { recursive: true });
153
167
  const now = (/* @__PURE__ */ new Date()).toISOString();
168
+ const author = args.author && args.author.length > 0 ? args.author : void 0;
169
+ const contributors = author ? [author] : [];
154
170
  const fm = {
155
171
  name: args.name,
156
172
  description: args.description,
157
173
  trigger: args.trigger,
174
+ author,
158
175
  source_sessions: args.sourceSessions,
176
+ contributors,
159
177
  version: 1,
160
178
  created_by_agent: args.agent,
161
179
  created_at: now,
@@ -166,7 +184,15 @@ function writeNewSkill(args) {
166
184
  ${args.body.trim()}
167
185
  `;
168
186
  writeFileSync(path, text);
169
- return { path, action: "created", version: 1, createdAt: now, updatedAt: now };
187
+ return {
188
+ path,
189
+ action: "created",
190
+ version: 1,
191
+ createdAt: now,
192
+ updatedAt: now,
193
+ author,
194
+ contributors
195
+ };
170
196
  }
171
197
  function mergeSkill(args) {
172
198
  assertValidSkillName(args.name);
@@ -179,12 +205,20 @@ function mergeSkill(args) {
179
205
  const prevVersion = parsed?.fm.version ?? 1;
180
206
  const prevSources = parsed?.fm.source_sessions ?? [];
181
207
  const merged = Array.from(/* @__PURE__ */ new Set([...prevSources, ...args.newSourceSessions]));
208
+ const author = parsed?.fm.author;
209
+ const prevContribs = parsed?.fm.contributors && parsed.fm.contributors.length > 0 ? parsed.fm.contributors : author ? [author] : [];
210
+ const contributors = [...prevContribs];
211
+ if (args.editor && args.editor.length > 0 && !contributors.includes(args.editor)) {
212
+ contributors.push(args.editor);
213
+ }
182
214
  const now = (/* @__PURE__ */ new Date()).toISOString();
183
215
  const fm = {
184
216
  name: args.name,
185
217
  description: args.description ?? parsed?.fm.description ?? "",
186
218
  trigger: parsed?.fm.trigger,
219
+ author,
187
220
  source_sessions: merged,
221
+ contributors,
188
222
  version: prevVersion + 1,
189
223
  created_by_agent: parsed?.fm.created_by_agent ?? args.agent,
190
224
  created_at: parsed?.fm.created_at ?? now,
@@ -195,7 +229,15 @@ function mergeSkill(args) {
195
229
  ${args.body.trim()}
196
230
  `;
197
231
  writeFileSync(path, text);
198
- return { path, action: "merged", version: fm.version, createdAt: fm.created_at, updatedAt: fm.updated_at };
232
+ return {
233
+ path,
234
+ action: "merged",
235
+ version: fm.version,
236
+ createdAt: fm.created_at,
237
+ updatedAt: fm.updated_at,
238
+ author,
239
+ contributors
240
+ };
199
241
  }
200
242
  function listSkills(skillsRoot) {
201
243
  if (!existsSync(skillsRoot))
@@ -220,9 +262,14 @@ function resolveSkillsRoot(install, cwd) {
220
262
  function listAllExistingSkills(cwd) {
221
263
  const projectRoot = resolveSkillsRoot("project", cwd);
222
264
  const globalRoot = resolveSkillsRoot("global", cwd);
265
+ const tag = (source) => (s) => {
266
+ const parsed = parseFrontmatter(s.body);
267
+ const author = typeof parsed?.fm.author === "string" && parsed.fm.author.length > 0 ? parsed.fm.author : void 0;
268
+ return { name: s.name, body: s.body, source, author };
269
+ };
223
270
  const tagged = [
224
- ...listSkills(projectRoot).map((s) => ({ ...s, source: "project" })),
225
- ...listSkills(globalRoot).map((s) => ({ ...s, source: "global" }))
271
+ ...listSkills(projectRoot).map(tag("project")),
272
+ ...listSkills(globalRoot).map(tag("global"))
226
273
  ];
227
274
  const seen = /* @__PURE__ */ new Set();
228
275
  const out = [];
@@ -242,12 +289,13 @@ function renderExistingSkillsBlock(cwd, charCap) {
242
289
  block: "(no existing skills \u2014 MERGE is NOT a valid choice; pick KEEP or SKIP only)"
243
290
  };
244
291
  }
245
- const mergeTargetNames = skills.filter((s) => s.source === "project").map((s) => s.name);
246
292
  let total = 0;
247
293
  const out = [];
294
+ const mergeTargetNames = [];
248
295
  for (const s of skills) {
249
- const tag = s.source === "project" ? "[project]" : "[global, read-only]";
250
- const block = `--- existing skill ${tag}: ${s.name} ---
296
+ const sourceTag = s.source === "project" ? "project" : "global, read-only";
297
+ const authorTag = s.author ? `, author=${s.author}` : "";
298
+ const block = `--- existing skill [${sourceTag}${authorTag}]: ${s.name} ---
251
299
  ${s.body}
252
300
  `;
253
301
  if (total + block.length > charCap) {
@@ -256,6 +304,8 @@ ${s.body}
256
304
  }
257
305
  out.push(block);
258
306
  total += block.length;
307
+ if (s.source === "project")
308
+ mergeTargetNames.push(s.name);
259
309
  }
260
310
  return { mergeTargetNames, block: out.join("\n") };
261
311
  }
@@ -274,7 +324,11 @@ function sqlIdent(name) {
274
324
  // dist/src/skillify/skills-table.js
275
325
  function createSkillsTableSql(tableName) {
276
326
  const safe = sqlIdent(tableName);
277
- return `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`;
327
+ return `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 '', contributors 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`;
328
+ }
329
+ function addContributorsColumnSql(tableName) {
330
+ const safe = sqlIdent(tableName);
331
+ return `ALTER TABLE "${safe}" ADD COLUMN IF NOT EXISTS contributors TEXT NOT NULL DEFAULT '[]'`;
278
332
  }
279
333
  function esc(s) {
280
334
  return s.replace(/\\/g, "\\\\").replace(/'/g, "''").replace(/[\x01-\x08\x0b\x0c\x0e-\x1f\x7f]/g, "");
@@ -282,12 +336,20 @@ function esc(s) {
282
336
  function isMissingTableError(message) {
283
337
  if (!message)
284
338
  return false;
339
+ if (/\bcolumn\b/i.test(message))
340
+ return false;
285
341
  return /Table does not exist|relation .* does not exist|no such table/i.test(message);
286
342
  }
343
+ function isMissingContributorsColumnError(message) {
344
+ if (!message)
345
+ return false;
346
+ return /contributors.*(?:does not exist|not found|unknown)/i.test(message) || /(?:does not exist|unknown column).*contributors/i.test(message);
347
+ }
287
348
  async function insertSkillRow(args) {
288
349
  const id = args.id ?? randomUUID();
289
350
  const sourceSessionsJson = JSON.stringify(args.sourceSessions);
290
- const sql = `INSERT INTO "${sqlIdent(args.tableName)}" (id, name, project, project_key, local_path, install, source_sessions, source_agent, scope, author, description, trigger_text, body, version, created_at, updated_at) VALUES ('${esc(id)}', '${esc(args.name)}', '${esc(args.project)}', '${esc(args.projectKey)}', '${esc(args.localPath)}', '${esc(args.install)}', '${esc(sourceSessionsJson)}', '${esc(args.sourceAgent)}', '${esc(args.scope)}', '${esc(args.author)}', '${esc(args.description)}', '${esc(args.trigger ?? "")}', '${esc(args.body)}', ${args.version}, '${esc(args.createdAt)}', '${esc(args.updatedAt)}')`;
351
+ const contributorsJson = JSON.stringify(args.contributors);
352
+ const sql = `INSERT INTO "${sqlIdent(args.tableName)}" (id, name, project, project_key, local_path, install, source_sessions, source_agent, scope, author, contributors, description, trigger_text, body, version, created_at, updated_at) VALUES ('${esc(id)}', '${esc(args.name)}', '${esc(args.project)}', '${esc(args.projectKey)}', '${esc(args.localPath)}', '${esc(args.install)}', '${esc(sourceSessionsJson)}', '${esc(args.sourceAgent)}', '${esc(args.scope)}', '${esc(args.author)}', '${esc(contributorsJson)}', '${esc(args.description)}', '${esc(args.trigger ?? "")}', '${esc(args.body)}', ${args.version}, '${esc(args.createdAt)}', '${esc(args.updatedAt)}')`;
291
353
  try {
292
354
  await args.query(sql);
293
355
  } catch (e) {
@@ -296,6 +358,11 @@ async function insertSkillRow(args) {
296
358
  await args.query(sql);
297
359
  return;
298
360
  }
361
+ if (isMissingContributorsColumnError(e?.message)) {
362
+ await args.query(addContributorsColumnSql(args.tableName));
363
+ await args.query(sql);
364
+ return;
365
+ }
299
366
  throw e;
300
367
  }
301
368
  }
@@ -447,6 +514,14 @@ function runGate(opts) {
447
514
  }
448
515
  }
449
516
 
517
+ // dist/src/skillify/scope-promotion.js
518
+ function isCrossAuthorMergeVerdict(args) {
519
+ return args.verdict === "MERGE" && args.resultAuthor !== void 0 && args.resultAuthor !== args.userName;
520
+ }
521
+ function resolveRecordScope(args) {
522
+ return args.isCrossAuthorMerge && args.configScope === "me" ? "team" : args.configScope;
523
+ }
524
+
450
525
  // dist/src/skillify/state.js
451
526
  import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, writeSync, mkdirSync as mkdirSync2, renameSync as renameSync2, existsSync as existsSync4, unlinkSync, openSync, closeSync } from "node:fs";
452
527
  import { execSync } from "node:child_process";
@@ -652,8 +727,6 @@ async function query(sql, retries = 4) {
652
727
  return [];
653
728
  }
654
729
  function authorClause() {
655
- if (cfg.scope === "org")
656
- return "";
657
730
  if (cfg.scope === "team" && cfg.team.length > 0) {
658
731
  const list = cfg.team.map((n) => `'${esc2(n)}'`).join(", ");
659
732
  return ` AND author IN (${list})`;
@@ -721,7 +794,7 @@ ${answer}
721
794
  }
722
795
  function buildPrompt(pairs) {
723
796
  const existing = renderExistingSkillsBlock(cfg.cwd, EXISTING_SKILLS_CHAR_CAP);
724
- const mergeTargetsClause = existing.mergeTargetNames.length > 0 ? `MERGE is allowed only if your "name" is EXACTLY one of: [${existing.mergeTargetNames.join(", ")}]. Any other name MUST use KEEP, not MERGE.` : `MERGE is FORBIDDEN \u2014 there are no project skills to merge into. Use KEEP or SKIP only.`;
797
+ const mergeTargetsClause = existing.mergeTargetNames.length > 0 ? `MERGE is allowed only if your "name" is EXACTLY one of: [${existing.mergeTargetNames.join(", ")}]. Any other name MUST use KEEP, not MERGE.` : `MERGE is FORBIDDEN \u2014 there are no existing skills to merge into. Use KEEP or SKIP only.`;
725
798
  return [
726
799
  `You are a skill curator for the "${cfg.project}" project. You decide whether the recent`,
727
800
  `agent activity below contains a recurring, non-trivial pattern worth crystallizing as a`,
@@ -731,18 +804,19 @@ function buildPrompt(pairs) {
731
804
  `- KEEP only if the pattern recurs across at least 3 of the exchanges, is non-obvious to a`,
732
805
  ` competent engineer, and is not already covered by an existing skill below.`,
733
806
  `- SKIP if the activity is one-off, generic engineering work, or already covered.`,
734
- `- MERGE if the pattern is a meaningful extension of an existing PROJECT skill \u2014 produce a`,
807
+ `- MERGE if the pattern is a meaningful extension of an existing skill \u2014 produce a`,
735
808
  ` merged body that incorporates the new evidence without exceeding ~3000 characters or`,
736
809
  ` covering unrelated domains.`,
737
810
  `- ${mergeTargetsClause}`,
738
- `- Skills tagged [global, read-only] are autopulled from the team's shared skills`,
739
- ` table. They exist so you can recognise patterns already covered globally and pick`,
740
- ` SKIP (or a more specific KEEP) instead of duplicating them. They are NOT valid`,
741
- ` MERGE targets \u2014 only [project] skills can be merged into.`,
811
+ `- Cross-author MERGE has a real cost: editing a skill authored by someone else is`,
812
+ ` recorded as a team-level edit (scope=team, contributors+="${cfg.userName}"). Use it only`,
813
+ ` when the new evidence genuinely extends the existing skill; otherwise pick KEEP or SKIP.`,
814
+ ` Tags like [project, author=alice] / [global, author=bob] tell you whose skill it is.`,
742
815
  `- Skill bodies should follow the existing style: short sections (When to use, Workflow,`,
743
816
  ` Anti-patterns), concrete commands and file paths drawn from the exchanges, no marketing.`,
744
817
  ``,
745
- `=== EXISTING SKILLS ([project] are MERGE-eligible, [global] are reference only) ===`,
818
+ `=== EXISTING SKILLS (all MERGE-eligible; [global, author=X] entries from teammate X mean`,
819
+ `cross-author MERGE auto-promotes scope to team) ===`,
746
820
  existing.block,
747
821
  ``,
748
822
  `=== RECENT EXCHANGES (prompt + answer pairs, tool calls already stripped) ===`,
@@ -865,6 +939,17 @@ async function main() {
865
939
  const watermarkDate = oldest.lastMsg;
866
940
  const sourceSessions = usable.map((c) => (c.path.split("/").pop() ?? "").replace(/\.[^.]+$/, ""));
867
941
  async function recordToDeeplake(result, verdict2) {
942
+ const author = result.author ?? cfg.userName;
943
+ const isCrossAuthorMerge = isCrossAuthorMergeVerdict({
944
+ verdict: verdict2.verdict,
945
+ resultAuthor: result.author,
946
+ userName: cfg.userName
947
+ });
948
+ const scope = resolveRecordScope({
949
+ configScope: cfg.scope,
950
+ isCrossAuthorMerge
951
+ });
952
+ const contributors = result.contributors;
868
953
  try {
869
954
  await insertSkillRow({
870
955
  query,
@@ -876,8 +961,9 @@ async function main() {
876
961
  install: cfg.install,
877
962
  sourceSessions,
878
963
  sourceAgent: cfg.agent,
879
- scope: cfg.scope,
880
- author: cfg.userName,
964
+ scope,
965
+ author,
966
+ contributors,
881
967
  description: verdict2.description ?? "",
882
968
  trigger: verdict2.trigger,
883
969
  body: verdict2.body,
@@ -885,7 +971,7 @@ async function main() {
885
971
  createdAt: result.createdAt,
886
972
  updatedAt: result.updatedAt
887
973
  });
888
- wlog(`recorded to skills table: name=${verdict2.name} v${result.version}`);
974
+ wlog(`recorded to skills table: name=${verdict2.name} v${result.version} author=${author} scope=${scope} contributors=${contributors.length}` + (isCrossAuthorMerge ? " [auto-promoted me->team]" : ""));
889
975
  } catch (e) {
890
976
  wlog(`skills table insert failed (non-fatal): ${e.message}`);
891
977
  }
@@ -899,7 +985,8 @@ async function main() {
899
985
  trigger: verdict.trigger,
900
986
  body: verdict.body,
901
987
  sourceSessions,
902
- agent: cfg.agent
988
+ agent: cfg.agent,
989
+ author: cfg.userName
903
990
  });
904
991
  wlog(`wrote new skill: ${result.path}`);
905
992
  recordSkill(cfg.projectKey, verdict.name, watermarkUuid, watermarkDate);
@@ -916,7 +1003,8 @@ async function main() {
916
1003
  description: verdict.description,
917
1004
  body: verdict.body,
918
1005
  newSourceSessions: sourceSessions,
919
- agent: cfg.agent
1006
+ agent: cfg.agent,
1007
+ editor: cfg.userName
920
1008
  });
921
1009
  wlog(`merged into skill: ${result.path} (v${result.version})`);
922
1010
  recordSkill(cfg.projectKey, verdict.name, watermarkUuid, watermarkDate);
@@ -932,7 +1020,8 @@ async function main() {
932
1020
  trigger: verdict.trigger,
933
1021
  body: verdict.body,
934
1022
  sourceSessions,
935
- agent: cfg.agent
1023
+ agent: cfg.agent,
1024
+ author: cfg.userName
936
1025
  });
937
1026
  wlog(`wrote new skill (merge fallback): ${result.path}`);
938
1027
  recordSkill(cfg.projectKey, verdict.name, watermarkUuid, watermarkDate);
@@ -135,13 +135,16 @@ async function uploadSummary(query2, params) {
135
135
  const desc = extractDescription(text);
136
136
  const sizeBytes = Buffer.byteLength(text);
137
137
  const embSql = embeddingSqlLiteral(params.embedding ?? null);
138
+ const pluginVersion = params.pluginVersion;
138
139
  const existing = await query2(`SELECT path FROM "${tableName}" WHERE path = '${esc(vpath)}' LIMIT 1`);
139
140
  if (existing.length > 0) {
140
- const sql2 = `UPDATE "${tableName}" SET summary = E'${esc(text)}', summary_embedding = ${embSql}, size_bytes = ${sizeBytes}, description = E'${esc(desc)}', last_update_date = '${ts}' WHERE path = '${esc(vpath)}'`;
141
+ const pluginVersionSet = pluginVersion === void 0 ? "" : `plugin_version = '${esc(pluginVersion)}', `;
142
+ const sql2 = `UPDATE "${tableName}" SET summary = E'${esc(text)}', summary_embedding = ${embSql}, size_bytes = ${sizeBytes}, description = E'${esc(desc)}', ` + pluginVersionSet + `last_update_date = '${ts}' WHERE path = '${esc(vpath)}'`;
141
143
  await query2(sql2);
142
144
  return { path: "update", sql: sql2, descLength: desc.length, summaryLength: text.length };
143
145
  }
144
- const sql = `INSERT INTO "${tableName}" (id, path, filename, summary, summary_embedding, author, mime_type, size_bytes, project, description, agent, creation_date, last_update_date) VALUES ('${randomUUID()}', '${esc(vpath)}', '${esc(fname)}', E'${esc(text)}', ${embSql}, '${esc(userName)}', 'text/markdown', ${sizeBytes}, '${esc(project)}', E'${esc(desc)}', '${esc(agent)}', '${ts}', '${ts}')`;
146
+ const pluginVersionForInsert = pluginVersion ?? "";
147
+ const sql = `INSERT INTO "${tableName}" (id, path, filename, summary, summary_embedding, author, mime_type, size_bytes, project, description, agent, plugin_version, creation_date, last_update_date) VALUES ('${randomUUID()}', '${esc(vpath)}', '${esc(fname)}', E'${esc(text)}', ${embSql}, '${esc(userName)}', 'text/markdown', ${sizeBytes}, '${esc(project)}', E'${esc(desc)}', '${esc(agent)}', '${esc(pluginVersionForInsert)}', '${ts}', '${ts}')`;
145
148
  await query2(sql);
146
149
  return { path: "insert", sql, descLength: desc.length, summaryLength: text.length };
147
150
  }
@@ -544,7 +547,8 @@ async function main() {
544
547
  agent: "hermes",
545
548
  sessionId: cfg.sessionId,
546
549
  text,
547
- embedding
550
+ embedding,
551
+ pluginVersion: cfg.pluginVersion ?? ""
548
552
  });
549
553
  wlog(`uploaded ${vpath} (summary=${result.summaryLength}, desc=${result.descLength})`);
550
554
  try {
@@ -23724,13 +23724,14 @@ var DeeplakeApi = class {
23724
23724
  const tables = await this.listTables();
23725
23725
  if (!tables.includes(tbl)) {
23726
23726
  log2(`table "${tbl}" not found, creating`);
23727
- await this.createTableWithRetry(`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 '', creation_date TEXT NOT NULL DEFAULT '', last_update_date TEXT NOT NULL DEFAULT '') USING deeplake`, tbl);
23727
+ await this.createTableWithRetry(`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`, tbl);
23728
23728
  log2(`table "${tbl}" created`);
23729
23729
  if (!tables.includes(tbl))
23730
23730
  this._tablesCache = [...tables, tbl];
23731
23731
  }
23732
23732
  await this.ensureEmbeddingColumn(tbl, SUMMARY_EMBEDDING_COL);
23733
23733
  await this.ensureColumn(tbl, "agent", "TEXT NOT NULL DEFAULT ''");
23734
+ await this.ensureColumn(tbl, "plugin_version", "TEXT NOT NULL DEFAULT ''");
23734
23735
  }
23735
23736
  /** Create the sessions table (uses JSONB for message since every row is a JSON event). */
23736
23737
  async ensureSessionsTable(name) {
@@ -23738,13 +23739,14 @@ var DeeplakeApi = class {
23738
23739
  const tables = await this.listTables();
23739
23740
  if (!tables.includes(safe)) {
23740
23741
  log2(`table "${safe}" not found, creating`);
23741
- await this.createTableWithRetry(`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 '', creation_date TEXT NOT NULL DEFAULT '', last_update_date TEXT NOT NULL DEFAULT '') USING deeplake`, safe);
23742
+ await this.createTableWithRetry(`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`, safe);
23742
23743
  log2(`table "${safe}" created`);
23743
23744
  if (!tables.includes(safe))
23744
23745
  this._tablesCache = [...tables, safe];
23745
23746
  }
23746
23747
  await this.ensureEmbeddingColumn(safe, MESSAGE_EMBEDDING_COL);
23747
23748
  await this.ensureColumn(safe, "agent", "TEXT NOT NULL DEFAULT ''");
23749
+ await this.ensureColumn(safe, "plugin_version", "TEXT NOT NULL DEFAULT ''");
23748
23750
  await this.ensureLookupIndex(safe, "path_creation_date", `("path", "creation_date")`);
23749
23751
  }
23750
23752
  /**
@@ -461,7 +461,7 @@ var DeeplakeApi = class {
461
461
  if (!tables.includes(tbl)) {
462
462
  log2(`table "${tbl}" not found, creating`);
463
463
  await this.createTableWithRetry(
464
- `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 '', creation_date TEXT NOT NULL DEFAULT '', last_update_date TEXT NOT NULL DEFAULT '') USING deeplake`,
464
+ `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`,
465
465
  tbl
466
466
  );
467
467
  log2(`table "${tbl}" created`);
@@ -469,6 +469,7 @@ var DeeplakeApi = class {
469
469
  }
470
470
  await this.ensureEmbeddingColumn(tbl, SUMMARY_EMBEDDING_COL);
471
471
  await this.ensureColumn(tbl, "agent", "TEXT NOT NULL DEFAULT ''");
472
+ await this.ensureColumn(tbl, "plugin_version", "TEXT NOT NULL DEFAULT ''");
472
473
  }
473
474
  /** Create the sessions table (uses JSONB for message since every row is a JSON event). */
474
475
  async ensureSessionsTable(name) {
@@ -477,7 +478,7 @@ var DeeplakeApi = class {
477
478
  if (!tables.includes(safe)) {
478
479
  log2(`table "${safe}" not found, creating`);
479
480
  await this.createTableWithRetry(
480
- `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 '', creation_date TEXT NOT NULL DEFAULT '', last_update_date TEXT NOT NULL DEFAULT '') USING deeplake`,
481
+ `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`,
481
482
  safe
482
483
  );
483
484
  log2(`table "${safe}" created`);
@@ -485,6 +486,7 @@ var DeeplakeApi = class {
485
486
  }
486
487
  await this.ensureEmbeddingColumn(safe, MESSAGE_EMBEDDING_COL);
487
488
  await this.ensureColumn(safe, "agent", "TEXT NOT NULL DEFAULT ''");
489
+ await this.ensureColumn(safe, "plugin_version", "TEXT NOT NULL DEFAULT ''");
488
490
  await this.ensureLookupIndex(safe, "path_creation_date", `("path", "creation_date")`);
489
491
  }
490
492
  /**
@@ -1071,7 +1073,7 @@ function extractLatestVersion(body) {
1071
1073
  return typeof v === "string" && v.length > 0 ? v : null;
1072
1074
  }
1073
1075
  function getInstalledVersion() {
1074
- return "0.7.21".length > 0 ? "0.7.21" : null;
1076
+ return "0.7.23".length > 0 ? "0.7.23" : null;
1075
1077
  }
1076
1078
  function isNewer(latest, current) {
1077
1079
  const parse = (v) => v.replace(/-.*$/, "").split(".").map(Number);
@@ -1762,7 +1764,7 @@ ${body.slice(0, 500)}`;
1762
1764
  const hook = (event, handler) => {
1763
1765
  pluginApi.on(event, handler);
1764
1766
  };
1765
- if ('---\nname: hivemind\ndescription: Global team and org memory powered by Activeloop. ALWAYS check BOTH built-in memory AND Hivemind memory when recalling information.\nallowed-tools: hivemind_search, hivemind_read, hivemind_index\n---\n\n# Hivemind Memory\n\nYou have TWO memory sources. ALWAYS check BOTH when the user asks you to recall, remember, or look up ANY information:\n\n1. **Your built-in memory** \u2014 personal per-project notes from the host agent\n2. **Hivemind global memory** \u2014 global memory shared across all sessions, users, and agents in the org, accessed via the tools below\n\n## Memory Structure\n\n```\n/index.md \u2190 START HERE \u2014 table of all sessions\n/summaries/\n <username>/\n <session-id>.md \u2190 AI-generated wiki summary per session\n/sessions/\n <username>/\n <user_org_ws_slug>.jsonl \u2190 raw session data\n```\n\n## How to Search\n\n1. **First**: call `hivemind_index()` \u2014 table of all sessions with dates, projects, descriptions\n2. **If you need details**: call `hivemind_read("/summaries/<username>/<session>.md")`\n3. **If you need raw data**: call `hivemind_read("/sessions/<username>/<file>.jsonl")`\n4. **Keyword search**: call `hivemind_search("keyword")` \u2014 substring search across both summaries and sessions, returns `path:line` hits\n\nDo NOT jump straight to reading raw JSONL files. Always start with `hivemind_index` and summaries.\n\n## Organization Management\n\n- `/hivemind_login` \u2014 sign in via device flow\n- `/hivemind_capture` \u2014 toggle capture on/off (off = no data sent)\n- `/hivemind_whoami` \u2014 show current org and workspace\n- `/hivemind_orgs` \u2014 list organizations\n- `/hivemind_switch_org <name-or-id>` \u2014 switch organization\n- `/hivemind_workspaces` \u2014 list workspaces\n- `/hivemind_switch_workspace <id>` \u2014 switch workspace\n- `/hivemind_version` \u2014 show installed version and check npm for updates\n- `/hivemind_update` \u2014 shows how to install (ask the agent, or run `hivemind update` in your terminal)\n- `/hivemind_autoupdate [on|off]` \u2014 toggle the agent-facing update nudge (on by default: when a newer version is available, the agent is prompted to install it via `exec` if you ask to update)\n\n## Skill Management (skillify)\n\nHivemind also mines reusable Claude skills from agent sessions and stores them in a per-org Deeplake table. Openclaw itself doesn\'t run sessions to mine, but you can pull skills others have already mined for the user. These run in the user\'s terminal (the openclaw plugin does not register them as `/hivemind_*` commands):\n\n- `hivemind skillify` \u2014 show scope/team/install + per-project state\n- `hivemind skillify pull` \u2014 sync skills for the current project from the org table\n- `hivemind skillify pull --user <email>` \u2014 only that author\'s skills\n- `hivemind skillify pull --users a,b,c` \u2014 multiple authors (CSV)\n- `hivemind skillify pull --all-users` \u2014 explicit "no author filter"\n- `hivemind skillify pull --to project|global` \u2014 install location (`<cwd>/.claude/skills/` vs `~/.claude/skills/`)\n- `hivemind skillify pull --dry-run` \u2014 preview without touching disk\n- `hivemind skillify pull --force` \u2014 overwrite local (creates `.bak`)\n- `hivemind skillify pull <skill-name>` \u2014 pull only that one skill (combines with `--user`)\n- `hivemind skillify unpull` \u2014 remove every skill previously installed by pull\n- `hivemind skillify unpull --user <email>` \u2014 remove only that author\'s pulls\n- `hivemind skillify unpull --not-mine` \u2014 remove all pulls except your own\n- `hivemind skillify unpull --dry-run` \u2014 preview without touching disk\n- `hivemind skillify scope <me|team|org>` \u2014 set sharing scope for new skills\n- `hivemind skillify install <project|global>` \u2014 default install location\n- `hivemind skillify team add|remove|list <name>` \u2014 manage team list\n\nIf the user asks to "pull skills from X", "share skills with the team", or similar, suggest the matching `hivemind skillify` command. Run `hivemind skillify --help` for the full reference.\n\n## Limits\n\nDo NOT delegate to subagents when reading Hivemind memory. If a tool call returns empty after 2 attempts, skip it and move on. Report what you found rather than exhaustively retrying.\n\n## Getting Started\n\nAfter installing the plugin:\n1. Run `/hivemind_login` to authenticate\n2. Run `/hivemind_setup` to enable the memory tools in your openclaw allowlist (one-time, per install)\n3. Start using memory \u2014 ask questions, the agent automatically captures and searches\n\n## Sharing memory\n\nMultiple agents share memory when users are in the same Activeloop organization.\n'.length > 0) {
1767
+ if ('---\nname: hivemind\ndescription: Global team and org memory powered by Activeloop. ALWAYS check BOTH built-in memory AND Hivemind memory when recalling information.\nallowed-tools: hivemind_search, hivemind_read, hivemind_index\n---\n\n# Hivemind Memory\n\nYou have TWO memory sources. ALWAYS check BOTH when the user asks you to recall, remember, or look up ANY information:\n\n1. **Your built-in memory** \u2014 personal per-project notes from the host agent\n2. **Hivemind global memory** \u2014 global memory shared across all sessions, users, and agents in the org, accessed via the tools below\n\n## Memory Structure\n\n```\n/index.md \u2190 START HERE \u2014 table of all sessions\n/summaries/\n <username>/\n <session-id>.md \u2190 AI-generated wiki summary per session\n/sessions/\n <username>/\n <user_org_ws_slug>.jsonl \u2190 raw session data\n```\n\n## How to Search\n\n1. **First**: call `hivemind_index()` \u2014 table of all sessions with dates, projects, descriptions\n2. **If you need details**: call `hivemind_read("/summaries/<username>/<session>.md")`\n3. **If you need raw data**: call `hivemind_read("/sessions/<username>/<file>.jsonl")`\n4. **Keyword search**: call `hivemind_search("keyword")` \u2014 substring search across both summaries and sessions, returns `path:line` hits\n\nDo NOT jump straight to reading raw JSONL files. Always start with `hivemind_index` and summaries.\n\n## Organization Management\n\n- `/hivemind_login` \u2014 sign in via device flow\n- `/hivemind_capture` \u2014 toggle capture on/off (off = no data sent)\n- `/hivemind_whoami` \u2014 show current org and workspace\n- `/hivemind_orgs` \u2014 list organizations\n- `/hivemind_switch_org <name-or-id>` \u2014 switch organization\n- `/hivemind_workspaces` \u2014 list workspaces\n- `/hivemind_switch_workspace <id>` \u2014 switch workspace\n- `/hivemind_version` \u2014 show installed version and check npm for updates\n- `/hivemind_update` \u2014 shows how to install (ask the agent, or run `hivemind update` in your terminal)\n- `/hivemind_autoupdate [on|off]` \u2014 toggle the agent-facing update nudge (on by default: when a newer version is available, the agent is prompted to install it via `exec` if you ask to update)\n\n## Skill Management (skillify)\n\nHivemind also mines reusable Claude skills from agent sessions and stores them in a per-org Deeplake table. Openclaw itself doesn\'t run sessions to mine, but you can pull skills others have already mined for the user. These run in the user\'s terminal (the openclaw plugin does not register them as `/hivemind_*` commands):\n\n- `hivemind skillify` \u2014 show scope/team/install + per-project state\n- `hivemind skillify pull` \u2014 sync skills for the current project from the org table\n- `hivemind skillify pull --user <email>` \u2014 only that author\'s skills\n- `hivemind skillify pull --users a,b,c` \u2014 multiple authors (CSV)\n- `hivemind skillify pull --all-users` \u2014 explicit "no author filter"\n- `hivemind skillify pull --to project|global` \u2014 install location (`<cwd>/.claude/skills/` vs `~/.claude/skills/`)\n- `hivemind skillify pull --dry-run` \u2014 preview without touching disk\n- `hivemind skillify pull --force` \u2014 overwrite local (creates `.bak`)\n- `hivemind skillify pull <skill-name>` \u2014 pull only that one skill (combines with `--user`)\n- `hivemind skillify unpull` \u2014 remove every skill previously installed by pull\n- `hivemind skillify unpull --user <email>` \u2014 remove only that author\'s pulls\n- `hivemind skillify unpull --not-mine` \u2014 remove all pulls except your own\n- `hivemind skillify unpull --dry-run` \u2014 preview without touching disk\n- `hivemind skillify scope <me|team>` \u2014 set sharing scope for new skills\n- `hivemind skillify install <project|global>` \u2014 default install location\n- `hivemind skillify team add|remove|list <name>` \u2014 manage team list\n\nIf the user asks to "pull skills from X", "share skills with the team", or similar, suggest the matching `hivemind skillify` command. Run `hivemind skillify --help` for the full reference.\n\n## Limits\n\nDo NOT delegate to subagents when reading Hivemind memory. If a tool call returns empty after 2 attempts, skip it and move on. Report what you found rather than exhaustively retrying.\n\n## Getting Started\n\nAfter installing the plugin:\n1. Run `/hivemind_login` to authenticate\n2. Run `/hivemind_setup` to enable the memory tools in your openclaw allowlist (one-time, per install)\n3. Start using memory \u2014 ask questions, the agent automatically captures and searches\n\n## Sharing memory\n\nMultiple agents share memory when users are in the same Activeloop organization.\n'.length > 0) {
1766
1768
  const setupConfigPromise = loadSetupConfig();
1767
1769
  hook("before_prompt_build", async () => {
1768
1770
  const { detectAllowlistMissing } = await setupConfigPromise;
@@ -1774,7 +1776,7 @@ A newer Hivemind version is available: ${pendingUpdate.current} \u2192 ${pending
1774
1776
  </hivemind-update-available>
1775
1777
  ` : "";
1776
1778
  return {
1777
- prependSystemContext: allowlistNudge + updateNudge + '\n\n<hivemind-skill>\n---\nname: hivemind\ndescription: Global team and org memory powered by Activeloop. ALWAYS check BOTH built-in memory AND Hivemind memory when recalling information.\nallowed-tools: hivemind_search, hivemind_read, hivemind_index\n---\n\n# Hivemind Memory\n\nYou have TWO memory sources. ALWAYS check BOTH when the user asks you to recall, remember, or look up ANY information:\n\n1. **Your built-in memory** \u2014 personal per-project notes from the host agent\n2. **Hivemind global memory** \u2014 global memory shared across all sessions, users, and agents in the org, accessed via the tools below\n\n## Memory Structure\n\n```\n/index.md \u2190 START HERE \u2014 table of all sessions\n/summaries/\n <username>/\n <session-id>.md \u2190 AI-generated wiki summary per session\n/sessions/\n <username>/\n <user_org_ws_slug>.jsonl \u2190 raw session data\n```\n\n## How to Search\n\n1. **First**: call `hivemind_index()` \u2014 table of all sessions with dates, projects, descriptions\n2. **If you need details**: call `hivemind_read("/summaries/<username>/<session>.md")`\n3. **If you need raw data**: call `hivemind_read("/sessions/<username>/<file>.jsonl")`\n4. **Keyword search**: call `hivemind_search("keyword")` \u2014 substring search across both summaries and sessions, returns `path:line` hits\n\nDo NOT jump straight to reading raw JSONL files. Always start with `hivemind_index` and summaries.\n\n## Organization Management\n\n- `/hivemind_login` \u2014 sign in via device flow\n- `/hivemind_capture` \u2014 toggle capture on/off (off = no data sent)\n- `/hivemind_whoami` \u2014 show current org and workspace\n- `/hivemind_orgs` \u2014 list organizations\n- `/hivemind_switch_org <name-or-id>` \u2014 switch organization\n- `/hivemind_workspaces` \u2014 list workspaces\n- `/hivemind_switch_workspace <id>` \u2014 switch workspace\n- `/hivemind_version` \u2014 show installed version and check npm for updates\n- `/hivemind_update` \u2014 shows how to install (ask the agent, or run `hivemind update` in your terminal)\n- `/hivemind_autoupdate [on|off]` \u2014 toggle the agent-facing update nudge (on by default: when a newer version is available, the agent is prompted to install it via `exec` if you ask to update)\n\n## Skill Management (skillify)\n\nHivemind also mines reusable Claude skills from agent sessions and stores them in a per-org Deeplake table. Openclaw itself doesn\'t run sessions to mine, but you can pull skills others have already mined for the user. These run in the user\'s terminal (the openclaw plugin does not register them as `/hivemind_*` commands):\n\n- `hivemind skillify` \u2014 show scope/team/install + per-project state\n- `hivemind skillify pull` \u2014 sync skills for the current project from the org table\n- `hivemind skillify pull --user <email>` \u2014 only that author\'s skills\n- `hivemind skillify pull --users a,b,c` \u2014 multiple authors (CSV)\n- `hivemind skillify pull --all-users` \u2014 explicit "no author filter"\n- `hivemind skillify pull --to project|global` \u2014 install location (`<cwd>/.claude/skills/` vs `~/.claude/skills/`)\n- `hivemind skillify pull --dry-run` \u2014 preview without touching disk\n- `hivemind skillify pull --force` \u2014 overwrite local (creates `.bak`)\n- `hivemind skillify pull <skill-name>` \u2014 pull only that one skill (combines with `--user`)\n- `hivemind skillify unpull` \u2014 remove every skill previously installed by pull\n- `hivemind skillify unpull --user <email>` \u2014 remove only that author\'s pulls\n- `hivemind skillify unpull --not-mine` \u2014 remove all pulls except your own\n- `hivemind skillify unpull --dry-run` \u2014 preview without touching disk\n- `hivemind skillify scope <me|team|org>` \u2014 set sharing scope for new skills\n- `hivemind skillify install <project|global>` \u2014 default install location\n- `hivemind skillify team add|remove|list <name>` \u2014 manage team list\n\nIf the user asks to "pull skills from X", "share skills with the team", or similar, suggest the matching `hivemind skillify` command. Run `hivemind skillify --help` for the full reference.\n\n## Limits\n\nDo NOT delegate to subagents when reading Hivemind memory. If a tool call returns empty after 2 attempts, skip it and move on. Report what you found rather than exhaustively retrying.\n\n## Getting Started\n\nAfter installing the plugin:\n1. Run `/hivemind_login` to authenticate\n2. Run `/hivemind_setup` to enable the memory tools in your openclaw allowlist (one-time, per install)\n3. Start using memory \u2014 ask questions, the agent automatically captures and searches\n\n## Sharing memory\n\nMultiple agents share memory when users are in the same Activeloop organization.\n\n</hivemind-skill>\n'
1779
+ prependSystemContext: allowlistNudge + updateNudge + '\n\n<hivemind-skill>\n---\nname: hivemind\ndescription: Global team and org memory powered by Activeloop. ALWAYS check BOTH built-in memory AND Hivemind memory when recalling information.\nallowed-tools: hivemind_search, hivemind_read, hivemind_index\n---\n\n# Hivemind Memory\n\nYou have TWO memory sources. ALWAYS check BOTH when the user asks you to recall, remember, or look up ANY information:\n\n1. **Your built-in memory** \u2014 personal per-project notes from the host agent\n2. **Hivemind global memory** \u2014 global memory shared across all sessions, users, and agents in the org, accessed via the tools below\n\n## Memory Structure\n\n```\n/index.md \u2190 START HERE \u2014 table of all sessions\n/summaries/\n <username>/\n <session-id>.md \u2190 AI-generated wiki summary per session\n/sessions/\n <username>/\n <user_org_ws_slug>.jsonl \u2190 raw session data\n```\n\n## How to Search\n\n1. **First**: call `hivemind_index()` \u2014 table of all sessions with dates, projects, descriptions\n2. **If you need details**: call `hivemind_read("/summaries/<username>/<session>.md")`\n3. **If you need raw data**: call `hivemind_read("/sessions/<username>/<file>.jsonl")`\n4. **Keyword search**: call `hivemind_search("keyword")` \u2014 substring search across both summaries and sessions, returns `path:line` hits\n\nDo NOT jump straight to reading raw JSONL files. Always start with `hivemind_index` and summaries.\n\n## Organization Management\n\n- `/hivemind_login` \u2014 sign in via device flow\n- `/hivemind_capture` \u2014 toggle capture on/off (off = no data sent)\n- `/hivemind_whoami` \u2014 show current org and workspace\n- `/hivemind_orgs` \u2014 list organizations\n- `/hivemind_switch_org <name-or-id>` \u2014 switch organization\n- `/hivemind_workspaces` \u2014 list workspaces\n- `/hivemind_switch_workspace <id>` \u2014 switch workspace\n- `/hivemind_version` \u2014 show installed version and check npm for updates\n- `/hivemind_update` \u2014 shows how to install (ask the agent, or run `hivemind update` in your terminal)\n- `/hivemind_autoupdate [on|off]` \u2014 toggle the agent-facing update nudge (on by default: when a newer version is available, the agent is prompted to install it via `exec` if you ask to update)\n\n## Skill Management (skillify)\n\nHivemind also mines reusable Claude skills from agent sessions and stores them in a per-org Deeplake table. Openclaw itself doesn\'t run sessions to mine, but you can pull skills others have already mined for the user. These run in the user\'s terminal (the openclaw plugin does not register them as `/hivemind_*` commands):\n\n- `hivemind skillify` \u2014 show scope/team/install + per-project state\n- `hivemind skillify pull` \u2014 sync skills for the current project from the org table\n- `hivemind skillify pull --user <email>` \u2014 only that author\'s skills\n- `hivemind skillify pull --users a,b,c` \u2014 multiple authors (CSV)\n- `hivemind skillify pull --all-users` \u2014 explicit "no author filter"\n- `hivemind skillify pull --to project|global` \u2014 install location (`<cwd>/.claude/skills/` vs `~/.claude/skills/`)\n- `hivemind skillify pull --dry-run` \u2014 preview without touching disk\n- `hivemind skillify pull --force` \u2014 overwrite local (creates `.bak`)\n- `hivemind skillify pull <skill-name>` \u2014 pull only that one skill (combines with `--user`)\n- `hivemind skillify unpull` \u2014 remove every skill previously installed by pull\n- `hivemind skillify unpull --user <email>` \u2014 remove only that author\'s pulls\n- `hivemind skillify unpull --not-mine` \u2014 remove all pulls except your own\n- `hivemind skillify unpull --dry-run` \u2014 preview without touching disk\n- `hivemind skillify scope <me|team>` \u2014 set sharing scope for new skills\n- `hivemind skillify install <project|global>` \u2014 default install location\n- `hivemind skillify team add|remove|list <name>` \u2014 manage team list\n\nIf the user asks to "pull skills from X", "share skills with the team", or similar, suggest the matching `hivemind skillify` command. Run `hivemind skillify --help` for the full reference.\n\n## Limits\n\nDo NOT delegate to subagents when reading Hivemind memory. If a tool call returns empty after 2 attempts, skip it and move on. Report what you found rather than exhaustively retrying.\n\n## Getting Started\n\nAfter installing the plugin:\n1. Run `/hivemind_login` to authenticate\n2. Run `/hivemind_setup` to enable the memory tools in your openclaw allowlist (one-time, per install)\n3. Start using memory \u2014 ask questions, the agent automatically captures and searches\n\n## Sharing memory\n\nMultiple agents share memory when users are in the same Activeloop organization.\n\n</hivemind-skill>\n'
1778
1780
  };
1779
1781
  });
1780
1782
  }
@@ -1879,7 +1881,7 @@ One brain for every agent on your team.
1879
1881
  };
1880
1882
  const line = JSON.stringify(entry);
1881
1883
  const jsonForSql = line.replace(/'/g, "''");
1882
- const insertSql = `INSERT INTO "${sessionsTable}" (id, path, filename, message, author, size_bytes, project, description, agent, creation_date, last_update_date) VALUES ('${crypto.randomUUID()}', '${sqlStr(sessionPath)}', '${sqlStr(filename)}', '${jsonForSql}'::jsonb, '${sqlStr(cfg.userName)}', ${Buffer.byteLength(line, "utf-8")}, '${sqlStr(projectName)}', '${sqlStr(msg.role)}', 'openclaw', '${ts}', '${ts}')`;
1884
+ const insertSql = `INSERT INTO "${sessionsTable}" (id, path, filename, message, author, size_bytes, project, description, agent, plugin_version, creation_date, last_update_date) VALUES ('${crypto.randomUUID()}', '${sqlStr(sessionPath)}', '${sqlStr(filename)}', '${jsonForSql}'::jsonb, '${sqlStr(cfg.userName)}', ${Buffer.byteLength(line, "utf-8")}, '${sqlStr(projectName)}', '${sqlStr(msg.role)}', 'openclaw', '${sqlStr(getInstalledVersion() ?? "")}', '${ts}', '${ts}')`;
1883
1885
  try {
1884
1886
  await dl.query(insertSql);
1885
1887
  } catch (e) {