ofiere-openclaw-plugin 4.41.1 → 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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. 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.41.1",
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/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 };