@deeplake/hivemind 0.7.79 → 0.7.80

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.
@@ -1828,7 +1828,7 @@ function extractLatestVersion(body) {
1828
1828
  return typeof v === "string" && v.length > 0 ? v : null;
1829
1829
  }
1830
1830
  function getInstalledVersion() {
1831
- return "0.7.79".length > 0 ? "0.7.79" : null;
1831
+ return "0.7.80".length > 0 ? "0.7.80" : null;
1832
1832
  }
1833
1833
  function isNewer(latest, current) {
1834
1834
  const parse = (v) => v.replace(/-.*$/, "").split(".").map(Number);
@@ -54,5 +54,5 @@
54
54
  }
55
55
  }
56
56
  },
57
- "version": "0.7.79"
57
+ "version": "0.7.80"
58
58
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hivemind",
3
- "version": "0.7.79",
3
+ "version": "0.7.80",
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",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deeplake/hivemind",
3
- "version": "0.7.79",
3
+ "version": "0.7.80",
4
4
  "description": "Cloud-backed persistent shared memory for AI agents powered by Deeplake",
5
5
  "type": "module",
6
6
  "repository": {
@@ -466,6 +466,94 @@ function runAutopullWorker(): void {
466
466
  }
467
467
  }
468
468
 
469
+ // ---------- SkillOpt: arm on org-skill use, react on the next user message ------------
470
+ // Mirrors the CC PreToolUse/UserPromptSubmit wiring, inlined because this extension is raw
471
+ // .ts with zero non-builtin deps (it can't import the skillify trigger). pi has no first-class
472
+ // `Skill` tool — it USES a skill by READING its SKILL.md — so we arm on a tool_result whose
473
+ // path is .../skills/<name--author>/SKILL.md, then on the next user prompt (the reaction) spawn
474
+ // the bundled skillopt-worker to judge + improve. Env-var names are the cross-process contract
475
+ // with the worker (SKILLOPT_ENV in src/skillify/skillopt-env.ts) — kept as literals here since
476
+ // the extension can't import. Fully swallowed; never blocks pi. Both call sites sit AFTER the
477
+ // handler's captureEnabled check, so the worker's own pi-judge subprocess (HIVEMIND_CAPTURE=false)
478
+ // can't re-arm/re-react — that's the recursion guard.
479
+ const PI_SKILLOPT_WORKER_PATH = join(homedir(), ".pi", "agent", "hivemind", "skillopt-worker.js");
480
+ // Mirror getStateDir()'s contract: a non-empty (trimmed) HIVEMIND_STATE_DIR overrides the default
481
+ // ~/.deeplake/state/skillify root, so pi's pending state co-locates with the rest of Skillify
482
+ // (and test-isolation overrides apply here too, not just in the shared trigger).
483
+ const SKILLOPT_STATE_ROOT = (typeof process.env.HIVEMIND_STATE_DIR === "string" && process.env.HIVEMIND_STATE_DIR.trim())
484
+ ? process.env.HIVEMIND_STATE_DIR.trim()
485
+ : join(homedir(), ".deeplake", "state", "skillify");
486
+ const SKILLOPT_PENDING_DIR = join(SKILLOPT_STATE_ROOT, "skillopt", "pending");
487
+ const SKILLOPT_JUDGE_WINDOW = 3; // K reactions to keep judging after a skill use (DEFAULT_JUDGE_WINDOW)
488
+
489
+ /** Recover an org-skill ref (name--author) from a path that loads a skill's SKILL.md, else null. */
490
+ function skilloptRefFromPath(p: unknown): string | null {
491
+ if (typeof p !== "string") return null;
492
+ const m = p.match(/\/skills\/([^/]+)\/SKILL\.md$/);
493
+ if (!m) return null;
494
+ const ref = m[1];
495
+ // org shape only: name--author, no plugin namespace / path separators / traversal.
496
+ if (ref.includes(":") || ref.includes("/") || ref.includes("\\") || ref.includes("..")) return null;
497
+ const i = ref.lastIndexOf("--");
498
+ return i > 0 && i + 2 < ref.length ? ref : null;
499
+ }
500
+
501
+ function skilloptPendingFile(sessionId: string): string {
502
+ const safe = sessionId.replace(/[^A-Za-z0-9_-]/g, "_").slice(0, 200);
503
+ return join(SKILLOPT_PENDING_DIR, `${safe}.json`);
504
+ }
505
+
506
+ /** tool_result: pi read an org skill's SKILL.md → open a K-message judgment window. */
507
+ function skilloptArm(sessionId: string, toolName: unknown, toolInput: any, toolCallId: unknown): void {
508
+ try {
509
+ if (process.env.HIVEMIND_SKILLOPT_DISABLED === "1") return;
510
+ // Arm only on a READ of the SKILL.md — USING a skill is reading it. An edit/write of a
511
+ // SKILL.md (even one whose input carries a matching path) must NOT open a judgment window.
512
+ if (!/^read/i.test(String(toolName ?? ""))) return;
513
+ const ref = skilloptRefFromPath(toolInput?.path ?? toolInput?.file ?? toolInput?.filePath);
514
+ if (!ref) return;
515
+ mkdirSync(SKILLOPT_PENDING_DIR, { recursive: true });
516
+ const f = skilloptPendingFile(sessionId);
517
+ const tmp = `${f}.${process.pid}.tmp`;
518
+ writeFileSync(tmp, JSON.stringify({ skill: ref, budget: SKILLOPT_JUDGE_WINDOW, toolUseId: typeof toolCallId === "string" ? toolCallId : undefined }));
519
+ renameSync(tmp, f);
520
+ logHm(`skillopt: armed ${ref} for ${sessionId}`);
521
+ } catch (e: any) { logHm(`skillopt arm swallowed: ${e?.message ?? e}`); }
522
+ }
523
+
524
+ /** input: the user's reaction → spawn the detached worker to judge the pending skill; spend budget. */
525
+ function skilloptReact(sessionId: string, reaction: string): void {
526
+ try {
527
+ if (process.env.HIVEMIND_SKILLOPT_DISABLED === "1" || process.env.HIVEMIND_WIKI_WORKER === "1") return;
528
+ if (!reaction.trim()) return;
529
+ const f = skilloptPendingFile(sessionId);
530
+ let p: { skill?: string; budget?: number; toolUseId?: string };
531
+ try { p = JSON.parse(readFileSync(f, "utf8")); } catch { return; } // no window open → no-op
532
+ if (!p?.skill || typeof p.budget !== "number") return;
533
+ if (!existsSync(PI_SKILLOPT_WORKER_PATH)) { logHm(`skillopt: worker bundle missing at ${PI_SKILLOPT_WORKER_PATH} — run 'hivemind pi install'`); return; }
534
+ // Spend one message of the budget; close the window when exhausted.
535
+ try {
536
+ if (p.budget - 1 <= 0) { unlinkSync(f); }
537
+ else { const tmp = `${f}.${process.pid}.tmp`; writeFileSync(tmp, JSON.stringify({ ...p, budget: p.budget - 1 })); renameSync(tmp, f); }
538
+ } catch { /* best-effort */ }
539
+ const child = spawn(process.execPath, [PI_SKILLOPT_WORKER_PATH], {
540
+ detached: true,
541
+ stdio: "ignore",
542
+ env: {
543
+ ...process.env,
544
+ HIVEMIND_SKILLOPT_WORKER: "1", // recursion guard (worker won't re-fire the trigger)
545
+ HIVEMIND_SKILLOPT_SESSION: sessionId,
546
+ HIVEMIND_SKILLOPT_SKILL: p.skill,
547
+ HIVEMIND_SKILLOPT_REACTION: reaction.slice(0, 8000),
548
+ HIVEMIND_SKILLOPT_AGENT: "pi", // judge/proposer run on pi (the user's own agent)
549
+ ...(p.toolUseId ? { HIVEMIND_SKILLOPT_TOOL_USE_ID: p.toolUseId } : {}),
550
+ },
551
+ });
552
+ child.unref();
553
+ logHm(`skillopt: spawned worker for ${p.skill} in ${sessionId} (agent=pi)`);
554
+ } catch (e: any) { logHm(`skillopt react swallowed: ${e?.message ?? e}`); }
555
+ }
556
+
469
557
  interface SummaryState {
470
558
  lastSummaryAt: number;
471
559
  lastSummaryCount: number;
@@ -1255,6 +1343,8 @@ export default function hivemindExtension(pi: ExtensionAPI): void {
1255
1343
  } catch (e: any) {
1256
1344
  logHm(`input: writeSessionRow swallowed: ${e?.message ?? e}`);
1257
1345
  }
1346
+ // SkillOpt: this prompt is the user's reaction to a recently-used org skill. Swallowed.
1347
+ skilloptReact(sessionId, text);
1258
1348
  maybeTriggerPeriodicSummary(creds, sessionId, cwd);
1259
1349
  });
1260
1350
 
@@ -1286,6 +1376,9 @@ export default function hivemindExtension(pi: ExtensionAPI): void {
1286
1376
  } catch (e: any) {
1287
1377
  logHm(`tool_result: writeSessionRow swallowed: ${e?.message ?? e}`);
1288
1378
  }
1379
+ // SkillOpt: pi USES an org skill by reading its SKILL.md — arm the judgment window on
1380
+ // a successful such read (skip errored reads). Swallowed.
1381
+ if (event.isError !== true) skilloptArm(sessionId, event.toolName, event.input, event.toolCallId);
1289
1382
  maybeTriggerPeriodicSummary(creds, sessionId, cwd);
1290
1383
  });
1291
1384