bereach-openclaw 1.5.5 → 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/lifecycle.ts +22 -2
- package/src/index.ts +7 -1
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/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
|
@@ -18,6 +18,9 @@ const log = createLogger("init");
|
|
|
18
18
|
const registeredApis = new WeakSet<object>();
|
|
19
19
|
let registeredCount = 0;
|
|
20
20
|
let autoDetectDone = false;
|
|
21
|
+
let lastRegisterAt = 0;
|
|
22
|
+
/** After initial burst, suppress noisy re-registration logs */
|
|
23
|
+
const REGISTER_LOG_THRESHOLD = 5;
|
|
21
24
|
|
|
22
25
|
/**
|
|
23
26
|
* Resolve the BeReach API key from all supported sources (in priority order):
|
|
@@ -120,8 +123,11 @@ export default function register(api: any) {
|
|
|
120
123
|
}
|
|
121
124
|
|
|
122
125
|
registeredCount++;
|
|
123
|
-
|
|
126
|
+
lastRegisterAt = Date.now();
|
|
127
|
+
if (registeredCount > 1 && registeredCount <= REGISTER_LOG_THRESHOLD) {
|
|
124
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`);
|
|
125
131
|
}
|
|
126
132
|
if (api && typeof api === "object") {
|
|
127
133
|
registeredApis.add(api);
|