ofiere-openclaw-plugin 4.41.0 → 4.42.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.
- package/package.json +1 -1
- package/src/agent-tier.ts +32 -14
- package/src/tools.ts +123 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ofiere-openclaw-plugin",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.42.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "OpenClaw plugin for Ofiere PM - 16 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, and corporate frameworks",
|
|
6
6
|
"keywords": ["openclaw", "ofiere", "project-management", "agents", "plugin"],
|
package/src/agent-tier.ts
CHANGED
|
@@ -53,12 +53,12 @@ export async function resolveAgentTier(
|
|
|
53
53
|
if (!id || !userId) return { tier: null, source: "none" };
|
|
54
54
|
|
|
55
55
|
const key = k(userId, id);
|
|
56
|
-
const hit = cache.get(key);
|
|
57
|
-
if (hit && hit.expires > Date.now()) return hit.value;
|
|
58
56
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
//
|
|
57
|
+
// 1. Manual override — ALWAYS fresh (uncached). Direct-SQL writes to
|
|
58
|
+
// agent_tier_overrides must win immediately, even when a non-override
|
|
59
|
+
// branch is already cached from a prior call.
|
|
60
|
+
let overrideRow: { tier?: string | null } | null = null;
|
|
61
|
+
let overrideQueryFailed = false;
|
|
62
62
|
try {
|
|
63
63
|
const { data } = await supabase
|
|
64
64
|
.from("agent_tier_overrides")
|
|
@@ -66,23 +66,41 @@ export async function resolveAgentTier(
|
|
|
66
66
|
.eq("user_id", userId)
|
|
67
67
|
.eq("agent_id", id)
|
|
68
68
|
.maybeSingle();
|
|
69
|
-
|
|
70
|
-
result = { tier: data.tier, source: "manual" };
|
|
71
|
-
cache.set(key, { value: result, expires: Date.now() + TTL_MS });
|
|
72
|
-
return result;
|
|
73
|
-
}
|
|
69
|
+
overrideRow = data ?? null;
|
|
74
70
|
} catch {
|
|
75
|
-
// Table missing in older installs —
|
|
71
|
+
// Table missing in older installs — skip override path
|
|
72
|
+
overrideQueryFailed = true;
|
|
76
73
|
}
|
|
77
74
|
|
|
78
|
-
|
|
75
|
+
if (overrideRow?.tier === "c-suite" || overrideRow?.tier === "staff") {
|
|
76
|
+
const result: TierResolution = { tier: overrideRow.tier, source: "manual" };
|
|
77
|
+
cache.set(key, { value: result, expires: Date.now() + TTL_MS });
|
|
78
|
+
return result;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Override absent: if cache holds a stale `manual` entry from a deleted
|
|
82
|
+
// override row, drop it so the auto branches re-resolve.
|
|
83
|
+
if (!overrideQueryFailed) {
|
|
84
|
+
const cachedManual = cache.get(key);
|
|
85
|
+
if (cachedManual && cachedManual.value.source === "manual") {
|
|
86
|
+
cache.delete(key);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// 2. Cache lookup for non-override branches.
|
|
91
|
+
const hit = cache.get(key);
|
|
92
|
+
if (hit && hit.expires > Date.now()) return hit.value;
|
|
93
|
+
|
|
94
|
+
let result: TierResolution = { tier: null, source: "none" };
|
|
95
|
+
|
|
96
|
+
// 3. C-Suite: hardcoded roster
|
|
79
97
|
if (ROSTER_IDS.has(id)) {
|
|
80
98
|
result = { tier: "c-suite", source: "roster" };
|
|
81
99
|
cache.set(key, { value: result, expires: Date.now() + TTL_MS });
|
|
82
100
|
return result;
|
|
83
101
|
}
|
|
84
102
|
|
|
85
|
-
//
|
|
103
|
+
// 4. agent_architectures.executive_role / department_role
|
|
86
104
|
try {
|
|
87
105
|
const { data } = await supabase
|
|
88
106
|
.from("agent_architectures")
|
|
@@ -105,7 +123,7 @@ export async function resolveAgentTier(
|
|
|
105
123
|
// Table missing — fall through
|
|
106
124
|
}
|
|
107
125
|
|
|
108
|
-
//
|
|
126
|
+
// 5. Staff: registered as subagent
|
|
109
127
|
try {
|
|
110
128
|
const { data } = await supabase
|
|
111
129
|
.from("agent_subagents")
|
package/src/tools.ts
CHANGED
|
@@ -38,6 +38,42 @@ function err(message: string): ToolResult {
|
|
|
38
38
|
};
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
+
// ─── Subagent ↔ chief invariant ──────────────────────────────────────────────
|
|
42
|
+
// Mirrors dashboard/lib/subagentValidation.ts. Used by TASK_OPS + SCHEDULE_OPS
|
|
43
|
+
// when a chief delegates work to one of their staff via tool call.
|
|
44
|
+
//
|
|
45
|
+
// Returns:
|
|
46
|
+
// { ok: true, subagentId: <id> | null } — passed (or no subagent provided)
|
|
47
|
+
// { ok: false, reason: string } — invariant violated
|
|
48
|
+
async function validateSubagentForChief(
|
|
49
|
+
supabase: SupabaseClient,
|
|
50
|
+
userId: string,
|
|
51
|
+
subagentIdInput: unknown,
|
|
52
|
+
chiefAgentId: string | null | undefined,
|
|
53
|
+
): Promise<{ ok: true; subagentId: string | null } | { ok: false; reason: string }> {
|
|
54
|
+
const subagentId =
|
|
55
|
+
typeof subagentIdInput === "string" && subagentIdInput.trim()
|
|
56
|
+
? subagentIdInput.trim()
|
|
57
|
+
: null;
|
|
58
|
+
if (!subagentId) return { ok: true, subagentId: null };
|
|
59
|
+
|
|
60
|
+
const { data: sub } = await supabase
|
|
61
|
+
.from("agent_subagents")
|
|
62
|
+
.select("id, chief_agent_id")
|
|
63
|
+
.eq("user_id", userId)
|
|
64
|
+
.eq("id", subagentId)
|
|
65
|
+
.maybeSingle();
|
|
66
|
+
|
|
67
|
+
if (!sub) {
|
|
68
|
+
return { ok: false, reason: "subagent_not_found_or_not_yours" };
|
|
69
|
+
}
|
|
70
|
+
const chief = (chiefAgentId || "").trim();
|
|
71
|
+
if (!chief || sub.chief_agent_id !== chief) {
|
|
72
|
+
return { ok: false, reason: "subagent_chief_mismatch" };
|
|
73
|
+
}
|
|
74
|
+
return { ok: true, subagentId };
|
|
75
|
+
}
|
|
76
|
+
|
|
41
77
|
// ─── Helper: extract calling agent's accountId from OpenClaw context ─────────
|
|
42
78
|
|
|
43
79
|
// Module-level: set once at registration time from index.ts
|
|
@@ -205,8 +241,8 @@ function registerTaskOps(
|
|
|
205
241
|
`Actions:\n` +
|
|
206
242
|
`- "list": List/filter tasks. Optional: status, agent_id, space_id, folder_id, task_id, limit\n` +
|
|
207
243
|
`- "get": Get a single task by ID. Required: task_id\n` +
|
|
208
|
-
`- "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. ⚠️ PLANNING GATE: If the task has 3+ execution steps, goals, or constraints, FIRST ask the user "Plan first or create directly?" If they choose to plan, use OFIERE_PLAN_OPS instead.\n` +
|
|
209
|
-
`- "update": Update a task. Required: task_id. Optional: all create fields + progress\n` +
|
|
244
|
+
`- "create": Create a task. Required: title. Optional: agent_id, subagent_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. ⚠️ PLANNING GATE: If the task has 3+ execution steps, goals, or constraints, FIRST ask the user "Plan first or create directly?" If they choose to plan, use OFIERE_PLAN_OPS instead.\n` +
|
|
245
|
+
`- "update": Update a task. Required: task_id. Optional: all create fields + progress + subagent_id\n` +
|
|
210
246
|
`- "delete": Delete task + subtasks. Required: task_id\n` +
|
|
211
247
|
`- "add_approval": Request approval on a task. Required: task_id, approver_name. Optional: approver_type (human|agent, auto-detected), due_date, comment\n` +
|
|
212
248
|
`- "list_approvals": List approvals. Optional: task_id, approval_status filter (pending|approved|rejected)\n` +
|
|
@@ -214,6 +250,7 @@ function registerTaskOps(
|
|
|
214
250
|
`For complex tasks, fill in execution_plan (step-by-step plan), goals, constraints, and system_prompt to help the executing agent.\n` +
|
|
215
251
|
`For simple tasks, just provide title and optionally description.\n` +
|
|
216
252
|
`agent_id: Pass your name to self-assign, another agent's name, or 'none'.\n` +
|
|
253
|
+
`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` +
|
|
217
254
|
`For recurring tasks: set start_date + recurrence_type + recurrence_interval. Example: every 2 minutes = recurrence_type: "minutely", recurrence_interval: 2.\n` +
|
|
218
255
|
`Approvals: Use add_approval to request sign-off from humans or agents. Approvals are separate from workflow gate nodes.\n` +
|
|
219
256
|
`Status: PENDING, IN_PROGRESS, DONE, FAILED | Priority: 0=LOW, 1=MEDIUM, 2=HIGH, 3=CRITICAL`,
|
|
@@ -237,6 +274,10 @@ function registerTaskOps(
|
|
|
237
274
|
type: "string",
|
|
238
275
|
description: "Agent name or ID. Your name to self-assign, 'none' for unassigned.",
|
|
239
276
|
},
|
|
277
|
+
subagent_id: {
|
|
278
|
+
type: "string",
|
|
279
|
+
description: "Staff subagent UUID for delegation. Must belong to the chief in agent_id (verified server-side). Discover available staff via OFIERE_AGENT_OPS list_subagents.",
|
|
280
|
+
},
|
|
240
281
|
status: {
|
|
241
282
|
type: "string",
|
|
242
283
|
description: "Task status",
|
|
@@ -437,6 +478,13 @@ async function handleCreateTask(
|
|
|
437
478
|
|
|
438
479
|
const assignee = isUnassigned ? null : await resolveAgent(rawAgentId);
|
|
439
480
|
|
|
481
|
+
// ── Subagent delegation: validate staff↔chief invariant ──────────────
|
|
482
|
+
const subCheck = await validateSubagentForChief(supabase, userId, params.subagent_id, assignee);
|
|
483
|
+
if (!subCheck.ok) {
|
|
484
|
+
return err(subCheck.reason);
|
|
485
|
+
}
|
|
486
|
+
const subagentId = subCheck.subagentId;
|
|
487
|
+
|
|
440
488
|
// Build custom_fields from task-ops extended fields
|
|
441
489
|
const cf: Record<string, unknown> = {};
|
|
442
490
|
|
|
@@ -533,6 +581,7 @@ async function handleCreateTask(
|
|
|
533
581
|
title: params.title,
|
|
534
582
|
description: (params.description as string) || (params.instructions as string) || null,
|
|
535
583
|
agent_id: assignee,
|
|
584
|
+
subagent_id: subagentId,
|
|
536
585
|
assignee_type: "agent",
|
|
537
586
|
status: (params.status as string) || "PENDING",
|
|
538
587
|
priority: params.priority !== undefined ? params.priority : 1,
|
|
@@ -666,6 +715,7 @@ async function handleCreateTask(
|
|
|
666
715
|
user_id: userId,
|
|
667
716
|
task_id: id,
|
|
668
717
|
agent_id: effectiveAgentId,
|
|
718
|
+
subagent_id: subagentId,
|
|
669
719
|
title: params.title,
|
|
670
720
|
description: (params.description as string) || (params.instructions as string) || null,
|
|
671
721
|
scheduled_date: scheduledDateFinal,
|
|
@@ -743,6 +793,37 @@ async function handleUpdateTask(
|
|
|
743
793
|
}
|
|
744
794
|
if (params.status === "DONE") updates.completed_at = new Date().toISOString();
|
|
745
795
|
|
|
796
|
+
// ── Subagent + chief invariant ───────────────────────────────────────
|
|
797
|
+
// Mirrors dashboard PATCH: chief change without explicit subagent_id
|
|
798
|
+
// clears any existing subagent assignment; subagent_id alone validates
|
|
799
|
+
// against the row's current agent_id.
|
|
800
|
+
if (params.agent_id !== undefined || params.subagent_id !== undefined) {
|
|
801
|
+
if (params.agent_id !== undefined && params.subagent_id === undefined) {
|
|
802
|
+
// Chief switched, no explicit subagent — clear subagent_id.
|
|
803
|
+
updates.subagent_id = null;
|
|
804
|
+
} else {
|
|
805
|
+
let effectiveAgentId: string | null | undefined =
|
|
806
|
+
params.agent_id !== undefined ? (params.agent_id as string) : undefined;
|
|
807
|
+
if (effectiveAgentId === undefined) {
|
|
808
|
+
const { data: row } = await supabase
|
|
809
|
+
.from("tasks")
|
|
810
|
+
.select("agent_id")
|
|
811
|
+
.eq("id", params.task_id as string)
|
|
812
|
+
.eq("user_id", userId)
|
|
813
|
+
.maybeSingle();
|
|
814
|
+
effectiveAgentId = (row?.agent_id as string) ?? null;
|
|
815
|
+
}
|
|
816
|
+
const subCheck = await validateSubagentForChief(
|
|
817
|
+
supabase,
|
|
818
|
+
userId,
|
|
819
|
+
params.subagent_id,
|
|
820
|
+
effectiveAgentId,
|
|
821
|
+
);
|
|
822
|
+
if (!subCheck.ok) return err(subCheck.reason);
|
|
823
|
+
updates.subagent_id = subCheck.subagentId;
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
|
|
746
827
|
// If task is being marked DONE or FAILED, auto-complete any linked scheduler events
|
|
747
828
|
if (params.status === "DONE" || params.status === "FAILED") {
|
|
748
829
|
try {
|
|
@@ -1395,8 +1476,8 @@ function registerScheduleOps(
|
|
|
1395
1476
|
`Manage calendar events and schedule tasks on the timeline.\n\n` +
|
|
1396
1477
|
`Actions:\n` +
|
|
1397
1478
|
`- "list": List events. Optional: start_date, end_date, agent_id\n` +
|
|
1398
|
-
`- "create": Schedule an event. Required: title, scheduled_date. Optional: task_id, agent_id, scheduled_time, duration_minutes, recurrence_type, recurrence_interval, color, priority\n` +
|
|
1399
|
-
`- "update": Update event. Required: id. Optional: title, scheduled_date, scheduled_time, duration_minutes, status, recurrence_type\n` +
|
|
1479
|
+
`- "create": Schedule an event. Required: title, scheduled_date. Optional: task_id, agent_id, subagent_id, scheduled_time, duration_minutes, recurrence_type, recurrence_interval, color, priority\n` +
|
|
1480
|
+
`- "update": Update event. Required: id. Optional: title, scheduled_date, scheduled_time, duration_minutes, status, recurrence_type, agent_id, subagent_id\n` +
|
|
1400
1481
|
`- "delete": Remove event. Required: id\n` +
|
|
1401
1482
|
`recurrence_type: none, hourly, daily, weekly, monthly\n` +
|
|
1402
1483
|
`priority: 0=low, 1=medium, 2=high, 3=critical`,
|
|
@@ -1410,6 +1491,7 @@ function registerScheduleOps(
|
|
|
1410
1491
|
description: { type: "string" },
|
|
1411
1492
|
task_id: { type: "string", description: "Link to a task" },
|
|
1412
1493
|
agent_id: { type: "string", description: "Assigned agent" },
|
|
1494
|
+
subagent_id: { type: "string", description: "Staff subagent UUID for delegation. Must belong to the chief in agent_id (verified server-side)." },
|
|
1413
1495
|
scheduled_date: { type: "string", description: "Date (YYYY-MM-DD)" },
|
|
1414
1496
|
scheduled_time: { type: "string", description: "Time (HH:MM)" },
|
|
1415
1497
|
start_date: { type: "string", description: "List filter: start (YYYY-MM-DD)" },
|
|
@@ -1442,6 +1524,15 @@ function registerScheduleOps(
|
|
|
1442
1524
|
const pVal = typeof params.priority === "number" ? params.priority
|
|
1443
1525
|
: priorityMap[String(params.priority || "").toLowerCase()] ?? 0;
|
|
1444
1526
|
|
|
1527
|
+
// Validate subagent ↔ chief invariant before insert
|
|
1528
|
+
const evtSubCheck = await validateSubagentForChief(
|
|
1529
|
+
supabase,
|
|
1530
|
+
userId,
|
|
1531
|
+
params.subagent_id,
|
|
1532
|
+
(params.agent_id as string) || null,
|
|
1533
|
+
);
|
|
1534
|
+
if (!evtSubCheck.ok) return err(evtSubCheck.reason);
|
|
1535
|
+
|
|
1445
1536
|
// Compute next_run_at from scheduled_date + scheduled_time
|
|
1446
1537
|
let nextRunAt: number | null = null;
|
|
1447
1538
|
try {
|
|
@@ -1461,6 +1552,7 @@ function registerScheduleOps(
|
|
|
1461
1552
|
user_id: userId,
|
|
1462
1553
|
task_id: (params.task_id as string) || null,
|
|
1463
1554
|
agent_id: (params.agent_id as string) || null,
|
|
1555
|
+
subagent_id: evtSubCheck.subagentId,
|
|
1464
1556
|
title: params.title,
|
|
1465
1557
|
description: (params.description as string) || null,
|
|
1466
1558
|
scheduled_date: params.scheduled_date,
|
|
@@ -1498,6 +1590,33 @@ function registerScheduleOps(
|
|
|
1498
1590
|
"recurrence_type", "recurrence_interval", "status", "color", "priority", "agent_id"]) {
|
|
1499
1591
|
if ((params as any)[f] !== undefined) upd[f] = (params as any)[f];
|
|
1500
1592
|
}
|
|
1593
|
+
|
|
1594
|
+
// Subagent + chief invariant. Same rule as TASK_OPS update.
|
|
1595
|
+
if (params.agent_id !== undefined || params.subagent_id !== undefined) {
|
|
1596
|
+
if (params.agent_id !== undefined && params.subagent_id === undefined) {
|
|
1597
|
+
upd.subagent_id = null;
|
|
1598
|
+
} else {
|
|
1599
|
+
let effectiveAgent: string | null | undefined =
|
|
1600
|
+
params.agent_id !== undefined ? (params.agent_id as string) : undefined;
|
|
1601
|
+
if (effectiveAgent === undefined) {
|
|
1602
|
+
const { data: row } = await supabase
|
|
1603
|
+
.from("scheduler_events")
|
|
1604
|
+
.select("agent_id")
|
|
1605
|
+
.eq("id", params.id as string)
|
|
1606
|
+
.eq("user_id", userId)
|
|
1607
|
+
.maybeSingle();
|
|
1608
|
+
effectiveAgent = (row?.agent_id as string) ?? null;
|
|
1609
|
+
}
|
|
1610
|
+
const evtUpdSub = await validateSubagentForChief(
|
|
1611
|
+
supabase,
|
|
1612
|
+
userId,
|
|
1613
|
+
params.subagent_id,
|
|
1614
|
+
effectiveAgent,
|
|
1615
|
+
);
|
|
1616
|
+
if (!evtUpdSub.ok) return err(evtUpdSub.reason);
|
|
1617
|
+
upd.subagent_id = evtUpdSub.subagentId;
|
|
1618
|
+
}
|
|
1619
|
+
}
|
|
1501
1620
|
// Map string priority to number if provided
|
|
1502
1621
|
if (upd.priority !== undefined && typeof upd.priority === "string") {
|
|
1503
1622
|
const pMap: Record<string, number> = { low: 0, medium: 1, high: 2, critical: 3 };
|