@deeplake/hivemind 0.7.17 → 0.7.18
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 +148 -111
- package/codex/bundle/session-start.js +89 -55
- package/codex/bundle/{skilify-worker.js → skillify-worker.js} +65 -30
- package/codex/bundle/stop.js +105 -68
- package/cursor/bundle/capture.js +84 -47
- package/cursor/bundle/session-end.js +82 -45
- package/cursor/bundle/session-start.js +87 -53
- package/cursor/bundle/{skilify-worker.js → skillify-worker.js} +65 -30
- package/hermes/bundle/capture.js +84 -47
- package/hermes/bundle/session-end.js +82 -45
- package/hermes/bundle/session-start.js +87 -53
- package/hermes/bundle/{skilify-worker.js → skillify-worker.js} +65 -30
- package/openclaw/dist/index.js +47 -30
- package/openclaw/dist/{skilify-worker.js → skillify-worker.js} +65 -30
- package/openclaw/openclaw.plugin.json +1 -1
- package/openclaw/package.json +1 -1
- package/openclaw/skills/SKILL.md +19 -19
- package/package.json +1 -1
- package/pi/extension-source/hivemind.ts +43 -43
package/openclaw/dist/index.js
CHANGED
|
@@ -1027,7 +1027,8 @@ import {
|
|
|
1027
1027
|
openSync as fsOpen,
|
|
1028
1028
|
closeSync as fsClose,
|
|
1029
1029
|
writeFileSync as fsWriteFile,
|
|
1030
|
-
constants as fsConstants
|
|
1030
|
+
constants as fsConstants,
|
|
1031
|
+
renameSync as fsRename
|
|
1031
1032
|
} from "node:fs";
|
|
1032
1033
|
import { createHash } from "node:crypto";
|
|
1033
1034
|
import { createRequire } from "node:module";
|
|
@@ -1070,7 +1071,7 @@ function extractLatestVersion(body) {
|
|
|
1070
1071
|
return typeof v === "string" && v.length > 0 ? v : null;
|
|
1071
1072
|
}
|
|
1072
1073
|
function getInstalledVersion() {
|
|
1073
|
-
return "0.7.
|
|
1074
|
+
return "0.7.18".length > 0 ? "0.7.18" : null;
|
|
1074
1075
|
}
|
|
1075
1076
|
function isNewer(latest, current) {
|
|
1076
1077
|
const parse = (v) => v.replace(/-.*$/, "").split(".").map(Number);
|
|
@@ -1175,17 +1176,33 @@ var capturedCounts = /* @__PURE__ */ new Map();
|
|
|
1175
1176
|
var fallbackSessionId = crypto.randomUUID();
|
|
1176
1177
|
var __openclaw_filename = fileURLToPath(import.meta.url);
|
|
1177
1178
|
var __openclaw_dirname = dirnamePath(__openclaw_filename);
|
|
1178
|
-
var
|
|
1179
|
-
var
|
|
1179
|
+
var OPENCLAW_SKILLIFY_WORKER_PATH = joinPath(__openclaw_dirname, "skillify-worker.js");
|
|
1180
|
+
var OPENCLAW_SKILLIFY_STATE_DIR = joinPath(homedir2(), ".deeplake", "state", "skillify");
|
|
1181
|
+
var OPENCLAW_SKILLIFY_LEGACY_STATE_DIR = joinPath(homedir2(), ".deeplake", "state", "skilify");
|
|
1182
|
+
var openclawSkillifyMigrationAttempted = false;
|
|
1183
|
+
function migrateOpenclawSkillifyLegacyStateDir() {
|
|
1184
|
+
if (openclawSkillifyMigrationAttempted) return;
|
|
1185
|
+
openclawSkillifyMigrationAttempted = true;
|
|
1186
|
+
if (!fsExists(OPENCLAW_SKILLIFY_LEGACY_STATE_DIR)) return;
|
|
1187
|
+
if (fsExists(OPENCLAW_SKILLIFY_STATE_DIR)) return;
|
|
1188
|
+
try {
|
|
1189
|
+
fsRename(OPENCLAW_SKILLIFY_LEGACY_STATE_DIR, OPENCLAW_SKILLIFY_STATE_DIR);
|
|
1190
|
+
} catch (err) {
|
|
1191
|
+
const code = err.code;
|
|
1192
|
+
if (code === "EXDEV" || code === "EPERM") return;
|
|
1193
|
+
throw err;
|
|
1194
|
+
}
|
|
1195
|
+
}
|
|
1180
1196
|
function deriveOpenclawProjectKey(channel) {
|
|
1181
1197
|
const project = channel || "openclaw";
|
|
1182
1198
|
const key = createHash("sha1").update(project).digest("hex").slice(0, 16);
|
|
1183
1199
|
return { key, project };
|
|
1184
1200
|
}
|
|
1185
|
-
function
|
|
1201
|
+
function tryAcquireOpenclawSkillifyLock(projectKey) {
|
|
1186
1202
|
try {
|
|
1187
|
-
|
|
1188
|
-
|
|
1203
|
+
migrateOpenclawSkillifyLegacyStateDir();
|
|
1204
|
+
fsMkdir(OPENCLAW_SKILLIFY_STATE_DIR, { recursive: true });
|
|
1205
|
+
const lockPath = joinPath(OPENCLAW_SKILLIFY_STATE_DIR, `${projectKey}.worker.lock`);
|
|
1189
1206
|
const fd = fsOpen(lockPath, fsConstants.O_CREAT | fsConstants.O_EXCL | fsConstants.O_WRONLY);
|
|
1190
1207
|
fsClose(fd);
|
|
1191
1208
|
return true;
|
|
@@ -1210,25 +1227,25 @@ function detectOpenclawGateAgent() {
|
|
|
1210
1227
|
}
|
|
1211
1228
|
return null;
|
|
1212
1229
|
}
|
|
1213
|
-
function
|
|
1214
|
-
if (!fsExists(
|
|
1215
|
-
a.loggerWarn?.(`
|
|
1230
|
+
function spawnOpenclawSkillifyWorker(a) {
|
|
1231
|
+
if (!fsExists(OPENCLAW_SKILLIFY_WORKER_PATH)) {
|
|
1232
|
+
a.loggerWarn?.(`skillify worker missing at ${OPENCLAW_SKILLIFY_WORKER_PATH} \u2014 reinstall openclaw plugin`);
|
|
1216
1233
|
return;
|
|
1217
1234
|
}
|
|
1218
1235
|
const gateAgent = detectOpenclawGateAgent();
|
|
1219
1236
|
if (!gateAgent) {
|
|
1220
|
-
a.loggerWarn?.(`
|
|
1237
|
+
a.loggerWarn?.(`skillify spawn: no delegate gate CLI found on PATH (need one of: claude, codex, cursor-agent, hermes, pi). Mining skipped.`);
|
|
1221
1238
|
return;
|
|
1222
1239
|
}
|
|
1223
1240
|
const { key: projectKey, project } = deriveOpenclawProjectKey(a.channel);
|
|
1224
|
-
if (!
|
|
1241
|
+
if (!tryAcquireOpenclawSkillifyLock(projectKey)) {
|
|
1225
1242
|
return;
|
|
1226
1243
|
}
|
|
1227
|
-
const tmpDir = joinPath(tmpdir(), `deeplake-
|
|
1244
|
+
const tmpDir = joinPath(tmpdir(), `deeplake-skillify-openclaw-${projectKey}-${Date.now()}`);
|
|
1228
1245
|
try {
|
|
1229
1246
|
fsMkdir(tmpDir, { recursive: true, mode: 448 });
|
|
1230
1247
|
} catch (e) {
|
|
1231
|
-
a.loggerWarn?.(`
|
|
1248
|
+
a.loggerWarn?.(`skillify spawn: mkdir failed: ${e?.message ?? e}`);
|
|
1232
1249
|
return;
|
|
1233
1250
|
}
|
|
1234
1251
|
const configPath = joinPath(tmpDir, "config.json");
|
|
@@ -1256,23 +1273,23 @@ function spawnOpenclawSkilifyWorker(a) {
|
|
|
1256
1273
|
cursorModel: void 0,
|
|
1257
1274
|
hermesProvider: void 0,
|
|
1258
1275
|
hermesModel: void 0,
|
|
1259
|
-
|
|
1276
|
+
skillifyLog: joinPath(homedir2(), ".deeplake", "hivemind-openclaw-skillify.log"),
|
|
1260
1277
|
currentSessionId: a.sessionId
|
|
1261
1278
|
};
|
|
1262
1279
|
try {
|
|
1263
1280
|
fsWriteFile(configPath, JSON.stringify(config), { mode: 384 });
|
|
1264
1281
|
} catch (e) {
|
|
1265
|
-
a.loggerWarn?.(`
|
|
1282
|
+
a.loggerWarn?.(`skillify spawn: config write failed: ${e?.message ?? e}`);
|
|
1266
1283
|
return;
|
|
1267
1284
|
}
|
|
1268
1285
|
try {
|
|
1269
|
-
realSpawn(process.execPath, [
|
|
1286
|
+
realSpawn(process.execPath, [OPENCLAW_SKILLIFY_WORKER_PATH, configPath], {
|
|
1270
1287
|
detached: true,
|
|
1271
1288
|
stdio: "ignore",
|
|
1272
|
-
env: { ...process.env,
|
|
1289
|
+
env: { ...process.env, HIVEMIND_SKILLIFY_WORKER: "1", HIVEMIND_CAPTURE: "false" }
|
|
1273
1290
|
}).unref();
|
|
1274
1291
|
} catch (e) {
|
|
1275
|
-
a.loggerWarn?.(`
|
|
1292
|
+
a.loggerWarn?.(`skillify spawn: spawn failed: ${e?.message ?? e}`);
|
|
1276
1293
|
}
|
|
1277
1294
|
}
|
|
1278
1295
|
function buildSessionPath(config, sessionId) {
|
|
@@ -1483,22 +1500,22 @@ ${available}` };
|
|
|
1483
1500
|
handler: async () => {
|
|
1484
1501
|
const { ensureHivemindAllowlisted } = await loadSetupConfig();
|
|
1485
1502
|
const result = ensureHivemindAllowlisted();
|
|
1486
|
-
const
|
|
1503
|
+
const skillifyHint = `
|
|
1487
1504
|
|
|
1488
|
-
Skill mining (
|
|
1489
|
-
hivemind
|
|
1490
|
-
hivemind
|
|
1505
|
+
Skill mining (skillify) runs in the background after each turn \u2014 your conversations get crystallised into reusable skills automatically. From your terminal:
|
|
1506
|
+
hivemind skillify status \u2014 see what's been mined
|
|
1507
|
+
hivemind skillify pull \u2014 fetch teammates' skills`;
|
|
1491
1508
|
if (result.status === "already-set") {
|
|
1492
1509
|
return { text: `\u2705 Hivemind tools are already enabled in your allowlist.
|
|
1493
1510
|
|
|
1494
|
-
No changes needed \u2014 memory tools are available to the agent.${
|
|
1511
|
+
No changes needed \u2014 memory tools are available to the agent.${skillifyHint}` };
|
|
1495
1512
|
}
|
|
1496
1513
|
if (result.status === "added") {
|
|
1497
1514
|
return { text: `\u2705 Added "hivemind" to your tool allowlist.
|
|
1498
1515
|
|
|
1499
1516
|
Openclaw will detect the config change and restart. On the next turn, the agent will have access to hivemind_search, hivemind_read, and hivemind_index.
|
|
1500
1517
|
|
|
1501
|
-
Backup of previous config: ${result.backupPath}${
|
|
1518
|
+
Backup of previous config: ${result.backupPath}${skillifyHint}` };
|
|
1502
1519
|
}
|
|
1503
1520
|
return { text: `\u26A0\uFE0F Could not update allowlist: ${result.error}
|
|
1504
1521
|
|
|
@@ -1745,7 +1762,7 @@ ${body.slice(0, 500)}`;
|
|
|
1745
1762
|
const hook = (event, handler) => {
|
|
1746
1763
|
pluginApi.on(event, handler);
|
|
1747
1764
|
};
|
|
1748
|
-
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 (
|
|
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) {
|
|
1749
1766
|
const setupConfigPromise = loadSetupConfig();
|
|
1750
1767
|
hook("before_prompt_build", async () => {
|
|
1751
1768
|
const { detectAllowlistMissing } = await setupConfigPromise;
|
|
@@ -1757,7 +1774,7 @@ A newer Hivemind version is available: ${pendingUpdate.current} \u2192 ${pending
|
|
|
1757
1774
|
</hivemind-update-available>
|
|
1758
1775
|
` : "";
|
|
1759
1776
|
return {
|
|
1760
|
-
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 (
|
|
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'
|
|
1761
1778
|
};
|
|
1762
1779
|
});
|
|
1763
1780
|
}
|
|
@@ -1876,7 +1893,7 @@ One brain for every agent on your team.
|
|
|
1876
1893
|
}
|
|
1877
1894
|
logger.info?.(`Auto-captured ${newMessages.length} messages`);
|
|
1878
1895
|
try {
|
|
1879
|
-
|
|
1896
|
+
spawnOpenclawSkillifyWorker({
|
|
1880
1897
|
apiUrl: cfg.apiUrl,
|
|
1881
1898
|
token: cfg.token,
|
|
1882
1899
|
orgId: cfg.orgId,
|
|
@@ -1884,10 +1901,10 @@ One brain for every agent on your team.
|
|
|
1884
1901
|
userName: cfg.userName,
|
|
1885
1902
|
channel: ev.channel || "openclaw",
|
|
1886
1903
|
sessionId: sid,
|
|
1887
|
-
loggerWarn: (msg) => logger.error(`
|
|
1904
|
+
loggerWarn: (msg) => logger.error(`Skillify spawn: ${msg}`)
|
|
1888
1905
|
});
|
|
1889
1906
|
} catch (e) {
|
|
1890
|
-
logger.error(`
|
|
1907
|
+
logger.error(`Skillify spawn threw: ${e?.message ?? e}`);
|
|
1891
1908
|
}
|
|
1892
1909
|
} catch (err) {
|
|
1893
1910
|
logger.error(`Auto-capture failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
// dist/src/
|
|
4
|
-
import { readFileSync as readFileSync3, writeFileSync as writeFileSync3, existsSync as
|
|
5
|
-
import { join as
|
|
3
|
+
// dist/src/skillify/skillify-worker.js
|
|
4
|
+
import { readFileSync as readFileSync3, writeFileSync as writeFileSync3, existsSync as existsSync5, appendFileSync as appendFileSync2, rmSync } from "node:fs";
|
|
5
|
+
import { join as join6 } from "node:path";
|
|
6
6
|
|
|
7
7
|
// dist/src/utils/debug.js
|
|
8
8
|
import { appendFileSync } from "node:fs";
|
|
@@ -29,7 +29,7 @@ function deeplakeClientHeader() {
|
|
|
29
29
|
return { [DEEPLAKE_CLIENT_HEADER]: deeplakeClientValue() };
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
// dist/src/
|
|
32
|
+
// dist/src/skillify/extractors/index.js
|
|
33
33
|
function extractPairs(rows) {
|
|
34
34
|
const pairs = [];
|
|
35
35
|
let pendingPrompt = null;
|
|
@@ -60,7 +60,7 @@ function extractPairs(rows) {
|
|
|
60
60
|
return pairs;
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
-
// dist/src/
|
|
63
|
+
// dist/src/skillify/skill-writer.js
|
|
64
64
|
import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
|
|
65
65
|
import { homedir as homedir2 } from "node:os";
|
|
66
66
|
import { join as join2 } from "node:path";
|
|
@@ -216,7 +216,7 @@ function resolveSkillsRoot(install, cwd) {
|
|
|
216
216
|
return join2(cwd, ".claude", "skills");
|
|
217
217
|
}
|
|
218
218
|
|
|
219
|
-
// dist/src/
|
|
219
|
+
// dist/src/skillify/skills-table.js
|
|
220
220
|
import { randomUUID } from "node:crypto";
|
|
221
221
|
|
|
222
222
|
// dist/src/utils/sql.js
|
|
@@ -227,7 +227,7 @@ function sqlIdent(name) {
|
|
|
227
227
|
return name;
|
|
228
228
|
}
|
|
229
229
|
|
|
230
|
-
// dist/src/
|
|
230
|
+
// dist/src/skillify/skills-table.js
|
|
231
231
|
function createSkillsTableSql(tableName) {
|
|
232
232
|
const safe = sqlIdent(tableName);
|
|
233
233
|
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`;
|
|
@@ -256,7 +256,7 @@ async function insertSkillRow(args) {
|
|
|
256
256
|
}
|
|
257
257
|
}
|
|
258
258
|
|
|
259
|
-
// dist/src/
|
|
259
|
+
// dist/src/skillify/gate-parser.js
|
|
260
260
|
function extractJsonBlock(s) {
|
|
261
261
|
const trimmed = s.trim();
|
|
262
262
|
if (!trimmed)
|
|
@@ -294,7 +294,7 @@ function parseVerdict(raw) {
|
|
|
294
294
|
}
|
|
295
295
|
}
|
|
296
296
|
|
|
297
|
-
// dist/src/
|
|
297
|
+
// dist/src/skillify/gate-runner.js
|
|
298
298
|
import { execFileSync } from "node:child_process";
|
|
299
299
|
import { existsSync as existsSync2 } from "node:fs";
|
|
300
300
|
import { homedir as homedir3 } from "node:os";
|
|
@@ -403,28 +403,61 @@ function runGate(opts) {
|
|
|
403
403
|
}
|
|
404
404
|
}
|
|
405
405
|
|
|
406
|
-
// dist/src/
|
|
407
|
-
import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, writeSync, mkdirSync as mkdirSync2, renameSync, existsSync as
|
|
406
|
+
// dist/src/skillify/state.js
|
|
407
|
+
import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, writeSync, mkdirSync as mkdirSync2, renameSync as renameSync2, existsSync as existsSync4, unlinkSync, openSync, closeSync } from "node:fs";
|
|
408
408
|
import { execSync } from "node:child_process";
|
|
409
|
-
import { homedir as
|
|
409
|
+
import { homedir as homedir5 } from "node:os";
|
|
410
410
|
import { createHash } from "node:crypto";
|
|
411
|
-
import { join as
|
|
412
|
-
|
|
413
|
-
|
|
411
|
+
import { join as join5, basename } from "node:path";
|
|
412
|
+
|
|
413
|
+
// dist/src/skillify/legacy-migration.js
|
|
414
|
+
import { existsSync as existsSync3, renameSync } from "node:fs";
|
|
415
|
+
import { homedir as homedir4 } from "node:os";
|
|
416
|
+
import { join as join4 } from "node:path";
|
|
417
|
+
var dlog = (msg) => log("skillify-migrate", msg);
|
|
418
|
+
var attempted = false;
|
|
419
|
+
function migrateLegacyStateDir() {
|
|
420
|
+
if (attempted)
|
|
421
|
+
return;
|
|
422
|
+
attempted = true;
|
|
423
|
+
const root = join4(homedir4(), ".deeplake", "state");
|
|
424
|
+
const legacy = join4(root, "skilify");
|
|
425
|
+
const current = join4(root, "skillify");
|
|
426
|
+
if (!existsSync3(legacy))
|
|
427
|
+
return;
|
|
428
|
+
if (existsSync3(current))
|
|
429
|
+
return;
|
|
430
|
+
try {
|
|
431
|
+
renameSync(legacy, current);
|
|
432
|
+
dlog(`migrated ${legacy} -> ${current}`);
|
|
433
|
+
} catch (err) {
|
|
434
|
+
const code = err.code;
|
|
435
|
+
if (code === "EXDEV" || code === "EPERM") {
|
|
436
|
+
dlog(`migration failed (${code}); leaving legacy dir in place`);
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
throw err;
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
// dist/src/skillify/state.js
|
|
444
|
+
var dlog2 = (msg) => log("skillify-state", msg);
|
|
445
|
+
var STATE_DIR = join5(homedir5(), ".deeplake", "state", "skillify");
|
|
414
446
|
var YIELD_BUF = new Int32Array(new SharedArrayBuffer(4));
|
|
415
447
|
var TRIGGER_THRESHOLD = (() => {
|
|
416
|
-
const n = Number(process.env.
|
|
448
|
+
const n = Number(process.env.HIVEMIND_SKILLIFY_EVERY_N_TURNS ?? "");
|
|
417
449
|
return Number.isInteger(n) && n > 0 ? n : 20;
|
|
418
450
|
})();
|
|
419
451
|
function statePath(projectKey) {
|
|
420
|
-
return
|
|
452
|
+
return join5(STATE_DIR, `${projectKey}.json`);
|
|
421
453
|
}
|
|
422
454
|
function lockPath(projectKey) {
|
|
423
|
-
return
|
|
455
|
+
return join5(STATE_DIR, `${projectKey}.lock`);
|
|
424
456
|
}
|
|
425
457
|
function readState(projectKey) {
|
|
458
|
+
migrateLegacyStateDir();
|
|
426
459
|
const p = statePath(projectKey);
|
|
427
|
-
if (!
|
|
460
|
+
if (!existsSync4(p))
|
|
428
461
|
return null;
|
|
429
462
|
try {
|
|
430
463
|
return JSON.parse(readFileSync2(p, "utf-8"));
|
|
@@ -433,13 +466,15 @@ function readState(projectKey) {
|
|
|
433
466
|
}
|
|
434
467
|
}
|
|
435
468
|
function writeState(projectKey, state) {
|
|
469
|
+
migrateLegacyStateDir();
|
|
436
470
|
mkdirSync2(STATE_DIR, { recursive: true });
|
|
437
471
|
const p = statePath(projectKey);
|
|
438
472
|
const tmp = `${p}.${process.pid}.${Date.now()}.tmp`;
|
|
439
473
|
writeFileSync2(tmp, JSON.stringify(state, null, 2));
|
|
440
|
-
|
|
474
|
+
renameSync2(tmp, p);
|
|
441
475
|
}
|
|
442
476
|
function withRmwLock(projectKey, fn) {
|
|
477
|
+
migrateLegacyStateDir();
|
|
443
478
|
mkdirSync2(STATE_DIR, { recursive: true });
|
|
444
479
|
const rmw = lockPath(projectKey) + ".rmw";
|
|
445
480
|
const deadline = Date.now() + 2e3;
|
|
@@ -451,11 +486,11 @@ function withRmwLock(projectKey, fn) {
|
|
|
451
486
|
if (e.code !== "EEXIST")
|
|
452
487
|
throw e;
|
|
453
488
|
if (Date.now() > deadline) {
|
|
454
|
-
|
|
489
|
+
dlog2(`rmw lock deadline exceeded for ${projectKey}, reclaiming stale lock`);
|
|
455
490
|
try {
|
|
456
491
|
unlinkSync(rmw);
|
|
457
492
|
} catch (unlinkErr) {
|
|
458
|
-
|
|
493
|
+
dlog2(`stale rmw lock unlink failed for ${projectKey}: ${unlinkErr.message}`);
|
|
459
494
|
}
|
|
460
495
|
continue;
|
|
461
496
|
}
|
|
@@ -469,7 +504,7 @@ function withRmwLock(projectKey, fn) {
|
|
|
469
504
|
try {
|
|
470
505
|
unlinkSync(rmw);
|
|
471
506
|
} catch (unlinkErr) {
|
|
472
|
-
|
|
507
|
+
dlog2(`rmw lock cleanup failed for ${projectKey}: ${unlinkErr.message}`);
|
|
473
508
|
}
|
|
474
509
|
}
|
|
475
510
|
}
|
|
@@ -509,18 +544,18 @@ function releaseWorkerLock(projectKey) {
|
|
|
509
544
|
}
|
|
510
545
|
}
|
|
511
546
|
|
|
512
|
-
// dist/src/
|
|
547
|
+
// dist/src/skillify/skillify-worker.js
|
|
513
548
|
var cfg = JSON.parse(readFileSync3(process.argv[2], "utf-8"));
|
|
514
549
|
var tmpDir = cfg.tmpDir;
|
|
515
|
-
var verdictPath =
|
|
516
|
-
var promptPath =
|
|
550
|
+
var verdictPath = join6(tmpDir, "verdict.json");
|
|
551
|
+
var promptPath = join6(tmpDir, "prompt.txt");
|
|
517
552
|
var SESSIONS_TO_MINE = 10;
|
|
518
553
|
var PAIR_CHAR_CAP = 2e3;
|
|
519
554
|
var TOTAL_PAIRS_CHAR_CAP = 4e4;
|
|
520
555
|
var EXISTING_SKILLS_CHAR_CAP = 3e4;
|
|
521
556
|
function wlog(msg) {
|
|
522
557
|
try {
|
|
523
|
-
appendFileSync2(cfg.
|
|
558
|
+
appendFileSync2(cfg.skillifyLog, `[${utcTimestamp()}] skillify-worker(${cfg.projectKey}): ${msg}
|
|
524
559
|
`);
|
|
525
560
|
} catch {
|
|
526
561
|
}
|
|
@@ -715,7 +750,7 @@ function buildPrompt(pairs) {
|
|
|
715
750
|
].join("\n");
|
|
716
751
|
}
|
|
717
752
|
function readVerdict(stdout) {
|
|
718
|
-
if (
|
|
753
|
+
if (existsSync5(verdictPath)) {
|
|
719
754
|
try {
|
|
720
755
|
const text = readFileSync3(verdictPath, "utf-8");
|
|
721
756
|
const v2 = parseVerdict(text);
|
|
@@ -784,9 +819,9 @@ async function main() {
|
|
|
784
819
|
timeoutMs: 12e4
|
|
785
820
|
});
|
|
786
821
|
try {
|
|
787
|
-
writeFileSync3(
|
|
822
|
+
writeFileSync3(join6(tmpDir, "gate-stdout.txt"), gate.stdout);
|
|
788
823
|
if (gate.stderr)
|
|
789
|
-
writeFileSync3(
|
|
824
|
+
writeFileSync3(join6(tmpDir, "gate-stderr.txt"), gate.stderr);
|
|
790
825
|
} catch {
|
|
791
826
|
}
|
|
792
827
|
if (gate.errored) {
|
package/openclaw/package.json
CHANGED
package/openclaw/skills/SKILL.md
CHANGED
|
@@ -45,28 +45,28 @@ Do NOT jump straight to reading raw JSONL files. Always start with `hivemind_ind
|
|
|
45
45
|
- `/hivemind_update` — shows how to install (ask the agent, or run `hivemind update` in your terminal)
|
|
46
46
|
- `/hivemind_autoupdate [on|off]` — 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)
|
|
47
47
|
|
|
48
|
-
## Skill Management (
|
|
48
|
+
## Skill Management (skillify)
|
|
49
49
|
|
|
50
50
|
Hivemind 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):
|
|
51
51
|
|
|
52
|
-
- `hivemind
|
|
53
|
-
- `hivemind
|
|
54
|
-
- `hivemind
|
|
55
|
-
- `hivemind
|
|
56
|
-
- `hivemind
|
|
57
|
-
- `hivemind
|
|
58
|
-
- `hivemind
|
|
59
|
-
- `hivemind
|
|
60
|
-
- `hivemind
|
|
61
|
-
- `hivemind
|
|
62
|
-
- `hivemind
|
|
63
|
-
- `hivemind
|
|
64
|
-
- `hivemind
|
|
65
|
-
- `hivemind
|
|
66
|
-
- `hivemind
|
|
67
|
-
- `hivemind
|
|
68
|
-
|
|
69
|
-
If the user asks to "pull skills from X", "share skills with the team", or similar, suggest the matching `hivemind
|
|
52
|
+
- `hivemind skillify` — show scope/team/install + per-project state
|
|
53
|
+
- `hivemind skillify pull` — sync skills for the current project from the org table
|
|
54
|
+
- `hivemind skillify pull --user <email>` — only that author's skills
|
|
55
|
+
- `hivemind skillify pull --users a,b,c` — multiple authors (CSV)
|
|
56
|
+
- `hivemind skillify pull --all-users` — explicit "no author filter"
|
|
57
|
+
- `hivemind skillify pull --to project|global` — install location (`<cwd>/.claude/skills/` vs `~/.claude/skills/`)
|
|
58
|
+
- `hivemind skillify pull --dry-run` — preview without touching disk
|
|
59
|
+
- `hivemind skillify pull --force` — overwrite local (creates `.bak`)
|
|
60
|
+
- `hivemind skillify pull <skill-name>` — pull only that one skill (combines with `--user`)
|
|
61
|
+
- `hivemind skillify unpull` — remove every skill previously installed by pull
|
|
62
|
+
- `hivemind skillify unpull --user <email>` — remove only that author's pulls
|
|
63
|
+
- `hivemind skillify unpull --not-mine` — remove all pulls except your own
|
|
64
|
+
- `hivemind skillify unpull --dry-run` — preview without touching disk
|
|
65
|
+
- `hivemind skillify scope <me|team|org>` — set sharing scope for new skills
|
|
66
|
+
- `hivemind skillify install <project|global>` — default install location
|
|
67
|
+
- `hivemind skillify team add|remove|list <name>` — manage team list
|
|
68
|
+
|
|
69
|
+
If 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.
|
|
70
70
|
|
|
71
71
|
## Limits
|
|
72
72
|
|