bereach-openclaw 1.6.3 → 1.6.4
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/node_modules/@bereach/tools/src/enforcement-types.ts +0 -4
- package/openclaw.plugin.json +1 -9
- package/package.json +1 -4
- package/src/commands/setup.ts +0 -61
- package/src/hooks/context/formatters.ts +1 -1
- package/src/hooks/context/index.ts +3 -7
- package/src/hooks/detect-task-mode.ts +1 -13
- package/src/hooks/types.ts +2 -2
- package/src/index.ts +1 -72
- package/src/tools/index.ts +3 -17
|
@@ -198,10 +198,6 @@ export interface PluginConfig {
|
|
|
198
198
|
writePacingMax?: number;
|
|
199
199
|
/** Minimum minutes between direct DM sends (default 5). Override via dm_pacing_minutes context. */
|
|
200
200
|
dmPacingMinutes?: number;
|
|
201
|
-
/** Gateway URL for webhook execution */
|
|
202
|
-
gatewayUrl?: string;
|
|
203
|
-
/** Gateway hooks auth token */
|
|
204
|
-
hooksToken?: string;
|
|
205
201
|
}
|
|
206
202
|
|
|
207
203
|
// ---------------------------------------------------------------------------
|
package/openclaw.plugin.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "bereach-openclaw",
|
|
3
3
|
"name": "BeReach",
|
|
4
|
-
"version": "1.6.
|
|
4
|
+
"version": "1.6.4",
|
|
5
5
|
"description": "LinkedIn outreach automation — 75+ tools, hook-based enforcement, dynamic context",
|
|
6
6
|
"configSchema": {
|
|
7
7
|
"type": "object",
|
|
@@ -66,14 +66,6 @@
|
|
|
66
66
|
"maximum": 30,
|
|
67
67
|
"default": 10,
|
|
68
68
|
"description": "Maximum delay (seconds) before write operations (default: 10s)"
|
|
69
|
-
},
|
|
70
|
-
"gatewayUrl": {
|
|
71
|
-
"type": "string",
|
|
72
|
-
"description": "Gateway URL for webhook execution (default: http://localhost:18789)"
|
|
73
|
-
},
|
|
74
|
-
"hooksToken": {
|
|
75
|
-
"type": "string",
|
|
76
|
-
"description": "Gateway hooks auth token (set by /bereach setup)"
|
|
77
69
|
}
|
|
78
70
|
}
|
|
79
71
|
},
|
package/package.json
CHANGED
|
@@ -1,14 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bereach-openclaw",
|
|
3
|
-
"version": "1.6.
|
|
3
|
+
"version": "1.6.4",
|
|
4
4
|
"description": "BeReach LinkedIn automation plugin for OpenClaw",
|
|
5
5
|
"license": "AGPL-3.0",
|
|
6
6
|
"exports": {
|
|
7
7
|
".": "./src/index.ts"
|
|
8
8
|
},
|
|
9
|
-
"bin": {
|
|
10
|
-
"bereach-openclaw": "./src/connector-cli.ts"
|
|
11
|
-
},
|
|
12
9
|
"files": [
|
|
13
10
|
"src/",
|
|
14
11
|
"skills/",
|
package/src/commands/setup.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { errMsg } from "@bereach/tools/utils";
|
|
2
|
-
import { randomBytes } from "node:crypto";
|
|
3
2
|
import { API_BASE } from "../hooks/utils";
|
|
4
3
|
import { isApiBaseConfigured } from "../hooks/cache";
|
|
5
4
|
import { readEnv } from "../env";
|
|
@@ -50,66 +49,6 @@ export function registerSetupCommand(api: any) {
|
|
|
50
49
|
issues.push("API Key: NOT configured. Set BEREACH_API_KEY in plugin settings or environment.");
|
|
51
50
|
}
|
|
52
51
|
|
|
53
|
-
// 3. Gateway hooks configuration (auto-configure if missing)
|
|
54
|
-
let hooksConfigured = false;
|
|
55
|
-
try {
|
|
56
|
-
if (typeof api?.config?.get === "function") {
|
|
57
|
-
const hooksEnabled = api.config.get("hooks.enabled");
|
|
58
|
-
const hooksToken = api.config.get("hooks.token");
|
|
59
|
-
if (hooksEnabled && hooksToken) {
|
|
60
|
-
ok.push("Gateway Hooks: enabled (token set)");
|
|
61
|
-
hooksConfigured = true;
|
|
62
|
-
} else if (hooksEnabled) {
|
|
63
|
-
const token = randomBytes(24).toString("hex");
|
|
64
|
-
api.config.set("hooks.token", token);
|
|
65
|
-
ok.push(`Gateway Hooks: enabled (token auto-generated: ${token.slice(0, 8)}...)`);
|
|
66
|
-
hooksConfigured = true;
|
|
67
|
-
} else {
|
|
68
|
-
api.config.set("hooks.enabled", true);
|
|
69
|
-
const token = randomBytes(24).toString("hex");
|
|
70
|
-
api.config.set("hooks.token", token);
|
|
71
|
-
ok.push(`Gateway Hooks: auto-enabled (token: ${token.slice(0, 8)}...). Restart gateway to apply.`);
|
|
72
|
-
hooksConfigured = true;
|
|
73
|
-
}
|
|
74
|
-
} else {
|
|
75
|
-
issues.push("Gateway Hooks: cannot check (api.config not available).");
|
|
76
|
-
}
|
|
77
|
-
} catch (err) {
|
|
78
|
-
issues.push(`Gateway Hooks: error (${errMsg(err)})`);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
// 4. Gateway webhook probe
|
|
82
|
-
if (hooksConfigured) {
|
|
83
|
-
const gatewayUrl = api?.pluginConfig?.gatewayUrl
|
|
84
|
-
?? readEnv("OPENCLAW_GATEWAY_URL")
|
|
85
|
-
?? "http://localhost:18789";
|
|
86
|
-
const hooksToken = api?.config?.get?.("hooks.token")
|
|
87
|
-
?? api?.pluginConfig?.hooksToken
|
|
88
|
-
?? readEnv("OPENCLAW_HOOKS_TOKEN");
|
|
89
|
-
|
|
90
|
-
if (hooksToken) {
|
|
91
|
-
try {
|
|
92
|
-
const res = await fetch(`${gatewayUrl}/hooks/agent`, {
|
|
93
|
-
method: "POST",
|
|
94
|
-
headers: {
|
|
95
|
-
Authorization: `Bearer ${hooksToken}`,
|
|
96
|
-
"Content-Type": "application/json",
|
|
97
|
-
},
|
|
98
|
-
body: JSON.stringify({ message: "ping", model: "echo" }),
|
|
99
|
-
signal: AbortSignal.timeout(5000),
|
|
100
|
-
});
|
|
101
|
-
if (res.status === 401 || res.status === 403) {
|
|
102
|
-
issues.push(`Webhook probe: auth rejected (HTTP ${res.status}). Token mismatch between plugin and gateway config.`);
|
|
103
|
-
} else {
|
|
104
|
-
ok.push(`Webhook endpoint: reachable at ${gatewayUrl}`);
|
|
105
|
-
}
|
|
106
|
-
} catch {
|
|
107
|
-
// Gateway not up yet is OK during initial setup
|
|
108
|
-
ok.push(`Webhook endpoint: ${gatewayUrl} (not reachable yet - gateway may need restart)`);
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
52
|
// Build output
|
|
114
53
|
const lines: string[] = [
|
|
115
54
|
"BeReach Setup",
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* These functions take data and return strings — no side effects, no API calls.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { cacheSet, type CacheStore, type
|
|
6
|
+
import { cacheSet, type CacheStore, type OnboardingState, type RecentEvent } from "../cache";
|
|
7
7
|
import { type DbCampaign, type SessionState } from "../types";
|
|
8
8
|
import { errMsg, createLogger, CHAT_BASE, PRICING_URL, apiFetch } from "../utils";
|
|
9
9
|
import { readEnv } from "../../env";
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
import { getOrFetch, sessionStart, type CacheStore, type ContextEntry } from "../cache";
|
|
10
10
|
import { SOUL_TEMPLATE } from "../../soul-template-content";
|
|
11
|
-
|
|
11
|
+
|
|
12
12
|
import { type SessionState, detectTaskMode } from "../types";
|
|
13
13
|
import {
|
|
14
14
|
errMsg,
|
|
@@ -237,10 +237,8 @@ export function registerContextHook(api: any, apiKey: string | undefined, state:
|
|
|
237
237
|
const metadata: Record<string, unknown> = { ...(promptCtx?.metadata ?? {}) };
|
|
238
238
|
|
|
239
239
|
// Parse [TASK_META: ...] from the prompt message (used by Docker test runner
|
|
240
|
-
//
|
|
241
|
-
|
|
242
|
-
// env-var path always takes priority. In production chat, users don't type TASK_META.
|
|
243
|
-
if (!metadata.taskType && !readEnv("BEREACH_TASK_TYPE")) {
|
|
240
|
+
// to pass task metadata). In production chat, users don't type TASK_META.
|
|
241
|
+
if (!metadata.taskType) {
|
|
244
242
|
const msgs = Array.isArray(promptCtx?.messages) ? promptCtx.messages : [];
|
|
245
243
|
const lastUserMsg = msgs.filter((m: any) => m?.role === "user").pop();
|
|
246
244
|
const msgText = promptCtx?.prompt
|
|
@@ -329,5 +327,3 @@ export function registerContextHook(api: any, apiKey: string | undefined, state:
|
|
|
329
327
|
export const _checkBusinessHours = checkBusinessHours;
|
|
330
328
|
/** @internal — exported for testing only */
|
|
331
329
|
export { rankCampaigns as _rankCampaigns } from "./formatters";
|
|
332
|
-
/** @internal — exported for testing only */
|
|
333
|
-
export const _formatAnthropicKeyWarning = formatAnthropicKeyWarning;
|
|
@@ -4,7 +4,6 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import type { TaskModeInfo } from "@bereach/tools/enforcement-types";
|
|
7
|
-
import { readEnv } from "../env";
|
|
8
7
|
|
|
9
8
|
/** Safely parse maxCredits from any input (string, number, undefined). */
|
|
10
9
|
function parseMaxCredits(value: unknown): number {
|
|
@@ -18,7 +17,7 @@ function parseMaxCredits(value: unknown): number {
|
|
|
18
17
|
|
|
19
18
|
/**
|
|
20
19
|
* Detect task mode from the sessionKey convention: "hook:{userId}:{campaignId}:{type}"
|
|
21
|
-
* or from metadata
|
|
20
|
+
* or from metadata fields (passed via webhook or promptCtx).
|
|
22
21
|
* Returns TaskModeInfo if this is a task-driven session, null for interactive.
|
|
23
22
|
*/
|
|
24
23
|
export function detectTaskMode(sessionKey: string | undefined | null, metadata?: Record<string, unknown>): TaskModeInfo | null {
|
|
@@ -53,16 +52,5 @@ export function detectTaskMode(sessionKey: string | undefined | null, metadata?:
|
|
|
53
52
|
};
|
|
54
53
|
}
|
|
55
54
|
|
|
56
|
-
// Strategy 3: environment variables (set when spawning CLI)
|
|
57
|
-
const envTaskType = readEnv("BEREACH_TASK_TYPE");
|
|
58
|
-
if (envTaskType) {
|
|
59
|
-
return {
|
|
60
|
-
taskId: readEnv("BEREACH_TASK_ID") || fallbackTaskId(),
|
|
61
|
-
taskType: envTaskType,
|
|
62
|
-
campaignId: readEnv("BEREACH_TASK_CAMPAIGN_ID") ?? null,
|
|
63
|
-
maxCredits: parseMaxCredits(readEnv("BEREACH_TASK_MAX_CREDITS")),
|
|
64
|
-
};
|
|
65
|
-
}
|
|
66
|
-
|
|
67
55
|
return null;
|
|
68
56
|
}
|
package/src/hooks/types.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Shared types, constants, and tool categories for BeReach OpenClaw hooks.
|
|
3
3
|
*
|
|
4
4
|
* Re-exports from @bereach/tools (single source of truth).
|
|
5
|
-
* detectTaskMode stays local (
|
|
5
|
+
* detectTaskMode stays local (OpenClaw-specific session detection).
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
// Re-export everything from shared package
|
|
@@ -25,5 +25,5 @@ export type {
|
|
|
25
25
|
DbCampaign,
|
|
26
26
|
} from "@bereach/tools/enforcement-types";
|
|
27
27
|
|
|
28
|
-
// Keep detectTaskMode locally (
|
|
28
|
+
// Keep detectTaskMode locally (OpenClaw-specific session/metadata detection)
|
|
29
29
|
export { detectTaskMode } from "./detect-task-mode";
|
package/src/index.ts
CHANGED
|
@@ -9,11 +9,6 @@ import { setTtl, isApiBaseConfigured } from "./hooks/cache";
|
|
|
9
9
|
import { createSessionState, type PluginConfig } from "./hooks/types";
|
|
10
10
|
import { errMsg, createLogger } from "./hooks/utils";
|
|
11
11
|
import { autoDetectModels } from "./auto-detect-models";
|
|
12
|
-
import { randomBytes } from "node:crypto";
|
|
13
|
-
import { readFileSync, writeFileSync, existsSync, unlinkSync } from "node:fs";
|
|
14
|
-
import { join } from "node:path";
|
|
15
|
-
import { homedir } from "node:os";
|
|
16
|
-
import { spawn } from "node:child_process";
|
|
17
12
|
import { readEnv } from "./env";
|
|
18
13
|
|
|
19
14
|
const log = createLogger("init");
|
|
@@ -77,12 +72,7 @@ export default function register(api: any) {
|
|
|
77
72
|
setTtl(config.contextCacheTtlMs);
|
|
78
73
|
}
|
|
79
74
|
|
|
80
|
-
|
|
81
|
-
// is set per-task. Register only whitelisted tools to reduce token count (~19K savings).
|
|
82
|
-
// In gateway mode (webhook), register all tools; per-request filtering
|
|
83
|
-
// is handled via allowedTools in the before_prompt_build hook return.
|
|
84
|
-
const execFileTaskType = readEnv("BEREACH_TASK_TYPE");
|
|
85
|
-
registerAllTools(api, execFileTaskType || undefined);
|
|
75
|
+
registerAllTools(api);
|
|
86
76
|
registerCommands(api, state, apiKey);
|
|
87
77
|
registerSetupCommand(api);
|
|
88
78
|
|
|
@@ -117,67 +107,6 @@ export default function register(api: any) {
|
|
|
117
107
|
const masked = apiKey ? `...${apiKey.slice(-6)}` : "NOT SET";
|
|
118
108
|
log(`registered: tools, commands, hooks (${registered.join(", ")}), API key: ${masked}`);
|
|
119
109
|
|
|
120
|
-
// Auto-configure hooks in openclaw.json so the gateway exposes /hooks/agent.
|
|
121
|
-
// The OpenClaw plugin API config accessors are undefined, so we read/write
|
|
122
|
-
// the config file directly - same approach as Docker entrypoint.sh.
|
|
123
|
-
let hooksToken = config.hooksToken || readEnv("OPENCLAW_HOOKS_TOKEN");
|
|
124
|
-
let needsRestart = false;
|
|
125
|
-
if (!hooksToken) {
|
|
126
|
-
try {
|
|
127
|
-
const configPath = join(homedir(), ".openclaw", "openclaw.json");
|
|
128
|
-
if (existsSync(configPath)) {
|
|
129
|
-
const raw = readFileSync(configPath, "utf8");
|
|
130
|
-
const cfg = JSON.parse(raw);
|
|
131
|
-
if (cfg.hooks?.enabled && typeof cfg.hooks.token === "string" && cfg.hooks.token) {
|
|
132
|
-
// Hooks already configured - grab the existing token
|
|
133
|
-
hooksToken = cfg.hooks.token;
|
|
134
|
-
log("discovered hooks token from openclaw.json");
|
|
135
|
-
} else {
|
|
136
|
-
// Hooks not configured - generate token and enable them.
|
|
137
|
-
// The gateway reads config at startup before loading plugins,
|
|
138
|
-
// so this change requires a restart to take effect.
|
|
139
|
-
const token = randomBytes(24).toString("hex");
|
|
140
|
-
cfg.hooks = { ...cfg.hooks, enabled: true, token };
|
|
141
|
-
writeFileSync(configPath, JSON.stringify(cfg, null, 2));
|
|
142
|
-
hooksToken = token;
|
|
143
|
-
needsRestart = true;
|
|
144
|
-
log("auto-configured hooks in openclaw.json — gateway restart required");
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
} catch (err) {
|
|
148
|
-
log(`hooks auto-config: ${errMsg(err)}`);
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
// If hooks were just written to config, the gateway must restart to load them.
|
|
153
|
-
// Spawn a detached process that waits, cleans lock/pid, then restarts the gateway.
|
|
154
|
-
if (needsRestart) {
|
|
155
|
-
const ocDir = join(homedir(), ".openclaw");
|
|
156
|
-
const lockFile = join(ocDir, "gateway.lock");
|
|
157
|
-
const pidFile = join(ocDir, "gateway.pid");
|
|
158
|
-
|
|
159
|
-
try {
|
|
160
|
-
// Clean up lock + pid files so the gateway can start fresh
|
|
161
|
-
if (existsSync(lockFile)) unlinkSync(lockFile);
|
|
162
|
-
if (existsSync(pidFile)) unlinkSync(pidFile);
|
|
163
|
-
} catch { /* best effort */ }
|
|
164
|
-
|
|
165
|
-
try {
|
|
166
|
-
const child = spawn("sh", ["-c", "sleep 3 && openclaw gateway start"], {
|
|
167
|
-
detached: true,
|
|
168
|
-
stdio: "ignore",
|
|
169
|
-
});
|
|
170
|
-
child.unref();
|
|
171
|
-
log("hooks config written — spawned detached restart, exiting in 3s");
|
|
172
|
-
} catch (err) {
|
|
173
|
-
log(`restart spawn failed: ${errMsg(err)}`);
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
setTimeout(() => {
|
|
177
|
-
process.exit(0);
|
|
178
|
-
}, 3000);
|
|
179
|
-
}
|
|
180
|
-
|
|
181
110
|
// Auto-detect LLM provider and set workspace model defaults.
|
|
182
111
|
// Fire-and-forget — non-critical, must not block registration.
|
|
183
112
|
if (apiKey) {
|
package/src/tools/index.ts
CHANGED
|
@@ -2,20 +2,15 @@ import { definitions } from "@bereach/tools";
|
|
|
2
2
|
import { executeApiCall } from "@bereach/tools/api-client";
|
|
3
3
|
import { cacheSet } from "../hooks/cache";
|
|
4
4
|
import { resolveApiKey } from "../index";
|
|
5
|
-
import { getTaskToolWhitelist } from "@bereach/tools/task-tool-whitelist";
|
|
6
5
|
|
|
7
6
|
const API_BASE = "https://api.bereach.ai";
|
|
8
7
|
|
|
9
8
|
/**
|
|
10
|
-
* Register tools with OpenClaw.
|
|
11
|
-
*
|
|
12
|
-
* In gateway mode (webhook), all tools are registered; per-request filtering
|
|
13
|
-
* is handled via allowedTools in the hook return.
|
|
9
|
+
* Register all tools with OpenClaw.
|
|
10
|
+
* Per-request filtering is handled via allowedTools in the before_prompt_build hook.
|
|
14
11
|
*/
|
|
15
|
-
export function registerAllTools(api: any
|
|
16
|
-
const whitelist = taskType ? getTaskToolWhitelist(taskType) : null;
|
|
12
|
+
export function registerAllTools(api: any) {
|
|
17
13
|
let registered = 0;
|
|
18
|
-
let skipped = 0;
|
|
19
14
|
|
|
20
15
|
for (const def of definitions) {
|
|
21
16
|
if (!def.apiPath && def.handler !== "__local__" && def.handler !== "__set_api_key__") {
|
|
@@ -23,12 +18,6 @@ export function registerAllTools(api: any, taskType?: string) {
|
|
|
23
18
|
continue;
|
|
24
19
|
}
|
|
25
20
|
|
|
26
|
-
// Skip tools not in the whitelist for this task type
|
|
27
|
-
if (whitelist && !whitelist.has(def.name)) {
|
|
28
|
-
skipped++;
|
|
29
|
-
continue;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
21
|
registered++;
|
|
33
22
|
api.registerTool({
|
|
34
23
|
name: def.name,
|
|
@@ -82,7 +71,4 @@ export function registerAllTools(api: any, taskType?: string) {
|
|
|
82
71
|
});
|
|
83
72
|
}
|
|
84
73
|
|
|
85
|
-
if (whitelist) {
|
|
86
|
-
console.log(`[bereach:tools] Task mode (${taskType}): registered ${registered} tools, pruned ${skipped}`);
|
|
87
|
-
}
|
|
88
74
|
}
|