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 +61 -3
- package/package.json +1 -1
- package/src/tools.ts +64 -3
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
|
-
|
|
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:
|
|
856
|
-
scheduled_time:
|
|
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.
|
|
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
|
-
|
|
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:
|
|
953
|
-
scheduled_time:
|
|
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,
|