bereach-openclaw 1.5.4 → 1.5.6
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/README.md +6 -3
- package/node_modules/@bereach/tools/src/definitions.ts +1 -0
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/src/commands/connector.ts +44 -19
- package/src/hooks/context.ts +4 -4
- package/src/hooks/lifecycle.ts +22 -2
- package/src/index.ts +97 -1
- package/src/tools/index.ts +1 -0
package/README.md
CHANGED
|
@@ -21,8 +21,11 @@ openclaw plugins install bereach-openclaw
|
|
|
21
21
|
**Before upgrading:** note your `BEREACH_API_KEY` - uninstall may remove `plugins.entries.bereach-openclaw`.
|
|
22
22
|
|
|
23
23
|
```bash
|
|
24
|
-
# 1. Uninstall
|
|
24
|
+
# 1. Uninstall + fully delete (rm prevents stale .old copies causing 401 loops)
|
|
25
25
|
openclaw plugins uninstall bereach-openclaw
|
|
26
|
+
rm -rf /data/.openclaw/extensions/bereach-openclaw*
|
|
27
|
+
rm -rf /data/.openclaw/node_modules/bereach-openclaw
|
|
28
|
+
rm -rf /data/.openclaw/node_modules/bereach
|
|
26
29
|
|
|
27
30
|
# 2. Reinstall latest
|
|
28
31
|
openclaw plugins install bereach-openclaw
|
|
@@ -108,9 +111,9 @@ OpenClaw loads plugins from **two locations**:
|
|
|
108
111
|
|
|
109
112
|
If `extensions/bereach-openclaw/` is corrupt or incomplete, you get trim/undefined errors. Fix:
|
|
110
113
|
|
|
111
|
-
**1.
|
|
114
|
+
**1. Fully remove the active extension** (do NOT rename to `.bak` - OpenClaw loads `.bak`/`.old` copies as duplicate plugins):
|
|
112
115
|
```bash
|
|
113
|
-
|
|
116
|
+
rm -rf /data/.openclaw/extensions/bereach-openclaw*
|
|
114
117
|
```
|
|
115
118
|
|
|
116
119
|
**2. Reinstall from npm (inside the container):**
|
|
@@ -1428,6 +1428,7 @@ export const definitions: ToolDefinition[] = [
|
|
|
1428
1428
|
lifecycleStage: { type: "string", enum: ["contact", "lead", "qualified", "approved", "rejected"], description: "New stage (forward-only, or rejected)." },
|
|
1429
1429
|
hotScore: { type: "integer", minimum: 0, maximum: 100 },
|
|
1430
1430
|
qualificationNotes: { type: "string", description: "Agent reasoning for qualification/rejection." },
|
|
1431
|
+
leadBrief: { type: "string", description: "2-3 sentence human-readable lead summary for sales prep. NOT ICP analysis — a cheat sheet: who this person is, what they care about, notable background, conversation hooks." },
|
|
1431
1432
|
notes: { type: "string" },
|
|
1432
1433
|
name: { type: "string", description: "Update contact name." },
|
|
1433
1434
|
profileData: { type: "object", description: "Full LinkedIn profile snapshot (JSON)." },
|
package/openclaw.plugin.json
CHANGED
package/package.json
CHANGED
|
@@ -58,6 +58,7 @@ type TaskResult = {
|
|
|
58
58
|
commentsPosted?: number;
|
|
59
59
|
invitationsAccepted?: number;
|
|
60
60
|
error?: string;
|
|
61
|
+
reason?: string;
|
|
61
62
|
};
|
|
62
63
|
|
|
63
64
|
const MIN_POLL_MS = 5000;
|
|
@@ -230,13 +231,12 @@ async function isWebhookAvailable(config: ConnectorConfig): Promise<boolean> {
|
|
|
230
231
|
if (res.status === 401 || res.status === 403) return false;
|
|
231
232
|
if (res.ok || res.status === 405 || res.status === 204) return true;
|
|
232
233
|
|
|
233
|
-
//
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
});
|
|
238
|
-
|
|
239
|
-
return rootRes.ok || rootRes.status === 404;
|
|
234
|
+
// OPTIONS returned non-2xx/405 (e.g. 404): /hooks/agent doesn't exist.
|
|
235
|
+
// Don't assume hooks are available just because the gateway root responds.
|
|
236
|
+
// This prevents a false-positive where the connector keeps dispatching
|
|
237
|
+
// to a webhook URL that doesn't exist, failing every task with 404.
|
|
238
|
+
console.log(`[connector] Webhook probe: OPTIONS /hooks/agent returned ${res.status}, hooks not available`);
|
|
239
|
+
return false;
|
|
240
240
|
} catch {
|
|
241
241
|
return false;
|
|
242
242
|
}
|
|
@@ -480,9 +480,18 @@ async function executeOnOpenClaw(
|
|
|
480
480
|
config: ConnectorConfig,
|
|
481
481
|
task: NonNullable<PullResponse["task"]>,
|
|
482
482
|
webhookAvailable: boolean,
|
|
483
|
-
): Promise<{ result: TaskResult | null; error: string | null }> {
|
|
483
|
+
): Promise<{ result: TaskResult | null; error: string | null; webhookDead?: boolean }> {
|
|
484
484
|
if (webhookAvailable) {
|
|
485
|
-
|
|
485
|
+
const res = await executeViaWebhook(config, task);
|
|
486
|
+
// Detect webhook 404 — hooks endpoint doesn't exist (plugin version mismatch,
|
|
487
|
+
// gateway restart needed). Fall back to execFile for this task AND signal the
|
|
488
|
+
// main loop to flip webhookAvailable so subsequent tasks don't keep failing.
|
|
489
|
+
if (res.error?.startsWith("Webhook HTTP 404")) {
|
|
490
|
+
console.warn(`[connector] Webhook returned 404, falling back to execFile for task ${task.id}`);
|
|
491
|
+
const fallback = await executeViaExecFile(task);
|
|
492
|
+
return { ...fallback, webhookDead: true };
|
|
493
|
+
}
|
|
494
|
+
return res;
|
|
486
495
|
}
|
|
487
496
|
console.log(`[connector] Webhook unavailable, falling back to execFile`);
|
|
488
497
|
return executeViaExecFile(task);
|
|
@@ -612,6 +621,7 @@ export async function runConnectorLoop(
|
|
|
612
621
|
);
|
|
613
622
|
|
|
614
623
|
try {
|
|
624
|
+
(task as any)._startedAt = Date.now();
|
|
615
625
|
await updateTaskStatus(config, task.id, "accepted");
|
|
616
626
|
await updateTaskStatus(config, task.id, "running");
|
|
617
627
|
|
|
@@ -625,9 +635,9 @@ export async function runConnectorLoop(
|
|
|
625
635
|
}, watchdogMs);
|
|
626
636
|
watchdogTimer.unref(); // Don't prevent graceful shutdown
|
|
627
637
|
|
|
628
|
-
const
|
|
638
|
+
const execResult = await Promise.race([
|
|
629
639
|
executeOnOpenClaw(config, task, webhookAvailable),
|
|
630
|
-
new Promise<{ result: null; error: string }>((resolve) => {
|
|
640
|
+
new Promise<{ result: null; error: string; webhookDead?: boolean }>((resolve) => {
|
|
631
641
|
const check = setTimeout(() => {
|
|
632
642
|
if (watchdogFired) {
|
|
633
643
|
resolve({ result: null, error: `Watchdog: task execution exceeded ${Math.round(watchdogMs / 1000)}s timeout` });
|
|
@@ -636,21 +646,36 @@ export async function runConnectorLoop(
|
|
|
636
646
|
check.unref();
|
|
637
647
|
}),
|
|
638
648
|
]);
|
|
649
|
+
const { result, error } = execResult;
|
|
650
|
+
// If webhook returned 404, disable it for future tasks
|
|
651
|
+
if (execResult.webhookDead) {
|
|
652
|
+
console.warn(`[connector] Disabling webhook mode — /hooks/agent not available`);
|
|
653
|
+
webhookAvailable = false;
|
|
654
|
+
}
|
|
639
655
|
clearTimeout(watchdogTimer);
|
|
640
656
|
|
|
641
657
|
const taskStatus = error ? "failed" : (result?.success !== false ? "succeeded" : "failed");
|
|
658
|
+
const execDuration = Date.now() - (task as any)._startedAt;
|
|
642
659
|
console.log(
|
|
643
|
-
`[connector] Task ${task.id} ${taskStatus}${error ? `: ${error.slice(0, 100)}` : ""}${task.workflowRunId ? ` workflow=${task.workflowRunId}` : ""}`,
|
|
660
|
+
`[connector] Task ${task.id} ${taskStatus} (${Math.round(execDuration / 1000)}s)${error ? `: ${error.slice(0, 100)}` : ""}${result?.reason ? ` reason=${result.reason}` : ""}${task.workflowRunId ? ` workflow=${task.workflowRunId}` : ""}`,
|
|
644
661
|
);
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
// when lifecycle hook may not have fired (agent crash).
|
|
649
|
-
// Don't submit if result looks successful — lifecycle hook already posted.
|
|
650
|
-
if (!webhookAvailable || error) {
|
|
651
|
-
await submitResult(config, task.id, result, error);
|
|
662
|
+
// Flag suspicious fast failures with no tools for investigation
|
|
663
|
+
if (!error && result?.success === false && (result as any)?.toolCallCount === 0 && execDuration < 10_000) {
|
|
664
|
+
console.warn(`[connector] DIAGNOSTIC: Task ${task.id} failed in <10s with 0 tool calls — likely context injection failure or model error`);
|
|
652
665
|
}
|
|
653
666
|
|
|
667
|
+
// Always submit from connector as safety net. In webhook mode the lifecycle
|
|
668
|
+
// hook usually reports first, but if it fails silently (network, crash) the
|
|
669
|
+
// result is lost and the task stays "running" forever. The result endpoint
|
|
670
|
+
// uses an optimistic lock so double-submissions are harmless (second is a no-op).
|
|
671
|
+
// Derive error when result indicates failure but no explicit error exists.
|
|
672
|
+
const derivedError = error ?? (
|
|
673
|
+
result?.success === false
|
|
674
|
+
? (result.error ?? result.reason ?? "Task failed (no details from agent)")
|
|
675
|
+
: undefined
|
|
676
|
+
);
|
|
677
|
+
await submitResult(config, task.id, result, derivedError ?? null);
|
|
678
|
+
|
|
654
679
|
totalTasksExecuted++;
|
|
655
680
|
pollInterval = 5_000;
|
|
656
681
|
} finally {
|
package/src/hooks/context.ts
CHANGED
|
@@ -374,7 +374,7 @@ function formatCampaignDispatch(
|
|
|
374
374
|
const shown = Math.min(ranked.length, 7);
|
|
375
375
|
for (let i = 0; i < shown; i++) {
|
|
376
376
|
const { campaign: c } = ranked[i];
|
|
377
|
-
const pipelineUrl = `${CHAT_BASE}/
|
|
377
|
+
const pipelineUrl = `${CHAT_BASE}/campaigns/${c.id}/pipeline`;
|
|
378
378
|
const s = c.stageCounts;
|
|
379
379
|
const funnel = s && (s.lead || s.qualified || s.approved || s.rejected)
|
|
380
380
|
? ` — ${s.lead ?? 0}L / ${s.qualified ?? 0}Q / ${s.approved ?? 0}A / ${s.rejected ?? 0}R`
|
|
@@ -479,7 +479,7 @@ function formatOnboardingDirective(state: SessionState, data: CacheStore, apiKey
|
|
|
479
479
|
"",
|
|
480
480
|
"Search 5 matching prospects, visit top 3. Present: name, title, company, why they fit, suggested approach.",
|
|
481
481
|
"Offer first action: connect, warm up (like/comment), or save to campaign.",
|
|
482
|
-
"Execute, then link: [
|
|
482
|
+
"Execute, then link: [Campaigns](${CHAT_BASE}/campaigns)",
|
|
483
483
|
'- `bereach_state_patch({ key: "onboarding", data: { quickWinDone: true } })`',
|
|
484
484
|
"",
|
|
485
485
|
);
|
|
@@ -576,7 +576,7 @@ function formatLiveStatus(state: SessionState, data: CacheStore, apiKey?: string
|
|
|
576
576
|
lines.push("");
|
|
577
577
|
}
|
|
578
578
|
lines.push("### Dashboard Links");
|
|
579
|
-
lines.push(`[
|
|
579
|
+
lines.push(`[Campaigns](${CHAT_BASE}/campaigns) | [Activity](${CHAT_BASE}/activity) | [Context](${CHAT_BASE}/context) | [Settings](${CHAT_BASE}/settings) | [Pricing](${PRICING_URL})`);
|
|
580
580
|
lines.push("");
|
|
581
581
|
log(`live-status: ONBOARDING mode (${lines.join("\n").length} chars)`);
|
|
582
582
|
return lines.join("\n");
|
|
@@ -733,7 +733,7 @@ function formatLiveStatus(state: SessionState, data: CacheStore, apiKey?: string
|
|
|
733
733
|
if (upgradeBlock) lines.push(upgradeBlock);
|
|
734
734
|
|
|
735
735
|
// Dashboard links
|
|
736
|
-
lines.push(`**Links**: [
|
|
736
|
+
lines.push(`**Links**: [Campaigns](${CHAT_BASE}/campaigns) | [Activity](${CHAT_BASE}/activity) | [Context](${CHAT_BASE}/context) | [Settings](${CHAT_BASE}/settings) | [Pricing](${PRICING_URL})`);
|
|
737
737
|
lines.push("");
|
|
738
738
|
|
|
739
739
|
log(`live-status: ACTIVE mode, campaigns=${hasCampaigns} contacts=${hasContacts} pending=${data.pendingDrafts > 0 || data.failedDrafts > 0 || data.unreadDMs > 0} (${lines.join("\n").length} chars)`);
|
package/src/hooks/lifecycle.ts
CHANGED
|
@@ -125,9 +125,25 @@ export function registerLifecycleHook(
|
|
|
125
125
|
creditsUsed: state.creditsUsedThisSession,
|
|
126
126
|
toolCallCount: state.toolCallCount,
|
|
127
127
|
visitCount: state.visitCount,
|
|
128
|
-
...(state.toolCallCount === 0 ? { reason: "no_tools_called" } : {}),
|
|
128
|
+
...(state.toolCallCount === 0 ? { reason: "no_tools_called", error: "Task completed with no tool calls" } : {}),
|
|
129
129
|
};
|
|
130
130
|
|
|
131
|
+
// Diagnostic logging for no_tools_called — capture WHY the agent did nothing.
|
|
132
|
+
// This helps diagnose context injection failures, model errors, etc.
|
|
133
|
+
if (state.toolCallCount === 0) {
|
|
134
|
+
const msgCount = allMessages.length;
|
|
135
|
+
const userMsgCount = userMsgs.length;
|
|
136
|
+
const assistantMsgCount = assistantMsgs.length;
|
|
137
|
+
const lastAssistantText = outputText?.slice(0, 300) || "(empty)";
|
|
138
|
+
const hasTaskMeta = userMsgs.some((m: any) => extractTextFromContent(m.content).includes("TASK_META"));
|
|
139
|
+
const endSuccess = (endCtx as any)?.success;
|
|
140
|
+
const endError = (endCtx as any)?.error;
|
|
141
|
+
log(`NO_TOOLS_CALLED diagnostic for ${taskMode.taskId}: ` +
|
|
142
|
+
`msgs=${msgCount} (user=${userMsgCount} assistant=${assistantMsgCount}) ` +
|
|
143
|
+
`hasTaskMeta=${hasTaskMeta} endSuccess=${endSuccess} endError=${endError ? String(endError).slice(0, 200) : "none"} ` +
|
|
144
|
+
`lastAssistant="${lastAssistantText}"`);
|
|
145
|
+
}
|
|
146
|
+
|
|
131
147
|
// B17 fix: In execFile mode, the connector also submits results after
|
|
132
148
|
// parsing the OpenClaw --json wrapper (extractTaskResultFromWrapper).
|
|
133
149
|
// The lifecycle hook fires first (inside subprocess), so it wins the race.
|
|
@@ -148,10 +164,14 @@ export function registerLifecycleHook(
|
|
|
148
164
|
attempts++;
|
|
149
165
|
try {
|
|
150
166
|
if (attempt > 0) await new Promise((r) => setTimeout(r, 2000 * attempt));
|
|
167
|
+
// Derive error from result when task failed but no explicit error exists
|
|
168
|
+
const taskError = taskResult.success === false
|
|
169
|
+
? (taskResult.error ?? taskResult.reason ?? "Task failed (no details captured)")
|
|
170
|
+
: undefined;
|
|
151
171
|
const res = await fetch(`${API_BASE}/tasks/${taskMode.taskId}/result`, {
|
|
152
172
|
method: "POST",
|
|
153
173
|
headers: { Authorization: `Bearer ${key}`, "Content-Type": "application/json" },
|
|
154
|
-
body: JSON.stringify({ result: taskResult }),
|
|
174
|
+
body: JSON.stringify({ result: taskResult, ...(taskError ? { error: taskError } : {}) }),
|
|
155
175
|
signal: AbortSignal.timeout(10000),
|
|
156
176
|
});
|
|
157
177
|
if (res.ok) {
|
package/src/index.ts
CHANGED
|
@@ -17,6 +17,10 @@ const log = createLogger("init");
|
|
|
17
17
|
/** Guard against multiple register() calls with the same api instance */
|
|
18
18
|
const registeredApis = new WeakSet<object>();
|
|
19
19
|
let registeredCount = 0;
|
|
20
|
+
let autoDetectDone = false;
|
|
21
|
+
let lastRegisterAt = 0;
|
|
22
|
+
/** After initial burst, suppress noisy re-registration logs */
|
|
23
|
+
const REGISTER_LOG_THRESHOLD = 5;
|
|
20
24
|
|
|
21
25
|
/**
|
|
22
26
|
* Resolve the BeReach API key from all supported sources (in priority order):
|
|
@@ -32,6 +36,86 @@ export function resolveApiKey(api: any): string | undefined {
|
|
|
32
36
|
return typeof key === "string" && key.trim().length > 0 ? key.trim() : undefined;
|
|
33
37
|
}
|
|
34
38
|
|
|
39
|
+
/**
|
|
40
|
+
* Auto-detect LLM provider from available API keys and set workspace defaults.
|
|
41
|
+
*
|
|
42
|
+
* Runs once at install time. If the workspace still has Anthropic defaults but
|
|
43
|
+
* no ANTHROPIC_API_KEY is available (only GEMINI_API_KEY), switches to Gemini
|
|
44
|
+
* models automatically. This prevents tasks from failing because the default
|
|
45
|
+
* models require an API key the user doesn't have.
|
|
46
|
+
*
|
|
47
|
+
* Does nothing if:
|
|
48
|
+
* - Already ran this session (idempotent guard)
|
|
49
|
+
* - ANTHROPIC_API_KEY is available (defaults are correct)
|
|
50
|
+
* - No GEMINI_API_KEY available (nothing to switch to)
|
|
51
|
+
* - User already changed models from defaults (respects manual choice)
|
|
52
|
+
*/
|
|
53
|
+
async function autoDetectModels(apiKey: string): Promise<void> {
|
|
54
|
+
if (autoDetectDone) return;
|
|
55
|
+
autoDetectDone = true;
|
|
56
|
+
|
|
57
|
+
const hasAnthropic = !!readEnv("ANTHROPIC_API_KEY");
|
|
58
|
+
const hasGemini = !!(readEnv("GEMINI_API_KEY") || readEnv("GOOGLE_API_KEY"));
|
|
59
|
+
|
|
60
|
+
// If Anthropic key exists, defaults are already correct
|
|
61
|
+
if (hasAnthropic) {
|
|
62
|
+
log("model auto-detect: Anthropic key available, keeping defaults");
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// If no Gemini key either, nothing we can switch to
|
|
67
|
+
if (!hasGemini) {
|
|
68
|
+
log("model auto-detect: no LLM provider keys found, skipping");
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Gemini key available, no Anthropic key - check if we need to switch
|
|
73
|
+
try {
|
|
74
|
+
const res = await fetch(`${API_BASE}/me/settings`, {
|
|
75
|
+
headers: { Authorization: `Bearer ${apiKey}` },
|
|
76
|
+
signal: AbortSignal.timeout(5000),
|
|
77
|
+
});
|
|
78
|
+
if (!res.ok) {
|
|
79
|
+
log(`model auto-detect: settings GET failed (HTTP ${res.status}), skipping`);
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const data = await res.json() as {
|
|
84
|
+
aiModels?: { fast?: string; creative?: string };
|
|
85
|
+
};
|
|
86
|
+
const currentFast = data.aiModels?.fast;
|
|
87
|
+
const currentCreative = data.aiModels?.creative;
|
|
88
|
+
|
|
89
|
+
// Only auto-switch if still on Anthropic defaults (respect manual choices)
|
|
90
|
+
if (
|
|
91
|
+
currentFast === "anthropic/claude-haiku-4-5" &&
|
|
92
|
+
currentCreative === "anthropic/claude-sonnet-4-6"
|
|
93
|
+
) {
|
|
94
|
+
const patchRes = await fetch(`${API_BASE}/me/settings`, {
|
|
95
|
+
method: "PATCH",
|
|
96
|
+
headers: {
|
|
97
|
+
Authorization: `Bearer ${apiKey}`,
|
|
98
|
+
"Content-Type": "application/json",
|
|
99
|
+
},
|
|
100
|
+
body: JSON.stringify({
|
|
101
|
+
aiFastModel: "google/gemini-2.5-flash",
|
|
102
|
+
aiCreativeModel: "google/gemini-2.5-pro",
|
|
103
|
+
}),
|
|
104
|
+
signal: AbortSignal.timeout(5000),
|
|
105
|
+
});
|
|
106
|
+
if (patchRes.ok) {
|
|
107
|
+
log("model auto-detect: no Anthropic key, Gemini key found - switched to Gemini Flash (fast) + Gemini Pro (creative)");
|
|
108
|
+
} else {
|
|
109
|
+
log(`model auto-detect: PATCH failed (HTTP ${patchRes.status})`);
|
|
110
|
+
}
|
|
111
|
+
} else {
|
|
112
|
+
log("model auto-detect: models already customized, skipping");
|
|
113
|
+
}
|
|
114
|
+
} catch (err) {
|
|
115
|
+
log(`model auto-detect: ${err instanceof Error ? err.message : String(err)}`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
35
119
|
export default function register(api: any) {
|
|
36
120
|
if (api && typeof api === "object" && registeredApis.has(api)) {
|
|
37
121
|
log(`skip duplicate register() call (same api instance, call #${registeredCount + 1})`);
|
|
@@ -39,8 +123,11 @@ export default function register(api: any) {
|
|
|
39
123
|
}
|
|
40
124
|
|
|
41
125
|
registeredCount++;
|
|
42
|
-
|
|
126
|
+
lastRegisterAt = Date.now();
|
|
127
|
+
if (registeredCount > 1 && registeredCount <= REGISTER_LOG_THRESHOLD) {
|
|
43
128
|
log(`register() called again (call #${registeredCount}) — re-registering on new api instance`);
|
|
129
|
+
} else if (registeredCount === REGISTER_LOG_THRESHOLD + 1) {
|
|
130
|
+
log(`register() call #${registeredCount} — suppressing further re-registration logs`);
|
|
44
131
|
}
|
|
45
132
|
if (api && typeof api === "object") {
|
|
46
133
|
registeredApis.add(api);
|
|
@@ -127,6 +214,15 @@ export default function register(api: any) {
|
|
|
127
214
|
}
|
|
128
215
|
}
|
|
129
216
|
|
|
217
|
+
// Auto-detect LLM provider and set workspace model defaults.
|
|
218
|
+
// Runs before connector start so tasks use the correct models from the first poll.
|
|
219
|
+
// Fire-and-forget — non-critical, must not block registration.
|
|
220
|
+
if (apiKey) {
|
|
221
|
+
autoDetectModels(apiKey).catch((err) => {
|
|
222
|
+
log(`model auto-detect error: ${err instanceof Error ? err.message : String(err)}`);
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
|
|
130
226
|
// Auto-start connector if API key is present and connector is enabled.
|
|
131
227
|
// Skip when running as an agent child process (openclaw agent) — each child
|
|
132
228
|
// would start its own polling loop, stealing tasks from the main connector.
|
package/src/tools/index.ts
CHANGED
|
@@ -155,6 +155,7 @@ export function registerAllTools(api: any, taskType?: string) {
|
|
|
155
155
|
content: [{ type: "text" as const, text: JSON.stringify({ success: false, error: "Invalid key format. Must start with brc_. Get your key at https://bereach.ai/token" }) }],
|
|
156
156
|
};
|
|
157
157
|
}
|
|
158
|
+
|
|
158
159
|
if (typeof api?.config?.set !== "function") {
|
|
159
160
|
return {
|
|
160
161
|
content: [{ type: "text" as const, text: JSON.stringify({ success: false, error: "Config API not available. Set BEREACH_API_KEY as an environment variable instead." }) }],
|