bereach-openclaw 1.6.1 → 1.6.2
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/__tests__/enforcement-rules.test.ts +4 -4
- package/node_modules/@bereach/tools/src/__tests__/llm-errors-and-whitelist.test.ts +28 -28
- package/node_modules/@bereach/tools/src/definitions.ts +3 -3
- package/node_modules/@bereach/tools/src/enforcement-rules.ts +2 -1
- package/node_modules/@bereach/tools/src/task-tool-whitelist.ts +12 -12
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/skills/bereach/SKILL.md +37 -24
- package/skills/bereach/sub/content.md +2 -2
- package/skills/bereach/sub/inbox.md +27 -19
- package/skills/bereach/sub/lead-gen.md +6 -6
- package/skills/bereach/sub/lead-magnet.md +17 -7
- package/skills/bereach/sub/outreach.md +24 -13
- package/skills/bereach/sub/warmup.md +3 -3
- package/src/index.ts +24 -3
|
@@ -16,7 +16,7 @@ import type { TaskModeInfo } from "../enforcement-types.js";
|
|
|
16
16
|
|
|
17
17
|
function taskMode(overrides: Partial<TaskModeInfo> = {}): TaskModeInfo {
|
|
18
18
|
return {
|
|
19
|
-
taskType: "outreach-
|
|
19
|
+
taskType: "outreach-draft",
|
|
20
20
|
taskId: "task-1",
|
|
21
21
|
campaignId: "camp-1",
|
|
22
22
|
maxCredits: 30,
|
|
@@ -127,7 +127,7 @@ describe("warmupActionGuard", () => {
|
|
|
127
127
|
});
|
|
128
128
|
|
|
129
129
|
it("passes for allowed warmup actions (like, comment, visit)", () => {
|
|
130
|
-
const tm = taskMode({ taskType: "
|
|
130
|
+
const tm = taskMode({ taskType: "engage-warm" });
|
|
131
131
|
expect(warmupActionGuard("bereach_like_post", tm)).toBeNull();
|
|
132
132
|
expect(warmupActionGuard("bereach_visit_profile", tm)).toBeNull();
|
|
133
133
|
expect(warmupActionGuard("bereach_comment_on_post", tm)).toBeNull();
|
|
@@ -135,14 +135,14 @@ describe("warmupActionGuard", () => {
|
|
|
135
135
|
});
|
|
136
136
|
|
|
137
137
|
it("blocks DMs during warmup", () => {
|
|
138
|
-
const tm = taskMode({ taskType: "
|
|
138
|
+
const tm = taskMode({ taskType: "engage-warm" });
|
|
139
139
|
expect(warmupActionGuard("bereach_send_message", tm)).toContain("BLOCKED");
|
|
140
140
|
expect(warmupActionGuard("bereach_scheduled_message_create", tm)).toContain("BLOCKED");
|
|
141
141
|
expect(warmupActionGuard("bereach_draft_schedule", tm)).toContain("BLOCKED");
|
|
142
142
|
});
|
|
143
143
|
|
|
144
144
|
it("blocks connection requests during warmup", () => {
|
|
145
|
-
const tm = taskMode({ taskType: "
|
|
145
|
+
const tm = taskMode({ taskType: "connect-grow" });
|
|
146
146
|
expect(warmupActionGuard("bereach_connect_profile", tm)).toContain("BLOCKED");
|
|
147
147
|
});
|
|
148
148
|
});
|
|
@@ -99,19 +99,19 @@ describe("detectLlmError", () => {
|
|
|
99
99
|
|
|
100
100
|
describe("getTaskToolWhitelist", () => {
|
|
101
101
|
const ALL_TASK_TYPES = [
|
|
102
|
-
"
|
|
103
|
-
"
|
|
104
|
-
"
|
|
105
|
-
"outreach-
|
|
106
|
-
"outreach-
|
|
107
|
-
"
|
|
108
|
-
"
|
|
109
|
-
"
|
|
110
|
-
"
|
|
111
|
-
"
|
|
112
|
-
"content-
|
|
102
|
+
"discover-search",
|
|
103
|
+
"discover-visit",
|
|
104
|
+
"discover-qualify",
|
|
105
|
+
"outreach-draft",
|
|
106
|
+
"outreach-reply",
|
|
107
|
+
"engage-comment",
|
|
108
|
+
"connect-send",
|
|
109
|
+
"connect-review",
|
|
110
|
+
"engage-warm",
|
|
111
|
+
"connect-grow",
|
|
112
|
+
"content-draft",
|
|
113
113
|
"inbox-triage",
|
|
114
|
-
"inbox-
|
|
114
|
+
"inbox-reply",
|
|
115
115
|
];
|
|
116
116
|
|
|
117
117
|
const SHARED_TOOLS = [
|
|
@@ -141,37 +141,37 @@ describe("getTaskToolWhitelist", () => {
|
|
|
141
141
|
}
|
|
142
142
|
});
|
|
143
143
|
|
|
144
|
-
it("outreach-
|
|
145
|
-
const wl = getTaskToolWhitelist("outreach-
|
|
144
|
+
it("outreach-draft includes scheduled message but not send_message", () => {
|
|
145
|
+
const wl = getTaskToolWhitelist("outreach-draft")!;
|
|
146
146
|
expect(wl.has("bereach_scheduled_message_create")).toBe(true);
|
|
147
147
|
expect(wl.has("bereach_send_message")).toBe(false);
|
|
148
148
|
});
|
|
149
149
|
|
|
150
|
-
it("outreach-
|
|
151
|
-
const wl = getTaskToolWhitelist("outreach-
|
|
150
|
+
it("outreach-reply includes send_message for live replies", () => {
|
|
151
|
+
const wl = getTaskToolWhitelist("outreach-reply")!;
|
|
152
152
|
expect(wl.has("bereach_send_message")).toBe(true);
|
|
153
153
|
});
|
|
154
154
|
|
|
155
|
-
it("
|
|
156
|
-
const wl = getTaskToolWhitelist("
|
|
155
|
+
it("engage-warm does NOT include send_message or connect_profile", () => {
|
|
156
|
+
const wl = getTaskToolWhitelist("engage-warm")!;
|
|
157
157
|
expect(wl.has("bereach_send_message")).toBe(false);
|
|
158
158
|
expect(wl.has("bereach_connect_profile")).toBe(false);
|
|
159
159
|
});
|
|
160
160
|
|
|
161
|
-
it("
|
|
162
|
-
const wl = getTaskToolWhitelist("
|
|
161
|
+
it("discover-search includes search tools", () => {
|
|
162
|
+
const wl = getTaskToolWhitelist("discover-search")!;
|
|
163
163
|
expect(wl.has("bereach_unified_search")).toBe(true);
|
|
164
164
|
expect(wl.has("bereach_search_sales_nav")).toBe(true);
|
|
165
165
|
expect(wl.has("bereach_collect_likes")).toBe(true);
|
|
166
166
|
});
|
|
167
167
|
|
|
168
|
-
it("content-
|
|
169
|
-
const wl = getTaskToolWhitelist("content-
|
|
168
|
+
it("content-draft includes publish_post", () => {
|
|
169
|
+
const wl = getTaskToolWhitelist("content-draft")!;
|
|
170
170
|
expect(wl.has("bereach_publish_post")).toBe(true);
|
|
171
171
|
});
|
|
172
172
|
|
|
173
|
-
it("inbox-
|
|
174
|
-
const wl = getTaskToolWhitelist("inbox-
|
|
173
|
+
it("inbox-reply includes send_message for replies", () => {
|
|
174
|
+
const wl = getTaskToolWhitelist("inbox-reply")!;
|
|
175
175
|
expect(wl.has("bereach_send_message")).toBe(true);
|
|
176
176
|
});
|
|
177
177
|
});
|
|
@@ -184,19 +184,19 @@ describe("getTaskToolNames", () => {
|
|
|
184
184
|
});
|
|
185
185
|
|
|
186
186
|
it("returns an array for known task type", () => {
|
|
187
|
-
const result = getTaskToolNames("outreach-
|
|
187
|
+
const result = getTaskToolNames("outreach-draft");
|
|
188
188
|
expect(Array.isArray(result)).toBe(true);
|
|
189
189
|
expect(result!.length).toBeGreaterThan(0);
|
|
190
190
|
});
|
|
191
191
|
|
|
192
192
|
it("has no duplicates", () => {
|
|
193
|
-
const result = getTaskToolNames("outreach-
|
|
193
|
+
const result = getTaskToolNames("outreach-draft")!;
|
|
194
194
|
expect(new Set(result).size).toBe(result.length);
|
|
195
195
|
});
|
|
196
196
|
|
|
197
197
|
it("returns same tools as getTaskToolWhitelist", () => {
|
|
198
|
-
const set = getTaskToolWhitelist("
|
|
199
|
-
const arr = getTaskToolNames("
|
|
198
|
+
const set = getTaskToolWhitelist("discover-qualify")!;
|
|
199
|
+
const arr = getTaskToolNames("discover-qualify")!;
|
|
200
200
|
expect(arr.length).toBe(set.size);
|
|
201
201
|
for (const tool of arr) {
|
|
202
202
|
expect(set.has(tool)).toBe(true);
|
|
@@ -1298,7 +1298,7 @@ export const definitions: ToolDefinition[] = [
|
|
|
1298
1298
|
},
|
|
1299
1299
|
taskOverrides: {
|
|
1300
1300
|
type: "object",
|
|
1301
|
-
description: "Per-task-type config overrides. Keys are task types (e.g. '
|
|
1301
|
+
description: "Per-task-type config overrides. Keys are task types (e.g. 'discover-visit', 'discover-qualify'). Values are config objects with: maxRunsPerDay, minIntervalMinutes, defaultBatchSize, maxCreditsPerRun.",
|
|
1302
1302
|
additionalProperties: {
|
|
1303
1303
|
type: "object",
|
|
1304
1304
|
properties: {
|
|
@@ -1312,7 +1312,7 @@ export const definitions: ToolDefinition[] = [
|
|
|
1312
1312
|
disabledTaskTypes: {
|
|
1313
1313
|
type: "array",
|
|
1314
1314
|
items: { type: "string" },
|
|
1315
|
-
description: "Task types to stop dispatching for this campaign (e.g. ['
|
|
1315
|
+
description: "Task types to stop dispatching for this campaign (e.g. ['discover-search']). Use when strategically pausing a task type (low conversion, enough leads). Set to [] to re-enable all.",
|
|
1316
1316
|
},
|
|
1317
1317
|
},
|
|
1318
1318
|
},
|
|
@@ -2069,7 +2069,7 @@ export const definitions: ToolDefinition[] = [
|
|
|
2069
2069
|
type: "object",
|
|
2070
2070
|
properties: {
|
|
2071
2071
|
status: { type: "string", description: "Filter by task status (queued, dispatched, running, succeeded, failed, cancelled)." },
|
|
2072
|
-
type: { type: "string", description: "Filter by task type (e.g. outreach-
|
|
2072
|
+
type: { type: "string", description: "Filter by task type (e.g. outreach-draft, discover-qualify, engage-comment)." },
|
|
2073
2073
|
campaignId: { type: "string", description: "Filter by campaign ID." },
|
|
2074
2074
|
limit: { type: "integer", minimum: 1, maximum: 100, description: "Max results (default 50)." },
|
|
2075
2075
|
offset: { type: "integer", minimum: 0, description: "Pagination offset." },
|
|
@@ -48,7 +48,8 @@ export function warmupActionGuard(
|
|
|
48
48
|
taskMode: TaskModeInfo | null,
|
|
49
49
|
): string | null {
|
|
50
50
|
if (!taskMode) return null;
|
|
51
|
-
|
|
51
|
+
const WARMUP_TASKS = new Set(["engage-warm", "connect-grow"]);
|
|
52
|
+
if (!WARMUP_TASKS.has(taskMode.taskType)) return null;
|
|
52
53
|
|
|
53
54
|
const blocked = [
|
|
54
55
|
"bereach_send_message",
|
|
@@ -17,7 +17,7 @@ const SHARED_TOOLS = [
|
|
|
17
17
|
|
|
18
18
|
/** Per-task-type required tools (excluding shared). */
|
|
19
19
|
const TASK_TOOLS: Record<string, readonly string[]> = {
|
|
20
|
-
"
|
|
20
|
+
"discover-search": [
|
|
21
21
|
"bereach_state_get",
|
|
22
22
|
"bereach_state_patch",
|
|
23
23
|
"bereach_resolve_parameters",
|
|
@@ -31,7 +31,7 @@ const TASK_TOOLS: Record<string, readonly string[]> = {
|
|
|
31
31
|
// search/collect tools now auto-add to campaign via campaignSlug param
|
|
32
32
|
],
|
|
33
33
|
|
|
34
|
-
"
|
|
34
|
+
"discover-visit": [
|
|
35
35
|
"bereach_contacts_search",
|
|
36
36
|
"bereach_contacts_update",
|
|
37
37
|
"bereach_contacts_log_activity",
|
|
@@ -39,13 +39,13 @@ const TASK_TOOLS: Record<string, readonly string[]> = {
|
|
|
39
39
|
"bereach_bulk_visit_batch_status",
|
|
40
40
|
],
|
|
41
41
|
|
|
42
|
-
"
|
|
42
|
+
"discover-qualify": [
|
|
43
43
|
"bereach_contacts_search",
|
|
44
44
|
"bereach_contacts_update",
|
|
45
45
|
"bereach_contacts_log_activity",
|
|
46
46
|
],
|
|
47
47
|
|
|
48
|
-
"outreach-
|
|
48
|
+
"outreach-draft": [
|
|
49
49
|
"bereach_contacts_search",
|
|
50
50
|
"bereach_contacts_get_activities",
|
|
51
51
|
"bereach_get_conversation_summary",
|
|
@@ -53,7 +53,7 @@ const TASK_TOOLS: Record<string, readonly string[]> = {
|
|
|
53
53
|
"bereach_scheduled_message_create",
|
|
54
54
|
],
|
|
55
55
|
|
|
56
|
-
"outreach-
|
|
56
|
+
"outreach-reply": [
|
|
57
57
|
"bereach_contacts_search",
|
|
58
58
|
"bereach_get_dm_history",
|
|
59
59
|
"bereach_get_conversation_summary",
|
|
@@ -63,7 +63,7 @@ const TASK_TOOLS: Record<string, readonly string[]> = {
|
|
|
63
63
|
"bereach_contacts_log_activity",
|
|
64
64
|
],
|
|
65
65
|
|
|
66
|
-
"
|
|
66
|
+
"engage-comment": [
|
|
67
67
|
"bereach_contacts_search",
|
|
68
68
|
"bereach_contacts_get_activities",
|
|
69
69
|
"bereach_profile_activity",
|
|
@@ -71,7 +71,7 @@ const TASK_TOOLS: Record<string, readonly string[]> = {
|
|
|
71
71
|
"bereach_contacts_log_activity",
|
|
72
72
|
],
|
|
73
73
|
|
|
74
|
-
"
|
|
74
|
+
"connect-send": [
|
|
75
75
|
"bereach_contacts_search",
|
|
76
76
|
"bereach_visit_profile",
|
|
77
77
|
"bereach_connect_profile",
|
|
@@ -79,7 +79,7 @@ const TASK_TOOLS: Record<string, readonly string[]> = {
|
|
|
79
79
|
"bereach_contacts_log_activity",
|
|
80
80
|
],
|
|
81
81
|
|
|
82
|
-
"
|
|
82
|
+
"connect-review": [
|
|
83
83
|
"bereach_list_invitations",
|
|
84
84
|
"bereach_accept_invitation",
|
|
85
85
|
"bereach_contacts_upsert",
|
|
@@ -87,7 +87,7 @@ const TASK_TOOLS: Record<string, readonly string[]> = {
|
|
|
87
87
|
"bereach_contacts_log_activity",
|
|
88
88
|
],
|
|
89
89
|
|
|
90
|
-
"
|
|
90
|
+
"engage-warm": [
|
|
91
91
|
"bereach_contacts_search",
|
|
92
92
|
"bereach_profile_activity",
|
|
93
93
|
"bereach_like_post",
|
|
@@ -97,7 +97,7 @@ const TASK_TOOLS: Record<string, readonly string[]> = {
|
|
|
97
97
|
"bereach_contacts_log_activity",
|
|
98
98
|
],
|
|
99
99
|
|
|
100
|
-
"
|
|
100
|
+
"connect-grow": [
|
|
101
101
|
"bereach_list_invitations",
|
|
102
102
|
"bereach_accept_invitation",
|
|
103
103
|
"bereach_visit_profile",
|
|
@@ -105,7 +105,7 @@ const TASK_TOOLS: Record<string, readonly string[]> = {
|
|
|
105
105
|
"bereach_contacts_log_activity",
|
|
106
106
|
],
|
|
107
107
|
|
|
108
|
-
"content-
|
|
108
|
+
"content-draft": [
|
|
109
109
|
"bereach_state_get",
|
|
110
110
|
"bereach_state_patch",
|
|
111
111
|
"bereach_publish_post",
|
|
@@ -121,7 +121,7 @@ const TASK_TOOLS: Record<string, readonly string[]> = {
|
|
|
121
121
|
"bereach_contacts_get_by_url",
|
|
122
122
|
],
|
|
123
123
|
|
|
124
|
-
"inbox-
|
|
124
|
+
"inbox-reply": [
|
|
125
125
|
"bereach_list_conversations",
|
|
126
126
|
"bereach_get_messages",
|
|
127
127
|
"bereach_send_message",
|
package/openclaw.plugin.json
CHANGED
package/package.json
CHANGED
package/skills/bereach/SKILL.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: bereach
|
|
3
3
|
description: "Automate LinkedIn outreach via BeReach (bereach.ai). Use when: prospecting, engaging posts, scraping engagement, searching LinkedIn, managing inbox, running campaigns, managing invitations, analytics, company pages, Sales Navigator, content engagement, feed monitoring. Requires BEREACH_API_KEY."
|
|
4
|
-
lastUpdatedAt:
|
|
4
|
+
lastUpdatedAt: 1775908473
|
|
5
5
|
metadata: { "openclaw": { "requires": { "env": ["BEREACH_API_KEY"] }, "primaryEnv": "BEREACH_API_KEY" } }
|
|
6
6
|
---
|
|
7
7
|
|
|
@@ -22,12 +22,12 @@ Load sub-skills **on-demand** when the user's request matches a workflow.
|
|
|
22
22
|
|
|
23
23
|
| Sub-skill | Keywords | URL | lastUpdatedAt |
|
|
24
24
|
| ------------- | -------- | --- | ------------- |
|
|
25
|
-
| Lead Gen | lead gen, find leads, search, qualify, ICP, pipeline, scrape, competitor, prospecting, hashtag, Sales Navigator | sub/lead-gen.md |
|
|
26
|
-
| Outreach | outreach, connect, DM, message, follow up, connection request, reply, warming, draft, batch | sub/outreach.md |
|
|
27
|
-
| Engagement | engagement, comment warming, accept invitations, connection requests,
|
|
28
|
-
| Warmup | warmup, warm up, account warmup, engagement, likes, visibility, ramp up, pre-warming | sub/warmup.md |
|
|
29
|
-
| Content | content, post, publish, LinkedIn post, content strategy, draft, article, thought leadership | sub/content.md |
|
|
30
|
-
| Inbox | inbox, triage, classify, archive, star, respond, unread, conversation, spam, inbox management | sub/inbox.md |
|
|
25
|
+
| Lead Gen | lead gen, find leads, search, qualify, ICP, pipeline, scrape, competitor, prospecting, hashtag, Sales Navigator | sub/lead-gen.md | 1775908473 |
|
|
26
|
+
| Outreach | outreach, connect, DM, message, follow up, connection request, reply, warming, draft, batch | sub/outreach.md | 1775908473 |
|
|
27
|
+
| Engagement | engagement, comment warming, accept invitations, connection requests, engage-comment, connect-review, connect-send | sub/lead-magnet.md | 1775908473 |
|
|
28
|
+
| Warmup | warmup, warm up, account warmup, engagement, likes, visibility, ramp up, pre-warming | sub/warmup.md | 1775908473 |
|
|
29
|
+
| Content | content, post, publish, LinkedIn post, content strategy, draft, article, thought leadership | sub/content.md | 1775908473 |
|
|
30
|
+
| Inbox | inbox, triage, classify, archive, star, respond, unread, conversation, spam, inbox management | sub/inbox.md | 1775908473 |
|
|
31
31
|
| SDK Reference | sdk, method, parameter, script, TypeScript, generate code, automate | sdk-reference.md | 1775759685 |
|
|
32
32
|
|
|
33
33
|
### Workspace Templates
|
|
@@ -87,10 +87,10 @@ One execution processes a search/scrape or bulk operation.
|
|
|
87
87
|
|
|
88
88
|
| Task Type | What the server does |
|
|
89
89
|
|---|---|
|
|
90
|
-
|
|
|
91
|
-
|
|
|
92
|
-
|
|
|
93
|
-
|
|
|
90
|
+
| discover-search | AI-planned search strategy, LinkedIn API calls, auto-upsert contacts |
|
|
91
|
+
| connect-review | Bulk invitation acceptance with ICP filtering |
|
|
92
|
+
| engage-warm | Like, comment, view profiles for visibility |
|
|
93
|
+
| connect-grow | Connection requests for network growth |
|
|
94
94
|
| inbox-triage | Categorize unread messages, archive spam |
|
|
95
95
|
|
|
96
96
|
### Unit Tasks
|
|
@@ -98,13 +98,13 @@ One execution processes **one contact** with full, clean context. No context pol
|
|
|
98
98
|
|
|
99
99
|
| Task Type | What the server does |
|
|
100
100
|
|---|---|
|
|
101
|
-
|
|
|
102
|
-
| outreach-
|
|
103
|
-
| outreach-
|
|
104
|
-
|
|
|
105
|
-
|
|
|
106
|
-
| content-
|
|
107
|
-
| inbox-
|
|
101
|
+
| discover-qualify | Visit + score one contact against ICP |
|
|
102
|
+
| outreach-draft | Visit + draft one personalized message |
|
|
103
|
+
| outreach-reply | Smart follow-up on one reply or new connection |
|
|
104
|
+
| engage-comment | Read posts + compose one genuine comment |
|
|
105
|
+
| connect-send | Visit + send one personalized connection request |
|
|
106
|
+
| content-draft | Draft + optionally publish one LinkedIn post |
|
|
107
|
+
| inbox-reply | AI-drafted reply to one conversation |
|
|
108
108
|
|
|
109
109
|
## Campaign Lifecycle
|
|
110
110
|
|
|
@@ -132,12 +132,25 @@ Warmup campaigns can also be campaign-linked: if the warmup campaign has contact
|
|
|
132
132
|
|
|
133
133
|
| Campaign type | Task types dispatched |
|
|
134
134
|
| --- | --- |
|
|
135
|
-
| `lead_gen` | `
|
|
136
|
-
| `outreach` | `outreach-
|
|
137
|
-
| `lead_magnet` | `
|
|
138
|
-
| `warmup` | `
|
|
139
|
-
| `content` | `content-
|
|
140
|
-
| `inbox` | `inbox-triage`, `inbox-
|
|
135
|
+
| `lead_gen` | `discover-search`, `discover-visit`, `discover-qualify` |
|
|
136
|
+
| `outreach` | `discover-visit`, `discover-qualify`, `connect-send`, `outreach-draft`, `outreach-reply` |
|
|
137
|
+
| `lead_magnet` | `engage-comment`, `connect-review`, `outreach-draft`, `outreach-reply` |
|
|
138
|
+
| `warmup` | `engage-warm`, `connect-grow` |
|
|
139
|
+
| `content` | `content-draft` |
|
|
140
|
+
| `inbox` | `inbox-triage`, `inbox-reply` |
|
|
141
|
+
|
|
142
|
+
### Outreach ownership
|
|
143
|
+
|
|
144
|
+
Each contact's outreach is owned by ONE campaign (`outreachCampaignId`). This prevents cross-campaign conflicts:
|
|
145
|
+
- `connect-send` claims ownership when sending a connection request
|
|
146
|
+
- Other campaigns see the contact as "contributed" (they helped discover/qualify, but don't own outreach)
|
|
147
|
+
- Signals (connection accepted, reply received) route only to the owning campaign
|
|
148
|
+
|
|
149
|
+
### Draft mode
|
|
150
|
+
|
|
151
|
+
Each campaign has a `draftMode` setting:
|
|
152
|
+
- **review** (default): All actions create drafts for user approval. Reactive replies show as urgent drafts.
|
|
153
|
+
- **autopilot**: Drafts auto-send. Reactive replies send immediately.
|
|
141
154
|
|
|
142
155
|
## Handling Blocked Tool Calls
|
|
143
156
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
lastUpdatedAt:
|
|
2
|
+
lastUpdatedAt: 1775908473
|
|
3
3
|
---
|
|
4
4
|
|
|
5
5
|
<!--
|
|
@@ -15,7 +15,7 @@ Draft and publish LinkedIn posts from a content strategy. The agent writes value
|
|
|
15
15
|
|
|
16
16
|
## Task Types
|
|
17
17
|
|
|
18
|
-
### content-
|
|
18
|
+
### content-draft (unit, goal-driven)
|
|
19
19
|
|
|
20
20
|
Picks the next topic from the content strategy, drafts a post, and either publishes it or saves as a draft for review.
|
|
21
21
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
lastUpdatedAt:
|
|
2
|
+
lastUpdatedAt: 1775908473
|
|
3
3
|
---
|
|
4
4
|
|
|
5
5
|
<!--
|
|
@@ -9,42 +9,50 @@ lastUpdatedAt: 1775410333
|
|
|
9
9
|
Any direct edit to this file WILL be overwritten.
|
|
10
10
|
-->
|
|
11
11
|
|
|
12
|
-
# Inbox Management - Triage & Respond
|
|
12
|
+
# Inbox Management - Triage, Auto-qualify & Respond
|
|
13
13
|
|
|
14
|
-
Keep the LinkedIn inbox organized and
|
|
14
|
+
Keep the LinkedIn inbox organized and turn inbound messages into qualified pipeline. Classifies conversations, auto-qualifies potential leads against running campaign ICPs, archives spam, and responds to non-campaign messages.
|
|
15
15
|
|
|
16
16
|
## Task Types
|
|
17
17
|
|
|
18
18
|
### inbox-triage (batch, simple)
|
|
19
19
|
|
|
20
|
-
Reads
|
|
20
|
+
Reads conversations, classifies them, and auto-qualifies inbound leads.
|
|
21
21
|
|
|
22
22
|
**Classifications:**
|
|
23
|
-
- **
|
|
24
|
-
- **
|
|
25
|
-
- **
|
|
26
|
-
- **
|
|
27
|
-
- **
|
|
23
|
+
- **potential_lead**: They messaged first and match your target audience. Auto-qualified against running campaign ICPs.
|
|
24
|
+
- **needs_reply**: Genuine question, request -> needs response
|
|
25
|
+
- **informational**: Info shared, no response needed
|
|
26
|
+
- **spam**: Recruiter spam, sales pitches, bots -> mark opted_out
|
|
27
|
+
- **follow_up_later**: Warm but no immediate action
|
|
28
|
+
- **closed**: Conversation done (declined, completed)
|
|
28
29
|
|
|
29
|
-
**
|
|
30
|
+
**Inbound auto-qualify flow:**
|
|
31
|
+
When an unowned contact is classified as `potential_lead` or high-priority `needs_reply`:
|
|
32
|
+
1. Load running campaign ICPs (outreach + lead_magnet campaigns)
|
|
33
|
+
2. Match contact profile against each campaign's ICP
|
|
34
|
+
3. If score >= 60: auto-add to campaign (source: "inbound", role: "qualified")
|
|
35
|
+
4. Claim outreach ownership (`outreachCampaignId`)
|
|
36
|
+
5. Generate draft reply using the matched campaign's playbook and tone-voice
|
|
37
|
+
6. Draft appears in Drafts page with priority "urgent" and type "inbound_reply"
|
|
30
38
|
|
|
31
|
-
**Critical rule:** Never archive or modify campaign-managed conversations. The outreach-
|
|
39
|
+
**Critical rule:** Never archive or modify campaign-managed conversations. The outreach-reply task handles those.
|
|
32
40
|
|
|
33
|
-
### inbox-
|
|
41
|
+
### inbox-reply (unit, reactive)
|
|
34
42
|
|
|
35
|
-
Responds to one
|
|
43
|
+
Responds to one conversation that needs a reply. Respects campaign draftMode.
|
|
36
44
|
|
|
37
|
-
**
|
|
45
|
+
**In review mode:** Creates a draft with priority "urgent" for user approval.
|
|
46
|
+
**In autopilot mode:** Sends the reply directly.
|
|
38
47
|
|
|
39
48
|
**Response style:**
|
|
40
49
|
- Conversational and human
|
|
41
|
-
- Follows the account
|
|
50
|
+
- Follows the campaign playbook (if owned) or account tone-voice (if unowned)
|
|
42
51
|
- Concise (2-5 sentences)
|
|
43
52
|
- For lead opportunities: express interest, suggest next step
|
|
44
53
|
- For questions: answer helpfully
|
|
45
|
-
- For thank-yous: acknowledge warmly
|
|
46
54
|
|
|
47
|
-
**What it skips:** Campaign-managed contacts
|
|
55
|
+
**What it skips:** Campaign-managed contacts handled by outreach-reply.
|
|
48
56
|
|
|
49
57
|
## Campaign Setup (Interactive Mode)
|
|
50
58
|
|
|
@@ -66,8 +74,8 @@ No ICP context required. Tone-voice is strongly recommended for natural replies.
|
|
|
66
74
|
The triage and respond tasks coordinate via the star mechanism:
|
|
67
75
|
1. `inbox-triage` runs first (higher frequency: every 30 min, 8x/day)
|
|
68
76
|
2. It stars conversations that need human-like responses
|
|
69
|
-
3. `inbox-
|
|
77
|
+
3. `inbox-reply` picks up starred conversations one by one (reactive loop)
|
|
70
78
|
4. After responding, it unstars the conversation
|
|
71
79
|
5. Loop continues while `pendingItems > 0`
|
|
72
80
|
|
|
73
|
-
Priority ordering ensures inbox tasks run before warmup but after outreach-
|
|
81
|
+
Priority ordering ensures inbox tasks run before warmup but after outreach-reply (real campaign replies are always highest priority).
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: bereach-lead-gen
|
|
3
3
|
description: "Lead generation - discover leads via search/scrape (batch), qualify one contact at a time (unit)."
|
|
4
|
-
lastUpdatedAt:
|
|
4
|
+
lastUpdatedAt: 1775908473
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
<!--
|
|
@@ -19,9 +19,9 @@ lastUpdatedAt: 1775513814
|
|
|
19
19
|
|
|
20
20
|
| Task type | Mode | What it does |
|
|
21
21
|
| --- | --- | --- |
|
|
22
|
-
| `
|
|
23
|
-
| `
|
|
24
|
-
| `
|
|
22
|
+
| `discover-search` | **Batch** | Search/scrape channels, add profiles to pipeline. No visits. |
|
|
23
|
+
| `discover-visit` | **Batch** | Headline ICP exclusion (0 credits) then bulk visit matches (1 credit each). Promotes to "lead". |
|
|
24
|
+
| `discover-qualify` | **Batch** | Deep ICP scoring on leads (profile data already populated). Promotes to "qualified". |
|
|
25
25
|
|
|
26
26
|
## Discovery (Batch Mode)
|
|
27
27
|
|
|
@@ -137,8 +137,8 @@ bereach_contacts_update_campaign({
|
|
|
137
137
|
dailyTarget: 50, // daily goal (leads/day)
|
|
138
138
|
totalTarget: 500, // total campaign goal
|
|
139
139
|
taskOverrides: {
|
|
140
|
-
"
|
|
141
|
-
"
|
|
140
|
+
"discover-visit": { maxRunsPerDay: 20, minIntervalMinutes: 5 },
|
|
141
|
+
"discover-qualify": { maxRunsPerDay: 15, defaultBatchSize: 3 }
|
|
142
142
|
}
|
|
143
143
|
})
|
|
144
144
|
```
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: bereach-engagement
|
|
3
3
|
description: "Engagement warming - comment on one contact's post (unit), accept ICP invitations (batch), connect with one contact (unit)."
|
|
4
|
-
lastUpdatedAt:
|
|
4
|
+
lastUpdatedAt: 1775908473
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
<!--
|
|
@@ -36,11 +36,21 @@ When a user pastes a post URL and asks for an update:
|
|
|
36
36
|
|
|
37
37
|
| Task type | Mode | What it does |
|
|
38
38
|
| --- | --- | --- |
|
|
39
|
-
| `
|
|
40
|
-
| `
|
|
41
|
-
| `
|
|
39
|
+
| `engage-comment` | **Unit** | Read ONE contact's posts, compose ONE genuine comment. Stores commentUrn for thread follow-ups. |
|
|
40
|
+
| `connect-review` | **Batch** | List pending invitations, fast-check against ICP, accept matches. |
|
|
41
|
+
| `outreach-draft` | **Unit** | Draft ONE DM for a warmed contact. Can reply to comment threads before DM'ing (via reply_to_comment tool). |
|
|
42
|
+
| `outreach-reply` | **Unit** | Handle ONE reply to a DM. Classify intent + respond. |
|
|
42
43
|
|
|
43
|
-
|
|
44
|
+
**Note:** `connect-send` is NOT in the lead_magnet recipe by default. The pipeline is: comment (warm) -> DM (deliver value). Connection is optional - add it via multi-type (`enabledTypes: ["lead_magnet", "outreach"]`).
|
|
45
|
+
|
|
46
|
+
### Lead magnet pipeline
|
|
47
|
+
|
|
48
|
+
1. **engage-comment** runs first (goal-driven: N comments per day) - builds rapport
|
|
49
|
+
2. After warming period, **outreach-draft** picks up contacts with engagement history - drafts DM referencing the comment exchange
|
|
50
|
+
3. **outreach-reply** handles responses (reactive chain)
|
|
51
|
+
4. **connect-review** handles incoming invitations from warmed contacts
|
|
52
|
+
|
|
53
|
+
## engage-comment — Comment on ONE Contact's Post
|
|
44
54
|
|
|
45
55
|
You receive one contact (contactId or URL). Read their posts and leave one genuine comment.
|
|
46
56
|
|
|
@@ -72,7 +82,7 @@ You receive one contact (contactId or URL). Read their posts and leave one genui
|
|
|
72
82
|
}
|
|
73
83
|
```
|
|
74
84
|
|
|
75
|
-
##
|
|
85
|
+
## connect-review — Accept ICP Invitations (Batch)
|
|
76
86
|
|
|
77
87
|
One agent call processes all pending invitations. Fast-checking headlines is lightweight.
|
|
78
88
|
|
|
@@ -102,7 +112,7 @@ One agent call processes all pending invitations. Fast-checking headlines is lig
|
|
|
102
112
|
}
|
|
103
113
|
```
|
|
104
114
|
|
|
105
|
-
##
|
|
115
|
+
## connect-send — Connect with ONE Contact
|
|
106
116
|
|
|
107
117
|
You receive one contact (contactId or URL). Visit and send a personalized connection request.
|
|
108
118
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: bereach-outreach
|
|
3
3
|
description: "LinkedIn outreach - draft one message per contact (unit), handle one reply per contact (unit). No bulk loops."
|
|
4
|
-
lastUpdatedAt:
|
|
4
|
+
lastUpdatedAt: 1775908473
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
<!--
|
|
@@ -19,12 +19,12 @@ lastUpdatedAt: 1775161435
|
|
|
19
19
|
|
|
20
20
|
| Task type | Mode | What it does |
|
|
21
21
|
| --- | --- | --- |
|
|
22
|
-
| `outreach-
|
|
23
|
-
| `outreach-
|
|
22
|
+
| `outreach-draft` | **Unit** | Visit + create ONE draft for one contact. User reviews before sending. |
|
|
23
|
+
| `outreach-reply` | **Unit** | Handle ONE reply or ONE new connection. Send immediately. |
|
|
24
24
|
|
|
25
25
|
Each task processes exactly one contact with clean, isolated context. The platform dispatches one task per contact.
|
|
26
26
|
|
|
27
|
-
## outreach-
|
|
27
|
+
## outreach-draft — Draft ONE Message
|
|
28
28
|
|
|
29
29
|
You receive one contact (contactId or URL) in the task prompt. Create a draft — never send directly.
|
|
30
30
|
|
|
@@ -53,7 +53,7 @@ You receive one contact (contactId or URL) in the task prompt. Create a draft
|
|
|
53
53
|
}
|
|
54
54
|
```
|
|
55
55
|
|
|
56
|
-
## outreach-
|
|
56
|
+
## outreach-reply — Handle ONE Conversation
|
|
57
57
|
|
|
58
58
|
You receive one contact in the task prompt. Send immediately — this person is waiting.
|
|
59
59
|
|
|
@@ -93,15 +93,26 @@ You receive one contact in the task prompt. Send immediately — this person is
|
|
|
93
93
|
|
|
94
94
|
## Status-Driven Decision Tree
|
|
95
95
|
|
|
96
|
-
| `outreachStatus` | Task | Action |
|
|
96
|
+
| `outreachStatus` | Owner? | Task | Action |
|
|
97
|
+
| --- | --- | --- | --- |
|
|
98
|
+
| `none` | No owner | connect-send | Claim ownership + send connection request |
|
|
99
|
+
| `none` (memberDistance=1) | No owner | connect-send | Already connected - claim + advance to "connected" |
|
|
100
|
+
| `connection_sent` | This campaign | — | Skip (polling detects acceptance) |
|
|
101
|
+
| `connected` | This campaign | outreach-reply | Icebreaker (draft in review mode, send in autopilot) |
|
|
102
|
+
| `dm_sent` | This campaign | outreach-draft | Draft follow-up (different angle) |
|
|
103
|
+
| `replied` | This campaign | outreach-reply | Classify + respond (urgent draft or direct send) |
|
|
104
|
+
| `in_conversation` | This campaign | outreach-reply | Continue conversation |
|
|
105
|
+
| `none` | Other campaign | — | Skip (role = "contributed") |
|
|
106
|
+
| Terminal | This campaign | — | Clear owner, contact available for re-engagement |
|
|
107
|
+
|
|
108
|
+
### Draft mode behavior
|
|
109
|
+
|
|
110
|
+
| Task | review mode | autopilot mode |
|
|
97
111
|
| --- | --- | --- |
|
|
98
|
-
| `
|
|
99
|
-
| `
|
|
100
|
-
| `
|
|
101
|
-
| `
|
|
102
|
-
| `replied` | outreach-reactive | Classify + respond immediately |
|
|
103
|
-
| `in_conversation` | outreach-reactive | Continue conversation |
|
|
104
|
-
| `meeting_booked` / `not_interested` | — | Terminal states |
|
|
112
|
+
| `connect-send` | Draft connection note | Send connection directly |
|
|
113
|
+
| `outreach-reply` (icebreaker) | Urgent draft + notification | Send immediately |
|
|
114
|
+
| `outreach-reply` (reply) | Urgent draft + notification | Send immediately |
|
|
115
|
+
| `outreach-draft` (follow-up) | Draft for review | Auto-schedule |
|
|
105
116
|
|
|
106
117
|
## Delivery Modes
|
|
107
118
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
lastUpdatedAt:
|
|
2
|
+
lastUpdatedAt: 1775908473
|
|
3
3
|
---
|
|
4
4
|
|
|
5
5
|
<!--
|
|
@@ -30,7 +30,7 @@ Warmup works in two modes depending on the campaign:
|
|
|
30
30
|
|
|
31
31
|
## Task Types
|
|
32
32
|
|
|
33
|
-
###
|
|
33
|
+
### engage-warm (batch, goal-driven)
|
|
34
34
|
|
|
35
35
|
Engages naturally on LinkedIn through likes, comments, and profile views.
|
|
36
36
|
|
|
@@ -45,7 +45,7 @@ Engages naturally on LinkedIn through likes, comments, and profile views.
|
|
|
45
45
|
|
|
46
46
|
**What it does NOT do:** Send DMs, send connection requests, qualify contacts, change lifecycle stages. Enforcement blocks these actions at the tool level.
|
|
47
47
|
|
|
48
|
-
###
|
|
48
|
+
### connect-grow (batch, simple)
|
|
49
49
|
|
|
50
50
|
Accepts pending connection invitations and views profiles.
|
|
51
51
|
|
package/src/index.ts
CHANGED
|
@@ -10,9 +10,10 @@ import { createSessionState, type PluginConfig } from "./hooks/types";
|
|
|
10
10
|
import { errMsg, createLogger } from "./hooks/utils";
|
|
11
11
|
import { autoDetectModels } from "./auto-detect-models";
|
|
12
12
|
import { randomBytes } from "node:crypto";
|
|
13
|
-
import { readFileSync, writeFileSync, existsSync } from "node:fs";
|
|
13
|
+
import { readFileSync, writeFileSync, existsSync, unlinkSync } from "node:fs";
|
|
14
14
|
import { join } from "node:path";
|
|
15
15
|
import { homedir } from "node:os";
|
|
16
|
+
import { spawn } from "node:child_process";
|
|
16
17
|
import { readEnv } from "./env";
|
|
17
18
|
|
|
18
19
|
const log = createLogger("init");
|
|
@@ -149,10 +150,30 @@ export default function register(api: any) {
|
|
|
149
150
|
}
|
|
150
151
|
|
|
151
152
|
// If hooks were just written to config, the gateway must restart to load them.
|
|
152
|
-
//
|
|
153
|
+
// Spawn a detached process that waits, cleans lock/pid, then restarts the gateway.
|
|
153
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
|
+
|
|
154
176
|
setTimeout(() => {
|
|
155
|
-
log("hooks config written — exiting so the gateway restarts with hooks enabled");
|
|
156
177
|
process.exit(0);
|
|
157
178
|
}, 3000);
|
|
158
179
|
}
|