ofiere-openclaw-plugin 4.40.0 → 4.41.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ofiere-openclaw-plugin",
3
- "version": "4.40.0",
3
+ "version": "4.41.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
@@ -134,6 +134,36 @@ export async function getAgentTier(
134
134
  return (await resolveAgentTier(supabase, agentId, userId)).tier;
135
135
  }
136
136
 
137
+ // Target-aware resolution: when a work target carries a subagent_id, the
138
+ // work is staff-tier regardless of which chief routes it. Uncached lookup —
139
+ // subagent rows can be deleted out from under us.
140
+ export interface TargetTierInput {
141
+ agentId: string | null;
142
+ subagentId?: string | null;
143
+ }
144
+
145
+ export async function resolveTargetTier(
146
+ supabase: SupabaseClient,
147
+ target: TargetTierInput,
148
+ userId: string,
149
+ ): Promise<TierResolution> {
150
+ if (target.subagentId) {
151
+ try {
152
+ const { data } = await supabase
153
+ .from("agent_subagents")
154
+ .select("id")
155
+ .eq("user_id", userId)
156
+ .eq("id", target.subagentId)
157
+ .maybeSingle();
158
+ if (data?.id) return { tier: "staff", source: "subagent" };
159
+ } catch {
160
+ // Fall through to chief lookup
161
+ }
162
+ }
163
+ if (target.agentId) return resolveAgentTier(supabase, target.agentId, userId);
164
+ return { tier: null, source: "none" };
165
+ }
166
+
137
167
  export function isDocKindValidForTier(
138
168
  docKind: "sop" | "framework",
139
169
  tier: AgentTier,
@@ -10,7 +10,7 @@
10
10
  import type { SupabaseClient } from "@supabase/supabase-js";
11
11
  import { renderAttachmentBlock } from "./sop-render.js";
12
12
  import {
13
- resolveAgentTier,
13
+ resolveTargetTier,
14
14
  isDocKindValidForTier,
15
15
  invalidateAgentTier,
16
16
  } from "./agent-tier.js";
@@ -99,24 +99,33 @@ async function applyAttachmentWrite(args: {
99
99
  return { ok: false, error: "unsupported target_kind" };
100
100
  }
101
101
 
102
- // ── Load target row (mainly to read its agent_id for tier check) ──
103
- async function loadTargetAgentId(args: {
102
+ // ── Load target row identity (chief agent + optional subagent) for tier check ──
103
+ interface TargetIdentity {
104
+ agentId: string | null;
105
+ subagentId: string | null;
106
+ }
107
+
108
+ async function loadTargetIdentity(args: {
104
109
  supabase: SupabaseClient;
105
110
  userId: string;
106
111
  targetKind: TargetKind;
107
112
  targetId: string;
108
- }): Promise<string | null> {
113
+ }): Promise<TargetIdentity | null> {
109
114
  const { supabase, userId, targetKind, targetId } = args;
110
115
  const tbl =
111
116
  targetKind === "conversation" ? "conversations" :
112
117
  targetKind === "task" ? "tasks" : "scheduler_events";
113
118
  const { data } = await supabase
114
119
  .from(tbl)
115
- .select("agent_id")
120
+ .select("agent_id, subagent_id")
116
121
  .eq("user_id", userId)
117
122
  .eq("id", targetId)
118
123
  .maybeSingle();
119
- return (data?.agent_id as string) || null;
124
+ if (!data) return null;
125
+ return {
126
+ agentId: (data.agent_id as string | null) ?? null,
127
+ subagentId: (data.subagent_id as string | null) ?? null,
128
+ };
120
129
  }
121
130
 
122
131
  // ── Validate that every doc id belongs to userId in the right table ──
@@ -159,12 +168,14 @@ export async function handleProposeAttach(args: {
159
168
  (x): x is string => typeof x === "string",
160
169
  );
161
170
 
162
- const targetAgentId = await loadTargetAgentId({
171
+ const identity = await loadTargetIdentity({
163
172
  supabase, userId, targetKind, targetId,
164
173
  });
165
- if (!targetAgentId) return err("target_not_found_or_no_agent");
174
+ if (!identity || (!identity.agentId && !identity.subagentId)) {
175
+ return err("target_not_found_or_no_agent");
176
+ }
166
177
 
167
- const tierResolution = await resolveAgentTier(supabase, targetAgentId, userId);
178
+ const tierResolution = await resolveTargetTier(supabase, identity, userId);
168
179
  if (!tierResolution.tier) {
169
180
  return err("agent_unclassified — set tier in dashboard agent settings or via override");
170
181
  }
package/src/prompt.ts CHANGED
@@ -13,7 +13,7 @@
13
13
 
14
14
  const TOOL_SUMMARIES: Record<string, string> = {
15
15
  OFIERE_TASK_OPS: "Manage tasks: list, create, update, delete, approvals",
16
- OFIERE_AGENT_OPS: "Query agents: list all with IDs, names, roles",
16
+ OFIERE_AGENT_OPS: "Query + manage agents and their staff subagents",
17
17
  OFIERE_PROJECT_OPS: "PM hierarchy: spaces, folders, dependencies",
18
18
  OFIERE_SCHEDULE_OPS: "Calendar: list, create, update, delete events",
19
19
  OFIERE_KNOWLEDGE_OPS: "Knowledge library: search, list, create, update, delete",
@@ -48,7 +48,13 @@ Actions: "list", "create", "update", "delete", "add_approval", "list_approvals",
48
48
  - list_approvals: List approvals. Optional: task_id, approval_status (pending|approved|rejected)
49
49
  - resolve_approval: Approve/reject. Requires: approval_id, approval_status. Optional: comment`,
50
50
 
51
- OFIERE_AGENT_OPS: `Query agents. Action: "list" — see all agents with IDs, names, roles for task assignment.`,
51
+ OFIERE_AGENT_OPS: `Query + manage agents and their staff subagents.
52
+ Actions: "list", "list_subagents", "create_subagent", "delete_subagent", "invalidate_tier_cache"
53
+ - list: All top-level agents (chiefs / native + OpenClaw) with IDs, names, roles. Use for task assignment lookup.
54
+ - list_subagents: Staff under a chief. Required: chief_agent_id.
55
+ - create_subagent: Add a staff subagent under a chief (max 5 per chief). Required: chief_agent_id, name. Optional: role (default "Staff"), codename, color_hex.
56
+ - delete_subagent: Remove a staff subagent. Required: subagent_id.
57
+ - invalidate_tier_cache: Flush plugin's in-process tier cache (5 min TTL). Optional: agent_id. Use after direct-DB mutation of agent_tier_overrides.`,
52
58
 
53
59
  OFIERE_PROJECT_OPS: `Manage PM hierarchy.
54
60
  Actions: "list_spaces", "create_space", "update_space", "delete_space", "list_folders", "create_folder", "update_folder", "delete_folder", "list_dependencies", "add_dependency", "remove_dependency"
@@ -107,7 +113,7 @@ Actions: "list", "get", "create", "update", "delete", "add_nodes", "execute"
107
113
  - Execution maps ALL fields: execution_steps, goals, constraints, system_prompt`,
108
114
 
109
115
  OFIERE_SOP_OPS: `Standard Operating Procedures.
110
- Actions: "list_templates", "create", "list", "get", "update", "delete", "list_subagents", "create_subagent", "delete_subagent", "apply_template", "propose_attach", "commit_attach"
116
+ Actions: "list_templates", "create", "list", "get", "update", "delete", "list_subagents", "apply_template", "propose_attach", "commit_attach"
111
117
  - sop_data: { title, purpose, scope, applicability, prerequisites:{ conditions, required_tools, required_permissions, safety_warnings }, procedure_steps[], expected_outputs[], escalation_rules[], acceptance_criteria[], rollback_procedure, notes }
112
118
  - Legacy field names still accepted (objective→purpose, steps→procedure_steps, deliverables→expected_outputs, escalationRules→escalation_rules, successCriteria→acceptance_criteria) — prefer new names.
113
119
  - propose_attach / commit_attach: attach SOPs to a run (target_kind: conversation|task|scheduler_event). Tier rule: SOPs only attach to STAFF-tier targets; for c-suite use OFIERE_FRAMEWORK_OPS instead. propose_attach returns a token-cost summary + confirmation_token (5-min ttl). You MUST surface the cost and ask the user before calling commit_attach. The user must explicitly approve.
package/src/tools.ts CHANGED
@@ -18,6 +18,7 @@ import {
18
18
  handleCommitAttach,
19
19
  registerAttachmentContextHook,
20
20
  } from "./attachments.js";
21
+ import { invalidateAgentTier } from "./agent-tier.js";
21
22
 
22
23
  // ─── Tool result shape (matches OpenClaw SDK) ────────────────────────────────
23
24
 
@@ -1088,9 +1089,13 @@ function registerAgentOps(
1088
1089
  name: "OFIERE_AGENT_OPS",
1089
1090
  label: "Ofiere Agent Operations",
1090
1091
  description:
1091
- `Query agents in the Ofiere PM system.\n\n` +
1092
+ `Query and manage agents + their staff subagents in the Ofiere PM system.\n\n` +
1092
1093
  `Actions:\n` +
1093
- `- "list": List all available agents with their IDs, names, roles, and status. Use this to find the correct agent_id for task assignment.`,
1094
+ `- "list": List all top-level agents (chiefs / native + OpenClaw) with their IDs, names, roles, and status. Use this to find the correct agent_id for task assignment.\n` +
1095
+ `- "list_subagents": List staff subagents under a chief. Required: chief_agent_id.\n` +
1096
+ `- "create_subagent": Create a staff subagent under a chief (max 5 per chief). Required: chief_agent_id, name. Optional: role (default "Staff"), codename, color_hex (default "#64748b").\n` +
1097
+ `- "delete_subagent": Remove a staff subagent. Required: subagent_id.\n` +
1098
+ `- "invalidate_tier_cache": Flush the plugin's in-process tier resolver cache (5 min TTL). Optional: agent_id to flush a single entry; omit to flush all entries for the calling user. Use after any direct-DB mutation of agent_tier_overrides.`,
1094
1099
  parameters: {
1095
1100
  type: "object",
1096
1101
  required: ["action"],
@@ -1098,8 +1103,15 @@ function registerAgentOps(
1098
1103
  action: {
1099
1104
  type: "string",
1100
1105
  description: "The operation to perform",
1101
- enum: ["list"],
1106
+ enum: ["list", "list_subagents", "create_subagent", "delete_subagent", "invalidate_tier_cache"],
1102
1107
  },
1108
+ chief_agent_id: { type: "string", description: "Chief agent ID (required for list_subagents, create_subagent)" },
1109
+ subagent_id: { type: "string", description: "Subagent ID (required for delete_subagent)" },
1110
+ agent_id: { type: "string", description: "Agent ID (optional for invalidate_tier_cache — omit to flush all entries for the user)" },
1111
+ name: { type: "string", description: "Subagent display name (required for create_subagent)" },
1112
+ role: { type: "string", description: "Subagent role label, e.g. 'Staff', 'Analyst'. Defaults to 'Staff'." },
1113
+ codename: { type: "string", description: "Optional subagent codename" },
1114
+ color_hex: { type: "string", description: "Optional subagent UI color, default '#64748b'" },
1103
1115
  },
1104
1116
  },
1105
1117
  async execute(_id: string, params: Record<string, unknown>) {
@@ -1108,13 +1120,35 @@ function registerAgentOps(
1108
1120
  switch (action) {
1109
1121
  case "list":
1110
1122
  return handleListAgents(api, supabase, userId, fallbackAgentId);
1123
+ case "list_subagents":
1124
+ return handleListSubagents(supabase, userId, params);
1125
+ case "create_subagent":
1126
+ return handleCreateSubagent(supabase, userId, params);
1127
+ case "delete_subagent":
1128
+ return handleDeleteSubagent(supabase, userId, params);
1129
+ case "invalidate_tier_cache":
1130
+ return handleInvalidateTierCache(userId, params);
1111
1131
  default:
1112
- return err(`Unknown action "${action}". Valid actions: list`);
1132
+ return err(`Unknown action "${action}". Valid actions: list, list_subagents, create_subagent, delete_subagent, invalidate_tier_cache`);
1113
1133
  }
1114
1134
  },
1115
1135
  });
1116
1136
  }
1117
1137
 
1138
+ function handleInvalidateTierCache(userId: string, params: Record<string, unknown>): ToolResult {
1139
+ const agentId = params.agent_id as string | undefined;
1140
+ invalidateAgentTier(userId, agentId);
1141
+ return ok({
1142
+ ok: true,
1143
+ scope: agentId ? "single" : "user",
1144
+ user_id: userId,
1145
+ agent_id: agentId ?? null,
1146
+ message: agentId
1147
+ ? `Plugin tier cache flushed for agent ${agentId}.`
1148
+ : `Plugin tier cache flushed for all entries belonging to user ${userId}.`,
1149
+ });
1150
+ }
1151
+
1118
1152
  async function handleListAgents(
1119
1153
  api: any,
1120
1154
  supabase: SupabaseClient,
@@ -4847,9 +4881,7 @@ function registerSOPOps(
4847
4881
  `- "get": Get full SOP details. Required: sop_id\n` +
4848
4882
  `- "update": Modify SOP. Required: sop_id. Optional: title, sop_data, status, department\n` +
4849
4883
  `- "delete": Remove SOP. Required: sop_id\n` +
4850
- `- "list_subagents": List subagents for a chief. Required: chief_agent_id\n` +
4851
- `- "create_subagent": Create a staff subagent under a chief (max 5 per chief). Required: chief_agent_id, name. Optional: role (default "Staff"), codename, color_hex (default "#64748b").\n` +
4852
- `- "delete_subagent": Delete a staff subagent. Required: subagent_id\n` +
4884
+ `- "list_subagents": List subagents for a chief. Required: chief_agent_id. (Provided here for convenience when scoping SOP authoring; team management lives in OFIERE_AGENT_OPS.)\n` +
4853
4885
  `- "apply_template": Create SOP from template. Required: agent_id, template_id. Optional: title, department\n` +
4854
4886
  `- "propose_attach": Propose attaching SOPs to a run target (conversation/task/scheduler_event). Returns token cost + confirmation_token. Required: target_kind, target_id, doc_ids[]. The user MUST be asked to approve before commit.\n` +
4855
4887
  `- "commit_attach": Commit a proposed attachment. Required: target_kind, target_id, doc_ids[], confirmation_token (from propose_attach). Only call AFTER user approves the token cost.\n` +
@@ -4873,15 +4905,10 @@ function registerSOPOps(
4873
4905
  type: "object",
4874
4906
  required: ["action"],
4875
4907
  properties: {
4876
- action: { type: "string", enum: ["list_templates", "create", "list", "get", "update", "delete", "list_subagents", "create_subagent", "delete_subagent", "apply_template", "propose_attach", "commit_attach"], description: "Valid actions: list_templates, create, list, get, update, delete, list_subagents, create_subagent, delete_subagent, apply_template, propose_attach, commit_attach" },
4908
+ action: { type: "string", enum: ["list_templates", "create", "list", "get", "update", "delete", "list_subagents", "apply_template", "propose_attach", "commit_attach"], description: "Valid actions: list_templates, create, list, get, update, delete, list_subagents, apply_template, propose_attach, commit_attach" },
4877
4909
  sop_id: { type: "string", description: "SOP ID (required for get, update, delete)" },
4878
4910
  agent_id: { type: "string", description: "Agent ID (required for create, list filter, apply_template)" },
4879
- chief_agent_id: { type: "string", description: "Chief agent ID (required for list_subagents, create_subagent)" },
4880
- subagent_id: { type: "string", description: "Subagent ID (required for delete_subagent)" },
4881
- name: { type: "string", description: "Subagent display name (required for create_subagent)" },
4882
- role: { type: "string", description: "Subagent role label, e.g. 'Staff', 'Analyst'. Defaults to 'Staff'." },
4883
- codename: { type: "string", description: "Optional subagent codename" },
4884
- color_hex: { type: "string", description: "Optional subagent UI color, default '#64748b'" },
4911
+ chief_agent_id: { type: "string", description: "Chief agent ID (required for list_subagents)" },
4885
4912
  template_id: { type: "string", description: "Template ID (required for apply_template)" },
4886
4913
  target_kind: { type: "string", enum: ["conversation", "task", "scheduler_event"], description: "Run target kind (for propose_attach/commit_attach)" },
4887
4914
  target_id: { type: "string", description: "Run target id (for propose_attach/commit_attach)" },
@@ -4938,13 +4965,11 @@ function registerSOPOps(
4938
4965
  case "get": return handleSOPGet(supabase, userId, params);
4939
4966
  case "update": return handleSOPUpdate(supabase, userId, params);
4940
4967
  case "delete": return handleSOPDelete(supabase, userId, params);
4941
- case "list_subagents": return handleSOPListSubagents(supabase, userId, params);
4942
- case "create_subagent": return handleSOPCreateSubagent(supabase, userId, params);
4943
- case "delete_subagent": return handleSOPDeleteSubagent(supabase, userId, params);
4968
+ case "list_subagents": return handleListSubagents(supabase, userId, params);
4944
4969
  case "apply_template": return handleSOPApplyTemplate(supabase, userId, resolveAgent, params);
4945
4970
  case "propose_attach": return handleProposeAttach({ supabase, userId, docKind: "sop", params });
4946
4971
  case "commit_attach": return handleCommitAttach({ supabase, userId, docKind: "sop", params });
4947
- default: return err(`Unknown action "${action}". Valid: list_templates, create, list, get, update, delete, list_subagents, create_subagent, delete_subagent, apply_template, propose_attach, commit_attach`);
4972
+ default: return err(`Unknown action "${action}". Valid: list_templates, create, list, get, update, delete, list_subagents, apply_template, propose_attach, commit_attach`);
4948
4973
  }
4949
4974
  },
4950
4975
  });
@@ -5185,7 +5210,12 @@ async function handleSOPDelete(supabase: SupabaseClient, userId: string, params:
5185
5210
  } catch (e) { return err(e instanceof Error ? e.message : String(e)); }
5186
5211
  }
5187
5212
 
5188
- async function handleSOPListSubagents(supabase: SupabaseClient, userId: string, params: Record<string, unknown>): Promise<ToolResult> {
5213
+ // ── Shared subagent handlers (used by OFIERE_AGENT_OPS; SOP_OPS keeps a
5214
+ // convenience wrapper for `list_subagents` only). ──────────────────────────
5215
+
5216
+ const MAX_SUBAGENTS_PER_CHIEF = 5;
5217
+
5218
+ async function handleListSubagents(supabase: SupabaseClient, userId: string, params: Record<string, unknown>): Promise<ToolResult> {
5189
5219
  try {
5190
5220
  if (!params.chief_agent_id) return err("Missing required field: chief_agent_id");
5191
5221
  const { data, error } = await supabase
@@ -5195,13 +5225,11 @@ async function handleSOPListSubagents(supabase: SupabaseClient, userId: string,
5195
5225
  .eq("chief_agent_id", params.chief_agent_id as string)
5196
5226
  .order("created_at", { ascending: true });
5197
5227
  if (error) return err(error.message);
5198
- return ok({ subagents: data || [], count: (data || []).length, max_allowed: 5 });
5228
+ return ok({ subagents: data || [], count: (data || []).length, max_allowed: MAX_SUBAGENTS_PER_CHIEF });
5199
5229
  } catch (e) { return err(e instanceof Error ? e.message : String(e)); }
5200
5230
  }
5201
5231
 
5202
- const MAX_SUBAGENTS_PER_CHIEF = 5;
5203
-
5204
- async function handleSOPCreateSubagent(supabase: SupabaseClient, userId: string, params: Record<string, unknown>): Promise<ToolResult> {
5232
+ async function handleCreateSubagent(supabase: SupabaseClient, userId: string, params: Record<string, unknown>): Promise<ToolResult> {
5205
5233
  try {
5206
5234
  const chiefAgentId = params.chief_agent_id as string | undefined;
5207
5235
  const name = params.name as string | undefined;
@@ -5233,7 +5261,7 @@ async function handleSOPCreateSubagent(supabase: SupabaseClient, userId: string,
5233
5261
  } catch (e) { return err(e instanceof Error ? e.message : String(e)); }
5234
5262
  }
5235
5263
 
5236
- async function handleSOPDeleteSubagent(supabase: SupabaseClient, userId: string, params: Record<string, unknown>): Promise<ToolResult> {
5264
+ async function handleDeleteSubagent(supabase: SupabaseClient, userId: string, params: Record<string, unknown>): Promise<ToolResult> {
5237
5265
  try {
5238
5266
  const subagentId = params.subagent_id as string | undefined;
5239
5267
  if (!subagentId) return err("Missing required field: subagent_id");