@deeplake/hivemind 0.7.51 → 0.7.53
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 +3 -3
- package/.claude-plugin/plugin.json +1 -1
- package/bundle/cli.js +414 -14
- package/codex/bundle/commands/auth-login.js +8 -1
- package/codex/bundle/session-start.js +61 -1
- package/cursor/bundle/commands/auth-login.js +8 -1
- package/cursor/bundle/session-start.js +61 -1
- package/hermes/bundle/commands/auth-login.js +8 -1
- package/hermes/bundle/session-start.js +62 -1
- package/openclaw/dist/index.js +66 -2
- package/openclaw/openclaw.plugin.json +1 -1
- package/openclaw/package.json +1 -1
- package/package.json +1 -1
- package/pi/extension-source/hivemind.ts +48 -1
|
@@ -6,18 +6,18 @@
|
|
|
6
6
|
},
|
|
7
7
|
"metadata": {
|
|
8
8
|
"description": "Cloud-backed persistent shared memory for AI agents powered by Deeplake",
|
|
9
|
-
"version": "0.7.
|
|
9
|
+
"version": "0.7.53"
|
|
10
10
|
},
|
|
11
11
|
"plugins": [
|
|
12
12
|
{
|
|
13
13
|
"name": "hivemind",
|
|
14
14
|
"description": "Persistent shared memory powered by Deeplake — captures all session activity and provides cross-session, cross-agent memory search",
|
|
15
|
-
"version": "0.7.
|
|
15
|
+
"version": "0.7.53",
|
|
16
16
|
"source": {
|
|
17
17
|
"source": "git-subdir",
|
|
18
18
|
"url": "https://github.com/activeloopai/hivemind.git",
|
|
19
19
|
"path": "claude-code",
|
|
20
|
-
"sha": "
|
|
20
|
+
"sha": "2f9768a8c9ed54952bdc272261f2a527f5482fe2"
|
|
21
21
|
},
|
|
22
22
|
"homepage": "https://github.com/activeloopai/hivemind"
|
|
23
23
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hivemind",
|
|
3
3
|
"description": "Cloud-backed persistent memory powered by Deeplake — read, write, and share memory across Claude Code sessions and agents",
|
|
4
|
-
"version": "0.7.
|
|
4
|
+
"version": "0.7.53",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Activeloop",
|
|
7
7
|
"url": "https://deeplake.ai"
|
package/bundle/cli.js
CHANGED
|
@@ -4270,7 +4270,14 @@ async function switchOrg(orgId, orgName) {
|
|
|
4270
4270
|
const creds = loadCredentials();
|
|
4271
4271
|
if (!creds)
|
|
4272
4272
|
throw new Error("Not logged in. Run deeplake login first.");
|
|
4273
|
-
|
|
4273
|
+
const apiUrl = creds.apiUrl ?? DEFAULT_API_URL;
|
|
4274
|
+
const tokenName = `deeplake-plugin-switch-${Date.now()}`;
|
|
4275
|
+
const tokenData = await apiPost("/users/me/tokens", {
|
|
4276
|
+
name: tokenName,
|
|
4277
|
+
duration: 365 * 24 * 3600,
|
|
4278
|
+
organization_id: orgId
|
|
4279
|
+
}, creds.token, apiUrl);
|
|
4280
|
+
saveCredentials({ ...creds, orgId, orgName, token: tokenData.token.token });
|
|
4274
4281
|
}
|
|
4275
4282
|
async function listWorkspaces(token, apiUrl = DEFAULT_API_URL, orgId) {
|
|
4276
4283
|
const raw = await apiGet("/workspaces", token, apiUrl, orgId);
|
|
@@ -9665,7 +9672,45 @@ function writeLocalManifest(m, path = LOCAL_MANIFEST_PATH) {
|
|
|
9665
9672
|
mkdirSync21(dirname15(path), { recursive: true });
|
|
9666
9673
|
writeFileSync24(path, JSON.stringify(m, null, 2));
|
|
9667
9674
|
}
|
|
9675
|
+
function countLocalManifestEntries(path = LOCAL_MANIFEST_PATH) {
|
|
9676
|
+
const m = readLocalManifest(path);
|
|
9677
|
+
return Array.isArray(m?.entries) ? m.entries.length : 0;
|
|
9678
|
+
}
|
|
9668
9679
|
var LATEST_RUN_WINDOW_MS = 5 * 60 * 1e3;
|
|
9680
|
+
function getLatestInsightEntry(path = LOCAL_MANIFEST_PATH) {
|
|
9681
|
+
const m = readLocalManifest(path);
|
|
9682
|
+
if (!m || !Array.isArray(m.entries))
|
|
9683
|
+
return null;
|
|
9684
|
+
let newestTs = Number.NEGATIVE_INFINITY;
|
|
9685
|
+
for (const e of m.entries) {
|
|
9686
|
+
if (!e)
|
|
9687
|
+
continue;
|
|
9688
|
+
const ts = Date.parse(e.created_at ?? "");
|
|
9689
|
+
if (Number.isFinite(ts) && ts > newestTs)
|
|
9690
|
+
newestTs = ts;
|
|
9691
|
+
}
|
|
9692
|
+
if (!Number.isFinite(newestTs))
|
|
9693
|
+
return null;
|
|
9694
|
+
let best = null;
|
|
9695
|
+
let bestTs = Number.NEGATIVE_INFINITY;
|
|
9696
|
+
let bestIsPrimary = false;
|
|
9697
|
+
for (const e of m.entries) {
|
|
9698
|
+
if (!e || typeof e.insight !== "string" || e.insight.trim().length === 0)
|
|
9699
|
+
continue;
|
|
9700
|
+
const ts = Date.parse(e.created_at ?? "");
|
|
9701
|
+
if (!Number.isFinite(ts))
|
|
9702
|
+
continue;
|
|
9703
|
+
if (newestTs - ts > LATEST_RUN_WINDOW_MS)
|
|
9704
|
+
continue;
|
|
9705
|
+
const isPrimary = e.primary === true;
|
|
9706
|
+
if (!best || isPrimary && !bestIsPrimary || isPrimary === bestIsPrimary && ts > bestTs) {
|
|
9707
|
+
best = e;
|
|
9708
|
+
bestTs = ts;
|
|
9709
|
+
bestIsPrimary = isPrimary;
|
|
9710
|
+
}
|
|
9711
|
+
}
|
|
9712
|
+
return best;
|
|
9713
|
+
}
|
|
9669
9714
|
|
|
9670
9715
|
// dist/src/commands/mine-local.js
|
|
9671
9716
|
import { unlinkSync as unlinkSync12 } from "node:fs";
|
|
@@ -10012,18 +10057,24 @@ async function runMineLocalImpl(args) {
|
|
|
10012
10057
|
const force = takeBoolFlag(work, "--force");
|
|
10013
10058
|
const dryRun = takeBoolFlag(work, "--dry-run");
|
|
10014
10059
|
const nRaw = takeFlagValue(work, "--n");
|
|
10060
|
+
const onlyAgent = takeFlagValue(work, "--only");
|
|
10015
10061
|
if (loadManifest2() && !force) {
|
|
10016
10062
|
console.error(`Local skills have already been mined on this machine.`);
|
|
10017
10063
|
console.error(`Manifest: ${MANIFEST_PATH}`);
|
|
10018
10064
|
console.error(`Pass --force to re-mine.`);
|
|
10019
10065
|
process.exit(1);
|
|
10020
10066
|
}
|
|
10021
|
-
const
|
|
10022
|
-
if (
|
|
10067
|
+
const installsAll = detectInstalledAgents();
|
|
10068
|
+
if (installsAll.length === 0) {
|
|
10023
10069
|
console.error(`No agent session directories detected. Run a session first.`);
|
|
10024
10070
|
process.exit(1);
|
|
10025
10071
|
}
|
|
10026
|
-
|
|
10072
|
+
const installs = onlyAgent ? installsAll.filter((i) => i.agent === onlyAgent) : installsAll;
|
|
10073
|
+
if (installs.length === 0) {
|
|
10074
|
+
console.error(`No '${onlyAgent}' session directory detected. Skipping mine-local.`);
|
|
10075
|
+
process.exit(1);
|
|
10076
|
+
}
|
|
10077
|
+
console.log(`Detected installed agents: ${installs.map((i) => i.agent).join(", ")}${onlyAgent ? ` (filtered to ${onlyAgent})` : ""}`);
|
|
10027
10078
|
const host = detectHostAgent();
|
|
10028
10079
|
const fallback = installs[0].agent;
|
|
10029
10080
|
const gateAgent = gateAgentFor(host, fallback, installs);
|
|
@@ -11461,7 +11512,7 @@ async function runUpdate(opts = {}) {
|
|
|
11461
11512
|
}
|
|
11462
11513
|
log(`Update available: ${current} \u2192 ${latest}`);
|
|
11463
11514
|
const detected = opts.installKindOverride ?? detectInstallKind();
|
|
11464
|
-
const
|
|
11515
|
+
const spawn5 = opts.spawn ?? defaultSpawn;
|
|
11465
11516
|
switch (detected.kind) {
|
|
11466
11517
|
case "npm-global": {
|
|
11467
11518
|
if (opts.dryRun) {
|
|
@@ -11476,7 +11527,7 @@ async function runUpdate(opts = {}) {
|
|
|
11476
11527
|
try {
|
|
11477
11528
|
log(`Upgrading via npm\u2026`);
|
|
11478
11529
|
try {
|
|
11479
|
-
|
|
11530
|
+
spawn5("npm", ["install", "-g", `${PKG_NAME}@latest`]);
|
|
11480
11531
|
} catch (e) {
|
|
11481
11532
|
warn(`npm install failed: ${e.message}`);
|
|
11482
11533
|
warn(`Try running it manually: npm install -g ${PKG_NAME}@latest`);
|
|
@@ -11485,7 +11536,7 @@ async function runUpdate(opts = {}) {
|
|
|
11485
11536
|
log(``);
|
|
11486
11537
|
log(`Refreshing agent bundles\u2026`);
|
|
11487
11538
|
try {
|
|
11488
|
-
|
|
11539
|
+
spawn5("hivemind", ["install", "--skip-auth"]);
|
|
11489
11540
|
} catch (e) {
|
|
11490
11541
|
warn(`Agent refresh failed: ${e.message}`);
|
|
11491
11542
|
warn(`Run manually: hivemind install`);
|
|
@@ -11536,6 +11587,325 @@ async function runUpdate(opts = {}) {
|
|
|
11536
11587
|
}
|
|
11537
11588
|
}
|
|
11538
11589
|
|
|
11590
|
+
// dist/src/cli/install-scan.js
|
|
11591
|
+
import { spawn as spawn4 } from "node:child_process";
|
|
11592
|
+
import { existsSync as existsSync39, readFileSync as readFileSync35, readdirSync as readdirSync9, unlinkSync as unlinkSync14 } from "node:fs";
|
|
11593
|
+
import { homedir as homedir26 } from "node:os";
|
|
11594
|
+
import { join as join49 } from "node:path";
|
|
11595
|
+
|
|
11596
|
+
// dist/src/skillify/advisor.js
|
|
11597
|
+
import { spawn as spawn3 } from "node:child_process";
|
|
11598
|
+
import { existsSync as existsSync38 } from "node:fs";
|
|
11599
|
+
var ADVISOR_TIMEOUT_MS = 6e4;
|
|
11600
|
+
var MAX_CANDIDATES = 20;
|
|
11601
|
+
function buildAdvisorPrompt(candidates) {
|
|
11602
|
+
const lines = [
|
|
11603
|
+
"You are reviewing skill candidates extracted from a user's coding sessions.",
|
|
11604
|
+
"Pick the ONE candidate whose `insight` field is most useful to show the user as a",
|
|
11605
|
+
"concrete finding from their past work. Reply on EXACTLY ONE LINE.",
|
|
11606
|
+
"",
|
|
11607
|
+
"GOOD insights are:",
|
|
11608
|
+
" - Concrete and counted (cite specific numbers, file names, durations)",
|
|
11609
|
+
' - About a real coding mistake or pattern the USER made (in 2nd person \u2014 "You did X")',
|
|
11610
|
+
" - Actionable: the user can change behavior based on knowing this",
|
|
11611
|
+
"",
|
|
11612
|
+
"BAD insights (REJECT these) are:",
|
|
11613
|
+
' - Meta-commentary about why the skill was saved ("User explicitly requested...")',
|
|
11614
|
+
" - Vague / generic engineering platitudes the user already knows",
|
|
11615
|
+
" - About someone other than the user (a teammate, a third party)",
|
|
11616
|
+
' - Hypothetical ("could lead to...", "might cause...") rather than observed',
|
|
11617
|
+
"",
|
|
11618
|
+
"Output format \u2014 STRICT, one line only:",
|
|
11619
|
+
" PICK: <number 1-N>",
|
|
11620
|
+
"OR",
|
|
11621
|
+
" REJECT_ALL: <short reason why every candidate failed>",
|
|
11622
|
+
"",
|
|
11623
|
+
"Candidates:"
|
|
11624
|
+
];
|
|
11625
|
+
for (const [i, c] of candidates.entries()) {
|
|
11626
|
+
lines.push(`${i + 1}. name=${c.skill_name} insight=${JSON.stringify((c.insight ?? "").slice(0, 400))}`);
|
|
11627
|
+
}
|
|
11628
|
+
return lines.join("\n");
|
|
11629
|
+
}
|
|
11630
|
+
function parseAdvisorOutput(raw, candidates) {
|
|
11631
|
+
const cleaned = raw.trim();
|
|
11632
|
+
const pickMatch = cleaned.match(/PICK:\s*(\d+)/i);
|
|
11633
|
+
if (pickMatch) {
|
|
11634
|
+
const idx = parseInt(pickMatch[1], 10) - 1;
|
|
11635
|
+
if (idx >= 0 && idx < candidates.length) {
|
|
11636
|
+
return {
|
|
11637
|
+
pickedSkillName: candidates[idx].skill_name,
|
|
11638
|
+
reason: cleaned,
|
|
11639
|
+
rawOutput: raw
|
|
11640
|
+
};
|
|
11641
|
+
}
|
|
11642
|
+
}
|
|
11643
|
+
const rejectMatch = cleaned.match(/REJECT_ALL:\s*(.+)/i);
|
|
11644
|
+
if (rejectMatch) {
|
|
11645
|
+
return { pickedSkillName: null, reason: rejectMatch[1].trim(), rawOutput: raw };
|
|
11646
|
+
}
|
|
11647
|
+
return { pickedSkillName: null, reason: `unparseable advisor output: ${cleaned.slice(0, 120)}`, rawOutput: raw };
|
|
11648
|
+
}
|
|
11649
|
+
function runAdvisorGate(prompt, claudeBin) {
|
|
11650
|
+
return new Promise((resolve6, reject) => {
|
|
11651
|
+
const child = spawn3(claudeBin, [
|
|
11652
|
+
"-p",
|
|
11653
|
+
"--no-session-persistence",
|
|
11654
|
+
"--model",
|
|
11655
|
+
"sonnet",
|
|
11656
|
+
"--permission-mode",
|
|
11657
|
+
"bypassPermissions"
|
|
11658
|
+
], {
|
|
11659
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
11660
|
+
env: { ...process.env, HIVEMIND_WIKI_WORKER: "1", HIVEMIND_CAPTURE: "false" }
|
|
11661
|
+
});
|
|
11662
|
+
let stdout = "";
|
|
11663
|
+
let stderr = "";
|
|
11664
|
+
let settled = false;
|
|
11665
|
+
const finish = (err, out) => {
|
|
11666
|
+
if (settled)
|
|
11667
|
+
return;
|
|
11668
|
+
settled = true;
|
|
11669
|
+
if (err)
|
|
11670
|
+
reject(err);
|
|
11671
|
+
else
|
|
11672
|
+
resolve6(out);
|
|
11673
|
+
};
|
|
11674
|
+
const timer = setTimeout(() => {
|
|
11675
|
+
try {
|
|
11676
|
+
child.kill("SIGKILL");
|
|
11677
|
+
} catch {
|
|
11678
|
+
}
|
|
11679
|
+
finish(new Error(`advisor timed out after ${ADVISOR_TIMEOUT_MS}ms`), "");
|
|
11680
|
+
}, ADVISOR_TIMEOUT_MS);
|
|
11681
|
+
child.stdout.on("data", (b) => {
|
|
11682
|
+
stdout += b.toString("utf-8");
|
|
11683
|
+
});
|
|
11684
|
+
child.stderr.on("data", (b) => {
|
|
11685
|
+
stderr += b.toString("utf-8");
|
|
11686
|
+
});
|
|
11687
|
+
child.on("error", (e) => {
|
|
11688
|
+
clearTimeout(timer);
|
|
11689
|
+
finish(e, "");
|
|
11690
|
+
});
|
|
11691
|
+
child.on("close", (code) => {
|
|
11692
|
+
clearTimeout(timer);
|
|
11693
|
+
if (code !== 0) {
|
|
11694
|
+
finish(new Error(`advisor CLI exit ${code}; stderr=${stderr.slice(0, 200)}`), "");
|
|
11695
|
+
} else {
|
|
11696
|
+
finish(null, stdout);
|
|
11697
|
+
}
|
|
11698
|
+
});
|
|
11699
|
+
child.stdin.on("error", (e) => {
|
|
11700
|
+
clearTimeout(timer);
|
|
11701
|
+
finish(e, "");
|
|
11702
|
+
});
|
|
11703
|
+
child.stdin.end(prompt);
|
|
11704
|
+
});
|
|
11705
|
+
}
|
|
11706
|
+
async function runAdvisor(manifestPath3 = LOCAL_MANIFEST_PATH) {
|
|
11707
|
+
const m = readLocalManifest(manifestPath3);
|
|
11708
|
+
if (!m || !Array.isArray(m.entries))
|
|
11709
|
+
return null;
|
|
11710
|
+
const insightBearing = m.entries.filter((e) => e && typeof e.insight === "string" && e.insight.trim().length > 0);
|
|
11711
|
+
if (insightBearing.length === 0)
|
|
11712
|
+
return null;
|
|
11713
|
+
if (insightBearing.length === 1) {
|
|
11714
|
+
insightBearing[0].primary = true;
|
|
11715
|
+
writeLocalManifest(m, manifestPath3);
|
|
11716
|
+
return {
|
|
11717
|
+
pickedSkillName: insightBearing[0].skill_name,
|
|
11718
|
+
reason: "trivial pick (single candidate)",
|
|
11719
|
+
rawOutput: ""
|
|
11720
|
+
};
|
|
11721
|
+
}
|
|
11722
|
+
const claudeBin = findAgentBin("claude_code");
|
|
11723
|
+
if (!claudeBin || !existsSync38(claudeBin)) {
|
|
11724
|
+
return null;
|
|
11725
|
+
}
|
|
11726
|
+
const ranked = [...insightBearing].sort((a, b) => (b.created_at ?? "").localeCompare(a.created_at ?? "")).slice(0, MAX_CANDIDATES);
|
|
11727
|
+
const prompt = buildAdvisorPrompt(ranked);
|
|
11728
|
+
let raw;
|
|
11729
|
+
try {
|
|
11730
|
+
raw = await runAdvisorGate(prompt, claudeBin);
|
|
11731
|
+
} catch (err) {
|
|
11732
|
+
return {
|
|
11733
|
+
pickedSkillName: null,
|
|
11734
|
+
reason: `advisor invocation failed: ${err.message}`,
|
|
11735
|
+
rawOutput: ""
|
|
11736
|
+
};
|
|
11737
|
+
}
|
|
11738
|
+
const result = parseAdvisorOutput(raw, ranked);
|
|
11739
|
+
if (result.pickedSkillName) {
|
|
11740
|
+
for (const e of m.entries) {
|
|
11741
|
+
if (e && e.primary === true)
|
|
11742
|
+
delete e.primary;
|
|
11743
|
+
}
|
|
11744
|
+
for (const e of m.entries) {
|
|
11745
|
+
if (e && e.skill_name === result.pickedSkillName) {
|
|
11746
|
+
e.primary = true;
|
|
11747
|
+
break;
|
|
11748
|
+
}
|
|
11749
|
+
}
|
|
11750
|
+
writeLocalManifest(m, manifestPath3);
|
|
11751
|
+
}
|
|
11752
|
+
return result;
|
|
11753
|
+
}
|
|
11754
|
+
|
|
11755
|
+
// dist/src/cli/install-scan.js
|
|
11756
|
+
function claudeProjectsDir() {
|
|
11757
|
+
return join49(homedir26(), ".claude", "projects");
|
|
11758
|
+
}
|
|
11759
|
+
function manifestPath2() {
|
|
11760
|
+
return join49(homedir26(), ".claude", "hivemind", "local-mined.json");
|
|
11761
|
+
}
|
|
11762
|
+
var SCAN_TIMEOUT_MS = 3e5;
|
|
11763
|
+
var INSTALL_SCAN_SESSION_COUNT = 10;
|
|
11764
|
+
function manifestIsTrulyEmpty() {
|
|
11765
|
+
const p = manifestPath2();
|
|
11766
|
+
if (!existsSync39(p))
|
|
11767
|
+
return false;
|
|
11768
|
+
try {
|
|
11769
|
+
const m = JSON.parse(readFileSync35(p, "utf-8"));
|
|
11770
|
+
return Array.isArray(m.entries) && m.entries.length === 0;
|
|
11771
|
+
} catch {
|
|
11772
|
+
return false;
|
|
11773
|
+
}
|
|
11774
|
+
}
|
|
11775
|
+
function hasLocalClaudeSessions() {
|
|
11776
|
+
const projectsDir = claudeProjectsDir();
|
|
11777
|
+
if (!existsSync39(projectsDir))
|
|
11778
|
+
return false;
|
|
11779
|
+
let subdirs;
|
|
11780
|
+
try {
|
|
11781
|
+
subdirs = readdirSync9(projectsDir);
|
|
11782
|
+
} catch {
|
|
11783
|
+
return false;
|
|
11784
|
+
}
|
|
11785
|
+
for (const sub of subdirs) {
|
|
11786
|
+
let files;
|
|
11787
|
+
try {
|
|
11788
|
+
files = readdirSync9(join49(projectsDir, sub));
|
|
11789
|
+
} catch {
|
|
11790
|
+
continue;
|
|
11791
|
+
}
|
|
11792
|
+
if (files.some((f) => f.endsWith(".jsonl")))
|
|
11793
|
+
return true;
|
|
11794
|
+
}
|
|
11795
|
+
return false;
|
|
11796
|
+
}
|
|
11797
|
+
function canOfferInstallScan() {
|
|
11798
|
+
const bin = findAgentBin("claude_code");
|
|
11799
|
+
if (!bin || !existsSync39(bin))
|
|
11800
|
+
return false;
|
|
11801
|
+
if (!hasLocalClaudeSessions())
|
|
11802
|
+
return false;
|
|
11803
|
+
if (existsSync39(manifestPath2()))
|
|
11804
|
+
return false;
|
|
11805
|
+
return true;
|
|
11806
|
+
}
|
|
11807
|
+
function unlinkManifestIfCorrupt() {
|
|
11808
|
+
const p = manifestPath2();
|
|
11809
|
+
if (!existsSync39(p))
|
|
11810
|
+
return;
|
|
11811
|
+
if (readLocalManifest(p) === null) {
|
|
11812
|
+
try {
|
|
11813
|
+
unlinkSync14(p);
|
|
11814
|
+
} catch {
|
|
11815
|
+
}
|
|
11816
|
+
}
|
|
11817
|
+
}
|
|
11818
|
+
function runInstallScan() {
|
|
11819
|
+
return new Promise((resolve6) => {
|
|
11820
|
+
const cliPath = process.argv[1];
|
|
11821
|
+
if (!cliPath || !existsSync39(cliPath)) {
|
|
11822
|
+
resolve6({ insight: null, skillsCount: 0 });
|
|
11823
|
+
return;
|
|
11824
|
+
}
|
|
11825
|
+
const child = spawn4(process.execPath, [
|
|
11826
|
+
cliPath,
|
|
11827
|
+
"skillify",
|
|
11828
|
+
"mine-local",
|
|
11829
|
+
"--n",
|
|
11830
|
+
String(INSTALL_SCAN_SESSION_COUNT),
|
|
11831
|
+
// The install copy advertises a "Claude Code" scan, so filter
|
|
11832
|
+
// the mine-local picker to claude_code sessions. Without this,
|
|
11833
|
+
// mine-local walks every installed agent (Codex, Cursor,
|
|
11834
|
+
// Hermes, pi) and could surface an insight from a Codex
|
|
11835
|
+
// session despite what we promised — codex PR #198 P2.
|
|
11836
|
+
"--only",
|
|
11837
|
+
"claude_code"
|
|
11838
|
+
], {
|
|
11839
|
+
stdio: ["ignore", "ignore", "ignore"],
|
|
11840
|
+
// HIVEMIND_CAPTURE=false: the spawned mine-local would otherwise
|
|
11841
|
+
// try to capture its own activity, which is a no-op without
|
|
11842
|
+
// credentials but spams the log. Keep it quiet.
|
|
11843
|
+
env: { ...process.env, HIVEMIND_CAPTURE: "false" }
|
|
11844
|
+
});
|
|
11845
|
+
let settled = false;
|
|
11846
|
+
const finish = (result) => {
|
|
11847
|
+
if (settled)
|
|
11848
|
+
return;
|
|
11849
|
+
settled = true;
|
|
11850
|
+
resolve6(result);
|
|
11851
|
+
};
|
|
11852
|
+
const timer = setTimeout(() => {
|
|
11853
|
+
try {
|
|
11854
|
+
child.kill("SIGKILL");
|
|
11855
|
+
} catch {
|
|
11856
|
+
}
|
|
11857
|
+
unlinkManifestIfCorrupt();
|
|
11858
|
+
finish({ insight: null, skillsCount: 0 });
|
|
11859
|
+
}, SCAN_TIMEOUT_MS);
|
|
11860
|
+
child.on("close", async (code) => {
|
|
11861
|
+
clearTimeout(timer);
|
|
11862
|
+
if (code !== 0) {
|
|
11863
|
+
unlinkManifestIfCorrupt();
|
|
11864
|
+
finish({ insight: null, skillsCount: 0 });
|
|
11865
|
+
return;
|
|
11866
|
+
}
|
|
11867
|
+
let advisorResult = null;
|
|
11868
|
+
try {
|
|
11869
|
+
advisorResult = await runAdvisor();
|
|
11870
|
+
} catch {
|
|
11871
|
+
}
|
|
11872
|
+
const advisorRejected = advisorResult !== null && advisorResult.pickedSkillName === null;
|
|
11873
|
+
let entry = null;
|
|
11874
|
+
if (!advisorRejected) {
|
|
11875
|
+
try {
|
|
11876
|
+
entry = getLatestInsightEntry();
|
|
11877
|
+
} catch {
|
|
11878
|
+
}
|
|
11879
|
+
}
|
|
11880
|
+
let skillsCount = 0;
|
|
11881
|
+
try {
|
|
11882
|
+
skillsCount = countLocalManifestEntries();
|
|
11883
|
+
} catch {
|
|
11884
|
+
}
|
|
11885
|
+
if (!entry && manifestIsTrulyEmpty()) {
|
|
11886
|
+
try {
|
|
11887
|
+
unlinkSync14(manifestPath2());
|
|
11888
|
+
} catch {
|
|
11889
|
+
}
|
|
11890
|
+
skillsCount = 0;
|
|
11891
|
+
}
|
|
11892
|
+
finish({ insight: entry, skillsCount });
|
|
11893
|
+
});
|
|
11894
|
+
child.on("error", () => {
|
|
11895
|
+
clearTimeout(timer);
|
|
11896
|
+
unlinkManifestIfCorrupt();
|
|
11897
|
+
finish({ insight: null, skillsCount: 0 });
|
|
11898
|
+
});
|
|
11899
|
+
});
|
|
11900
|
+
}
|
|
11901
|
+
function formatScanResult(entry) {
|
|
11902
|
+
const rawInsight = (entry.insight ?? "").replace(/\s+/g, " ").trim();
|
|
11903
|
+
const insight = rawInsight.length > 280 ? rawInsight.slice(0, 277).replace(/\s\S*$/, "") + "\u2026" : rawInsight;
|
|
11904
|
+
return `\u2713 Found a pattern in your past sessions:
|
|
11905
|
+
\u{1F4CC} ${insight}
|
|
11906
|
+
\u2728 Skill \`${entry.skill_name}\` ready to catch it next time`;
|
|
11907
|
+
}
|
|
11908
|
+
|
|
11539
11909
|
// dist/src/cli/index.js
|
|
11540
11910
|
var AUTH_SUBCOMMANDS = /* @__PURE__ */ new Set([
|
|
11541
11911
|
"whoami",
|
|
@@ -11717,14 +12087,44 @@ async function runAuthGate(args) {
|
|
|
11717
12087
|
log("Or run `hivemind login` after install.");
|
|
11718
12088
|
return;
|
|
11719
12089
|
}
|
|
12090
|
+
let foundInsight = null;
|
|
12091
|
+
if (canOfferInstallScan()) {
|
|
12092
|
+
log("");
|
|
12093
|
+
log("\u{1F41D} Want me to scan your recent Claude Code sessions for repeatable mistakes?");
|
|
12094
|
+
log("Takes 2-4 minutes. Scans 10 sessions in parallel using your Claude Code subscription.");
|
|
12095
|
+
log("");
|
|
12096
|
+
const scanOk = await confirm("Scan now?", true);
|
|
12097
|
+
if (scanOk) {
|
|
12098
|
+
log("");
|
|
12099
|
+
log("Scanning your 10 most-recent sessions (up to 5 min). Be patient \u2014 haiku is running in the background.");
|
|
12100
|
+
const { insight, skillsCount } = await runInstallScan();
|
|
12101
|
+
log("");
|
|
12102
|
+
if (insight && insight.insight && insight.insight.trim().length > 0) {
|
|
12103
|
+
log(formatScanResult(insight));
|
|
12104
|
+
foundInsight = { skill_name: insight.skill_name };
|
|
12105
|
+
} else if (skillsCount > 0) {
|
|
12106
|
+
log(`Mined ${skillsCount} skill${skillsCount === 1 ? "" : "s"} locally \u2014 they'll be available in your next claude session.`);
|
|
12107
|
+
log("(No banner-quality insight to surface here \u2014 the gate is conservative on what gets the top-line.)");
|
|
12108
|
+
} else {
|
|
12109
|
+
log("No repeatable patterns found in this scan. (That's OK \u2014 the gate is conservative.)");
|
|
12110
|
+
}
|
|
12111
|
+
}
|
|
12112
|
+
}
|
|
11720
12113
|
log("");
|
|
11721
|
-
|
|
11722
|
-
|
|
11723
|
-
|
|
11724
|
-
|
|
11725
|
-
|
|
11726
|
-
|
|
11727
|
-
|
|
12114
|
+
if (foundInsight) {
|
|
12115
|
+
log("\u{1F41D} Sign in to keep this skill across machines and share it with your team.");
|
|
12116
|
+
log("");
|
|
12117
|
+
log(`Without sign-in, \`${foundInsight.skill_name}\` lives only on this machine and`);
|
|
12118
|
+
log("won't follow you to a new laptop or be shared with teammates who'd benefit.");
|
|
12119
|
+
} else {
|
|
12120
|
+
log("\u{1F41D} One more step to unlock Hivemind");
|
|
12121
|
+
log("");
|
|
12122
|
+
log("To enable shared memory and auto-learning across your agents,");
|
|
12123
|
+
log("we need to sign you in. Your traces will be securely stored in");
|
|
12124
|
+
log("your private Hivemind, so all your agents can recall them.");
|
|
12125
|
+
log("");
|
|
12126
|
+
log("You can later connect your own cloud storage like S3/GCS/Azure Blob.");
|
|
12127
|
+
}
|
|
11728
12128
|
log("");
|
|
11729
12129
|
const yes = await confirm("Sign in now?", true);
|
|
11730
12130
|
let signedIn = false;
|
|
@@ -259,7 +259,14 @@ async function switchOrg(orgId, orgName) {
|
|
|
259
259
|
const creds = loadCredentials();
|
|
260
260
|
if (!creds)
|
|
261
261
|
throw new Error("Not logged in. Run deeplake login first.");
|
|
262
|
-
|
|
262
|
+
const apiUrl = creds.apiUrl ?? DEFAULT_API_URL;
|
|
263
|
+
const tokenName = `deeplake-plugin-switch-${Date.now()}`;
|
|
264
|
+
const tokenData = await apiPost("/users/me/tokens", {
|
|
265
|
+
name: tokenName,
|
|
266
|
+
duration: 365 * 24 * 3600,
|
|
267
|
+
organization_id: orgId
|
|
268
|
+
}, creds.token, apiUrl);
|
|
269
|
+
saveCredentials({ ...creds, orgId, orgName, token: tokenData.token.token });
|
|
263
270
|
}
|
|
264
271
|
async function listWorkspaces(token, apiUrl = DEFAULT_API_URL, orgId) {
|
|
265
272
|
const raw = await apiGet("/workspaces", token, apiUrl, orgId);
|
|
@@ -92,6 +92,65 @@ function loadCredentials() {
|
|
|
92
92
|
return null;
|
|
93
93
|
}
|
|
94
94
|
}
|
|
95
|
+
function saveCredentials(creds) {
|
|
96
|
+
mkdirSync2(configDir(), { recursive: true, mode: 448 });
|
|
97
|
+
writeFileSync2(credsPath(), JSON.stringify({ ...creds, savedAt: (/* @__PURE__ */ new Date()).toISOString() }, null, 2), { mode: 384 });
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// dist/src/commands/auth.js
|
|
101
|
+
var DEFAULT_API_URL = "https://api.deeplake.ai";
|
|
102
|
+
function decodeJwtPayload(token) {
|
|
103
|
+
try {
|
|
104
|
+
const parts = token.split(".");
|
|
105
|
+
if (parts.length !== 3)
|
|
106
|
+
return null;
|
|
107
|
+
let payload = parts[1].replace(/-/g, "+").replace(/_/g, "/");
|
|
108
|
+
while (payload.length % 4)
|
|
109
|
+
payload += "=";
|
|
110
|
+
return JSON.parse(Buffer.from(payload, "base64").toString("utf8"));
|
|
111
|
+
} catch {
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
async function apiPost(path, body, token, apiUrl, orgId) {
|
|
116
|
+
const headers = {
|
|
117
|
+
Authorization: `Bearer ${token}`,
|
|
118
|
+
"Content-Type": "application/json",
|
|
119
|
+
...deeplakeClientHeader()
|
|
120
|
+
};
|
|
121
|
+
if (orgId)
|
|
122
|
+
headers["X-Activeloop-Org-Id"] = orgId;
|
|
123
|
+
const resp = await fetch(`${apiUrl}${path}`, { method: "POST", headers, body: JSON.stringify(body) });
|
|
124
|
+
if (!resp.ok)
|
|
125
|
+
throw new Error(`API ${resp.status}: ${await resp.text().catch(() => "")}`);
|
|
126
|
+
return resp.json();
|
|
127
|
+
}
|
|
128
|
+
async function healDriftedOrgToken(creds, log6 = () => {
|
|
129
|
+
}) {
|
|
130
|
+
if (!creds.token || !creds.orgId)
|
|
131
|
+
return creds;
|
|
132
|
+
const payload = decodeJwtPayload(creds.token);
|
|
133
|
+
const claimOrg = payload && typeof payload.org_id === "string" ? payload.org_id : void 0;
|
|
134
|
+
if (!claimOrg || claimOrg === creds.orgId)
|
|
135
|
+
return creds;
|
|
136
|
+
log6(`token org drift detected: jwt.org_id=${claimOrg} creds.orgId=${creds.orgId} \u2014 re-minting`);
|
|
137
|
+
try {
|
|
138
|
+
const apiUrl = creds.apiUrl ?? DEFAULT_API_URL;
|
|
139
|
+
const tokenName = `deeplake-plugin-heal-${Date.now()}`;
|
|
140
|
+
const tokenData = await apiPost("/users/me/tokens", {
|
|
141
|
+
name: tokenName,
|
|
142
|
+
duration: 365 * 24 * 3600,
|
|
143
|
+
organization_id: creds.orgId
|
|
144
|
+
}, creds.token, apiUrl);
|
|
145
|
+
const healed = { ...creds, token: tokenData.token.token };
|
|
146
|
+
saveCredentials(healed);
|
|
147
|
+
log6(`token re-minted for org=${creds.orgId}`);
|
|
148
|
+
return healed;
|
|
149
|
+
} catch (err) {
|
|
150
|
+
log6(`token re-mint failed (continuing with stale token): ${err.message}`);
|
|
151
|
+
return creds;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
95
154
|
|
|
96
155
|
// dist/src/utils/stdin.js
|
|
97
156
|
function readStdin() {
|
|
@@ -1827,13 +1886,14 @@ async function main() {
|
|
|
1827
1886
|
if (process.env.HIVEMIND_WIKI_WORKER === "1")
|
|
1828
1887
|
return;
|
|
1829
1888
|
const input = await readStdin();
|
|
1830
|
-
|
|
1889
|
+
let creds = loadCredentials();
|
|
1831
1890
|
if (!creds?.token) {
|
|
1832
1891
|
log5("no credentials found \u2014 run auth login to authenticate");
|
|
1833
1892
|
const auto = maybeAutoMineLocal();
|
|
1834
1893
|
log5(`auto-mine: ${auto.triggered ? "triggered (background)" : `skipped (${auto.reason})`}`);
|
|
1835
1894
|
} else {
|
|
1836
1895
|
log5(`credentials loaded: org=${creds.orgName ?? creds.orgId}`);
|
|
1896
|
+
creds = await healDriftedOrgToken(creds, log5);
|
|
1837
1897
|
}
|
|
1838
1898
|
if (creds?.token) {
|
|
1839
1899
|
const setupScript = join17(__bundleDir, "session-start-setup.js");
|
|
@@ -259,7 +259,14 @@ async function switchOrg(orgId, orgName) {
|
|
|
259
259
|
const creds = loadCredentials();
|
|
260
260
|
if (!creds)
|
|
261
261
|
throw new Error("Not logged in. Run deeplake login first.");
|
|
262
|
-
|
|
262
|
+
const apiUrl = creds.apiUrl ?? DEFAULT_API_URL;
|
|
263
|
+
const tokenName = `deeplake-plugin-switch-${Date.now()}`;
|
|
264
|
+
const tokenData = await apiPost("/users/me/tokens", {
|
|
265
|
+
name: tokenName,
|
|
266
|
+
duration: 365 * 24 * 3600,
|
|
267
|
+
organization_id: orgId
|
|
268
|
+
}, creds.token, apiUrl);
|
|
269
|
+
saveCredentials({ ...creds, orgId, orgName, token: tokenData.token.token });
|
|
263
270
|
}
|
|
264
271
|
async function listWorkspaces(token, apiUrl = DEFAULT_API_URL, orgId) {
|
|
265
272
|
const raw = await apiGet("/workspaces", token, apiUrl, orgId);
|
|
@@ -91,6 +91,65 @@ function loadCredentials() {
|
|
|
91
91
|
return null;
|
|
92
92
|
}
|
|
93
93
|
}
|
|
94
|
+
function saveCredentials(creds) {
|
|
95
|
+
mkdirSync2(configDir(), { recursive: true, mode: 448 });
|
|
96
|
+
writeFileSync2(credsPath(), JSON.stringify({ ...creds, savedAt: (/* @__PURE__ */ new Date()).toISOString() }, null, 2), { mode: 384 });
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// dist/src/commands/auth.js
|
|
100
|
+
var DEFAULT_API_URL = "https://api.deeplake.ai";
|
|
101
|
+
function decodeJwtPayload(token) {
|
|
102
|
+
try {
|
|
103
|
+
const parts = token.split(".");
|
|
104
|
+
if (parts.length !== 3)
|
|
105
|
+
return null;
|
|
106
|
+
let payload = parts[1].replace(/-/g, "+").replace(/_/g, "/");
|
|
107
|
+
while (payload.length % 4)
|
|
108
|
+
payload += "=";
|
|
109
|
+
return JSON.parse(Buffer.from(payload, "base64").toString("utf8"));
|
|
110
|
+
} catch {
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
async function apiPost(path, body, token, apiUrl, orgId) {
|
|
115
|
+
const headers = {
|
|
116
|
+
Authorization: `Bearer ${token}`,
|
|
117
|
+
"Content-Type": "application/json",
|
|
118
|
+
...deeplakeClientHeader()
|
|
119
|
+
};
|
|
120
|
+
if (orgId)
|
|
121
|
+
headers["X-Activeloop-Org-Id"] = orgId;
|
|
122
|
+
const resp = await fetch(`${apiUrl}${path}`, { method: "POST", headers, body: JSON.stringify(body) });
|
|
123
|
+
if (!resp.ok)
|
|
124
|
+
throw new Error(`API ${resp.status}: ${await resp.text().catch(() => "")}`);
|
|
125
|
+
return resp.json();
|
|
126
|
+
}
|
|
127
|
+
async function healDriftedOrgToken(creds, log7 = () => {
|
|
128
|
+
}) {
|
|
129
|
+
if (!creds.token || !creds.orgId)
|
|
130
|
+
return creds;
|
|
131
|
+
const payload = decodeJwtPayload(creds.token);
|
|
132
|
+
const claimOrg = payload && typeof payload.org_id === "string" ? payload.org_id : void 0;
|
|
133
|
+
if (!claimOrg || claimOrg === creds.orgId)
|
|
134
|
+
return creds;
|
|
135
|
+
log7(`token org drift detected: jwt.org_id=${claimOrg} creds.orgId=${creds.orgId} \u2014 re-minting`);
|
|
136
|
+
try {
|
|
137
|
+
const apiUrl = creds.apiUrl ?? DEFAULT_API_URL;
|
|
138
|
+
const tokenName = `deeplake-plugin-heal-${Date.now()}`;
|
|
139
|
+
const tokenData = await apiPost("/users/me/tokens", {
|
|
140
|
+
name: tokenName,
|
|
141
|
+
duration: 365 * 24 * 3600,
|
|
142
|
+
organization_id: creds.orgId
|
|
143
|
+
}, creds.token, apiUrl);
|
|
144
|
+
const healed = { ...creds, token: tokenData.token.token };
|
|
145
|
+
saveCredentials(healed);
|
|
146
|
+
log7(`token re-minted for org=${creds.orgId}`);
|
|
147
|
+
return healed;
|
|
148
|
+
} catch (err) {
|
|
149
|
+
log7(`token re-mint failed (continuing with stale token): ${err.message}`);
|
|
150
|
+
return creds;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
94
153
|
|
|
95
154
|
// dist/src/config.js
|
|
96
155
|
import { readFileSync as readFileSync3, existsSync } from "node:fs";
|
|
@@ -2156,13 +2215,14 @@ async function main() {
|
|
|
2156
2215
|
const input = await readStdin();
|
|
2157
2216
|
const sessionId = resolveSessionId(input);
|
|
2158
2217
|
const cwd = resolveCwd(input);
|
|
2159
|
-
|
|
2218
|
+
let creds = loadCredentials();
|
|
2160
2219
|
if (!creds?.token) {
|
|
2161
2220
|
log6("no credentials found");
|
|
2162
2221
|
const auto = maybeAutoMineLocal();
|
|
2163
2222
|
log6(`auto-mine: ${auto.triggered ? "triggered (background)" : `skipped (${auto.reason})`}`);
|
|
2164
2223
|
} else {
|
|
2165
2224
|
log6(`credentials loaded: org=${creds.orgName ?? creds.orgId}`);
|
|
2225
|
+
creds = await healDriftedOrgToken(creds, log6);
|
|
2166
2226
|
}
|
|
2167
2227
|
await autoUpdate(creds, { agent: "cursor" });
|
|
2168
2228
|
const current = getInstalledVersion(__bundleDir, ".claude-plugin");
|
|
@@ -259,7 +259,14 @@ async function switchOrg(orgId, orgName) {
|
|
|
259
259
|
const creds = loadCredentials();
|
|
260
260
|
if (!creds)
|
|
261
261
|
throw new Error("Not logged in. Run deeplake login first.");
|
|
262
|
-
|
|
262
|
+
const apiUrl = creds.apiUrl ?? DEFAULT_API_URL;
|
|
263
|
+
const tokenName = `deeplake-plugin-switch-${Date.now()}`;
|
|
264
|
+
const tokenData = await apiPost("/users/me/tokens", {
|
|
265
|
+
name: tokenName,
|
|
266
|
+
duration: 365 * 24 * 3600,
|
|
267
|
+
organization_id: orgId
|
|
268
|
+
}, creds.token, apiUrl);
|
|
269
|
+
saveCredentials({ ...creds, orgId, orgName, token: tokenData.token.token });
|
|
263
270
|
}
|
|
264
271
|
async function listWorkspaces(token, apiUrl = DEFAULT_API_URL, orgId) {
|
|
265
272
|
const raw = await apiGet("/workspaces", token, apiUrl, orgId);
|
|
@@ -90,6 +90,65 @@ function loadCredentials() {
|
|
|
90
90
|
return null;
|
|
91
91
|
}
|
|
92
92
|
}
|
|
93
|
+
function saveCredentials(creds) {
|
|
94
|
+
mkdirSync2(configDir(), { recursive: true, mode: 448 });
|
|
95
|
+
writeFileSync2(credsPath(), JSON.stringify({ ...creds, savedAt: (/* @__PURE__ */ new Date()).toISOString() }, null, 2), { mode: 384 });
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// dist/src/commands/auth.js
|
|
99
|
+
var DEFAULT_API_URL = "https://api.deeplake.ai";
|
|
100
|
+
function decodeJwtPayload(token) {
|
|
101
|
+
try {
|
|
102
|
+
const parts = token.split(".");
|
|
103
|
+
if (parts.length !== 3)
|
|
104
|
+
return null;
|
|
105
|
+
let payload = parts[1].replace(/-/g, "+").replace(/_/g, "/");
|
|
106
|
+
while (payload.length % 4)
|
|
107
|
+
payload += "=";
|
|
108
|
+
return JSON.parse(Buffer.from(payload, "base64").toString("utf8"));
|
|
109
|
+
} catch {
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
async function apiPost(path, body, token, apiUrl, orgId) {
|
|
114
|
+
const headers = {
|
|
115
|
+
Authorization: `Bearer ${token}`,
|
|
116
|
+
"Content-Type": "application/json",
|
|
117
|
+
...deeplakeClientHeader()
|
|
118
|
+
};
|
|
119
|
+
if (orgId)
|
|
120
|
+
headers["X-Activeloop-Org-Id"] = orgId;
|
|
121
|
+
const resp = await fetch(`${apiUrl}${path}`, { method: "POST", headers, body: JSON.stringify(body) });
|
|
122
|
+
if (!resp.ok)
|
|
123
|
+
throw new Error(`API ${resp.status}: ${await resp.text().catch(() => "")}`);
|
|
124
|
+
return resp.json();
|
|
125
|
+
}
|
|
126
|
+
async function healDriftedOrgToken(creds, log7 = () => {
|
|
127
|
+
}) {
|
|
128
|
+
if (!creds.token || !creds.orgId)
|
|
129
|
+
return creds;
|
|
130
|
+
const payload = decodeJwtPayload(creds.token);
|
|
131
|
+
const claimOrg = payload && typeof payload.org_id === "string" ? payload.org_id : void 0;
|
|
132
|
+
if (!claimOrg || claimOrg === creds.orgId)
|
|
133
|
+
return creds;
|
|
134
|
+
log7(`token org drift detected: jwt.org_id=${claimOrg} creds.orgId=${creds.orgId} \u2014 re-minting`);
|
|
135
|
+
try {
|
|
136
|
+
const apiUrl = creds.apiUrl ?? DEFAULT_API_URL;
|
|
137
|
+
const tokenName = `deeplake-plugin-heal-${Date.now()}`;
|
|
138
|
+
const tokenData = await apiPost("/users/me/tokens", {
|
|
139
|
+
name: tokenName,
|
|
140
|
+
duration: 365 * 24 * 3600,
|
|
141
|
+
organization_id: creds.orgId
|
|
142
|
+
}, creds.token, apiUrl);
|
|
143
|
+
const healed = { ...creds, token: tokenData.token.token };
|
|
144
|
+
saveCredentials(healed);
|
|
145
|
+
log7(`token re-minted for org=${creds.orgId}`);
|
|
146
|
+
return healed;
|
|
147
|
+
} catch (err) {
|
|
148
|
+
log7(`token re-mint failed (continuing with stale token): ${err.message}`);
|
|
149
|
+
return creds;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
93
152
|
|
|
94
153
|
// dist/src/config.js
|
|
95
154
|
import { readFileSync as readFileSync3, existsSync } from "node:fs";
|
|
@@ -2146,10 +2205,12 @@ async function main() {
|
|
|
2146
2205
|
const input = await readStdin();
|
|
2147
2206
|
const sessionId = input.session_id ?? `hermes-${Date.now()}`;
|
|
2148
2207
|
const cwd = input.cwd ?? process.cwd();
|
|
2149
|
-
|
|
2208
|
+
let creds = loadCredentials();
|
|
2150
2209
|
const captureEnabled = process.env.HIVEMIND_CAPTURE !== "false";
|
|
2151
2210
|
if (!creds?.token) {
|
|
2152
2211
|
maybeAutoMineLocal();
|
|
2212
|
+
} else {
|
|
2213
|
+
creds = await healDriftedOrgToken(creds, log6);
|
|
2153
2214
|
}
|
|
2154
2215
|
await autoUpdate(creds, { agent: "hermes" });
|
|
2155
2216
|
const current = getInstalledVersion(__bundleDir, ".claude-plugin");
|
package/openclaw/dist/index.js
CHANGED
|
@@ -52,6 +52,17 @@ function hivemindInstallIDHeader() {
|
|
|
52
52
|
|
|
53
53
|
// src/commands/auth.ts
|
|
54
54
|
var DEFAULT_API_URL = "https://api.deeplake.ai";
|
|
55
|
+
function decodeJwtPayload(token) {
|
|
56
|
+
try {
|
|
57
|
+
const parts = token.split(".");
|
|
58
|
+
if (parts.length !== 3) return null;
|
|
59
|
+
let payload = parts[1].replace(/-/g, "+").replace(/_/g, "/");
|
|
60
|
+
while (payload.length % 4) payload += "=";
|
|
61
|
+
return JSON.parse(Buffer.from(payload, "base64").toString("utf8"));
|
|
62
|
+
} catch {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
55
66
|
async function apiGet(path, token, apiUrl, orgId) {
|
|
56
67
|
const headers = {
|
|
57
68
|
Authorization: `Bearer ${token}`,
|
|
@@ -63,6 +74,17 @@ async function apiGet(path, token, apiUrl, orgId) {
|
|
|
63
74
|
if (!resp.ok) throw new Error(`API ${resp.status}: ${await resp.text().catch(() => "")}`);
|
|
64
75
|
return resp.json();
|
|
65
76
|
}
|
|
77
|
+
async function apiPost(path, body, token, apiUrl, orgId) {
|
|
78
|
+
const headers = {
|
|
79
|
+
Authorization: `Bearer ${token}`,
|
|
80
|
+
"Content-Type": "application/json",
|
|
81
|
+
...deeplakeClientHeader()
|
|
82
|
+
};
|
|
83
|
+
if (orgId) headers["X-Activeloop-Org-Id"] = orgId;
|
|
84
|
+
const resp = await fetch(`${apiUrl}${path}`, { method: "POST", headers, body: JSON.stringify(body) });
|
|
85
|
+
if (!resp.ok) throw new Error(`API ${resp.status}: ${await resp.text().catch(() => "")}`);
|
|
86
|
+
return resp.json();
|
|
87
|
+
}
|
|
66
88
|
async function requestDeviceCode(apiUrl = DEFAULT_API_URL) {
|
|
67
89
|
const resp = await fetch(`${apiUrl}/auth/device/code`, {
|
|
68
90
|
method: "POST",
|
|
@@ -101,7 +123,38 @@ async function listOrgs(token, apiUrl = DEFAULT_API_URL) {
|
|
|
101
123
|
async function switchOrg(orgId, orgName) {
|
|
102
124
|
const creds = loadCredentials();
|
|
103
125
|
if (!creds) throw new Error("Not logged in. Run deeplake login first.");
|
|
104
|
-
|
|
126
|
+
const apiUrl = creds.apiUrl ?? DEFAULT_API_URL;
|
|
127
|
+
const tokenName = `deeplake-plugin-switch-${Date.now()}`;
|
|
128
|
+
const tokenData = await apiPost("/users/me/tokens", {
|
|
129
|
+
name: tokenName,
|
|
130
|
+
duration: 365 * 24 * 3600,
|
|
131
|
+
organization_id: orgId
|
|
132
|
+
}, creds.token, apiUrl);
|
|
133
|
+
saveCredentials({ ...creds, orgId, orgName, token: tokenData.token.token });
|
|
134
|
+
}
|
|
135
|
+
async function healDriftedOrgToken(creds, log4 = () => {
|
|
136
|
+
}) {
|
|
137
|
+
if (!creds.token || !creds.orgId) return creds;
|
|
138
|
+
const payload = decodeJwtPayload(creds.token);
|
|
139
|
+
const claimOrg = payload && typeof payload.org_id === "string" ? payload.org_id : void 0;
|
|
140
|
+
if (!claimOrg || claimOrg === creds.orgId) return creds;
|
|
141
|
+
log4(`token org drift detected: jwt.org_id=${claimOrg} creds.orgId=${creds.orgId} \u2014 re-minting`);
|
|
142
|
+
try {
|
|
143
|
+
const apiUrl = creds.apiUrl ?? DEFAULT_API_URL;
|
|
144
|
+
const tokenName = `deeplake-plugin-heal-${Date.now()}`;
|
|
145
|
+
const tokenData = await apiPost("/users/me/tokens", {
|
|
146
|
+
name: tokenName,
|
|
147
|
+
duration: 365 * 24 * 3600,
|
|
148
|
+
organization_id: creds.orgId
|
|
149
|
+
}, creds.token, apiUrl);
|
|
150
|
+
const healed = { ...creds, token: tokenData.token.token };
|
|
151
|
+
saveCredentials(healed);
|
|
152
|
+
log4(`token re-minted for org=${creds.orgId}`);
|
|
153
|
+
return healed;
|
|
154
|
+
} catch (err) {
|
|
155
|
+
log4(`token re-mint failed (continuing with stale token): ${err.message}`);
|
|
156
|
+
return creds;
|
|
157
|
+
}
|
|
105
158
|
}
|
|
106
159
|
async function listWorkspaces(token, apiUrl = DEFAULT_API_URL, orgId) {
|
|
107
160
|
const raw = await apiGet("/workspaces", token, apiUrl, orgId);
|
|
@@ -1727,7 +1780,7 @@ function extractLatestVersion(body) {
|
|
|
1727
1780
|
return typeof v === "string" && v.length > 0 ? v : null;
|
|
1728
1781
|
}
|
|
1729
1782
|
function getInstalledVersion() {
|
|
1730
|
-
return "0.7.
|
|
1783
|
+
return "0.7.53".length > 0 ? "0.7.53" : null;
|
|
1731
1784
|
}
|
|
1732
1785
|
function isNewer(latest, current) {
|
|
1733
1786
|
const parse = (v) => v.replace(/-.*$/, "").split(".").map(Number);
|
|
@@ -1752,6 +1805,7 @@ async function checkForUpdate(logger) {
|
|
|
1752
1805
|
}
|
|
1753
1806
|
var authPending = false;
|
|
1754
1807
|
var authUrl = null;
|
|
1808
|
+
var driftHealPromise = null;
|
|
1755
1809
|
var pendingUpdate = null;
|
|
1756
1810
|
var justAuthenticated = false;
|
|
1757
1811
|
async function requestAuth() {
|
|
@@ -2010,6 +2064,16 @@ function normalizeVirtualPath(p) {
|
|
|
2010
2064
|
}
|
|
2011
2065
|
async function getApi() {
|
|
2012
2066
|
if (api) return api;
|
|
2067
|
+
if (!driftHealPromise) {
|
|
2068
|
+
driftHealPromise = (async () => {
|
|
2069
|
+
try {
|
|
2070
|
+
const creds = await loadCredentials2();
|
|
2071
|
+
if (creds?.token) await healDriftedOrgToken(creds);
|
|
2072
|
+
} catch {
|
|
2073
|
+
}
|
|
2074
|
+
})();
|
|
2075
|
+
}
|
|
2076
|
+
await driftHealPromise;
|
|
2013
2077
|
const config = await loadConfig();
|
|
2014
2078
|
if (!config) {
|
|
2015
2079
|
if (!authPending) await requestAuth();
|
package/openclaw/package.json
CHANGED
package/package.json
CHANGED
|
@@ -93,6 +93,52 @@ function loadCreds(): Creds | null {
|
|
|
93
93
|
}
|
|
94
94
|
}
|
|
95
95
|
|
|
96
|
+
// Inline copies of decodeJwtPayload + healDriftedOrgToken (the shared helpers
|
|
97
|
+
// live in src/commands/auth.ts, but pi extensions ship as raw .ts with no
|
|
98
|
+
// shared-module imports — kept in lockstep with that file).
|
|
99
|
+
function decodeJwtPayloadInline(token: string): Record<string, unknown> | null {
|
|
100
|
+
try {
|
|
101
|
+
const parts = token.split(".");
|
|
102
|
+
if (parts.length !== 3) return null;
|
|
103
|
+
let payload = parts[1].replace(/-/g, "+").replace(/_/g, "/");
|
|
104
|
+
while (payload.length % 4) payload += "=";
|
|
105
|
+
return JSON.parse(Buffer.from(payload, "base64").toString("utf8"));
|
|
106
|
+
} catch { return null; }
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async function healDriftedOrgTokenInline(creds: Creds): Promise<Creds> {
|
|
110
|
+
if (!creds.token || !creds.orgId) return creds;
|
|
111
|
+
const payload = decodeJwtPayloadInline(creds.token);
|
|
112
|
+
const claimOrg = payload && typeof payload.org_id === "string" ? payload.org_id : undefined;
|
|
113
|
+
if (!claimOrg || claimOrg === creds.orgId) return creds;
|
|
114
|
+
logHm(`session_start: token org drift detected: jwt.org_id=${claimOrg} creds.orgId=${creds.orgId} — re-minting`);
|
|
115
|
+
try {
|
|
116
|
+
const resp = await fetch(`${creds.apiUrl}/users/me/tokens`, {
|
|
117
|
+
method: "POST",
|
|
118
|
+
headers: { Authorization: `Bearer ${creds.token}`, "Content-Type": "application/json" },
|
|
119
|
+
body: JSON.stringify({
|
|
120
|
+
name: `deeplake-plugin-heal-${Date.now()}`,
|
|
121
|
+
duration: 365 * 24 * 3600,
|
|
122
|
+
organization_id: creds.orgId,
|
|
123
|
+
}),
|
|
124
|
+
});
|
|
125
|
+
if (!resp.ok) throw new Error(`API ${resp.status}: ${(await resp.text().catch(() => "")).slice(0, 200)}`);
|
|
126
|
+
const data = await resp.json() as { token: { token: string } };
|
|
127
|
+
const newToken = data.token.token;
|
|
128
|
+
// Read + merge + write the WHOLE creds file so we don't drop fields pi
|
|
129
|
+
// doesn't model (e.g. savedAt). Atomic via writeFileSync with mode 0o600.
|
|
130
|
+
const path = join(homedir(), ".deeplake", "credentials.json");
|
|
131
|
+
const raw = JSON.parse(readFileSync(path, "utf-8"));
|
|
132
|
+
raw.token = newToken;
|
|
133
|
+
writeFileSync(path, JSON.stringify(raw, null, 2), { mode: 0o600 });
|
|
134
|
+
logHm(`session_start: token re-minted for org=${creds.orgId}`);
|
|
135
|
+
return { ...creds, token: newToken };
|
|
136
|
+
} catch (e: any) {
|
|
137
|
+
logHm(`session_start: token re-mint failed (continuing with stale token): ${e?.message ?? e}`);
|
|
138
|
+
return creds;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
96
142
|
const MEMORY_TABLE = process.env.HIVEMIND_TABLE ?? "memory";
|
|
97
143
|
const SESSIONS_TABLE = process.env.HIVEMIND_SESSIONS_TABLE ?? "sessions";
|
|
98
144
|
|
|
@@ -1076,11 +1122,12 @@ export default function hivemindExtension(pi: ExtensionAPI): void {
|
|
|
1076
1122
|
|
|
1077
1123
|
pi.on("session_start", async (_event: any, _ctx: any) => {
|
|
1078
1124
|
logHm(`session_start: fired (capture=${captureEnabled}, embed=${process.env.HIVEMIND_EMBEDDINGS !== "false"}, table=${SESSIONS_TABLE})`);
|
|
1079
|
-
|
|
1125
|
+
let creds = loadCreds();
|
|
1080
1126
|
if (!creds) {
|
|
1081
1127
|
logHm(`session_start: no credentials at ~/.deeplake/credentials.json — capture disabled this session`);
|
|
1082
1128
|
} else {
|
|
1083
1129
|
logHm(`session_start: creds org=${creds.orgName ?? creds.orgId} ws=${creds.workspaceId}`);
|
|
1130
|
+
creds = await healDriftedOrgTokenInline(creds);
|
|
1084
1131
|
}
|
|
1085
1132
|
|
|
1086
1133
|
// Centralized autoupdate: shells out to `hivemind update` (npm-based,
|