@deeplake/hivemind 0.7.22 → 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.
@@ -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);
@@ -1073,7 +1073,7 @@ function extractLatestVersion(body) {
1073
1073
  return typeof v === "string" && v.length > 0 ? v : null;
1074
1074
  }
1075
1075
  function getInstalledVersion() {
1076
- return "0.7.22".length > 0 ? "0.7.22" : null;
1076
+ return "0.7.23".length > 0 ? "0.7.23" : null;
1077
1077
  }
1078
1078
  function isNewer(latest, current) {
1079
1079
  const parse = (v) => v.replace(/-.*$/, "").split(".").map(Number);
@@ -1764,7 +1764,7 @@ ${body.slice(0, 500)}`;
1764
1764
  const hook = (event, handler) => {
1765
1765
  pluginApi.on(event, handler);
1766
1766
  };
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|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) {
1768
1768
  const setupConfigPromise = loadSetupConfig();
1769
1769
  hook("before_prompt_build", async () => {
1770
1770
  const { detectAllowlistMissing } = await setupConfigPromise;
@@ -1776,7 +1776,7 @@ A newer Hivemind version is available: ${pendingUpdate.current} \u2192 ${pending
1776
1776
  </hivemind-update-available>
1777
1777
  ` : "";
1778
1778
  return {
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|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'
1780
1780
  };
1781
1781
  });
1782
1782
  }