ofiere-openclaw-plugin 4.35.1 → 4.37.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 +2 -2
  2. package/src/tools.ts +281 -59
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "ofiere-openclaw-plugin",
3
- "version": "4.35.1",
3
+ "version": "4.37.0",
4
4
  "type": "module",
5
- "description": "OpenClaw plugin for Ofiere PM - 14 meta-tools covering tasks, agents, projects, scheduling, knowledge, workflows, notifications, memory, prompts, constellation, space file management, execution plan builder, SOP management, and agent brain (memory + self-improvement)",
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"],
7
7
  "homepage": "https://github.com/gilanggemar/Ofiere",
8
8
  "repository": {
package/src/tools.ts CHANGED
@@ -4847,16 +4847,18 @@ function registerSOPOps(
4847
4847
  `sop_data structure (JSON object):\n` +
4848
4848
  `{\n` +
4849
4849
  ` title: string,\n` +
4850
- ` objective: string (purpose and expected outcome),\n` +
4850
+ ` purpose: string (why this procedure exists),\n` +
4851
4851
  ` scope: string (department or domain),\n` +
4852
- ` prerequisites: [{ text: string, checked: boolean }],\n` +
4853
- ` steps: [{ id: string, name: string, action: string, owner: string, output: string }],\n` +
4854
- ` deliverables: [string],\n` +
4855
- ` escalationRules: [{ trigger: string, escalateTo: string, priority: "P1"|"P2"|"P3" }],\n` +
4856
- ` successCriteria: [{ text: string, checked: boolean }],\n` +
4852
+ ` applicability: string (who/what this applies to),\n` +
4853
+ ` prerequisites: { conditions: [{text,checked}], required_tools: [string], required_permissions: [string], safety_warnings: [string] },\n` +
4854
+ ` procedure_steps: [{ id, name, action, owner, output, decision_logic, failure_state, fallback_action, estimated_duration }],\n` +
4855
+ ` expected_outputs: [string],\n` +
4856
+ ` escalation_rules: [{ trigger, escalateTo, priority: "P1"|"P2"|"P3" }],\n` +
4857
+ ` acceptance_criteria: [{ text, checked }],\n` +
4858
+ ` rollback_procedure: string,\n` +
4857
4859
  ` notes: string\n` +
4858
4860
  `}\n\n` +
4859
- `Status values: "draft", "active", "archived"`,
4861
+ `Status values: "draft", "active", "archived", "under_review"`,
4860
4862
  parameters: {
4861
4863
  type: "object",
4862
4864
  required: ["action"],
@@ -4868,19 +4870,21 @@ function registerSOPOps(
4868
4870
  template_id: { type: "string", description: "Template ID (required for apply_template)" },
4869
4871
  title: { type: "string", description: "SOP title" },
4870
4872
  department: { type: "string", description: "Department name (e.g. Marketing & Revenue)" },
4871
- status: { type: "string", enum: ["draft", "active", "archived"], description: "SOP status" },
4873
+ status: { type: "string", enum: ["draft", "active", "archived", "under_review"], description: "SOP status" },
4872
4874
  sop_data: {
4873
4875
  type: "object",
4874
- description: "Structured SOP content — pass a complete SOPData JSON object",
4876
+ description: "Structured SOP content — pass a complete SOPData JSON object (accepts both old and new field names)",
4875
4877
  properties: {
4876
4878
  title: { type: "string" },
4877
- objective: { type: "string" },
4879
+ purpose: { type: "string", description: "Why this procedure exists (also accepts 'objective')" },
4878
4880
  scope: { type: "string" },
4879
- prerequisites: { type: "array", items: { type: "object", properties: { text: { type: "string" }, checked: { type: "boolean" } }, required: ["text"] } },
4880
- steps: { type: "array", items: { type: "object", properties: { id: { type: "string" }, name: { type: "string" }, action: { type: "string" }, owner: { type: "string" }, output: { type: "string" } }, required: ["name", "action"] } },
4881
- deliverables: { type: "array", items: { type: "string" } },
4882
- escalationRules: { type: "array", items: { type: "object", properties: { trigger: { type: "string" }, escalateTo: { type: "string" }, priority: { type: "string", enum: ["P1", "P2", "P3"] } }, required: ["trigger", "escalateTo"] } },
4883
- successCriteria: { type: "array", items: { type: "object", properties: { text: { type: "string" }, checked: { type: "boolean" } }, required: ["text"] } },
4881
+ applicability: { type: "string" },
4882
+ prerequisites: { type: "object", description: "{ conditions: [{text,checked}], required_tools: [string], required_permissions: [string], safety_warnings: [string] }" },
4883
+ procedure_steps: { type: "array", description: "Also accepts 'steps'", items: { type: "object", properties: { id: { type: "string" }, name: { type: "string" }, action: { type: "string" }, owner: { type: "string" }, output: { type: "string" }, decision_logic: { type: "string" }, failure_state: { type: "string" }, fallback_action: { type: "string" }, estimated_duration: { type: "string" } }, required: ["name", "action"] } },
4884
+ expected_outputs: { type: "array", description: "Also accepts 'deliverables'", items: { type: "string" } },
4885
+ escalation_rules: { type: "array", description: "Also accepts 'escalationRules'", items: { type: "object", properties: { trigger: { type: "string" }, escalateTo: { type: "string" }, priority: { type: "string", enum: ["P1", "P2", "P3"] } }, required: ["trigger", "escalateTo"] } },
4886
+ acceptance_criteria: { type: "array", description: "Also accepts 'successCriteria'", items: { type: "object", properties: { text: { type: "string" }, checked: { type: "boolean" } }, required: ["text"] } },
4887
+ rollback_procedure: { type: "string" },
4884
4888
  notes: { type: "string" },
4885
4889
  },
4886
4890
  },
@@ -4928,56 +4932,74 @@ async function handleSOPCreate(
4928
4932
  const title = params.title as string;
4929
4933
  if (!title) return err("Missing required field: title");
4930
4934
 
4931
- // Build the SOPData JSON
4935
+ // Build the SOPData JSON (new schema, backward-compat with old field names)
4932
4936
  const sopDataRaw = params.sop_data as Record<string, unknown> | undefined;
4933
4937
  let sopContent: string;
4934
4938
 
4935
4939
  if (sopDataRaw) {
4936
- // Ensure each step has an id
4937
- const steps = Array.isArray(sopDataRaw.steps)
4938
- ? (sopDataRaw.steps as any[]).map((s: any, i: number) => ({
4940
+ // Accept both old (steps) and new (procedure_steps) field names
4941
+ const rawSteps = sopDataRaw.procedure_steps || sopDataRaw.steps;
4942
+ const procedure_steps = Array.isArray(rawSteps)
4943
+ ? (rawSteps as any[]).map((s: any, i: number) => ({
4939
4944
  id: s.id || `step-${Date.now()}-${i}`,
4940
- name: s.name || "",
4941
- action: s.action || "",
4942
- owner: s.owner || "",
4943
- output: s.output || "",
4945
+ name: s.name || "", action: s.action || "", owner: s.owner || "", output: s.output || "",
4946
+ decision_logic: s.decision_logic || "", failure_state: s.failure_state || "",
4947
+ fallback_action: s.fallback_action || "", estimated_duration: s.estimated_duration || "",
4944
4948
  }))
4945
4949
  : [];
4946
- const prerequisites = Array.isArray(sopDataRaw.prerequisites)
4947
- ? (sopDataRaw.prerequisites as any[]).map((p: any) => ({ text: p.text || "", checked: !!p.checked }))
4948
- : [];
4949
- const successCriteria = Array.isArray(sopDataRaw.successCriteria)
4950
- ? (sopDataRaw.successCriteria as any[]).map((c: any) => ({ text: c.text || "", checked: !!c.checked }))
4950
+ // Prerequisites: accept old array or new object format
4951
+ const rawPrereqs = sopDataRaw.prerequisites;
4952
+ let prerequisites: any;
4953
+ if (rawPrereqs && typeof rawPrereqs === 'object' && !Array.isArray(rawPrereqs)) {
4954
+ const rp = rawPrereqs as any;
4955
+ prerequisites = {
4956
+ conditions: Array.isArray(rp.conditions) ? rp.conditions.map((p: any) => ({ text: p.text || "", checked: !!p.checked })) : [],
4957
+ required_tools: Array.isArray(rp.required_tools) ? rp.required_tools : [],
4958
+ required_permissions: Array.isArray(rp.required_permissions) ? rp.required_permissions : [],
4959
+ safety_warnings: Array.isArray(rp.safety_warnings) ? rp.safety_warnings : [],
4960
+ };
4961
+ } else {
4962
+ prerequisites = {
4963
+ conditions: Array.isArray(rawPrereqs) ? (rawPrereqs as any[]).map((p: any) => ({ text: p.text || "", checked: !!p.checked })) : [],
4964
+ required_tools: [], required_permissions: [], safety_warnings: [],
4965
+ };
4966
+ }
4967
+ const rawCriteria = sopDataRaw.acceptance_criteria || sopDataRaw.successCriteria;
4968
+ const acceptance_criteria = Array.isArray(rawCriteria)
4969
+ ? (rawCriteria as any[]).map((c: any) => ({ text: c.text || "", checked: !!c.checked }))
4951
4970
  : [];
4952
- const escalationRules = Array.isArray(sopDataRaw.escalationRules)
4953
- ? (sopDataRaw.escalationRules as any[]).map((r: any) => ({
4954
- trigger: r.trigger || "",
4955
- escalateTo: r.escalateTo || r.escalate_to || "",
4971
+ const rawEsc = sopDataRaw.escalation_rules || sopDataRaw.escalationRules;
4972
+ const escalation_rules = Array.isArray(rawEsc)
4973
+ ? (rawEsc as any[]).map((r: any) => ({
4974
+ trigger: r.trigger || "", escalateTo: r.escalateTo || r.escalate_to || "",
4956
4975
  priority: (r.priority || "P2") as "P1" | "P2" | "P3",
4957
4976
  }))
4958
4977
  : [];
4959
- const deliverables = Array.isArray(sopDataRaw.deliverables)
4960
- ? (sopDataRaw.deliverables as string[])
4961
- : [];
4978
+ const rawOutputs = sopDataRaw.expected_outputs || sopDataRaw.deliverables;
4979
+ const expected_outputs = Array.isArray(rawOutputs) ? (rawOutputs as string[]) : [];
4962
4980
 
4963
4981
  const sopData = {
4964
4982
  title: (sopDataRaw.title as string) || title,
4965
- objective: (sopDataRaw.objective as string) || "",
4983
+ sop_id: (sopDataRaw.sop_id as string) || "", version: (sopDataRaw.version as string) || "1.0.0",
4984
+ effective_date: "", review_date: "", author: "", approver: "", category: "", tags: [],
4985
+ purpose: (sopDataRaw.purpose as string) || (sopDataRaw.objective as string) || "",
4966
4986
  scope: (sopDataRaw.scope as string) || "",
4967
- prerequisites,
4968
- steps,
4969
- deliverables,
4970
- escalationRules,
4971
- successCriteria,
4987
+ applicability: (sopDataRaw.applicability as string) || "",
4988
+ prerequisites, procedure_steps, expected_outputs,
4989
+ acceptance_criteria, escalation_rules,
4990
+ rollback_procedure: (sopDataRaw.rollback_procedure as string) || "",
4991
+ related_sops: [], references: [], change_log: [],
4972
4992
  notes: (sopDataRaw.notes as string) || "",
4973
4993
  };
4974
4994
  sopContent = JSON.stringify(sopData);
4975
4995
  } else {
4976
- // Create empty SOP data
4977
4996
  sopContent = JSON.stringify({
4978
- title, objective: "", scope: "", prerequisites: [],
4979
- steps: [{ id: `step-${Date.now()}-0`, name: "", action: "", owner: "", output: "" }],
4980
- deliverables: [], escalationRules: [], successCriteria: [], notes: "",
4997
+ title, sop_id: "", version: "1.0.0", effective_date: "", review_date: "", author: "", approver: "", category: "", tags: [],
4998
+ purpose: "", scope: "", applicability: "",
4999
+ prerequisites: { conditions: [], required_tools: [], required_permissions: [], safety_warnings: [] },
5000
+ procedure_steps: [{ id: `step-${Date.now()}-0`, name: "", action: "", owner: "", output: "", decision_logic: "", failure_state: "", fallback_action: "", estimated_duration: "" }],
5001
+ expected_outputs: [], acceptance_criteria: [], escalation_rules: [],
5002
+ rollback_procedure: "", related_sops: [], references: [], change_log: [], notes: "",
4981
5003
  });
4982
5004
  }
4983
5005
 
@@ -5044,20 +5066,38 @@ async function handleSOPUpdate(supabase: SupabaseClient, userId: string, params:
5044
5066
 
5045
5067
  if (params.sop_data) {
5046
5068
  const sopDataRaw = params.sop_data as Record<string, unknown>;
5047
- const steps = Array.isArray(sopDataRaw.steps)
5048
- ? (sopDataRaw.steps as any[]).map((s: any, i: number) => ({
5069
+ const rawSteps = sopDataRaw.procedure_steps || sopDataRaw.steps;
5070
+ const procedure_steps = Array.isArray(rawSteps)
5071
+ ? (rawSteps as any[]).map((s: any, i: number) => ({
5049
5072
  id: s.id || `step-${Date.now()}-${i}`,
5050
5073
  name: s.name || "", action: s.action || "", owner: s.owner || "", output: s.output || "",
5074
+ decision_logic: s.decision_logic || "", failure_state: s.failure_state || "",
5075
+ fallback_action: s.fallback_action || "", estimated_duration: s.estimated_duration || "",
5051
5076
  }))
5052
5077
  : [];
5053
- const prerequisites = Array.isArray(sopDataRaw.prerequisites)
5054
- ? (sopDataRaw.prerequisites as any[]).map((p: any) => ({ text: p.text || "", checked: !!p.checked }))
5055
- : [];
5056
- const successCriteria = Array.isArray(sopDataRaw.successCriteria)
5057
- ? (sopDataRaw.successCriteria as any[]).map((c: any) => ({ text: c.text || "", checked: !!c.checked }))
5078
+ const rawPrereqs = sopDataRaw.prerequisites;
5079
+ let prerequisites: any;
5080
+ if (rawPrereqs && typeof rawPrereqs === 'object' && !Array.isArray(rawPrereqs)) {
5081
+ const rp = rawPrereqs as any;
5082
+ prerequisites = {
5083
+ conditions: Array.isArray(rp.conditions) ? rp.conditions.map((p: any) => ({ text: p.text || "", checked: !!p.checked })) : [],
5084
+ required_tools: Array.isArray(rp.required_tools) ? rp.required_tools : [],
5085
+ required_permissions: Array.isArray(rp.required_permissions) ? rp.required_permissions : [],
5086
+ safety_warnings: Array.isArray(rp.safety_warnings) ? rp.safety_warnings : [],
5087
+ };
5088
+ } else {
5089
+ prerequisites = {
5090
+ conditions: Array.isArray(rawPrereqs) ? (rawPrereqs as any[]).map((p: any) => ({ text: p.text || "", checked: !!p.checked })) : [],
5091
+ required_tools: [], required_permissions: [], safety_warnings: [],
5092
+ };
5093
+ }
5094
+ const rawCriteria = sopDataRaw.acceptance_criteria || sopDataRaw.successCriteria;
5095
+ const acceptance_criteria = Array.isArray(rawCriteria)
5096
+ ? (rawCriteria as any[]).map((c: any) => ({ text: c.text || "", checked: !!c.checked }))
5058
5097
  : [];
5059
- const escalationRules = Array.isArray(sopDataRaw.escalationRules)
5060
- ? (sopDataRaw.escalationRules as any[]).map((r: any) => ({
5098
+ const rawEsc = sopDataRaw.escalation_rules || sopDataRaw.escalationRules;
5099
+ const escalation_rules = Array.isArray(rawEsc)
5100
+ ? (rawEsc as any[]).map((r: any) => ({
5061
5101
  trigger: r.trigger || "", escalateTo: r.escalateTo || r.escalate_to || "",
5062
5102
  priority: (r.priority || "P2") as "P1" | "P2" | "P3",
5063
5103
  }))
@@ -5065,11 +5105,16 @@ async function handleSOPUpdate(supabase: SupabaseClient, userId: string, params:
5065
5105
 
5066
5106
  updates.content = JSON.stringify({
5067
5107
  title: (sopDataRaw.title as string) || (params.title as string) || "",
5068
- objective: (sopDataRaw.objective as string) || "",
5108
+ sop_id: (sopDataRaw.sop_id as string) || "", version: (sopDataRaw.version as string) || "1.0.0",
5109
+ effective_date: "", review_date: "", author: "", approver: "", category: "", tags: [],
5110
+ purpose: (sopDataRaw.purpose as string) || (sopDataRaw.objective as string) || "",
5069
5111
  scope: (sopDataRaw.scope as string) || "",
5070
- prerequisites, steps,
5071
- deliverables: Array.isArray(sopDataRaw.deliverables) ? sopDataRaw.deliverables : [],
5072
- escalationRules, successCriteria,
5112
+ applicability: (sopDataRaw.applicability as string) || "",
5113
+ prerequisites, procedure_steps,
5114
+ expected_outputs: Array.isArray(sopDataRaw.expected_outputs || sopDataRaw.deliverables) ? (sopDataRaw.expected_outputs || sopDataRaw.deliverables) : [],
5115
+ acceptance_criteria, escalation_rules,
5116
+ rollback_procedure: (sopDataRaw.rollback_procedure as string) || "",
5117
+ related_sops: [], references: [], change_log: [],
5073
5118
  notes: (sopDataRaw.notes as string) || "",
5074
5119
  });
5075
5120
  }
@@ -5152,6 +5197,182 @@ async function handleSOPApplyTemplate(
5152
5197
  }
5153
5198
 
5154
5199
 
5200
+ // ═══════════════════════════════════════════════════════════════════════════════
5201
+ // META-TOOL 16: OFIERE_FRAMEWORK_OPS — Corporate Frameworks
5202
+ // ═══════════════════════════════════════════════════════════════════════════════
5203
+
5204
+ function registerFrameworkOps(
5205
+ api: any,
5206
+ supabase: SupabaseClient,
5207
+ userId: string,
5208
+ resolveAgent: (id?: string) => Promise<string | null>,
5209
+ ): void {
5210
+ api.registerTool({
5211
+ name: "OFIERE_FRAMEWORK_OPS",
5212
+ label: "Ofiere Framework Operations",
5213
+ description:
5214
+ `Create and manage Corporate Frameworks — strategic mandates, KPIs, risk governance, and operational boundaries.\n\n` +
5215
+ `Actions:\n` +
5216
+ `- "create": Create a new framework. Required: agent_id, title. Provide framework body via 'content' (JSON string). Optional: department, category, status\n` +
5217
+ `- "list": List frameworks. Optional: agent_id to filter\n` +
5218
+ `- "get": Get full framework details. Required: framework_id\n` +
5219
+ `- "update": Modify framework. Required: framework_id. Optional: title, content, status, department, category\n` +
5220
+ `- "delete": Remove framework. Required: framework_id\n\n` +
5221
+ `content: A JSON string containing the full framework body. Example:\n` +
5222
+ `"{\\"mission\\":\\"Drive brand growth\\",\\"vision\\":\\"Market leader by 2027\\",\\"core_principles\\":[\\"Data-driven\\",\\"Customer-first\\"],` +
5223
+ `\\"decision_authority\\":\\"CMO approves >$10k spend\\",\\"budget_constraints\\":\\"$50k monthly cap\\",` +
5224
+ `\\"strategic_objectives\\":[{\\"objective\\":\\"Grow MRR\\",\\"target\\":\\"$100k\\",\\"measurement\\":\\"Revenue dashboard\\"}],` +
5225
+ `\\"risk_appetite\\":\\"moderate\\",\\"escalation_matrix\\":[{\\"trigger\\":\\"Budget >80%\\",\\"escalate_to\\":\\"CEO\\",\\"priority\\":\\"P1\\"}],` +
5226
+ `\\"notes\\":\\"Quarterly review\\"}"\n\n` +
5227
+ `Supported content fields: mission, vision, core_principles, decision_authority, budget_constraints, ` +
5228
+ `resource_limits, tool_stack, strategic_objectives, risk_appetite, compliance_requirements, ` +
5229
+ `escalation_matrix, team_composition, integration_points, review_frequency, notes — or any custom fields.\n\n` +
5230
+ `Status values: "draft", "active", "under_review", "archived"`,
5231
+ parameters: {
5232
+ type: "object",
5233
+ required: ["action"],
5234
+ properties: {
5235
+ action: { type: "string", enum: ["create", "list", "get", "update", "delete"], description: "Framework action" },
5236
+ framework_id: { type: "string", description: "Framework ID (required for get, update, delete)" },
5237
+ agent_id: { type: "string", description: "Agent ID (required for create, optional filter for list)" },
5238
+ title: { type: "string", description: "Framework title" },
5239
+ content: { type: "string", description: "Framework body as a JSON string containing mission, vision, core_principles, decision_authority, budget_constraints, strategic_objectives, risk_appetite, escalation_matrix, and any other fields. Pass as stringified JSON." },
5240
+ department: { type: "string" },
5241
+ category: { type: "string" },
5242
+ status: { type: "string", enum: ["draft", "active", "under_review", "archived"] },
5243
+ },
5244
+ },
5245
+ async execute(_id: string, params: Record<string, unknown>) {
5246
+ const action = params.action as string;
5247
+ switch (action) {
5248
+ case "create": return handleFWCreate(supabase, userId, resolveAgent, params);
5249
+ case "list": return handleFWList(supabase, userId, params);
5250
+ case "get": return handleFWGet(supabase, userId, params);
5251
+ case "update": return handleFWUpdate(supabase, userId, params);
5252
+ case "delete": return handleFWDelete(supabase, userId, params);
5253
+ default: return err(`Unknown action "${action}". Valid: create, list, get, update, delete`);
5254
+ }
5255
+ },
5256
+ });
5257
+ }
5258
+
5259
+ // ── Framework action handlers ────────────────────────────────────────────────
5260
+
5261
+ async function handleFWCreate(
5262
+ supabase: SupabaseClient, userId: string,
5263
+ resolveAgent: (id?: string) => Promise<string | null>,
5264
+ params: Record<string, unknown>,
5265
+ ): Promise<ToolResult> {
5266
+ try {
5267
+ const agentIdRaw = params.agent_id as string;
5268
+ if (!agentIdRaw) return err("Missing required field: agent_id");
5269
+ const title = params.title as string;
5270
+ if (!title) return err("Missing required field: title");
5271
+
5272
+ // Accept content via: (1) 'content' string param (preferred, matches SOP pattern)
5273
+ // (2) 'framework_data' object param (legacy, kept for compat)
5274
+ let content: string;
5275
+ if (typeof params.content === "string" && params.content.trim()) {
5276
+ // Already a JSON string — merge with title
5277
+ try {
5278
+ const parsed = JSON.parse(params.content);
5279
+ content = JSON.stringify({ title, ...parsed });
5280
+ } catch {
5281
+ // Not valid JSON — wrap as notes
5282
+ content = JSON.stringify({ title, notes: params.content });
5283
+ }
5284
+ } else if (params.framework_data && typeof params.framework_data === "object") {
5285
+ content = JSON.stringify({ title, ...(params.framework_data as Record<string, unknown>) });
5286
+ } else {
5287
+ content = JSON.stringify({ title });
5288
+ }
5289
+
5290
+ const row = {
5291
+ user_id: userId, agent_id: agentIdRaw, title, content,
5292
+ status: (params.status as string) || "draft",
5293
+ department: (params.department as string) || null,
5294
+ category: (params.category as string) || null,
5295
+ };
5296
+
5297
+ const { data, error } = await supabase.from("frameworks").insert(row).select().single();
5298
+ if (error) return err(error.message);
5299
+ return ok({
5300
+ message: `Framework "${title}" created for agent ${agentIdRaw}`,
5301
+ framework: { id: data.id, title: data.title, status: data.status, agent_id: data.agent_id },
5302
+ });
5303
+ } catch (e) { return err(e instanceof Error ? e.message : String(e)); }
5304
+ }
5305
+
5306
+ async function handleFWList(supabase: SupabaseClient, userId: string, params: Record<string, unknown>): Promise<ToolResult> {
5307
+ try {
5308
+ let query = supabase.from("frameworks")
5309
+ .select("id, agent_id, title, status, department, category, created_at, updated_at")
5310
+ .eq("user_id", userId).order("updated_at", { ascending: false });
5311
+ if (params.agent_id) query = query.eq("agent_id", params.agent_id as string);
5312
+ const { data, error } = await query;
5313
+ if (error) return err(error.message);
5314
+ return ok({ frameworks: data || [], count: (data || []).length });
5315
+ } catch (e) { return err(e instanceof Error ? e.message : String(e)); }
5316
+ }
5317
+
5318
+ async function handleFWGet(supabase: SupabaseClient, userId: string, params: Record<string, unknown>): Promise<ToolResult> {
5319
+ try {
5320
+ if (!params.framework_id) return err("Missing required field: framework_id");
5321
+ const { data, error } = await supabase.from("frameworks").select("*")
5322
+ .eq("id", params.framework_id as string).eq("user_id", userId);
5323
+ if (error) return err(error.message);
5324
+ if (!data || data.length === 0) return err("Framework not found");
5325
+ const fw = data[0];
5326
+ let parsed: any = {};
5327
+ try { parsed = JSON.parse(fw.content || "{}"); } catch { /* leave empty */ }
5328
+ return ok({ framework: { ...fw, content: parsed } });
5329
+ } catch (e) { return err(e instanceof Error ? e.message : String(e)); }
5330
+ }
5331
+
5332
+ async function handleFWUpdate(supabase: SupabaseClient, userId: string, params: Record<string, unknown>): Promise<ToolResult> {
5333
+ try {
5334
+ if (!params.framework_id) return err("Missing required field: framework_id");
5335
+ const updates: Record<string, any> = { updated_at: new Date().toISOString() };
5336
+ if (params.title !== undefined) updates.title = params.title;
5337
+ if (params.status !== undefined) updates.status = params.status;
5338
+ if (params.department !== undefined) updates.department = params.department;
5339
+ if (params.category !== undefined) updates.category = params.category;
5340
+
5341
+ // Accept content via: (1) 'content' string param (preferred)
5342
+ // (2) 'framework_data' object param (legacy compat)
5343
+ if (typeof params.content === "string" && params.content.trim()) {
5344
+ try {
5345
+ const parsed = JSON.parse(params.content);
5346
+ updates.content = JSON.stringify({ title: parsed.title || (params.title as string) || "", ...parsed });
5347
+ } catch {
5348
+ updates.content = JSON.stringify({ title: (params.title as string) || "", notes: params.content });
5349
+ }
5350
+ } else if (params.framework_data && typeof params.framework_data === "object") {
5351
+ const raw = params.framework_data as Record<string, unknown>;
5352
+ updates.content = JSON.stringify({ title: (raw.title as string) || (params.title as string) || "", ...raw });
5353
+ }
5354
+
5355
+ const { data, error } = await supabase.from("frameworks").update(updates)
5356
+ .eq("id", params.framework_id as string).eq("user_id", userId)
5357
+ .select("id, title, status").maybeSingle();
5358
+ if (error) return err(error.message);
5359
+ if (!data) return err("Framework not found");
5360
+ return ok({ message: `Framework "${data.title}" updated`, framework: data });
5361
+ } catch (e) { return err(e instanceof Error ? e.message : String(e)); }
5362
+ }
5363
+
5364
+ async function handleFWDelete(supabase: SupabaseClient, userId: string, params: Record<string, unknown>): Promise<ToolResult> {
5365
+ try {
5366
+ if (!params.framework_id) return err("Missing required field: framework_id");
5367
+ const { data, error } = await supabase.from("frameworks").delete()
5368
+ .eq("id", params.framework_id as string).eq("user_id", userId)
5369
+ .select("id, title").maybeSingle();
5370
+ if (error) return err(error.message);
5371
+ if (!data) return err("Framework not found — nothing deleted");
5372
+ return ok({ message: `Framework "${data.title}" deleted`, deleted: true });
5373
+ } catch (e) { return err(e instanceof Error ? e.message : String(e)); }
5374
+ }
5375
+
5155
5376
  // ═══════════════════════════════════════════════════════════════════════════════
5156
5377
  // META-TOOL 14: OFIERE_BRAIN_OPS — Agent Memory + Self-Improvement
5157
5378
  // ═══════════════════════════════════════════════════════════════════════════════
@@ -5849,6 +6070,7 @@ export function registerTools(
5849
6070
  registerSOPOps(api, supabase, userId, resolveAgent); // 13
5850
6071
  registerBrainOps(api, supabase, userId, resolveAgent); // 14
5851
6072
  registerTalentOps(api, supabase, userId); // 15
6073
+ registerFrameworkOps(api, supabase, userId, resolveAgent); // 16
5852
6074
 
5853
6075
  // ── Register dynamic brain context hook ──
5854
6076
  registerBrainContextHook(api, supabase, userId, fallbackAgentId);
@@ -5860,7 +6082,7 @@ export function registerTools(
5860
6082
  registerBrainExtractionHook(api, supabase, userId, fallbackAgentId);
5861
6083
 
5862
6084
  // ── Count and log ──
5863
- const toolCount = 15;
6085
+ const toolCount = 16;
5864
6086
  const callerName = getCallingAgentName(api);
5865
6087
  const agentLabel = fallbackAgentId || callerName || "auto-detect";
5866
6088
  api.logger.info(`[ofiere] ${toolCount} meta-tools registered (agent: ${agentLabel})`);