ofiere-openclaw-plugin 4.18.4 → 4.19.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 +2 -2
- package/src/prompt.ts +19 -0
- package/src/tools.ts +348 -10
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ofiere-openclaw-plugin",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.19.0",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"description": "OpenClaw plugin for Ofiere PM -
|
|
5
|
+
"description": "OpenClaw plugin for Ofiere PM - 13 meta-tools covering tasks, agents, projects, scheduling, knowledge, workflows, notifications, memory, prompts, constellation, space file management, execution plan builder, and SOP management",
|
|
6
6
|
"keywords": ["openclaw", "ofiere", "project-management", "agents", "plugin"],
|
|
7
7
|
"homepage": "https://github.com/gilanggemar/Ofiere",
|
|
8
8
|
"repository": {
|
package/src/prompt.ts
CHANGED
|
@@ -130,6 +130,19 @@ const TOOL_DOCS: Record<string, string> = {
|
|
|
130
130
|
- Plans are visual DAG drafts — they don't become real tasks until you call "execute"
|
|
131
131
|
- Execution maps ALL node fields into real tasks: execution_steps → custom_fields.execution_plan, goals → custom_fields.goals, constraints → custom_fields.constraints, system_prompt → custom_fields.system_prompt
|
|
132
132
|
- The user can see and edit your plans in the Planning Tab of the dashboard in real-time`,
|
|
133
|
+
|
|
134
|
+
OFIERE_SOP_OPS: `- **OFIERE_SOP_OPS** — Standard Operating Procedures for department chiefs (action: "list_templates", "create", "list", "get", "update", "delete", "list_subagents", "apply_template")
|
|
135
|
+
- list_templates: See available SOP templates (built-in + user-created)
|
|
136
|
+
- create: Create a new SOP. Required: agent_id, title, sop_data. Optional: department, status
|
|
137
|
+
- list: List SOPs. Optional: agent_id to filter by department chief
|
|
138
|
+
- get: Full SOP details with structured content. Required: sop_id
|
|
139
|
+
- update: Modify SOP content/status. Required: sop_id. Optional: title, sop_data, status, department
|
|
140
|
+
- delete: Remove an SOP. Required: sop_id
|
|
141
|
+
- list_subagents: View staff under a chief. Required: chief_agent_id
|
|
142
|
+
- apply_template: Create SOP from a saved template. Required: agent_id, template_id
|
|
143
|
+
- sop_data is a JSON object: { title, objective, scope, prerequisites[{text,checked}], steps[{name,action,owner,output}], deliverables[], escalationRules[{trigger,escalateTo,priority}], successCriteria[{text,checked}], notes }
|
|
144
|
+
- Status values: draft, active, archived
|
|
145
|
+
- SOPs appear in the SOP Manager page immediately via real-time sync`,
|
|
133
146
|
};
|
|
134
147
|
|
|
135
148
|
export function getSystemPrompt(state: {
|
|
@@ -195,6 +208,12 @@ ${toolDocs}
|
|
|
195
208
|
- When creating a plan with nodes, nest children inside each node's children[] array. Sequential children execute in order; set parallel: true on a parent node to fork its children into parallel branches.
|
|
196
209
|
- Always call OFIERE_PLAN_OPS action:"get" before action:"add_nodes" or action:"update" to see the current tree structure and node IDs.
|
|
197
210
|
- Execution ("execute") maps plan nodes 1:1 into real PM tasks with ALL enrichment fields preserved: execution_steps, goals, constraints, system_prompt. No data is lost in the handoff.
|
|
211
|
+
- SOP CREATION: When asked to create an SOP, use OFIERE_SOP_OPS action:"create" with a COMPLETE sop_data object. Fill ALL fields with actionable, department-specific content — do NOT leave fields empty.
|
|
212
|
+
- Each SOP step MUST have: a clear name, a specific action description, an assigned owner, and a concrete expected output.
|
|
213
|
+
- Include escalation rules with appropriate priority levels (P1=critical blockers, P2=scope changes, P3=advisory).
|
|
214
|
+
- When creating SOPs for department chiefs (Thalia=CMO, Ivy=COO, Daisy=CTO-Intel, Celia=CTO-Eng), tailor content to their domain expertise.
|
|
215
|
+
- Prerequisites should be actionable checklist items. Success criteria should be measurable outcomes.
|
|
216
|
+
- After creating an SOP, suggest the agent set it to "active" status when ready for execution.
|
|
198
217
|
</ofiere-pm>`;
|
|
199
218
|
}
|
|
200
219
|
|
package/src/tools.ts
CHANGED
|
@@ -4442,21 +4442,29 @@ async function handleExecutePlan(
|
|
|
4442
4442
|
return idMap.get(n.id);
|
|
4443
4443
|
}
|
|
4444
4444
|
|
|
4445
|
+
// Helper: insert a dependency row into pm_dependencies
|
|
4446
|
+
async function insertDep(predId: string, succId: string): Promise<boolean> {
|
|
4447
|
+
const { error } = await supabase.from("pm_dependencies").insert({
|
|
4448
|
+
id: `dep-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
|
4449
|
+
user_id: userId, predecessor_id: predId, successor_id: succId,
|
|
4450
|
+
dependency_type: "finish_to_start", lag_days: 0,
|
|
4451
|
+
});
|
|
4452
|
+
return !error;
|
|
4453
|
+
}
|
|
4454
|
+
|
|
4445
4455
|
while (depsQueue.length > 0) {
|
|
4446
4456
|
const node = depsQueue.shift()!;
|
|
4447
4457
|
const nodeRealId = resolveTaskId(node);
|
|
4448
4458
|
|
|
4449
4459
|
// Chain siblings: sequential children of this node get chained left-to-right
|
|
4450
4460
|
if (Array.isArray(node.children) && node.children.length > 1 && !node.parallel) {
|
|
4451
|
-
// Collect task-type children in order for chaining
|
|
4452
4461
|
const taskChildren: string[] = [];
|
|
4453
4462
|
for (const child of node.children) {
|
|
4454
4463
|
const cid = resolveTaskId(child);
|
|
4455
4464
|
if (cid) taskChildren.push(cid);
|
|
4456
4465
|
}
|
|
4457
4466
|
for (let i = 1; i < taskChildren.length; i++) {
|
|
4458
|
-
await
|
|
4459
|
-
depsCreated++;
|
|
4467
|
+
if (await insertDep(taskChildren[i - 1], taskChildren[i])) depsCreated++;
|
|
4460
4468
|
}
|
|
4461
4469
|
}
|
|
4462
4470
|
|
|
@@ -4464,8 +4472,7 @@ async function handleExecutePlan(
|
|
|
4464
4472
|
if (nodeRealId && !node.parallel && node.children?.length > 0) {
|
|
4465
4473
|
const firstChildId = resolveTaskId(node.children[0]);
|
|
4466
4474
|
if (firstChildId) {
|
|
4467
|
-
await
|
|
4468
|
-
depsCreated++;
|
|
4475
|
+
if (await insertDep(nodeRealId, firstChildId)) depsCreated++;
|
|
4469
4476
|
}
|
|
4470
4477
|
}
|
|
4471
4478
|
|
|
@@ -4474,8 +4481,7 @@ async function handleExecutePlan(
|
|
|
4474
4481
|
for (const child of node.children) {
|
|
4475
4482
|
const childId = resolveTaskId(child);
|
|
4476
4483
|
if (childId) {
|
|
4477
|
-
await
|
|
4478
|
-
depsCreated++;
|
|
4484
|
+
if (await insertDep(nodeRealId, childId)) depsCreated++;
|
|
4479
4485
|
}
|
|
4480
4486
|
}
|
|
4481
4487
|
}
|
|
@@ -4490,8 +4496,7 @@ async function handleExecutePlan(
|
|
|
4490
4496
|
if (rid) rootTaskIds.push(rid);
|
|
4491
4497
|
}
|
|
4492
4498
|
for (let i = 1; i < rootTaskIds.length; i++) {
|
|
4493
|
-
await
|
|
4494
|
-
depsCreated++;
|
|
4499
|
+
if (await insertDep(rootTaskIds[i - 1], rootTaskIds[i])) depsCreated++;
|
|
4495
4500
|
}
|
|
4496
4501
|
|
|
4497
4502
|
// Mark plan as deployed
|
|
@@ -4509,6 +4514,338 @@ async function handleExecutePlan(
|
|
|
4509
4514
|
} catch (e) { return err(e instanceof Error ? e.message : String(e)); }
|
|
4510
4515
|
}
|
|
4511
4516
|
|
|
4517
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
4518
|
+
// META-TOOL 13: OFIERE_SOP_OPS — Standard Operating Procedures
|
|
4519
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
4520
|
+
|
|
4521
|
+
function registerSOPOps(
|
|
4522
|
+
api: any,
|
|
4523
|
+
supabase: SupabaseClient,
|
|
4524
|
+
userId: string,
|
|
4525
|
+
resolveAgent: (id?: string) => Promise<string | null>,
|
|
4526
|
+
): void {
|
|
4527
|
+
api.registerTool({
|
|
4528
|
+
name: "OFIERE_SOP_OPS",
|
|
4529
|
+
label: "Ofiere SOP Operations",
|
|
4530
|
+
description:
|
|
4531
|
+
`Create, manage, and deploy Standard Operating Procedures for department chiefs.\n\n` +
|
|
4532
|
+
`Actions:\n` +
|
|
4533
|
+
`- "list_templates": List available SOP templates\n` +
|
|
4534
|
+
`- "create": Create a new SOP for an agent. Required: agent_id, title, sop_data. Optional: department, status\n` +
|
|
4535
|
+
`- "list": List SOPs. Optional: agent_id to filter\n` +
|
|
4536
|
+
`- "get": Get full SOP details. Required: sop_id\n` +
|
|
4537
|
+
`- "update": Modify SOP. Required: sop_id. Optional: title, sop_data, status, department\n` +
|
|
4538
|
+
`- "delete": Remove SOP. Required: sop_id\n` +
|
|
4539
|
+
`- "list_subagents": List subagents for a chief. Required: chief_agent_id\n` +
|
|
4540
|
+
`- "apply_template": Create SOP from template. Required: agent_id, template_id. Optional: title, department\n\n` +
|
|
4541
|
+
`sop_data structure (JSON object):\n` +
|
|
4542
|
+
`{\n` +
|
|
4543
|
+
` title: string,\n` +
|
|
4544
|
+
` objective: string (purpose and expected outcome),\n` +
|
|
4545
|
+
` scope: string (department or domain),\n` +
|
|
4546
|
+
` prerequisites: [{ text: string, checked: boolean }],\n` +
|
|
4547
|
+
` steps: [{ id: string, name: string, action: string, owner: string, output: string }],\n` +
|
|
4548
|
+
` deliverables: [string],\n` +
|
|
4549
|
+
` escalationRules: [{ trigger: string, escalateTo: string, priority: "P1"|"P2"|"P3" }],\n` +
|
|
4550
|
+
` successCriteria: [{ text: string, checked: boolean }],\n` +
|
|
4551
|
+
` notes: string\n` +
|
|
4552
|
+
`}\n\n` +
|
|
4553
|
+
`Status values: "draft", "active", "archived"`,
|
|
4554
|
+
parameters: {
|
|
4555
|
+
type: "object",
|
|
4556
|
+
required: ["action"],
|
|
4557
|
+
properties: {
|
|
4558
|
+
action: { type: "string", enum: ["list_templates", "create", "list", "get", "update", "delete", "list_subagents", "apply_template"] },
|
|
4559
|
+
sop_id: { type: "string", description: "SOP ID (required for get, update, delete)" },
|
|
4560
|
+
agent_id: { type: "string", description: "Agent ID (required for create, list filter, apply_template)" },
|
|
4561
|
+
chief_agent_id: { type: "string", description: "Chief agent ID (required for list_subagents)" },
|
|
4562
|
+
template_id: { type: "string", description: "Template ID (required for apply_template)" },
|
|
4563
|
+
title: { type: "string", description: "SOP title" },
|
|
4564
|
+
department: { type: "string", description: "Department name (e.g. Marketing & Revenue)" },
|
|
4565
|
+
status: { type: "string", enum: ["draft", "active", "archived"], description: "SOP status" },
|
|
4566
|
+
sop_data: {
|
|
4567
|
+
type: "object",
|
|
4568
|
+
description: "Structured SOP content — pass a complete SOPData JSON object",
|
|
4569
|
+
properties: {
|
|
4570
|
+
title: { type: "string" },
|
|
4571
|
+
objective: { type: "string" },
|
|
4572
|
+
scope: { type: "string" },
|
|
4573
|
+
prerequisites: { type: "array", items: { type: "object", properties: { text: { type: "string" }, checked: { type: "boolean" } }, required: ["text"] } },
|
|
4574
|
+
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"] } },
|
|
4575
|
+
deliverables: { type: "array", items: { type: "string" } },
|
|
4576
|
+
escalationRules: { type: "array", items: { type: "object", properties: { trigger: { type: "string" }, escalateTo: { type: "string" }, priority: { type: "string", enum: ["P1", "P2", "P3"] } }, required: ["trigger", "escalateTo"] } },
|
|
4577
|
+
successCriteria: { type: "array", items: { type: "object", properties: { text: { type: "string" }, checked: { type: "boolean" } }, required: ["text"] } },
|
|
4578
|
+
notes: { type: "string" },
|
|
4579
|
+
},
|
|
4580
|
+
},
|
|
4581
|
+
},
|
|
4582
|
+
},
|
|
4583
|
+
async execute(_id: string, params: Record<string, unknown>) {
|
|
4584
|
+
const action = params.action as string;
|
|
4585
|
+
switch (action) {
|
|
4586
|
+
case "list_templates": return handleSOPListTemplates(supabase, userId);
|
|
4587
|
+
case "create": return handleSOPCreate(supabase, userId, resolveAgent, params);
|
|
4588
|
+
case "list": return handleSOPList(supabase, userId, params);
|
|
4589
|
+
case "get": return handleSOPGet(supabase, userId, params);
|
|
4590
|
+
case "update": return handleSOPUpdate(supabase, userId, params);
|
|
4591
|
+
case "delete": return handleSOPDelete(supabase, userId, params);
|
|
4592
|
+
case "list_subagents": return handleSOPListSubagents(supabase, userId, params);
|
|
4593
|
+
case "apply_template": return handleSOPApplyTemplate(supabase, userId, resolveAgent, params);
|
|
4594
|
+
default: return err(`Unknown action "${action}". Valid: list_templates, create, list, get, update, delete, list_subagents, apply_template`);
|
|
4595
|
+
}
|
|
4596
|
+
},
|
|
4597
|
+
});
|
|
4598
|
+
}
|
|
4599
|
+
|
|
4600
|
+
// ── SOP action handlers ──────────────────────────────────────────────────────
|
|
4601
|
+
|
|
4602
|
+
async function handleSOPListTemplates(supabase: SupabaseClient, userId: string): Promise<ToolResult> {
|
|
4603
|
+
try {
|
|
4604
|
+
const { data, error } = await supabase
|
|
4605
|
+
.from("sop_templates")
|
|
4606
|
+
.select("id, name, description, is_default, created_at")
|
|
4607
|
+
.eq("user_id", userId)
|
|
4608
|
+
.order("created_at", { ascending: false });
|
|
4609
|
+
if (error) return err(error.message);
|
|
4610
|
+
return ok({ templates: data || [], count: (data || []).length });
|
|
4611
|
+
} catch (e) { return err(e instanceof Error ? e.message : String(e)); }
|
|
4612
|
+
}
|
|
4613
|
+
|
|
4614
|
+
async function handleSOPCreate(
|
|
4615
|
+
supabase: SupabaseClient, userId: string,
|
|
4616
|
+
resolveAgent: (id?: string) => Promise<string | null>,
|
|
4617
|
+
params: Record<string, unknown>,
|
|
4618
|
+
): Promise<ToolResult> {
|
|
4619
|
+
try {
|
|
4620
|
+
const agentIdRaw = params.agent_id as string;
|
|
4621
|
+
if (!agentIdRaw) return err("Missing required field: agent_id");
|
|
4622
|
+
const title = params.title as string;
|
|
4623
|
+
if (!title) return err("Missing required field: title");
|
|
4624
|
+
|
|
4625
|
+
// Build the SOPData JSON
|
|
4626
|
+
const sopDataRaw = params.sop_data as Record<string, unknown> | undefined;
|
|
4627
|
+
let sopContent: string;
|
|
4628
|
+
|
|
4629
|
+
if (sopDataRaw) {
|
|
4630
|
+
// Ensure each step has an id
|
|
4631
|
+
const steps = Array.isArray(sopDataRaw.steps)
|
|
4632
|
+
? (sopDataRaw.steps as any[]).map((s: any, i: number) => ({
|
|
4633
|
+
id: s.id || `step-${Date.now()}-${i}`,
|
|
4634
|
+
name: s.name || "",
|
|
4635
|
+
action: s.action || "",
|
|
4636
|
+
owner: s.owner || "",
|
|
4637
|
+
output: s.output || "",
|
|
4638
|
+
}))
|
|
4639
|
+
: [];
|
|
4640
|
+
const prerequisites = Array.isArray(sopDataRaw.prerequisites)
|
|
4641
|
+
? (sopDataRaw.prerequisites as any[]).map((p: any) => ({ text: p.text || "", checked: !!p.checked }))
|
|
4642
|
+
: [];
|
|
4643
|
+
const successCriteria = Array.isArray(sopDataRaw.successCriteria)
|
|
4644
|
+
? (sopDataRaw.successCriteria as any[]).map((c: any) => ({ text: c.text || "", checked: !!c.checked }))
|
|
4645
|
+
: [];
|
|
4646
|
+
const escalationRules = Array.isArray(sopDataRaw.escalationRules)
|
|
4647
|
+
? (sopDataRaw.escalationRules as any[]).map((r: any) => ({
|
|
4648
|
+
trigger: r.trigger || "",
|
|
4649
|
+
escalateTo: r.escalateTo || r.escalate_to || "",
|
|
4650
|
+
priority: (r.priority || "P2") as "P1" | "P2" | "P3",
|
|
4651
|
+
}))
|
|
4652
|
+
: [];
|
|
4653
|
+
const deliverables = Array.isArray(sopDataRaw.deliverables)
|
|
4654
|
+
? (sopDataRaw.deliverables as string[])
|
|
4655
|
+
: [];
|
|
4656
|
+
|
|
4657
|
+
const sopData = {
|
|
4658
|
+
title: (sopDataRaw.title as string) || title,
|
|
4659
|
+
objective: (sopDataRaw.objective as string) || "",
|
|
4660
|
+
scope: (sopDataRaw.scope as string) || "",
|
|
4661
|
+
prerequisites,
|
|
4662
|
+
steps,
|
|
4663
|
+
deliverables,
|
|
4664
|
+
escalationRules,
|
|
4665
|
+
successCriteria,
|
|
4666
|
+
notes: (sopDataRaw.notes as string) || "",
|
|
4667
|
+
};
|
|
4668
|
+
sopContent = JSON.stringify(sopData);
|
|
4669
|
+
} else {
|
|
4670
|
+
// Create empty SOP data
|
|
4671
|
+
sopContent = JSON.stringify({
|
|
4672
|
+
title, objective: "", scope: "", prerequisites: [],
|
|
4673
|
+
steps: [{ id: `step-${Date.now()}-0`, name: "", action: "", owner: "", output: "" }],
|
|
4674
|
+
deliverables: [], escalationRules: [], successCriteria: [], notes: "",
|
|
4675
|
+
});
|
|
4676
|
+
}
|
|
4677
|
+
|
|
4678
|
+
const row = {
|
|
4679
|
+
user_id: userId,
|
|
4680
|
+
agent_id: agentIdRaw,
|
|
4681
|
+
title,
|
|
4682
|
+
content: sopContent,
|
|
4683
|
+
status: (params.status as string) || "draft",
|
|
4684
|
+
department: (params.department as string) || null,
|
|
4685
|
+
};
|
|
4686
|
+
|
|
4687
|
+
const { data, error } = await supabase.from("agent_sops").insert(row).select().single();
|
|
4688
|
+
if (error) return err(error.message);
|
|
4689
|
+
|
|
4690
|
+
return ok({
|
|
4691
|
+
message: `SOP "${title}" created for agent ${agentIdRaw}`,
|
|
4692
|
+
sop: { id: data.id, title: data.title, status: data.status, agent_id: data.agent_id, department: data.department },
|
|
4693
|
+
});
|
|
4694
|
+
} catch (e) { return err(e instanceof Error ? e.message : String(e)); }
|
|
4695
|
+
}
|
|
4696
|
+
|
|
4697
|
+
async function handleSOPList(supabase: SupabaseClient, userId: string, params: Record<string, unknown>): Promise<ToolResult> {
|
|
4698
|
+
try {
|
|
4699
|
+
let query = supabase
|
|
4700
|
+
.from("agent_sops")
|
|
4701
|
+
.select("id, agent_id, title, status, department, created_at, updated_at")
|
|
4702
|
+
.eq("user_id", userId)
|
|
4703
|
+
.order("updated_at", { ascending: false });
|
|
4704
|
+
if (params.agent_id) query = query.eq("agent_id", params.agent_id as string);
|
|
4705
|
+
const { data, error } = await query;
|
|
4706
|
+
if (error) return err(error.message);
|
|
4707
|
+
return ok({ sops: data || [], count: (data || []).length });
|
|
4708
|
+
} catch (e) { return err(e instanceof Error ? e.message : String(e)); }
|
|
4709
|
+
}
|
|
4710
|
+
|
|
4711
|
+
async function handleSOPGet(supabase: SupabaseClient, userId: string, params: Record<string, unknown>): Promise<ToolResult> {
|
|
4712
|
+
try {
|
|
4713
|
+
if (!params.sop_id) return err("Missing required field: sop_id");
|
|
4714
|
+
const { data, error } = await supabase
|
|
4715
|
+
.from("agent_sops")
|
|
4716
|
+
.select("*")
|
|
4717
|
+
.eq("id", params.sop_id as string)
|
|
4718
|
+
.eq("user_id", userId)
|
|
4719
|
+
.single();
|
|
4720
|
+
if (error) return err(error.message);
|
|
4721
|
+
if (!data) return err("SOP not found");
|
|
4722
|
+
|
|
4723
|
+
// Parse the content to return structured data
|
|
4724
|
+
let parsedContent: any = {};
|
|
4725
|
+
try { parsedContent = JSON.parse(data.content || "{}"); } catch { /* leave empty */ }
|
|
4726
|
+
|
|
4727
|
+
return ok({ sop: { ...data, content: parsedContent } });
|
|
4728
|
+
} catch (e) { return err(e instanceof Error ? e.message : String(e)); }
|
|
4729
|
+
}
|
|
4730
|
+
|
|
4731
|
+
async function handleSOPUpdate(supabase: SupabaseClient, userId: string, params: Record<string, unknown>): Promise<ToolResult> {
|
|
4732
|
+
try {
|
|
4733
|
+
if (!params.sop_id) return err("Missing required field: sop_id");
|
|
4734
|
+
const updates: Record<string, any> = { updated_at: new Date().toISOString() };
|
|
4735
|
+
if (params.title !== undefined) updates.title = params.title;
|
|
4736
|
+
if (params.status !== undefined) updates.status = params.status;
|
|
4737
|
+
if (params.department !== undefined) updates.department = params.department;
|
|
4738
|
+
|
|
4739
|
+
if (params.sop_data) {
|
|
4740
|
+
const sopDataRaw = params.sop_data as Record<string, unknown>;
|
|
4741
|
+
const steps = Array.isArray(sopDataRaw.steps)
|
|
4742
|
+
? (sopDataRaw.steps as any[]).map((s: any, i: number) => ({
|
|
4743
|
+
id: s.id || `step-${Date.now()}-${i}`,
|
|
4744
|
+
name: s.name || "", action: s.action || "", owner: s.owner || "", output: s.output || "",
|
|
4745
|
+
}))
|
|
4746
|
+
: [];
|
|
4747
|
+
const prerequisites = Array.isArray(sopDataRaw.prerequisites)
|
|
4748
|
+
? (sopDataRaw.prerequisites as any[]).map((p: any) => ({ text: p.text || "", checked: !!p.checked }))
|
|
4749
|
+
: [];
|
|
4750
|
+
const successCriteria = Array.isArray(sopDataRaw.successCriteria)
|
|
4751
|
+
? (sopDataRaw.successCriteria as any[]).map((c: any) => ({ text: c.text || "", checked: !!c.checked }))
|
|
4752
|
+
: [];
|
|
4753
|
+
const escalationRules = Array.isArray(sopDataRaw.escalationRules)
|
|
4754
|
+
? (sopDataRaw.escalationRules as any[]).map((r: any) => ({
|
|
4755
|
+
trigger: r.trigger || "", escalateTo: r.escalateTo || r.escalate_to || "",
|
|
4756
|
+
priority: (r.priority || "P2") as "P1" | "P2" | "P3",
|
|
4757
|
+
}))
|
|
4758
|
+
: [];
|
|
4759
|
+
|
|
4760
|
+
updates.content = JSON.stringify({
|
|
4761
|
+
title: (sopDataRaw.title as string) || (params.title as string) || "",
|
|
4762
|
+
objective: (sopDataRaw.objective as string) || "",
|
|
4763
|
+
scope: (sopDataRaw.scope as string) || "",
|
|
4764
|
+
prerequisites, steps,
|
|
4765
|
+
deliverables: Array.isArray(sopDataRaw.deliverables) ? sopDataRaw.deliverables : [],
|
|
4766
|
+
escalationRules, successCriteria,
|
|
4767
|
+
notes: (sopDataRaw.notes as string) || "",
|
|
4768
|
+
});
|
|
4769
|
+
}
|
|
4770
|
+
|
|
4771
|
+
const { data, error } = await supabase
|
|
4772
|
+
.from("agent_sops").update(updates)
|
|
4773
|
+
.eq("id", params.sop_id as string).eq("user_id", userId)
|
|
4774
|
+
.select("id, title, status").maybeSingle();
|
|
4775
|
+
if (error) return err(error.message);
|
|
4776
|
+
if (!data) return err("SOP not found");
|
|
4777
|
+
return ok({ message: `SOP "${data.title}" updated`, sop: data });
|
|
4778
|
+
} catch (e) { return err(e instanceof Error ? e.message : String(e)); }
|
|
4779
|
+
}
|
|
4780
|
+
|
|
4781
|
+
async function handleSOPDelete(supabase: SupabaseClient, userId: string, params: Record<string, unknown>): Promise<ToolResult> {
|
|
4782
|
+
try {
|
|
4783
|
+
if (!params.sop_id) return err("Missing required field: sop_id");
|
|
4784
|
+
const { data, error } = await supabase
|
|
4785
|
+
.from("agent_sops").delete()
|
|
4786
|
+
.eq("id", params.sop_id as string).eq("user_id", userId)
|
|
4787
|
+
.select("id, title").maybeSingle();
|
|
4788
|
+
if (error) return err(error.message);
|
|
4789
|
+
if (!data) return err("SOP not found — nothing deleted");
|
|
4790
|
+
return ok({ message: `SOP "${data.title}" deleted`, deleted: true });
|
|
4791
|
+
} catch (e) { return err(e instanceof Error ? e.message : String(e)); }
|
|
4792
|
+
}
|
|
4793
|
+
|
|
4794
|
+
async function handleSOPListSubagents(supabase: SupabaseClient, userId: string, params: Record<string, unknown>): Promise<ToolResult> {
|
|
4795
|
+
try {
|
|
4796
|
+
if (!params.chief_agent_id) return err("Missing required field: chief_agent_id");
|
|
4797
|
+
const { data, error } = await supabase
|
|
4798
|
+
.from("agent_subagents")
|
|
4799
|
+
.select("id, name, role, codename, color_hex, created_at")
|
|
4800
|
+
.eq("user_id", userId)
|
|
4801
|
+
.eq("chief_agent_id", params.chief_agent_id as string)
|
|
4802
|
+
.order("created_at", { ascending: true });
|
|
4803
|
+
if (error) return err(error.message);
|
|
4804
|
+
return ok({ subagents: data || [], count: (data || []).length, max_allowed: 5 });
|
|
4805
|
+
} catch (e) { return err(e instanceof Error ? e.message : String(e)); }
|
|
4806
|
+
}
|
|
4807
|
+
|
|
4808
|
+
async function handleSOPApplyTemplate(
|
|
4809
|
+
supabase: SupabaseClient, userId: string,
|
|
4810
|
+
resolveAgent: (id?: string) => Promise<string | null>,
|
|
4811
|
+
params: Record<string, unknown>,
|
|
4812
|
+
): Promise<ToolResult> {
|
|
4813
|
+
try {
|
|
4814
|
+
if (!params.agent_id) return err("Missing required field: agent_id");
|
|
4815
|
+
if (!params.template_id) return err("Missing required field: template_id");
|
|
4816
|
+
|
|
4817
|
+
// Fetch template
|
|
4818
|
+
const { data: template, error: tErr } = await supabase
|
|
4819
|
+
.from("sop_templates")
|
|
4820
|
+
.select("name, content")
|
|
4821
|
+
.eq("id", params.template_id as string)
|
|
4822
|
+
.eq("user_id", userId)
|
|
4823
|
+
.single();
|
|
4824
|
+
if (tErr || !template) return err("Template not found");
|
|
4825
|
+
|
|
4826
|
+
const title = (params.title as string) || template.name || "SOP from Template";
|
|
4827
|
+
|
|
4828
|
+
const row = {
|
|
4829
|
+
user_id: userId,
|
|
4830
|
+
agent_id: params.agent_id as string,
|
|
4831
|
+
template_id: params.template_id as string,
|
|
4832
|
+
title,
|
|
4833
|
+
content: template.content, // Template content is already JSON
|
|
4834
|
+
status: "draft",
|
|
4835
|
+
department: (params.department as string) || null,
|
|
4836
|
+
};
|
|
4837
|
+
|
|
4838
|
+
const { data, error } = await supabase.from("agent_sops").insert(row).select().single();
|
|
4839
|
+
if (error) return err(error.message);
|
|
4840
|
+
|
|
4841
|
+
return ok({
|
|
4842
|
+
message: `SOP "${title}" created from template "${template.name}" for agent ${params.agent_id}`,
|
|
4843
|
+
sop: { id: data.id, title: data.title, status: data.status, agent_id: data.agent_id },
|
|
4844
|
+
});
|
|
4845
|
+
} catch (e) { return err(e instanceof Error ? e.message : String(e)); }
|
|
4846
|
+
}
|
|
4847
|
+
|
|
4848
|
+
|
|
4512
4849
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
4513
4850
|
// Public: Register All Meta-Tools
|
|
4514
4851
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
@@ -4538,9 +4875,10 @@ export function registerTools(
|
|
|
4538
4875
|
registerConstellationOps(api, supabase, userId); // 10
|
|
4539
4876
|
registerFileOps(api, supabase, userId); // 11
|
|
4540
4877
|
registerPlanOps(api, supabase, userId, resolveAgent); // 12
|
|
4878
|
+
registerSOPOps(api, supabase, userId, resolveAgent); // 13
|
|
4541
4879
|
|
|
4542
4880
|
// ── Count and log ──
|
|
4543
|
-
const toolCount =
|
|
4881
|
+
const toolCount = 13;
|
|
4544
4882
|
const callerName = getCallingAgentName(api);
|
|
4545
4883
|
const agentLabel = fallbackAgentId || callerName || "auto-detect";
|
|
4546
4884
|
api.logger.info(`[ofiere] ${toolCount} meta-tools registered (agent: ${agentLabel})`);
|