ofiere-openclaw-plugin 4.30.0 → 4.31.0
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/package.json +1 -1
- package/src/agent-resolver.ts +3 -17
- package/src/tools.ts +90 -78
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ofiere-openclaw-plugin",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.31.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "OpenClaw plugin for Ofiere PM - 14 meta-tools covering tasks, agents, projects, scheduling, knowledge, workflows, notifications, memory, prompts, constellation, space file management, execution plan builder, SOP management, and agent brain (memory + self-improvement)",
|
|
6
6
|
"keywords": ["openclaw", "ofiere", "project-management", "agents", "plugin"],
|
package/src/agent-resolver.ts
CHANGED
|
@@ -35,12 +35,12 @@ export async function resolveAgentId(
|
|
|
35
35
|
return cache.get(cacheKey)!;
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
// 2. Look up by name (
|
|
38
|
+
// 2. Look up by name OR codename in a single query (v4.30.0 optimization)
|
|
39
39
|
const { data: existing } = await supabase
|
|
40
40
|
.from("agents")
|
|
41
41
|
.select("id")
|
|
42
42
|
.eq("user_id", userId)
|
|
43
|
-
.
|
|
43
|
+
.or(`name.ilike.${accountId},codename.ilike.${accountId}`)
|
|
44
44
|
.limit(1)
|
|
45
45
|
.single();
|
|
46
46
|
|
|
@@ -49,21 +49,7 @@ export async function resolveAgentId(
|
|
|
49
49
|
return existing.id;
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
// 3.
|
|
53
|
-
const { data: byCodename } = await supabase
|
|
54
|
-
.from("agents")
|
|
55
|
-
.select("id")
|
|
56
|
-
.eq("user_id", userId)
|
|
57
|
-
.ilike("codename", accountId)
|
|
58
|
-
.limit(1)
|
|
59
|
-
.single();
|
|
60
|
-
|
|
61
|
-
if (byCodename?.id) {
|
|
62
|
-
cache.set(cacheKey, byCodename.id);
|
|
63
|
-
return byCodename.id;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// 4. Auto-register a new agent
|
|
52
|
+
// 3. Auto-register a new agent
|
|
67
53
|
const newId = `agent-${accountId.toLowerCase()}-${Date.now()}`;
|
|
68
54
|
const { data: created } = await supabase
|
|
69
55
|
.from("agents")
|
package/src/tools.ts
CHANGED
|
@@ -113,10 +113,16 @@ const SYSTEM_NAME_BLOCKLIST = new Set([
|
|
|
113
113
|
"ofiere pm plugin", "ofiere-openclaw-plugin",
|
|
114
114
|
]);
|
|
115
115
|
|
|
116
|
+
// Module-scoped brain context cache (v4.30.0)
|
|
117
|
+
// Shared between registerBrainContextHook and BRAIN_OPS save_memory
|
|
118
|
+
// so explicit saves invalidate the cache for immediate consistency.
|
|
119
|
+
const brainCache = new Map<string, { text: string; at: number }>();
|
|
120
|
+
|
|
116
121
|
function isSystemName(name: string): boolean {
|
|
117
122
|
return SYSTEM_NAME_BLOCKLIST.has(name.toLowerCase().trim());
|
|
118
123
|
}
|
|
119
124
|
|
|
125
|
+
|
|
120
126
|
function createAgentResolver(
|
|
121
127
|
api: any,
|
|
122
128
|
supabase: SupabaseClient,
|
|
@@ -5213,6 +5219,10 @@ function registerBrainOps(
|
|
|
5213
5219
|
}).select("id, tier, importance, decay_score, created_at").single();
|
|
5214
5220
|
|
|
5215
5221
|
if (error) return err(error.message);
|
|
5222
|
+
|
|
5223
|
+
// Invalidate brain context cache so next prompt build sees the new memory
|
|
5224
|
+
if (agentId) brainCache.delete(agentId);
|
|
5225
|
+
|
|
5216
5226
|
return ok({ message: `Memory saved to ${tier}`, memory: data });
|
|
5217
5227
|
}
|
|
5218
5228
|
|
|
@@ -5690,10 +5700,12 @@ function registerBrainExtractionHook(
|
|
|
5690
5700
|
|
|
5691
5701
|
api.logger.debug?.(`[ofiere-brain] agent_end identity: ctx.agentId=${ctx?.agentId || "(none)"} event.agentId=${event?.agentId || "(none)"} resolved=${resolvedAgentId}`);
|
|
5692
5702
|
|
|
5693
|
-
// ── Fast Stream: Write L1_focus ──
|
|
5703
|
+
// ── Fast Stream: Write L1_focus + L2_episode in parallel ──
|
|
5694
5704
|
const rawContent = `User: ${lastUser}\nAssistant: ${lastAssistant}`;
|
|
5705
|
+
const immediateWrites: Promise<any>[] = [];
|
|
5706
|
+
|
|
5695
5707
|
if (rawContent.length > 50) {
|
|
5696
|
-
|
|
5708
|
+
immediateWrites.push(supabase.from("agent_memories").insert({
|
|
5697
5709
|
user_id: userId,
|
|
5698
5710
|
agent_id: resolvedAgentId,
|
|
5699
5711
|
tier: "L1_focus",
|
|
@@ -5702,16 +5714,14 @@ function registerBrainExtractionHook(
|
|
|
5702
5714
|
importance: 3,
|
|
5703
5715
|
context_key: sessionKey ? `conversation:${sessionKey}` : null,
|
|
5704
5716
|
expires_at: new Date(Date.now() + 48 * 60 * 60 * 1000).toISOString(),
|
|
5705
|
-
});
|
|
5717
|
+
}));
|
|
5706
5718
|
}
|
|
5707
5719
|
|
|
5708
|
-
// ── Lightweight L2 episode summary ──
|
|
5709
5720
|
if (lastUser.length + lastAssistant.length > 200) {
|
|
5710
5721
|
const userSummary = lastUser.split(/[.!?]\s/)[0];
|
|
5711
5722
|
const assistSummary = lastAssistant.split(/[.!?]\s/)[0];
|
|
5712
5723
|
const episodeSummary = `User asked: "${userSummary}". Agent responded: "${assistSummary}"`;
|
|
5713
|
-
|
|
5714
|
-
await supabase.from("agent_memories").insert({
|
|
5724
|
+
immediateWrites.push(supabase.from("agent_memories").insert({
|
|
5715
5725
|
user_id: userId,
|
|
5716
5726
|
agent_id: resolvedAgentId,
|
|
5717
5727
|
tier: "L2_episode",
|
|
@@ -5719,70 +5729,86 @@ function registerBrainExtractionHook(
|
|
|
5719
5729
|
source: "auto",
|
|
5720
5730
|
importance: 4,
|
|
5721
5731
|
context_key: sessionKey ? `episode:${sessionKey}` : null,
|
|
5722
|
-
});
|
|
5732
|
+
}));
|
|
5723
5733
|
}
|
|
5724
5734
|
|
|
5725
|
-
//
|
|
5726
|
-
|
|
5735
|
+
// Fire L1+L2 writes in parallel (no await needed between them)
|
|
5736
|
+
await Promise.all(immediateWrites);
|
|
5737
|
+
|
|
5738
|
+
// ── Batched L3/L4 extraction (v4.30.0 optimization) ──
|
|
5739
|
+
// Collect all candidates first, dedup in one parallel batch, insert in one batch.
|
|
5727
5740
|
const factPatterns = [
|
|
5728
5741
|
/(?:my name is|i'm called|call me)\s+(\w+)/gi,
|
|
5729
5742
|
/(?:i (?:work|am working) (?:at|for|with))\s+(.+?)(?:\.|,|$)/gi,
|
|
5730
5743
|
/(?:i (?:like|love|prefer|enjoy|hate|dislike))\s+(.+?)(?:\.|,|$)/gi,
|
|
5731
5744
|
/(?:i (?:am|'m))\s+(?:a|an)\s+(.+?)(?:\.|,|$)/gi,
|
|
5732
5745
|
];
|
|
5746
|
+
const rulePatterns = [
|
|
5747
|
+
/(?:always|never|make sure|don't|do not|please always)\s+(.+?)(?:\.|!|$)/gi,
|
|
5748
|
+
/(?:remember to|keep in mind|note that)\s+(.+?)(?:\.|!|$)/gi,
|
|
5749
|
+
];
|
|
5750
|
+
|
|
5751
|
+
// Phase 1: Collect all regex candidates
|
|
5752
|
+
type MemCandidate = { tier: string; content: string; contextKey: string; importance: number };
|
|
5753
|
+
const candidates: MemCandidate[] = [];
|
|
5733
5754
|
|
|
5734
5755
|
for (const pattern of factPatterns) {
|
|
5735
|
-
const
|
|
5736
|
-
for (const match of matches) {
|
|
5756
|
+
for (const match of lastUser.matchAll(pattern)) {
|
|
5737
5757
|
const fact = match[0].trim();
|
|
5738
|
-
|
|
5739
|
-
// Dedup: check if context_key already exists
|
|
5740
|
-
const { data: existing } = await supabase.from("agent_memories")
|
|
5741
|
-
.select("id").eq("user_id", userId).eq("agent_id", resolvedAgentId)
|
|
5742
|
-
.eq("context_key", contextKey).is("superseded_by", null).limit(1);
|
|
5743
|
-
if (existing && existing.length > 0) continue;
|
|
5744
|
-
|
|
5745
|
-
await supabase.from("agent_memories").insert({
|
|
5746
|
-
user_id: userId,
|
|
5747
|
-
agent_id: resolvedAgentId,
|
|
5758
|
+
candidates.push({
|
|
5748
5759
|
tier: "L3_pattern",
|
|
5749
5760
|
content: fact,
|
|
5750
|
-
|
|
5761
|
+
contextKey: `fact:${fact.slice(0, 50).toLowerCase().replace(/\s+/g, "_")}`,
|
|
5751
5762
|
importance: 6,
|
|
5752
|
-
context_key: contextKey,
|
|
5753
5763
|
});
|
|
5754
5764
|
}
|
|
5755
5765
|
}
|
|
5756
|
-
|
|
5757
|
-
// Extract directives → L4_rule
|
|
5758
|
-
const rulePatterns = [
|
|
5759
|
-
/(?:always|never|make sure|don't|do not|please always)\s+(.+?)(?:\.|!|$)/gi,
|
|
5760
|
-
/(?:remember to|keep in mind|note that)\s+(.+?)(?:\.|!|$)/gi,
|
|
5761
|
-
];
|
|
5762
|
-
|
|
5763
5766
|
for (const pattern of rulePatterns) {
|
|
5764
|
-
const
|
|
5765
|
-
for (const match of matches) {
|
|
5767
|
+
for (const match of lastUser.matchAll(pattern)) {
|
|
5766
5768
|
const rule = match[0].trim();
|
|
5767
5769
|
if (rule.length < 10) continue;
|
|
5768
|
-
|
|
5769
|
-
const { data: existing } = await supabase.from("agent_memories")
|
|
5770
|
-
.select("id").eq("user_id", userId).eq("agent_id", resolvedAgentId)
|
|
5771
|
-
.eq("context_key", contextKey).is("superseded_by", null).limit(1);
|
|
5772
|
-
if (existing && existing.length > 0) continue;
|
|
5773
|
-
|
|
5774
|
-
await supabase.from("agent_memories").insert({
|
|
5775
|
-
user_id: userId,
|
|
5776
|
-
agent_id: resolvedAgentId,
|
|
5770
|
+
candidates.push({
|
|
5777
5771
|
tier: "L4_rule",
|
|
5778
5772
|
content: rule,
|
|
5779
|
-
|
|
5773
|
+
contextKey: `rule:${rule.slice(0, 50).toLowerCase().replace(/\s+/g, "_")}`,
|
|
5780
5774
|
importance: 7,
|
|
5781
|
-
context_key: contextKey,
|
|
5782
5775
|
});
|
|
5783
5776
|
}
|
|
5784
5777
|
}
|
|
5785
5778
|
|
|
5779
|
+
// Phase 2: Parallel dedup check (1 query per candidate, all at once)
|
|
5780
|
+
if (candidates.length > 0) {
|
|
5781
|
+
const dedupResults = await Promise.all(
|
|
5782
|
+
candidates.map(c =>
|
|
5783
|
+
supabase.from("agent_memories")
|
|
5784
|
+
.select("id")
|
|
5785
|
+
.eq("user_id", userId)
|
|
5786
|
+
.eq("agent_id", resolvedAgentId)
|
|
5787
|
+
.eq("context_key", c.contextKey)
|
|
5788
|
+
.is("superseded_by", null)
|
|
5789
|
+
.limit(1)
|
|
5790
|
+
)
|
|
5791
|
+
);
|
|
5792
|
+
|
|
5793
|
+
// Phase 3: Batch insert only new candidates
|
|
5794
|
+
const toInsert = candidates
|
|
5795
|
+
.filter((_, i) => !dedupResults[i].data?.length)
|
|
5796
|
+
.map(c => ({
|
|
5797
|
+
user_id: userId,
|
|
5798
|
+
agent_id: resolvedAgentId,
|
|
5799
|
+
tier: c.tier,
|
|
5800
|
+
content: c.content,
|
|
5801
|
+
source: "auto" as const,
|
|
5802
|
+
importance: c.importance,
|
|
5803
|
+
context_key: c.contextKey,
|
|
5804
|
+
decay_score: 1.0,
|
|
5805
|
+
}));
|
|
5806
|
+
|
|
5807
|
+
if (toInsert.length > 0) {
|
|
5808
|
+
await supabase.from("agent_memories").insert(toInsert);
|
|
5809
|
+
}
|
|
5810
|
+
}
|
|
5811
|
+
|
|
5786
5812
|
api.logger.debug?.(`[ofiere-brain] Extracted memory for agent=${resolvedAgentId} session=${sessionKey || "none"}`);
|
|
5787
5813
|
} catch (e) {
|
|
5788
5814
|
// Silent — brain extraction must never block chat
|
|
@@ -5815,11 +5841,9 @@ function registerBrainContextHook(
|
|
|
5815
5841
|
userId: string,
|
|
5816
5842
|
fallbackAgentId: string,
|
|
5817
5843
|
): void {
|
|
5818
|
-
//
|
|
5819
|
-
//
|
|
5820
|
-
|
|
5821
|
-
const brainCache = new Map<string, { text: string; at: number }>();
|
|
5822
|
-
const CACHE_TTL_MS = 60_000;
|
|
5844
|
+
// Uses module-scoped brainCache (declared at top of file) for cross-function
|
|
5845
|
+
// invalidation. save_memory calls brainCache.delete(agentId) for immediate consistency.
|
|
5846
|
+
const CACHE_TTL_MS = 300_000;
|
|
5823
5847
|
|
|
5824
5848
|
try {
|
|
5825
5849
|
api.on("before_prompt_build", async (_event: any, ctx: any) => {
|
|
@@ -5846,38 +5870,26 @@ function registerBrainContextHook(
|
|
|
5846
5870
|
}
|
|
5847
5871
|
|
|
5848
5872
|
// ── Query brain memories for THIS specific agent ──
|
|
5873
|
+
// v4.30.0: Single combined query instead of 3 separate tier queries.
|
|
5849
5874
|
const now = new Date().toISOString();
|
|
5850
5875
|
|
|
5851
|
-
const
|
|
5852
|
-
|
|
5853
|
-
|
|
5854
|
-
|
|
5855
|
-
|
|
5856
|
-
|
|
5857
|
-
|
|
5858
|
-
|
|
5859
|
-
|
|
5860
|
-
|
|
5861
|
-
|
|
5862
|
-
|
|
5863
|
-
|
|
5864
|
-
|
|
5865
|
-
|
|
5866
|
-
|
|
5867
|
-
|
|
5868
|
-
.limit(10),
|
|
5869
|
-
supabase.from("agent_memories")
|
|
5870
|
-
.select("content")
|
|
5871
|
-
.eq("user_id", userId)
|
|
5872
|
-
.eq("agent_id", resolvedAgentId)
|
|
5873
|
-
.eq("tier", "L5_persona")
|
|
5874
|
-
.order("importance", { ascending: false })
|
|
5875
|
-
.limit(3),
|
|
5876
|
-
]);
|
|
5877
|
-
|
|
5878
|
-
const l1 = l1Res.data || [];
|
|
5879
|
-
const l4 = l4Res.data || [];
|
|
5880
|
-
const l5 = l5Res.data || [];
|
|
5876
|
+
const { data: allBrain } = await supabase
|
|
5877
|
+
.from("agent_memories")
|
|
5878
|
+
.select("content, importance, tier")
|
|
5879
|
+
.eq("user_id", userId)
|
|
5880
|
+
.eq("agent_id", resolvedAgentId)
|
|
5881
|
+
.in("tier", ["L1_focus", "L4_rule", "L5_persona"])
|
|
5882
|
+
.gt("decay_score", 0.1)
|
|
5883
|
+
.is("superseded_by", null)
|
|
5884
|
+
.or(`expires_at.is.null,expires_at.gt.${now}`)
|
|
5885
|
+
.order("importance", { ascending: false })
|
|
5886
|
+
.limit(18); // 5 L1 + 10 L4 + 3 L5
|
|
5887
|
+
|
|
5888
|
+
// Client-side split by tier
|
|
5889
|
+
const all = allBrain || [];
|
|
5890
|
+
const l1 = all.filter((m: any) => m.tier === "L1_focus").slice(0, 5);
|
|
5891
|
+
const l4 = all.filter((m: any) => m.tier === "L4_rule").slice(0, 10);
|
|
5892
|
+
const l5 = all.filter((m: any) => m.tier === "L5_persona").slice(0, 3);
|
|
5881
5893
|
|
|
5882
5894
|
if (l1.length === 0 && l4.length === 0 && l5.length === 0) {
|
|
5883
5895
|
brainCache.set(resolvedAgentId, { text: "", at: Date.now() });
|