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 +8 -1
- package/src/attachments.ts +78 -19
- package/src/tools.ts +4 -2
- package/src/types/openclaw.d.ts +8 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ofiere-openclaw-plugin",
|
|
3
|
-
"version": "4.
|
|
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
|
}
|
package/src/attachments.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
286
|
+
sopIds: string[];
|
|
287
|
+
fwIds: string[];
|
|
284
288
|
}): Promise<string> {
|
|
285
|
-
const { supabase, userId,
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
+
}
|