@deeplake/hivemind 0.7.16 → 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.
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- // dist/src/skilify/skilify-worker.js
4
- import { readFileSync as readFileSync3, writeFileSync as writeFileSync3, existsSync as existsSync4, appendFileSync as appendFileSync2, rmSync } from "node:fs";
5
- import { join as join5 } from "node:path";
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/skilify/extractors/index.js
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/skilify/skill-writer.js
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/skilify/skills-table.js
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/skilify/skills-table.js
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/skilify/gate-parser.js
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/skilify/gate-runner.js
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/skilify/state.js
407
- import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, writeSync, mkdirSync as mkdirSync2, renameSync, existsSync as existsSync3, unlinkSync, openSync, closeSync } from "node:fs";
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 homedir4 } from "node:os";
409
+ import { homedir as homedir5 } from "node:os";
410
410
  import { createHash } from "node:crypto";
411
- import { join as join4, basename } from "node:path";
412
- var dlog = (msg) => log("skilify-state", msg);
413
- var STATE_DIR = join4(homedir4(), ".deeplake", "state", "skilify");
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.HIVEMIND_SKILIFY_EVERY_N_TURNS ?? "");
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 join4(STATE_DIR, `${projectKey}.json`);
452
+ return join5(STATE_DIR, `${projectKey}.json`);
421
453
  }
422
454
  function lockPath(projectKey) {
423
- return join4(STATE_DIR, `${projectKey}.lock`);
455
+ return join5(STATE_DIR, `${projectKey}.lock`);
424
456
  }
425
457
  function readState(projectKey) {
458
+ migrateLegacyStateDir();
426
459
  const p = statePath(projectKey);
427
- if (!existsSync3(p))
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
- renameSync(tmp, p);
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
- dlog(`rmw lock deadline exceeded for ${projectKey}, reclaiming stale lock`);
489
+ dlog2(`rmw lock deadline exceeded for ${projectKey}, reclaiming stale lock`);
455
490
  try {
456
491
  unlinkSync(rmw);
457
492
  } catch (unlinkErr) {
458
- dlog(`stale rmw lock unlink failed for ${projectKey}: ${unlinkErr.message}`);
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
- dlog(`rmw lock cleanup failed for ${projectKey}: ${unlinkErr.message}`);
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/skilify/skilify-worker.js
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 = join5(tmpDir, "verdict.json");
516
- var promptPath = join5(tmpDir, "prompt.txt");
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.skilifyLog, `[${utcTimestamp()}] skilify-worker(${cfg.projectKey}): ${msg}
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 (existsSync4(verdictPath)) {
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(join5(tmpDir, "gate-stdout.txt"), gate.stdout);
822
+ writeFileSync3(join6(tmpDir, "gate-stdout.txt"), gate.stdout);
788
823
  if (gate.stderr)
789
- writeFileSync3(join5(tmpDir, "gate-stderr.txt"), gate.stderr);
824
+ writeFileSync3(join6(tmpDir, "gate-stderr.txt"), gate.stderr);
790
825
  } catch {
791
826
  }
792
827
  if (gate.errored) {
@@ -52,5 +52,5 @@
52
52
  }
53
53
  }
54
54
  },
55
- "version": "0.7.16"
55
+ "version": "0.7.18"
56
56
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hivemind",
3
- "version": "0.7.16",
3
+ "version": "0.7.18",
4
4
  "type": "module",
5
5
  "description": "Hivemind — cloud-backed persistent shared memory for AI agents, powered by DeepLake",
6
6
  "license": "Apache-2.0",
@@ -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 (skilify)
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 skilify` — show scope/team/install + per-project state
53
- - `hivemind skilify pull` — sync skills for the current project from the org table
54
- - `hivemind skilify pull --user <email>` — only that author's skills
55
- - `hivemind skilify pull --users a,b,c` — multiple authors (CSV)
56
- - `hivemind skilify pull --all-users` — explicit "no author filter"
57
- - `hivemind skilify pull --to project|global` — install location (`<cwd>/.claude/skills/` vs `~/.claude/skills/`)
58
- - `hivemind skilify pull --dry-run` — preview without touching disk
59
- - `hivemind skilify pull --force` — overwrite local (creates `.bak`)
60
- - `hivemind skilify pull <skill-name>` — pull only that one skill (combines with `--user`)
61
- - `hivemind skilify unpull` — remove every skill previously installed by pull
62
- - `hivemind skilify unpull --user <email>` — remove only that author's pulls
63
- - `hivemind skilify unpull --not-mine` — remove all pulls except your own
64
- - `hivemind skilify unpull --dry-run` — preview without touching disk
65
- - `hivemind skilify scope <me|team|org>` — set sharing scope for new skills
66
- - `hivemind skilify install <project|global>` — default install location
67
- - `hivemind skilify 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 skilify` command. Run `hivemind skilify --help` for the full reference.
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
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deeplake/hivemind",
3
- "version": "0.7.16",
3
+ "version": "0.7.18",
4
4
  "description": "Cloud-backed persistent shared memory for AI agents powered by Deeplake",
5
5
  "type": "module",
6
6
  "repository": {
@@ -26,12 +26,12 @@
26
26
  import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
27
27
  import {
28
28
  readFileSync, existsSync, appendFileSync, mkdirSync, writeFileSync,
29
- openSync, closeSync, constants as fsConstants,
29
+ openSync, closeSync, renameSync, constants as fsConstants,
30
30
  } from "node:fs";
31
31
  import { homedir, tmpdir } from "node:os";
32
32
  import { join, dirname } from "node:path";
33
33
  import { connect } from "node:net";
34
- import { spawn, execSync } from "node:child_process";
34
+ import { spawn, spawnSync, execSync } from "node:child_process";
35
35
  import { createHash } from "node:crypto";
36
36
 
37
37
  // ---------- diagnostic logging --------------------------------------------------
@@ -205,11 +205,51 @@ function tryEmbedOverSocket(text: string, kind: "document" | "query"): Promise<n
205
205
 
206
206
  const SUMMARY_STATE_DIR = join(homedir(), ".claude", "hooks", "summary-state");
207
207
  const PI_WIKI_WORKER_PATH = join(homedir(), ".pi", "agent", "hivemind", "wiki-worker.js");
208
- // Skilify worker installed alongside wiki-worker by `hivemind pi install`.
208
+ // Skillify worker installed alongside wiki-worker by `hivemind pi install`.
209
209
  // Spawned on session_shutdown to mine reusable Claude skills from the just-
210
210
  // finished session. Same shared bundle used by CC/Codex/Cursor/Hermes.
211
- const PI_SKILIFY_WORKER_PATH = join(homedir(), ".pi", "agent", "hivemind", "skilify-worker.js");
212
- const SKILIFY_STATE_DIR = join(homedir(), ".deeplake", "state", "skilify");
211
+ const PI_SKILLIFY_WORKER_PATH = join(homedir(), ".pi", "agent", "hivemind", "skillify-worker.js");
212
+ // Auto-pull worker installed alongside wiki-worker / skillify-worker by
213
+ // `hivemind pi install`. Spawned synchronously on session_start to fetch
214
+ // all-author skills from the org table. The worker is a thin wrapper
215
+ // around the shared autoPullSkills() that codex / cursor / hermes call
216
+ // directly — pi can't import the TS module (raw .ts, zero deps), so it
217
+ // routes through this child process. Keeps pi's pulled skills layout +
218
+ // symlink fan-out in lockstep with the other agents automatically.
219
+ const PI_AUTOPULL_WORKER_PATH = join(homedir(), ".pi", "agent", "hivemind", "autopull-worker.js");
220
+
221
+ /**
222
+ * Synchronously run the bundled auto-pull worker. Bounded by a 6s
223
+ * wall-clock cap (the worker's internal timeout is 5s; the extra second
224
+ * is defence-in-depth for spawn overhead). Returns when the worker
225
+ * exits, regardless of exit code — autoPullSkills is documented as
226
+ * never-rejecting and the worker swallows all failures, so a non-zero
227
+ * exit code can only mean an unrecoverable runtime error that we want
228
+ * to ignore here too. Pi's session_start blocks on this, mirroring the
229
+ * `await autoPullSkills()` in the other agents.
230
+ */
231
+ function runAutopullWorker(): void {
232
+ if (!existsSync(PI_AUTOPULL_WORKER_PATH)) {
233
+ logHm(`autopull: worker bundle missing at ${PI_AUTOPULL_WORKER_PATH} — skipping`);
234
+ return;
235
+ }
236
+ try {
237
+ const result = spawnSync(process.execPath, [PI_AUTOPULL_WORKER_PATH], {
238
+ stdio: "ignore",
239
+ timeout: 6_000,
240
+ env: process.env,
241
+ });
242
+ if (result.error) {
243
+ logHm(`autopull: spawn failed (swallowed): ${result.error.message}`);
244
+ } else if (result.signal) {
245
+ logHm(`autopull: worker killed by signal ${result.signal} (likely the 6s cap)`);
246
+ } else {
247
+ logHm(`autopull: worker exited code=${result.status}`);
248
+ }
249
+ } catch (e: any) {
250
+ logHm(`autopull: spawn threw (swallowed): ${e?.message ?? e}`);
251
+ }
252
+ }
213
253
 
214
254
  interface SummaryState {
215
255
  lastSummaryAt: number;
@@ -404,20 +444,20 @@ function spawnWikiWorker(
404
444
  }
405
445
  }
406
446
 
407
- // ---------- skilify worker spawn ---------------------------------------------
447
+ // ---------- skillify worker spawn ---------------------------------------------
408
448
  //
409
- // Mirror of src/skilify/spawn-skilify-worker.ts and src/skilify/triggers.ts —
449
+ // Mirror of src/skillify/spawn-skillify-worker.ts and src/skillify/triggers.ts —
410
450
  // inlined here because pi/extension-source/hivemind.ts is shipped as raw .ts
411
451
  // with zero non-builtin runtime dependencies (pi compiles + loads it at
412
- // extension-load time). The shared TypeScript modules under src/skilify/
452
+ // extension-load time). The shared TypeScript modules under src/skillify/
413
453
  // can't be imported from this file.
414
454
  //
415
- // The skilify worker mines the just-finished session for reusable Claude
455
+ // The skillify worker mines the just-finished session for reusable Claude
416
456
  // skills, gates each cluster via a model call, and writes SKILL.md files +
417
457
  // rows in the org's skills Deeplake table.
418
458
 
419
- /** Stable project key — sha1(cwd) truncated, mirrors src/skilify/state.ts deriveProjectKey. */
420
- function deriveSkilifyProjectKey(cwd: string): { key: string; project: string } {
459
+ /** Stable project key — sha1(cwd) truncated, mirrors src/skillify/state.ts deriveProjectKey. */
460
+ function deriveSkillifyProjectKey(cwd: string): { key: string; project: string } {
421
461
  const project = (cwd ?? "").split("/").pop() || "unknown";
422
462
  // Pi's extension can't easily run `git config` synchronously here; use cwd
423
463
  // as the signature. Two checkouts of the same repo at different paths get
@@ -427,15 +467,15 @@ function deriveSkilifyProjectKey(cwd: string): { key: string; project: string }
427
467
  return { key, project };
428
468
  }
429
469
 
430
- function spawnPiSkilifyWorker(creds: Creds, sessionId: string, cwd: string): void {
431
- if (!existsSync(PI_SKILIFY_WORKER_PATH)) {
432
- logHm(`spawnPiSkilifyWorker: no worker at ${PI_SKILIFY_WORKER_PATH} — install via 'hivemind pi install' or rebuild`);
470
+ function spawnPiSkillifyWorker(creds: Creds, sessionId: string, cwd: string): void {
471
+ if (!existsSync(PI_SKILLIFY_WORKER_PATH)) {
472
+ logHm(`spawnPiSkillifyWorker: no worker at ${PI_SKILLIFY_WORKER_PATH} — install via 'hivemind pi install' or rebuild`);
433
473
  return;
434
474
  }
435
- const { key: projectKey, project } = deriveSkilifyProjectKey(cwd);
475
+ const { key: projectKey, project } = deriveSkillifyProjectKey(cwd);
436
476
 
437
477
  // No spawn-side lock: the worker itself acquires `<projectKey>.lock` via
438
- // src/skilify/state.ts:tryAcquireWorkerLock and releases it on exit (with
478
+ // src/skillify/state.ts:tryAcquireWorkerLock and releases it on exit (with
439
479
  // a 10-min stale-lock fallback). A spawn-side lock here would create a
440
480
  // SECOND lockfile (`<projectKey>.worker.lock`) that nobody releases,
441
481
  // permanently blocking subsequent spawns from the same Pi runtime
@@ -443,12 +483,12 @@ function spawnPiSkilifyWorker(creds: Creds, sessionId: string, cwd: string): voi
443
483
  // back-to-back spawns where a worker is in flight cost only one extra
444
484
  // node cold-start (~50ms) before the worker self-skips on the lock.
445
485
 
446
- const tmpDir = join(tmpdir(), `deeplake-skilify-${projectKey}-${Date.now()}`);
486
+ const tmpDir = join(tmpdir(), `deeplake-skillify-${projectKey}-${Date.now()}`);
447
487
  try { mkdirSync(tmpDir, { recursive: true, mode: 0o700 }); }
448
- catch (e: any) { logHm(`spawnPiSkilifyWorker: mkdir failed: ${e?.message ?? e}`); return; }
488
+ catch (e: any) { logHm(`spawnPiSkillifyWorker: mkdir failed: ${e?.message ?? e}`); return; }
449
489
  const configPath = join(tmpDir, "config.json");
450
490
 
451
- // Same shape the spawn-skilify-worker.ts module writes for the other agents.
491
+ // Same shape the spawn-skillify-worker.ts module writes for the other agents.
452
492
  // Defaults match scope-config.ts: scope=me, install=project, no team list.
453
493
  // Pi-specific: no per-agent gate binary (`gateBin: null`) — the worker's
454
494
  // gate-runner falls back to its agent dispatch which for `agent: "pi"`
@@ -476,21 +516,21 @@ function spawnPiSkilifyWorker(creds: Creds, sessionId: string, cwd: string): voi
476
516
  // pi-specific gate args — match wikiWorker config defaults (google + gemini-2.5-flash)
477
517
  piProvider: process.env.HIVEMIND_PI_PROVIDER ?? "google",
478
518
  piModel: process.env.HIVEMIND_PI_MODEL ?? "gemini-2.5-flash",
479
- skilifyLog: join(homedir(), ".deeplake", "hivemind-pi-skilify.log"),
519
+ skillifyLog: join(homedir(), ".deeplake", "hivemind-pi-skillify.log"),
480
520
  currentSessionId: sessionId,
481
521
  };
482
522
  try { writeFileSync(configPath, JSON.stringify(config), { mode: 0o600 }); }
483
- catch (e: any) { logHm(`spawnPiSkilifyWorker: config write failed: ${e?.message ?? e}`); return; }
523
+ catch (e: any) { logHm(`spawnPiSkillifyWorker: config write failed: ${e?.message ?? e}`); return; }
484
524
 
485
- logHm(`spawnPiSkilifyWorker: spawning ${PI_SKILIFY_WORKER_PATH} project=${project} key=${projectKey} session=${sessionId}`);
525
+ logHm(`spawnPiSkillifyWorker: spawning ${PI_SKILLIFY_WORKER_PATH} project=${project} key=${projectKey} session=${sessionId}`);
486
526
  try {
487
- spawn(process.execPath, [PI_SKILIFY_WORKER_PATH, configPath], {
527
+ spawn(process.execPath, [PI_SKILLIFY_WORKER_PATH, configPath], {
488
528
  detached: true,
489
529
  stdio: "ignore",
490
- env: { ...process.env, HIVEMIND_SKILIFY_WORKER: "1", HIVEMIND_CAPTURE: "false" },
530
+ env: { ...process.env, HIVEMIND_SKILLIFY_WORKER: "1", HIVEMIND_CAPTURE: "false" },
491
531
  }).unref();
492
532
  } catch (e: any) {
493
- logHm(`spawnPiSkilifyWorker: spawn failed: ${e?.message ?? e}`);
533
+ logHm(`spawnPiSkillifyWorker: spawn failed: ${e?.message ?? e}`);
494
534
  }
495
535
  }
496
536
 
@@ -656,23 +696,23 @@ Organization management — each argument is SEPARATE (do NOT quote subcommands
656
696
  - hivemind members — list members
657
697
  - hivemind remove <user-id> — remove member
658
698
 
659
- SKILLS (skilify) — mine + share reusable skills across the org. Run these in a terminal (or via shell if available):
660
- - hivemind skilify — show scope/team/install + per-project state
661
- - hivemind skilify pull — sync project skills from the org table
662
- - hivemind skilify pull --user <email> — only that author's skills
663
- - hivemind skilify pull --users a,b,c — multiple authors (CSV)
664
- - hivemind skilify pull --all-users — explicit "no author filter"
665
- - hivemind skilify pull --to project|global — install location
666
- - hivemind skilify pull --dry-run — preview only
667
- - hivemind skilify pull --force — overwrite local (creates .bak)
668
- - hivemind skilify pull <skill-name> — pull only that skill (combines with --user)
669
- - hivemind skilify unpull — remove every skill previously installed by pull
670
- - hivemind skilify unpull --user <email> — remove only that author's pulls
671
- - hivemind skilify unpull --not-mine — remove all pulls except your own
672
- - hivemind skilify unpull --dry-run — preview without touching disk
673
- - hivemind skilify scope <me|team|org> — sharing scope for new skills
674
- - hivemind skilify install <project|global> — default install location
675
- - hivemind skilify team add|remove|list <name> — manage team list`;
699
+ SKILLS (skillify) — mine + share reusable skills across the org. Run these in a terminal (or via shell if available):
700
+ - hivemind skillify — show scope/team/install + per-project state
701
+ - hivemind skillify pull — sync project skills from the org table
702
+ - hivemind skillify pull --user <email> — only that author's skills
703
+ - hivemind skillify pull --users a,b,c — multiple authors (CSV)
704
+ - hivemind skillify pull --all-users — explicit "no author filter"
705
+ - hivemind skillify pull --to project|global — install location
706
+ - hivemind skillify pull --dry-run — preview only
707
+ - hivemind skillify pull --force — overwrite local (creates .bak)
708
+ - hivemind skillify pull <skill-name> — pull only that skill (combines with --user)
709
+ - hivemind skillify unpull — remove every skill previously installed by pull
710
+ - hivemind skillify unpull --user <email> — remove only that author's pulls
711
+ - hivemind skillify unpull --not-mine — remove all pulls except your own
712
+ - hivemind skillify unpull --dry-run — preview without touching disk
713
+ - hivemind skillify scope <me|team|org> — sharing scope for new skills
714
+ - hivemind skillify install <project|global> — default install location
715
+ - hivemind skillify team add|remove|list <name> — manage team list`;
676
716
 
677
717
  export default function hivemindExtension(pi: ExtensionAPI): void {
678
718
  const captureEnabled = process.env.HIVEMIND_CAPTURE !== "false";
@@ -850,6 +890,15 @@ export default function hivemindExtension(pi: ExtensionAPI): void {
850
890
  }
851
891
  logHm(`session_start: sessions table visible=${visible} (probe took ${Date.now() - start}ms)`);
852
892
  }
893
+
894
+ // Auto-pull all-author skills via the bundled worker (same shared
895
+ // autoPullSkills as codex / cursor / hermes — see runAutopullWorker
896
+ // above). Synchronous so freshly pulled skills are visible to pi
897
+ // before the first prompt; 6s upper bound. Throttling, layout, and
898
+ // per-agent symlink fan-out all live in the worker — no inline
899
+ // duplicate maintained here.
900
+ if (creds) runAutopullWorker();
901
+
853
902
  const additional = creds
854
903
  ? `${CONTEXT_PREAMBLE}\nLogged in to Deeplake as org: ${creds.orgName ?? creds.orgId} (workspace: ${creds.workspaceId}).`
855
904
  : `${CONTEXT_PREAMBLE}\nNot logged in to Deeplake. Run \`hivemind login\` to authenticate.`;
@@ -958,13 +1007,13 @@ export default function hivemindExtension(pi: ExtensionAPI): void {
958
1007
  // skips if a periodic worker is mid-flight. Non-fatal either way.
959
1008
  spawnWikiWorker(creds, sessionId, cwd, "final");
960
1009
 
961
- // Also kick off the skilify worker so this session's prompt+answer
1010
+ // Also kick off the skillify worker so this session's prompt+answer
962
1011
  // pairs become candidates for reusable skills. Lock keyed on
963
1012
  // projectKey, not sessionId — multiple sessions in the same project
964
1013
  // shouldn't race the gate. Non-fatal: failure here only loses the
965
1014
  // mining for this one session, never breaks the wiki summary above.
966
- try { spawnPiSkilifyWorker(creds, sessionId, cwd); }
967
- catch (e: any) { logHm(`session_shutdown: skilify spawn threw: ${e?.message ?? e}`); }
1015
+ try { spawnPiSkillifyWorker(creds, sessionId, cwd); }
1016
+ catch (e: any) { logHm(`session_shutdown: skillify spawn threw: ${e?.message ?? e}`); }
968
1017
  });
969
1018
 
970
1019
  // Module-load breadcrumb so we know the extension's default export ran at all.