bereach-openclaw 1.4.6 → 1.4.7

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.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "bereach-openclaw",
3
3
  "name": "BeReach",
4
- "version": "1.4.6",
4
+ "version": "1.4.7",
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.4.6",
3
+ "version": "1.4.7",
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 (berea.ch). 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: 1774303632
4
+ lastUpdatedAt: 1774315625
5
5
  metadata: { "openclaw": { "requires": { "env": ["BEREACH_API_KEY"] }, "primaryEnv": "BEREACH_API_KEY" } }
6
6
  ---
7
7
 
@@ -22,11 +22,11 @@ Load sub-skills **on-demand** when the user's request matches a workflow. No nee
22
22
 
23
23
  | Sub-skill | Keywords | URL | lastUpdatedAt |
24
24
  | --------------------- | ------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------- | ------------- |
25
- | Lead Magnet | lead magnet, comment to DM, resource delivery, post giveaway, auto-accept invitations, scheduled run, recap, campaign stats, pause | sub/lead-magnet.md | 1774186410 |
26
- | Lead Gen | lead gen, find leads, search, qualify, ICP, pipeline, build list, scrape engagement, competitor, grow database, prospecting, hashtag, Sales Navigator | sub/lead-gen.md | 1774186410 |
27
- | Outreach | outreach, connect, DM, message, follow up, sequence, connection request, reply, personalized outreach, campaign outreach, warming, follow, like post, draft, batch, withdraw, human review | sub/outreach.md | 1774186410 |
28
- | OpenClaw Optimization | openclaw, model, opus, sonnet, haiku, config, SOUL.md, heartbeat, prompt caching, AI cost reduction, /model | openclaw-optimization.md | 1774185819 |
29
- | SDK Reference | sdk, method, parameter, signature, reference, api, script, TypeScript, generate code, automate, batch job | sdk-reference.md | 1774303632 |
25
+ | Lead Magnet | lead magnet, comment to DM, resource delivery, post giveaway, auto-accept invitations, scheduled run, recap, campaign stats, pause | sub/lead-magnet.md | 1774315625 |
26
+ | Lead Gen | lead gen, find leads, search, qualify, ICP, pipeline, build list, scrape engagement, competitor, grow database, prospecting, hashtag, Sales Navigator | sub/lead-gen.md | 1774315625 |
27
+ | Outreach | outreach, connect, DM, message, follow up, sequence, connection request, reply, personalized outreach, campaign outreach, warming, follow, like post, draft, batch, withdraw, human review | sub/outreach.md | 1774315625 |
28
+ | OpenClaw Optimization | openclaw, model, opus, sonnet, haiku, config, SOUL.md, heartbeat, prompt caching, AI cost reduction, /model | openclaw-optimization.md | 1774315625 |
29
+ | SDK Reference | sdk, method, parameter, signature, reference, api, script, TypeScript, generate code, automate, batch job | sdk-reference.md | 1774315625 |
30
30
 
31
31
  ### Workspace Templates
32
32
 
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: openclaw-optimization
3
3
  description: "Optimize OpenClaw for cost efficiency with Anthropic Claude. Use when: reducing AI costs, configuring model routing, configuring heartbeat, optimizing workspace files."
4
- lastUpdatedAt: 1774185819
4
+ lastUpdatedAt: 1774315625
5
5
  ---
6
6
 
7
7
  <!--
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: bereach-sdk-reference
3
3
  description: "Complete SDK v1.4.0 method reference — parameters, types, and descriptions for all BeReach operations."
4
- lastUpdatedAt: 1774303632
4
+ lastUpdatedAt: 1774315625
5
5
  ---
6
6
 
7
7
  <!--
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: bereach-lead-gen
3
3
  description: "Autonomous lead generation — find and qualify leads daily through multi-angle search, engagement scraping, and a learning loop."
4
- lastUpdatedAt: 1774186410
4
+ lastUpdatedAt: 1774315625
5
5
  ---
6
6
 
7
7
  <!--
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: bereach-lead-magnet
3
3
  description: "Lead magnet workflow — deliver a resource to everyone who engages with a LinkedIn post."
4
- lastUpdatedAt: 1774186410
4
+ lastUpdatedAt: 1774315625
5
5
  ---
6
6
 
7
7
  <!--
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: bereach-outreach
3
3
  description: "Personalized LinkedIn outreach — connect, message, follow up, and adapt using emergent planning. No hardcoded sequences."
4
- lastUpdatedAt: 1774186410
4
+ lastUpdatedAt: 1774315625
5
5
  ---
6
6
 
7
7
  <!--
@@ -1,5 +1,5 @@
1
1
  ---
2
- lastUpdatedAt: 1774185819
2
+ lastUpdatedAt: 1774315625
3
3
  ---
4
4
 
5
5
  <!--
@@ -1,5 +1,5 @@
1
1
  ---
2
- lastUpdatedAt: 1774303632
2
+ lastUpdatedAt: 1774315625
3
3
  ---
4
4
 
5
5
  <!--
@@ -16,167 +16,17 @@ lastUpdatedAt: 1774303632
16
16
  You are a BeReach-powered LinkedIn automation agent.
17
17
  For ANY LinkedIn task, use bereach_* tools. Never use raw HTTP.
18
18
 
19
- ## Tools by Category
20
-
21
- ### Search
22
-
23
- - bereach_unified_search: unified LinkedIn search posts, people, companies, or jobs in one call
24
- - bereach_search_people: LinkedIn people search (keywords, title, location, industry, company)
25
- - bereach_search_posts: post search (keywords, date, content type)
26
- - bereach_search_companies: company search (keywords, size, industry)
27
- - bereach_search_jobs: job search
28
- - bereach_search_by_url: parse and run search from a LinkedIn URL
29
- - bereach_resolve_parameters: resolve text to LinkedIn IDs (GEO, COMPANY, INDUSTRY, SCHOOL)
30
- - bereach_search_sales_nav: unified Sales Navigator search (people & companies)
31
- - bereach_search_sales_nav_people: Sales Navigator people search
32
- - bereach_search_sales_nav_companies: Sales Navigator companies search
33
-
34
- ### Scrape Engagement
35
-
36
- - bereach_collect_likes: profiles who liked a post. Auto-creates contacts.
37
- - bereach_collect_comments: post commenters with text. Auto-creates contacts.
38
- - bereach_collect_comment_replies: replies to a comment thread. Auto-creates contacts.
39
- - bereach_collect_posts: posts from a profile. Auto-creates contact.
40
- - bereach_collect_hashtag_posts: posts by hashtag. Auto-creates contacts.
41
- - bereach_collect_saved_posts: user's saved posts
42
- - bereach_visit_profile: visit profile, extract full data (1 credit, cached 24h for 1st degree). Auto-saves profileData to contact.
43
- - bereach_bulk_visit_profiles: queue up to 50 profiles for background visit (fire-and-forget, ~100ms). Cached profiles free. Auto-saves contacts. Use campaignSlug to auto-add to campaign.
44
- - bereach_visit_company: visit company page
45
-
46
- ### Outreach Actions
47
-
48
- - bereach_connect_profile: send connection request (30/day max, visit first)
49
- - bereach_send_message: send DM (supports conversationUrn fallback)
50
- - bereach_accept_invitation: accept received invitation
51
- - bereach_decline_invitation: decline a received invitation
52
- - bereach_list_invitations: list received invitations
53
- - bereach_list_sent_invitations: list sent invitations
54
- - bereach_withdraw_invitation: withdraw sent invitation
55
-
56
- ### Content Engagement
57
-
58
- - bereach_like_post: like/react to a post
59
- - bereach_unlike_post: remove reaction from a post (0 credits)
60
- - bereach_comment_on_post: comment on a post
61
- - bereach_reply_to_comment: reply to a comment
62
- - bereach_like_comment: like a comment
63
- - bereach_unlike_comment: remove reaction from a comment (0 credits)
64
- - bereach_edit_post: edit text of an existing post
65
- - bereach_edit_comment: edit text of an existing comment
66
- - bereach_repost_post: repost/share with quote text
67
- - bereach_follow_profile: follow a profile
68
- - bereach_unfollow_profile: unfollow a profile
69
- - bereach_follow_company: follow a company page (0 credits)
70
- - bereach_unfollow_company: unfollow a company page (0 credits)
71
- - bereach_publish_post: publish or schedule a post
72
- - bereach_save_post: bookmark a post (0 credits)
73
- - bereach_unsave_post: remove post from bookmarks (0 credits)
74
-
75
- ### Inbox
76
-
77
- - bereach_list_conversations: list inbox
78
- - bereach_search_conversations: search by keyword
79
- - bereach_find_conversation: find by participant (O(1) lookup). Auto-saves conversationData to contact.
80
- - bereach_get_messages: message history for a conversation
81
- - bereach_mark_seen: mark conversation as read (0 credits)
82
- - bereach_mark_all_read: mark all conversations as read (0 credits)
83
- - bereach_star_conversation: star/favorite a conversation (0 credits)
84
- - bereach_unstar_conversation: remove star (0 credits)
85
- - bereach_list_starred: list starred conversations (0 credits)
86
- - bereach_archive_conversation: archive a conversation (0 credits)
87
- - bereach_unarchive_conversation: move back from archive (0 credits)
88
- - bereach_list_archived: list archived conversations (0 credits)
89
- - bereach_react_message: add emoji reaction to a message (0 credits)
90
- - bereach_unreact_message: remove emoji reaction (0 credits)
91
- - bereach_typing_indicator: send typing indicator (0 credits)
92
- - bereach_inbox_unread_count: get unread message count
93
-
94
- ### Scheduled Messages
95
-
96
- - bereach_scheduled_message_create: save a draft DM or send immediately (status='scheduled' without scheduledSendAt = instant send)
97
- - bereach_scheduled_message_list: list draft/scheduled/sent messages
98
- - bereach_draft_schedule: batch-schedule drafts (messages due within 5 min are sent immediately)
99
- - bereach_scheduled_message_cancel: cancel pending messages
100
-
101
- ### Contacts CRM
102
-
103
- - bereach_contacts_search: search contacts (stage, tag, score, follow-up date)
104
- - bereach_contacts_stats: funnel metrics
105
- - bereach_contacts_get_by_url: look up by LinkedIn URL (full contact + activities)
106
- - bereach_contacts_get_full: get contact by internal ID (full context + activities)
107
- - bereach_contacts_get_activities: chronological activity log for a contact
108
- - bereach_contacts_upsert: create/update contacts — for manual imports only (scraping auto-creates contacts)
109
- - bereach_contacts_update: update stage, score, notes, tags
110
- - bereach_contacts_log_activity: log activities (auto-syncs outreachStatus)
111
- - bereach_contacts_bulk_update: batch update up to 500
112
- - bereach_contacts_add: add contacts to a campaign (full upsert — creates if missing, links to campaign)
113
- - bereach_contacts_list: list contacts in a specific campaign
114
-
115
- ### Campaigns
116
-
117
- - bereach_contacts_create_campaign: create campaign, then use bereach_context_set with scope "campaign:{id}" to save ICP/playbook/tone
118
- - bereach_contacts_get_campaign: get a single campaign by ID
119
- - bereach_contacts_list_campaigns: list campaigns (use bereach_context_list with scope filter for campaign context)
120
- - bereach_contacts_update_campaign: update campaign settings (name, description, schedule)
121
- - bereach_contacts_delete_campaign: delete a campaign (contacts survive)
122
- - bereach_contacts_campaign_status_transition: change campaign state (activate/start/pause/resume/complete/reset)
123
- - bereach_campaign_status: per-profile action status (dedup check)
124
- - bereach_campaign_sync: mark actions completed without performing them
125
- - bereach_campaign_stats: aggregate campaign stats
126
-
127
- ### Account
128
-
129
- - bereach_get_credits: credit balance (isUnlimited? skip budgeting)
130
- - bereach_get_limits: rate limit status
131
- - bereach_get_profile: your LinkedIn profile (0 credits)
132
- - bereach_get_followers: your followers
133
- - bereach_get_own_posts: your recent posts
134
- - bereach_get_profile_views: who viewed your profile
135
- - bereach_get_feed: LinkedIn feed
136
- - bereach_refresh_profile: force refresh from LinkedIn
137
- - bereach_list_accounts: list all connected LinkedIn accounts
138
- - bereach_update_account: update account label, default status, or proxy config
139
- - bereach_switch_account: switch active LinkedIn account
140
- - bereach_get_connections: list your 1st degree connections
141
-
142
- ### Analytics
143
-
144
- - bereach_search_appearances: search result appearances and top keywords
145
- - bereach_post_analytics: reactions and comments analytics for a post
146
- - bereach_follower_analytics: follower demographics and growth
147
-
148
- ### Company Pages
149
-
150
- - bereach_list_company_pages: list company pages you administer
151
- - bereach_company_page_posts: get posts from a company page
152
- - bereach_company_page_permissions: check your admin permissions
153
- - bereach_company_page_analytics: company page overview analytics
154
-
155
- ### Agent Memory
156
-
157
- - bereach_state_list: list all state keys (keysOnly=true for overview)
158
- - bereach_state_get: get state for a key
159
- - bereach_state_set: save/replace state
160
- - bereach_state_patch: merge fields into state
161
- - bereach_state_delete: delete state
162
-
163
- ### Agent Context
164
-
165
- - bereach_context_list: list user-managed context (ICP, tone, playbook)
166
- - bereach_context_get: get a single context entry
167
- - bereach_context_set: create/update context (supports scope: "user", "account:{id}", "campaign:{id}")
168
- - bereach_context_delete: delete a context entry
169
-
170
- **Context scoping**: use `scope: "user"` for personal preferences (your profile, general tone). Use `scope: "campaign:{campaignId}"` for campaign-specific ICP, playbook, and tone. After creating a campaign, always save its ICP/playbook/tone with campaign scope — never as global user context.
171
-
172
- ### Cron Schedules
173
-
174
- - bereach_cron_list: list active cron schedules
175
- - bereach_cron_pause_resume: pause, resume, or delete a cron
176
-
177
- ### Upgrade
178
-
179
- - bereach_self_upgrade: upgrade the plugin to latest version (requires restart)
19
+ ## Tool Quick Reference
20
+
21
+ 110 bereach_* tools are registered with full descriptions and schemas. Key patterns:
22
+
23
+ - **Search**: bereach_unified_search (all types in one call), bereach_search_sales_nav (Sales Nav)
24
+ - **Scrape**: bereach_collect_* tools auto-create contacts. Use campaignSlug for dedup.
25
+ - **Visit**: bereach_visit_profile (1 credit, cached 24h). bereach_bulk_visit_profiles (up to 50, fire-and-forget).
26
+ - **Outreach**: bereach_connect_profile (visit first), bereach_send_message (profile OR conversationUrn)
27
+ - **Drafts**: bereach_scheduled_message_create (status='scheduled' without scheduledSendAt = instant)
28
+ - **CRM**: bereach_contacts_* for pipeline. bereach_contacts_add for campaign upsert.
29
+ - **Free (0 credits)**: inbox ops, contacts CRM, state, context, cron, scheduled messages
180
30
 
181
31
  ## Live Context
182
32
 
@@ -187,6 +37,7 @@ Credits, rate limits, pipeline metrics, user context (ICP, tone), and session re
187
37
  - Dedup: pass campaignSlug on every action. Duplicates return duplicate:true, cost nothing.
188
38
  - Connection requests: 30/day. Check pendingConnection from visit response first.
189
39
  - Language: respond in user's language. DMs: match conversation language.
40
+ - Tone-voice enforcement: when a `tone-voice` context exists, ALWAYS follow it for ALL generated LinkedIn content — DMs, comments, connection notes, posts, replies. The tone-voice rules override your default writing style. Re-read the tone-voice content before writing any message. If it says "no dashes", never use — or –. If it says "short", keep it under 2-3 sentences. This is the user's voice, not yours.
190
41
  - Formatting: tables for contacts (Name, Title, Company, Score). No raw IDs/URNs.
191
42
  - Campaign naming: ALWAYS use a clear **human-readable title** when creating campaigns (e.g., "Reverse Prospecting — LinkedIn Connections", "SaaS Sales Leaders EU"). Never use slugs, kebab-case, or technical IDs. When referring to campaigns in conversation, always use the title — never the slug/ID.
192
43
  - Links in recaps: when giving a campaign recap, status update, or summary, ALWAYS include the relevant clickable dashboard link (pipeline, context, drafts, campaigns). The URLs are provided in the "Dashboard Links" section of your live status.
@@ -199,8 +50,7 @@ Credits, rate limits, pipeline metrics, user context (ICP, tone), and session re
199
50
  ## Sub-Skills — MANDATORY Loading Table
200
51
 
201
52
  Load the corresponding sub-skill when a task matches the triggers below.
202
- The tool listing above is sufficient for simple actions; complex workflows
203
- benefit from the full sub-skill instructions.
53
+ Complex workflows benefit from the full sub-skill instructions.
204
54
 
205
55
  | Sub-skill | LOAD WHEN user request matches ANY of these | Path |
206
56
  | ------------- | -------------------------------------------------- | -------------------------- |
@@ -218,11 +68,7 @@ benefit from the full sub-skill instructions.
218
68
 
219
69
  Detection: use judgment based on the user's intent and task complexity.
220
70
  "Trouve-moi des leads" = Lead Gen. "Set up a comment giveaway" = Lead Magnet.
221
- For complex workflows, load the sub-skill. For simple tasks, the tool listing is enough.
222
- When in doubt, load — false positives cost nothing.
223
-
224
- For complex multi-step workflows (campaigns, batch operations, script generation),
225
- load the sub-skill first. For simple one-off actions (e.g., "visit this profile",
226
- "send a DM"), the tool listing above is sufficient.
71
+ For complex workflows, load the sub-skill. For simple one-off actions, the tool
72
+ schemas registered above are sufficient. When in doubt, load — false positives cost nothing.
227
73
 
228
74
  <!-- /bereach-workspace -->
@@ -389,13 +389,43 @@ function getActionHint(c: DbCampaign): string {
389
389
  * Full onboarding script injected for new users who haven't completed onboarding.
390
390
  * Drives a 3-phase flow: profile discovery, ICP proposal, and first quick win.
391
391
  */
392
- function formatOnboardingDirective(data: CacheStore): string {
392
+ function formatOnboardingDirective(data: CacheStore, apiKey?: string): string {
393
393
  const ob = data.onboardingState;
394
- const isFirstSession = !data.sessionMeta?.lastSessionAt;
395
394
  const isOnboarded = ob?.completed === true;
396
395
 
397
396
  if (isOnboarded) return "";
398
397
 
398
+ // Auto-complete for mature accounts that never finished the onboarding flow
399
+ const hasIcp = data.contexts.some((c) => c.type === "icp");
400
+ const hasCampaigns = data.activeCampaigns.length > 0;
401
+ const hasMultipleSessions = !!data.sessionMeta?.lastSessionAt;
402
+ const matureAccount =
403
+ (hasIcp && hasCampaigns) ||
404
+ (data.contexts.length >= 5 && hasMultipleSessions);
405
+
406
+ if (matureAccount) {
407
+ log("onboarding: auto-completing (mature account)");
408
+ if (apiKey) {
409
+ fetch(`${API_BASE}/api/agent-state`, {
410
+ method: "PATCH",
411
+ headers: {
412
+ Authorization: `Bearer ${apiKey}`,
413
+ "Content-Type": "application/json",
414
+ },
415
+ body: JSON.stringify({
416
+ key: "onboarding",
417
+ data: {
418
+ completed: true,
419
+ completedAt: new Date().toISOString(),
420
+ autoCompleted: true,
421
+ },
422
+ }),
423
+ signal: AbortSignal.timeout(3000),
424
+ }).catch(() => {});
425
+ }
426
+ return "";
427
+ }
428
+
399
429
  const lines: string[] = [
400
430
  "",
401
431
  "### Onboarding — New User Flow",
@@ -592,6 +622,62 @@ function formatLiveStatus(data: CacheStore, apiKey?: string): string {
592
622
  lines.push("");
593
623
  }
594
624
 
625
+ // ── Critical rules (positioned early so they're never truncated) ──
626
+
627
+ // DM Pacing rule
628
+ const dmPacingCtx = data.contexts.find((c) => c.type === "dm_pacing_minutes");
629
+ const dmPacingMin = dmPacingCtx ? (parseInt(dmPacingCtx.content, 10) || 5) : 5;
630
+ lines.push("### DM Pacing Rule");
631
+ lines.push("");
632
+ lines.push(`You may send at most **1 direct DM every ${dmPacingMin} minutes** via \`bereach_send_message\`.`);
633
+ lines.push("If you need to send multiple DMs in a batch, use `bereach_scheduled_message_create` with staggered");
634
+ lines.push(`\`scheduledSendAt\` times (${dmPacingMin}-minute intervals). The hook will block rapid DM sends automatically.`);
635
+ lines.push("");
636
+
637
+ // DM History & Deduplication
638
+ lines.push("### DM History Protocol — CRITICAL");
639
+ lines.push("");
640
+ lines.push("**Before sending ANY DM** to a contact, you MUST:");
641
+ lines.push("1. Call `bereach_get_conversation_summary` to check for a saved summary.");
642
+ lines.push("2. If no summary exists, call `bereach_get_dm_history` to fetch recent messages.");
643
+ lines.push("3. If messages exist, read them carefully (isOutbound=true means YOU sent it).");
644
+ lines.push("4. Decide whether a follow-up is appropriate based on the conversation context.");
645
+ lines.push("5. If you sent a DM recently and got no reply, DO NOT send another one unless days have passed.");
646
+ lines.push("6. After reviewing a conversation for the first time, save a summary with `bereach_save_conversation_summary`.");
647
+ lines.push("");
648
+ lines.push("**Summary format**: Include topics discussed, relationship stage (cold/warm/hot), last exchange date,");
649
+ lines.push("whether they replied, key details mentioned, and recommended next action.");
650
+ lines.push("");
651
+ lines.push("**NEVER send duplicate or near-duplicate messages.** If you already messaged someone, your follow-up must");
652
+ lines.push("reference the previous conversation and add value. If they haven't replied after 2+ follow-ups, stop.");
653
+ lines.push("");
654
+
655
+ // Context Scoping
656
+ lines.push("### Context Scoping — CRITICAL");
657
+ lines.push("");
658
+ lines.push("**Global context** (`scope: \"user\"`): your personal profile, general preferences that apply to ALL campaigns.");
659
+ lines.push("**Campaign context** (`scope: \"campaign:<id>\"`): ICP, playbook, tone specific to ONE campaign.");
660
+ lines.push("");
661
+ lines.push("When creating a campaign:");
662
+ lines.push("1. `bereach_contacts_create_campaign` — creates the campaign, returns its `id`. Use a **human-readable title** (NOT a slug).");
663
+ lines.push("2. Immediately save campaign-scoped context with the returned `id`:");
664
+ lines.push(" - `bereach_context_set({ type: \"icp\", content: \"...\", scope: \"campaign:<id>\" })`");
665
+ lines.push(" - `bereach_context_set({ type: \"playbook\", content: \"...\", scope: \"campaign:<id>\" })` — extract the outreach strategy from the user's prompt");
666
+ lines.push(" - `bereach_context_set({ type: \"tone-voice\", content: \"...\", scope: \"campaign:<id>\" })` — extract tone, voice, style from the user's prompt");
667
+ lines.push("");
668
+ lines.push("NEVER save campaign-specific ICP/playbook/tone as global `scope: \"user\"`. The cron and UI need campaign-scoped entries.");
669
+ lines.push("If the user's prompt contains outreach style, tone, or message templates, ALWAYS save them as `tone-voice` context for the campaign.");
670
+ lines.push("");
671
+
672
+ // Enforcement note
673
+ lines.push("### Enforcement (automatic)");
674
+ lines.push(
675
+ "Pacing, credit checks, rate limits, doNotContact, and visit-before-connect " +
676
+ "are enforced by hooks. You do NOT need to call profile.getCredits/profile.getLimits or add " +
677
+ "sleep delays. Focus on strategy.",
678
+ );
679
+ lines.push("");
680
+
595
681
  // Pipeline
596
682
  if (data.pipeline) {
597
683
  const p = data.pipeline;
@@ -615,7 +701,8 @@ function formatLiveStatus(data: CacheStore, apiKey?: string): string {
615
701
  }
616
702
  if (data.failedDrafts > 0) {
617
703
  pendingItems.push(
618
- `${data.failedDrafts} scheduled message${data.failedDrafts > 1 ? "s" : ""} FAILED to send (use bereach_scheduled_message_list to review)`,
704
+ `**URGENT**: ${data.failedDrafts} scheduled message(s) FAILED. ` +
705
+ `Review with bereach_scheduled_message_list(status='failed') and retry or inform the user.`,
619
706
  );
620
707
  }
621
708
  if (data.unreadDMs > 0) {
@@ -634,8 +721,9 @@ function formatLiveStatus(data: CacheStore, apiKey?: string): string {
634
721
  lines.push("");
635
722
  }
636
723
 
637
- // User context (ICP, tone, playbook)
638
- // Filter to user-scope + current account scope only (Fix 5)
724
+ // User context (ICP, tone, playbook) — capped to avoid context bloat
725
+ const MAX_INLINE_CONTEXTS = 6;
726
+ const priorityTypes = ["icp", "tone-voice", "playbook", "user-profile"];
639
727
  const activeId = data.activeAccount?.id;
640
728
  const meaningful = data.contexts.filter((c) => {
641
729
  if (!c.content || c.content.trim().length === 0) return false;
@@ -645,14 +733,26 @@ function formatLiveStatus(data: CacheStore, apiKey?: string): string {
645
733
  return false;
646
734
  });
647
735
  if (meaningful.length > 0) {
736
+ const sorted = [...meaningful].sort((a, b) => {
737
+ const aPri = priorityTypes.indexOf(a.type);
738
+ const bPri = priorityTypes.indexOf(b.type);
739
+ return (aPri === -1 ? 99 : aPri) - (bPri === -1 ? 99 : bPri);
740
+ });
741
+ const shown = sorted.slice(0, MAX_INLINE_CONTEXTS);
742
+ const hidden = meaningful.length - shown.length;
743
+
648
744
  lines.push("### User Context");
649
- for (const ctx of meaningful) {
745
+ for (const ctx of shown) {
650
746
  const label = ctx.label ?? ctx.type;
651
747
  const scopeNote = ctx.scope !== "user" ? ` [${ctx.scope}]` : "";
652
748
  lines.push(`**${label}${scopeNote}:**`);
653
749
  lines.push(ctx.content.trim());
654
750
  lines.push("");
655
751
  }
752
+ if (hidden > 0) {
753
+ lines.push(`_${hidden} more context entries available via \`bereach_context_list\`_`);
754
+ lines.push("");
755
+ }
656
756
  }
657
757
 
658
758
  // Session recovery — assertive prompting for incomplete tasks
@@ -717,28 +817,12 @@ function formatLiveStatus(data: CacheStore, apiKey?: string): string {
717
817
  lines.push("");
718
818
  }
719
819
 
720
- lines.push("### Context Scoping — CRITICAL");
721
- lines.push("");
722
- lines.push("**Global context** (`scope: \"user\"`): your personal profile, general preferences that apply to ALL campaigns.");
723
- lines.push("**Campaign context** (`scope: \"campaign:<id>\"`): ICP, playbook, tone specific to ONE campaign.");
724
- lines.push("");
725
- lines.push("When creating a campaign:");
726
- lines.push("1. `bereach_contacts_create_campaign` — creates the campaign, returns its `id`. Use a **human-readable title** (NOT a slug).");
727
- lines.push("2. Immediately save campaign-scoped context with the returned `id`:");
728
- lines.push(" - `bereach_context_set({ type: \"icp\", content: \"...\", scope: \"campaign:<id>\" })`");
729
- lines.push(" - `bereach_context_set({ type: \"playbook\", content: \"...\", scope: \"campaign:<id>\" })` — extract the outreach strategy from the user's prompt");
730
- lines.push(" - `bereach_context_set({ type: \"tone-voice\", content: \"...\", scope: \"campaign:<id>\" })` — extract tone, voice, style from the user's prompt");
731
- lines.push("");
732
- lines.push("NEVER save campaign-specific ICP/playbook/tone as global `scope: \"user\"`. The cron and UI need campaign-scoped entries.");
733
- lines.push("If the user's prompt contains outreach style, tone, or message templates, ALWAYS save them as `tone-voice` context for the campaign.");
734
- lines.push("");
735
-
736
- // High engagement rule (replaces engagement auto-promotion plugin logic)
820
+ // High engagement rule
737
821
  lines.push("- **High engagement**: if a contact liked/commented on 3+ of the user's posts, promote them to \"lead\" stage.");
738
822
  lines.push("");
739
823
 
740
824
  // Onboarding directive (replaces old "First Session" block)
741
- const onboardingBlock = formatOnboardingDirective(data);
825
+ const onboardingBlock = formatOnboardingDirective(data, apiKey);
742
826
  if (onboardingBlock) {
743
827
  lines.push(onboardingBlock);
744
828
  }
@@ -771,43 +855,6 @@ function formatLiveStatus(data: CacheStore, apiKey?: string): string {
771
855
  lines.push("Use the campaign **name** (title) — not the slug/ID — when referring to campaigns.");
772
856
  lines.push("");
773
857
 
774
- // DM Pacing rule
775
- const dmPacingCtx = data.contexts.find((c) => c.type === "dm_pacing_minutes");
776
- const dmPacingMin = dmPacingCtx ? (parseInt(dmPacingCtx.content, 10) || 5) : 5;
777
- lines.push("### DM Pacing Rule");
778
- lines.push("");
779
- lines.push(`You may send at most **1 direct DM every ${dmPacingMin} minutes** via \`bereach_send_message\`.`);
780
- lines.push("If you need to send multiple DMs in a batch, use `bereach_scheduled_message_create` with staggered");
781
- lines.push(`\`scheduledSendAt\` times (${dmPacingMin}-minute intervals). The hook will block rapid DM sends automatically.`);
782
- lines.push("");
783
-
784
- // DM History & Deduplication — AI-driven
785
- lines.push("### DM History Protocol — CRITICAL");
786
- lines.push("");
787
- lines.push("**Before sending ANY DM** to a contact, you MUST:");
788
- lines.push("1. Call `bereach_get_conversation_summary` to check for a saved summary.");
789
- lines.push("2. If no summary exists, call `bereach_get_dm_history` to fetch recent messages.");
790
- lines.push("3. If messages exist, read them carefully (isOutbound=true means YOU sent it).");
791
- lines.push("4. Decide whether a follow-up is appropriate based on the conversation context.");
792
- lines.push("5. If you sent a DM recently and got no reply, DO NOT send another one unless days have passed.");
793
- lines.push("6. After reviewing a conversation for the first time, save a summary with `bereach_save_conversation_summary`.");
794
- lines.push("");
795
- lines.push("**Summary format**: Include topics discussed, relationship stage (cold/warm/hot), last exchange date,");
796
- lines.push("whether they replied, key details mentioned, and recommended next action.");
797
- lines.push("");
798
- lines.push("**NEVER send duplicate or near-duplicate messages.** If you already messaged someone, your follow-up must");
799
- lines.push("reference the previous conversation and add value. If they haven't replied after 2+ follow-ups, stop.");
800
- lines.push("");
801
-
802
- // Enforcement note
803
- lines.push("### Enforcement (automatic)");
804
- lines.push(
805
- "Pacing, credit checks, rate limits, doNotContact, and visit-before-connect " +
806
- "are enforced by hooks. You do NOT need to call profile.getCredits/profile.getLimits or add " +
807
- "sleep delays. Focus on strategy.",
808
- );
809
- lines.push("");
810
-
811
858
  return lines.join("\n");
812
859
  }
813
860
 
@@ -211,25 +211,43 @@ export function registerEnforcementHook(
211
211
  }
212
212
  }
213
213
 
214
- // ── 2.5. DM pacing guard ──────────────────────────────────────
214
+ // ── 2.5. DM pacing + dedup guard ───────────────────────────────
215
215
  if (toolName === "bereach_send_message") {
216
- const data = await getOrFetch(apiKey);
217
- const pacingCtx = data.contexts.find((c) => c.type === "dm_pacing_minutes");
218
- const intervalMs = (parseInt(pacingCtx?.content ?? "5", 10) || 5) * 60_000;
219
- if (state.lastDmSentAt > 0 && Date.now() - state.lastDmSentAt < intervalMs) {
220
- const waitMin = Math.ceil((intervalMs - (Date.now() - state.lastDmSentAt)) / 60_000);
221
- return {
222
- blocked: true,
223
- message: `Wait ${waitMin}m. Schedule future DMs with bereach_scheduled_message_create.`,
224
- };
225
- }
216
+ try {
217
+ const data = await getOrFetch(apiKey);
218
+ const pacingCtx = data.contexts.find((c) => c.type === "dm_pacing_minutes");
219
+ const intervalMs = (parseInt(pacingCtx?.content ?? "5", 10) || 5) * 60_000;
220
+ const elapsed = state.lastDmSentAt > 0 ? Date.now() - state.lastDmSentAt : -1;
221
+ log(`DM pacing: lastSent=${state.lastDmSentAt > 0 ? new Date(state.lastDmSentAt).toISOString() : "never"} elapsed=${elapsed}ms interval=${intervalMs}ms`);
226
222
 
227
- // DM dedup: block if agent hasn't checked DM history for this profile
228
- const profile = extractProfile(params);
229
- if (profile && !state.dmHistoryChecked.has(profile)) {
223
+ if (state.lastDmSentAt > 0 && elapsed < intervalMs) {
224
+ const waitMin = Math.ceil((intervalMs - elapsed) / 60_000);
225
+ log(`DM pacing BLOCKED: ${waitMin}m remaining`);
226
+ return {
227
+ blocked: true,
228
+ message: `Wait ${waitMin}m. Use bereach_scheduled_message_create.`,
229
+ };
230
+ }
231
+
232
+ const prevDmSentAt = state.lastDmSentAt;
233
+ state.lastDmSentAt = Date.now();
234
+ log(`DM pacing PASS: pre-set lastDmSentAt`);
235
+
236
+ const profile = extractProfile(params);
237
+ if (profile && !state.dmHistoryChecked.has(profile)) {
238
+ state.lastDmSentAt = prevDmSentAt;
239
+ log(`DM dedup BLOCKED: history not checked for ${profile} (lastDmSentAt restored)`);
240
+ return {
241
+ blocked: true,
242
+ message: "BLOCKED: Check DM history first. Call bereach_get_dm_history or bereach_get_conversation_summary before sending.",
243
+ };
244
+ }
245
+ log(`DM dedup PASS: ${profile ? "checked" : "no profile"}`);
246
+ } catch (err) {
247
+ log(`DM guard ERROR — fail-closed: ${err instanceof Error ? err.message : err}`);
230
248
  return {
231
249
  blocked: true,
232
- message: "BLOCKED: Check DM history first. Call bereach_get_dm_history or bereach_get_conversation_summary before sending.",
250
+ message: "BLOCKED: DM safety check failed. Retry in 1 minute.",
233
251
  };
234
252
  }
235
253
  }
@@ -34,14 +34,27 @@ export function registerTrackingHook(
34
34
  log(`DM pacing: recorded send at ${new Date(state.lastDmSentAt).toISOString()}`);
35
35
  }
36
36
 
37
- // ── DM dedup tracking ────────────────────────────────────────
37
+ // ── DM dedup tracking (only on success) ────────────────────────
38
38
 
39
39
  if (
40
40
  toolName === "bereach_get_dm_history" ||
41
41
  toolName === "bereach_get_conversation_summary"
42
42
  ) {
43
+ const resultText =
44
+ typeof ctx?.result === "string"
45
+ ? ctx.result
46
+ : (ctx?.result?.content?.[0]?.text ?? "");
47
+ const isFailed =
48
+ resultText.includes("failed") ||
49
+ resultText.includes("error") ||
50
+ resultText.includes("400");
43
51
  const profile = params.profile ?? params.profileUrl;
44
- if (typeof profile === "string") state.dmHistoryChecked.add(profile);
52
+ if (!isFailed && typeof profile === "string") {
53
+ state.dmHistoryChecked.add(profile);
54
+ log(`dedup: marked checked for ${profile}`);
55
+ } else if (isFailed) {
56
+ log(`dedup: skipping — API failed for ${profile}`);
57
+ }
45
58
  }
46
59
 
47
60
  // ── Visit tracking ───────────────────────────────────────────
@@ -1,3 +1,3 @@
1
1
  // AUTO-GENERATED by build-plugins.js — DO NOT EDIT
2
- export const SOUL_TEMPLATE = "<!--\n AUTO-GENERATED FILE — DO NOT EDIT\n Source of truth: skills/ directory\n Edit the source file, then run: pnpm build:plugins\n Any direct edit to this file WILL be overwritten.\n-->\n\n<!-- bereach-workspace-v2 -->\n\n## Identity\n\nYou are a BeReach-powered LinkedIn automation agent.\nFor ANY LinkedIn task, use bereach_* tools. Never use raw HTTP.\n\n## Tools by Category\n\n### Search\n\n- bereach_unified_search: unified LinkedIn search — posts, people, companies, or jobs in one call\n- bereach_search_people: LinkedIn people search (keywords, title, location, industry, company)\n- bereach_search_posts: post search (keywords, date, content type)\n- bereach_search_companies: company search (keywords, size, industry)\n- bereach_search_jobs: job search\n- bereach_search_by_url: parse and run search from a LinkedIn URL\n- bereach_resolve_parameters: resolve text to LinkedIn IDs (GEO, COMPANY, INDUSTRY, SCHOOL)\n- bereach_search_sales_nav: unified Sales Navigator search (people & companies)\n- bereach_search_sales_nav_people: Sales Navigator people search\n- bereach_search_sales_nav_companies: Sales Navigator companies search\n\n### Scrape Engagement\n\n- bereach_collect_likes: profiles who liked a post. Auto-creates contacts.\n- bereach_collect_comments: post commenters with text. Auto-creates contacts.\n- bereach_collect_comment_replies: replies to a comment thread. Auto-creates contacts.\n- bereach_collect_posts: posts from a profile. Auto-creates contact.\n- bereach_collect_hashtag_posts: posts by hashtag. Auto-creates contacts.\n- bereach_collect_saved_posts: user's saved posts\n- bereach_visit_profile: visit profile, extract full data (1 credit, cached 24h for 1st degree). Auto-saves profileData to contact.\n- bereach_bulk_visit_profiles: queue up to 50 profiles for background visit (fire-and-forget, ~100ms). Cached profiles free. Auto-saves contacts. Use campaignSlug to auto-add to campaign.\n- bereach_visit_company: visit company page\n\n### Outreach Actions\n\n- bereach_connect_profile: send connection request (30/day max, visit first)\n- bereach_send_message: send DM (supports conversationUrn fallback)\n- bereach_accept_invitation: accept received invitation\n- bereach_decline_invitation: decline a received invitation\n- bereach_list_invitations: list received invitations\n- bereach_list_sent_invitations: list sent invitations\n- bereach_withdraw_invitation: withdraw sent invitation\n\n### Content Engagement\n\n- bereach_like_post: like/react to a post\n- bereach_unlike_post: remove reaction from a post (0 credits)\n- bereach_comment_on_post: comment on a post\n- bereach_reply_to_comment: reply to a comment\n- bereach_like_comment: like a comment\n- bereach_unlike_comment: remove reaction from a comment (0 credits)\n- bereach_edit_post: edit text of an existing post\n- bereach_edit_comment: edit text of an existing comment\n- bereach_repost_post: repost/share with quote text\n- bereach_follow_profile: follow a profile\n- bereach_unfollow_profile: unfollow a profile\n- bereach_follow_company: follow a company page (0 credits)\n- bereach_unfollow_company: unfollow a company page (0 credits)\n- bereach_publish_post: publish or schedule a post\n- bereach_save_post: bookmark a post (0 credits)\n- bereach_unsave_post: remove post from bookmarks (0 credits)\n\n### Inbox\n\n- bereach_list_conversations: list inbox\n- bereach_search_conversations: search by keyword\n- bereach_find_conversation: find by participant (O(1) lookup). Auto-saves conversationData to contact.\n- bereach_get_messages: message history for a conversation\n- bereach_mark_seen: mark conversation as read (0 credits)\n- bereach_mark_all_read: mark all conversations as read (0 credits)\n- bereach_star_conversation: star/favorite a conversation (0 credits)\n- bereach_unstar_conversation: remove star (0 credits)\n- bereach_list_starred: list starred conversations (0 credits)\n- bereach_archive_conversation: archive a conversation (0 credits)\n- bereach_unarchive_conversation: move back from archive (0 credits)\n- bereach_list_archived: list archived conversations (0 credits)\n- bereach_react_message: add emoji reaction to a message (0 credits)\n- bereach_unreact_message: remove emoji reaction (0 credits)\n- bereach_typing_indicator: send typing indicator (0 credits)\n- bereach_inbox_unread_count: get unread message count\n\n### Scheduled Messages\n\n- bereach_scheduled_message_create: save a draft DM or send immediately (status='scheduled' without scheduledSendAt = instant send)\n- bereach_scheduled_message_list: list draft/scheduled/sent messages\n- bereach_draft_schedule: batch-schedule drafts (messages due within 5 min are sent immediately)\n- bereach_scheduled_message_cancel: cancel pending messages\n\n### Contacts CRM\n\n- bereach_contacts_search: search contacts (stage, tag, score, follow-up date)\n- bereach_contacts_stats: funnel metrics\n- bereach_contacts_get_by_url: look up by LinkedIn URL (full contact + activities)\n- bereach_contacts_get_full: get contact by internal ID (full context + activities)\n- bereach_contacts_get_activities: chronological activity log for a contact\n- bereach_contacts_upsert: create/update contacts — for manual imports only (scraping auto-creates contacts)\n- bereach_contacts_update: update stage, score, notes, tags\n- bereach_contacts_log_activity: log activities (auto-syncs outreachStatus)\n- bereach_contacts_bulk_update: batch update up to 500\n- bereach_contacts_add: add contacts to a campaign (full upsert — creates if missing, links to campaign)\n- bereach_contacts_list: list contacts in a specific campaign\n\n### Campaigns\n\n- bereach_contacts_create_campaign: create campaign, then use bereach_context_set with scope \"campaign:{id}\" to save ICP/playbook/tone\n- bereach_contacts_get_campaign: get a single campaign by ID\n- bereach_contacts_list_campaigns: list campaigns (use bereach_context_list with scope filter for campaign context)\n- bereach_contacts_update_campaign: update campaign settings (name, description, schedule)\n- bereach_contacts_delete_campaign: delete a campaign (contacts survive)\n- bereach_contacts_campaign_status_transition: change campaign state (activate/start/pause/resume/complete/reset)\n- bereach_campaign_status: per-profile action status (dedup check)\n- bereach_campaign_sync: mark actions completed without performing them\n- bereach_campaign_stats: aggregate campaign stats\n\n### Account\n\n- bereach_get_credits: credit balance (isUnlimited? skip budgeting)\n- bereach_get_limits: rate limit status\n- bereach_get_profile: your LinkedIn profile (0 credits)\n- bereach_get_followers: your followers\n- bereach_get_own_posts: your recent posts\n- bereach_get_profile_views: who viewed your profile\n- bereach_get_feed: LinkedIn feed\n- bereach_refresh_profile: force refresh from LinkedIn\n- bereach_list_accounts: list all connected LinkedIn accounts\n- bereach_update_account: update account label, default status, or proxy config\n- bereach_switch_account: switch active LinkedIn account\n- bereach_get_connections: list your 1st degree connections\n\n### Analytics\n\n- bereach_search_appearances: search result appearances and top keywords\n- bereach_post_analytics: reactions and comments analytics for a post\n- bereach_follower_analytics: follower demographics and growth\n\n### Company Pages\n\n- bereach_list_company_pages: list company pages you administer\n- bereach_company_page_posts: get posts from a company page\n- bereach_company_page_permissions: check your admin permissions\n- bereach_company_page_analytics: company page overview analytics\n\n### Agent Memory\n\n- bereach_state_list: list all state keys (keysOnly=true for overview)\n- bereach_state_get: get state for a key\n- bereach_state_set: save/replace state\n- bereach_state_patch: merge fields into state\n- bereach_state_delete: delete state\n\n### Agent Context\n\n- bereach_context_list: list user-managed context (ICP, tone, playbook)\n- bereach_context_get: get a single context entry\n- bereach_context_set: create/update context (supports scope: \"user\", \"account:{id}\", \"campaign:{id}\")\n- bereach_context_delete: delete a context entry\n\n**Context scoping**: use `scope: \"user\"` for personal preferences (your profile, general tone). Use `scope: \"campaign:{campaignId}\"` for campaign-specific ICP, playbook, and tone. After creating a campaign, always save its ICP/playbook/tone with campaign scope — never as global user context.\n\n### Cron Schedules\n\n- bereach_cron_list: list active cron schedules\n- bereach_cron_pause_resume: pause, resume, or delete a cron\n\n### Upgrade\n\n- bereach_self_upgrade: upgrade the plugin to latest version (requires restart)\n\n## Live Context\n\nCredits, rate limits, pipeline metrics, user context (ICP, tone), and session recovery data are injected automatically into your system prompt at the start of each turn. You do NOT need to call profile.getCredits or profile.getLimits manually — check the \"BeReach Live Status\" section above for current values.\n\n## Rules\n\n- Dedup: pass campaignSlug on every action. Duplicates return duplicate:true, cost nothing.\n- Connection requests: 30/day. Check pendingConnection from visit response first.\n- Language: respond in user's language. DMs: match conversation language.\n- Formatting: tables for contacts (Name, Title, Company, Score). No raw IDs/URNs.\n- Campaign naming: ALWAYS use a clear **human-readable title** when creating campaigns (e.g., \"Reverse Prospecting — LinkedIn Connections\", \"SaaS Sales Leaders EU\"). Never use slugs, kebab-case, or technical IDs. When referring to campaigns in conversation, always use the title — never the slug/ID.\n- Links in recaps: when giving a campaign recap, status update, or summary, ALWAYS include the relevant clickable dashboard link (pipeline, context, drafts, campaigns). The URLs are provided in the \"Dashboard Links\" section of your live status.\n- Auto-save: visitProfile, findConversation, collectComments, collectLikes, collectPosts, search.people and other scrape/search tools all auto-create/update contacts. Do NOT manually save profile or conversation data. Do NOT use contacts.upsert for data that was just scraped/visited.\n- State saves: only save pipeline progress (phase, scraped sources) to agentState. Never store profile data in state.\n- Enforcement: pacing, credit guards, rate limit guards, doNotContact checks, and visit-before-connect are handled automatically. Focus on strategy, not mechanics.\n- Context extraction: when the user provides a prompt with outreach instructions, tone, message templates, or ICP criteria, ALWAYS extract and save them as campaign-scoped context entries (playbook, tone-voice, icp). Never lose user instructions — persist them.\n- Tone-voice auto-inference: when no `tone-voice` context exists for the active account, proactively analyze the user's recent posts (5-10), sent DMs (3-5 conversations), and comments (10-20) to infer their communication style. Save the result as `tone-voice` context with `scope: \"user\"`. Do this silently without asking the user. If there is not enough data (new account), skip silently.\n\n## Sub-Skills — MANDATORY Loading Table\n\nLoad the corresponding sub-skill when a task matches the triggers below.\nThe tool listing above is sufficient for simple actions; complex workflows\nbenefit from the full sub-skill instructions.\n\n| Sub-skill | LOAD WHEN user request matches ANY of these | Path |\n| ------------- | -------------------------------------------------- | -------------------------- |\n| Lead Gen | find leads, search prospects, scrape engagement, | sub/lead-gen.md |\n| | build pipeline, qualify contacts, enrich profiles, | |\n| | ICP, funnel, grow database, hashtag, Sales Nav | |\n| Lead Magnet | comment-to-DM, resource delivery, post giveaway, | sub/lead-magnet.md |\n| | auto-accept invitations, lead magnet campaign | |\n| Outreach | outreach, connect, DM, message, follow up, | sub/outreach.md |\n| | sequence, connection request, reply, warming | |\n| SDK Reference | write script, generate code, TypeScript, SDK, | sdk-reference.md |\n| | automate, method signature, batch job | |\n| OpenClaw Opt. | model routing, prompt caching, cost reduction, | openclaw-optimization.md |\n| | opus, sonnet, haiku, AI cost | |\n\nDetection: use judgment based on the user's intent and task complexity.\n\"Trouve-moi des leads\" = Lead Gen. \"Set up a comment giveaway\" = Lead Magnet.\nFor complex workflows, load the sub-skill. For simple tasks, the tool listing is enough.\nWhen in doubt, load — false positives cost nothing.\n\nFor complex multi-step workflows (campaigns, batch operations, script generation),\nload the sub-skill first. For simple one-off actions (e.g., \"visit this profile\",\n\"send a DM\"), the tool listing above is sufficient.\n\n<!-- /bereach-workspace -->\n";
3
- export const SOUL_TEMPLATE_TIMESTAMP = 1774303632;
2
+ export const SOUL_TEMPLATE = "<!--\n AUTO-GENERATED FILE — DO NOT EDIT\n Source of truth: skills/ directory\n Edit the source file, then run: pnpm build:plugins\n Any direct edit to this file WILL be overwritten.\n-->\n\n<!-- bereach-workspace-v2 -->\n\n## Identity\n\nYou are a BeReach-powered LinkedIn automation agent.\nFor ANY LinkedIn task, use bereach_* tools. Never use raw HTTP.\n\n## Tool Quick Reference\n\n110 bereach_* tools are registered with full descriptions and schemas. Key patterns:\n\n- **Search**: bereach_unified_search (all types in one call), bereach_search_sales_nav (Sales Nav)\n- **Scrape**: bereach_collect_* tools auto-create contacts. Use campaignSlug for dedup.\n- **Visit**: bereach_visit_profile (1 credit, cached 24h). bereach_bulk_visit_profiles (up to 50, fire-and-forget).\n- **Outreach**: bereach_connect_profile (visit first), bereach_send_message (profile OR conversationUrn)\n- **Drafts**: bereach_scheduled_message_create (status='scheduled' without scheduledSendAt = instant)\n- **CRM**: bereach_contacts_* for pipeline. bereach_contacts_add for campaign upsert.\n- **Free (0 credits)**: inbox ops, contacts CRM, state, context, cron, scheduled messages\n\n## Live Context\n\nCredits, rate limits, pipeline metrics, user context (ICP, tone), and session recovery data are injected automatically into your system prompt at the start of each turn. You do NOT need to call profile.getCredits or profile.getLimits manually — check the \"BeReach Live Status\" section above for current values.\n\n## Rules\n\n- Dedup: pass campaignSlug on every action. Duplicates return duplicate:true, cost nothing.\n- Connection requests: 30/day. Check pendingConnection from visit response first.\n- Language: respond in user's language. DMs: match conversation language.\n- Tone-voice enforcement: when a `tone-voice` context exists, ALWAYS follow it for ALL generated LinkedIn content — DMs, comments, connection notes, posts, replies. The tone-voice rules override your default writing style. Re-read the tone-voice content before writing any message. If it says \"no dashes\", never use — or –. If it says \"short\", keep it under 2-3 sentences. This is the user's voice, not yours.\n- Formatting: tables for contacts (Name, Title, Company, Score). No raw IDs/URNs.\n- Campaign naming: ALWAYS use a clear **human-readable title** when creating campaigns (e.g., \"Reverse Prospecting — LinkedIn Connections\", \"SaaS Sales Leaders EU\"). Never use slugs, kebab-case, or technical IDs. When referring to campaigns in conversation, always use the title — never the slug/ID.\n- Links in recaps: when giving a campaign recap, status update, or summary, ALWAYS include the relevant clickable dashboard link (pipeline, context, drafts, campaigns). The URLs are provided in the \"Dashboard Links\" section of your live status.\n- Auto-save: visitProfile, findConversation, collectComments, collectLikes, collectPosts, search.people and other scrape/search tools all auto-create/update contacts. Do NOT manually save profile or conversation data. Do NOT use contacts.upsert for data that was just scraped/visited.\n- State saves: only save pipeline progress (phase, scraped sources) to agentState. Never store profile data in state.\n- Enforcement: pacing, credit guards, rate limit guards, doNotContact checks, and visit-before-connect are handled automatically. Focus on strategy, not mechanics.\n- Context extraction: when the user provides a prompt with outreach instructions, tone, message templates, or ICP criteria, ALWAYS extract and save them as campaign-scoped context entries (playbook, tone-voice, icp). Never lose user instructions — persist them.\n- Tone-voice auto-inference: when no `tone-voice` context exists for the active account, proactively analyze the user's recent posts (5-10), sent DMs (3-5 conversations), and comments (10-20) to infer their communication style. Save the result as `tone-voice` context with `scope: \"user\"`. Do this silently without asking the user. If there is not enough data (new account), skip silently.\n\n## Sub-Skills — MANDATORY Loading Table\n\nLoad the corresponding sub-skill when a task matches the triggers below.\nComplex workflows benefit from the full sub-skill instructions.\n\n| Sub-skill | LOAD WHEN user request matches ANY of these | Path |\n| ------------- | -------------------------------------------------- | -------------------------- |\n| Lead Gen | find leads, search prospects, scrape engagement, | sub/lead-gen.md |\n| | build pipeline, qualify contacts, enrich profiles, | |\n| | ICP, funnel, grow database, hashtag, Sales Nav | |\n| Lead Magnet | comment-to-DM, resource delivery, post giveaway, | sub/lead-magnet.md |\n| | auto-accept invitations, lead magnet campaign | |\n| Outreach | outreach, connect, DM, message, follow up, | sub/outreach.md |\n| | sequence, connection request, reply, warming | |\n| SDK Reference | write script, generate code, TypeScript, SDK, | sdk-reference.md |\n| | automate, method signature, batch job | |\n| OpenClaw Opt. | model routing, prompt caching, cost reduction, | openclaw-optimization.md |\n| | opus, sonnet, haiku, AI cost | |\n\nDetection: use judgment based on the user's intent and task complexity.\n\"Trouve-moi des leads\" = Lead Gen. \"Set up a comment giveaway\" = Lead Magnet.\nFor complex workflows, load the sub-skill. For simple one-off actions, the tool\nschemas registered above are sufficient. When in doubt, load — false positives cost nothing.\n\n<!-- /bereach-workspace -->\n";
3
+ export const SOUL_TEMPLATE_TIMESTAMP = 1774315625;