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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. 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.29.0",
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
- api.registerTool({
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 resolveAgent(params.agent_id as string);
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 ? await resolveAgent(params.agent_id as string) : null;
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 resolveAgent(params.agent_id as string);
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 ? await resolveAgent(params.agent_id as string) : null;
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 resolveAgent(params.agent_id as string);
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 resolveAgent(params.agent_id as string);
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 ? await resolveAgent(params.agent_id as string) : null;
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 resolveAgent(params.agent_id as string);
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 ? await resolveAgent(params.agent_id as string) : null;
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
- // ── Inject brain context at bootstrap ──
5572
- // This loads the agent's active memories + unresolved learnings into the
5573
- // system prompt so the agent starts every conversation with full context.
5574
- // Runs async does NOT block registration.
5575
- injectBrainContext(api, supabase, userId, fallbackAgentId).catch((e: any) => {
5576
- api.logger.warn?.(`[ofiere] Brain context injection failed: ${e?.message || e}`);
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
- // Resolve agent identity from event context
5639
- const agentId = event?.agentId
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
- // Queries the agent's active memories at boot and injects them into the system
5766
- // prompt via api.on("before_prompt_build"). Uses the TMT hierarchy:
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 (source='reflection', sorted by importance)
5803
+ // L4_rule — operational guardrails (non-superseded, sorted by importance)
5769
5804
  // L5_persona — identity context (permanent, top 3)
5770
- // Runs once at registration time (~20ms, 3 parallel queries).
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
- async function injectBrainContext(
5812
+ function registerBrainContextHook(
5773
5813
  api: any,
5774
5814
  supabase: SupabaseClient,
5775
5815
  userId: string,
5776
- agentId: string,
5777
- ): Promise<void> {
5778
- if (!agentId) return;
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
- const now = new Date().toISOString();
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
- const [l1Res, l4Res, l5Res] = await Promise.all([
5783
- // L1: Active working memory (not expired)
5784
- supabase.from("agent_memories")
5785
- .select("content, importance")
5786
- .eq("user_id", userId)
5787
- .eq("agent_id", agentId)
5788
- .eq("tier", "L1_focus")
5789
- .gt("decay_score", 0.1)
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
- const l1 = l1Res.data || [];
5813
- const l4 = l4Res.data || [];
5814
- const l5 = l5Res.data || [];
5840
+ if (!resolvedAgentId) return;
5815
5841
 
5816
- if (l1.length === 0 && l4.length === 0 && l5.length === 0) return;
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
- const sections: string[] = [];
5848
+ // ── Query brain memories for THIS specific agent ──
5849
+ const now = new Date().toISOString();
5819
5850
 
5820
- if (l5.length > 0) {
5821
- sections.push("### Identity (L5_persona)\n" + l5.map((m: any) => `- ${m.content}`).join("\n"));
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
- if (l4.length > 0) {
5825
- sections.push("### ⚠️ Guardrails (L4_rule — DO NOT violate)\n" + l4.map((m: any) => `- ${m.content}`).join("\n"));
5826
- }
5878
+ const l1 = l1Res.data || [];
5879
+ const l4 = l4Res.data || [];
5880
+ const l5 = l5Res.data || [];
5827
5881
 
5828
- if (l1.length > 0) {
5829
- sections.push("### Active Focus (L1_focus)\n" + l1.map((m: any) => `- ${m.content}`).join("\n"));
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
- const brainContext = `<agent-brain>\n## Your Brain Context (TMT)\n\n${sections.join("\n\n")}\n</agent-brain>`;
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
- try {
5835
- api.on("before_prompt_build", () => ({
5836
- appendSystemContext: brainContext,
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
  }