ofiere-openclaw-plugin 4.56.5 → 4.56.7

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();
@@ -833,7 +849,8 @@ async function handleCreateTask(supabase, userId, resolveAgent, params, fallback
833
849
  const effectiveAgentId = insertData.agent_id || assignee;
834
850
  if (startDate && effectiveAgentId && normalized) {
835
851
  try {
836
- let { nextRunAtEpoch } = normalized;
852
+ const originalEpoch = normalized.nextRunAtEpoch;
853
+ let nextRunAtEpoch = originalEpoch;
837
854
  const nowEpoch = Math.floor(Date.now() / 1000);
838
855
  // Safety net: if computed time is in the past, schedule for now + 60s
839
856
  // Fix #2 (2026-05-17): jitter past-time bump 0..5min so bulk creates
@@ -844,6 +861,47 @@ async function handleCreateTask(supabase, userId, resolveAgent, params, fallback
844
861
  if (nextRunAtEpoch <= nowEpoch) {
845
862
  nextRunAtEpoch = nowEpoch + 60 + Math.floor(Math.random() * 300);
846
863
  }
864
+ // Fix #3 (2026-05-17): bulk-recurring stagger safety net. Fix #2
865
+ // only jitters past-time bumps; an LLM that intentionally seeds
866
+ // 6+ recurring tasks with a shared FUTURE T0 anchor still bursts
867
+ // every tick. Detect the burst by counting recent recurring
868
+ // scheduler_events for this user (last 60s); once >3 exist, push
869
+ // the new task's first fire by (recent × per-slot) seconds where
870
+ // per-slot = min(recurrence_interval_secs, 1800). For 6 minutely
871
+ // creates this spreads first-fires across ~5 min instead of one
872
+ // pg_cron tick. Cap per-slot at 30min so long-interval cadences
873
+ // (hourly/daily) don't push first fires hours out.
874
+ const rTypeStr = params.recurrence_type || "none";
875
+ const rIntervalNum = params.recurrence_interval || 1;
876
+ if (rTypeStr !== "none" && rIntervalNum > 0) {
877
+ const sinceISO = new Date(Date.now() - 60_000).toISOString();
878
+ const { count: recentCount } = await supabase.from("scheduler_events")
879
+ .select("id", { count: "exact", head: true })
880
+ .eq("user_id", userId)
881
+ .neq("recurrence_type", "none")
882
+ .gte("created_at", sinceISO);
883
+ const recent = recentCount ?? 0;
884
+ if (recent >= 3) {
885
+ const ivSecs = recurrenceIntervalSeconds(rTypeStr, rIntervalNum);
886
+ const perSlotSecs = Math.min(ivSecs, 1800);
887
+ nextRunAtEpoch += recent * perSlotSecs;
888
+ }
889
+ }
890
+ // Fix #3 cosmetic parity (2026-05-17): when either Fix #2 jitter or
891
+ // Fix #3 stagger mutated nextRunAtEpoch, recompute the local-clock
892
+ // display fields so the dashboard UI doesn't show the ORIGINAL T0 for
893
+ // an event that actually fires minutes later. Dispatch uses
894
+ // next_run_at, but users read scheduled_date+scheduled_time.
895
+ let scheduledDateLocalFinal = normalized.scheduledDateLocal;
896
+ let scheduledTimeLocalFinal = normalized.scheduledTimeLocal;
897
+ if (nextRunAtEpoch !== originalEpoch) {
898
+ const offsetMin = Math.round(TZ_OFFSET_HOURS * 60);
899
+ const localMs = nextRunAtEpoch * 1000 + offsetMin * 60 * 1000;
900
+ const localD = new Date(localMs);
901
+ const padN = (n) => String(n).padStart(2, "0");
902
+ scheduledDateLocalFinal = `${localD.getUTCFullYear()}-${padN(localD.getUTCMonth() + 1)}-${padN(localD.getUTCDate())}`;
903
+ scheduledTimeLocalFinal = `${padN(localD.getUTCHours())}:${padN(localD.getUTCMinutes())}`;
904
+ }
847
905
  await supabase.from("scheduler_events").insert({
848
906
  id: crypto.randomUUID(),
849
907
  user_id: userId,
@@ -852,8 +910,8 @@ async function handleCreateTask(supabase, userId, resolveAgent, params, fallback
852
910
  subagent_id: subagentId,
853
911
  title: params.title,
854
912
  description: params.description || params.instructions || null,
855
- scheduled_date: normalized.scheduledDateLocal,
856
- scheduled_time: normalized.scheduledTimeLocal,
913
+ scheduled_date: scheduledDateLocalFinal,
914
+ scheduled_time: scheduledTimeLocalFinal,
857
915
  duration_minutes: 30,
858
916
  recurrence_type: params.recurrence_type || "none",
859
917
  recurrence_interval: params.recurrence_interval || 1,
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.7",
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 } {
@@ -929,7 +946,8 @@ async function handleCreateTask(
929
946
  const effectiveAgentId = (insertData.agent_id as string) || assignee;
930
947
  if (startDate && effectiveAgentId && normalized) {
931
948
  try {
932
- let { nextRunAtEpoch } = normalized;
949
+ const originalEpoch = normalized.nextRunAtEpoch;
950
+ let nextRunAtEpoch = originalEpoch;
933
951
  const nowEpoch = Math.floor(Date.now() / 1000);
934
952
  // Safety net: if computed time is in the past, schedule for now + 60s
935
953
  // Fix #2 (2026-05-17): jitter past-time bump 0..5min so bulk creates
@@ -941,6 +959,49 @@ async function handleCreateTask(
941
959
  nextRunAtEpoch = nowEpoch + 60 + Math.floor(Math.random() * 300);
942
960
  }
943
961
 
962
+ // Fix #3 (2026-05-17): bulk-recurring stagger safety net. Fix #2
963
+ // only jitters past-time bumps; an LLM that intentionally seeds
964
+ // 6+ recurring tasks with a shared FUTURE T0 anchor still bursts
965
+ // every tick. Detect the burst by counting recent recurring
966
+ // scheduler_events for this user (last 60s); once >3 exist, push
967
+ // the new task's first fire by (recent × per-slot) seconds where
968
+ // per-slot = min(recurrence_interval_secs, 1800). For 6 minutely
969
+ // creates this spreads first-fires across ~5 min instead of one
970
+ // pg_cron tick. Cap per-slot at 30min so long-interval cadences
971
+ // (hourly/daily) don't push first fires hours out.
972
+ const rTypeStr = (params.recurrence_type as string) || "none";
973
+ const rIntervalNum = (params.recurrence_interval as number) || 1;
974
+ if (rTypeStr !== "none" && rIntervalNum > 0) {
975
+ const sinceISO = new Date(Date.now() - 60_000).toISOString();
976
+ const { count: recentCount } = await supabase.from("scheduler_events")
977
+ .select("id", { count: "exact", head: true })
978
+ .eq("user_id", userId)
979
+ .neq("recurrence_type", "none")
980
+ .gte("created_at", sinceISO);
981
+ const recent = recentCount ?? 0;
982
+ if (recent >= 3) {
983
+ const ivSecs = recurrenceIntervalSeconds(rTypeStr, rIntervalNum);
984
+ const perSlotSecs = Math.min(ivSecs, 1800);
985
+ nextRunAtEpoch += recent * perSlotSecs;
986
+ }
987
+ }
988
+
989
+ // Fix #3 cosmetic parity (2026-05-17): when either Fix #2 jitter or
990
+ // Fix #3 stagger mutated nextRunAtEpoch, recompute the local-clock
991
+ // display fields so the dashboard UI doesn't show the ORIGINAL T0 for
992
+ // an event that actually fires minutes later. Dispatch uses
993
+ // next_run_at, but users read scheduled_date+scheduled_time.
994
+ let scheduledDateLocalFinal = normalized.scheduledDateLocal;
995
+ let scheduledTimeLocalFinal = normalized.scheduledTimeLocal;
996
+ if (nextRunAtEpoch !== originalEpoch) {
997
+ const offsetMin = Math.round(TZ_OFFSET_HOURS * 60);
998
+ const localMs = nextRunAtEpoch * 1000 + offsetMin * 60 * 1000;
999
+ const localD = new Date(localMs);
1000
+ const padN = (n: number) => String(n).padStart(2, "0");
1001
+ scheduledDateLocalFinal = `${localD.getUTCFullYear()}-${padN(localD.getUTCMonth() + 1)}-${padN(localD.getUTCDate())}`;
1002
+ scheduledTimeLocalFinal = `${padN(localD.getUTCHours())}:${padN(localD.getUTCMinutes())}`;
1003
+ }
1004
+
944
1005
  await supabase.from("scheduler_events").insert({
945
1006
  id: crypto.randomUUID(),
946
1007
  user_id: userId,
@@ -949,8 +1010,8 @@ async function handleCreateTask(
949
1010
  subagent_id: subagentId,
950
1011
  title: params.title,
951
1012
  description: (params.description as string) || (params.instructions as string) || null,
952
- scheduled_date: normalized.scheduledDateLocal,
953
- scheduled_time: normalized.scheduledTimeLocal,
1013
+ scheduled_date: scheduledDateLocalFinal,
1014
+ scheduled_time: scheduledTimeLocalFinal,
954
1015
  duration_minutes: 30,
955
1016
  recurrence_type: (params.recurrence_type as string) || "none",
956
1017
  recurrence_interval: (params.recurrence_interval as number) || 1,