ofiere-openclaw-plugin 4.4.0 → 4.6.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 +103 -19
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ofiere-openclaw-plugin",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.6.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "OpenClaw plugin for Ofiere PM - 10 meta-tools with 13-action workflow mastery covering tasks, agents, projects, scheduling, knowledge, workflows, notifications, memory, prompts, and constellation agent architecture",
|
|
6
6
|
"keywords": ["openclaw", "ofiere", "project-management", "agents", "plugin"],
|
package/src/tools.ts
CHANGED
|
@@ -311,7 +311,7 @@ async function handleListTasks(
|
|
|
311
311
|
.from("tasks")
|
|
312
312
|
.select(
|
|
313
313
|
"id, title, description, status, priority, agent_id, space_id, folder_id, " +
|
|
314
|
-
"start_date, due_date, progress, tags, custom_fields, created_at, updated_at",
|
|
314
|
+
"start_date, due_date, progress, tags, custom_fields, completed_at, created_at, updated_at",
|
|
315
315
|
)
|
|
316
316
|
.eq("user_id", userId)
|
|
317
317
|
.order("updated_at", { ascending: false });
|
|
@@ -325,16 +325,27 @@ async function handleListTasks(
|
|
|
325
325
|
const { data, error } = await query;
|
|
326
326
|
if (error) return err(error.message);
|
|
327
327
|
|
|
328
|
-
//
|
|
328
|
+
// BUG 1 fix: normalize legacy statuses to documented enum
|
|
329
|
+
const STATUS_NORMALIZE: Record<string, string> = {
|
|
330
|
+
NEW: "PENDING",
|
|
331
|
+
COMPLETED: "DONE",
|
|
332
|
+
CANCELLED: "FAILED",
|
|
333
|
+
};
|
|
334
|
+
|
|
335
|
+
// Unpack custom_fields for readability, then strip raw custom_fields (BUG 9)
|
|
329
336
|
const tasks = (data || []).map((t: any) => {
|
|
330
337
|
const cf = t.custom_fields || {};
|
|
338
|
+
const normalizedStatus = STATUS_NORMALIZE[t.status] || t.status;
|
|
339
|
+
const { custom_fields: _cf, ...rest } = t;
|
|
331
340
|
return {
|
|
332
|
-
...
|
|
341
|
+
...rest,
|
|
342
|
+
status: normalizedStatus,
|
|
333
343
|
execution_plan: cf.execution_plan || undefined,
|
|
334
344
|
goals: cf.goals || undefined,
|
|
335
345
|
constraints: cf.constraints || undefined,
|
|
336
346
|
system_prompt: cf.system_prompt || undefined,
|
|
337
|
-
instructions: cf.instructions ||
|
|
347
|
+
instructions: cf.instructions || undefined,
|
|
348
|
+
completed_at: t.completed_at || undefined,
|
|
338
349
|
};
|
|
339
350
|
});
|
|
340
351
|
|
|
@@ -515,14 +526,24 @@ async function handleCreateTask(
|
|
|
515
526
|
if (cf.goals) extras.push(`${(cf.goals as any[]).length} goals`);
|
|
516
527
|
if (cf.constraints) extras.push(`${(cf.constraints as any[]).length} constraints`);
|
|
517
528
|
if (cf.system_prompt) extras.push("custom system prompt");
|
|
518
|
-
|
|
529
|
+
|
|
530
|
+
// BUG 6 fix: surface recurrence fields in create response
|
|
531
|
+
const recurrenceInfo = (params.recurrence_type && params.recurrence_type !== "none")
|
|
532
|
+
? { recurrence_type: params.recurrence_type, recurrence_interval: (params.recurrence_interval as number) || 1 }
|
|
533
|
+
: undefined;
|
|
534
|
+
|
|
535
|
+
// BUG 7 fix: only claim scheduling when bridge actually fired
|
|
536
|
+
const didSchedule = !!(startDate && effectiveAgentId);
|
|
537
|
+
if (didSchedule) extras.push(`scheduled for ${startDate}`);
|
|
538
|
+
|
|
519
539
|
const extrasStr = extras.length > 0 ? ` with ${extras.join(", ")}` : "";
|
|
520
540
|
|
|
521
541
|
return ok({
|
|
522
542
|
id,
|
|
523
543
|
message: `Task "${params.title}" created and assigned to ${assignee || "no one"}${extrasStr}`,
|
|
524
544
|
task: insertData,
|
|
525
|
-
scheduledExecution:
|
|
545
|
+
scheduledExecution: didSchedule ? `Will auto-execute on ${startDate}` : undefined,
|
|
546
|
+
recurrence: recurrenceInfo,
|
|
526
547
|
});
|
|
527
548
|
} catch (e) {
|
|
528
549
|
return err(e instanceof Error ? e.message : String(e));
|
|
@@ -614,16 +635,30 @@ async function handleUpdateTask(
|
|
|
614
635
|
updates.custom_fields = mergedCf;
|
|
615
636
|
}
|
|
616
637
|
|
|
638
|
+
// BUG 2+4 fix: return ALL mutable fields including custom_fields and completed_at
|
|
617
639
|
const { data, error } = await supabase
|
|
618
640
|
.from("tasks")
|
|
619
641
|
.update(updates)
|
|
620
642
|
.eq("id", params.task_id as string)
|
|
621
643
|
.eq("user_id", userId)
|
|
622
|
-
.select("id, title, status, priority, agent_id, start_date, due_date, progress, updated_at")
|
|
644
|
+
.select("id, title, description, status, priority, agent_id, space_id, folder_id, start_date, due_date, progress, tags, custom_fields, completed_at, updated_at")
|
|
623
645
|
.single();
|
|
624
646
|
|
|
625
647
|
if (error) return err(error.message);
|
|
626
|
-
|
|
648
|
+
|
|
649
|
+
// Unpack custom_fields for readability in response, strip raw (BUG 9 consistency)
|
|
650
|
+
const cf = (data as any)?.custom_fields || {};
|
|
651
|
+
const { custom_fields: _cf, ...rest } = data as any;
|
|
652
|
+
const taskResponse = {
|
|
653
|
+
...rest,
|
|
654
|
+
execution_plan: cf.execution_plan || undefined,
|
|
655
|
+
goals: cf.goals || undefined,
|
|
656
|
+
constraints: cf.constraints || undefined,
|
|
657
|
+
system_prompt: cf.system_prompt || undefined,
|
|
658
|
+
instructions: cf.instructions || undefined,
|
|
659
|
+
};
|
|
660
|
+
|
|
661
|
+
return ok({ message: `Task "${data?.title}" updated`, task: taskResponse });
|
|
627
662
|
} catch (e) {
|
|
628
663
|
return err(e instanceof Error ? e.message : String(e));
|
|
629
664
|
}
|
|
@@ -956,7 +991,7 @@ function registerScheduleOps(
|
|
|
956
991
|
recurrence_type: { type: "string", enum: ["none", "hourly", "daily", "weekly", "monthly"] },
|
|
957
992
|
recurrence_interval: { type: "number", description: "Repeat every N periods" },
|
|
958
993
|
color: { type: "string", description: "Hex color" },
|
|
959
|
-
priority: { type: "number", description: "0-3" },
|
|
994
|
+
priority: { type: ["number", "string"], description: "0-3 (numeric) or 'low'/'medium'/'high'/'critical' (string)" },
|
|
960
995
|
status: { type: "string", enum: ["scheduled", "completed", "cancelled"] },
|
|
961
996
|
},
|
|
962
997
|
},
|
|
@@ -979,6 +1014,21 @@ function registerScheduleOps(
|
|
|
979
1014
|
const priorityMap: Record<string, number> = { low: 0, medium: 1, high: 2, critical: 3 };
|
|
980
1015
|
const pVal = typeof params.priority === "number" ? params.priority
|
|
981
1016
|
: priorityMap[String(params.priority || "").toLowerCase()] ?? 0;
|
|
1017
|
+
|
|
1018
|
+
// Compute next_run_at from scheduled_date + scheduled_time
|
|
1019
|
+
let nextRunAt: number | null = null;
|
|
1020
|
+
try {
|
|
1021
|
+
const dateStr = params.scheduled_date as string;
|
|
1022
|
+
const timeStr = (params.scheduled_time as string) || "09:00";
|
|
1023
|
+
const dt = new Date(`${dateStr}T${timeStr}:00Z`);
|
|
1024
|
+
if (!isNaN(dt.getTime())) {
|
|
1025
|
+
nextRunAt = Math.floor(dt.getTime() / 1000);
|
|
1026
|
+
// If in the past, bump to now + 60s
|
|
1027
|
+
const nowEpoch = Math.floor(Date.now() / 1000);
|
|
1028
|
+
if (nextRunAt <= nowEpoch) nextRunAt = nowEpoch + 60;
|
|
1029
|
+
}
|
|
1030
|
+
} catch { /* non-fatal */ }
|
|
1031
|
+
|
|
982
1032
|
const insertData: Record<string, any> = {
|
|
983
1033
|
id: evtId,
|
|
984
1034
|
user_id: userId,
|
|
@@ -992,6 +1042,7 @@ function registerScheduleOps(
|
|
|
992
1042
|
recurrence_type: (params.recurrence_type as string) || "none",
|
|
993
1043
|
recurrence_interval: (params.recurrence_interval as number) || 1,
|
|
994
1044
|
status: "scheduled",
|
|
1045
|
+
next_run_at: nextRunAt,
|
|
995
1046
|
run_count: 0,
|
|
996
1047
|
color: (params.color as string) || null,
|
|
997
1048
|
priority: pVal,
|
|
@@ -1007,6 +1058,36 @@ function registerScheduleOps(
|
|
|
1007
1058
|
"recurrence_type", "recurrence_interval", "status", "color", "priority", "agent_id"]) {
|
|
1008
1059
|
if ((params as any)[f] !== undefined) upd[f] = (params as any)[f];
|
|
1009
1060
|
}
|
|
1061
|
+
// Map string priority to number if provided
|
|
1062
|
+
if (upd.priority !== undefined && typeof upd.priority === "string") {
|
|
1063
|
+
const pMap: Record<string, number> = { low: 0, medium: 1, high: 2, critical: 3 };
|
|
1064
|
+
upd.priority = pMap[upd.priority.toLowerCase()] ?? 0;
|
|
1065
|
+
}
|
|
1066
|
+
// Recompute next_run_at when date/time/recurrence changes
|
|
1067
|
+
const dateChanged = params.scheduled_date !== undefined || params.scheduled_time !== undefined;
|
|
1068
|
+
const recurrenceChanged = params.recurrence_type !== undefined;
|
|
1069
|
+
if (dateChanged || recurrenceChanged) {
|
|
1070
|
+
try {
|
|
1071
|
+
// Fetch current event to merge with updates
|
|
1072
|
+
const { data: current } = await supabase.from("scheduler_events").select("scheduled_date, scheduled_time, status").eq("id", params.id).single();
|
|
1073
|
+
const effDate = (upd.scheduled_date || current?.scheduled_date) as string;
|
|
1074
|
+
const effTime = (upd.scheduled_time || current?.scheduled_time || "09:00") as string;
|
|
1075
|
+
const effStatus = (upd.status || current?.status) as string;
|
|
1076
|
+
if (effDate && effStatus !== "completed" && effStatus !== "cancelled") {
|
|
1077
|
+
const dt = new Date(`${effDate}T${effTime}:00Z`);
|
|
1078
|
+
if (!isNaN(dt.getTime())) {
|
|
1079
|
+
let nra = Math.floor(dt.getTime() / 1000);
|
|
1080
|
+
const nowEpoch = Math.floor(Date.now() / 1000);
|
|
1081
|
+
if (nra <= nowEpoch) nra = nowEpoch + 60;
|
|
1082
|
+
upd.next_run_at = nra;
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
} catch { /* non-fatal */ }
|
|
1086
|
+
}
|
|
1087
|
+
// Clear next_run_at on completion/cancellation
|
|
1088
|
+
if (upd.status === "completed" || upd.status === "cancelled") {
|
|
1089
|
+
upd.next_run_at = null;
|
|
1090
|
+
}
|
|
1010
1091
|
const { error } = await supabase.from("scheduler_events").update(upd).eq("id", params.id);
|
|
1011
1092
|
if (error) return err(error.message);
|
|
1012
1093
|
return ok({ message: "Event updated", ok: true });
|
|
@@ -2483,8 +2564,10 @@ function registerConstellationOps(
|
|
|
2483
2564
|
const name = (params.agent_name as string).toLowerCase();
|
|
2484
2565
|
const wsPath = getWorkspacePath(name);
|
|
2485
2566
|
if (!fs.existsSync(wsPath)) return err(`Agent workspace not found: workspace-${name}`);
|
|
2486
|
-
const
|
|
2487
|
-
const
|
|
2567
|
+
const allMd = fs.readdirSync(wsPath).filter((f: string) => f.endsWith(".md"));
|
|
2568
|
+
const userMd = allMd.filter((f: string) => !SYSTEM_MD_FILES.has(f));
|
|
2569
|
+
const systemMd = allMd.filter((f: string) => SYSTEM_MD_FILES.has(f));
|
|
2570
|
+
const fileDetails = userMd.map((f: string) => {
|
|
2488
2571
|
const stat = fs.statSync(path.join(wsPath, f));
|
|
2489
2572
|
return { name: f, size_bytes: stat.size, modified: stat.mtime.toISOString() };
|
|
2490
2573
|
});
|
|
@@ -2502,7 +2585,7 @@ function registerConstellationOps(
|
|
|
2502
2585
|
if (emojiMatch) identity.emoji = emojiMatch[1].trim();
|
|
2503
2586
|
} catch {}
|
|
2504
2587
|
const hasSkills = fs.existsSync(path.join(wsPath, "skills"));
|
|
2505
|
-
return ok({ agent_name: name, identity, files: fileDetails, has_skills: hasSkills, workspace_path: wsPath });
|
|
2588
|
+
return ok({ agent_name: name, identity, files: fileDetails, system_files: systemMd, has_skills: hasSkills, workspace_path: wsPath });
|
|
2506
2589
|
}
|
|
2507
2590
|
|
|
2508
2591
|
// ── Read a file ──
|
|
@@ -2711,12 +2794,12 @@ function registerConstellationOps(
|
|
|
2711
2794
|
|
|
2712
2795
|
// Safety gate: confirm must be explicitly true
|
|
2713
2796
|
if (params.confirm !== true) {
|
|
2714
|
-
// List only user-visible files
|
|
2797
|
+
// List only user-visible files (exclude system md + internal dirs)
|
|
2715
2798
|
let userFiles: string[] = [];
|
|
2716
2799
|
try {
|
|
2717
2800
|
if (fs.existsSync(wsPath)) {
|
|
2718
2801
|
userFiles = fs.readdirSync(wsPath)
|
|
2719
|
-
.filter((f: string) => f.endsWith(".md") || f === "skills");
|
|
2802
|
+
.filter((f: string) => (f.endsWith(".md") && !SYSTEM_MD_FILES.has(f)) || f === "skills");
|
|
2720
2803
|
}
|
|
2721
2804
|
} catch {}
|
|
2722
2805
|
return err(
|
|
@@ -2732,10 +2815,11 @@ function registerConstellationOps(
|
|
|
2732
2815
|
return err(`Agent workspace-${agentName} does not exist at ${wsPath}`);
|
|
2733
2816
|
}
|
|
2734
2817
|
|
|
2735
|
-
// Count files before deletion for the report
|
|
2736
|
-
let
|
|
2818
|
+
// Count user-visible files before deletion for the report
|
|
2819
|
+
let deletedUserFiles: string[] = [];
|
|
2737
2820
|
try {
|
|
2738
|
-
|
|
2821
|
+
deletedUserFiles = fs.readdirSync(wsPath)
|
|
2822
|
+
.filter((f: string) => (f.endsWith(".md") && !SYSTEM_MD_FILES.has(f)) || f === "skills");
|
|
2739
2823
|
} catch {}
|
|
2740
2824
|
|
|
2741
2825
|
// Remove the entire workspace directory
|
|
@@ -2762,13 +2846,13 @@ function registerConstellationOps(
|
|
|
2762
2846
|
unregResult = { success: false, message: `Unregistration may have failed: ${unregCombined.slice(0, 200)}` };
|
|
2763
2847
|
}
|
|
2764
2848
|
|
|
2765
|
-
api.logger?.info?.(`[ofiere] Deleted agent "${agentName}" — ${
|
|
2849
|
+
api.logger?.info?.(`[ofiere] Deleted agent "${agentName}" — ${deletedUserFiles.length} files removed`);
|
|
2766
2850
|
|
|
2767
2851
|
return ok({
|
|
2768
2852
|
message: `Agent "${agentName}" has been permanently deleted`,
|
|
2769
2853
|
agent_name: agentName,
|
|
2770
2854
|
workspace_deleted: wsPath,
|
|
2771
|
-
files_removed:
|
|
2855
|
+
files_removed: deletedUserFiles.length,
|
|
2772
2856
|
unregistration: unregResult,
|
|
2773
2857
|
});
|
|
2774
2858
|
}
|