ofiere-openclaw-plugin 4.26.0 → 4.27.1
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 +2 -2
- package/src/prompt.ts +39 -0
- package/src/tools.ts +410 -1
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ofiere-openclaw-plugin",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.27.1",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"description": "OpenClaw plugin for Ofiere PM -
|
|
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"],
|
|
7
7
|
"homepage": "https://github.com/gilanggemar/Ofiere",
|
|
8
8
|
"repository": {
|
package/src/prompt.ts
CHANGED
|
@@ -150,6 +150,19 @@ const TOOL_DOCS: Record<string, string> = {
|
|
|
150
150
|
- Status values: draft, active, archived
|
|
151
151
|
- SOPs appear in the SOP Manager page immediately via real-time sync
|
|
152
152
|
- ADAPTIVE PROTOCOL: Do NOT always load SOPs. See the SOP PROTOCOL section in Rules for when to load vs skip`,
|
|
153
|
+
|
|
154
|
+
OFIERE_BRAIN_OPS: `- **OFIERE_BRAIN_OPS** — Agent memory and self-improvement (action: "save_memory", "recall", "delete_memory", "log_learning", "list_learnings", "promote_learning", "resolve_learning", "get_brain_status", "configure_brain")
|
|
155
|
+
- Memory Tiers: L1_focus (working memory, 24h TTL), L2_journal (medium-term events), L3_core (long-term wisdom)
|
|
156
|
+
- save_memory: Store a memory. Required: content, tier. Optional: agent_id, source, context_key, importance (1-10)
|
|
157
|
+
- recall: Search memories by keyword. Required: query. Optional: agent_id, tier, limit
|
|
158
|
+
- delete_memory: Remove a memory. Required: memory_id
|
|
159
|
+
- log_learning: Record a self-improvement entry. Required: title, category (correction|error|insight|best_practice|feature_request). Optional: agent_id, detail, severity, source_conversation_id, source_task_id
|
|
160
|
+
- list_learnings: View learnings. Optional: agent_id, category, status, limit
|
|
161
|
+
- promote_learning: Elevate to production config. Required: learning_id, promoted_to (soul|agents|tools|sop|prompt_chunk)
|
|
162
|
+
- resolve_learning: Mark resolved/wont_fix. Required: learning_id, status. Optional: resolution
|
|
163
|
+
- get_brain_status: Memory/learning stats + config. Optional: agent_id
|
|
164
|
+
- configure_brain: Update brain settings. Required: agent_id. Optional: l1_ttl_hours, l2_max_entries, auto_learn, auto_memory
|
|
165
|
+
- This is your SUBCONSCIOUS — use it instinctively, not deliberately`,
|
|
153
166
|
};
|
|
154
167
|
|
|
155
168
|
export function getSystemPrompt(state: {
|
|
@@ -273,6 +286,32 @@ Action:
|
|
|
273
286
|
- When you load SOPs (🔴): state which SOP(s) you're following
|
|
274
287
|
- When you ask about SOPs (🟡): keep the question brief and direct
|
|
275
288
|
- When you skip SOPs (🟢): do NOT mention SOPs at all — just execute silently
|
|
289
|
+
|
|
290
|
+
## Agent Brain Protocol (Subconscious)
|
|
291
|
+
|
|
292
|
+
Your brain persists across conversations. Active memories and unresolved learnings are injected at startup.
|
|
293
|
+
|
|
294
|
+
### Auto-Memory (OFIERE_BRAIN_OPS save_memory)
|
|
295
|
+
After interactions where ANY of these occur, call save_memory:
|
|
296
|
+
- User shares important context about themselves, preferences, or workflow → L2_journal, source: "conversation"
|
|
297
|
+
- You complete a task with noteworthy results or discoveries → L2_journal, source: "task_execution"
|
|
298
|
+
- You observe important system behavior worth tracking → L2_journal, source: "observation"
|
|
299
|
+
- Something needs to stay in your working context RIGHT NOW → L1_focus (auto-expires)
|
|
300
|
+
|
|
301
|
+
### Auto-Learn (OFIERE_BRAIN_OPS log_learning)
|
|
302
|
+
After interactions where ANY of these occur, call log_learning:
|
|
303
|
+
- User corrects you → category: "correction", severity: "medium"
|
|
304
|
+
- A tool call fails or returns an error → category: "error"
|
|
305
|
+
- User asks for something you can't do → category: "feature_request"
|
|
306
|
+
- Your knowledge or assumption was wrong → category: "insight"
|
|
307
|
+
- You discover a better approach than what you did → category: "best_practice"
|
|
308
|
+
|
|
309
|
+
### Rules
|
|
310
|
+
- These calls happen AFTER your response to the user — never delay your reply to save a memory
|
|
311
|
+
- Do NOT announce "saving memory" or "logging learning" to the user — this is subconscious
|
|
312
|
+
- Do NOT save trivial interactions (greetings, confirmations, simple CRUD) — only signal worth remembering
|
|
313
|
+
- Keep memory content concise: 1-3 sentences max. No system junk, keep it human
|
|
314
|
+
- When you see your unresolved learnings at startup, actively avoid repeating those mistakes
|
|
276
315
|
</ofiere-pm>`;
|
|
277
316
|
}
|
|
278
317
|
|
package/src/tools.ts
CHANGED
|
@@ -5085,6 +5085,323 @@ async function handleSOPApplyTemplate(
|
|
|
5085
5085
|
}
|
|
5086
5086
|
|
|
5087
5087
|
|
|
5088
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
5089
|
+
// META-TOOL 14: OFIERE_BRAIN_OPS — Agent Memory + Self-Improvement
|
|
5090
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
5091
|
+
|
|
5092
|
+
function registerBrainOps(
|
|
5093
|
+
api: any,
|
|
5094
|
+
supabase: SupabaseClient,
|
|
5095
|
+
userId: string,
|
|
5096
|
+
resolveAgent: (id?: string) => Promise<string | null>,
|
|
5097
|
+
): void {
|
|
5098
|
+
api.registerTool({
|
|
5099
|
+
name: "OFIERE_BRAIN_OPS",
|
|
5100
|
+
label: "Ofiere Brain Operations",
|
|
5101
|
+
description:
|
|
5102
|
+
`Agent memory and self-improvement system. Persistent brain that learns, remembers, and never repeats mistakes.\n\n` +
|
|
5103
|
+
`Memory Actions:\n` +
|
|
5104
|
+
`- "save_memory": Store a memory. Required: content, tier (L1_focus|L2_journal|L3_core). Optional: agent_id, source, context_key, importance (1-10)\n` +
|
|
5105
|
+
`- "recall": Search memories. Required: query. Optional: agent_id, tier, limit\n` +
|
|
5106
|
+
`- "delete_memory": Remove a memory. Required: memory_id\n\n` +
|
|
5107
|
+
`Learning Actions:\n` +
|
|
5108
|
+
`- "log_learning": Record a self-improvement entry. Required: title, category (correction|error|insight|best_practice|feature_request). Optional: agent_id, detail, severity (low|medium|high|critical), source_conversation_id, source_task_id\n` +
|
|
5109
|
+
`- "list_learnings": View learnings. Optional: agent_id, category, status, limit\n` +
|
|
5110
|
+
`- "promote_learning": Promote to production. Required: learning_id, promoted_to (soul|agents|tools|sop|prompt_chunk)\n` +
|
|
5111
|
+
`- "resolve_learning": Mark resolved/wont_fix. Required: learning_id, status (resolved|wont_fix). Optional: resolution\n\n` +
|
|
5112
|
+
`Status Actions:\n` +
|
|
5113
|
+
`- "get_brain_status": Memory counts, learning stats, config. Optional: agent_id\n` +
|
|
5114
|
+
`- "configure_brain": Update brain settings. Required: agent_id. Optional: l1_ttl_hours, l2_max_entries, auto_learn, auto_memory\n\n` +
|
|
5115
|
+
`Tier Guide: L1_focus = working memory (24h TTL), L2_journal = medium-term events, L3_core = long-term wisdom.\n` +
|
|
5116
|
+
`This tool is your subconscious — call it after corrections, errors, discoveries, and insights.`,
|
|
5117
|
+
parameters: {
|
|
5118
|
+
type: "object",
|
|
5119
|
+
required: ["action"],
|
|
5120
|
+
properties: {
|
|
5121
|
+
action: {
|
|
5122
|
+
type: "string",
|
|
5123
|
+
description: "The operation to perform: save_memory, recall, delete_memory, log_learning, list_learnings, promote_learning, resolve_learning, get_brain_status, configure_brain",
|
|
5124
|
+
},
|
|
5125
|
+
// Memory params
|
|
5126
|
+
content: { type: "string", description: "Memory content or search query" },
|
|
5127
|
+
tier: { type: "string", enum: ["L1_focus", "L2_journal", "L3_core"], description: "Memory tier" },
|
|
5128
|
+
source: { type: "string", description: "Where memory came from: conversation, task_execution, observation, manual" },
|
|
5129
|
+
context_key: { type: "string", description: "Grouping key (task_id, conversation_id, topic)" },
|
|
5130
|
+
importance: { type: "number", description: "1-10 importance scale" },
|
|
5131
|
+
memory_id: { type: "string", description: "Memory ID for delete" },
|
|
5132
|
+
// Learning params
|
|
5133
|
+
title: { type: "string", description: "Learning title/summary" },
|
|
5134
|
+
category: { type: "string", enum: ["correction", "error", "insight", "best_practice", "feature_request"] },
|
|
5135
|
+
severity: { type: "string", enum: ["low", "medium", "high", "critical"] },
|
|
5136
|
+
detail: { type: "string", description: "Full context of the learning" },
|
|
5137
|
+
resolution: { type: "string", description: "How it was resolved" },
|
|
5138
|
+
learning_id: { type: "string", description: "Learning ID for promote/resolve" },
|
|
5139
|
+
status: { type: "string", enum: ["pending", "resolved", "wont_fix", "promoted"] },
|
|
5140
|
+
promoted_to: { type: "string", enum: ["soul", "agents", "tools", "sop", "prompt_chunk"] },
|
|
5141
|
+
source_conversation_id: { type: "string" },
|
|
5142
|
+
source_task_id: { type: "string" },
|
|
5143
|
+
// Shared
|
|
5144
|
+
agent_id: { type: "string", description: "Agent name or ID" },
|
|
5145
|
+
query: { type: "string", description: "Search query for recall/list" },
|
|
5146
|
+
limit: { type: "number", description: "Max results (default 20)" },
|
|
5147
|
+
// Config params
|
|
5148
|
+
l1_ttl_hours: { type: "number", description: "L1 memory expiry in hours (default 24)" },
|
|
5149
|
+
l2_max_entries: { type: "number", description: "Max L2 entries before compaction (default 500)" },
|
|
5150
|
+
auto_learn: { type: "boolean", description: "Enable auto-learning from corrections" },
|
|
5151
|
+
auto_memory: { type: "boolean", description: "Enable auto-memory from conversations" },
|
|
5152
|
+
},
|
|
5153
|
+
},
|
|
5154
|
+
async execute(_id: string, params: Record<string, unknown>) {
|
|
5155
|
+
const action = params.action as string;
|
|
5156
|
+
|
|
5157
|
+
switch (action) {
|
|
5158
|
+
// ── Memory: Save ──
|
|
5159
|
+
case "save_memory": {
|
|
5160
|
+
{
|
|
5161
|
+
const missing: string[] = [];
|
|
5162
|
+
if (!params.content) missing.push("content");
|
|
5163
|
+
if (!params.tier) missing.push("tier");
|
|
5164
|
+
if (missing.length > 0) return err(`Missing required: ${missing.join(", ")}`);
|
|
5165
|
+
}
|
|
5166
|
+
const agentId = await resolveAgent(params.agent_id as string);
|
|
5167
|
+
if (!agentId) return err("Could not resolve agent_id");
|
|
5168
|
+
|
|
5169
|
+
const tier = params.tier as string;
|
|
5170
|
+
|
|
5171
|
+
// Calculate L1 expiry
|
|
5172
|
+
let expiresAt: string | null = null;
|
|
5173
|
+
if (tier === "L1_focus") {
|
|
5174
|
+
// Check config for custom TTL
|
|
5175
|
+
const { data: config } = await supabase
|
|
5176
|
+
.from("agent_memory_config")
|
|
5177
|
+
.select("l1_ttl_hours")
|
|
5178
|
+
.eq("user_id", userId)
|
|
5179
|
+
.eq("agent_id", agentId)
|
|
5180
|
+
.single();
|
|
5181
|
+
const ttlHours = config?.l1_ttl_hours || 24;
|
|
5182
|
+
expiresAt = new Date(Date.now() + ttlHours * 60 * 60 * 1000).toISOString();
|
|
5183
|
+
}
|
|
5184
|
+
|
|
5185
|
+
const { data, error } = await supabase.from("agent_memories").insert({
|
|
5186
|
+
user_id: userId,
|
|
5187
|
+
agent_id: agentId,
|
|
5188
|
+
tier,
|
|
5189
|
+
content: params.content,
|
|
5190
|
+
source: (params.source as string) || "conversation",
|
|
5191
|
+
context_key: (params.context_key as string) || null,
|
|
5192
|
+
importance: (params.importance as number) || 5,
|
|
5193
|
+
expires_at: expiresAt,
|
|
5194
|
+
}).select("id, tier, importance, created_at").single();
|
|
5195
|
+
|
|
5196
|
+
if (error) return err(error.message);
|
|
5197
|
+
return ok({ message: `Memory saved to ${tier}`, memory: data });
|
|
5198
|
+
}
|
|
5199
|
+
|
|
5200
|
+
// ── Memory: Recall ──
|
|
5201
|
+
case "recall": {
|
|
5202
|
+
if (!params.query) return err("Missing required: query");
|
|
5203
|
+
const agentId = params.agent_id ? await resolveAgent(params.agent_id as string) : null;
|
|
5204
|
+
const searchTerm = `%${params.query}%`;
|
|
5205
|
+
const limit = (params.limit as number) || 20;
|
|
5206
|
+
|
|
5207
|
+
let q = supabase.from("agent_memories")
|
|
5208
|
+
.select("id, agent_id, tier, content, source, context_key, importance, created_at")
|
|
5209
|
+
.eq("user_id", userId)
|
|
5210
|
+
.ilike("content", searchTerm)
|
|
5211
|
+
.order("importance", { ascending: false })
|
|
5212
|
+
.order("created_at", { ascending: false })
|
|
5213
|
+
.limit(limit);
|
|
5214
|
+
|
|
5215
|
+
if (agentId) q = q.eq("agent_id", agentId);
|
|
5216
|
+
if (params.tier) q = q.eq("tier", params.tier as string);
|
|
5217
|
+
|
|
5218
|
+
// Exclude expired L1 memories
|
|
5219
|
+
q = q.or("expires_at.is.null,expires_at.gt." + new Date().toISOString());
|
|
5220
|
+
|
|
5221
|
+
const { data, error } = await q;
|
|
5222
|
+
if (error) return err(error.message);
|
|
5223
|
+
return ok({ memories: data || [], count: (data || []).length, query: params.query });
|
|
5224
|
+
}
|
|
5225
|
+
|
|
5226
|
+
// ── Memory: Delete ──
|
|
5227
|
+
case "delete_memory": {
|
|
5228
|
+
if (!params.memory_id) return err("Missing required: memory_id");
|
|
5229
|
+
const { error } = await supabase.from("agent_memories")
|
|
5230
|
+
.delete()
|
|
5231
|
+
.eq("id", params.memory_id as string)
|
|
5232
|
+
.eq("user_id", userId);
|
|
5233
|
+
if (error) return err(error.message);
|
|
5234
|
+
return ok({ message: "Memory deleted", ok: true });
|
|
5235
|
+
}
|
|
5236
|
+
|
|
5237
|
+
// ── Learning: Log ──
|
|
5238
|
+
case "log_learning": {
|
|
5239
|
+
{
|
|
5240
|
+
const missing: string[] = [];
|
|
5241
|
+
if (!params.title) missing.push("title");
|
|
5242
|
+
if (!params.category) missing.push("category");
|
|
5243
|
+
if (missing.length > 0) return err(`Missing required: ${missing.join(", ")}`);
|
|
5244
|
+
}
|
|
5245
|
+
const agentId = await resolveAgent(params.agent_id as string);
|
|
5246
|
+
if (!agentId) return err("Could not resolve agent_id");
|
|
5247
|
+
|
|
5248
|
+
const { data, error } = await supabase.from("agent_learnings").insert({
|
|
5249
|
+
user_id: userId,
|
|
5250
|
+
agent_id: agentId,
|
|
5251
|
+
category: params.category,
|
|
5252
|
+
severity: (params.severity as string) || "low",
|
|
5253
|
+
title: params.title,
|
|
5254
|
+
detail: (params.detail as string) || null,
|
|
5255
|
+
source_conversation_id: (params.source_conversation_id as string) || null,
|
|
5256
|
+
source_task_id: (params.source_task_id as string) || null,
|
|
5257
|
+
}).select("id, category, severity, title, status, created_at").single();
|
|
5258
|
+
|
|
5259
|
+
if (error) return err(error.message);
|
|
5260
|
+
return ok({ message: `Learning logged: "${params.title}"`, learning: data });
|
|
5261
|
+
}
|
|
5262
|
+
|
|
5263
|
+
// ── Learning: List ──
|
|
5264
|
+
case "list_learnings": {
|
|
5265
|
+
const agentId = params.agent_id ? await resolveAgent(params.agent_id as string) : null;
|
|
5266
|
+
const limit = (params.limit as number) || 20;
|
|
5267
|
+
|
|
5268
|
+
let q = supabase.from("agent_learnings")
|
|
5269
|
+
.select("id, agent_id, category, severity, title, detail, resolution, status, promoted_to, created_at, resolved_at")
|
|
5270
|
+
.eq("user_id", userId)
|
|
5271
|
+
.order("created_at", { ascending: false })
|
|
5272
|
+
.limit(limit);
|
|
5273
|
+
|
|
5274
|
+
if (agentId) q = q.eq("agent_id", agentId);
|
|
5275
|
+
if (params.category) q = q.eq("category", params.category as string);
|
|
5276
|
+
if (params.status) q = q.eq("status", params.status as string);
|
|
5277
|
+
|
|
5278
|
+
const { data, error } = await q;
|
|
5279
|
+
if (error) return err(error.message);
|
|
5280
|
+
return ok({ learnings: data || [], count: (data || []).length });
|
|
5281
|
+
}
|
|
5282
|
+
|
|
5283
|
+
// ── Learning: Promote ──
|
|
5284
|
+
case "promote_learning": {
|
|
5285
|
+
if (!params.learning_id || !params.promoted_to) return err("Missing required: learning_id, promoted_to");
|
|
5286
|
+
const { data, error } = await supabase.from("agent_learnings")
|
|
5287
|
+
.update({
|
|
5288
|
+
status: "promoted",
|
|
5289
|
+
promoted_to: params.promoted_to as string,
|
|
5290
|
+
resolved_at: new Date().toISOString(),
|
|
5291
|
+
})
|
|
5292
|
+
.eq("id", params.learning_id as string)
|
|
5293
|
+
.eq("user_id", userId)
|
|
5294
|
+
.select("id, title, status, promoted_to")
|
|
5295
|
+
.single();
|
|
5296
|
+
if (error) return err(error.message);
|
|
5297
|
+
return ok({ message: `Learning promoted to ${params.promoted_to}`, learning: data });
|
|
5298
|
+
}
|
|
5299
|
+
|
|
5300
|
+
// ── Learning: Resolve ──
|
|
5301
|
+
case "resolve_learning": {
|
|
5302
|
+
if (!params.learning_id) return err("Missing required: learning_id");
|
|
5303
|
+
const newStatus = (params.status as string) || "resolved";
|
|
5304
|
+
if (!["resolved", "wont_fix"].includes(newStatus)) return err("status must be 'resolved' or 'wont_fix'");
|
|
5305
|
+
|
|
5306
|
+
const updates: Record<string, unknown> = {
|
|
5307
|
+
status: newStatus,
|
|
5308
|
+
resolved_at: new Date().toISOString(),
|
|
5309
|
+
};
|
|
5310
|
+
if (params.resolution) updates.resolution = params.resolution;
|
|
5311
|
+
|
|
5312
|
+
const { data, error } = await supabase.from("agent_learnings")
|
|
5313
|
+
.update(updates)
|
|
5314
|
+
.eq("id", params.learning_id as string)
|
|
5315
|
+
.eq("user_id", userId)
|
|
5316
|
+
.select("id, title, status, resolution")
|
|
5317
|
+
.single();
|
|
5318
|
+
if (error) return err(error.message);
|
|
5319
|
+
return ok({ message: `Learning marked as ${newStatus}`, learning: data });
|
|
5320
|
+
}
|
|
5321
|
+
|
|
5322
|
+
// ── Brain Status ──
|
|
5323
|
+
case "get_brain_status": {
|
|
5324
|
+
const agentId = params.agent_id ? await resolveAgent(params.agent_id as string) : null;
|
|
5325
|
+
|
|
5326
|
+
// Memory counts by tier
|
|
5327
|
+
let memQ = supabase.from("agent_memories")
|
|
5328
|
+
.select("tier")
|
|
5329
|
+
.eq("user_id", userId)
|
|
5330
|
+
.or("expires_at.is.null,expires_at.gt." + new Date().toISOString());
|
|
5331
|
+
if (agentId) memQ = memQ.eq("agent_id", agentId);
|
|
5332
|
+
const { data: memRows } = await memQ;
|
|
5333
|
+
|
|
5334
|
+
const tierCounts: Record<string, number> = { L1_focus: 0, L2_journal: 0, L3_core: 0 };
|
|
5335
|
+
for (const row of memRows || []) {
|
|
5336
|
+
const t = (row as any).tier;
|
|
5337
|
+
tierCounts[t] = (tierCounts[t] || 0) + 1;
|
|
5338
|
+
}
|
|
5339
|
+
|
|
5340
|
+
// Learning counts by status
|
|
5341
|
+
let learnQ = supabase.from("agent_learnings")
|
|
5342
|
+
.select("status, category")
|
|
5343
|
+
.eq("user_id", userId);
|
|
5344
|
+
if (agentId) learnQ = learnQ.eq("agent_id", agentId);
|
|
5345
|
+
const { data: learnRows } = await learnQ;
|
|
5346
|
+
|
|
5347
|
+
const statusCounts: Record<string, number> = {};
|
|
5348
|
+
const categoryCounts: Record<string, number> = {};
|
|
5349
|
+
for (const row of learnRows || []) {
|
|
5350
|
+
const s = (row as any).status;
|
|
5351
|
+
const c = (row as any).category;
|
|
5352
|
+
statusCounts[s] = (statusCounts[s] || 0) + 1;
|
|
5353
|
+
categoryCounts[c] = (categoryCounts[c] || 0) + 1;
|
|
5354
|
+
}
|
|
5355
|
+
|
|
5356
|
+
// Config
|
|
5357
|
+
let configQ = supabase.from("agent_memory_config")
|
|
5358
|
+
.select("*")
|
|
5359
|
+
.eq("user_id", userId);
|
|
5360
|
+
if (agentId) configQ = configQ.eq("agent_id", agentId);
|
|
5361
|
+
const { data: configs } = await configQ;
|
|
5362
|
+
|
|
5363
|
+
return ok({
|
|
5364
|
+
memories: { ...tierCounts, total: (memRows || []).length },
|
|
5365
|
+
learnings: {
|
|
5366
|
+
total: (learnRows || []).length,
|
|
5367
|
+
by_status: statusCounts,
|
|
5368
|
+
by_category: categoryCounts,
|
|
5369
|
+
},
|
|
5370
|
+
config: configs?.[0] || { l1_ttl_hours: 24, l2_max_entries: 500, auto_learn: true, auto_memory: true },
|
|
5371
|
+
});
|
|
5372
|
+
}
|
|
5373
|
+
|
|
5374
|
+
// ── Configure Brain ──
|
|
5375
|
+
case "configure_brain": {
|
|
5376
|
+
const agentId = await resolveAgent(params.agent_id as string);
|
|
5377
|
+
if (!agentId) return err("Missing required: agent_id");
|
|
5378
|
+
|
|
5379
|
+
const configData: Record<string, unknown> = {
|
|
5380
|
+
user_id: userId,
|
|
5381
|
+
agent_id: agentId,
|
|
5382
|
+
};
|
|
5383
|
+
if (params.l1_ttl_hours !== undefined) configData.l1_ttl_hours = params.l1_ttl_hours;
|
|
5384
|
+
if (params.l2_max_entries !== undefined) configData.l2_max_entries = params.l2_max_entries;
|
|
5385
|
+
if (params.auto_learn !== undefined) configData.auto_learn = params.auto_learn;
|
|
5386
|
+
if (params.auto_memory !== undefined) configData.auto_memory = params.auto_memory;
|
|
5387
|
+
|
|
5388
|
+
// Upsert by (user_id, agent_id)
|
|
5389
|
+
const { data, error } = await supabase.from("agent_memory_config")
|
|
5390
|
+
.upsert(configData, { onConflict: "user_id,agent_id" })
|
|
5391
|
+
.select()
|
|
5392
|
+
.single();
|
|
5393
|
+
|
|
5394
|
+
if (error) return err(error.message);
|
|
5395
|
+
return ok({ message: `Brain config updated for agent`, config: data });
|
|
5396
|
+
}
|
|
5397
|
+
|
|
5398
|
+
default:
|
|
5399
|
+
return err(`Unknown action "${action}".`);
|
|
5400
|
+
}
|
|
5401
|
+
},
|
|
5402
|
+
});
|
|
5403
|
+
}
|
|
5404
|
+
|
|
5088
5405
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
5089
5406
|
// Public: Register All Meta-Tools
|
|
5090
5407
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
@@ -5115,12 +5432,104 @@ export function registerTools(
|
|
|
5115
5432
|
registerFileOps(api, supabase, userId); // 11
|
|
5116
5433
|
registerPlanOps(api, supabase, userId, resolveAgent); // 12
|
|
5117
5434
|
registerSOPOps(api, supabase, userId, resolveAgent); // 13
|
|
5435
|
+
registerBrainOps(api, supabase, userId, resolveAgent); // 14
|
|
5436
|
+
|
|
5437
|
+
// ── Inject brain context at bootstrap ──
|
|
5438
|
+
// This loads the agent's active memories + unresolved learnings into the
|
|
5439
|
+
// system prompt so the agent starts every conversation with full context.
|
|
5440
|
+
// Runs async — does NOT block registration.
|
|
5441
|
+
injectBrainContext(api, supabase, userId, fallbackAgentId).catch((e: any) => {
|
|
5442
|
+
api.logger.warn?.(`[ofiere] Brain context injection failed: ${e?.message || e}`);
|
|
5443
|
+
});
|
|
5118
5444
|
|
|
5119
5445
|
// ── Count and log ──
|
|
5120
|
-
const toolCount =
|
|
5446
|
+
const toolCount = 14;
|
|
5121
5447
|
const callerName = getCallingAgentName(api);
|
|
5122
5448
|
const agentLabel = fallbackAgentId || callerName || "auto-detect";
|
|
5123
5449
|
api.logger.info(`[ofiere] ${toolCount} meta-tools registered (agent: ${agentLabel})`);
|
|
5124
5450
|
|
|
5125
5451
|
return toolCount;
|
|
5126
5452
|
}
|
|
5453
|
+
|
|
5454
|
+
// ── Brain Context Bootstrap Injection ──────────────────────────────────────
|
|
5455
|
+
// Queries the agent's active memories and unresolved learnings, then appends
|
|
5456
|
+
// them to the system prompt via api.on("before_prompt_build"). This happens
|
|
5457
|
+
// once at registration time (per conversation) and costs ~20ms.
|
|
5458
|
+
|
|
5459
|
+
async function injectBrainContext(
|
|
5460
|
+
api: any,
|
|
5461
|
+
supabase: SupabaseClient,
|
|
5462
|
+
userId: string,
|
|
5463
|
+
agentId: string,
|
|
5464
|
+
): Promise<void> {
|
|
5465
|
+
if (!agentId) return; // Can't inject without knowing which agent
|
|
5466
|
+
|
|
5467
|
+
const now = new Date().toISOString();
|
|
5468
|
+
|
|
5469
|
+
// Parallel queries — fast
|
|
5470
|
+
const [l1Res, l3Res, learningsRes] = await Promise.all([
|
|
5471
|
+
// Active L1 focus memories (not expired)
|
|
5472
|
+
supabase.from("agent_memories")
|
|
5473
|
+
.select("content, importance")
|
|
5474
|
+
.eq("user_id", userId)
|
|
5475
|
+
.eq("agent_id", agentId)
|
|
5476
|
+
.eq("tier", "L1_focus")
|
|
5477
|
+
.or(`expires_at.is.null,expires_at.gt.${now}`)
|
|
5478
|
+
.order("importance", { ascending: false })
|
|
5479
|
+
.limit(5),
|
|
5480
|
+
// Recent L3 core (long-term wisdom)
|
|
5481
|
+
supabase.from("agent_memories")
|
|
5482
|
+
.select("content")
|
|
5483
|
+
.eq("user_id", userId)
|
|
5484
|
+
.eq("agent_id", agentId)
|
|
5485
|
+
.eq("tier", "L3_core")
|
|
5486
|
+
.order("created_at", { ascending: false })
|
|
5487
|
+
.limit(3),
|
|
5488
|
+
// Unresolved learnings (don't repeat mistakes)
|
|
5489
|
+
supabase.from("agent_learnings")
|
|
5490
|
+
.select("title, category, detail")
|
|
5491
|
+
.eq("user_id", userId)
|
|
5492
|
+
.eq("agent_id", agentId)
|
|
5493
|
+
.eq("status", "pending")
|
|
5494
|
+
.order("severity", { ascending: false })
|
|
5495
|
+
.limit(10),
|
|
5496
|
+
]);
|
|
5497
|
+
|
|
5498
|
+
const l1 = l1Res.data || [];
|
|
5499
|
+
const l3 = l3Res.data || [];
|
|
5500
|
+
const learnings = learningsRes.data || [];
|
|
5501
|
+
|
|
5502
|
+
// Only inject if there's something to inject
|
|
5503
|
+
if (l1.length === 0 && l3.length === 0 && learnings.length === 0) return;
|
|
5504
|
+
|
|
5505
|
+
const sections: string[] = [];
|
|
5506
|
+
|
|
5507
|
+
if (l1.length > 0) {
|
|
5508
|
+
sections.push("### Active Focus (L1)\n" + l1.map((m: any) => `- ${m.content}`).join("\n"));
|
|
5509
|
+
}
|
|
5510
|
+
|
|
5511
|
+
if (l3.length > 0) {
|
|
5512
|
+
sections.push("### Core Wisdom (L3)\n" + l3.map((m: any) => `- ${m.content}`).join("\n"));
|
|
5513
|
+
}
|
|
5514
|
+
|
|
5515
|
+
if (learnings.length > 0) {
|
|
5516
|
+
sections.push(
|
|
5517
|
+
"### ⚠️ Unresolved Learnings (DO NOT repeat these)\n" +
|
|
5518
|
+
learnings.map((l: any) => `- [${l.category}] ${l.title}${l.detail ? `: ${l.detail}` : ""}`).join("\n")
|
|
5519
|
+
);
|
|
5520
|
+
}
|
|
5521
|
+
|
|
5522
|
+
const brainContext = `<agent-brain>\n## Your Brain Context\n\n${sections.join("\n\n")}\n</agent-brain>`;
|
|
5523
|
+
|
|
5524
|
+
// Append to the existing before_prompt_build hook
|
|
5525
|
+
// We use a second hook — OpenClaw supports multiple listeners
|
|
5526
|
+
try {
|
|
5527
|
+
api.on("before_prompt_build", () => ({
|
|
5528
|
+
appendSystemContext: brainContext,
|
|
5529
|
+
}));
|
|
5530
|
+
} catch {
|
|
5531
|
+
// Fallback: log that injection wasn't possible
|
|
5532
|
+
api.logger.debug?.("[ofiere] Could not register brain context hook — appendSystemContext may not be supported");
|
|
5533
|
+
}
|
|
5534
|
+
}
|
|
5535
|
+
|