Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 2 additions & 29 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,37 +82,10 @@ The plugin auto-updates on each session start. To manually update:
#### Install from ClawHub (Telegram, TUI, WhatsApp):

```
openclaw plugins install clawhub:hivemind
openclaw plugins install hivemind
```

Then type `/hivemind_login` in chat, click the auth link, and sign in.

#### Commands

| Command | Description |
|---------|-------------|
| `/hivemind_login` | Sign in via device flow |
| `/hivemind_capture` | Toggle capture on/off |
| `/hivemind_whoami` | Show current org and workspace |
| `/hivemind_orgs` | List organizations |
| `/hivemind_switch_org <name>` | Switch organization |
| `/hivemind_workspaces` | List workspaces |
| `/hivemind_switch_workspace <id>` | Switch workspace |
| `/hivemind_update` | Check for plugin updates |

Auto-recall and auto-capture are enabled by default. Data is stored in the same `sessions` table as Claude Code and Codex.

#### Coexistence with `memory-core`

Hivemind runs **alongside** OpenClaw's built-in `memory-core` plugin. It does **not** claim the memory slot, so `memory-core`'s dreaming cron (`"0 3 * * *"`) and other memory-slot-dependent jobs keep working. Hivemind captures session activity and exposes its own commands; `memory-core` keeps owning recall/promotion/dreaming.

#### Troubleshooting

- **Hivemind seems slow or unresponsive.** Check the agent model in `~/.openclaw/openclaw.json` under `agents.defaults.model`. Hivemind makes many small tool calls per turn; a large reasoning model like Opus will feel sluggish. Recommended default: `anthropic/claude-haiku-4-5-20251001`.
- **`openclaw model <id>` says "plugins.allow excludes model".** The `model` plugin CLI is disabled by default. Edit `~/.openclaw/openclaw.json` directly (key `agents.defaults.model`) and restart the gateway: `systemctl --user restart openclaw-gateway.service`.
- **Model switch rejected as "not allowed".** Use the exact dated provider-prefixed ID (`anthropic/claude-haiku-4-5-20251001`, `anthropic/claude-sonnet-4-6`). Legacy IDs like `claude-3-5-haiku-latest` and unprefixed bare IDs are not on OpenClaw's allowlist.
- **Self-update via Telegram fails with "elevated is not available".** `tools.elevated.allowFrom` must include `telegram` before elevated commands work from that channel. Safer alternative: run the upgrade in a local shell with `openclaw plugins update hivemind`.
- **`npm error EACCES` during self-update.** OpenClaw was installed under a root-owned npm prefix (e.g. `/usr/lib/node_modules/openclaw`). Reinstall under a user-writable prefix, or run the update with appropriate privileges locally — not via a channel.
Send a message. The plugin sends you an auth link. Click, sign in, done.
</details>
<details>
<summary><b> Codex Setup </b></summary>
Expand Down
79 changes: 0 additions & 79 deletions openclaw/README.md

This file was deleted.

22 changes: 10 additions & 12 deletions openclaw/skills/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
---
name: hivemind
description: Cloud-backed shared memory for AI agents. Install once, memory persists across sessions, machines, and channels.
allowed-tools: Read, Bash
allowed-tools: Read
---

# Hivemind
# Hivemind Memory

Cloud-backed shared memory powered by Deeplake.

Expand All @@ -14,26 +14,24 @@ Cloud-backed shared memory powered by Deeplake.

## Authentication

The user types `/hivemind_login` in chat. The plugin returns an auth URL. The user clicks it, signs in, and memory activates on the next message. A long-lived API token is stored at `~/.deeplake/credentials.json`.
The user types `/hivemind_login` in chat. The plugin returns an auth URL. The user clicks it, signs in, and memory activates on the next message.

## What the plugin does
## How it works

- **Captures** every conversation (user + assistant messages) and sends them to `api.deeplake.ai`. Disable anytime with `/hivemind_capture`.
- **Recalls** relevant memories before each agent turn via keyword search.
- **Stores** a long-lived API token at `~/.deeplake/credentials.json` after login.
- **Does NOT** modify OpenClaw configuration or replace the built-in memory plugin.
- All network requests go to `api.deeplake.ai` only.
The plugin automatically:
- **Captures** every conversation (user + assistant messages) to Deeplake cloud
- **Recalls** relevant memories before each agent turn via keyword search
- All data stored as structured rows — searchable, persistent, shared

## Commands

- `/hivemind_login` — sign in via device flow
- `/hivemind_capture` — toggle capture on/off (off = no data sent)
- `/hivemind_login` — sign in
- `/hivemind_capture` — toggle capture on/off
- `/hivemind_whoami` — show current org and workspace
- `/hivemind_orgs` — list organizations
- `/hivemind_switch_org <name-or-id>` — switch organization
- `/hivemind_workspaces` — list workspaces
- `/hivemind_switch_workspace <id>` — switch workspace
- `/hivemind_update` — check for plugin updates

## Sharing memory

Expand Down
101 changes: 28 additions & 73 deletions openclaw/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
function definePluginEntry<T>(entry: T): T { return entry; }
import { existsSync, readFileSync, writeFileSync } from "node:fs";
import { homedir } from "node:os";
import { join } from "node:path";

// Shared core imports
import { loadConfig } from "../../src/config.js";
import { loadCredentials, saveCredentials, requestDeviceCode, pollForToken, listOrgs, switchOrg, listWorkspaces, switchWorkspace } from "../../src/commands/auth.js";
Expand Down Expand Up @@ -34,46 +38,6 @@ interface PluginAPI {
}

const DEFAULT_API_URL = "https://api.deeplake.ai";
const VERSION_URL = "https://raw.githubusercontent.com/activeloopai/hivemind/main/openclaw/openclaw.plugin.json";

async function getInstalledVersion(): Promise<string | null> {
try {
const [{ readFileSync }, { join }] = await Promise.all([
import("node:fs"),
import("node:path"),
]);
const dir = new URL(".", import.meta.url).pathname;
const candidates = [join(dir, "..", "package.json"), join(dir, "package.json")];
for (const c of candidates) {
try {
const pkg = JSON.parse(readFileSync(c, "utf-8"));
if (pkg.name === "hivemind" && pkg.version) return pkg.version;
} catch {}
}
} catch {}
return null;
}

function isNewer(latest: string, current: string): boolean {
const parse = (v: string) => v.replace(/-.*$/, "").split(".").map(Number);
const [la, lb, lc] = parse(latest);
const [ca, cb, cc] = parse(current);
return la > ca || (la === ca && lb > cb) || (la === ca && lb === cb && lc > cc);
}

async function checkForUpdate(logger: PluginLogger): Promise<void> {
try {
const current = await getInstalledVersion();
if (!current) return;
const res = await fetch(VERSION_URL, { signal: AbortSignal.timeout(3000) });
if (!res.ok) return;
const manifest = await res.json() as { version?: string };
const latest = manifest.version ?? null;
if (latest && isNewer(latest, current)) {
logger.info?.(`⬆️ Hivemind update available: ${current} → ${latest}. Run: openclaw plugins update hivemind`);
}
} catch {}
}

// --- Auth state ---
let authPending = false;
Expand Down Expand Up @@ -143,6 +107,22 @@ async function requestAuth(): Promise<string> {
}
}

// --- OpenClaw-specific: ensure plugin is in load.paths for hook wiring ---
function addToLoadPaths(): void {
const ocConfigPath = join(homedir(), ".openclaw", "openclaw.json");
if (!existsSync(ocConfigPath)) return;
try {
const ocConfig = JSON.parse(readFileSync(ocConfigPath, "utf-8"));
const installPath = ocConfig?.plugins?.installs?.["hivemind"]?.installPath;
if (!installPath) return;
const loadPaths: string[] = ocConfig?.plugins?.load?.paths ?? [];
if (loadPaths.includes(installPath)) return;
if (!ocConfig.plugins.load) ocConfig.plugins.load = {};
ocConfig.plugins.load.paths = [...loadPaths, installPath];
writeFileSync(ocConfigPath, JSON.stringify(ocConfig, null, 2));
} catch {}
}

// --- API instance ---
let api: DeeplakeApi | null = null;
let sessionsTable = "sessions";
Expand Down Expand Up @@ -174,9 +154,12 @@ export default definePluginEntry({
id: "hivemind",
name: "Hivemind",
description: "Cloud-backed shared memory powered by Deeplake",
kind: "memory",

register(pluginApi: PluginAPI) {
try {
addToLoadPaths();

// Login command — works immediately after install, no hook dependency
if (pluginApi.registerCommand) {
pluginApi.registerCommand({
Expand Down Expand Up @@ -272,28 +255,6 @@ export default definePluginEntry({
return { text: `Switched to workspace: ${match.name}` };
},
});

pluginApi.registerCommand({
name: "hivemind_update",
description: "Check for Hivemind updates and show how to upgrade",
handler: async () => {
const current = await getInstalledVersion();
if (!current) return { text: "Could not determine installed version." };
try {
const res = await fetch(VERSION_URL, { signal: AbortSignal.timeout(3000) });
if (!res.ok) return { text: `Current version: ${current}. Could not check for updates.` };
const pkg = await res.json();
const latest = typeof pkg.version === "string" ? pkg.version : null;
if (!latest) return { text: `Current version: ${current}. Could not parse latest version.` };
if (isNewer(latest, current)) {
return { text: `⬆️ Update available: ${current} → ${latest}\n\nRun in your terminal:\n\`openclaw plugins update hivemind\`` };
}
return { text: `✅ Hivemind v${current} is up to date.` };
} catch {
return { text: `Current version: ${current}. Could not check for updates.` };
}
},
});
}

const config = (pluginApi.pluginConfig ?? {}) as PluginConfig;
Expand Down Expand Up @@ -427,20 +388,14 @@ export default definePluginEntry({
});
}

// Prompt login if not authenticated
// Pre-fetch auth URL during registration
const creds = loadCredentials();
if (!creds?.token) {
logger.info?.("Hivemind installed. Run /hivemind_login to authenticate and activate shared memory.");
if (!authPending) {
requestAuth().catch(err => {
logger.error(`Pre-auth failed: ${err instanceof Error ? err.message : String(err)}`);
});
}
if (!creds?.token && !authPending) {
requestAuth().catch(err => {
logger.error(`Pre-auth failed: ${err instanceof Error ? err.message : String(err)}`);
});
}

// Non-blocking version check
checkForUpdate(logger).catch(() => {});

logger.info?.("Hivemind plugin registered");
} catch (err) {
pluginApi.logger?.error?.(`Hivemind register failed: ${err instanceof Error ? err.message : String(err)}`);
Expand Down
Loading