ofiere-openclaw-plugin 4.56.5 → 4.56.6

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/dist/src/tools.js CHANGED
@@ -254,6 +254,7 @@ function registerTaskOps(api, supabase, userId, resolveAgent, timezone) {
254
254
  ` • "every N weeks" → recurrence_type:"weekly" + recurrence_interval:N\n` +
255
255
  ` • "every N months" → recurrence_type:"monthly" + recurrence_interval:N\n` +
256
256
  `🛑 NEVER use daily+1 (or hourly+24, etc.) when the user said a minute-level cadence — the task will fire ONCE per 24h instead of every N minutes. This was a real prod bug (v1 smoke 2026-05-17).\n` +
257
+ `🚦 BURST GUARD (Fix #3, 2026-05-17): when you create >3 recurring tasks within 60s for the same user, the server auto-staggers each new task's first fire by (count × min(recurrence_interval_secs, 1800)) so 6+ tasks with a shared T0 anchor do NOT all burst on the same pg_cron tick. Pass distinct start_date values per task if you need precise first-fire times.\n` +
257
258
  `\n` +
258
259
  `Approvals: Use add_approval to request sign-off from humans or agents. Approvals are separate from workflow gate nodes.\n` +
259
260
  `Status: PENDING, IN_PROGRESS, DONE, FAILED | Priority: 0=LOW, 1=MEDIUM, 2=HIGH, 3=CRITICAL`,
@@ -593,6 +594,21 @@ function humanizeCadence(type, interval) {
593
594
  const unit = unitMap[type] || type;
594
595
  return interval === 1 ? `every ${unit}` : `every ${interval} ${unit}s`;
595
596
  }
597
+ // Fix #3 (2026-05-17): bulk-create stagger helper. Maps recurrence_type +
598
+ // interval to cadence in seconds; used to compute the per-slot offset
599
+ // applied when a user bulk-creates >3 recurring scheduler_events within a
600
+ // 60s window (see handleCreateTask below). monthly approximated as 30 days.
601
+ function recurrenceIntervalSeconds(rtype, ri) {
602
+ const safe = Math.max(1, ri || 1);
603
+ switch (rtype) {
604
+ case "minutely": return 60 * safe;
605
+ case "hourly": return 3600 * safe;
606
+ case "daily": return 86400 * safe;
607
+ case "weekly": return 604800 * safe;
608
+ case "monthly": return 2592000 * safe;
609
+ default: return 60;
610
+ }
611
+ }
596
612
  function validateRecurrence(params) {
597
613
  const rtRaw = params.recurrence_type ?? "none";
598
614
  const rt = rtRaw.toLowerCase();
@@ -844,6 +860,32 @@ async function handleCreateTask(supabase, userId, resolveAgent, params, fallback
844
860
  if (nextRunAtEpoch <= nowEpoch) {
845
861
  nextRunAtEpoch = nowEpoch + 60 + Math.floor(Math.random() * 300);
846
862
  }
863
+ // Fix #3 (2026-05-17): bulk-recurring stagger safety net. Fix #2
864
+ // only jitters past-time bumps; an LLM that intentionally seeds
865
+ // 6+ recurring tasks with a shared FUTURE T0 anchor still bursts
866
+ // every tick. Detect the burst by counting recent recurring
867
+ // scheduler_events for this user (last 60s); once >3 exist, push
868
+ // the new task's first fire by (recent × per-slot) seconds where
869
+ // per-slot = min(recurrence_interval_secs, 1800). For 6 minutely
870
+ // creates this spreads first-fires across ~5 min instead of one
871
+ // pg_cron tick. Cap per-slot at 30min so long-interval cadences
872
+ // (hourly/daily) don't push first fires hours out.
873
+ const rTypeStr = params.recurrence_type || "none";
874
+ const rIntervalNum = params.recurrence_interval || 1;
875
+ if (rTypeStr !== "none" && rIntervalNum > 0) {
876
+ const sinceISO = new Date(Date.now() - 60_000).toISOString();
877
+ const { count: recentCount } = await supabase.from("scheduler_events")
878
+ .select("id", { count: "exact", head: true })
879
+ .eq("user_id", userId)
880
+ .neq("recurrence_type", "none")
881
+ .gte("created_at", sinceISO);
882
+ const recent = recentCount ?? 0;
883
+ if (recent >= 3) {
884
+ const ivSecs = recurrenceIntervalSeconds(rTypeStr, rIntervalNum);
885
+ const perSlotSecs = Math.min(ivSecs, 1800);
886
+ nextRunAtEpoch += recent * perSlotSecs;
887
+ }
888
+ }
847
889
  await supabase.from("scheduler_events").insert({
848
890
  id: crypto.randomUUID(),
849
891
  user_id: userId,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ofiere-openclaw-plugin",
3
- "version": "4.56.5",
3
+ "version": "4.56.6",
4
4
  "type": "module",
5
5
  "description": "OpenClaw plugin for Ofiere PM - 18 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, corporate frameworks, agent office canvas, and PM gate approvals",
6
6
  "keywords": [
package/src/tools.ts CHANGED
@@ -292,6 +292,7 @@ function registerTaskOps(
292
292
  ` • "every N weeks" → recurrence_type:"weekly" + recurrence_interval:N\n` +
293
293
  ` • "every N months" → recurrence_type:"monthly" + recurrence_interval:N\n` +
294
294
  `🛑 NEVER use daily+1 (or hourly+24, etc.) when the user said a minute-level cadence — the task will fire ONCE per 24h instead of every N minutes. This was a real prod bug (v1 smoke 2026-05-17).\n` +
295
+ `🚦 BURST GUARD (Fix #3, 2026-05-17): when you create >3 recurring tasks within 60s for the same user, the server auto-staggers each new task's first fire by (count × min(recurrence_interval_secs, 1800)) so 6+ tasks with a shared T0 anchor do NOT all burst on the same pg_cron tick. Pass distinct start_date values per task if you need precise first-fire times.\n` +
295
296
  `\n` +
296
297
  `Approvals: Use add_approval to request sign-off from humans or agents. Approvals are separate from workflow gate nodes.\n` +
297
298
  `Status: PENDING, IN_PROGRESS, DONE, FAILED | Priority: 0=LOW, 1=MEDIUM, 2=HIGH, 3=CRITICAL`,
@@ -660,6 +661,22 @@ function humanizeCadence(type: string, interval: number): string {
660
661
  return interval === 1 ? `every ${unit}` : `every ${interval} ${unit}s`;
661
662
  }
662
663
 
664
+ // Fix #3 (2026-05-17): bulk-create stagger helper. Maps recurrence_type +
665
+ // interval to cadence in seconds; used to compute the per-slot offset
666
+ // applied when a user bulk-creates >3 recurring scheduler_events within a
667
+ // 60s window (see handleCreateTask below). monthly approximated as 30 days.
668
+ function recurrenceIntervalSeconds(rtype: string, ri: number): number {
669
+ const safe = Math.max(1, ri || 1);
670
+ switch (rtype) {
671
+ case "minutely": return 60 * safe;
672
+ case "hourly": return 3600 * safe;
673
+ case "daily": return 86400 * safe;
674
+ case "weekly": return 604800 * safe;
675
+ case "monthly": return 2592000 * safe;
676
+ default: return 60;
677
+ }
678
+ }
679
+
663
680
  function validateRecurrence(
664
681
  params: Record<string, unknown>,
665
682
  ): { ok: true } | { ok: false; reason: string } {
@@ -941,6 +958,33 @@ async function handleCreateTask(
941
958
  nextRunAtEpoch = nowEpoch + 60 + Math.floor(Math.random() * 300);
942
959
  }
943
960
 
961
+ // Fix #3 (2026-05-17): bulk-recurring stagger safety net. Fix #2
962
+ // only jitters past-time bumps; an LLM that intentionally seeds
963
+ // 6+ recurring tasks with a shared FUTURE T0 anchor still bursts
964
+ // every tick. Detect the burst by counting recent recurring
965
+ // scheduler_events for this user (last 60s); once >3 exist, push
966
+ // the new task's first fire by (recent × per-slot) seconds where
967
+ // per-slot = min(recurrence_interval_secs, 1800). For 6 minutely
968
+ // creates this spreads first-fires across ~5 min instead of one
969
+ // pg_cron tick. Cap per-slot at 30min so long-interval cadences
970
+ // (hourly/daily) don't push first fires hours out.
971
+ const rTypeStr = (params.recurrence_type as string) || "none";
972
+ const rIntervalNum = (params.recurrence_interval as number) || 1;
973
+ if (rTypeStr !== "none" && rIntervalNum > 0) {
974
+ const sinceISO = new Date(Date.now() - 60_000).toISOString();
975
+ const { count: recentCount } = await supabase.from("scheduler_events")
976
+ .select("id", { count: "exact", head: true })
977
+ .eq("user_id", userId)
978
+ .neq("recurrence_type", "none")
979
+ .gte("created_at", sinceISO);
980
+ const recent = recentCount ?? 0;
981
+ if (recent >= 3) {
982
+ const ivSecs = recurrenceIntervalSeconds(rTypeStr, rIntervalNum);
983
+ const perSlotSecs = Math.min(ivSecs, 1800);
984
+ nextRunAtEpoch += recent * perSlotSecs;
985
+ }
986
+ }
987
+
944
988
  await supabase.from("scheduler_events").insert({
945
989
  id: crypto.randomUUID(),
946
990
  user_id: userId,