ofiere-openclaw-plugin 3.2.0 → 3.4.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 +63 -12
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ofiere-openclaw-plugin",
3
- "version": "3.2.0",
3
+ "version": "3.4.0",
4
4
  "type": "module",
5
5
  "description": "OpenClaw plugin for Ofiere PM — 9 meta-tools covering tasks, agents, projects, scheduling, knowledge, workflows, notifications, memory, and prompts",
6
6
  "keywords": ["openclaw", "ofiere", "project-management", "agents", "plugin"],
package/src/tools.ts CHANGED
@@ -191,12 +191,13 @@ function registerTaskOps(
191
191
  `Manage tasks in the Ofiere PM dashboard. All task operations go through this tool.\n\n` +
192
192
  `Actions:\n` +
193
193
  `- "list": List/filter tasks. Optional: status, agent_id, space_id, folder_id, limit\n` +
194
- `- "create": Create a task. Required: title. Optional: agent_id, description, status, priority, space_id, folder_id, start_date, due_date, tags, instructions, execution_plan, goals, constraints, system_prompt\n` +
194
+ `- "create": Create a task. Required: title. Optional: agent_id, description, status, priority, space_id, folder_id, start_date, due_date, tags, instructions, execution_plan, goals, constraints, system_prompt, recurrence_type, recurrence_interval, scheduled_time\n` +
195
195
  `- "update": Update a task. Required: task_id. Optional: all create fields + progress\n` +
196
196
  `- "delete": Delete task + subtasks. Required: task_id\n\n` +
197
197
  `For complex tasks, fill in execution_plan (step-by-step plan), goals, constraints, and system_prompt to help the executing agent.\n` +
198
198
  `For simple tasks, just provide title and optionally description.\n` +
199
199
  `agent_id: Pass your name to self-assign, another agent's name, or 'none'.\n` +
200
+ `For recurring tasks: set start_date + recurrence_type + recurrence_interval. Example: every 2 minutes = recurrence_type: "minutely", recurrence_interval: 2.\n` +
200
201
  `Status: PENDING, IN_PROGRESS, DONE, FAILED | Priority: 0=LOW, 1=MEDIUM, 2=HIGH, 3=CRITICAL`,
201
202
  parameters: {
202
203
  type: "object",
@@ -224,8 +225,15 @@ function registerTaskOps(
224
225
  progress: { type: "number", description: "Progress percentage 0-100 (update only)" },
225
226
  space_id: { type: "string", description: "PM Space ID" },
226
227
  folder_id: { type: "string", description: "PM Folder ID" },
227
- start_date: { type: "string", description: "Start date (ISO 8601)" },
228
+ start_date: { type: "string", description: "Start date (ISO 8601). Required for scheduled/recurring tasks." },
228
229
  due_date: { type: "string", description: "Due date (ISO 8601)" },
230
+ scheduled_time: { type: "string", description: "Time to execute in HH:MM format (UTC). If omitted, extracted from start_date or defaults to now+60s." },
231
+ recurrence_type: {
232
+ type: "string",
233
+ description: "How often the task recurs. 'none' for one-shot.",
234
+ enum: ["none", "minutely", "hourly", "daily", "weekly", "monthly"],
235
+ },
236
+ recurrence_interval: { type: "number", description: "Recurrence interval. e.g. recurrence_type='minutely', recurrence_interval=2 → every 2 minutes. Default: 1" },
229
237
  tags: {
230
238
  type: "array",
231
239
  items: { type: "string" },
@@ -430,11 +438,54 @@ async function handleCreateTask(
430
438
  const effectiveAgentId = (insertData.agent_id as string) || assignee;
431
439
  if (startDate && effectiveAgentId) {
432
440
  try {
433
- const scheduledTime = (params.scheduled_time as string) || "09:00"; // default 9am
434
- const datePart = startDate; // YYYY-MM-DD
435
- const timePart = scheduledTime; // HH:MM
436
- const dt = new Date(`${datePart}T${timePart}:00`);
437
- const nextRunAt = Math.floor(dt.getTime() / 1000);
441
+ // Parse start_date robustly it can be:
442
+ // "2026-04-19" (date only)
443
+ // "2026-04-19T18:45:00" (local datetime)
444
+ // "2026-04-19 11:45:00+00" (Supabase timestamptz)
445
+ // "2026-04-19T11:45:00.000Z" (ISO UTC)
446
+ const parsedDate = new Date(startDate);
447
+ const explicitScheduledTime = params.scheduled_time as string | undefined;
448
+
449
+ let nextRunAtEpoch: number;
450
+ let scheduledTimeFinal: string;
451
+ let scheduledDateFinal: string;
452
+
453
+ if (!isNaN(parsedDate.getTime())) {
454
+ // Valid date — check if it includes a meaningful time component
455
+ const hasTimeInfo = /[T ]\d{2}:\d{2}/.test(startDate);
456
+
457
+ if (explicitScheduledTime) {
458
+ // Agent explicitly passed a scheduled_time — use date from start_date + explicit time
459
+ const dateStr = parsedDate.toISOString().split("T")[0]; // YYYY-MM-DD
460
+ const dt = new Date(`${dateStr}T${explicitScheduledTime}:00Z`);
461
+ nextRunAtEpoch = Math.floor(dt.getTime() / 1000);
462
+ scheduledTimeFinal = explicitScheduledTime;
463
+ scheduledDateFinal = dateStr;
464
+ } else if (hasTimeInfo) {
465
+ // start_date already contains time — use it directly
466
+ nextRunAtEpoch = Math.floor(parsedDate.getTime() / 1000);
467
+ scheduledTimeFinal = `${String(parsedDate.getUTCHours()).padStart(2, "0")}:${String(parsedDate.getUTCMinutes()).padStart(2, "0")}`;
468
+ scheduledDateFinal = parsedDate.toISOString().split("T")[0];
469
+ } else {
470
+ // Date only, no time — default to 09:00 UTC
471
+ const dateStr = parsedDate.toISOString().split("T")[0];
472
+ const dt = new Date(`${dateStr}T09:00:00Z`);
473
+ nextRunAtEpoch = Math.floor(dt.getTime() / 1000);
474
+ scheduledTimeFinal = "09:00";
475
+ scheduledDateFinal = dateStr;
476
+ }
477
+ } else {
478
+ // Unparseable date — fallback to now + 60s
479
+ nextRunAtEpoch = Math.floor(Date.now() / 1000) + 60;
480
+ scheduledTimeFinal = "00:00";
481
+ scheduledDateFinal = new Date().toISOString().split("T")[0];
482
+ }
483
+
484
+ // Safety net: if computed time is in the past, schedule for now + 60s
485
+ const nowEpoch = Math.floor(Date.now() / 1000);
486
+ if (nextRunAtEpoch <= nowEpoch) {
487
+ nextRunAtEpoch = nowEpoch + 60;
488
+ }
438
489
 
439
490
  await supabase.from("scheduler_events").insert({
440
491
  id: crypto.randomUUID(),
@@ -443,13 +494,13 @@ async function handleCreateTask(
443
494
  agent_id: effectiveAgentId,
444
495
  title: params.title,
445
496
  description: (params.description as string) || (params.instructions as string) || null,
446
- scheduled_date: datePart,
447
- scheduled_time: timePart,
497
+ scheduled_date: scheduledDateFinal,
498
+ scheduled_time: scheduledTimeFinal,
448
499
  duration_minutes: 30,
449
- recurrence_type: "none",
450
- recurrence_interval: 1,
500
+ recurrence_type: (params.recurrence_type as string) || "none",
501
+ recurrence_interval: (params.recurrence_interval as number) || 1,
451
502
  status: "scheduled",
452
- next_run_at: nextRunAt,
503
+ next_run_at: nextRunAtEpoch,
453
504
  run_count: 0,
454
505
  priority: params.priority !== undefined ? params.priority : 1,
455
506
  });