@deeplake/hivemind 0.7.79 → 0.7.81

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/.claude-plugin/marketplace.json +3 -3
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/bundle/cli.js +140 -94
  4. package/codex/bundle/capture.js +232 -72
  5. package/codex/bundle/commands/auth-login.js +14 -10
  6. package/codex/bundle/embeddings/embed-daemon.js +9 -5
  7. package/codex/bundle/graph-on-stop.js +32 -28
  8. package/codex/bundle/graph-pull-worker.js +27 -23
  9. package/codex/bundle/pre-tool-use.js +366 -123
  10. package/codex/bundle/session-start-setup.js +18 -14
  11. package/codex/bundle/session-start.js +26 -22
  12. package/codex/bundle/shell/deeplake-shell.js +30 -26
  13. package/codex/bundle/skillify-worker.js +14 -10
  14. package/codex/bundle/skillopt-worker.js +2061 -0
  15. package/codex/bundle/stop.js +50 -45
  16. package/codex/bundle/wiki-worker.js +18 -14
  17. package/cursor/bundle/capture.js +71 -44
  18. package/cursor/bundle/commands/auth-login.js +14 -10
  19. package/cursor/bundle/embeddings/embed-daemon.js +9 -5
  20. package/cursor/bundle/graph-on-stop.js +32 -28
  21. package/cursor/bundle/graph-pull-worker.js +27 -23
  22. package/cursor/bundle/pre-tool-use.js +28 -24
  23. package/cursor/bundle/session-end.js +40 -36
  24. package/cursor/bundle/session-start.js +39 -35
  25. package/cursor/bundle/shell/deeplake-shell.js +30 -26
  26. package/cursor/bundle/skillify-worker.js +14 -10
  27. package/cursor/bundle/wiki-worker.js +18 -14
  28. package/hermes/bundle/capture.js +235 -85
  29. package/hermes/bundle/commands/auth-login.js +14 -10
  30. package/hermes/bundle/embeddings/embed-daemon.js +9 -5
  31. package/hermes/bundle/graph-on-stop.js +32 -28
  32. package/hermes/bundle/graph-pull-worker.js +27 -23
  33. package/hermes/bundle/pre-tool-use.js +298 -74
  34. package/hermes/bundle/session-end.js +40 -36
  35. package/hermes/bundle/session-start.js +39 -35
  36. package/hermes/bundle/shell/deeplake-shell.js +30 -26
  37. package/hermes/bundle/skillify-worker.js +14 -10
  38. package/hermes/bundle/skillopt-worker.js +2061 -0
  39. package/hermes/bundle/wiki-worker.js +18 -14
  40. package/mcp/bundle/server.js +15 -11
  41. package/openclaw/dist/index.js +11 -7
  42. package/openclaw/dist/skillify-worker.js +14 -10
  43. package/openclaw/openclaw.plugin.json +1 -1
  44. package/openclaw/package.json +1 -1
  45. package/package.json +1 -1
  46. package/pi/extension-source/hivemind.ts +93 -0
@@ -1,19 +1,19 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // dist/src/hooks/hermes/wiki-worker.js
4
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, existsSync as existsSync4, appendFileSync as appendFileSync2, mkdirSync as mkdirSync3, rmSync } from "node:fs";
4
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, existsSync as existsSync4, appendFileSync as appendFileSync2, mkdirSync as mkdirSync4, rmSync } from "node:fs";
5
5
  import { execFileSync } from "node:child_process";
6
- import { dirname as dirname2, join as join6 } from "node:path";
6
+ import { dirname as dirname3, join as join6 } from "node:path";
7
7
  import { fileURLToPath } from "node:url";
8
8
 
9
9
  // dist/src/hooks/summary-state.js
10
- import { readFileSync, writeFileSync, writeSync, mkdirSync, renameSync, existsSync, unlinkSync, openSync, closeSync, statSync } from "node:fs";
10
+ import { readFileSync, writeFileSync, writeSync, mkdirSync as mkdirSync2, renameSync, existsSync, unlinkSync, openSync, closeSync, statSync } from "node:fs";
11
11
  import { homedir as homedir2 } from "node:os";
12
12
  import { join as join2 } from "node:path";
13
13
 
14
14
  // dist/src/utils/debug.js
15
- import { appendFileSync } from "node:fs";
16
- import { join } from "node:path";
15
+ import { appendFileSync, mkdirSync } from "node:fs";
16
+ import { dirname, join } from "node:path";
17
17
  import { homedir } from "node:os";
18
18
  var LOG = join(homedir(), ".deeplake", "hook-debug.log");
19
19
  function isDebug() {
@@ -22,8 +22,12 @@ function isDebug() {
22
22
  function log(tag, msg) {
23
23
  if (!isDebug())
24
24
  return;
25
- appendFileSync(LOG, `${(/* @__PURE__ */ new Date()).toISOString()} [${tag}] ${msg}
25
+ try {
26
+ mkdirSync(dirname(LOG), { recursive: true });
27
+ appendFileSync(LOG, `${(/* @__PURE__ */ new Date()).toISOString()} [${tag}] ${msg}
26
28
  `);
29
+ } catch {
30
+ }
27
31
  }
28
32
 
29
33
  // dist/src/hooks/summary-state.js
@@ -47,14 +51,14 @@ function readState(sessionId) {
47
51
  }
48
52
  }
49
53
  function writeState(sessionId, state) {
50
- mkdirSync(STATE_DIR, { recursive: true });
54
+ mkdirSync2(STATE_DIR, { recursive: true });
51
55
  const p = statePath(sessionId);
52
56
  const tmp = `${p}.${process.pid}.${Date.now()}.tmp`;
53
57
  writeFileSync(tmp, JSON.stringify(state));
54
58
  renameSync(tmp, p);
55
59
  }
56
60
  function withRmwLock(sessionId, fn) {
57
- mkdirSync(STATE_DIR, { recursive: true });
61
+ mkdirSync2(STATE_DIR, { recursive: true });
58
62
  const rmwLock = statePath(sessionId) + ".rmw";
59
63
  const deadline = Date.now() + 2e3;
60
64
  let fd = null;
@@ -543,9 +547,9 @@ import { join as join5 } from "node:path";
543
547
  import { pathToFileURL } from "node:url";
544
548
 
545
549
  // dist/src/user-config.js
546
- import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync3, renameSync as renameSync2, writeFileSync as writeFileSync2 } from "node:fs";
550
+ import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync3, renameSync as renameSync2, writeFileSync as writeFileSync2 } from "node:fs";
547
551
  import { homedir as homedir4 } from "node:os";
548
- import { dirname, join as join4 } from "node:path";
552
+ import { dirname as dirname2, join as join4 } from "node:path";
549
553
  var _configPath = () => process.env.HIVEMIND_CONFIG_PATH ?? join4(homedir4(), ".deeplake", "config.json");
550
554
  var _cache = null;
551
555
  var _migrated = false;
@@ -570,9 +574,9 @@ function writeUserConfig(patch) {
570
574
  const current = readUserConfig();
571
575
  const merged = deepMerge(current, patch);
572
576
  const path = _configPath();
573
- const dir = dirname(path);
577
+ const dir = dirname2(path);
574
578
  if (!existsSync3(dir))
575
- mkdirSync2(dir, { recursive: true });
579
+ mkdirSync3(dir, { recursive: true });
576
580
  const tmp = `${path}.tmp.${process.pid}`;
577
581
  writeFileSync2(tmp, JSON.stringify(merged, null, 2) + "\n", "utf-8");
578
582
  renameSync2(tmp, path);
@@ -671,7 +675,7 @@ var tmpJsonl = join6(tmpDir, "session.jsonl");
671
675
  var tmpSummary = join6(tmpDir, "summary.md");
672
676
  function wlog(msg) {
673
677
  try {
674
- mkdirSync3(cfg.hooksDir, { recursive: true });
678
+ mkdirSync4(cfg.hooksDir, { recursive: true });
675
679
  appendFileSync2(cfg.wikiLog, `[${(/* @__PURE__ */ new Date()).toISOString().replace("T", " ").slice(0, 19)}] wiki-worker(${cfg.sessionId}): ${msg}
676
680
  `);
677
681
  } catch {
@@ -773,7 +777,7 @@ async function main() {
773
777
  let embedding = null;
774
778
  if (!embeddingsDisabled()) {
775
779
  try {
776
- const daemonEntry = join6(dirname2(fileURLToPath(import.meta.url)), "embeddings", "embed-daemon.js");
780
+ const daemonEntry = join6(dirname3(fileURLToPath(import.meta.url)), "embeddings", "embed-daemon.js");
777
781
  embedding = await new EmbedClient({ daemonEntry }).embed(text, "document");
778
782
  } catch (e) {
779
783
  wlog(`summary embedding failed, writing NULL: ${e.message}`);
@@ -6809,7 +6809,7 @@ __export(index_marker_store_exports, {
6809
6809
  hasFreshIndexMarker: () => hasFreshIndexMarker,
6810
6810
  writeIndexMarker: () => writeIndexMarker
6811
6811
  });
6812
- import { existsSync as existsSync2, mkdirSync as mkdirSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "node:fs";
6812
+ import { existsSync as existsSync2, mkdirSync as mkdirSync5, readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "node:fs";
6813
6813
  import { join as join6 } from "node:path";
6814
6814
  import { tmpdir } from "node:os";
6815
6815
  function getIndexMarkerDir() {
@@ -6833,7 +6833,7 @@ function hasFreshIndexMarker(markerPath) {
6833
6833
  }
6834
6834
  }
6835
6835
  function writeIndexMarker(markerPath) {
6836
- mkdirSync4(getIndexMarkerDir(), { recursive: true });
6836
+ mkdirSync5(getIndexMarkerDir(), { recursive: true });
6837
6837
  writeFileSync4(markerPath, JSON.stringify({ updatedAt: (/* @__PURE__ */ new Date()).toISOString() }), "utf-8");
6838
6838
  }
6839
6839
  var INDEX_MARKER_TTL_MS;
@@ -23352,8 +23352,8 @@ function loadConfig() {
23352
23352
  import { randomUUID as randomUUID2 } from "node:crypto";
23353
23353
 
23354
23354
  // dist/src/utils/debug.js
23355
- import { appendFileSync } from "node:fs";
23356
- import { join as join4 } from "node:path";
23355
+ import { appendFileSync, mkdirSync as mkdirSync3 } from "node:fs";
23356
+ import { dirname, join as join4 } from "node:path";
23357
23357
  import { homedir as homedir4 } from "node:os";
23358
23358
  var LOG = join4(homedir4(), ".deeplake", "hook-debug.log");
23359
23359
  function isDebug() {
@@ -23362,8 +23362,12 @@ function isDebug() {
23362
23362
  function log(tag, msg) {
23363
23363
  if (!isDebug())
23364
23364
  return;
23365
- appendFileSync(LOG, `${(/* @__PURE__ */ new Date()).toISOString()} [${tag}] ${msg}
23365
+ try {
23366
+ mkdirSync3(dirname(LOG), { recursive: true });
23367
+ appendFileSync(LOG, `${(/* @__PURE__ */ new Date()).toISOString()} [${tag}] ${msg}
23366
23368
  `);
23369
+ } catch {
23370
+ }
23367
23371
  }
23368
23372
 
23369
23373
  // dist/src/utils/sql.js
@@ -23563,7 +23567,7 @@ async function healMissingColumns(args) {
23563
23567
  }
23564
23568
 
23565
23569
  // dist/src/notifications/queue.js
23566
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, renameSync, mkdirSync as mkdirSync3, openSync, closeSync, unlinkSync as unlinkSync2, statSync } from "node:fs";
23570
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, renameSync, mkdirSync as mkdirSync4, openSync, closeSync, unlinkSync as unlinkSync2, statSync } from "node:fs";
23567
23571
  import { join as join5, resolve } from "node:path";
23568
23572
  import { homedir as homedir5 } from "node:os";
23569
23573
  import { setTimeout as sleep } from "node:timers/promises";
@@ -23601,14 +23605,14 @@ function writeQueue(q) {
23601
23605
  if (!_isQueuePathInsideHome(path, home)) {
23602
23606
  throw new Error(`notifications-queue write blocked: ${path} is outside ${home}`);
23603
23607
  }
23604
- mkdirSync3(join5(home, ".deeplake"), { recursive: true, mode: 448 });
23608
+ mkdirSync4(join5(home, ".deeplake"), { recursive: true, mode: 448 });
23605
23609
  const tmp = `${path}.${process.pid}.tmp`;
23606
23610
  writeFileSync3(tmp, JSON.stringify(q, null, 2), { mode: 384 });
23607
23611
  renameSync(tmp, path);
23608
23612
  }
23609
23613
  async function withQueueLock(fn) {
23610
23614
  const path = lockPath();
23611
- mkdirSync3(join5(homedir5(), ".deeplake"), { recursive: true, mode: 448 });
23615
+ mkdirSync4(join5(homedir5(), ".deeplake"), { recursive: true, mode: 448 });
23612
23616
  let fd = null;
23613
23617
  for (let attempt = 0; attempt < LOCK_RETRY_MAX; attempt++) {
23614
23618
  try {
@@ -24561,8 +24565,8 @@ import { readFileSync as readFileSync7 } from "node:fs";
24561
24565
  import { join as join8 } from "node:path";
24562
24566
 
24563
24567
  // dist/src/cli/util.js
24564
- import { existsSync as existsSync3, mkdirSync as mkdirSync5, readFileSync as readFileSync6, writeFileSync as writeFileSync5, cpSync, symlinkSync, unlinkSync as unlinkSync3, lstatSync } from "node:fs";
24565
- import { join as join7, dirname } from "node:path";
24568
+ import { existsSync as existsSync3, mkdirSync as mkdirSync6, readFileSync as readFileSync6, writeFileSync as writeFileSync5, cpSync, symlinkSync, unlinkSync as unlinkSync3, lstatSync } from "node:fs";
24569
+ import { join as join7, dirname as dirname2 } from "node:path";
24566
24570
  import { homedir as homedir6 } from "node:os";
24567
24571
  import { fileURLToPath } from "node:url";
24568
24572
  import { createInterface } from "node:readline";
@@ -24576,7 +24580,7 @@ function pkgRoot() {
24576
24580
  return dir;
24577
24581
  } catch {
24578
24582
  }
24579
- const parent = dirname(dir);
24583
+ const parent = dirname2(dir);
24580
24584
  if (parent === dir)
24581
24585
  break;
24582
24586
  dir = parent;
@@ -198,8 +198,8 @@ async function switchWorkspace(workspaceId) {
198
198
  import { randomUUID as randomUUID2 } from "node:crypto";
199
199
 
200
200
  // src/utils/debug.ts
201
- import { appendFileSync } from "node:fs";
202
- import { join as join2 } from "node:path";
201
+ import { appendFileSync, mkdirSync as mkdirSync2 } from "node:fs";
202
+ import { dirname, join as join2 } from "node:path";
203
203
  import { homedir as homedir2 } from "node:os";
204
204
  var LOG = join2(homedir2(), ".deeplake", "hook-debug.log");
205
205
  function isDebug() {
@@ -207,8 +207,12 @@ function isDebug() {
207
207
  }
208
208
  function log(tag, msg) {
209
209
  if (!isDebug()) return;
210
- appendFileSync(LOG, `${(/* @__PURE__ */ new Date()).toISOString()} [${tag}] ${msg}
210
+ try {
211
+ mkdirSync2(dirname(LOG), { recursive: true });
212
+ appendFileSync(LOG, `${(/* @__PURE__ */ new Date()).toISOString()} [${tag}] ${msg}
211
213
  `);
214
+ } catch {
215
+ }
212
216
  }
213
217
 
214
218
  // src/utils/sql.ts
@@ -406,7 +410,7 @@ async function healMissingColumns(args) {
406
410
  }
407
411
 
408
412
  // src/notifications/queue.ts
409
- import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, renameSync, mkdirSync as mkdirSync2, openSync, closeSync, unlinkSync, statSync } from "node:fs";
413
+ import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, renameSync, mkdirSync as mkdirSync3, openSync, closeSync, unlinkSync, statSync } from "node:fs";
410
414
  import { join as join3, resolve } from "node:path";
411
415
  import { homedir as homedir3 } from "node:os";
412
416
  import { setTimeout as sleep } from "node:timers/promises";
@@ -444,14 +448,14 @@ function writeQueue(q) {
444
448
  if (!_isQueuePathInsideHome(path, home)) {
445
449
  throw new Error(`notifications-queue write blocked: ${path} is outside ${home}`);
446
450
  }
447
- mkdirSync2(join3(home, ".deeplake"), { recursive: true, mode: 448 });
451
+ mkdirSync3(join3(home, ".deeplake"), { recursive: true, mode: 448 });
448
452
  const tmp = `${path}.${process.pid}.tmp`;
449
453
  writeFileSync2(tmp, JSON.stringify(q, null, 2), { mode: 384 });
450
454
  renameSync(tmp, path);
451
455
  }
452
456
  async function withQueueLock(fn) {
453
457
  const path = lockPath();
454
- mkdirSync2(join3(homedir3(), ".deeplake"), { recursive: true, mode: 448 });
458
+ mkdirSync3(join3(homedir3(), ".deeplake"), { recursive: true, mode: 448 });
455
459
  let fd = null;
456
460
  for (let attempt = 0; attempt < LOCK_RETRY_MAX; attempt++) {
457
461
  try {
@@ -1828,7 +1832,7 @@ function extractLatestVersion(body) {
1828
1832
  return typeof v === "string" && v.length > 0 ? v : null;
1829
1833
  }
1830
1834
  function getInstalledVersion() {
1831
- return "0.7.79".length > 0 ? "0.7.79" : null;
1835
+ return "0.7.81".length > 0 ? "0.7.81" : null;
1832
1836
  }
1833
1837
  function isNewer(latest, current) {
1834
1838
  const parse = (v) => v.replace(/-.*$/, "").split(".").map(Number);
@@ -6,8 +6,8 @@ import { readFileSync as readFileSync3, writeFileSync as writeFileSync3, existsS
6
6
  import { join as join7 } from "node:path";
7
7
 
8
8
  // dist/src/utils/debug.js
9
- import { appendFileSync } from "node:fs";
10
- import { join } from "node:path";
9
+ import { appendFileSync, mkdirSync } from "node:fs";
10
+ import { dirname, join } from "node:path";
11
11
  import { homedir } from "node:os";
12
12
  var LOG = join(homedir(), ".deeplake", "hook-debug.log");
13
13
  function isDebug() {
@@ -19,8 +19,12 @@ function utcTimestamp(d = /* @__PURE__ */ new Date()) {
19
19
  function log(tag, msg) {
20
20
  if (!isDebug())
21
21
  return;
22
- appendFileSync(LOG, `${(/* @__PURE__ */ new Date()).toISOString()} [${tag}] ${msg}
22
+ try {
23
+ mkdirSync(dirname(LOG), { recursive: true });
24
+ appendFileSync(LOG, `${(/* @__PURE__ */ new Date()).toISOString()} [${tag}] ${msg}
23
25
  `);
26
+ } catch {
27
+ }
24
28
  }
25
29
 
26
30
  // dist/src/utils/client-header.js
@@ -64,7 +68,7 @@ function extractPairs(rows) {
64
68
  }
65
69
 
66
70
  // dist/src/skillify/skill-writer.js
67
- import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
71
+ import { existsSync, mkdirSync as mkdirSync2, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
68
72
  import { homedir as homedir2 } from "node:os";
69
73
  import { join as join2 } from "node:path";
70
74
  function assertValidSkillName(name) {
@@ -166,7 +170,7 @@ function writeNewSkill(args) {
166
170
  if (existsSync(path)) {
167
171
  throw new Error(`skill already exists at ${path}; use mergeSkill`);
168
172
  }
169
- mkdirSync(dir, { recursive: true });
173
+ mkdirSync2(dir, { recursive: true });
170
174
  const now = (/* @__PURE__ */ new Date()).toISOString();
171
175
  const author = args.author && args.author.length > 0 ? args.author : void 0;
172
176
  const contributors = author ? [author] : [];
@@ -755,7 +759,7 @@ function resolveRecordScope(args) {
755
759
  }
756
760
 
757
761
  // dist/src/skillify/state.js
758
- import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, writeSync, mkdirSync as mkdirSync2, renameSync as renameSync2, rmdirSync, existsSync as existsSync4, lstatSync, unlinkSync, openSync, closeSync } from "node:fs";
762
+ import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, writeSync, mkdirSync as mkdirSync3, renameSync as renameSync2, rmdirSync, existsSync as existsSync4, lstatSync, unlinkSync, openSync, closeSync } from "node:fs";
759
763
  import { join as join6 } from "node:path";
760
764
 
761
765
  // dist/src/utils/repo-identity.js
@@ -765,7 +769,7 @@ import { basename, resolve } from "node:path";
765
769
 
766
770
  // dist/src/skillify/legacy-migration.js
767
771
  import { existsSync as existsSync3, renameSync } from "node:fs";
768
- import { dirname, join as join5 } from "node:path";
772
+ import { dirname as dirname2, join as join5 } from "node:path";
769
773
 
770
774
  // dist/src/skillify/state-dir.js
771
775
  import { homedir as homedir4 } from "node:os";
@@ -785,7 +789,7 @@ function migrateLegacyStateDir() {
785
789
  return;
786
790
  attempted = true;
787
791
  const current = getStateDir();
788
- const legacy = join5(dirname(current), "skilify");
792
+ const legacy = join5(dirname2(current), "skilify");
789
793
  if (!existsSync3(legacy))
790
794
  return;
791
795
  if (existsSync3(current))
@@ -829,7 +833,7 @@ function readState(projectKey) {
829
833
  }
830
834
  function writeState(projectKey, state) {
831
835
  migrateLegacyStateDir();
832
- mkdirSync2(getStateDir(), { recursive: true });
836
+ mkdirSync3(getStateDir(), { recursive: true });
833
837
  const p = statePath(projectKey);
834
838
  const tmp = `${p}.${process.pid}.${Date.now()}.tmp`;
835
839
  writeFileSync2(tmp, JSON.stringify(state, null, 2));
@@ -837,7 +841,7 @@ function writeState(projectKey, state) {
837
841
  }
838
842
  function withRmwLock(projectKey, fn) {
839
843
  migrateLegacyStateDir();
840
- mkdirSync2(getStateDir(), { recursive: true });
844
+ mkdirSync3(getStateDir(), { recursive: true });
841
845
  const rmw = lockPath(projectKey) + ".rmw";
842
846
  const deadline = Date.now() + 2e3;
843
847
  let fd = null;
@@ -54,5 +54,5 @@
54
54
  }
55
55
  }
56
56
  },
57
- "version": "0.7.79"
57
+ "version": "0.7.81"
58
58
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hivemind",
3
- "version": "0.7.79",
3
+ "version": "0.7.81",
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.81",
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