ofiere-openclaw-plugin 4.56.4 → 4.56.5
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 +114 -3
- package/package.json +1 -1
- package/src/tools.ts +127 -3
package/dist/src/tools.js
CHANGED
|
@@ -244,7 +244,17 @@ function registerTaskOps(api, supabase, userId, resolveAgent, timezone) {
|
|
|
244
244
|
`For simple tasks, just provide title and optionally description.\n` +
|
|
245
245
|
`agent_id: Pass your name to self-assign, another agent's name, or 'none'.\n` +
|
|
246
246
|
`subagent_id: When delegating to one of your own staff, pass the staff UUID here AND set agent_id to yourself (or the chief). The staff's chief_agent_id MUST match agent_id or the call rejects with subagent_chief_mismatch. Use OFIERE_AGENT_OPS list_subagents to discover available staff under a chief.\n` +
|
|
247
|
-
|
|
247
|
+
`\n` +
|
|
248
|
+
`For recurring tasks: set start_date + recurrence_type + recurrence_interval.\n` +
|
|
249
|
+
`⚠️ CADENCE MAPPING — match the user's spoken phrase EXACTLY (server-side cadence-intent guard rejects mismatches):\n` +
|
|
250
|
+
` • "every N seconds" → recurrence_type:"minutely" + recurrence_interval:max(1, ceil(N/60)) ← cron resolution = 1 minute\n` +
|
|
251
|
+
` • "every N minutes" → recurrence_type:"minutely" + recurrence_interval:N ← e.g. every 35 min → minutely+35\n` +
|
|
252
|
+
` • "every N hours" → recurrence_type:"hourly" + recurrence_interval:N\n` +
|
|
253
|
+
` • "every N days" → recurrence_type:"daily" + recurrence_interval:N ← daily+1 = 24h gap!\n` +
|
|
254
|
+
` • "every N weeks" → recurrence_type:"weekly" + recurrence_interval:N\n` +
|
|
255
|
+
` • "every N months" → recurrence_type:"monthly" + recurrence_interval:N\n` +
|
|
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
|
+
`\n` +
|
|
248
258
|
`Approvals: Use add_approval to request sign-off from humans or agents. Approvals are separate from workflow gate nodes.\n` +
|
|
249
259
|
`Status: PENDING, IN_PROGRESS, DONE, FAILED | Priority: 0=LOW, 1=MEDIUM, 2=HIGH, 3=CRITICAL`,
|
|
250
260
|
parameters: {
|
|
@@ -561,10 +571,104 @@ function normalizeStartDate(startDate, explicitScheduledTime, tzOffsetHours) {
|
|
|
561
571
|
scheduledDateLocal: `${year}-${pad(month)}-${pad(day)}`,
|
|
562
572
|
};
|
|
563
573
|
}
|
|
574
|
+
// ── Fix #10 (2026-05-17): Cadence-intent guard ──────────────────────────────
|
|
575
|
+
// v1 smoke incident: LLM picked recurrence_type='daily' + recurrence_interval=1
|
|
576
|
+
// for a task whose title said "every 35 minutes" — so it fired ONCE per 24h
|
|
577
|
+
// instead of every 35 min. Silent test failure. This guard inspects the task
|
|
578
|
+
// text for an explicit "every N <unit>" phrase and rejects the create when the
|
|
579
|
+
// chosen recurrence_type/interval doesn't match. Combined with the tightened
|
|
580
|
+
// OFIERE_TASK_OPS description (cadence-mapping table), prevents the drift at
|
|
581
|
+
// both the LLM-prompt layer and the server-runtime layer.
|
|
582
|
+
const RECURRENCE_TYPES = ["none", "minutely", "hourly", "daily", "weekly", "monthly"];
|
|
583
|
+
function humanizeCadence(type, interval) {
|
|
584
|
+
if (type === "none")
|
|
585
|
+
return "one-shot";
|
|
586
|
+
const unitMap = {
|
|
587
|
+
minutely: "minute",
|
|
588
|
+
hourly: "hour",
|
|
589
|
+
daily: "day",
|
|
590
|
+
weekly: "week",
|
|
591
|
+
monthly: "month",
|
|
592
|
+
};
|
|
593
|
+
const unit = unitMap[type] || type;
|
|
594
|
+
return interval === 1 ? `every ${unit}` : `every ${interval} ${unit}s`;
|
|
595
|
+
}
|
|
596
|
+
function validateRecurrence(params) {
|
|
597
|
+
const rtRaw = params.recurrence_type ?? "none";
|
|
598
|
+
const rt = rtRaw.toLowerCase();
|
|
599
|
+
const riRaw = params.recurrence_interval;
|
|
600
|
+
const ri = typeof riRaw === "number" ? riRaw : (riRaw === undefined ? 1 : Number(riRaw));
|
|
601
|
+
if (!RECURRENCE_TYPES.includes(rt)) {
|
|
602
|
+
return {
|
|
603
|
+
ok: false,
|
|
604
|
+
reason: `Invalid recurrence_type "${rtRaw}". Must be one of: ${RECURRENCE_TYPES.join(", ")}.`,
|
|
605
|
+
};
|
|
606
|
+
}
|
|
607
|
+
if (rt === "none")
|
|
608
|
+
return { ok: true };
|
|
609
|
+
if (!Number.isFinite(ri) || !Number.isInteger(ri) || ri < 1 || ri > 9999) {
|
|
610
|
+
return {
|
|
611
|
+
ok: false,
|
|
612
|
+
reason: `Invalid recurrence_interval ${riRaw}. Must be a positive integer between 1 and 9999.`,
|
|
613
|
+
};
|
|
614
|
+
}
|
|
615
|
+
// Cadence-intent guard: scan title + description for an "every N <unit>"
|
|
616
|
+
// phrase. If present, the chosen recurrence_type+interval MUST match.
|
|
617
|
+
const text = `${params.title || ""} ${params.description || ""}`.toLowerCase();
|
|
618
|
+
const cadenceMatch = text.match(/every\s+(\d+)\s*(seconds?|secs?|minutes?|mins?|hours?|hrs?|days?|weeks?|months?)\b/);
|
|
619
|
+
if (!cadenceMatch)
|
|
620
|
+
return { ok: true };
|
|
621
|
+
const n = parseInt(cadenceMatch[1], 10);
|
|
622
|
+
const unitRaw = cadenceMatch[2];
|
|
623
|
+
let expectedType;
|
|
624
|
+
let expectedInterval;
|
|
625
|
+
if (/^sec/.test(unitRaw)) {
|
|
626
|
+
expectedType = "minutely";
|
|
627
|
+
expectedInterval = Math.max(1, Math.ceil(n / 60));
|
|
628
|
+
}
|
|
629
|
+
else if (/^min/.test(unitRaw)) {
|
|
630
|
+
expectedType = "minutely";
|
|
631
|
+
expectedInterval = n;
|
|
632
|
+
}
|
|
633
|
+
else if (/^(hour|hr)/.test(unitRaw)) {
|
|
634
|
+
expectedType = "hourly";
|
|
635
|
+
expectedInterval = n;
|
|
636
|
+
}
|
|
637
|
+
else if (/^day/.test(unitRaw)) {
|
|
638
|
+
expectedType = "daily";
|
|
639
|
+
expectedInterval = n;
|
|
640
|
+
}
|
|
641
|
+
else if (/^week/.test(unitRaw)) {
|
|
642
|
+
expectedType = "weekly";
|
|
643
|
+
expectedInterval = n;
|
|
644
|
+
}
|
|
645
|
+
else if (/^month/.test(unitRaw)) {
|
|
646
|
+
expectedType = "monthly";
|
|
647
|
+
expectedInterval = n;
|
|
648
|
+
}
|
|
649
|
+
else {
|
|
650
|
+
return { ok: true };
|
|
651
|
+
}
|
|
652
|
+
if (rt !== expectedType || ri !== expectedInterval) {
|
|
653
|
+
return {
|
|
654
|
+
ok: false,
|
|
655
|
+
reason: `Recurrence mismatch: task text says "every ${n} ${unitRaw}" but you passed ` +
|
|
656
|
+
`recurrence_type="${rt}", recurrence_interval=${ri} (= ${humanizeCadence(rt, ri)}). ` +
|
|
657
|
+
`Correct combo: recurrence_type="${expectedType}", recurrence_interval=${expectedInterval} ` +
|
|
658
|
+
`(= ${humanizeCadence(expectedType, expectedInterval)}). ` +
|
|
659
|
+
`Re-call with the matching values, OR remove the "every N ${unitRaw}" phrase from the title/description if you intended a different cadence.`,
|
|
660
|
+
};
|
|
661
|
+
}
|
|
662
|
+
return { ok: true };
|
|
663
|
+
}
|
|
564
664
|
async function handleCreateTask(supabase, userId, resolveAgent, params, fallbackTimezone = "Asia/Jakarta") {
|
|
565
665
|
try {
|
|
566
666
|
if (!params.title)
|
|
567
667
|
return err("Missing required field: title");
|
|
668
|
+
// Fix #10 (2026-05-17): cadence-intent guard — see helper comment above.
|
|
669
|
+
const recurCheck = validateRecurrence(params);
|
|
670
|
+
if (!recurCheck.ok)
|
|
671
|
+
return err(recurCheck.reason);
|
|
568
672
|
const id = `task-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
569
673
|
const now = new Date().toISOString();
|
|
570
674
|
// BUG 5 fix: resolve user's IANA timezone from their profile row. Plugin
|
|
@@ -774,9 +878,16 @@ async function handleCreateTask(supabase, userId, resolveAgent, params, fallback
|
|
|
774
878
|
extras.push(`${cf.constraints.length} constraints`);
|
|
775
879
|
if (cf.system_prompt)
|
|
776
880
|
extras.push("custom system prompt");
|
|
777
|
-
// BUG 6 fix: surface recurrence fields in create response
|
|
881
|
+
// BUG 6 fix: surface recurrence fields in create response.
|
|
882
|
+
// Fix #10 (2026-05-17): also surface effective_cadence so the LLM sees the
|
|
883
|
+
// human-readable interpretation (e.g. "every 35 minutes") of the values it
|
|
884
|
+
// just passed — defense-in-depth alongside validateRecurrence().
|
|
778
885
|
const recurrenceInfo = (params.recurrence_type && params.recurrence_type !== "none")
|
|
779
|
-
? {
|
|
886
|
+
? {
|
|
887
|
+
recurrence_type: params.recurrence_type,
|
|
888
|
+
recurrence_interval: params.recurrence_interval || 1,
|
|
889
|
+
effective_cadence: humanizeCadence(params.recurrence_type, params.recurrence_interval || 1),
|
|
890
|
+
}
|
|
780
891
|
: undefined;
|
|
781
892
|
// BUG 7 fix: only claim scheduling when bridge actually fired
|
|
782
893
|
const didSchedule = !!(startDate && effectiveAgentId);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ofiere-openclaw-plugin",
|
|
3
|
-
"version": "4.56.
|
|
3
|
+
"version": "4.56.5",
|
|
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
|
@@ -282,7 +282,17 @@ function registerTaskOps(
|
|
|
282
282
|
`For simple tasks, just provide title and optionally description.\n` +
|
|
283
283
|
`agent_id: Pass your name to self-assign, another agent's name, or 'none'.\n` +
|
|
284
284
|
`subagent_id: When delegating to one of your own staff, pass the staff UUID here AND set agent_id to yourself (or the chief). The staff's chief_agent_id MUST match agent_id or the call rejects with subagent_chief_mismatch. Use OFIERE_AGENT_OPS list_subagents to discover available staff under a chief.\n` +
|
|
285
|
-
|
|
285
|
+
`\n` +
|
|
286
|
+
`For recurring tasks: set start_date + recurrence_type + recurrence_interval.\n` +
|
|
287
|
+
`⚠️ CADENCE MAPPING — match the user's spoken phrase EXACTLY (server-side cadence-intent guard rejects mismatches):\n` +
|
|
288
|
+
` • "every N seconds" → recurrence_type:"minutely" + recurrence_interval:max(1, ceil(N/60)) ← cron resolution = 1 minute\n` +
|
|
289
|
+
` • "every N minutes" → recurrence_type:"minutely" + recurrence_interval:N ← e.g. every 35 min → minutely+35\n` +
|
|
290
|
+
` • "every N hours" → recurrence_type:"hourly" + recurrence_interval:N\n` +
|
|
291
|
+
` • "every N days" → recurrence_type:"daily" + recurrence_interval:N ← daily+1 = 24h gap!\n` +
|
|
292
|
+
` • "every N weeks" → recurrence_type:"weekly" + recurrence_interval:N\n` +
|
|
293
|
+
` • "every N months" → recurrence_type:"monthly" + recurrence_interval:N\n` +
|
|
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
|
+
`\n` +
|
|
286
296
|
`Approvals: Use add_approval to request sign-off from humans or agents. Approvals are separate from workflow gate nodes.\n` +
|
|
287
297
|
`Status: PENDING, IN_PROGRESS, DONE, FAILED | Priority: 0=LOW, 1=MEDIUM, 2=HIGH, 3=CRITICAL`,
|
|
288
298
|
parameters: {
|
|
@@ -625,6 +635,106 @@ function normalizeStartDate(
|
|
|
625
635
|
};
|
|
626
636
|
}
|
|
627
637
|
|
|
638
|
+
// ── Fix #10 (2026-05-17): Cadence-intent guard ──────────────────────────────
|
|
639
|
+
// v1 smoke incident: LLM picked recurrence_type='daily' + recurrence_interval=1
|
|
640
|
+
// for a task whose title said "every 35 minutes" — so it fired ONCE per 24h
|
|
641
|
+
// instead of every 35 min. Silent test failure. This guard inspects the task
|
|
642
|
+
// text for an explicit "every N <unit>" phrase and rejects the create when the
|
|
643
|
+
// chosen recurrence_type/interval doesn't match. Combined with the tightened
|
|
644
|
+
// OFIERE_TASK_OPS description (cadence-mapping table), prevents the drift at
|
|
645
|
+
// both the LLM-prompt layer and the server-runtime layer.
|
|
646
|
+
|
|
647
|
+
const RECURRENCE_TYPES = ["none", "minutely", "hourly", "daily", "weekly", "monthly"] as const;
|
|
648
|
+
type RecurrenceType = (typeof RECURRENCE_TYPES)[number];
|
|
649
|
+
|
|
650
|
+
function humanizeCadence(type: string, interval: number): string {
|
|
651
|
+
if (type === "none") return "one-shot";
|
|
652
|
+
const unitMap: Record<string, string> = {
|
|
653
|
+
minutely: "minute",
|
|
654
|
+
hourly: "hour",
|
|
655
|
+
daily: "day",
|
|
656
|
+
weekly: "week",
|
|
657
|
+
monthly: "month",
|
|
658
|
+
};
|
|
659
|
+
const unit = unitMap[type] || type;
|
|
660
|
+
return interval === 1 ? `every ${unit}` : `every ${interval} ${unit}s`;
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
function validateRecurrence(
|
|
664
|
+
params: Record<string, unknown>,
|
|
665
|
+
): { ok: true } | { ok: false; reason: string } {
|
|
666
|
+
const rtRaw = (params.recurrence_type as string | undefined) ?? "none";
|
|
667
|
+
const rt = rtRaw.toLowerCase() as RecurrenceType;
|
|
668
|
+
const riRaw = params.recurrence_interval;
|
|
669
|
+
const ri = typeof riRaw === "number" ? riRaw : (riRaw === undefined ? 1 : Number(riRaw));
|
|
670
|
+
|
|
671
|
+
if (!RECURRENCE_TYPES.includes(rt)) {
|
|
672
|
+
return {
|
|
673
|
+
ok: false,
|
|
674
|
+
reason:
|
|
675
|
+
`Invalid recurrence_type "${rtRaw}". Must be one of: ${RECURRENCE_TYPES.join(", ")}.`,
|
|
676
|
+
};
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
if (rt === "none") return { ok: true };
|
|
680
|
+
|
|
681
|
+
if (!Number.isFinite(ri) || !Number.isInteger(ri) || ri < 1 || ri > 9999) {
|
|
682
|
+
return {
|
|
683
|
+
ok: false,
|
|
684
|
+
reason:
|
|
685
|
+
`Invalid recurrence_interval ${riRaw}. Must be a positive integer between 1 and 9999.`,
|
|
686
|
+
};
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
// Cadence-intent guard: scan title + description for an "every N <unit>"
|
|
690
|
+
// phrase. If present, the chosen recurrence_type+interval MUST match.
|
|
691
|
+
const text = `${(params.title as string) || ""} ${(params.description as string) || ""}`.toLowerCase();
|
|
692
|
+
const cadenceMatch = text.match(
|
|
693
|
+
/every\s+(\d+)\s*(seconds?|secs?|minutes?|mins?|hours?|hrs?|days?|weeks?|months?)\b/,
|
|
694
|
+
);
|
|
695
|
+
if (!cadenceMatch) return { ok: true };
|
|
696
|
+
|
|
697
|
+
const n = parseInt(cadenceMatch[1], 10);
|
|
698
|
+
const unitRaw = cadenceMatch[2];
|
|
699
|
+
let expectedType: RecurrenceType;
|
|
700
|
+
let expectedInterval: number;
|
|
701
|
+
if (/^sec/.test(unitRaw)) {
|
|
702
|
+
expectedType = "minutely";
|
|
703
|
+
expectedInterval = Math.max(1, Math.ceil(n / 60));
|
|
704
|
+
} else if (/^min/.test(unitRaw)) {
|
|
705
|
+
expectedType = "minutely";
|
|
706
|
+
expectedInterval = n;
|
|
707
|
+
} else if (/^(hour|hr)/.test(unitRaw)) {
|
|
708
|
+
expectedType = "hourly";
|
|
709
|
+
expectedInterval = n;
|
|
710
|
+
} else if (/^day/.test(unitRaw)) {
|
|
711
|
+
expectedType = "daily";
|
|
712
|
+
expectedInterval = n;
|
|
713
|
+
} else if (/^week/.test(unitRaw)) {
|
|
714
|
+
expectedType = "weekly";
|
|
715
|
+
expectedInterval = n;
|
|
716
|
+
} else if (/^month/.test(unitRaw)) {
|
|
717
|
+
expectedType = "monthly";
|
|
718
|
+
expectedInterval = n;
|
|
719
|
+
} else {
|
|
720
|
+
return { ok: true };
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
if (rt !== expectedType || ri !== expectedInterval) {
|
|
724
|
+
return {
|
|
725
|
+
ok: false,
|
|
726
|
+
reason:
|
|
727
|
+
`Recurrence mismatch: task text says "every ${n} ${unitRaw}" but you passed ` +
|
|
728
|
+
`recurrence_type="${rt}", recurrence_interval=${ri} (= ${humanizeCadence(rt, ri)}). ` +
|
|
729
|
+
`Correct combo: recurrence_type="${expectedType}", recurrence_interval=${expectedInterval} ` +
|
|
730
|
+
`(= ${humanizeCadence(expectedType, expectedInterval)}). ` +
|
|
731
|
+
`Re-call with the matching values, OR remove the "every N ${unitRaw}" phrase from the title/description if you intended a different cadence.`,
|
|
732
|
+
};
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
return { ok: true };
|
|
736
|
+
}
|
|
737
|
+
|
|
628
738
|
async function handleCreateTask(
|
|
629
739
|
supabase: SupabaseClient,
|
|
630
740
|
userId: string,
|
|
@@ -635,6 +745,10 @@ async function handleCreateTask(
|
|
|
635
745
|
try {
|
|
636
746
|
if (!params.title) return err("Missing required field: title");
|
|
637
747
|
|
|
748
|
+
// Fix #10 (2026-05-17): cadence-intent guard — see helper comment above.
|
|
749
|
+
const recurCheck = validateRecurrence(params);
|
|
750
|
+
if (!recurCheck.ok) return err(recurCheck.reason);
|
|
751
|
+
|
|
638
752
|
const id = `task-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
639
753
|
const now = new Date().toISOString();
|
|
640
754
|
|
|
@@ -858,9 +972,19 @@ async function handleCreateTask(
|
|
|
858
972
|
if (cf.constraints) extras.push(`${(cf.constraints as any[]).length} constraints`);
|
|
859
973
|
if (cf.system_prompt) extras.push("custom system prompt");
|
|
860
974
|
|
|
861
|
-
// BUG 6 fix: surface recurrence fields in create response
|
|
975
|
+
// BUG 6 fix: surface recurrence fields in create response.
|
|
976
|
+
// Fix #10 (2026-05-17): also surface effective_cadence so the LLM sees the
|
|
977
|
+
// human-readable interpretation (e.g. "every 35 minutes") of the values it
|
|
978
|
+
// just passed — defense-in-depth alongside validateRecurrence().
|
|
862
979
|
const recurrenceInfo = (params.recurrence_type && params.recurrence_type !== "none")
|
|
863
|
-
? {
|
|
980
|
+
? {
|
|
981
|
+
recurrence_type: params.recurrence_type,
|
|
982
|
+
recurrence_interval: (params.recurrence_interval as number) || 1,
|
|
983
|
+
effective_cadence: humanizeCadence(
|
|
984
|
+
params.recurrence_type as string,
|
|
985
|
+
(params.recurrence_interval as number) || 1,
|
|
986
|
+
),
|
|
987
|
+
}
|
|
864
988
|
: undefined;
|
|
865
989
|
|
|
866
990
|
// BUG 7 fix: only claim scheduling when bridge actually fired
|