ofiere-openclaw-plugin 4.38.0 → 4.39.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ofiere-openclaw-plugin",
3
- "version": "4.38.0",
3
+ "version": "4.39.1",
4
4
  "type": "module",
5
5
  "description": "OpenClaw plugin for Ofiere PM - 16 meta-tools covering tasks, agents, projects, scheduling, knowledge, workflows, notifications, memory, prompts, constellation, space file management, execution plan builder, SOP management, agent brain, talent management, and corporate frameworks",
6
6
  "keywords": ["openclaw", "ofiere", "project-management", "agents", "plugin"],
@@ -16,6 +16,10 @@
16
16
  "openclaw.plugin.json",
17
17
  "README.md"
18
18
  ],
19
+ "scripts": {
20
+ "typecheck": "tsc --noEmit",
21
+ "build": "tsc --noEmit"
22
+ },
19
23
  "openclaw": {
20
24
  "extensions": [
21
25
  "./index.ts"
@@ -32,5 +36,8 @@
32
36
  "dependencies": {
33
37
  "@supabase/supabase-js": "^2.98.0",
34
38
  "zod": "^3.25.11"
39
+ },
40
+ "devDependencies": {
41
+ "typescript": "^5.6.0"
35
42
  }
36
43
  }
@@ -277,27 +277,16 @@ function isSystemName(name: string): boolean {
277
277
  return s === "" || s === "ofiere" || s === "openclaw" || s === "system" || s === "plugin" || s === "gateway" || s.includes("plugin");
278
278
  }
279
279
 
280
- async function buildAttachmentBlock(args: {
280
+ // Render a block from explicit doc-id lists. Used by both the dispatch-params
281
+ // path (when workflow/task-dispatcher hands ids in via ctx) and the conversation
282
+ // fallback path (when no explicit ids are present).
283
+ async function renderBlockForIds(args: {
281
284
  supabase: SupabaseClient;
282
285
  userId: string;
283
- agentId: string;
286
+ sopIds: string[];
287
+ fwIds: string[];
284
288
  }): Promise<string> {
285
- const { supabase, userId, agentId } = args;
286
-
287
- // Find the most recently active conversation for this agent. The dashboard
288
- // bumps `updated_at` whenever a message is sent or attachments change, so
289
- // this gives us the right run target most of the time.
290
- const { data: convRow } = await supabase
291
- .from("conversations")
292
- .select("id, attached_sop_ids, attached_framework_ids")
293
- .eq("user_id", userId)
294
- .eq("agent_id", agentId)
295
- .order("updated_at", { ascending: false })
296
- .limit(1)
297
- .maybeSingle();
298
-
299
- const sopIds: string[] = (convRow?.attached_sop_ids as string[] | null) || [];
300
- const fwIds: string[] = (convRow?.attached_framework_ids as string[] | null) || [];
289
+ const { supabase, userId, sopIds, fwIds } = args;
301
290
  if (!sopIds.length && !fwIds.length) return "";
302
291
 
303
292
  const [sopsRes, fwsRes] = await Promise.all([
@@ -329,6 +318,57 @@ async function buildAttachmentBlock(args: {
329
318
  return renderAttachmentBlock({ sops, frameworks });
330
319
  }
331
320
 
321
+ // Pluck attachment ids from the `before_prompt_build` ctx. Exact path varies by
322
+ // the dispatch surface that fires the prompt build, so we probe several known
323
+ // locations. Workflow executor + task-dispatcher edge function both stash ids
324
+ // under `metadata` on their request frames.
325
+ function readDispatchAttachmentIds(ctx: any): { sopIds: string[]; fwIds: string[] } {
326
+ const candidates: Array<Record<string, unknown> | undefined> = [
327
+ ctx?.metadata,
328
+ ctx?.params?.metadata,
329
+ ctx?.payload?.metadata,
330
+ ctx?.request?.metadata,
331
+ ctx?.options?.metadata,
332
+ ctx, // last resort: top level
333
+ ];
334
+ for (const c of candidates) {
335
+ if (!c || typeof c !== "object") continue;
336
+ const sop = (c as any).attached_sop_ids;
337
+ const fw = (c as any).attached_framework_ids;
338
+ if (Array.isArray(sop) || Array.isArray(fw)) {
339
+ return {
340
+ sopIds: Array.isArray(sop) ? sop.filter((x: unknown): x is string => typeof x === "string") : [],
341
+ fwIds: Array.isArray(fw) ? fw.filter((x: unknown): x is string => typeof x === "string") : [],
342
+ };
343
+ }
344
+ }
345
+ return { sopIds: [], fwIds: [] };
346
+ }
347
+
348
+ async function buildAttachmentBlock(args: {
349
+ supabase: SupabaseClient;
350
+ userId: string;
351
+ agentId: string;
352
+ }): Promise<string> {
353
+ const { supabase, userId, agentId } = args;
354
+
355
+ // Find the most recently active conversation for this agent. The dashboard
356
+ // bumps `updated_at` whenever a message is sent or attachments change, so
357
+ // this gives us the right run target most of the time.
358
+ const { data: convRow } = await supabase
359
+ .from("conversations")
360
+ .select("id, attached_sop_ids, attached_framework_ids")
361
+ .eq("user_id", userId)
362
+ .eq("agent_id", agentId)
363
+ .order("updated_at", { ascending: false })
364
+ .limit(1)
365
+ .maybeSingle();
366
+
367
+ const sopIds: string[] = (convRow?.attached_sop_ids as string[] | null) || [];
368
+ const fwIds: string[] = (convRow?.attached_framework_ids as string[] | null) || [];
369
+ return renderBlockForIds({ supabase, userId, sopIds, fwIds });
370
+ }
371
+
332
372
  export function registerAttachmentContextHook(args: {
333
373
  api: any;
334
374
  supabase: SupabaseClient;
@@ -351,7 +391,26 @@ export function registerAttachmentContextHook(args: {
351
391
  }
352
392
  if (!resolvedAgentId) return;
353
393
 
354
- const cacheKey = resolvedAgentId;
394
+ // Dispatch-params path: workflow executor + task-dispatcher edge function
395
+ // can stash explicit `attached_sop_ids` / `attached_framework_ids` on the
396
+ // chat.send frame's metadata. When present, prefer them over the
397
+ // most-recent-conversation lookup and bypass cache (per-dispatch ids).
398
+ const dispatchIds = readDispatchAttachmentIds(ctx);
399
+ if (dispatchIds.sopIds.length || dispatchIds.fwIds.length) {
400
+ api.logger?.debug?.(
401
+ `[ofiere-attach] dispatch ids agent=${resolvedAgentId} sops=${dispatchIds.sopIds.length} fws=${dispatchIds.fwIds.length}`,
402
+ );
403
+ const block = await renderBlockForIds({
404
+ supabase, userId,
405
+ sopIds: dispatchIds.sopIds,
406
+ fwIds: dispatchIds.fwIds,
407
+ });
408
+ return block ? { appendSystemContext: block } : undefined;
409
+ }
410
+
411
+ // Multi-tenant: a single plugin process can serve multiple users — key
412
+ // by (userId, agentId) so User B never sees User A's cached block.
413
+ const cacheKey = `${userId}::${resolvedAgentId}`;
355
414
  const cached = attachmentCache.get(cacheKey);
356
415
  if (cached) {
357
416
  const age = Date.now() - cached.at;
package/src/tools.ts CHANGED
@@ -5624,7 +5624,9 @@ function registerBrainOps(
5624
5624
  // Touch access_count + last_accessed_at for FadeMem
5625
5625
  if (data && data.length > 0) {
5626
5626
  const ids = data.map((m: any) => m.id);
5627
- supabase.rpc("touch_memories", { memory_ids: ids }).then(() => {}).catch(() => {});
5627
+ // PostgrestFilterBuilder is PromiseLike, not a full Promise use the
5628
+ // 2-arg form of .then() instead of .catch() to silence rejection.
5629
+ supabase.rpc("touch_memories", { memory_ids: ids }).then(() => {}, () => {});
5628
5630
  }
5629
5631
 
5630
5632
  return ok({ memories: data || [], count: (data || []).length, query: queryText, search_mode: "fulltext" });
@@ -6242,7 +6244,7 @@ function registerBrainExtractionHook(
6242
6244
 
6243
6245
  // ── Fast Stream: Write L1_focus + L2_episode in parallel ──
6244
6246
  const rawContent = `User: ${lastUser}\nAssistant: ${lastAssistant}`;
6245
- const immediateWrites: Promise<any>[] = [];
6247
+ const immediateWrites: PromiseLike<any>[] = [];
6246
6248
 
6247
6249
  if (rawContent.length > 50) {
6248
6250
  immediateWrites.push(supabase.from("agent_memories").insert({
@@ -0,0 +1,8 @@
1
+ // Ambient declaration for the OpenClaw plugin SDK. The gateway resolves this
2
+ // module at runtime; npm has no published types. Keep loose — the plugin only
3
+ // uses a small surface (logger, on, registerTool, registerCommand, etc.).
4
+
5
+ declare module "openclaw/plugin-sdk" {
6
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
7
+ export type OpenClawPluginApi = any;
8
+ }