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.
@@ -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-batch",
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: "warmup-engage" });
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: "warmup-engage" });
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: "warmup-network" });
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
- "lead-gen-discovery",
103
- "lead-gen-visit",
104
- "lead-gen-qualify",
105
- "outreach-batch",
106
- "outreach-reactive",
107
- "lm-comments",
108
- "lm-connections",
109
- "lm-invitations",
110
- "warmup-engage",
111
- "warmup-network",
112
- "content-create",
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-respond",
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-batch includes scheduled message but not send_message", () => {
145
- const wl = getTaskToolWhitelist("outreach-batch")!;
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-reactive includes send_message for live replies", () => {
151
- const wl = getTaskToolWhitelist("outreach-reactive")!;
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("warmup-engage does NOT include send_message or connect_profile", () => {
156
- const wl = getTaskToolWhitelist("warmup-engage")!;
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("lead-gen-discovery includes search tools", () => {
162
- const wl = getTaskToolWhitelist("lead-gen-discovery")!;
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-create includes publish_post", () => {
169
- const wl = getTaskToolWhitelist("content-create")!;
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-respond includes send_message for replies", () => {
174
- const wl = getTaskToolWhitelist("inbox-respond")!;
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-batch");
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-batch")!;
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("lead-gen-qualify")!;
199
- const arr = getTaskToolNames("lead-gen-qualify")!;
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. 'lead-gen-visit', 'lead-gen-qualify'). Values are config objects with: maxRunsPerDay, minIntervalMinutes, defaultBatchSize, maxCreditsPerRun.",
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. ['lead-gen-discovery']). Use when strategically pausing a task type (low conversion, enough leads). Set to [] to re-enable all.",
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-batch, lead-gen-qualify, lm-comments)." },
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
- if (!taskMode.taskType.startsWith("warmup-")) return null;
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
- "lead-gen-discovery": [
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
- "lead-gen-visit": [
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
- "lead-gen-qualify": [
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-batch": [
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-reactive": [
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
- "lm-comments": [
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
- "lm-connections": [
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
- "lm-invitations": [
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
- "warmup-engage": [
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
- "warmup-network": [
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-create": [
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-respond": [
124
+ "inbox-reply": [
125
125
  "bereach_list_conversations",
126
126
  "bereach_get_messages",
127
127
  "bereach_send_message",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "bereach-openclaw",
3
3
  "name": "BeReach",
4
- "version": "1.6.1",
4
+ "version": "1.6.2",
5
5
  "description": "LinkedIn outreach automation — 75+ tools, hook-based enforcement, dynamic context",
6
6
  "configSchema": {
7
7
  "type": "object",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bereach-openclaw",
3
- "version": "1.6.1",
3
+ "version": "1.6.2",
4
4
  "description": "BeReach LinkedIn automation plugin for OpenClaw",
5
5
  "license": "AGPL-3.0",
6
6
  "exports": {
@@ -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: 1775833744
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 | 1775513814 |
26
- | Outreach | outreach, connect, DM, message, follow up, connection request, reply, warming, draft, batch | sub/outreach.md | 1775161435 |
27
- | Engagement | engagement, comment warming, accept invitations, connection requests, lm-comments, lm-invitations, lm-connections | sub/lead-magnet.md | 1775236488 |
28
- | Warmup | warmup, warm up, account warmup, engagement, likes, visibility, ramp up, pre-warming | sub/warmup.md | 1775410333 |
29
- | Content | content, post, publish, LinkedIn post, content strategy, draft, article, thought leadership | sub/content.md | 1775410333 |
30
- | Inbox | inbox, triage, classify, archive, star, respond, unread, conversation, spam, inbox management | sub/inbox.md | 1775410333 |
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
- | lead-gen-discovery | AI-planned search strategy, LinkedIn API calls, auto-upsert contacts |
91
- | lm-invitations | Bulk invitation acceptance with ICP filtering |
92
- | warmup-engage | Like, comment, view profiles for visibility |
93
- | warmup-network | Connection requests for network growth |
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
- | lead-gen-qualify | Visit + score one contact against ICP |
102
- | outreach-batch | Visit + draft one personalized message |
103
- | outreach-reactive | Smart follow-up on one reply or new connection |
104
- | lm-comments | Read posts + compose one genuine comment |
105
- | lm-connections | Visit + send one personalized connection request |
106
- | content-create | Draft + optionally publish one LinkedIn post |
107
- | inbox-respond | AI-drafted reply to one conversation |
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` | `lead-gen-discovery`, `lead-gen-qualify` |
136
- | `outreach` | `outreach-reactive`, `outreach-batch` |
137
- | `lead_magnet` | `lm-comments`, `lm-invitations`, `lm-connections` |
138
- | `warmup` | `warmup-engage`, `warmup-network` |
139
- | `content` | `content-create` |
140
- | `inbox` | `inbox-triage`, `inbox-respond` |
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: 1775410333
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-create (unit, goal-driven)
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: 1775410333
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 responsive without manual effort. Classifies conversations, archives spam, stars opportunities, and responds to non-campaign messages.
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 unread conversations, classifies them, and takes organizational actions.
20
+ Reads conversations, classifies them, and auto-qualifies inbound leads.
21
21
 
22
22
  **Classifications:**
23
- - **lead-opportunity**: Inbound interest, warm lead -> star
24
- - **needs-response**: Genuine question, thank you, simple request -> star
25
- - **spam**: Recruiter spam, sales pitches, bots -> archive
26
- - **irrelevant**: Newsletters, group messages, dead threads -> mark seen
27
- - **campaign-managed**: Contact managed by an active campaign -> skip entirely
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
- **Tools used:** `bereach_list_conversations`, `bereach_get_messages`, `bereach_star_conversation`, `bereach_archive_conversation`, `bereach_mark_seen`, `bereach_contacts_get_by_url`
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-reactive task handles those.
39
+ **Critical rule:** Never archive or modify campaign-managed conversations. The outreach-reply task handles those.
32
40
 
33
- ### inbox-respond (unit, reactive)
41
+ ### inbox-reply (unit, reactive)
34
42
 
35
- Responds to one starred conversation that needs a reply. Processes conversations flagged by inbox-triage.
43
+ Responds to one conversation that needs a reply. Respects campaign draftMode.
36
44
 
37
- **Tools used:** `bereach_list_conversations`, `bereach_get_messages`, `bereach_send_message`, `bereach_star_conversation`, `bereach_contacts_get_by_url`, `bereach_get_conversation_summary`
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 owner's tone-voice context
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 (unstar and move on).
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-respond` picks up starred conversations one by one (reactive loop)
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-reactive (real campaign replies are always highest priority).
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: 1775513814
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
- | `lead-gen-discovery` | **Batch** | Search/scrape channels, add profiles to pipeline. No visits. |
23
- | `lead-gen-visit` | **Batch** | Headline ICP exclusion (0 credits) then bulk visit matches (1 credit each). Promotes to "lead". |
24
- | `lead-gen-qualify` | **Batch** | Deep ICP scoring on leads (profile data already populated). Promotes to "qualified". |
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
- "lead-gen-visit": { maxRunsPerDay: 20, minIntervalMinutes: 5 },
141
- "lead-gen-qualify": { maxRunsPerDay: 15, defaultBatchSize: 3 }
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: 1775236488
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
- | `lm-comments` | **Unit** | Read ONE contact's posts, compose ONE genuine comment. |
40
- | `lm-invitations` | **Batch** | List pending invitations, fast-check against ICP, accept matches. |
41
- | `lm-connections` | **Unit** | Visit ONE contact, send ONE personalized connection request. |
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
- ## lm-comments Comment on ONE Contact's Post
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
- ## lm-invitations — Accept ICP Invitations (Batch)
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
- ## lm-connections — Connect with ONE Contact
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: 1775161435
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-batch` | **Unit** | Visit + create ONE draft for one contact. User reviews before sending. |
23
- | `outreach-reactive` | **Unit** | Handle ONE reply or ONE new connection. Send immediately. |
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-batch — Draft ONE Message
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-reactive — Handle ONE Conversation
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
- | `none` | outreach-batch | Draft connection request |
99
- | `connection_sent` | | Skip (platform checks acceptance) |
100
- | `connected` | outreach-reactive | Send icebreaker immediately |
101
- | `dm_sent` | outreach-batch | Draft follow-up (if due) |
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: 1775410333
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
- ### warmup-engage (batch, goal-driven)
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
- ### warmup-network (batch, simple)
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
- // Exit the process so Docker/systemd restarts the gateway with the updated config.
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
  }