bereach-openclaw 1.6.13 → 1.6.14
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.
|
@@ -38,6 +38,14 @@ export const READ_TOOLS = new Set([
|
|
|
38
38
|
"bereach_get_connections",
|
|
39
39
|
"bereach_resolve_parameters",
|
|
40
40
|
"bereach_inbox_unread_count",
|
|
41
|
+
// Chat agent tools (same LinkedIn actions, different naming convention)
|
|
42
|
+
"bereach_search_people",
|
|
43
|
+
"bereach_search_posts",
|
|
44
|
+
"bereach_search_companies",
|
|
45
|
+
"bereach_search_jobs",
|
|
46
|
+
"bereach_search_by_url",
|
|
47
|
+
"bereach_search_sales_nav_people",
|
|
48
|
+
"bereach_search_sales_nav_companies",
|
|
41
49
|
]);
|
|
42
50
|
|
|
43
51
|
/** LinkedIn write operations (DM, connect, like, comment, etc.). Higher pacing. 1 credit each. */
|
package/openclaw.plugin.json
CHANGED
package/package.json
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* Uses TTL cache so only the first turn hits the API.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import { getOrFetch, sessionStart,
|
|
9
|
+
import { getOrFetch, sessionStart, type CacheStore, type ContextEntry } from "../cache";
|
|
10
10
|
import { SOUL_TEMPLATE } from "../../soul-template-content";
|
|
11
11
|
|
|
12
12
|
import { type SessionState, detectTaskMode } from "../types";
|
|
@@ -70,7 +70,17 @@ export function resetProfileState(state: SessionState) {
|
|
|
70
70
|
// Soul template fetcher
|
|
71
71
|
// ---------------------------------------------------------------------------
|
|
72
72
|
|
|
73
|
+
// Module-level soul cache — the soul template changes only when the user
|
|
74
|
+
// updates ICP/tone/playbook, which is rare. Refetching every turn wastes
|
|
75
|
+
// ~1-3s of API latency. 5-minute TTL matches Anthropic prompt cache TTL.
|
|
76
|
+
let _soulCache: { text: string; fetchedAt: number; key: string } | null = null;
|
|
77
|
+
const SOUL_TTL_MS = 5 * 60 * 1000;
|
|
78
|
+
|
|
73
79
|
async function fetchSoulTemplate(apiKey?: string): Promise<string> {
|
|
80
|
+
const key = apiKey ?? "";
|
|
81
|
+
if (_soulCache && _soulCache.key === key && Date.now() - _soulCache.fetchedAt < SOUL_TTL_MS) {
|
|
82
|
+
return _soulCache.text;
|
|
83
|
+
}
|
|
74
84
|
try {
|
|
75
85
|
const headers: Record<string, string> = {};
|
|
76
86
|
if (apiKey) headers.Authorization = `Bearer ${apiKey}`;
|
|
@@ -78,14 +88,25 @@ async function fetchSoulTemplate(apiKey?: string): Promise<string> {
|
|
|
78
88
|
headers,
|
|
79
89
|
signal: AbortSignal.timeout(3000),
|
|
80
90
|
});
|
|
81
|
-
if (!res.ok)
|
|
91
|
+
if (!res.ok) {
|
|
92
|
+
_soulCache = { text: SOUL_TEMPLATE, fetchedAt: Date.now(), key };
|
|
93
|
+
return SOUL_TEMPLATE;
|
|
94
|
+
}
|
|
82
95
|
const { soul } = await res.json();
|
|
83
|
-
|
|
96
|
+
const result = typeof soul === "string" && soul.includes(MARKER_START) ? soul : SOUL_TEMPLATE;
|
|
97
|
+
_soulCache = { text: result, fetchedAt: Date.now(), key };
|
|
98
|
+
return result;
|
|
84
99
|
} catch {
|
|
100
|
+
_soulCache = { text: SOUL_TEMPLATE, fetchedAt: Date.now(), key };
|
|
85
101
|
return SOUL_TEMPLATE;
|
|
86
102
|
}
|
|
87
103
|
}
|
|
88
104
|
|
|
105
|
+
/** Clear the soul template cache. Called by tracking hook when contexts change. */
|
|
106
|
+
export function clearSoulCache(): void {
|
|
107
|
+
_soulCache = null;
|
|
108
|
+
}
|
|
109
|
+
|
|
89
110
|
// ---------------------------------------------------------------------------
|
|
90
111
|
// Profile auto-init + daily refresh
|
|
91
112
|
// ---------------------------------------------------------------------------
|
|
@@ -206,12 +227,38 @@ function buildInteractiveContext(
|
|
|
206
227
|
// Hook registration
|
|
207
228
|
// ---------------------------------------------------------------------------
|
|
208
229
|
|
|
230
|
+
// Module-level dedup: OpenClaw calls register() with two api instances, both
|
|
231
|
+
// fire before_prompt_build for the same turn. Without dedup, we make double
|
|
232
|
+
// HTTP calls (soul + snapshot) and double-build the context. The inflight
|
|
233
|
+
// promise lets the second hook reuse the first's result.
|
|
234
|
+
let _inflightBuild: { promise: Promise<any>; at: number } | null = null;
|
|
235
|
+
|
|
209
236
|
export function registerContextHook(api: any, apiKey: string | undefined, state: SessionState) {
|
|
210
237
|
if (typeof api?.on !== "function") return;
|
|
211
238
|
|
|
212
239
|
api.on(
|
|
213
240
|
"before_prompt_build",
|
|
214
241
|
async (promptCtx: any) => {
|
|
242
|
+
try {
|
|
243
|
+
// Dedup: if another hook instance is already building context for this
|
|
244
|
+
// turn (within 200ms), reuse its result instead of duplicating work.
|
|
245
|
+
if (_inflightBuild && Date.now() - _inflightBuild.at < 200) {
|
|
246
|
+
log("context: SKIP duplicate hook (reusing inflight)");
|
|
247
|
+
return _inflightBuild.promise;
|
|
248
|
+
}
|
|
249
|
+
const buildPromise = _buildContext(promptCtx, api, apiKey, state);
|
|
250
|
+
_inflightBuild = { promise: buildPromise, at: Date.now() };
|
|
251
|
+
return buildPromise;
|
|
252
|
+
} catch (err) {
|
|
253
|
+
log(`error: ${errMsg(err)}`);
|
|
254
|
+
return { appendSystemContext: SOUL_TEMPLATE };
|
|
255
|
+
}
|
|
256
|
+
},
|
|
257
|
+
{ priority: 5 },
|
|
258
|
+
);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
async function _buildContext(promptCtx: any, api: any, apiKey: string | undefined, state: SessionState): Promise<any> {
|
|
215
262
|
try {
|
|
216
263
|
// Track active sessions so clearAll() at agent_end doesn't wipe cache for concurrent sessions.
|
|
217
264
|
// Only call once per session — before_prompt_build fires every turn, but sessionEnd is once.
|
|
@@ -303,13 +350,11 @@ export function registerContextHook(api: any, apiKey: string | undefined, state:
|
|
|
303
350
|
}
|
|
304
351
|
|
|
305
352
|
// INTERACTIVE MODE
|
|
306
|
-
//
|
|
307
|
-
//
|
|
308
|
-
//
|
|
309
|
-
//
|
|
310
|
-
//
|
|
311
|
-
// need mid-run refresh.
|
|
312
|
-
invalidateAndRefresh("credits");
|
|
353
|
+
// Cache serves soul + snapshot from memory when fresh (TTL-based).
|
|
354
|
+
// Tool-call tracking already invalidates specific keys on mutation
|
|
355
|
+
// (credits, contexts, pipeline, drafts). No need to force-refresh
|
|
356
|
+
// the entire snapshot every turn — it adds 2-3 blocking HTTP calls
|
|
357
|
+
// (~3-4s latency) per message.
|
|
313
358
|
const [soulTemplate, liveData] = await Promise.all([
|
|
314
359
|
fetchSoulTemplate(key),
|
|
315
360
|
getOrFetch(key),
|
|
@@ -380,9 +425,6 @@ export function registerContextHook(api: any, apiKey: string | undefined, state:
|
|
|
380
425
|
log(`error: ${errMsg(err)}`);
|
|
381
426
|
return { appendSystemContext: SOUL_TEMPLATE };
|
|
382
427
|
}
|
|
383
|
-
},
|
|
384
|
-
{ priority: 5 },
|
|
385
|
-
);
|
|
386
428
|
}
|
|
387
429
|
|
|
388
430
|
// ---------------------------------------------------------------------------
|
package/src/hooks/lifecycle.ts
CHANGED
|
@@ -106,7 +106,15 @@ export function registerLifecycleHook(
|
|
|
106
106
|
state.onboardingDirectiveInjected = false;
|
|
107
107
|
state.sessionStarted = false;
|
|
108
108
|
}
|
|
109
|
-
|
|
109
|
+
|
|
110
|
+
// Interactive mode: preserve cache across turns. The same user continues the
|
|
111
|
+
// conversation, so wiping forces 2-3 redundant HTTP calls per turn (~3-4s
|
|
112
|
+
// latency). TTL handles staleness; tool-call tracking invalidates specific
|
|
113
|
+
// keys on mutation. Multi-user key mismatch handled by getOrFetch().
|
|
114
|
+
// Task mode: clear cache so the next task gets fresh data for its campaign.
|
|
115
|
+
if (isTaskMode) {
|
|
116
|
+
clearAllSafe();
|
|
117
|
+
}
|
|
110
118
|
|
|
111
119
|
// Reset per-turn state (always safe to reset)
|
|
112
120
|
state.toolCallCount = 0;
|
package/src/hooks/tracking.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import { invalidate, invalidateAndRefresh, clearAllSafe } from "./cache";
|
|
8
|
-
import { resetProfileState } from "./context";
|
|
8
|
+
import { resetProfileState, clearSoulCache } from "./context";
|
|
9
9
|
import { FREE_TOOLS, PACED_FREE_TOOLS, type SessionState } from "./types";
|
|
10
10
|
import {
|
|
11
11
|
errMsg,
|
|
@@ -245,7 +245,7 @@ export function registerTrackingHook(api: any, state: SessionState) {
|
|
|
245
245
|
|
|
246
246
|
if (CONTACT_MUTATION_TOOLS.has(toolName)) invalidateAndRefresh("pipeline");
|
|
247
247
|
if (DRAFT_MUTATION_TOOLS.has(toolName)) invalidateAndRefresh("pendingDrafts");
|
|
248
|
-
if (toolName === "bereach_context_set" || toolName === "bereach_context_delete") invalidateAndRefresh("contexts");
|
|
248
|
+
if (toolName === "bereach_context_set" || toolName === "bereach_context_delete") { invalidateAndRefresh("contexts"); clearSoulCache(); }
|
|
249
249
|
if (toolName === "bereach_switch_account") { clearAllSafe(); resetProfileState(state); log("account switch: cleared caches + reset profile state"); }
|
|
250
250
|
if (CAMPAIGN_MUTATION_TOOLS.has(toolName)) clearAllSafe();
|
|
251
251
|
} catch (err) {
|