mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-03 03:03:24 -04:00
refactor: rename to openclaw
This commit is contained in:
@@ -3,7 +3,7 @@ import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
export function resolveBundledHooksDir(): string | undefined {
|
||||
const override = process.env.CLAWDBOT_BUNDLED_HOOKS_DIR?.trim();
|
||||
const override = process.env.OPENCLAW_BUNDLED_HOOKS_DIR?.trim();
|
||||
if (override) return override;
|
||||
|
||||
// bun --compile: ship a sibling `hooks/bundled/` next to the executable.
|
||||
@@ -16,7 +16,7 @@ export function resolveBundledHooksDir(): string | undefined {
|
||||
}
|
||||
|
||||
// npm: resolve `<packageRoot>/dist/hooks/bundled` relative to this module (compiled hooks).
|
||||
// This path works when installed via npm: node_modules/moltbot/dist/hooks/bundled-dir.js
|
||||
// This path works when installed via npm: node_modules/openclaw/dist/hooks/bundled-dir.js
|
||||
try {
|
||||
const moduleDir = path.dirname(fileURLToPath(import.meta.url));
|
||||
const distBundled = path.join(moduleDir, "bundled");
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Bundled Hooks
|
||||
|
||||
This directory contains hooks that ship with Clawdbot. These hooks are automatically discovered and can be enabled/disabled via CLI or configuration.
|
||||
This directory contains hooks that ship with OpenClaw. These hooks are automatically discovered and can be enabled/disabled via CLI or configuration.
|
||||
|
||||
## Available Hooks
|
||||
|
||||
@@ -10,12 +10,12 @@ Automatically saves session context to memory when you issue `/new`.
|
||||
|
||||
**Events**: `command:new`
|
||||
**What it does**: Creates a dated memory file with LLM-generated slug based on conversation content.
|
||||
**Output**: `<workspace>/memory/YYYY-MM-DD-slug.md` (defaults to `~/clawd`)
|
||||
**Output**: `<workspace>/memory/YYYY-MM-DD-slug.md` (defaults to `~/.openclaw/workspace`)
|
||||
|
||||
**Enable**:
|
||||
|
||||
```bash
|
||||
clawdbot hooks enable session-memory
|
||||
openclaw hooks enable session-memory
|
||||
```
|
||||
|
||||
### 📝 command-logger
|
||||
@@ -24,12 +24,12 @@ Logs all command events to a centralized audit file.
|
||||
|
||||
**Events**: `command` (all commands)
|
||||
**What it does**: Appends JSONL entries to command log file.
|
||||
**Output**: `~/.clawdbot/logs/commands.log`
|
||||
**Output**: `~/.openclaw/logs/commands.log`
|
||||
|
||||
**Enable**:
|
||||
|
||||
```bash
|
||||
clawdbot hooks enable command-logger
|
||||
openclaw hooks enable command-logger
|
||||
```
|
||||
|
||||
### 😈 soul-evil
|
||||
@@ -39,12 +39,12 @@ Swaps injected `SOUL.md` content with `SOUL_EVIL.md` during a purge window or by
|
||||
**Events**: `agent:bootstrap`
|
||||
**What it does**: Overrides the injected SOUL content before the system prompt is built.
|
||||
**Output**: No files written; swaps happen in-memory only.
|
||||
**Docs**: https://docs.molt.bot/hooks/soul-evil
|
||||
**Docs**: https://docs.openclaw.ai/hooks/soul-evil
|
||||
|
||||
**Enable**:
|
||||
|
||||
```bash
|
||||
clawdbot hooks enable soul-evil
|
||||
openclaw hooks enable soul-evil
|
||||
```
|
||||
|
||||
### 🚀 boot-md
|
||||
@@ -58,7 +58,7 @@ Runs `BOOT.md` whenever the gateway starts (after channels start).
|
||||
**Enable**:
|
||||
|
||||
```bash
|
||||
clawdbot hooks enable boot-md
|
||||
openclaw hooks enable boot-md
|
||||
```
|
||||
|
||||
## Hook Structure
|
||||
@@ -82,9 +82,9 @@ session-memory/
|
||||
---
|
||||
name: my-hook
|
||||
description: "Short description"
|
||||
homepage: https://docs.molt.bot/hooks#my-hook
|
||||
homepage: https://docs.openclaw.ai/hooks#my-hook
|
||||
metadata:
|
||||
{ "clawdbot": { "emoji": "🔗", "events": ["command:new"], "requires": { "bins": ["node"] } } }
|
||||
{ "openclaw": { "emoji": "🔗", "events": ["command:new"], "requires": { "bins": ["node"] } } }
|
||||
---
|
||||
# Hook Title
|
||||
|
||||
@@ -108,7 +108,7 @@ Documentation goes here...
|
||||
To create your own hooks, place them in:
|
||||
|
||||
- **Workspace hooks**: `<workspace>/hooks/` (highest precedence)
|
||||
- **Managed hooks**: `~/.clawdbot/hooks/` (shared across workspaces)
|
||||
- **Managed hooks**: `~/.openclaw/hooks/` (shared across workspaces)
|
||||
|
||||
Custom hooks follow the same structure as bundled hooks.
|
||||
|
||||
@@ -117,31 +117,31 @@ Custom hooks follow the same structure as bundled hooks.
|
||||
List all hooks:
|
||||
|
||||
```bash
|
||||
clawdbot hooks list
|
||||
openclaw hooks list
|
||||
```
|
||||
|
||||
Show hook details:
|
||||
|
||||
```bash
|
||||
clawdbot hooks info session-memory
|
||||
openclaw hooks info session-memory
|
||||
```
|
||||
|
||||
Check hook status:
|
||||
|
||||
```bash
|
||||
clawdbot hooks check
|
||||
openclaw hooks check
|
||||
```
|
||||
|
||||
Enable/disable:
|
||||
|
||||
```bash
|
||||
clawdbot hooks enable session-memory
|
||||
clawdbot hooks disable command-logger
|
||||
openclaw hooks enable session-memory
|
||||
openclaw hooks disable command-logger
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
Hooks can be configured in `~/.clawdbot/clawdbot.json`:
|
||||
Hooks can be configured in `~/.openclaw/openclaw.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -214,11 +214,11 @@ export default myHandler;
|
||||
Test your hooks by:
|
||||
|
||||
1. Place hook in workspace hooks directory
|
||||
2. Restart gateway: `pkill -9 -f 'clawdbot.*gateway' && pnpm clawdbot gateway`
|
||||
3. Enable the hook: `clawdbot hooks enable my-hook`
|
||||
2. Restart gateway: `pkill -9 -f 'openclaw.*gateway' && pnpm openclaw gateway`
|
||||
3. Enable the hook: `openclaw hooks enable my-hook`
|
||||
4. Trigger the event (e.g., send `/new` command)
|
||||
5. Check gateway logs for hook execution
|
||||
|
||||
## Documentation
|
||||
|
||||
Full documentation: https://docs.molt.bot/hooks
|
||||
Full documentation: https://docs.openclaw.ai/hooks
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
---
|
||||
name: boot-md
|
||||
description: "Run BOOT.md on gateway startup"
|
||||
homepage: https://docs.molt.bot/hooks#boot-md
|
||||
homepage: https://docs.openclaw.ai/hooks#boot-md
|
||||
metadata:
|
||||
{
|
||||
"moltbot":
|
||||
"openclaw":
|
||||
{
|
||||
"emoji": "🚀",
|
||||
"events": ["gateway:startup"],
|
||||
"requires": { "config": ["workspace.dir"] },
|
||||
"install": [{ "id": "bundled", "kind": "bundled", "label": "Bundled with Moltbot" }],
|
||||
"install": [{ "id": "bundled", "kind": "bundled", "label": "Bundled with OpenClaw" }],
|
||||
},
|
||||
}
|
||||
---
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import type { CliDeps } from "../../../cli/deps.js";
|
||||
import { createDefaultDeps } from "../../../cli/deps.js";
|
||||
import type { MoltbotConfig } from "../../../config/config.js";
|
||||
import type { OpenClawConfig } from "../../../config/config.js";
|
||||
import { runBootOnce } from "../../../gateway/boot.js";
|
||||
import type { HookHandler } from "../../hooks.js";
|
||||
|
||||
type BootHookContext = {
|
||||
cfg?: MoltbotConfig;
|
||||
cfg?: OpenClawConfig;
|
||||
workspaceDir?: string;
|
||||
deps?: CliDeps;
|
||||
};
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
---
|
||||
name: command-logger
|
||||
description: "Log all command events to a centralized audit file"
|
||||
homepage: https://docs.molt.bot/hooks#command-logger
|
||||
homepage: https://docs.openclaw.ai/hooks#command-logger
|
||||
metadata:
|
||||
{
|
||||
"moltbot":
|
||||
"openclaw":
|
||||
{
|
||||
"emoji": "📝",
|
||||
"events": ["command"],
|
||||
"install": [{ "id": "bundled", "kind": "bundled", "label": "Bundled with Moltbot" }],
|
||||
"install": [{ "id": "bundled", "kind": "bundled", "label": "Bundled with OpenClaw" }],
|
||||
},
|
||||
}
|
||||
---
|
||||
@@ -22,7 +22,7 @@ Logs all command events (`/new`, `/reset`, `/stop`, etc.) to a centralized audit
|
||||
Every time you issue a command to the agent:
|
||||
|
||||
1. **Captures event details** - Command action, timestamp, session key, sender ID, source
|
||||
2. **Appends to log file** - Writes a JSON line to `~/.clawdbot/logs/commands.log`
|
||||
2. **Appends to log file** - Writes a JSON line to `~/.openclaw/logs/commands.log`
|
||||
3. **Silent operation** - Runs in the background without user notifications
|
||||
|
||||
## Output Format
|
||||
@@ -43,7 +43,7 @@ Log entries are written in JSONL (JSON Lines) format:
|
||||
|
||||
## Log File Location
|
||||
|
||||
`~/.clawdbot/logs/commands.log`
|
||||
`~/.openclaw/logs/commands.log`
|
||||
|
||||
## Requirements
|
||||
|
||||
@@ -62,7 +62,7 @@ No configuration needed. The hook automatically:
|
||||
To disable this hook:
|
||||
|
||||
```bash
|
||||
moltbot hooks disable command-logger
|
||||
openclaw hooks disable command-logger
|
||||
```
|
||||
|
||||
Or via config:
|
||||
@@ -86,13 +86,13 @@ The hook does not automatically rotate logs. To manage log size, you can:
|
||||
1. **Manual rotation**:
|
||||
|
||||
```bash
|
||||
mv ~/.clawdbot/logs/commands.log ~/.clawdbot/logs/commands.log.old
|
||||
mv ~/.openclaw/logs/commands.log ~/.openclaw/logs/commands.log.old
|
||||
```
|
||||
|
||||
2. **Use logrotate** (Linux):
|
||||
Create `/etc/logrotate.d/moltbot`:
|
||||
Create `/etc/logrotate.d/openclaw`:
|
||||
```
|
||||
/home/username/.clawdbot/logs/commands.log {
|
||||
/home/username/.openclaw/logs/commands.log {
|
||||
weekly
|
||||
rotate 4
|
||||
compress
|
||||
@@ -106,17 +106,17 @@ The hook does not automatically rotate logs. To manage log size, you can:
|
||||
View recent commands:
|
||||
|
||||
```bash
|
||||
tail -n 20 ~/.clawdbot/logs/commands.log
|
||||
tail -n 20 ~/.openclaw/logs/commands.log
|
||||
```
|
||||
|
||||
Pretty-print with jq:
|
||||
|
||||
```bash
|
||||
cat ~/.clawdbot/logs/commands.log | jq .
|
||||
cat ~/.openclaw/logs/commands.log | jq .
|
||||
```
|
||||
|
||||
Filter by action:
|
||||
|
||||
```bash
|
||||
grep '"action":"new"' ~/.clawdbot/logs/commands.log | jq .
|
||||
grep '"action":"new"' ~/.openclaw/logs/commands.log | jq .
|
||||
```
|
||||
|
||||
@@ -39,7 +39,8 @@ const logCommand: HookHandler = async (event) => {
|
||||
|
||||
try {
|
||||
// Create log directory
|
||||
const logDir = path.join(os.homedir(), ".clawdbot", "logs");
|
||||
const stateDir = process.env.OPENCLAW_STATE_DIR?.trim() || path.join(os.homedir(), ".openclaw");
|
||||
const logDir = path.join(stateDir, "logs");
|
||||
await fs.mkdir(logDir, { recursive: true });
|
||||
|
||||
// Append to command log file
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
---
|
||||
name: session-memory
|
||||
description: "Save session context to memory when /new command is issued"
|
||||
homepage: https://docs.molt.bot/hooks#session-memory
|
||||
homepage: https://docs.openclaw.ai/hooks#session-memory
|
||||
metadata:
|
||||
{
|
||||
"moltbot":
|
||||
"openclaw":
|
||||
{
|
||||
"emoji": "💾",
|
||||
"events": ["command:new"],
|
||||
"requires": { "config": ["workspace.dir"] },
|
||||
"install": [{ "id": "bundled", "kind": "bundled", "label": "Bundled with Moltbot" }],
|
||||
"install": [{ "id": "bundled", "kind": "bundled", "label": "Bundled with OpenClaw" }],
|
||||
},
|
||||
}
|
||||
---
|
||||
@@ -82,7 +82,7 @@ Example configuration:
|
||||
|
||||
The hook automatically:
|
||||
|
||||
- Uses your workspace directory (`~/clawd` by default)
|
||||
- Uses your workspace directory (`~/.openclaw/workspace` by default)
|
||||
- Uses your configured LLM for slug generation
|
||||
- Falls back to timestamp slugs if LLM is unavailable
|
||||
|
||||
@@ -91,7 +91,7 @@ The hook automatically:
|
||||
To disable this hook:
|
||||
|
||||
```bash
|
||||
moltbot hooks disable session-memory
|
||||
openclaw hooks disable session-memory
|
||||
```
|
||||
|
||||
Or remove it from your config:
|
||||
|
||||
@@ -5,7 +5,7 @@ import { describe, expect, it } from "vitest";
|
||||
|
||||
import handler from "./handler.js";
|
||||
import { createHookEvent } from "../../hooks.js";
|
||||
import type { ClawdbotConfig } from "../../../config/config.js";
|
||||
import type { OpenClawConfig } from "../../../config/config.js";
|
||||
import { makeTempWorkspace, writeWorkspaceFile } from "../../../test-helpers/workspace.js";
|
||||
|
||||
/**
|
||||
@@ -33,7 +33,7 @@ function createMockSessionContent(
|
||||
|
||||
describe("session-memory hook", () => {
|
||||
it("skips non-command events", async () => {
|
||||
const tempDir = await makeTempWorkspace("clawdbot-session-memory-");
|
||||
const tempDir = await makeTempWorkspace("openclaw-session-memory-");
|
||||
|
||||
const event = createHookEvent("agent", "bootstrap", "agent:main:main", {
|
||||
workspaceDir: tempDir,
|
||||
@@ -47,7 +47,7 @@ describe("session-memory hook", () => {
|
||||
});
|
||||
|
||||
it("skips commands other than new", async () => {
|
||||
const tempDir = await makeTempWorkspace("clawdbot-session-memory-");
|
||||
const tempDir = await makeTempWorkspace("openclaw-session-memory-");
|
||||
|
||||
const event = createHookEvent("command", "help", "agent:main:main", {
|
||||
workspaceDir: tempDir,
|
||||
@@ -61,7 +61,7 @@ describe("session-memory hook", () => {
|
||||
});
|
||||
|
||||
it("creates memory file with session content on /new command", async () => {
|
||||
const tempDir = await makeTempWorkspace("clawdbot-session-memory-");
|
||||
const tempDir = await makeTempWorkspace("openclaw-session-memory-");
|
||||
const sessionsDir = path.join(tempDir, "sessions");
|
||||
await fs.mkdir(sessionsDir, { recursive: true });
|
||||
|
||||
@@ -78,7 +78,7 @@ describe("session-memory hook", () => {
|
||||
content: sessionContent,
|
||||
});
|
||||
|
||||
const cfg: ClawdbotConfig = {
|
||||
const cfg: OpenClawConfig = {
|
||||
agents: { defaults: { workspace: tempDir } },
|
||||
};
|
||||
|
||||
@@ -106,7 +106,7 @@ describe("session-memory hook", () => {
|
||||
});
|
||||
|
||||
it("filters out non-message entries (tool calls, system)", async () => {
|
||||
const tempDir = await makeTempWorkspace("clawdbot-session-memory-");
|
||||
const tempDir = await makeTempWorkspace("openclaw-session-memory-");
|
||||
const sessionsDir = path.join(tempDir, "sessions");
|
||||
await fs.mkdir(sessionsDir, { recursive: true });
|
||||
|
||||
@@ -124,7 +124,7 @@ describe("session-memory hook", () => {
|
||||
content: sessionContent,
|
||||
});
|
||||
|
||||
const cfg: ClawdbotConfig = {
|
||||
const cfg: OpenClawConfig = {
|
||||
agents: { defaults: { workspace: tempDir } },
|
||||
};
|
||||
|
||||
@@ -153,7 +153,7 @@ describe("session-memory hook", () => {
|
||||
});
|
||||
|
||||
it("filters out command messages starting with /", async () => {
|
||||
const tempDir = await makeTempWorkspace("clawdbot-session-memory-");
|
||||
const tempDir = await makeTempWorkspace("openclaw-session-memory-");
|
||||
const sessionsDir = path.join(tempDir, "sessions");
|
||||
await fs.mkdir(sessionsDir, { recursive: true });
|
||||
|
||||
@@ -169,7 +169,7 @@ describe("session-memory hook", () => {
|
||||
content: sessionContent,
|
||||
});
|
||||
|
||||
const cfg: ClawdbotConfig = {
|
||||
const cfg: OpenClawConfig = {
|
||||
agents: { defaults: { workspace: tempDir } },
|
||||
};
|
||||
|
||||
@@ -196,7 +196,7 @@ describe("session-memory hook", () => {
|
||||
});
|
||||
|
||||
it("respects custom messages config (limits to N messages)", async () => {
|
||||
const tempDir = await makeTempWorkspace("clawdbot-session-memory-");
|
||||
const tempDir = await makeTempWorkspace("openclaw-session-memory-");
|
||||
const sessionsDir = path.join(tempDir, "sessions");
|
||||
await fs.mkdir(sessionsDir, { recursive: true });
|
||||
|
||||
@@ -213,7 +213,7 @@ describe("session-memory hook", () => {
|
||||
});
|
||||
|
||||
// Configure to only include last 3 messages
|
||||
const cfg: ClawdbotConfig = {
|
||||
const cfg: OpenClawConfig = {
|
||||
agents: { defaults: { workspace: tempDir } },
|
||||
hooks: {
|
||||
internal: {
|
||||
@@ -247,7 +247,7 @@ describe("session-memory hook", () => {
|
||||
});
|
||||
|
||||
it("filters messages before slicing (fix for #2681)", async () => {
|
||||
const tempDir = await makeTempWorkspace("clawdbot-session-memory-");
|
||||
const tempDir = await makeTempWorkspace("openclaw-session-memory-");
|
||||
const sessionsDir = path.join(tempDir, "sessions");
|
||||
await fs.mkdir(sessionsDir, { recursive: true });
|
||||
|
||||
@@ -274,7 +274,7 @@ describe("session-memory hook", () => {
|
||||
|
||||
// Request 3 messages - if we sliced first, we'd only get 1-2 messages
|
||||
// because the last 3 lines include tool entries
|
||||
const cfg: ClawdbotConfig = {
|
||||
const cfg: OpenClawConfig = {
|
||||
agents: { defaults: { workspace: tempDir } },
|
||||
hooks: {
|
||||
internal: {
|
||||
@@ -307,7 +307,7 @@ describe("session-memory hook", () => {
|
||||
});
|
||||
|
||||
it("handles empty session files gracefully", async () => {
|
||||
const tempDir = await makeTempWorkspace("clawdbot-session-memory-");
|
||||
const tempDir = await makeTempWorkspace("openclaw-session-memory-");
|
||||
const sessionsDir = path.join(tempDir, "sessions");
|
||||
await fs.mkdir(sessionsDir, { recursive: true });
|
||||
|
||||
@@ -317,7 +317,7 @@ describe("session-memory hook", () => {
|
||||
content: "",
|
||||
});
|
||||
|
||||
const cfg: ClawdbotConfig = {
|
||||
const cfg: OpenClawConfig = {
|
||||
agents: { defaults: { workspace: tempDir } },
|
||||
};
|
||||
|
||||
@@ -339,7 +339,7 @@ describe("session-memory hook", () => {
|
||||
});
|
||||
|
||||
it("handles session files with fewer messages than requested", async () => {
|
||||
const tempDir = await makeTempWorkspace("clawdbot-session-memory-");
|
||||
const tempDir = await makeTempWorkspace("openclaw-session-memory-");
|
||||
const sessionsDir = path.join(tempDir, "sessions");
|
||||
await fs.mkdir(sessionsDir, { recursive: true });
|
||||
|
||||
@@ -354,7 +354,7 @@ describe("session-memory hook", () => {
|
||||
content: sessionContent,
|
||||
});
|
||||
|
||||
const cfg: ClawdbotConfig = {
|
||||
const cfg: OpenClawConfig = {
|
||||
agents: { defaults: { workspace: tempDir } },
|
||||
};
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import os from "node:os";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import type { MoltbotConfig } from "../../../config/config.js";
|
||||
import type { OpenClawConfig } from "../../../config/config.js";
|
||||
import { resolveAgentWorkspaceDir } from "../../../agents/agent-scope.js";
|
||||
import { resolveAgentIdFromSessionKey } from "../../../routing/session-key.js";
|
||||
import { resolveHookConfig } from "../../config.js";
|
||||
@@ -71,11 +71,11 @@ const saveSessionToMemory: HookHandler = async (event) => {
|
||||
console.log("[session-memory] Hook triggered for /new command");
|
||||
|
||||
const context = event.context || {};
|
||||
const cfg = context.cfg as MoltbotConfig | undefined;
|
||||
const cfg = context.cfg as OpenClawConfig | undefined;
|
||||
const agentId = resolveAgentIdFromSessionKey(event.sessionKey);
|
||||
const workspaceDir = cfg
|
||||
? resolveAgentWorkspaceDir(cfg, agentId)
|
||||
: path.join(os.homedir(), "clawd");
|
||||
: path.join(os.homedir(), ".openclaw", "workspace");
|
||||
const memoryDir = path.join(workspaceDir, "memory");
|
||||
await fs.mkdir(memoryDir, { recursive: true });
|
||||
|
||||
@@ -117,8 +117,8 @@ const saveSessionToMemory: HookHandler = async (event) => {
|
||||
// Dynamically import the LLM slug generator (avoids module caching issues)
|
||||
// When compiled, handler is at dist/hooks/bundled/session-memory/handler.js
|
||||
// Going up ../.. puts us at dist/hooks/, so just add llm-slug-generator.js
|
||||
const moltbotRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
|
||||
const slugGenPath = path.join(moltbotRoot, "llm-slug-generator.js");
|
||||
const openclawRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
|
||||
const slugGenPath = path.join(openclawRoot, "llm-slug-generator.js");
|
||||
const { generateSlugViaLLM } = await import(slugGenPath);
|
||||
|
||||
// Use LLM to generate a descriptive slug
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
---
|
||||
name: soul-evil
|
||||
description: "Swap SOUL.md with SOUL_EVIL.md during a purge window or by random chance"
|
||||
homepage: https://docs.molt.bot/hooks/soul-evil
|
||||
homepage: https://docs.openclaw.ai/hooks/soul-evil
|
||||
metadata:
|
||||
{
|
||||
"moltbot":
|
||||
"openclaw":
|
||||
{
|
||||
"emoji": "😈",
|
||||
"events": ["agent:bootstrap"],
|
||||
"requires": { "config": ["hooks.internal.entries.soul-evil.enabled"] },
|
||||
"install": [{ "id": "bundled", "kind": "bundled", "label": "Bundled with Moltbot" }],
|
||||
"install": [{ "id": "bundled", "kind": "bundled", "label": "Bundled with OpenClaw" }],
|
||||
},
|
||||
}
|
||||
---
|
||||
@@ -31,7 +31,7 @@ You can change the filename via hook config.
|
||||
|
||||
## Configuration
|
||||
|
||||
Add this to your config (`~/.clawdbot/moltbot.json`):
|
||||
Add this to your config (`~/.openclaw/openclaw.json`):
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -67,5 +67,5 @@ Add this to your config (`~/.clawdbot/moltbot.json`):
|
||||
## Enable
|
||||
|
||||
```bash
|
||||
moltbot hooks enable soul-evil
|
||||
openclaw hooks enable soul-evil
|
||||
```
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
# SOUL Evil Hook
|
||||
|
||||
Small persona swap hook for Clawdbot.
|
||||
Small persona swap hook for OpenClaw.
|
||||
|
||||
Docs: https://docs.molt.bot/hooks/soul-evil
|
||||
Docs: https://docs.openclaw.ai/hooks/soul-evil
|
||||
|
||||
## Setup
|
||||
|
||||
1. `clawdbot hooks enable soul-evil`
|
||||
1. `openclaw hooks enable soul-evil`
|
||||
2. Create `SOUL_EVIL.md` next to `SOUL.md` in your agent workspace
|
||||
3. Configure `hooks.internal.entries.soul-evil` (see docs)
|
||||
|
||||
@@ -5,19 +5,19 @@ import { describe, expect, it } from "vitest";
|
||||
import handler from "./handler.js";
|
||||
import { createHookEvent } from "../../hooks.js";
|
||||
import type { AgentBootstrapHookContext } from "../../hooks.js";
|
||||
import type { MoltbotConfig } from "../../../config/config.js";
|
||||
import type { OpenClawConfig } from "../../../config/config.js";
|
||||
import { makeTempWorkspace, writeWorkspaceFile } from "../../../test-helpers/workspace.js";
|
||||
|
||||
describe("soul-evil hook", () => {
|
||||
it("skips subagent sessions", async () => {
|
||||
const tempDir = await makeTempWorkspace("moltbot-soul-");
|
||||
const tempDir = await makeTempWorkspace("openclaw-soul-");
|
||||
await writeWorkspaceFile({
|
||||
dir: tempDir,
|
||||
name: "SOUL_EVIL.md",
|
||||
content: "chaotic",
|
||||
});
|
||||
|
||||
const cfg: MoltbotConfig = {
|
||||
const cfg: OpenClawConfig = {
|
||||
hooks: {
|
||||
internal: {
|
||||
entries: {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { MoltbotConfig } from "../../../config/config.js";
|
||||
import type { OpenClawConfig } from "../../../config/config.js";
|
||||
import { isSubagentSessionKey } from "../../../routing/session-key.js";
|
||||
import { resolveHookConfig } from "../../config.js";
|
||||
import { isAgentBootstrapEvent, type HookHandler } from "../../hooks.js";
|
||||
@@ -11,7 +11,7 @@ const soulEvilHook: HookHandler = async (event) => {
|
||||
|
||||
const context = event.context;
|
||||
if (context.sessionKey && isSubagentSessionKey(context.sessionKey)) return;
|
||||
const cfg = context.cfg as MoltbotConfig | undefined;
|
||||
const cfg = context.cfg as OpenClawConfig | undefined;
|
||||
const hookConfig = resolveHookConfig(cfg, HOOK_KEY);
|
||||
if (!hookConfig || hookConfig.enabled === false) return;
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import type { MoltbotConfig, HookConfig } from "../config/config.js";
|
||||
import type { OpenClawConfig, HookConfig } from "../config/config.js";
|
||||
import { resolveHookKey } from "./frontmatter.js";
|
||||
import type { HookEligibilityContext, HookEntry } from "./types.js";
|
||||
|
||||
@@ -18,7 +18,7 @@ function isTruthy(value: unknown): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
export function resolveConfigPath(config: MoltbotConfig | undefined, pathStr: string) {
|
||||
export function resolveConfigPath(config: OpenClawConfig | undefined, pathStr: string) {
|
||||
const parts = pathStr.split(".").filter(Boolean);
|
||||
let current: unknown = config;
|
||||
for (const part of parts) {
|
||||
@@ -28,7 +28,7 @@ export function resolveConfigPath(config: MoltbotConfig | undefined, pathStr: st
|
||||
return current;
|
||||
}
|
||||
|
||||
export function isConfigPathTruthy(config: MoltbotConfig | undefined, pathStr: string): boolean {
|
||||
export function isConfigPathTruthy(config: OpenClawConfig | undefined, pathStr: string): boolean {
|
||||
const value = resolveConfigPath(config, pathStr);
|
||||
if (value === undefined && pathStr in DEFAULT_CONFIG_VALUES) {
|
||||
return DEFAULT_CONFIG_VALUES[pathStr] === true;
|
||||
@@ -37,7 +37,7 @@ export function isConfigPathTruthy(config: MoltbotConfig | undefined, pathStr: s
|
||||
}
|
||||
|
||||
export function resolveHookConfig(
|
||||
config: MoltbotConfig | undefined,
|
||||
config: OpenClawConfig | undefined,
|
||||
hookKey: string,
|
||||
): HookConfig | undefined {
|
||||
const hooks = config?.hooks?.internal?.entries;
|
||||
@@ -68,13 +68,13 @@ export function hasBinary(bin: string): boolean {
|
||||
|
||||
export function shouldIncludeHook(params: {
|
||||
entry: HookEntry;
|
||||
config?: MoltbotConfig;
|
||||
config?: OpenClawConfig;
|
||||
eligibility?: HookEligibilityContext;
|
||||
}): boolean {
|
||||
const { entry, config, eligibility } = params;
|
||||
const hookKey = resolveHookKey(entry.hook.name, entry);
|
||||
const hookConfig = resolveHookConfig(config, hookKey);
|
||||
const pluginManaged = entry.hook.source === "moltbot-plugin";
|
||||
const pluginManaged = entry.hook.source === "openclaw-plugin";
|
||||
const osList = entry.metadata?.os ?? [];
|
||||
const remotePlatforms = eligibility?.remote?.platforms ?? [];
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
parseFrontmatter,
|
||||
resolveMoltbotMetadata,
|
||||
resolveOpenClawMetadata,
|
||||
resolveHookInvocationPolicy,
|
||||
} from "./frontmatter.js";
|
||||
|
||||
@@ -41,7 +41,7 @@ name: session-memory
|
||||
description: "Save session context"
|
||||
metadata:
|
||||
{
|
||||
"moltbot": {
|
||||
"openclaw": {
|
||||
"emoji": "💾",
|
||||
"events": ["command:new"]
|
||||
}
|
||||
@@ -58,8 +58,8 @@ metadata:
|
||||
|
||||
// Verify the metadata is valid JSON
|
||||
const parsed = JSON.parse(result.metadata as string);
|
||||
expect(parsed.moltbot.emoji).toBe("💾");
|
||||
expect(parsed.moltbot.events).toEqual(["command:new"]);
|
||||
expect(parsed.openclaw.emoji).toBe("💾");
|
||||
expect(parsed.openclaw.events).toEqual(["command:new"]);
|
||||
});
|
||||
|
||||
it("parses multi-line metadata with complex nested structure", () => {
|
||||
@@ -68,7 +68,7 @@ name: command-logger
|
||||
description: "Log all command events"
|
||||
metadata:
|
||||
{
|
||||
"moltbot":
|
||||
"openclaw":
|
||||
{
|
||||
"emoji": "📝",
|
||||
"events": ["command"],
|
||||
@@ -83,21 +83,21 @@ metadata:
|
||||
expect(result.metadata).toBeDefined();
|
||||
|
||||
const parsed = JSON.parse(result.metadata as string);
|
||||
expect(parsed.moltbot.emoji).toBe("📝");
|
||||
expect(parsed.moltbot.events).toEqual(["command"]);
|
||||
expect(parsed.moltbot.requires.config).toEqual(["workspace.dir"]);
|
||||
expect(parsed.moltbot.install[0].kind).toBe("bundled");
|
||||
expect(parsed.openclaw.emoji).toBe("📝");
|
||||
expect(parsed.openclaw.events).toEqual(["command"]);
|
||||
expect(parsed.openclaw.requires.config).toEqual(["workspace.dir"]);
|
||||
expect(parsed.openclaw.install[0].kind).toBe("bundled");
|
||||
});
|
||||
|
||||
it("handles single-line metadata (inline JSON)", () => {
|
||||
const content = `---
|
||||
name: simple-hook
|
||||
metadata: {"moltbot": {"events": ["test"]}}
|
||||
metadata: {"openclaw": {"events": ["test"]}}
|
||||
---
|
||||
`;
|
||||
const result = parseFrontmatter(content);
|
||||
expect(result.name).toBe("simple-hook");
|
||||
expect(result.metadata).toBe('{"moltbot": {"events": ["test"]}}');
|
||||
expect(result.metadata).toBe('{"openclaw": {"events": ["test"]}}');
|
||||
});
|
||||
|
||||
it("handles mixed single-line and multi-line values", () => {
|
||||
@@ -107,7 +107,7 @@ description: "A hook with mixed values"
|
||||
homepage: https://example.com
|
||||
metadata:
|
||||
{
|
||||
"moltbot": {
|
||||
"openclaw": {
|
||||
"events": ["command:new"]
|
||||
}
|
||||
}
|
||||
@@ -148,12 +148,12 @@ description: 'single-quoted'
|
||||
});
|
||||
});
|
||||
|
||||
describe("resolveMoltbotMetadata", () => {
|
||||
it("extracts moltbot metadata from parsed frontmatter", () => {
|
||||
describe("resolveOpenClawMetadata", () => {
|
||||
it("extracts openclaw metadata from parsed frontmatter", () => {
|
||||
const frontmatter = {
|
||||
name: "test-hook",
|
||||
metadata: JSON.stringify({
|
||||
moltbot: {
|
||||
openclaw: {
|
||||
emoji: "🔥",
|
||||
events: ["command:new", "command:reset"],
|
||||
requires: {
|
||||
@@ -164,7 +164,7 @@ describe("resolveMoltbotMetadata", () => {
|
||||
}),
|
||||
};
|
||||
|
||||
const result = resolveMoltbotMetadata(frontmatter);
|
||||
const result = resolveOpenClawMetadata(frontmatter);
|
||||
expect(result).toBeDefined();
|
||||
expect(result?.emoji).toBe("🔥");
|
||||
expect(result?.events).toEqual(["command:new", "command:reset"]);
|
||||
@@ -174,15 +174,15 @@ describe("resolveMoltbotMetadata", () => {
|
||||
|
||||
it("returns undefined when metadata is missing", () => {
|
||||
const frontmatter = { name: "no-metadata" };
|
||||
const result = resolveMoltbotMetadata(frontmatter);
|
||||
const result = resolveOpenClawMetadata(frontmatter);
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
|
||||
it("returns undefined when moltbot key is missing", () => {
|
||||
it("returns undefined when openclaw key is missing", () => {
|
||||
const frontmatter = {
|
||||
metadata: JSON.stringify({ other: "data" }),
|
||||
};
|
||||
const result = resolveMoltbotMetadata(frontmatter);
|
||||
const result = resolveOpenClawMetadata(frontmatter);
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
|
||||
@@ -190,41 +190,41 @@ describe("resolveMoltbotMetadata", () => {
|
||||
const frontmatter = {
|
||||
metadata: "not valid json {",
|
||||
};
|
||||
const result = resolveMoltbotMetadata(frontmatter);
|
||||
const result = resolveOpenClawMetadata(frontmatter);
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
|
||||
it("handles install specs", () => {
|
||||
const frontmatter = {
|
||||
metadata: JSON.stringify({
|
||||
moltbot: {
|
||||
openclaw: {
|
||||
events: ["command"],
|
||||
install: [
|
||||
{ id: "bundled", kind: "bundled", label: "Bundled with Moltbot" },
|
||||
{ id: "npm", kind: "npm", package: "@moltbot/hook" },
|
||||
{ id: "bundled", kind: "bundled", label: "Bundled with OpenClaw" },
|
||||
{ id: "npm", kind: "npm", package: "@openclaw/hook" },
|
||||
],
|
||||
},
|
||||
}),
|
||||
};
|
||||
|
||||
const result = resolveMoltbotMetadata(frontmatter);
|
||||
const result = resolveOpenClawMetadata(frontmatter);
|
||||
expect(result?.install).toHaveLength(2);
|
||||
expect(result?.install?.[0].kind).toBe("bundled");
|
||||
expect(result?.install?.[1].kind).toBe("npm");
|
||||
expect(result?.install?.[1].package).toBe("@moltbot/hook");
|
||||
expect(result?.install?.[1].package).toBe("@openclaw/hook");
|
||||
});
|
||||
|
||||
it("handles os restrictions", () => {
|
||||
const frontmatter = {
|
||||
metadata: JSON.stringify({
|
||||
moltbot: {
|
||||
openclaw: {
|
||||
events: ["command"],
|
||||
os: ["darwin", "linux"],
|
||||
},
|
||||
}),
|
||||
};
|
||||
|
||||
const result = resolveMoltbotMetadata(frontmatter);
|
||||
const result = resolveOpenClawMetadata(frontmatter);
|
||||
expect(result?.os).toEqual(["darwin", "linux"]);
|
||||
});
|
||||
|
||||
@@ -233,15 +233,15 @@ describe("resolveMoltbotMetadata", () => {
|
||||
const content = `---
|
||||
name: session-memory
|
||||
description: "Save session context to memory when /new command is issued"
|
||||
homepage: https://docs.molt.bot/hooks#session-memory
|
||||
homepage: https://docs.openclaw.ai/hooks#session-memory
|
||||
metadata:
|
||||
{
|
||||
"moltbot":
|
||||
"openclaw":
|
||||
{
|
||||
"emoji": "💾",
|
||||
"events": ["command:new"],
|
||||
"requires": { "config": ["workspace.dir"] },
|
||||
"install": [{ "id": "bundled", "kind": "bundled", "label": "Bundled with Moltbot" }],
|
||||
"install": [{ "id": "bundled", "kind": "bundled", "label": "Bundled with OpenClaw" }],
|
||||
},
|
||||
}
|
||||
---
|
||||
@@ -253,28 +253,28 @@ metadata:
|
||||
expect(frontmatter.name).toBe("session-memory");
|
||||
expect(frontmatter.metadata).toBeDefined();
|
||||
|
||||
const moltbot = resolveMoltbotMetadata(frontmatter);
|
||||
expect(moltbot).toBeDefined();
|
||||
expect(moltbot?.emoji).toBe("💾");
|
||||
expect(moltbot?.events).toEqual(["command:new"]);
|
||||
expect(moltbot?.requires?.config).toEqual(["workspace.dir"]);
|
||||
expect(moltbot?.install?.[0].kind).toBe("bundled");
|
||||
const openclaw = resolveOpenClawMetadata(frontmatter);
|
||||
expect(openclaw).toBeDefined();
|
||||
expect(openclaw?.emoji).toBe("💾");
|
||||
expect(openclaw?.events).toEqual(["command:new"]);
|
||||
expect(openclaw?.requires?.config).toEqual(["workspace.dir"]);
|
||||
expect(openclaw?.install?.[0].kind).toBe("bundled");
|
||||
});
|
||||
|
||||
it("parses YAML metadata map", () => {
|
||||
const content = `---
|
||||
name: yaml-metadata
|
||||
metadata:
|
||||
moltbot:
|
||||
openclaw:
|
||||
emoji: disk
|
||||
events:
|
||||
- command:new
|
||||
---
|
||||
`;
|
||||
const frontmatter = parseFrontmatter(content);
|
||||
const moltbot = resolveMoltbotMetadata(frontmatter);
|
||||
expect(moltbot?.emoji).toBe("disk");
|
||||
expect(moltbot?.events).toEqual(["command:new"]);
|
||||
const openclaw = resolveOpenClawMetadata(frontmatter);
|
||||
expect(openclaw?.emoji).toBe("disk");
|
||||
expect(openclaw?.events).toEqual(["command:new"]);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import JSON5 from "json5";
|
||||
|
||||
import { LEGACY_MANIFEST_KEY } from "../compat/legacy-names.js";
|
||||
import { LEGACY_MANIFEST_KEYS, MANIFEST_KEY } from "../compat/legacy-names.js";
|
||||
import { parseFrontmatterBlock } from "../markdown/frontmatter.js";
|
||||
import { parseBooleanValue } from "../utils/boolean.js";
|
||||
import type {
|
||||
MoltbotHookMetadata,
|
||||
OpenClawHookMetadata,
|
||||
HookEntry,
|
||||
HookInstallSpec,
|
||||
HookInvocationPolicy,
|
||||
@@ -63,17 +63,23 @@ function parseFrontmatterBool(value: string | undefined, fallback: boolean): boo
|
||||
return parsed === undefined ? fallback : parsed;
|
||||
}
|
||||
|
||||
export function resolveMoltbotMetadata(
|
||||
export function resolveOpenClawMetadata(
|
||||
frontmatter: ParsedHookFrontmatter,
|
||||
): MoltbotHookMetadata | undefined {
|
||||
): OpenClawHookMetadata | undefined {
|
||||
const raw = getFrontmatterValue(frontmatter, "metadata");
|
||||
if (!raw) return undefined;
|
||||
try {
|
||||
const parsed = JSON5.parse(raw) as { moltbot?: unknown } & Partial<
|
||||
Record<typeof LEGACY_MANIFEST_KEY, unknown>
|
||||
>;
|
||||
const parsed = JSON5.parse(raw) as Record<string, unknown>;
|
||||
if (!parsed || typeof parsed !== "object") return undefined;
|
||||
const metadataRaw = parsed.moltbot ?? parsed[LEGACY_MANIFEST_KEY];
|
||||
const metadataRawCandidates = [MANIFEST_KEY, ...LEGACY_MANIFEST_KEYS];
|
||||
let metadataRaw: unknown;
|
||||
for (const key of metadataRawCandidates) {
|
||||
const candidate = parsed[key];
|
||||
if (candidate && typeof candidate === "object") {
|
||||
metadataRaw = candidate;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!metadataRaw || typeof metadataRaw !== "object") return undefined;
|
||||
const metadataObj = metadataRaw as Record<string, unknown>;
|
||||
const requiresRaw =
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { spawn } from "node:child_process";
|
||||
|
||||
import {
|
||||
type MoltbotConfig,
|
||||
type OpenClawConfig,
|
||||
CONFIG_PATH,
|
||||
loadConfig,
|
||||
readConfigFileSnapshot,
|
||||
@@ -210,7 +210,7 @@ export async function runGmailSetup(opts: GmailSetupOptions) {
|
||||
true,
|
||||
);
|
||||
|
||||
const nextConfig: MoltbotConfig = {
|
||||
const nextConfig: OpenClawConfig = {
|
||||
...baseConfig,
|
||||
hooks: {
|
||||
...baseConfig.hooks,
|
||||
@@ -278,7 +278,7 @@ export async function runGmailSetup(opts: GmailSetupOptions) {
|
||||
defaultRuntime.log(`- push endpoint: ${pushEndpoint}`);
|
||||
defaultRuntime.log(`- hook url: ${hookUrl}`);
|
||||
defaultRuntime.log(`- config: ${displayPath(CONFIG_PATH)}`);
|
||||
defaultRuntime.log(`Next: ${formatCliCommand("moltbot webhooks gmail run")}`);
|
||||
defaultRuntime.log(`Next: ${formatCliCommand("openclaw webhooks gmail run")}`);
|
||||
}
|
||||
|
||||
export async function runGmailService(opts: GmailRunOptions) {
|
||||
|
||||
@@ -14,7 +14,7 @@ describe("resolvePythonExecutablePath", () => {
|
||||
itUnix(
|
||||
"resolves a working python path and caches the result",
|
||||
async () => {
|
||||
const tmp = await fs.mkdtemp(path.join(os.tmpdir(), "moltbot-python-"));
|
||||
const tmp = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-python-"));
|
||||
const originalPath = process.env.PATH;
|
||||
try {
|
||||
const realPython = path.join(tmp, "python-real");
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
import { type ChildProcess, spawn } from "node:child_process";
|
||||
import { hasBinary } from "../agents/skills.js";
|
||||
import type { MoltbotConfig } from "../config/config.js";
|
||||
import type { OpenClawConfig } from "../config/config.js";
|
||||
import { createSubsystemLogger } from "../logging/subsystem.js";
|
||||
import { runCommandWithTimeout } from "../process/exec.js";
|
||||
import {
|
||||
@@ -96,7 +96,7 @@ function spawnGogServe(cfg: GmailHookRuntimeConfig): ChildProcess {
|
||||
if (addressInUse) {
|
||||
log.warn(
|
||||
"gog serve failed to bind (address already in use); stopping restarts. " +
|
||||
"Another watcher is likely running. Set CLAWDBOT_SKIP_GMAIL_WATCHER=1 or stop the other process.",
|
||||
"Another watcher is likely running. Set OPENCLAW_SKIP_GMAIL_WATCHER=1 or stop the other process.",
|
||||
);
|
||||
watcherProcess = null;
|
||||
return;
|
||||
@@ -121,7 +121,7 @@ export type GmailWatcherStartResult = {
|
||||
* Start the Gmail watcher service.
|
||||
* Called automatically by the gateway if hooks.gmail is configured.
|
||||
*/
|
||||
export async function startGmailWatcher(cfg: MoltbotConfig): Promise<GmailWatcherStartResult> {
|
||||
export async function startGmailWatcher(cfg: OpenClawConfig): Promise<GmailWatcherStartResult> {
|
||||
// Check if gmail hooks are configured
|
||||
if (!cfg.hooks?.enabled) {
|
||||
return { started: false, reason: "hooks not enabled" };
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { type MoltbotConfig, DEFAULT_GATEWAY_PORT } from "../config/config.js";
|
||||
import { type OpenClawConfig, DEFAULT_GATEWAY_PORT } from "../config/config.js";
|
||||
import {
|
||||
buildDefaultHookUrl,
|
||||
buildTopicPath,
|
||||
@@ -11,12 +11,12 @@ const baseConfig = {
|
||||
hooks: {
|
||||
token: "hook-token",
|
||||
gmail: {
|
||||
account: "moltbot@gmail.com",
|
||||
account: "openclaw@gmail.com",
|
||||
topic: "projects/demo/topics/gog-gmail-watch",
|
||||
pushToken: "push-token",
|
||||
},
|
||||
},
|
||||
} satisfies MoltbotConfig;
|
||||
} satisfies OpenClawConfig;
|
||||
|
||||
describe("gmail hook config", () => {
|
||||
it("builds default hook url", () => {
|
||||
@@ -37,7 +37,7 @@ describe("gmail hook config", () => {
|
||||
const result = resolveGmailHookRuntimeConfig(baseConfig, {});
|
||||
expect(result.ok).toBe(true);
|
||||
if (result.ok) {
|
||||
expect(result.value.account).toBe("moltbot@gmail.com");
|
||||
expect(result.value.account).toBe("openclaw@gmail.com");
|
||||
expect(result.value.label).toBe("INBOX");
|
||||
expect(result.value.includeBody).toBe(true);
|
||||
expect(result.value.serve.port).toBe(8788);
|
||||
@@ -50,7 +50,7 @@ describe("gmail hook config", () => {
|
||||
{
|
||||
hooks: {
|
||||
gmail: {
|
||||
account: "moltbot@gmail.com",
|
||||
account: "openclaw@gmail.com",
|
||||
topic: "projects/demo/topics/gog-gmail-watch",
|
||||
pushToken: "push-token",
|
||||
},
|
||||
@@ -67,7 +67,7 @@ describe("gmail hook config", () => {
|
||||
hooks: {
|
||||
token: "hook-token",
|
||||
gmail: {
|
||||
account: "moltbot@gmail.com",
|
||||
account: "openclaw@gmail.com",
|
||||
topic: "projects/demo/topics/gog-gmail-watch",
|
||||
pushToken: "push-token",
|
||||
tailscale: { mode: "funnel" },
|
||||
@@ -89,7 +89,7 @@ describe("gmail hook config", () => {
|
||||
hooks: {
|
||||
token: "hook-token",
|
||||
gmail: {
|
||||
account: "moltbot@gmail.com",
|
||||
account: "openclaw@gmail.com",
|
||||
topic: "projects/demo/topics/gog-gmail-watch",
|
||||
pushToken: "push-token",
|
||||
serve: { path: "/gmail-pubsub" },
|
||||
@@ -112,7 +112,7 @@ describe("gmail hook config", () => {
|
||||
hooks: {
|
||||
token: "hook-token",
|
||||
gmail: {
|
||||
account: "moltbot@gmail.com",
|
||||
account: "openclaw@gmail.com",
|
||||
topic: "projects/demo/topics/gog-gmail-watch",
|
||||
pushToken: "push-token",
|
||||
serve: { path: "/custom" },
|
||||
@@ -135,7 +135,7 @@ describe("gmail hook config", () => {
|
||||
hooks: {
|
||||
token: "hook-token",
|
||||
gmail: {
|
||||
account: "moltbot@gmail.com",
|
||||
account: "openclaw@gmail.com",
|
||||
topic: "projects/demo/topics/gog-gmail-watch",
|
||||
pushToken: "push-token",
|
||||
serve: { path: "/custom" },
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { randomBytes } from "node:crypto";
|
||||
|
||||
import {
|
||||
type MoltbotConfig,
|
||||
type OpenClawConfig,
|
||||
DEFAULT_GATEWAY_PORT,
|
||||
type HooksGmailTailscaleMode,
|
||||
resolveGatewayPort,
|
||||
@@ -95,7 +95,7 @@ export function buildDefaultHookUrl(
|
||||
}
|
||||
|
||||
export function resolveGmailHookRuntimeConfig(
|
||||
cfg: MoltbotConfig,
|
||||
cfg: OpenClawConfig,
|
||||
overrides: GmailHookOverrides,
|
||||
): { ok: true; value: GmailHookRuntimeConfig } | { ok: false; error: string } {
|
||||
const hooks = cfg.hooks;
|
||||
|
||||
@@ -6,7 +6,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
const tempDirs: string[] = [];
|
||||
|
||||
async function makeTempDir() {
|
||||
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "moltbot-hooks-e2e-"));
|
||||
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-hooks-e2e-"));
|
||||
tempDirs.push(dir);
|
||||
return dir;
|
||||
}
|
||||
@@ -21,24 +21,24 @@ describe("hooks install (e2e)", () => {
|
||||
workspaceDir = path.join(baseDir, "workspace");
|
||||
await fs.mkdir(workspaceDir, { recursive: true });
|
||||
|
||||
prevStateDir = process.env.CLAWDBOT_STATE_DIR;
|
||||
prevBundledDir = process.env.CLAWDBOT_BUNDLED_HOOKS_DIR;
|
||||
process.env.CLAWDBOT_STATE_DIR = path.join(baseDir, "state");
|
||||
process.env.CLAWDBOT_BUNDLED_HOOKS_DIR = path.join(baseDir, "bundled-none");
|
||||
prevStateDir = process.env.OPENCLAW_STATE_DIR;
|
||||
prevBundledDir = process.env.OPENCLAW_BUNDLED_HOOKS_DIR;
|
||||
process.env.OPENCLAW_STATE_DIR = path.join(baseDir, "state");
|
||||
process.env.OPENCLAW_BUNDLED_HOOKS_DIR = path.join(baseDir, "bundled-none");
|
||||
vi.resetModules();
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
if (prevStateDir === undefined) {
|
||||
delete process.env.CLAWDBOT_STATE_DIR;
|
||||
delete process.env.OPENCLAW_STATE_DIR;
|
||||
} else {
|
||||
process.env.CLAWDBOT_STATE_DIR = prevStateDir;
|
||||
process.env.OPENCLAW_STATE_DIR = prevStateDir;
|
||||
}
|
||||
|
||||
if (prevBundledDir === undefined) {
|
||||
delete process.env.CLAWDBOT_BUNDLED_HOOKS_DIR;
|
||||
delete process.env.OPENCLAW_BUNDLED_HOOKS_DIR;
|
||||
} else {
|
||||
process.env.CLAWDBOT_BUNDLED_HOOKS_DIR = prevBundledDir;
|
||||
process.env.OPENCLAW_BUNDLED_HOOKS_DIR = prevBundledDir;
|
||||
}
|
||||
|
||||
vi.resetModules();
|
||||
@@ -63,7 +63,7 @@ describe("hooks install (e2e)", () => {
|
||||
{
|
||||
name: "@acme/hello-hooks",
|
||||
version: "0.0.0",
|
||||
moltbot: { hooks: ["./hooks/hello-hook"] },
|
||||
openclaw: { hooks: ["./hooks/hello-hook"] },
|
||||
},
|
||||
null,
|
||||
2,
|
||||
@@ -77,7 +77,7 @@ describe("hooks install (e2e)", () => {
|
||||
"---",
|
||||
'name: "hello-hook"',
|
||||
'description: "Test hook"',
|
||||
'metadata: {"moltbot":{"events":["command:new"]}}',
|
||||
'metadata: {"openclaw":{"events":["command:new"]}}',
|
||||
"---",
|
||||
"",
|
||||
"# Hello Hook",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import path from "node:path";
|
||||
|
||||
import type { MoltbotConfig } from "../config/config.js";
|
||||
import type { OpenClawConfig } from "../config/config.js";
|
||||
import { CONFIG_DIR } from "../utils.js";
|
||||
import { hasBinary, isConfigPathTruthy, resolveConfigPath, resolveHookConfig } from "./config.js";
|
||||
import type { HookEligibilityContext, HookEntry, HookInstallSpec } from "./types.js";
|
||||
@@ -75,7 +75,7 @@ function normalizeInstallOptions(entry: HookEntry): HookInstallOption[] {
|
||||
|
||||
if (!label) {
|
||||
if (spec.kind === "bundled") {
|
||||
label = "Bundled with Moltbot";
|
||||
label = "Bundled with OpenClaw";
|
||||
} else if (spec.kind === "npm" && spec.package) {
|
||||
label = `Install ${spec.package} (npm)`;
|
||||
} else if (spec.kind === "git" && spec.repository) {
|
||||
@@ -91,12 +91,12 @@ function normalizeInstallOptions(entry: HookEntry): HookInstallOption[] {
|
||||
|
||||
function buildHookStatus(
|
||||
entry: HookEntry,
|
||||
config?: MoltbotConfig,
|
||||
config?: OpenClawConfig,
|
||||
eligibility?: HookEligibilityContext,
|
||||
): HookStatusEntry {
|
||||
const hookKey = resolveHookKey(entry);
|
||||
const hookConfig = resolveHookConfig(config, hookKey);
|
||||
const managedByPlugin = entry.hook.source === "moltbot-plugin";
|
||||
const managedByPlugin = entry.hook.source === "openclaw-plugin";
|
||||
const disabled = managedByPlugin ? false : hookConfig?.enabled === false;
|
||||
const always = entry.metadata?.always === true;
|
||||
const emoji = entry.metadata?.emoji ?? entry.frontmatter.emoji;
|
||||
@@ -202,7 +202,7 @@ function buildHookStatus(
|
||||
export function buildWorkspaceHookStatus(
|
||||
workspaceDir: string,
|
||||
opts?: {
|
||||
config?: MoltbotConfig;
|
||||
config?: OpenClawConfig;
|
||||
managedHooksDir?: string;
|
||||
entries?: HookEntry[];
|
||||
eligibility?: HookEligibilityContext;
|
||||
|
||||
@@ -9,7 +9,7 @@ import { afterEach, describe, expect, it } from "vitest";
|
||||
const tempDirs: string[] = [];
|
||||
|
||||
function makeTempDir() {
|
||||
const dir = path.join(os.tmpdir(), `moltbot-hook-install-${randomUUID()}`);
|
||||
const dir = path.join(os.tmpdir(), `openclaw-hook-install-${randomUUID()}`);
|
||||
fs.mkdirSync(dir, { recursive: true });
|
||||
tempDirs.push(dir);
|
||||
return dir;
|
||||
@@ -35,9 +35,9 @@ describe("installHooksFromArchive", () => {
|
||||
zip.file(
|
||||
"package/package.json",
|
||||
JSON.stringify({
|
||||
name: "@moltbot/zip-hooks",
|
||||
name: "@openclaw/zip-hooks",
|
||||
version: "0.0.1",
|
||||
moltbot: { hooks: ["./hooks/zip-hook"] },
|
||||
openclaw: { hooks: ["./hooks/zip-hook"] },
|
||||
}),
|
||||
);
|
||||
zip.file(
|
||||
@@ -46,7 +46,7 @@ describe("installHooksFromArchive", () => {
|
||||
"---",
|
||||
"name: zip-hook",
|
||||
"description: Zip hook",
|
||||
'metadata: {"moltbot":{"events":["command:new"]}}',
|
||||
'metadata: {"openclaw":{"events":["command:new"]}}',
|
||||
"---",
|
||||
"",
|
||||
"# Zip Hook",
|
||||
@@ -78,9 +78,9 @@ describe("installHooksFromArchive", () => {
|
||||
fs.writeFileSync(
|
||||
path.join(pkgDir, "package.json"),
|
||||
JSON.stringify({
|
||||
name: "@moltbot/tar-hooks",
|
||||
name: "@openclaw/tar-hooks",
|
||||
version: "0.0.1",
|
||||
moltbot: { hooks: ["./hooks/tar-hook"] },
|
||||
openclaw: { hooks: ["./hooks/tar-hook"] },
|
||||
}),
|
||||
"utf-8",
|
||||
);
|
||||
@@ -90,7 +90,7 @@ describe("installHooksFromArchive", () => {
|
||||
"---",
|
||||
"name: tar-hook",
|
||||
"description: Tar hook",
|
||||
'metadata: {"moltbot":{"events":["command:new"]}}',
|
||||
'metadata: {"openclaw":{"events":["command:new"]}}',
|
||||
"---",
|
||||
"",
|
||||
"# Tar Hook",
|
||||
@@ -128,7 +128,7 @@ describe("installHooksFromPath", () => {
|
||||
"---",
|
||||
"name: my-hook",
|
||||
"description: My hook",
|
||||
'metadata: {"moltbot":{"events":["command:new"]}}',
|
||||
'metadata: {"openclaw":{"events":["command:new"]}}',
|
||||
"---",
|
||||
"",
|
||||
"# My Hook",
|
||||
|
||||
@@ -2,7 +2,7 @@ import fs from "node:fs/promises";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
|
||||
import { LEGACY_MANIFEST_KEY } from "../compat/legacy-names.js";
|
||||
import { MANIFEST_KEY } from "../compat/legacy-names.js";
|
||||
import { runCommandWithTimeout } from "../process/exec.js";
|
||||
import { CONFIG_DIR, resolveUserPath } from "../utils.js";
|
||||
import {
|
||||
@@ -23,9 +23,7 @@ type HookPackageManifest = {
|
||||
name?: string;
|
||||
version?: string;
|
||||
dependencies?: Record<string, string>;
|
||||
moltbot?: { hooks?: string[] };
|
||||
[LEGACY_MANIFEST_KEY]?: { hooks?: string[] };
|
||||
};
|
||||
} & Partial<Record<typeof MANIFEST_KEY, { hooks?: string[] }>>;
|
||||
|
||||
export type InstallHooksResult =
|
||||
| {
|
||||
@@ -56,14 +54,14 @@ export function resolveHookInstallDir(hookId: string, hooksDir?: string): string
|
||||
return path.join(hooksBase, safeDirName(hookId));
|
||||
}
|
||||
|
||||
async function ensureMoltbotHooks(manifest: HookPackageManifest) {
|
||||
const hooks = manifest.moltbot?.hooks ?? manifest[LEGACY_MANIFEST_KEY]?.hooks;
|
||||
async function ensureOpenClawHooks(manifest: HookPackageManifest) {
|
||||
const hooks = manifest[MANIFEST_KEY]?.hooks;
|
||||
if (!Array.isArray(hooks)) {
|
||||
throw new Error("package.json missing moltbot.hooks");
|
||||
throw new Error("package.json missing openclaw.hooks");
|
||||
}
|
||||
const list = hooks.map((e) => (typeof e === "string" ? e.trim() : "")).filter(Boolean);
|
||||
if (list.length === 0) {
|
||||
throw new Error("package.json moltbot.hooks is empty");
|
||||
throw new Error("package.json openclaw.hooks is empty");
|
||||
}
|
||||
return list;
|
||||
}
|
||||
@@ -122,7 +120,7 @@ async function installHookPackageFromDir(params: {
|
||||
|
||||
let hookEntries: string[];
|
||||
try {
|
||||
hookEntries = await ensureMoltbotHooks(manifest);
|
||||
hookEntries = await ensureOpenClawHooks(manifest);
|
||||
} catch (err) {
|
||||
return { ok: false, error: String(err) };
|
||||
}
|
||||
@@ -295,7 +293,7 @@ export async function installHooksFromArchive(params: {
|
||||
return { ok: false, error: `unsupported archive: ${archivePath}` };
|
||||
}
|
||||
|
||||
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "moltbot-hook-"));
|
||||
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-hook-"));
|
||||
const extractDir = path.join(tmpDir, "extract");
|
||||
await fs.mkdir(extractDir, { recursive: true });
|
||||
|
||||
@@ -353,7 +351,7 @@ export async function installHooksFromNpmSpec(params: {
|
||||
const spec = params.spec.trim();
|
||||
if (!spec) return { ok: false, error: "missing npm spec" };
|
||||
|
||||
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "moltbot-hook-pack-"));
|
||||
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-hook-pack-"));
|
||||
logger.info?.(`Downloading ${spec}…`);
|
||||
const res = await runCommandWithTimeout(["npm", "pack", spec], {
|
||||
timeoutMs: Math.max(timeoutMs, 300_000),
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import type { MoltbotConfig } from "../config/config.js";
|
||||
import type { OpenClawConfig } from "../config/config.js";
|
||||
import type { HookInstallRecord } from "../config/types.hooks.js";
|
||||
|
||||
export type HookInstallUpdate = HookInstallRecord & { hookId: string };
|
||||
|
||||
export function recordHookInstall(cfg: MoltbotConfig, update: HookInstallUpdate): MoltbotConfig {
|
||||
export function recordHookInstall(cfg: OpenClawConfig, update: HookInstallUpdate): OpenClawConfig {
|
||||
const { hookId, ...record } = update;
|
||||
const installs = {
|
||||
...cfg.hooks?.internal?.installs,
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
/**
|
||||
* Hook system for moltbot agent events
|
||||
* Hook system for OpenClaw agent events
|
||||
*
|
||||
* Provides an extensible event-driven hook system for agent events
|
||||
* like command processing, session lifecycle, etc.
|
||||
*/
|
||||
|
||||
import type { WorkspaceBootstrapFile } from "../agents/workspace.js";
|
||||
import type { MoltbotConfig } from "../config/config.js";
|
||||
import type { OpenClawConfig } from "../config/config.js";
|
||||
|
||||
export type InternalHookEventType = "command" | "session" | "agent" | "gateway";
|
||||
|
||||
export type AgentBootstrapHookContext = {
|
||||
workspaceDir: string;
|
||||
bootstrapFiles: WorkspaceBootstrapFile[];
|
||||
cfg?: MoltbotConfig;
|
||||
cfg?: OpenClawConfig;
|
||||
sessionKey?: string;
|
||||
sessionId?: string;
|
||||
agentId?: string;
|
||||
|
||||
@@ -6,7 +6,7 @@ import fs from "node:fs/promises";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { runEmbeddedPiAgent } from "../agents/pi-embedded.js";
|
||||
import type { MoltbotConfig } from "../config/config.js";
|
||||
import type { OpenClawConfig } from "../config/config.js";
|
||||
import {
|
||||
resolveDefaultAgentId,
|
||||
resolveAgentWorkspaceDir,
|
||||
@@ -18,7 +18,7 @@ import {
|
||||
*/
|
||||
export async function generateSlugViaLLM(params: {
|
||||
sessionContent: string;
|
||||
cfg: MoltbotConfig;
|
||||
cfg: OpenClawConfig;
|
||||
}): Promise<string | null> {
|
||||
let tempSessionFile: string | null = null;
|
||||
|
||||
@@ -28,7 +28,7 @@ export async function generateSlugViaLLM(params: {
|
||||
const agentDir = resolveAgentDir(params.cfg, agentId);
|
||||
|
||||
// Create a temporary session file for this one-off LLM call
|
||||
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "moltbot-slug-"));
|
||||
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-slug-"));
|
||||
tempSessionFile = path.join(tempDir, "session.jsonl");
|
||||
|
||||
const prompt = `Based on this conversation, generate a short 1-2 word filename slug (lowercase, hyphen-separated, no file extension).
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
triggerInternalHook,
|
||||
createInternalHookEvent,
|
||||
} from "./internal-hooks.js";
|
||||
import type { MoltbotConfig } from "../config/config.js";
|
||||
import type { OpenClawConfig } from "../config/config.js";
|
||||
|
||||
describe("loader", () => {
|
||||
let tmpDir: string;
|
||||
@@ -18,21 +18,21 @@ describe("loader", () => {
|
||||
beforeEach(async () => {
|
||||
clearInternalHooks();
|
||||
// Create a temp directory for test modules
|
||||
tmpDir = path.join(os.tmpdir(), `moltbot-test-${Date.now()}`);
|
||||
tmpDir = path.join(os.tmpdir(), `openclaw-test-${Date.now()}`);
|
||||
await fs.mkdir(tmpDir, { recursive: true });
|
||||
|
||||
// Disable bundled hooks during tests by setting env var to non-existent directory
|
||||
originalBundledDir = process.env.CLAWDBOT_BUNDLED_HOOKS_DIR;
|
||||
process.env.CLAWDBOT_BUNDLED_HOOKS_DIR = "/nonexistent/bundled/hooks";
|
||||
originalBundledDir = process.env.OPENCLAW_BUNDLED_HOOKS_DIR;
|
||||
process.env.OPENCLAW_BUNDLED_HOOKS_DIR = "/nonexistent/bundled/hooks";
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
clearInternalHooks();
|
||||
// Restore original env var
|
||||
if (originalBundledDir === undefined) {
|
||||
delete process.env.CLAWDBOT_BUNDLED_HOOKS_DIR;
|
||||
delete process.env.OPENCLAW_BUNDLED_HOOKS_DIR;
|
||||
} else {
|
||||
process.env.CLAWDBOT_BUNDLED_HOOKS_DIR = originalBundledDir;
|
||||
process.env.OPENCLAW_BUNDLED_HOOKS_DIR = originalBundledDir;
|
||||
}
|
||||
// Clean up temp directory
|
||||
try {
|
||||
@@ -44,7 +44,7 @@ describe("loader", () => {
|
||||
|
||||
describe("loadInternalHooks", () => {
|
||||
it("should return 0 when hooks are not enabled", async () => {
|
||||
const cfg: MoltbotConfig = {
|
||||
const cfg: OpenClawConfig = {
|
||||
hooks: {
|
||||
internal: {
|
||||
enabled: false,
|
||||
@@ -57,7 +57,7 @@ describe("loader", () => {
|
||||
});
|
||||
|
||||
it("should return 0 when hooks config is missing", async () => {
|
||||
const cfg: MoltbotConfig = {};
|
||||
const cfg: OpenClawConfig = {};
|
||||
const count = await loadInternalHooks(cfg, tmpDir);
|
||||
expect(count).toBe(0);
|
||||
});
|
||||
@@ -72,7 +72,7 @@ describe("loader", () => {
|
||||
`;
|
||||
await fs.writeFile(handlerPath, handlerCode, "utf-8");
|
||||
|
||||
const cfg: MoltbotConfig = {
|
||||
const cfg: OpenClawConfig = {
|
||||
hooks: {
|
||||
internal: {
|
||||
enabled: true,
|
||||
@@ -101,7 +101,7 @@ describe("loader", () => {
|
||||
await fs.writeFile(handler1Path, "export default async function() {}", "utf-8");
|
||||
await fs.writeFile(handler2Path, "export default async function() {}", "utf-8");
|
||||
|
||||
const cfg: MoltbotConfig = {
|
||||
const cfg: OpenClawConfig = {
|
||||
hooks: {
|
||||
internal: {
|
||||
enabled: true,
|
||||
@@ -131,7 +131,7 @@ describe("loader", () => {
|
||||
`;
|
||||
await fs.writeFile(handlerPath, handlerCode, "utf-8");
|
||||
|
||||
const cfg: MoltbotConfig = {
|
||||
const cfg: OpenClawConfig = {
|
||||
hooks: {
|
||||
internal: {
|
||||
enabled: true,
|
||||
@@ -153,7 +153,7 @@ describe("loader", () => {
|
||||
it("should handle module loading errors gracefully", async () => {
|
||||
const consoleError = vi.spyOn(console, "error").mockImplementation(() => {});
|
||||
|
||||
const cfg: MoltbotConfig = {
|
||||
const cfg: OpenClawConfig = {
|
||||
hooks: {
|
||||
internal: {
|
||||
enabled: true,
|
||||
@@ -184,7 +184,7 @@ describe("loader", () => {
|
||||
const handlerPath = path.join(tmpDir, "bad-export.js");
|
||||
await fs.writeFile(handlerPath, 'export default "not a function";', "utf-8");
|
||||
|
||||
const cfg: MoltbotConfig = {
|
||||
const cfg: OpenClawConfig = {
|
||||
hooks: {
|
||||
internal: {
|
||||
enabled: true,
|
||||
@@ -213,7 +213,7 @@ describe("loader", () => {
|
||||
// Get relative path from cwd
|
||||
const relativePath = path.relative(process.cwd(), handlerPath);
|
||||
|
||||
const cfg: MoltbotConfig = {
|
||||
const cfg: OpenClawConfig = {
|
||||
hooks: {
|
||||
internal: {
|
||||
enabled: true,
|
||||
@@ -245,7 +245,7 @@ describe("loader", () => {
|
||||
`;
|
||||
await fs.writeFile(handlerPath, handlerCode, "utf-8");
|
||||
|
||||
const cfg: MoltbotConfig = {
|
||||
const cfg: OpenClawConfig = {
|
||||
hooks: {
|
||||
internal: {
|
||||
enabled: true,
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
import { pathToFileURL } from "node:url";
|
||||
import path from "node:path";
|
||||
import { registerInternalHook } from "./internal-hooks.js";
|
||||
import type { MoltbotConfig } from "../config/config.js";
|
||||
import type { OpenClawConfig } from "../config/config.js";
|
||||
import type { InternalHookHandler } from "./internal-hooks.js";
|
||||
import { loadWorkspaceHookEntries } from "./workspace.js";
|
||||
import { resolveHookConfig } from "./config.js";
|
||||
@@ -21,7 +21,7 @@ import { shouldIncludeHook } from "./config.js";
|
||||
* 1. Directory-based discovery (bundled, managed, workspace)
|
||||
* 2. Legacy config handlers (backwards compatibility)
|
||||
*
|
||||
* @param cfg - Moltbot configuration
|
||||
* @param cfg - OpenClaw configuration
|
||||
* @param workspaceDir - Workspace directory for hook discovery
|
||||
* @returns Number of handlers successfully loaded
|
||||
*
|
||||
@@ -33,7 +33,10 @@ import { shouldIncludeHook } from "./config.js";
|
||||
* console.log(`Loaded ${count} hook handlers`);
|
||||
* ```
|
||||
*/
|
||||
export async function loadInternalHooks(cfg: MoltbotConfig, workspaceDir: string): Promise<number> {
|
||||
export async function loadInternalHooks(
|
||||
cfg: OpenClawConfig,
|
||||
workspaceDir: string,
|
||||
): Promise<number> {
|
||||
// Check if hooks are enabled
|
||||
if (!cfg.hooks?.internal?.enabled) {
|
||||
return 0;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import path from "node:path";
|
||||
import { pathToFileURL } from "node:url";
|
||||
|
||||
import type { MoltbotPluginApi } from "../plugins/types.js";
|
||||
import type { OpenClawPluginApi } from "../plugins/types.js";
|
||||
import type { HookEntry } from "./types.js";
|
||||
import { shouldIncludeHook } from "./config.js";
|
||||
import { loadHookEntriesFromDir } from "./workspace.js";
|
||||
@@ -14,17 +14,17 @@ export type PluginHookLoadResult = {
|
||||
errors: string[];
|
||||
};
|
||||
|
||||
function resolveHookDir(api: MoltbotPluginApi, dir: string): string {
|
||||
function resolveHookDir(api: OpenClawPluginApi, dir: string): string {
|
||||
if (path.isAbsolute(dir)) return dir;
|
||||
return path.resolve(path.dirname(api.source), dir);
|
||||
}
|
||||
|
||||
function normalizePluginHookEntry(api: MoltbotPluginApi, entry: HookEntry): HookEntry {
|
||||
function normalizePluginHookEntry(api: OpenClawPluginApi, entry: HookEntry): HookEntry {
|
||||
return {
|
||||
...entry,
|
||||
hook: {
|
||||
...entry.hook,
|
||||
source: "moltbot-plugin",
|
||||
source: "openclaw-plugin",
|
||||
pluginId: api.id,
|
||||
},
|
||||
metadata: {
|
||||
@@ -37,7 +37,7 @@ function normalizePluginHookEntry(api: MoltbotPluginApi, entry: HookEntry): Hook
|
||||
|
||||
async function loadHookHandler(
|
||||
entry: HookEntry,
|
||||
api: MoltbotPluginApi,
|
||||
api: OpenClawPluginApi,
|
||||
): Promise<InternalHookHandler | null> {
|
||||
try {
|
||||
const url = pathToFileURL(entry.hook.handlerPath).href;
|
||||
@@ -57,13 +57,13 @@ async function loadHookHandler(
|
||||
}
|
||||
|
||||
export async function registerPluginHooksFromDir(
|
||||
api: MoltbotPluginApi,
|
||||
api: OpenClawPluginApi,
|
||||
dir: string,
|
||||
): Promise<PluginHookLoadResult> {
|
||||
const resolvedDir = resolveHookDir(api, dir);
|
||||
const hooks = loadHookEntriesFromDir({
|
||||
dir: resolvedDir,
|
||||
source: "moltbot-plugin",
|
||||
source: "openclaw-plugin",
|
||||
pluginId: api.id,
|
||||
});
|
||||
|
||||
|
||||
@@ -116,7 +116,7 @@ describe("decideSoulEvil", () => {
|
||||
|
||||
describe("applySoulEvilOverride", () => {
|
||||
it("replaces SOUL content when evil is active and file exists", async () => {
|
||||
const tempDir = await makeTempWorkspace("moltbot-soul-");
|
||||
const tempDir = await makeTempWorkspace("openclaw-soul-");
|
||||
await writeWorkspaceFile({
|
||||
dir: tempDir,
|
||||
name: DEFAULT_SOUL_EVIL_FILENAME,
|
||||
@@ -140,7 +140,7 @@ describe("applySoulEvilOverride", () => {
|
||||
});
|
||||
|
||||
it("leaves SOUL content when evil file is missing", async () => {
|
||||
const tempDir = await makeTempWorkspace("moltbot-soul-");
|
||||
const tempDir = await makeTempWorkspace("openclaw-soul-");
|
||||
const files = makeFiles({
|
||||
path: path.join(tempDir, DEFAULT_SOUL_FILENAME),
|
||||
});
|
||||
@@ -158,7 +158,7 @@ describe("applySoulEvilOverride", () => {
|
||||
});
|
||||
|
||||
it("uses custom evil filename when configured", async () => {
|
||||
const tempDir = await makeTempWorkspace("moltbot-soul-");
|
||||
const tempDir = await makeTempWorkspace("openclaw-soul-");
|
||||
await writeWorkspaceFile({
|
||||
dir: tempDir,
|
||||
name: "SOUL_EVIL_CUSTOM.md",
|
||||
@@ -182,7 +182,7 @@ describe("applySoulEvilOverride", () => {
|
||||
});
|
||||
|
||||
it("warns and skips when evil file is empty", async () => {
|
||||
const tempDir = await makeTempWorkspace("moltbot-soul-");
|
||||
const tempDir = await makeTempWorkspace("openclaw-soul-");
|
||||
await writeWorkspaceFile({
|
||||
dir: tempDir,
|
||||
name: DEFAULT_SOUL_EVIL_FILENAME,
|
||||
@@ -209,7 +209,7 @@ describe("applySoulEvilOverride", () => {
|
||||
});
|
||||
|
||||
it("leaves files untouched when SOUL.md is not in bootstrap files", async () => {
|
||||
const tempDir = await makeTempWorkspace("moltbot-soul-");
|
||||
const tempDir = await makeTempWorkspace("openclaw-soul-");
|
||||
await writeWorkspaceFile({
|
||||
dir: tempDir,
|
||||
name: DEFAULT_SOUL_EVIL_FILENAME,
|
||||
|
||||
@@ -7,7 +7,7 @@ export type HookInstallSpec = {
|
||||
bins?: string[];
|
||||
};
|
||||
|
||||
export type MoltbotHookMetadata = {
|
||||
export type OpenClawHookMetadata = {
|
||||
always?: boolean;
|
||||
hookKey?: string;
|
||||
emoji?: string;
|
||||
@@ -35,7 +35,7 @@ export type ParsedHookFrontmatter = Record<string, string>;
|
||||
export type Hook = {
|
||||
name: string;
|
||||
description: string;
|
||||
source: "moltbot-bundled" | "moltbot-managed" | "moltbot-workspace" | "moltbot-plugin";
|
||||
source: "openclaw-bundled" | "openclaw-managed" | "openclaw-workspace" | "openclaw-plugin";
|
||||
pluginId?: string;
|
||||
filePath: string; // Path to HOOK.md
|
||||
baseDir: string; // Directory containing hook
|
||||
@@ -47,7 +47,7 @@ export type HookSource = Hook["source"];
|
||||
export type HookEntry = {
|
||||
hook: Hook;
|
||||
frontmatter: ParsedHookFrontmatter;
|
||||
metadata?: MoltbotHookMetadata;
|
||||
metadata?: OpenClawHookMetadata;
|
||||
invocation?: HookInvocationPolicy;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
|
||||
import { LEGACY_MANIFEST_KEY } from "../compat/legacy-names.js";
|
||||
import type { MoltbotConfig } from "../config/config.js";
|
||||
import { MANIFEST_KEY } from "../compat/legacy-names.js";
|
||||
import type { OpenClawConfig } from "../config/config.js";
|
||||
import { CONFIG_DIR, resolveUserPath } from "../utils.js";
|
||||
import { resolveBundledHooksDir } from "./bundled-dir.js";
|
||||
import { shouldIncludeHook } from "./config.js";
|
||||
import {
|
||||
parseFrontmatter,
|
||||
resolveMoltbotMetadata,
|
||||
resolveOpenClawMetadata,
|
||||
resolveHookInvocationPolicy,
|
||||
} from "./frontmatter.js";
|
||||
import type {
|
||||
@@ -22,13 +22,11 @@ import type {
|
||||
|
||||
type HookPackageManifest = {
|
||||
name?: string;
|
||||
moltbot?: { hooks?: string[] };
|
||||
[LEGACY_MANIFEST_KEY]?: { hooks?: string[] };
|
||||
};
|
||||
} & Partial<Record<typeof MANIFEST_KEY, { hooks?: string[] }>>;
|
||||
|
||||
function filterHookEntries(
|
||||
entries: HookEntry[],
|
||||
config?: MoltbotConfig,
|
||||
config?: OpenClawConfig,
|
||||
eligibility?: HookEligibilityContext,
|
||||
): HookEntry[] {
|
||||
return entries.filter((entry) => shouldIncludeHook({ entry, config, eligibility }));
|
||||
@@ -46,7 +44,7 @@ function readHookPackageManifest(dir: string): HookPackageManifest | null {
|
||||
}
|
||||
|
||||
function resolvePackageHooks(manifest: HookPackageManifest): string[] {
|
||||
const raw = manifest.moltbot?.hooks ?? manifest[LEGACY_MANIFEST_KEY]?.hooks;
|
||||
const raw = manifest[MANIFEST_KEY]?.hooks;
|
||||
if (!Array.isArray(raw)) return [];
|
||||
return raw.map((entry) => (typeof entry === "string" ? entry.trim() : "")).filter(Boolean);
|
||||
}
|
||||
@@ -169,7 +167,7 @@ export function loadHookEntriesFromDir(params: {
|
||||
pluginId: params.pluginId,
|
||||
},
|
||||
frontmatter,
|
||||
metadata: resolveMoltbotMetadata(frontmatter),
|
||||
metadata: resolveOpenClawMetadata(frontmatter),
|
||||
invocation: resolveHookInvocationPolicy(frontmatter),
|
||||
};
|
||||
return entry;
|
||||
@@ -179,7 +177,7 @@ export function loadHookEntriesFromDir(params: {
|
||||
function loadHookEntries(
|
||||
workspaceDir: string,
|
||||
opts?: {
|
||||
config?: MoltbotConfig;
|
||||
config?: OpenClawConfig;
|
||||
managedHooksDir?: string;
|
||||
bundledHooksDir?: string;
|
||||
},
|
||||
@@ -195,23 +193,23 @@ function loadHookEntries(
|
||||
const bundledHooks = bundledHooksDir
|
||||
? loadHooksFromDir({
|
||||
dir: bundledHooksDir,
|
||||
source: "moltbot-bundled",
|
||||
source: "openclaw-bundled",
|
||||
})
|
||||
: [];
|
||||
const extraHooks = extraDirs.flatMap((dir) => {
|
||||
const resolved = resolveUserPath(dir);
|
||||
return loadHooksFromDir({
|
||||
dir: resolved,
|
||||
source: "moltbot-workspace", // Extra dirs treated as workspace
|
||||
source: "openclaw-workspace", // Extra dirs treated as workspace
|
||||
});
|
||||
});
|
||||
const managedHooks = loadHooksFromDir({
|
||||
dir: managedHooksDir,
|
||||
source: "moltbot-managed",
|
||||
source: "openclaw-managed",
|
||||
});
|
||||
const workspaceHooks = loadHooksFromDir({
|
||||
dir: workspaceHooksDir,
|
||||
source: "moltbot-workspace",
|
||||
source: "openclaw-workspace",
|
||||
});
|
||||
|
||||
const merged = new Map<string, Hook>();
|
||||
@@ -232,7 +230,7 @@ function loadHookEntries(
|
||||
return {
|
||||
hook,
|
||||
frontmatter,
|
||||
metadata: resolveMoltbotMetadata(frontmatter),
|
||||
metadata: resolveOpenClawMetadata(frontmatter),
|
||||
invocation: resolveHookInvocationPolicy(frontmatter),
|
||||
};
|
||||
});
|
||||
@@ -241,7 +239,7 @@ function loadHookEntries(
|
||||
export function buildWorkspaceHookSnapshot(
|
||||
workspaceDir: string,
|
||||
opts?: {
|
||||
config?: MoltbotConfig;
|
||||
config?: OpenClawConfig;
|
||||
managedHooksDir?: string;
|
||||
bundledHooksDir?: string;
|
||||
entries?: HookEntry[];
|
||||
@@ -265,7 +263,7 @@ export function buildWorkspaceHookSnapshot(
|
||||
export function loadWorkspaceHookEntries(
|
||||
workspaceDir: string,
|
||||
opts?: {
|
||||
config?: MoltbotConfig;
|
||||
config?: OpenClawConfig;
|
||||
managedHooksDir?: string;
|
||||
bundledHooksDir?: string;
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user