ofiere-openclaw-plugin 4.29.0 → 4.30.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/tools.ts +147 -79
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ofiere-openclaw-plugin",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.30.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/tools.ts
CHANGED
|
@@ -5095,7 +5095,9 @@ function registerBrainOps(
|
|
|
5095
5095
|
userId: string,
|
|
5096
5096
|
resolveAgent: (id?: string) => Promise<string | null>,
|
|
5097
5097
|
): void {
|
|
5098
|
-
|
|
5098
|
+
// Register as a FACTORY so OpenClaw passes ToolContext with agentAccountId.
|
|
5099
|
+
// This is the primary fix for the Daisy→Celia misattribution bug.
|
|
5100
|
+
api.registerTool((toolCtx: any) => ({
|
|
5099
5101
|
name: "OFIERE_BRAIN_OPS",
|
|
5100
5102
|
label: "Ofiere Brain Operations",
|
|
5101
5103
|
description:
|
|
@@ -5165,6 +5167,21 @@ function registerBrainOps(
|
|
|
5165
5167
|
async execute(_id: string, params: Record<string, unknown>) {
|
|
5166
5168
|
const action = params.action as string;
|
|
5167
5169
|
|
|
5170
|
+
// ── Resolve agent identity ──────────────────────────────────
|
|
5171
|
+
// Priority: explicit param > factory context > resolver fallback
|
|
5172
|
+
// The factory context (toolCtx) carries the actual calling agent's
|
|
5173
|
+
// accountId from OpenClaw, fixing the Daisy→Celia misattribution.
|
|
5174
|
+
const ctxAgentHint = toolCtx?.agentAccountId || toolCtx?.agentId || "";
|
|
5175
|
+
|
|
5176
|
+
async function resolveCallingAgent(explicitId?: string): Promise<string | null> {
|
|
5177
|
+
// 1. Explicit param from LLM
|
|
5178
|
+
if (explicitId && explicitId.trim()) return resolveAgent(explicitId);
|
|
5179
|
+
// 2. OpenClaw tool context (the fix)
|
|
5180
|
+
if (ctxAgentHint) return resolveAgent(ctxAgentHint);
|
|
5181
|
+
// 3. Original resolver fallback
|
|
5182
|
+
return resolveAgent();
|
|
5183
|
+
}
|
|
5184
|
+
|
|
5168
5185
|
switch (action) {
|
|
5169
5186
|
// ── Memory: Save ──
|
|
5170
5187
|
case "save_memory": {
|
|
@@ -5174,7 +5191,7 @@ function registerBrainOps(
|
|
|
5174
5191
|
if (!params.tier) missing.push("tier");
|
|
5175
5192
|
if (missing.length > 0) return err(`Missing required: ${missing.join(", ")}`);
|
|
5176
5193
|
}
|
|
5177
|
-
const agentId = await
|
|
5194
|
+
const agentId = await resolveCallingAgent(params.agent_id as string);
|
|
5178
5195
|
if (!agentId) return err("Could not resolve agent_id");
|
|
5179
5196
|
|
|
5180
5197
|
const tier = params.tier as string;
|
|
@@ -5202,7 +5219,9 @@ function registerBrainOps(
|
|
|
5202
5219
|
// ── Memory: Recall (Full-text search) ──
|
|
5203
5220
|
case "recall": {
|
|
5204
5221
|
if (!params.query) return err("Missing required: query");
|
|
5205
|
-
const agentId = params.agent_id
|
|
5222
|
+
const agentId = params.agent_id
|
|
5223
|
+
? await resolveAgent(params.agent_id as string)
|
|
5224
|
+
: (ctxAgentHint ? await resolveAgent(ctxAgentHint) : null);
|
|
5206
5225
|
const limit = (params.limit as number) || 20;
|
|
5207
5226
|
const queryText = params.query as string;
|
|
5208
5227
|
|
|
@@ -5281,7 +5300,7 @@ function registerBrainOps(
|
|
|
5281
5300
|
if (!params.category) missing.push("category");
|
|
5282
5301
|
if (missing.length > 0) return err(`Missing required: ${missing.join(", ")}`);
|
|
5283
5302
|
}
|
|
5284
|
-
const agentId = await
|
|
5303
|
+
const agentId = await resolveCallingAgent(params.agent_id as string);
|
|
5285
5304
|
if (!agentId) return err("Could not resolve agent_id");
|
|
5286
5305
|
|
|
5287
5306
|
const content = `[${params.category}] ${params.title}${params.detail ? `: ${params.detail}` : ""}`;
|
|
@@ -5305,7 +5324,9 @@ function registerBrainOps(
|
|
|
5305
5324
|
|
|
5306
5325
|
// ── Learning: List (→ L4_rule query) ──
|
|
5307
5326
|
case "list_learnings": {
|
|
5308
|
-
const agentId = params.agent_id
|
|
5327
|
+
const agentId = params.agent_id
|
|
5328
|
+
? await resolveAgent(params.agent_id as string)
|
|
5329
|
+
: (ctxAgentHint ? await resolveAgent(ctxAgentHint) : null);
|
|
5309
5330
|
const limit = (params.limit as number) || 20;
|
|
5310
5331
|
|
|
5311
5332
|
let q = supabase.from("agent_memories")
|
|
@@ -5331,7 +5352,7 @@ function registerBrainOps(
|
|
|
5331
5352
|
|
|
5332
5353
|
if (params.resolution) {
|
|
5333
5354
|
// Create a superseding memory with the resolution
|
|
5334
|
-
const agentId = await
|
|
5355
|
+
const agentId = await resolveCallingAgent(params.agent_id as string);
|
|
5335
5356
|
const { data: newMem } = await supabase.from("agent_memories").insert({
|
|
5336
5357
|
user_id: userId,
|
|
5337
5358
|
agent_id: agentId || "system",
|
|
@@ -5364,7 +5385,7 @@ function registerBrainOps(
|
|
|
5364
5385
|
case "save_entity": {
|
|
5365
5386
|
if (!params.label) return err("Missing required: label");
|
|
5366
5387
|
if (!params.node_type) return err("Missing required: node_type");
|
|
5367
|
-
const agentId = await
|
|
5388
|
+
const agentId = await resolveCallingAgent(params.agent_id as string);
|
|
5368
5389
|
if (!agentId) return err("Could not resolve agent_id");
|
|
5369
5390
|
|
|
5370
5391
|
const { data, error } = await supabase.from("knowledge_graph_nodes").insert({
|
|
@@ -5404,7 +5425,9 @@ function registerBrainOps(
|
|
|
5404
5425
|
// ── Knowledge Graph: Query ──
|
|
5405
5426
|
case "query_graph": {
|
|
5406
5427
|
if (!params.query) return err("Missing required: query");
|
|
5407
|
-
const agentId = params.agent_id
|
|
5428
|
+
const agentId = params.agent_id
|
|
5429
|
+
? await resolveAgent(params.agent_id as string)
|
|
5430
|
+
: (ctxAgentHint ? await resolveAgent(ctxAgentHint) : null);
|
|
5408
5431
|
const limit = (params.limit as number) || 20;
|
|
5409
5432
|
|
|
5410
5433
|
let q = supabase.from("knowledge_graph_nodes")
|
|
@@ -5436,7 +5459,7 @@ function registerBrainOps(
|
|
|
5436
5459
|
|
|
5437
5460
|
// ── Trajectory: Start ──
|
|
5438
5461
|
case "start_trajectory": {
|
|
5439
|
-
const agentId = await
|
|
5462
|
+
const agentId = await resolveCallingAgent(params.agent_id as string);
|
|
5440
5463
|
if (!agentId) return err("Could not resolve agent_id");
|
|
5441
5464
|
|
|
5442
5465
|
const { data, error } = await supabase.from("execution_trajectories").insert({
|
|
@@ -5475,7 +5498,9 @@ function registerBrainOps(
|
|
|
5475
5498
|
|
|
5476
5499
|
// ── Brain Status ──
|
|
5477
5500
|
case "get_brain_status": {
|
|
5478
|
-
const agentId = params.agent_id
|
|
5501
|
+
const agentId = params.agent_id
|
|
5502
|
+
? await resolveAgent(params.agent_id as string)
|
|
5503
|
+
: (ctxAgentHint ? await resolveAgent(ctxAgentHint) : null);
|
|
5479
5504
|
const now = new Date().toISOString();
|
|
5480
5505
|
|
|
5481
5506
|
// Parallel queries for speed
|
|
@@ -5533,7 +5558,7 @@ function registerBrainOps(
|
|
|
5533
5558
|
return err(`Unknown action "${action}". Valid: save_memory, recall, delete_memory, promote_memory, log_learning, list_learnings, resolve_learning, save_entity, link_entities, query_graph, start_trajectory, end_trajectory, get_brain_status`);
|
|
5534
5559
|
}
|
|
5535
5560
|
},
|
|
5536
|
-
});
|
|
5561
|
+
}));
|
|
5537
5562
|
}
|
|
5538
5563
|
|
|
5539
5564
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
@@ -5568,13 +5593,12 @@ export function registerTools(
|
|
|
5568
5593
|
registerSOPOps(api, supabase, userId, resolveAgent); // 13
|
|
5569
5594
|
registerBrainOps(api, supabase, userId, resolveAgent); // 14
|
|
5570
5595
|
|
|
5571
|
-
// ──
|
|
5572
|
-
//
|
|
5573
|
-
//
|
|
5574
|
-
//
|
|
5575
|
-
|
|
5576
|
-
|
|
5577
|
-
});
|
|
5596
|
+
// ── Register dynamic brain context hook ──
|
|
5597
|
+
// FIX (v4.30.0): Was injectBrainContext() which loaded once at registration.
|
|
5598
|
+
// Now registers a before_prompt_build hook that dynamically resolves the
|
|
5599
|
+
// calling agent from ctx.agentId and loads THAT agent's brain memories.
|
|
5600
|
+
// Each agent sees its own brain context — Daisy sees Daisy's, Ivy sees Ivy's.
|
|
5601
|
+
registerBrainContextHook(api, supabase, userId, fallbackAgentId);
|
|
5578
5602
|
|
|
5579
5603
|
// ── Register agent_end hook for server-side brain extraction ──
|
|
5580
5604
|
// This is the FIX for Bug 2: extraction was client-side only (useSocket.ts).
|
|
@@ -5596,6 +5620,10 @@ export function registerTools(
|
|
|
5596
5620
|
// Extracts L1 raw fragments and lightweight L2 summaries.
|
|
5597
5621
|
// Replaces the client-side extractBrainMemory() in useSocket.ts.
|
|
5598
5622
|
// Requires allowConversationAccess: true in plugin config on the OpenClaw side.
|
|
5623
|
+
//
|
|
5624
|
+
// FIX (v4.30.0): OpenClaw passes (event, ctx: PluginHookAgentContext) where
|
|
5625
|
+
// ctx.agentId is the calling agent's accountId. Previously we only read the
|
|
5626
|
+
// event arg, causing all writes to fall back to the first DB agent alphabetically.
|
|
5599
5627
|
|
|
5600
5628
|
function registerBrainExtractionHook(
|
|
5601
5629
|
api: any,
|
|
@@ -5604,7 +5632,7 @@ function registerBrainExtractionHook(
|
|
|
5604
5632
|
fallbackAgentId: string,
|
|
5605
5633
|
): void {
|
|
5606
5634
|
try {
|
|
5607
|
-
api.on("agent_end", async (event: any) => {
|
|
5635
|
+
api.on("agent_end", async (event: any, ctx: any) => {
|
|
5608
5636
|
try {
|
|
5609
5637
|
// Extract messages from event — agent_end provides the conversation
|
|
5610
5638
|
const messages: any[] = event?.messages || event?.context?.messages || [];
|
|
@@ -5635,9 +5663,14 @@ function registerBrainExtractionHook(
|
|
|
5635
5663
|
// Skip trivial exchanges
|
|
5636
5664
|
if (lastUser.length < 20 || lastAssistant.length < 30) return;
|
|
5637
5665
|
|
|
5638
|
-
//
|
|
5639
|
-
|
|
5666
|
+
// ── Agent identity resolution (FIX) ──────────────────────────
|
|
5667
|
+
// Priority: ctx.agentId (from PluginHookAgentContext, the CORRECT source)
|
|
5668
|
+
// > event fields (legacy compat)
|
|
5669
|
+
// > fallbackAgentId (last resort)
|
|
5670
|
+
const agentId = ctx?.agentId
|
|
5671
|
+
|| event?.agentId
|
|
5640
5672
|
|| event?.context?.agentId
|
|
5673
|
+
|| ctx?.sessionKey?.split?.(":")?.[0]
|
|
5641
5674
|
|| event?.sessionKey?.split?.(":")?.[0]
|
|
5642
5675
|
|| fallbackAgentId
|
|
5643
5676
|
|| "";
|
|
@@ -5653,7 +5686,9 @@ function registerBrainExtractionHook(
|
|
|
5653
5686
|
// Use raw agentId
|
|
5654
5687
|
}
|
|
5655
5688
|
|
|
5656
|
-
const sessionKey = event?.sessionKey || event?.context?.sessionKey || undefined;
|
|
5689
|
+
const sessionKey = ctx?.sessionKey || event?.sessionKey || event?.context?.sessionKey || undefined;
|
|
5690
|
+
|
|
5691
|
+
api.logger.debug?.(`[ofiere-brain] agent_end identity: ctx.agentId=${ctx?.agentId || "(none)"} event.agentId=${event?.agentId || "(none)"} resolved=${resolvedAgentId}`);
|
|
5657
5692
|
|
|
5658
5693
|
// ── Fast Stream: Write L1_focus ──
|
|
5659
5694
|
const rawContent = `User: ${lastUser}\nAssistant: ${lastAssistant}`;
|
|
@@ -5762,79 +5797,112 @@ function registerBrainExtractionHook(
|
|
|
5762
5797
|
}
|
|
5763
5798
|
|
|
5764
5799
|
// ── Brain Context Bootstrap Injection ──────────────────────────────────────
|
|
5765
|
-
//
|
|
5766
|
-
//
|
|
5800
|
+
// Injects the calling agent's active memories into the system prompt via
|
|
5801
|
+
// api.on("before_prompt_build"). Uses the TMT hierarchy:
|
|
5767
5802
|
// L1_focus — active working memory (not expired, sorted by importance)
|
|
5768
|
-
// L4_rule — operational guardrails (
|
|
5803
|
+
// L4_rule — operational guardrails (non-superseded, sorted by importance)
|
|
5769
5804
|
// L5_persona — identity context (permanent, top 3)
|
|
5770
|
-
//
|
|
5805
|
+
//
|
|
5806
|
+
// FIX (v4.30.0): Previously loaded brain context ONCE at registration time
|
|
5807
|
+
// using fallbackAgentId (= first agent alphabetically). Now the hook is
|
|
5808
|
+
// DYNAMIC — it reads ctx.agentId from the PluginHookAgentContext passed by
|
|
5809
|
+
// OpenClaw on every prompt build, resolves the correct agent, and loads
|
|
5810
|
+
// that specific agent's brain. Each agent gets its own memories.
|
|
5771
5811
|
|
|
5772
|
-
|
|
5812
|
+
function registerBrainContextHook(
|
|
5773
5813
|
api: any,
|
|
5774
5814
|
supabase: SupabaseClient,
|
|
5775
5815
|
userId: string,
|
|
5776
|
-
|
|
5777
|
-
):
|
|
5778
|
-
|
|
5816
|
+
fallbackAgentId: string,
|
|
5817
|
+
): void {
|
|
5818
|
+
// Cache to avoid querying Supabase on every single prompt build.
|
|
5819
|
+
// Key: resolved agent UUID. Value: { brainContext, loadedAt }.
|
|
5820
|
+
// Cache TTL: 60 seconds — brain context refreshes at most once per minute.
|
|
5821
|
+
const brainCache = new Map<string, { text: string; at: number }>();
|
|
5822
|
+
const CACHE_TTL_MS = 60_000;
|
|
5779
5823
|
|
|
5780
|
-
|
|
5824
|
+
try {
|
|
5825
|
+
api.on("before_prompt_build", async (_event: any, ctx: any) => {
|
|
5826
|
+
try {
|
|
5827
|
+
// ── Resolve which agent is building its prompt ──
|
|
5828
|
+
const ctxAgentId = ctx?.agentId || "";
|
|
5829
|
+
let resolvedAgentId = fallbackAgentId;
|
|
5781
5830
|
|
|
5782
|
-
|
|
5783
|
-
|
|
5784
|
-
|
|
5785
|
-
|
|
5786
|
-
|
|
5787
|
-
|
|
5788
|
-
|
|
5789
|
-
|
|
5790
|
-
.or(`expires_at.is.null,expires_at.gt.${now}`)
|
|
5791
|
-
.order("importance", { ascending: false })
|
|
5792
|
-
.limit(5),
|
|
5793
|
-
// L4: Guardrails (non-superseded reflections)
|
|
5794
|
-
supabase.from("agent_memories")
|
|
5795
|
-
.select("content, importance")
|
|
5796
|
-
.eq("user_id", userId)
|
|
5797
|
-
.eq("agent_id", agentId)
|
|
5798
|
-
.eq("tier", "L4_rule")
|
|
5799
|
-
.is("superseded_by", null)
|
|
5800
|
-
.order("importance", { ascending: false })
|
|
5801
|
-
.limit(10),
|
|
5802
|
-
// L5: Persona (permanent identity)
|
|
5803
|
-
supabase.from("agent_memories")
|
|
5804
|
-
.select("content")
|
|
5805
|
-
.eq("user_id", userId)
|
|
5806
|
-
.eq("agent_id", agentId)
|
|
5807
|
-
.eq("tier", "L5_persona")
|
|
5808
|
-
.order("importance", { ascending: false })
|
|
5809
|
-
.limit(3),
|
|
5810
|
-
]);
|
|
5831
|
+
if (ctxAgentId && !isSystemName(ctxAgentId)) {
|
|
5832
|
+
try {
|
|
5833
|
+
const resolved = await resolveAgentId(ctxAgentId, userId, supabase);
|
|
5834
|
+
if (resolved) resolvedAgentId = resolved;
|
|
5835
|
+
} catch {
|
|
5836
|
+
// Fall through to fallback
|
|
5837
|
+
}
|
|
5838
|
+
}
|
|
5811
5839
|
|
|
5812
|
-
|
|
5813
|
-
const l4 = l4Res.data || [];
|
|
5814
|
-
const l5 = l5Res.data || [];
|
|
5840
|
+
if (!resolvedAgentId) return;
|
|
5815
5841
|
|
|
5816
|
-
|
|
5842
|
+
// ── Check cache ──
|
|
5843
|
+
const cached = brainCache.get(resolvedAgentId);
|
|
5844
|
+
if (cached && Date.now() - cached.at < CACHE_TTL_MS) {
|
|
5845
|
+
return cached.text ? { appendSystemContext: cached.text } : undefined;
|
|
5846
|
+
}
|
|
5817
5847
|
|
|
5818
|
-
|
|
5848
|
+
// ── Query brain memories for THIS specific agent ──
|
|
5849
|
+
const now = new Date().toISOString();
|
|
5819
5850
|
|
|
5820
|
-
|
|
5821
|
-
|
|
5822
|
-
|
|
5851
|
+
const [l1Res, l4Res, l5Res] = await Promise.all([
|
|
5852
|
+
supabase.from("agent_memories")
|
|
5853
|
+
.select("content, importance")
|
|
5854
|
+
.eq("user_id", userId)
|
|
5855
|
+
.eq("agent_id", resolvedAgentId)
|
|
5856
|
+
.eq("tier", "L1_focus")
|
|
5857
|
+
.gt("decay_score", 0.1)
|
|
5858
|
+
.or(`expires_at.is.null,expires_at.gt.${now}`)
|
|
5859
|
+
.order("importance", { ascending: false })
|
|
5860
|
+
.limit(5),
|
|
5861
|
+
supabase.from("agent_memories")
|
|
5862
|
+
.select("content, importance")
|
|
5863
|
+
.eq("user_id", userId)
|
|
5864
|
+
.eq("agent_id", resolvedAgentId)
|
|
5865
|
+
.eq("tier", "L4_rule")
|
|
5866
|
+
.is("superseded_by", null)
|
|
5867
|
+
.order("importance", { ascending: false })
|
|
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
|
+
]);
|
|
5823
5877
|
|
|
5824
|
-
|
|
5825
|
-
|
|
5826
|
-
|
|
5878
|
+
const l1 = l1Res.data || [];
|
|
5879
|
+
const l4 = l4Res.data || [];
|
|
5880
|
+
const l5 = l5Res.data || [];
|
|
5827
5881
|
|
|
5828
|
-
|
|
5829
|
-
|
|
5830
|
-
|
|
5882
|
+
if (l1.length === 0 && l4.length === 0 && l5.length === 0) {
|
|
5883
|
+
brainCache.set(resolvedAgentId, { text: "", at: Date.now() });
|
|
5884
|
+
return;
|
|
5885
|
+
}
|
|
5886
|
+
|
|
5887
|
+
const sections: string[] = [];
|
|
5888
|
+
if (l5.length > 0) {
|
|
5889
|
+
sections.push("### Identity (L5_persona)\n" + l5.map((m: any) => `- ${m.content}`).join("\n"));
|
|
5890
|
+
}
|
|
5891
|
+
if (l4.length > 0) {
|
|
5892
|
+
sections.push("### ⚠️ Guardrails (L4_rule — DO NOT violate)\n" + l4.map((m: any) => `- ${m.content}`).join("\n"));
|
|
5893
|
+
}
|
|
5894
|
+
if (l1.length > 0) {
|
|
5895
|
+
sections.push("### Active Focus (L1_focus)\n" + l1.map((m: any) => `- ${m.content}`).join("\n"));
|
|
5896
|
+
}
|
|
5831
5897
|
|
|
5832
|
-
|
|
5898
|
+
const brainContext = `<agent-brain>\n## Your Brain Context (TMT)\n\n${sections.join("\n\n")}\n</agent-brain>`;
|
|
5899
|
+
brainCache.set(resolvedAgentId, { text: brainContext, at: Date.now() });
|
|
5833
5900
|
|
|
5834
|
-
|
|
5835
|
-
|
|
5836
|
-
|
|
5837
|
-
|
|
5901
|
+
return { appendSystemContext: brainContext };
|
|
5902
|
+
} catch (e) {
|
|
5903
|
+
api.logger.debug?.(`[ofiere-brain] before_prompt_build error: ${e instanceof Error ? e.message : e}`);
|
|
5904
|
+
}
|
|
5905
|
+
});
|
|
5838
5906
|
} catch {
|
|
5839
5907
|
api.logger.debug?.("[ofiere] Could not register brain context hook — appendSystemContext may not be supported");
|
|
5840
5908
|
}
|