ofiere-openclaw-plugin 2.0.0 → 3.0.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 +50 -19
- package/src/tools.ts +933 -23
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ofiere-openclaw-plugin",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"description": "OpenClaw plugin for Ofiere PM —
|
|
5
|
+
"description": "OpenClaw plugin for Ofiere PM — 9 meta-tools covering tasks, agents, projects, scheduling, knowledge, workflows, notifications, memory, and prompts",
|
|
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
|
@@ -10,30 +10,58 @@
|
|
|
10
10
|
|
|
11
11
|
const TOOL_DOCS: Record<string, string> = {
|
|
12
12
|
OFIERE_TASK_OPS: `- **OFIERE_TASK_OPS** — Manage tasks (action: "list", "create", "update", "delete")
|
|
13
|
-
- list: Filter by status, agent_id, space_id, folder_id, limit
|
|
14
|
-
- create: Requires title
|
|
15
|
-
|
|
13
|
+
- list: Filter by status, agent_id, space_id, folder_id, limit. Returns execution_plan, goals, constraints if present
|
|
14
|
+
- create: Requires title. Optional: agent_id, description, instructions, execution_plan, goals, constraints, system_prompt, priority, tags, dates
|
|
15
|
+
- For COMPLEX tasks: include execution_plan (step-by-step), goals, constraints, and system_prompt
|
|
16
|
+
- For SIMPLE tasks: just title and optionally description
|
|
17
|
+
- update: Requires task_id. All create fields + progress
|
|
16
18
|
- delete: Requires task_id. Removes task and subtasks`,
|
|
17
19
|
|
|
18
20
|
OFIERE_AGENT_OPS: `- **OFIERE_AGENT_OPS** — Query agents (action: "list")
|
|
19
21
|
- list: See all agents with IDs, names, roles for task assignment`,
|
|
20
22
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
23
|
+
OFIERE_PROJECT_OPS: `- **OFIERE_PROJECT_OPS** — Manage PM hierarchy (action: "list_spaces", "create_space", "update_space", "delete_space", "list_folders", "create_folder", "update_folder", "delete_folder", "list_dependencies", "add_dependency", "remove_dependency")
|
|
24
|
+
- Spaces: Top-level containers. CRUD with name, icon, icon_color
|
|
25
|
+
- Folders: Live inside spaces. Can nest via parent_folder_id. Types: folder, project
|
|
26
|
+
- Dependencies: Link tasks with predecessor/successor relationships
|
|
27
|
+
- Types: finish_to_start (default), start_to_start, finish_to_finish, start_to_finish
|
|
28
|
+
- lag_days: optional delay between linked tasks`,
|
|
29
|
+
|
|
30
|
+
OFIERE_SCHEDULE_OPS: `- **OFIERE_SCHEDULE_OPS** — Calendar events (action: "list", "create", "update", "delete")
|
|
31
|
+
- list: Filter by start_date, end_date, agent_id
|
|
32
|
+
- create: Requires title + scheduled_date (YYYY-MM-DD). Optional: scheduled_time, duration_minutes, task_id, agent_id, recurrence_type, color
|
|
33
|
+
- Recurrence: none, hourly, daily, weekly, monthly with interval`,
|
|
34
|
+
|
|
35
|
+
OFIERE_KNOWLEDGE_OPS: `- **OFIERE_KNOWLEDGE_OPS** — Knowledge base documents (action: "search", "list", "create", "update", "delete")
|
|
36
|
+
- search: Keyword search across all docs. Requires: query
|
|
37
|
+
- list: Paginated listing with optional search filter
|
|
38
|
+
- create: Add knowledge. Requires: file_name. Optional: content, source, author, credibility_tier
|
|
39
|
+
- update/delete: By document ID`,
|
|
40
|
+
|
|
41
|
+
OFIERE_WORKFLOW_OPS: `- **OFIERE_WORKFLOW_OPS** — Automated workflows (action: "list", "get", "create", "list_runs", "trigger")
|
|
42
|
+
- list: All workflows, filter by status (draft, active, paused, archived)
|
|
43
|
+
- get: Full workflow details by ID
|
|
44
|
+
- create: New workflow with name, steps, schedule
|
|
45
|
+
- list_runs: Recent execution history for a workflow
|
|
46
|
+
- trigger: Start a workflow run (creates a run record)`,
|
|
47
|
+
|
|
48
|
+
OFIERE_NOTIFY_OPS: `- **OFIERE_NOTIFY_OPS** — Notifications (action: "list", "mark_read", "mark_all_read", "delete")
|
|
49
|
+
- list: Recent notifications. unread_only=true for unread only
|
|
50
|
+
- mark_read: Mark one notification read by ID
|
|
51
|
+
- mark_all_read: Mark all as read`,
|
|
52
|
+
|
|
53
|
+
OFIERE_MEMORY_OPS: `- **OFIERE_MEMORY_OPS** — Conversation history & knowledge memory (action: "list_conversations", "get_messages", "search_messages", "add_knowledge", "search_knowledge")
|
|
54
|
+
- list_conversations: Recent chats, filter by agent_id
|
|
55
|
+
- get_messages: Full message history for a conversation
|
|
56
|
+
- search_messages: Search across all messages by keyword
|
|
57
|
+
- add_knowledge: Store a knowledge fragment (requires agent_id, content, source)
|
|
58
|
+
- search_knowledge: Search stored knowledge for an agent`,
|
|
59
|
+
|
|
60
|
+
OFIERE_PROMPT_OPS: `- **OFIERE_PROMPT_OPS** — Manage prompt instruction chunks (action: "list", "get", "create", "update", "delete")
|
|
61
|
+
- list: All prompt chunks, filter by agent_id
|
|
62
|
+
- create: New chunk with label + content. These modify agent behavior
|
|
63
|
+
- update: Change label, content, enabled state, or sort_order
|
|
64
|
+
- All modifications are logged for audit`,
|
|
37
65
|
};
|
|
38
66
|
|
|
39
67
|
export function getSystemPrompt(state: {
|
|
@@ -73,6 +101,9 @@ ${toolDocs}
|
|
|
73
101
|
- Priority levels: 0=LOW, 1=MEDIUM, 2=HIGH, 3=CRITICAL.
|
|
74
102
|
- Changes appear in the Ofiere dashboard immediately via real-time sync.
|
|
75
103
|
- Do NOT fabricate task IDs — use OFIERE_TASK_OPS action:"list" to look up real IDs.
|
|
104
|
+
- For complex tasks, ALWAYS include execution_plan, goals, and constraints. For simple tasks, just title is enough.
|
|
105
|
+
- When creating dependencies, use OFIERE_PROJECT_OPS to link predecessor/successor tasks.
|
|
106
|
+
- Prompt chunk modifications (OFIERE_PROMPT_OPS) are powerful — use thoughtfully as they change agent behavior.
|
|
76
107
|
</ofiere-pm>`;
|
|
77
108
|
}
|
|
78
109
|
|
package/src/tools.ts
CHANGED
|
@@ -172,7 +172,9 @@ function createAgentResolver(
|
|
|
172
172
|
};
|
|
173
173
|
}
|
|
174
174
|
|
|
175
|
-
//
|
|
175
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
176
|
+
// META-TOOL 1: OFIERE_TASK_OPS — Task Management
|
|
177
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
176
178
|
|
|
177
179
|
function registerTaskOps(
|
|
178
180
|
api: any,
|
|
@@ -186,13 +188,14 @@ function registerTaskOps(
|
|
|
186
188
|
description:
|
|
187
189
|
`Manage tasks in the Ofiere PM dashboard. All task operations go through this tool.\n\n` +
|
|
188
190
|
`Actions:\n` +
|
|
189
|
-
`- "list": List/filter tasks. Optional
|
|
190
|
-
`- "create": Create a
|
|
191
|
-
`- "update": Update
|
|
192
|
-
`- "delete": Delete
|
|
193
|
-
`
|
|
194
|
-
`
|
|
195
|
-
`
|
|
191
|
+
`- "list": List/filter tasks. Optional: status, agent_id, space_id, folder_id, limit\n` +
|
|
192
|
+
`- "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\n` +
|
|
193
|
+
`- "update": Update a task. Required: task_id. Optional: all create fields + progress\n` +
|
|
194
|
+
`- "delete": Delete task + subtasks. Required: task_id\n\n` +
|
|
195
|
+
`For complex tasks, fill in execution_plan (step-by-step plan), goals, constraints, and system_prompt to help the executing agent.\n` +
|
|
196
|
+
`For simple tasks, just provide title and optionally description.\n` +
|
|
197
|
+
`agent_id: Pass your name to self-assign, another agent's name, or 'none'.\n` +
|
|
198
|
+
`Status: PENDING, IN_PROGRESS, DONE, FAILED | Priority: 0=LOW, 1=MEDIUM, 2=HIGH, 3=CRITICAL`,
|
|
196
199
|
parameters: {
|
|
197
200
|
type: "object",
|
|
198
201
|
required: ["action"],
|
|
@@ -202,14 +205,13 @@ function registerTaskOps(
|
|
|
202
205
|
description: "The operation to perform",
|
|
203
206
|
enum: ["list", "create", "update", "delete"],
|
|
204
207
|
},
|
|
205
|
-
// ── Shared / contextual params ──
|
|
206
208
|
task_id: { type: "string", description: "Task ID (required for update, delete)" },
|
|
207
209
|
title: { type: "string", description: "Task title (required for create)" },
|
|
208
210
|
description: { type: "string", description: "Task description" },
|
|
211
|
+
instructions: { type: "string", description: "Detailed instructions for the agent executing this task" },
|
|
209
212
|
agent_id: {
|
|
210
213
|
type: "string",
|
|
211
|
-
description:
|
|
212
|
-
"Agent name or ID. For create: your name to self-assign, another name to delegate, 'none' for unassigned. For list: filter by agent.",
|
|
214
|
+
description: "Agent name or ID. Your name to self-assign, 'none' for unassigned.",
|
|
213
215
|
},
|
|
214
216
|
status: {
|
|
215
217
|
type: "string",
|
|
@@ -227,6 +229,42 @@ function registerTaskOps(
|
|
|
227
229
|
items: { type: "string" },
|
|
228
230
|
description: "Tags for the task",
|
|
229
231
|
},
|
|
232
|
+
execution_plan: {
|
|
233
|
+
type: "array",
|
|
234
|
+
items: {
|
|
235
|
+
type: "object",
|
|
236
|
+
properties: {
|
|
237
|
+
text: { type: "string", description: "Step description" },
|
|
238
|
+
},
|
|
239
|
+
required: ["text"],
|
|
240
|
+
},
|
|
241
|
+
description: "Ordered execution steps for complex tasks. Each step: { text: '...' }",
|
|
242
|
+
},
|
|
243
|
+
goals: {
|
|
244
|
+
type: "array",
|
|
245
|
+
items: {
|
|
246
|
+
type: "object",
|
|
247
|
+
properties: {
|
|
248
|
+
type: { type: "string", enum: ["budget", "stack", "legal", "deadline", "custom"], description: "Goal category" },
|
|
249
|
+
label: { type: "string", description: "Goal description" },
|
|
250
|
+
},
|
|
251
|
+
required: ["label"],
|
|
252
|
+
},
|
|
253
|
+
description: "Task goals. Each: { type?: 'budget'|'stack'|'legal'|'deadline'|'custom', label: '...' }",
|
|
254
|
+
},
|
|
255
|
+
constraints: {
|
|
256
|
+
type: "array",
|
|
257
|
+
items: {
|
|
258
|
+
type: "object",
|
|
259
|
+
properties: {
|
|
260
|
+
type: { type: "string", enum: ["budget", "stack", "legal", "deadline", "custom"], description: "Constraint category" },
|
|
261
|
+
label: { type: "string", description: "Constraint description" },
|
|
262
|
+
},
|
|
263
|
+
required: ["label"],
|
|
264
|
+
},
|
|
265
|
+
description: "Task constraints. Each: { type?: 'budget'|'stack'|'legal'|'deadline'|'custom', label: '...' }",
|
|
266
|
+
},
|
|
267
|
+
system_prompt: { type: "string", description: "Custom system prompt injection for the executing agent" },
|
|
230
268
|
limit: { type: "number", description: "Max results for list (default 50)" },
|
|
231
269
|
},
|
|
232
270
|
},
|
|
@@ -263,7 +301,7 @@ async function handleListTasks(
|
|
|
263
301
|
.from("tasks")
|
|
264
302
|
.select(
|
|
265
303
|
"id, title, description, status, priority, agent_id, space_id, folder_id, " +
|
|
266
|
-
"start_date, due_date, progress, created_at, updated_at",
|
|
304
|
+
"start_date, due_date, progress, tags, custom_fields, created_at, updated_at",
|
|
267
305
|
)
|
|
268
306
|
.eq("user_id", userId)
|
|
269
307
|
.order("updated_at", { ascending: false });
|
|
@@ -276,7 +314,21 @@ async function handleListTasks(
|
|
|
276
314
|
|
|
277
315
|
const { data, error } = await query;
|
|
278
316
|
if (error) return err(error.message);
|
|
279
|
-
|
|
317
|
+
|
|
318
|
+
// Unpack custom_fields for readability
|
|
319
|
+
const tasks = (data || []).map((t: any) => {
|
|
320
|
+
const cf = t.custom_fields || {};
|
|
321
|
+
return {
|
|
322
|
+
...t,
|
|
323
|
+
execution_plan: cf.execution_plan || undefined,
|
|
324
|
+
goals: cf.goals || undefined,
|
|
325
|
+
constraints: cf.constraints || undefined,
|
|
326
|
+
system_prompt: cf.system_prompt || undefined,
|
|
327
|
+
instructions: cf.instructions || t.description || undefined,
|
|
328
|
+
};
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
return ok({ tasks, count: tasks.length });
|
|
280
332
|
} catch (e) {
|
|
281
333
|
return err(e instanceof Error ? e.message : String(e));
|
|
282
334
|
}
|
|
@@ -302,11 +354,41 @@ async function handleCreateTask(
|
|
|
302
354
|
|
|
303
355
|
const assignee = isUnassigned ? null : await resolveAgent(rawAgentId);
|
|
304
356
|
|
|
357
|
+
// Build custom_fields from task-ops extended fields
|
|
358
|
+
const cf: Record<string, unknown> = {};
|
|
359
|
+
|
|
360
|
+
if (params.execution_plan && Array.isArray(params.execution_plan) && (params.execution_plan as any[]).length > 0) {
|
|
361
|
+
cf.execution_plan = (params.execution_plan as any[]).map((step: any, i: number) => ({
|
|
362
|
+
id: `step-${Date.now()}-${i}`,
|
|
363
|
+
text: typeof step === "string" ? step : step.text || String(step),
|
|
364
|
+
order: i,
|
|
365
|
+
}));
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
if (params.goals && Array.isArray(params.goals) && (params.goals as any[]).length > 0) {
|
|
369
|
+
cf.goals = (params.goals as any[]).map((g: any, i: number) => ({
|
|
370
|
+
id: `goal-${Date.now()}-${i}`,
|
|
371
|
+
type: g.type || "custom",
|
|
372
|
+
label: typeof g === "string" ? g : g.label || String(g),
|
|
373
|
+
}));
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
if (params.constraints && Array.isArray(params.constraints) && (params.constraints as any[]).length > 0) {
|
|
377
|
+
cf.constraints = (params.constraints as any[]).map((c: any, i: number) => ({
|
|
378
|
+
id: `cstr-${Date.now()}-${i}`,
|
|
379
|
+
type: c.type || "custom",
|
|
380
|
+
label: typeof c === "string" ? c : c.label || String(c),
|
|
381
|
+
}));
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
if (params.system_prompt) cf.system_prompt = params.system_prompt;
|
|
385
|
+
if (params.instructions) cf.instructions = params.instructions;
|
|
386
|
+
|
|
305
387
|
const insertData: Record<string, unknown> = {
|
|
306
388
|
id,
|
|
307
389
|
user_id: userId,
|
|
308
390
|
title: params.title,
|
|
309
|
-
description: (params.description as string) || null,
|
|
391
|
+
description: (params.description as string) || (params.instructions as string) || null,
|
|
310
392
|
agent_id: assignee,
|
|
311
393
|
assignee_type: "agent",
|
|
312
394
|
status: (params.status as string) || "PENDING",
|
|
@@ -318,7 +400,7 @@ async function handleCreateTask(
|
|
|
318
400
|
tags: (params.tags as string[]) || [],
|
|
319
401
|
progress: 0,
|
|
320
402
|
sort_order: 0,
|
|
321
|
-
custom_fields: {},
|
|
403
|
+
custom_fields: Object.keys(cf).length > 0 ? cf : {},
|
|
322
404
|
created_at: now,
|
|
323
405
|
updated_at: now,
|
|
324
406
|
};
|
|
@@ -339,9 +421,16 @@ async function handleCreateTask(
|
|
|
339
421
|
return err(error.message);
|
|
340
422
|
}
|
|
341
423
|
|
|
424
|
+
const extras = [];
|
|
425
|
+
if (cf.execution_plan) extras.push(`${(cf.execution_plan as any[]).length} execution steps`);
|
|
426
|
+
if (cf.goals) extras.push(`${(cf.goals as any[]).length} goals`);
|
|
427
|
+
if (cf.constraints) extras.push(`${(cf.constraints as any[]).length} constraints`);
|
|
428
|
+
if (cf.system_prompt) extras.push("custom system prompt");
|
|
429
|
+
const extrasStr = extras.length > 0 ? ` with ${extras.join(", ")}` : "";
|
|
430
|
+
|
|
342
431
|
return ok({
|
|
343
432
|
id,
|
|
344
|
-
message: `Task "${params.title}" created and assigned to ${assignee || "no one"}`,
|
|
433
|
+
message: `Task "${params.title}" created and assigned to ${assignee || "no one"}${extrasStr}`,
|
|
345
434
|
task: insertData,
|
|
346
435
|
});
|
|
347
436
|
} catch (e) {
|
|
@@ -367,6 +456,61 @@ async function handleUpdateTask(
|
|
|
367
456
|
}
|
|
368
457
|
if (params.status === "DONE") updates.completed_at = new Date().toISOString();
|
|
369
458
|
|
|
459
|
+
// Handle custom_fields updates (execution_plan, goals, constraints, system_prompt, instructions)
|
|
460
|
+
const hasCustomFields = params.execution_plan !== undefined ||
|
|
461
|
+
params.goals !== undefined ||
|
|
462
|
+
params.constraints !== undefined ||
|
|
463
|
+
params.system_prompt !== undefined ||
|
|
464
|
+
params.instructions !== undefined;
|
|
465
|
+
|
|
466
|
+
if (hasCustomFields) {
|
|
467
|
+
// Fetch existing custom_fields to merge
|
|
468
|
+
const { data: existing } = await supabase
|
|
469
|
+
.from("tasks")
|
|
470
|
+
.select("custom_fields")
|
|
471
|
+
.eq("id", params.task_id as string)
|
|
472
|
+
.eq("user_id", userId)
|
|
473
|
+
.single();
|
|
474
|
+
|
|
475
|
+
const existingCf = (existing?.custom_fields || {}) as Record<string, any>;
|
|
476
|
+
const mergedCf = { ...existingCf };
|
|
477
|
+
|
|
478
|
+
if (params.execution_plan !== undefined) {
|
|
479
|
+
mergedCf.execution_plan = Array.isArray(params.execution_plan)
|
|
480
|
+
? (params.execution_plan as any[]).map((step: any, i: number) => ({
|
|
481
|
+
id: step.id || `step-${Date.now()}-${i}`,
|
|
482
|
+
text: typeof step === "string" ? step : step.text || String(step),
|
|
483
|
+
order: i,
|
|
484
|
+
}))
|
|
485
|
+
: [];
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
if (params.goals !== undefined) {
|
|
489
|
+
mergedCf.goals = Array.isArray(params.goals)
|
|
490
|
+
? (params.goals as any[]).map((g: any, i: number) => ({
|
|
491
|
+
id: g.id || `goal-${Date.now()}-${i}`,
|
|
492
|
+
type: g.type || "custom",
|
|
493
|
+
label: typeof g === "string" ? g : g.label || String(g),
|
|
494
|
+
}))
|
|
495
|
+
: [];
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
if (params.constraints !== undefined) {
|
|
499
|
+
mergedCf.constraints = Array.isArray(params.constraints)
|
|
500
|
+
? (params.constraints as any[]).map((c: any, i: number) => ({
|
|
501
|
+
id: c.id || `cstr-${Date.now()}-${i}`,
|
|
502
|
+
type: c.type || "custom",
|
|
503
|
+
label: typeof c === "string" ? c : c.label || String(c),
|
|
504
|
+
}))
|
|
505
|
+
: [];
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
if (params.system_prompt !== undefined) mergedCf.system_prompt = params.system_prompt;
|
|
509
|
+
if (params.instructions !== undefined) mergedCf.instructions = params.instructions;
|
|
510
|
+
|
|
511
|
+
updates.custom_fields = mergedCf;
|
|
512
|
+
}
|
|
513
|
+
|
|
370
514
|
const { data, error } = await supabase
|
|
371
515
|
.from("tasks")
|
|
372
516
|
.update(updates)
|
|
@@ -423,7 +567,9 @@ async function handleDeleteTask(
|
|
|
423
567
|
}
|
|
424
568
|
}
|
|
425
569
|
|
|
426
|
-
//
|
|
570
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
571
|
+
// META-TOOL 2: OFIERE_AGENT_OPS — Agent Management
|
|
572
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
427
573
|
|
|
428
574
|
function registerAgentOps(
|
|
429
575
|
api: any,
|
|
@@ -495,11 +641,768 @@ async function handleListAgents(
|
|
|
495
641
|
}
|
|
496
642
|
}
|
|
497
643
|
|
|
498
|
-
//
|
|
644
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
645
|
+
// META-TOOL 3: OFIERE_PROJECT_OPS — Spaces, Folders & Dependencies
|
|
646
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
647
|
+
|
|
648
|
+
function registerProjectOps(
|
|
649
|
+
api: any,
|
|
650
|
+
supabase: SupabaseClient,
|
|
651
|
+
userId: string,
|
|
652
|
+
): void {
|
|
653
|
+
api.registerTool({
|
|
654
|
+
name: "OFIERE_PROJECT_OPS",
|
|
655
|
+
label: "Ofiere Project Operations",
|
|
656
|
+
description:
|
|
657
|
+
`Manage PM hierarchy: spaces, folders, and task dependencies.\n\n` +
|
|
658
|
+
`Actions:\n` +
|
|
659
|
+
`- "list_spaces": List all PM spaces\n` +
|
|
660
|
+
`- "create_space": Create a space. Required: name. Optional: description, icon, icon_color\n` +
|
|
661
|
+
`- "update_space": Update a space. Required: id. Optional: name, description, icon, icon_color, sort_order\n` +
|
|
662
|
+
`- "delete_space": Delete a space. Required: id\n` +
|
|
663
|
+
`- "list_folders": List folders. Optional: space_id to filter\n` +
|
|
664
|
+
`- "create_folder": Create. Required: name, space_id. Optional: parent_folder_id, folder_type\n` +
|
|
665
|
+
`- "update_folder": Update. Required: id. Optional: name, space_id, parent_folder_id, sort_order\n` +
|
|
666
|
+
`- "delete_folder": Delete. Required: id\n` +
|
|
667
|
+
`- "list_dependencies": List task dependencies. Optional: task_id\n` +
|
|
668
|
+
`- "add_dependency": Link tasks. Required: predecessor_id, successor_id. Optional: dependency_type, lag_days\n` +
|
|
669
|
+
`- "remove_dependency": Unlink. Required: dependency_id\n` +
|
|
670
|
+
`dependency_type: finish_to_start (default), start_to_start, finish_to_finish, start_to_finish`,
|
|
671
|
+
parameters: {
|
|
672
|
+
type: "object",
|
|
673
|
+
required: ["action"],
|
|
674
|
+
properties: {
|
|
675
|
+
action: {
|
|
676
|
+
type: "string",
|
|
677
|
+
description: "The operation to perform",
|
|
678
|
+
enum: ["list_spaces", "create_space", "update_space", "delete_space",
|
|
679
|
+
"list_folders", "create_folder", "update_folder", "delete_folder",
|
|
680
|
+
"list_dependencies", "add_dependency", "remove_dependency"],
|
|
681
|
+
},
|
|
682
|
+
id: { type: "string", description: "Space, folder, or dependency ID" },
|
|
683
|
+
name: { type: "string", description: "Name for space/folder" },
|
|
684
|
+
description: { type: "string", description: "Description" },
|
|
685
|
+
icon: { type: "string", description: "Emoji icon for space" },
|
|
686
|
+
icon_color: { type: "string", description: "Hex color for space icon" },
|
|
687
|
+
space_id: { type: "string", description: "Parent space ID" },
|
|
688
|
+
parent_folder_id: { type: "string", description: "Parent folder ID for nesting" },
|
|
689
|
+
folder_type: { type: "string", enum: ["folder", "project"], description: "Folder type" },
|
|
690
|
+
sort_order: { type: "number", description: "Sort order" },
|
|
691
|
+
predecessor_id: { type: "string", description: "Task that must complete first" },
|
|
692
|
+
successor_id: { type: "string", description: "Task that depends on predecessor" },
|
|
693
|
+
dependency_type: {
|
|
694
|
+
type: "string",
|
|
695
|
+
enum: ["finish_to_start", "start_to_start", "finish_to_finish", "start_to_finish"],
|
|
696
|
+
description: "Type of dependency link",
|
|
697
|
+
},
|
|
698
|
+
lag_days: { type: "number", description: "Days of lag between tasks (default 0)" },
|
|
699
|
+
task_id: { type: "string", description: "Filter dependencies by task ID" },
|
|
700
|
+
dependency_id: { type: "string", description: "Dependency ID to remove" },
|
|
701
|
+
},
|
|
702
|
+
},
|
|
703
|
+
async execute(_id: string, params: Record<string, unknown>) {
|
|
704
|
+
const action = params.action as string;
|
|
705
|
+
switch (action) {
|
|
706
|
+
// ── Spaces ──
|
|
707
|
+
case "list_spaces": {
|
|
708
|
+
const { data, error } = await supabase.from("pm_spaces").select("*").eq("user_id", userId).order("sort_order");
|
|
709
|
+
if (error) return err(error.message);
|
|
710
|
+
return ok({ spaces: data || [], count: (data || []).length });
|
|
711
|
+
}
|
|
712
|
+
case "create_space": {
|
|
713
|
+
if (!params.name) return err("Missing required: name");
|
|
714
|
+
const { data, error } = await supabase.from("pm_spaces").insert({
|
|
715
|
+
user_id: userId,
|
|
716
|
+
name: params.name,
|
|
717
|
+
description: (params.description as string) || "",
|
|
718
|
+
icon: (params.icon as string) || "📁",
|
|
719
|
+
icon_color: (params.icon_color as string) || "#FF6D29",
|
|
720
|
+
access_type: "private",
|
|
721
|
+
sort_order: (params.sort_order as number) || 0,
|
|
722
|
+
}).select().single();
|
|
723
|
+
if (error) return err(error.message);
|
|
724
|
+
return ok({ message: `Space "${params.name}" created`, space: data });
|
|
725
|
+
}
|
|
726
|
+
case "update_space": {
|
|
727
|
+
if (!params.id) return err("Missing required: id");
|
|
728
|
+
const upd: Record<string, any> = { updated_at: new Date().toISOString() };
|
|
729
|
+
for (const f of ["name", "description", "icon", "icon_color", "sort_order"]) {
|
|
730
|
+
if ((params as any)[f] !== undefined) upd[f] = (params as any)[f];
|
|
731
|
+
}
|
|
732
|
+
const { error } = await supabase.from("pm_spaces").update(upd).eq("id", params.id).eq("user_id", userId);
|
|
733
|
+
if (error) return err(error.message);
|
|
734
|
+
return ok({ message: "Space updated", ok: true });
|
|
735
|
+
}
|
|
736
|
+
case "delete_space": {
|
|
737
|
+
if (!params.id) return err("Missing required: id");
|
|
738
|
+
const { error } = await supabase.from("pm_spaces").delete().eq("id", params.id).eq("user_id", userId);
|
|
739
|
+
if (error) return err(error.message);
|
|
740
|
+
return ok({ message: "Space deleted", ok: true });
|
|
741
|
+
}
|
|
742
|
+
// ── Folders ──
|
|
743
|
+
case "list_folders": {
|
|
744
|
+
let q = supabase.from("pm_folders").select("*").eq("user_id", userId).order("sort_order");
|
|
745
|
+
if (params.space_id) q = q.eq("space_id", params.space_id as string);
|
|
746
|
+
const { data, error } = await q;
|
|
747
|
+
if (error) return err(error.message);
|
|
748
|
+
return ok({ folders: data || [], count: (data || []).length });
|
|
749
|
+
}
|
|
750
|
+
case "create_folder": {
|
|
751
|
+
if (!params.name || !params.space_id) return err("Missing required: name, space_id");
|
|
752
|
+
const { data, error } = await supabase.from("pm_folders").insert({
|
|
753
|
+
user_id: userId,
|
|
754
|
+
space_id: params.space_id,
|
|
755
|
+
parent_folder_id: (params.parent_folder_id as string) || null,
|
|
756
|
+
name: params.name,
|
|
757
|
+
description: "",
|
|
758
|
+
folder_type: (params.folder_type as string) || "folder",
|
|
759
|
+
sort_order: (params.sort_order as number) || 0,
|
|
760
|
+
}).select().single();
|
|
761
|
+
if (error) return err(error.message);
|
|
762
|
+
return ok({ message: `Folder "${params.name}" created`, folder: data });
|
|
763
|
+
}
|
|
764
|
+
case "update_folder": {
|
|
765
|
+
if (!params.id) return err("Missing required: id");
|
|
766
|
+
const upd: Record<string, any> = { updated_at: new Date().toISOString() };
|
|
767
|
+
for (const f of ["name", "description", "space_id", "parent_folder_id", "folder_type", "sort_order"]) {
|
|
768
|
+
if ((params as any)[f] !== undefined) upd[f] = (params as any)[f];
|
|
769
|
+
}
|
|
770
|
+
const { error } = await supabase.from("pm_folders").update(upd).eq("id", params.id).eq("user_id", userId);
|
|
771
|
+
if (error) return err(error.message);
|
|
772
|
+
return ok({ message: "Folder updated", ok: true });
|
|
773
|
+
}
|
|
774
|
+
case "delete_folder": {
|
|
775
|
+
if (!params.id) return err("Missing required: id");
|
|
776
|
+
const { error } = await supabase.from("pm_folders").delete().eq("id", params.id).eq("user_id", userId);
|
|
777
|
+
if (error) return err(error.message);
|
|
778
|
+
return ok({ message: "Folder deleted", ok: true });
|
|
779
|
+
}
|
|
780
|
+
// ── Dependencies ──
|
|
781
|
+
case "list_dependencies": {
|
|
782
|
+
let q = supabase.from("pm_dependencies").select("*").eq("user_id", userId);
|
|
783
|
+
if (params.task_id) {
|
|
784
|
+
q = supabase.from("pm_dependencies").select("*").eq("user_id", userId)
|
|
785
|
+
.or(`predecessor_id.eq.${params.task_id},successor_id.eq.${params.task_id}`);
|
|
786
|
+
}
|
|
787
|
+
const { data, error } = await q;
|
|
788
|
+
if (error) return err(error.message);
|
|
789
|
+
return ok({ dependencies: data || [], count: (data || []).length });
|
|
790
|
+
}
|
|
791
|
+
case "add_dependency": {
|
|
792
|
+
if (!params.predecessor_id || !params.successor_id) return err("Missing required: predecessor_id, successor_id");
|
|
793
|
+
const { data, error } = await supabase.from("pm_dependencies").insert({
|
|
794
|
+
user_id: userId,
|
|
795
|
+
predecessor_id: params.predecessor_id,
|
|
796
|
+
successor_id: params.successor_id,
|
|
797
|
+
dependency_type: (params.dependency_type as string) || "finish_to_start",
|
|
798
|
+
lag_days: (params.lag_days as number) || 0,
|
|
799
|
+
}).select().single();
|
|
800
|
+
if (error) return err(error.message);
|
|
801
|
+
return ok({ message: "Dependency created", dependency: data });
|
|
802
|
+
}
|
|
803
|
+
case "remove_dependency": {
|
|
804
|
+
const depId = (params.dependency_id || params.id) as string;
|
|
805
|
+
if (!depId) return err("Missing required: dependency_id");
|
|
806
|
+
const { error } = await supabase.from("pm_dependencies").delete().eq("id", depId).eq("user_id", userId);
|
|
807
|
+
if (error) return err(error.message);
|
|
808
|
+
return ok({ message: "Dependency removed", ok: true });
|
|
809
|
+
}
|
|
810
|
+
default:
|
|
811
|
+
return err(`Unknown action "${action}".`);
|
|
812
|
+
}
|
|
813
|
+
},
|
|
814
|
+
});
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
818
|
+
// META-TOOL 4: OFIERE_SCHEDULE_OPS — Calendar & Scheduler Events
|
|
819
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
820
|
+
|
|
821
|
+
function registerScheduleOps(
|
|
822
|
+
api: any,
|
|
823
|
+
supabase: SupabaseClient,
|
|
824
|
+
userId: string,
|
|
825
|
+
): void {
|
|
826
|
+
api.registerTool({
|
|
827
|
+
name: "OFIERE_SCHEDULE_OPS",
|
|
828
|
+
label: "Ofiere Schedule Operations",
|
|
829
|
+
description:
|
|
830
|
+
`Manage calendar events and schedule tasks on the timeline.\n\n` +
|
|
831
|
+
`Actions:\n` +
|
|
832
|
+
`- "list": List events. Optional: start_date, end_date, agent_id\n` +
|
|
833
|
+
`- "create": Schedule an event. Required: title, scheduled_date. Optional: task_id, agent_id, scheduled_time, duration_minutes, recurrence_type, recurrence_interval, color, priority\n` +
|
|
834
|
+
`- "update": Update event. Required: id. Optional: title, scheduled_date, scheduled_time, duration_minutes, status, recurrence_type\n` +
|
|
835
|
+
`- "delete": Remove event. Required: id\n` +
|
|
836
|
+
`recurrence_type: none, hourly, daily, weekly, monthly\n` +
|
|
837
|
+
`priority: 0=low, 1=medium, 2=high, 3=critical`,
|
|
838
|
+
parameters: {
|
|
839
|
+
type: "object",
|
|
840
|
+
required: ["action"],
|
|
841
|
+
properties: {
|
|
842
|
+
action: { type: "string", enum: ["list", "create", "update", "delete"] },
|
|
843
|
+
id: { type: "string", description: "Event ID" },
|
|
844
|
+
title: { type: "string", description: "Event title" },
|
|
845
|
+
description: { type: "string" },
|
|
846
|
+
task_id: { type: "string", description: "Link to a task" },
|
|
847
|
+
agent_id: { type: "string", description: "Assigned agent" },
|
|
848
|
+
scheduled_date: { type: "string", description: "Date (YYYY-MM-DD)" },
|
|
849
|
+
scheduled_time: { type: "string", description: "Time (HH:MM)" },
|
|
850
|
+
start_date: { type: "string", description: "List filter: start (YYYY-MM-DD)" },
|
|
851
|
+
end_date: { type: "string", description: "List filter: end (YYYY-MM-DD)" },
|
|
852
|
+
duration_minutes: { type: "number", description: "Duration in minutes (default 30)" },
|
|
853
|
+
recurrence_type: { type: "string", enum: ["none", "hourly", "daily", "weekly", "monthly"] },
|
|
854
|
+
recurrence_interval: { type: "number", description: "Repeat every N periods" },
|
|
855
|
+
color: { type: "string", description: "Hex color" },
|
|
856
|
+
priority: { type: "number", description: "0-3" },
|
|
857
|
+
status: { type: "string", enum: ["scheduled", "completed", "cancelled"] },
|
|
858
|
+
},
|
|
859
|
+
},
|
|
860
|
+
async execute(_id: string, params: Record<string, unknown>) {
|
|
861
|
+
const action = params.action as string;
|
|
862
|
+
switch (action) {
|
|
863
|
+
case "list": {
|
|
864
|
+
let q = supabase.from("scheduler_events").select("*").eq("user_id", userId)
|
|
865
|
+
.order("scheduled_date", { ascending: true });
|
|
866
|
+
if (params.start_date) q = q.gte("scheduled_date", params.start_date as string);
|
|
867
|
+
if (params.end_date) q = q.lte("scheduled_date", params.end_date as string);
|
|
868
|
+
if (params.agent_id) q = q.eq("agent_id", params.agent_id as string);
|
|
869
|
+
const { data, error } = await q;
|
|
870
|
+
if (error) return err(error.message);
|
|
871
|
+
return ok({ events: data || [], count: (data || []).length });
|
|
872
|
+
}
|
|
873
|
+
case "create": {
|
|
874
|
+
if (!params.title || !params.scheduled_date) return err("Missing required: title, scheduled_date");
|
|
875
|
+
const evtId = crypto.randomUUID();
|
|
876
|
+
const priorityMap: Record<string, number> = { low: 0, medium: 1, high: 2, critical: 3 };
|
|
877
|
+
const pVal = typeof params.priority === "number" ? params.priority
|
|
878
|
+
: priorityMap[String(params.priority || "").toLowerCase()] ?? 0;
|
|
879
|
+
const insertData: Record<string, any> = {
|
|
880
|
+
id: evtId,
|
|
881
|
+
user_id: userId,
|
|
882
|
+
task_id: (params.task_id as string) || null,
|
|
883
|
+
agent_id: (params.agent_id as string) || null,
|
|
884
|
+
title: params.title,
|
|
885
|
+
description: (params.description as string) || null,
|
|
886
|
+
scheduled_date: params.scheduled_date,
|
|
887
|
+
scheduled_time: (params.scheduled_time as string) || null,
|
|
888
|
+
duration_minutes: (params.duration_minutes as number) || 30,
|
|
889
|
+
recurrence_type: (params.recurrence_type as string) || "none",
|
|
890
|
+
recurrence_interval: (params.recurrence_interval as number) || 1,
|
|
891
|
+
status: "scheduled",
|
|
892
|
+
run_count: 0,
|
|
893
|
+
color: (params.color as string) || null,
|
|
894
|
+
priority: pVal,
|
|
895
|
+
};
|
|
896
|
+
const { error } = await supabase.from("scheduler_events").insert(insertData);
|
|
897
|
+
if (error) return err(error.message);
|
|
898
|
+
return ok({ message: `Event "${params.title}" scheduled for ${params.scheduled_date}`, id: evtId });
|
|
899
|
+
}
|
|
900
|
+
case "update": {
|
|
901
|
+
if (!params.id) return err("Missing required: id");
|
|
902
|
+
const upd: Record<string, any> = { updated_at: new Date().toISOString() };
|
|
903
|
+
for (const f of ["title", "description", "scheduled_date", "scheduled_time", "duration_minutes",
|
|
904
|
+
"recurrence_type", "recurrence_interval", "status", "color", "priority", "agent_id"]) {
|
|
905
|
+
if ((params as any)[f] !== undefined) upd[f] = (params as any)[f];
|
|
906
|
+
}
|
|
907
|
+
const { error } = await supabase.from("scheduler_events").update(upd).eq("id", params.id);
|
|
908
|
+
if (error) return err(error.message);
|
|
909
|
+
return ok({ message: "Event updated", ok: true });
|
|
910
|
+
}
|
|
911
|
+
case "delete": {
|
|
912
|
+
if (!params.id) return err("Missing required: id");
|
|
913
|
+
const { error } = await supabase.from("scheduler_events").delete().eq("id", params.id);
|
|
914
|
+
if (error) return err(error.message);
|
|
915
|
+
return ok({ message: "Event deleted", ok: true });
|
|
916
|
+
}
|
|
917
|
+
default:
|
|
918
|
+
return err(`Unknown action "${action}".`);
|
|
919
|
+
}
|
|
920
|
+
},
|
|
921
|
+
});
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
925
|
+
// META-TOOL 5: OFIERE_KNOWLEDGE_OPS — Knowledge Base
|
|
926
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
927
|
+
|
|
928
|
+
function registerKnowledgeOps(
|
|
929
|
+
api: any,
|
|
930
|
+
supabase: SupabaseClient,
|
|
931
|
+
userId: string,
|
|
932
|
+
): void {
|
|
933
|
+
api.registerTool({
|
|
934
|
+
name: "OFIERE_KNOWLEDGE_OPS",
|
|
935
|
+
label: "Ofiere Knowledge Operations",
|
|
936
|
+
description:
|
|
937
|
+
`Search, add, and manage knowledge documents. This is the long-term memory system.\n\n` +
|
|
938
|
+
`Actions:\n` +
|
|
939
|
+
`- "search": Search docs by keyword. Required: query. Optional: limit\n` +
|
|
940
|
+
`- "list": List recent docs. Optional: page, page_size, search\n` +
|
|
941
|
+
`- "create": Add a document. Required: file_name. Optional: content, text, source, source_type, author, credibility_tier\n` +
|
|
942
|
+
`- "update": Edit a document. Required: id. Optional: file_name, content, text, source, source_type, author\n` +
|
|
943
|
+
`- "delete": Remove a document. Required: id`,
|
|
944
|
+
parameters: {
|
|
945
|
+
type: "object",
|
|
946
|
+
required: ["action"],
|
|
947
|
+
properties: {
|
|
948
|
+
action: { type: "string", enum: ["search", "list", "create", "update", "delete"] },
|
|
949
|
+
id: { type: "string", description: "Document ID" },
|
|
950
|
+
query: { type: "string", description: "Search query" },
|
|
951
|
+
file_name: { type: "string", description: "Document name" },
|
|
952
|
+
content: { type: "string", description: "Raw content" },
|
|
953
|
+
text: { type: "string", description: "Processed text" },
|
|
954
|
+
source: { type: "string", description: "Source URL or reference" },
|
|
955
|
+
source_type: { type: "string", description: "e.g. web, pdf, manual" },
|
|
956
|
+
author: { type: "string", description: "Author name" },
|
|
957
|
+
credibility_tier: { type: "string", description: "Credibility level" },
|
|
958
|
+
page: { type: "number", description: "Page number (default 1)" },
|
|
959
|
+
page_size: { type: "number", description: "Results per page (default 20)" },
|
|
960
|
+
search: { type: "string", description: "Filter for list action" },
|
|
961
|
+
limit: { type: "number", description: "Max results for search" },
|
|
962
|
+
},
|
|
963
|
+
},
|
|
964
|
+
async execute(_id: string, params: Record<string, unknown>) {
|
|
965
|
+
const action = params.action as string;
|
|
966
|
+
switch (action) {
|
|
967
|
+
case "search": {
|
|
968
|
+
if (!params.query) return err("Missing required: query");
|
|
969
|
+
const lim = (params.limit as number) || 20;
|
|
970
|
+
const searchTerm = `%${params.query}%`;
|
|
971
|
+
const { data, error } = await supabase
|
|
972
|
+
.from("knowledge_documents")
|
|
973
|
+
.select("id, file_name, file_type, content, source, source_type, author, credibility_tier")
|
|
974
|
+
.or(`file_name.ilike.${searchTerm},content.ilike.${searchTerm},author.ilike.${searchTerm},source.ilike.${searchTerm}`)
|
|
975
|
+
.order("created_at", { ascending: false })
|
|
976
|
+
.limit(lim);
|
|
977
|
+
if (error) return err(error.message);
|
|
978
|
+
return ok({ documents: data || [], count: (data || []).length, query: params.query });
|
|
979
|
+
}
|
|
980
|
+
case "list": {
|
|
981
|
+
const page = Math.max(1, (params.page as number) || 1);
|
|
982
|
+
const pageSize = Math.min(100, Math.max(1, (params.page_size as number) || 20));
|
|
983
|
+
const from = (page - 1) * pageSize;
|
|
984
|
+
const to = from + pageSize - 1;
|
|
985
|
+
let q = supabase.from("knowledge_documents")
|
|
986
|
+
.select("id, file_name, file_type, source, source_type, author, credibility_tier, created_at", { count: "exact" })
|
|
987
|
+
.order("created_at", { ascending: false })
|
|
988
|
+
.range(from, to);
|
|
989
|
+
if (params.search) {
|
|
990
|
+
const s = `%${params.search}%`;
|
|
991
|
+
q = q.or(`file_name.ilike.${s},content.ilike.${s},author.ilike.${s}`);
|
|
992
|
+
}
|
|
993
|
+
const { data, count, error } = await q;
|
|
994
|
+
if (error) return err(error.message);
|
|
995
|
+
return ok({ documents: data || [], total: count || 0, page, page_size: pageSize });
|
|
996
|
+
}
|
|
997
|
+
case "create": {
|
|
998
|
+
if (!params.file_name) return err("Missing required: file_name");
|
|
999
|
+
const docId = crypto.randomUUID();
|
|
1000
|
+
const { error } = await supabase.from("knowledge_documents").insert({
|
|
1001
|
+
id: docId,
|
|
1002
|
+
user_id: userId,
|
|
1003
|
+
file_name: params.file_name,
|
|
1004
|
+
file_type: (params.file_type as string) || null,
|
|
1005
|
+
content: (params.content as string) || null,
|
|
1006
|
+
text: (params.text as string) || null,
|
|
1007
|
+
source: (params.source as string) || null,
|
|
1008
|
+
source_type: (params.source_type as string) || null,
|
|
1009
|
+
author: (params.author as string) || null,
|
|
1010
|
+
credibility_tier: (params.credibility_tier as string) || null,
|
|
1011
|
+
size_bytes: params.content ? new TextEncoder().encode(params.content as string).length : 0,
|
|
1012
|
+
indexed: false,
|
|
1013
|
+
});
|
|
1014
|
+
if (error) return err(error.message);
|
|
1015
|
+
return ok({ message: `Knowledge doc "${params.file_name}" created`, id: docId });
|
|
1016
|
+
}
|
|
1017
|
+
case "update": {
|
|
1018
|
+
if (!params.id) return err("Missing required: id");
|
|
1019
|
+
const allowed = ["file_name", "file_type", "content", "text", "source", "source_type", "author", "credibility_tier"];
|
|
1020
|
+
const upd: Record<string, any> = {};
|
|
1021
|
+
for (const k of allowed) if ((params as any)[k] !== undefined) upd[k] = (params as any)[k];
|
|
1022
|
+
if (Object.keys(upd).length === 0) return err("No valid fields to update");
|
|
1023
|
+
const { error } = await supabase.from("knowledge_documents").update(upd).eq("id", params.id);
|
|
1024
|
+
if (error) return err(error.message);
|
|
1025
|
+
return ok({ message: "Document updated", ok: true });
|
|
1026
|
+
}
|
|
1027
|
+
case "delete": {
|
|
1028
|
+
if (!params.id) return err("Missing required: id");
|
|
1029
|
+
const { error } = await supabase.from("knowledge_documents").delete().eq("id", params.id);
|
|
1030
|
+
if (error) return err(error.message);
|
|
1031
|
+
return ok({ message: "Document deleted", ok: true });
|
|
1032
|
+
}
|
|
1033
|
+
default:
|
|
1034
|
+
return err(`Unknown action "${action}".`);
|
|
1035
|
+
}
|
|
1036
|
+
},
|
|
1037
|
+
});
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
1041
|
+
// META-TOOL 6: OFIERE_WORKFLOW_OPS — Workflow Management & Execution
|
|
1042
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
1043
|
+
|
|
1044
|
+
function registerWorkflowOps(
|
|
1045
|
+
api: any,
|
|
1046
|
+
supabase: SupabaseClient,
|
|
1047
|
+
userId: string,
|
|
1048
|
+
): void {
|
|
1049
|
+
api.registerTool({
|
|
1050
|
+
name: "OFIERE_WORKFLOW_OPS",
|
|
1051
|
+
label: "Ofiere Workflow Operations",
|
|
1052
|
+
description:
|
|
1053
|
+
`Manage and trigger automated workflows.\n\n` +
|
|
1054
|
+
`Actions:\n` +
|
|
1055
|
+
`- "list": List all workflows. Optional: status\n` +
|
|
1056
|
+
`- "get": Get workflow details. Required: id\n` +
|
|
1057
|
+
`- "create": Create a workflow. Required: name. Optional: description, steps, schedule, status\n` +
|
|
1058
|
+
`- "list_runs": List recent runs. Required: workflow_id. Optional: limit\n` +
|
|
1059
|
+
`- "trigger": Start a workflow run. Required: workflow_id`,
|
|
1060
|
+
parameters: {
|
|
1061
|
+
type: "object",
|
|
1062
|
+
required: ["action"],
|
|
1063
|
+
properties: {
|
|
1064
|
+
action: { type: "string", enum: ["list", "get", "create", "list_runs", "trigger"] },
|
|
1065
|
+
id: { type: "string", description: "Workflow ID" },
|
|
1066
|
+
workflow_id: { type: "string", description: "Workflow ID for runs/trigger" },
|
|
1067
|
+
name: { type: "string", description: "Workflow name" },
|
|
1068
|
+
description: { type: "string" },
|
|
1069
|
+
steps: { type: "array", items: { type: "object" }, description: "Workflow step definitions" },
|
|
1070
|
+
schedule: { type: "string", description: "Cron expression or schedule" },
|
|
1071
|
+
status: { type: "string", enum: ["draft", "active", "paused", "archived"] },
|
|
1072
|
+
limit: { type: "number", description: "Max results" },
|
|
1073
|
+
},
|
|
1074
|
+
},
|
|
1075
|
+
async execute(_id: string, params: Record<string, unknown>) {
|
|
1076
|
+
const action = params.action as string;
|
|
1077
|
+
switch (action) {
|
|
1078
|
+
case "list": {
|
|
1079
|
+
let q = supabase.from("workflows").select("*").eq("user_id", userId).order("updated_at", { ascending: false });
|
|
1080
|
+
if (params.status) q = q.eq("status", params.status as string);
|
|
1081
|
+
const { data, error } = await q;
|
|
1082
|
+
if (error) return err(error.message);
|
|
1083
|
+
return ok({ workflows: data || [], count: (data || []).length });
|
|
1084
|
+
}
|
|
1085
|
+
case "get": {
|
|
1086
|
+
const wfId = (params.id || params.workflow_id) as string;
|
|
1087
|
+
if (!wfId) return err("Missing required: id");
|
|
1088
|
+
const { data, error } = await supabase.from("workflows").select("*").eq("id", wfId).eq("user_id", userId).single();
|
|
1089
|
+
if (error) return err(error.message);
|
|
1090
|
+
return ok({ workflow: data });
|
|
1091
|
+
}
|
|
1092
|
+
case "create": {
|
|
1093
|
+
if (!params.name) return err("Missing required: name");
|
|
1094
|
+
const wfId = crypto.randomUUID();
|
|
1095
|
+
const stepsWithIds = ((params.steps as any[]) || []).map((s: any, i: number) => ({
|
|
1096
|
+
...s, id: s.id || `step-${i}`,
|
|
1097
|
+
}));
|
|
1098
|
+
const { data, error } = await supabase.from("workflows").insert({
|
|
1099
|
+
id: wfId, user_id: userId,
|
|
1100
|
+
name: params.name,
|
|
1101
|
+
description: (params.description as string) || null,
|
|
1102
|
+
steps: stepsWithIds,
|
|
1103
|
+
schedule: (params.schedule as string) || null,
|
|
1104
|
+
status: (params.status as string) || "draft",
|
|
1105
|
+
nodes: [], edges: [], definition_version: 1,
|
|
1106
|
+
}).select().single();
|
|
1107
|
+
if (error) return err(error.message);
|
|
1108
|
+
return ok({ message: `Workflow "${params.name}" created`, workflow: data });
|
|
1109
|
+
}
|
|
1110
|
+
case "list_runs": {
|
|
1111
|
+
const wfId = (params.workflow_id || params.id) as string;
|
|
1112
|
+
if (!wfId) return err("Missing required: workflow_id");
|
|
1113
|
+
const { data, error } = await supabase.from("workflow_runs").select("*")
|
|
1114
|
+
.eq("workflow_id", wfId)
|
|
1115
|
+
.order("created_at", { ascending: false })
|
|
1116
|
+
.limit((params.limit as number) || 20);
|
|
1117
|
+
if (error) return err(error.message);
|
|
1118
|
+
return ok({ runs: data || [], count: (data || []).length });
|
|
1119
|
+
}
|
|
1120
|
+
case "trigger": {
|
|
1121
|
+
const wfId = (params.workflow_id || params.id) as string;
|
|
1122
|
+
if (!wfId) return err("Missing required: workflow_id");
|
|
1123
|
+
const runId = crypto.randomUUID();
|
|
1124
|
+
const { error } = await supabase.from("workflow_runs").insert({
|
|
1125
|
+
id: runId,
|
|
1126
|
+
workflow_id: wfId,
|
|
1127
|
+
status: "running",
|
|
1128
|
+
started_at: new Date().toISOString(),
|
|
1129
|
+
trigger_type: "agent",
|
|
1130
|
+
});
|
|
1131
|
+
if (error) return err(error.message);
|
|
1132
|
+
return ok({ message: `Workflow run triggered`, run_id: runId, workflow_id: wfId });
|
|
1133
|
+
}
|
|
1134
|
+
default:
|
|
1135
|
+
return err(`Unknown action "${action}".`);
|
|
1136
|
+
}
|
|
1137
|
+
},
|
|
1138
|
+
});
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
1142
|
+
// META-TOOL 7: OFIERE_NOTIFY_OPS — Notifications
|
|
1143
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
1144
|
+
|
|
1145
|
+
function registerNotifyOps(
|
|
1146
|
+
api: any,
|
|
1147
|
+
supabase: SupabaseClient,
|
|
1148
|
+
userId: string,
|
|
1149
|
+
): void {
|
|
1150
|
+
api.registerTool({
|
|
1151
|
+
name: "OFIERE_NOTIFY_OPS",
|
|
1152
|
+
label: "Ofiere Notification Operations",
|
|
1153
|
+
description:
|
|
1154
|
+
`Read and manage notifications.\n\n` +
|
|
1155
|
+
`Actions:\n` +
|
|
1156
|
+
`- "list": List notifications. Optional: unread_only (true/false), limit\n` +
|
|
1157
|
+
`- "mark_read": Mark one as read. Required: id\n` +
|
|
1158
|
+
`- "mark_all_read": Mark all as read\n` +
|
|
1159
|
+
`- "delete": Delete a notification. Required: id`,
|
|
1160
|
+
parameters: {
|
|
1161
|
+
type: "object",
|
|
1162
|
+
required: ["action"],
|
|
1163
|
+
properties: {
|
|
1164
|
+
action: { type: "string", enum: ["list", "mark_read", "mark_all_read", "delete"] },
|
|
1165
|
+
id: { type: "string", description: "Notification ID" },
|
|
1166
|
+
unread_only: { type: "boolean", description: "Only show unread" },
|
|
1167
|
+
limit: { type: "number", description: "Max results (default 50)" },
|
|
1168
|
+
},
|
|
1169
|
+
},
|
|
1170
|
+
async execute(_id: string, params: Record<string, unknown>) {
|
|
1171
|
+
const action = params.action as string;
|
|
1172
|
+
switch (action) {
|
|
1173
|
+
case "list": {
|
|
1174
|
+
let q = supabase.from("notifications").select("*")
|
|
1175
|
+
.order("created_at", { ascending: false })
|
|
1176
|
+
.limit((params.limit as number) || 50);
|
|
1177
|
+
if (params.unread_only === true) q = q.eq("read", false);
|
|
1178
|
+
const { data, error } = await q;
|
|
1179
|
+
if (error) return err(error.message);
|
|
1180
|
+
const unread = (data || []).filter((n: any) => !n.read).length;
|
|
1181
|
+
return ok({ notifications: data || [], count: (data || []).length, unread_count: unread });
|
|
1182
|
+
}
|
|
1183
|
+
case "mark_read": {
|
|
1184
|
+
if (!params.id) return err("Missing required: id");
|
|
1185
|
+
const { error } = await supabase.from("notifications").update({ read: true }).eq("id", params.id);
|
|
1186
|
+
if (error) return err(error.message);
|
|
1187
|
+
return ok({ message: "Notification marked as read", ok: true });
|
|
1188
|
+
}
|
|
1189
|
+
case "mark_all_read": {
|
|
1190
|
+
const { error } = await supabase.from("notifications").update({ read: true }).eq("read", false);
|
|
1191
|
+
if (error) return err(error.message);
|
|
1192
|
+
return ok({ message: "All notifications marked as read", ok: true });
|
|
1193
|
+
}
|
|
1194
|
+
case "delete": {
|
|
1195
|
+
if (!params.id) return err("Missing required: id");
|
|
1196
|
+
const { error } = await supabase.from("notifications").delete().eq("id", params.id);
|
|
1197
|
+
if (error) return err(error.message);
|
|
1198
|
+
return ok({ message: "Notification deleted", ok: true });
|
|
1199
|
+
}
|
|
1200
|
+
default:
|
|
1201
|
+
return err(`Unknown action "${action}".`);
|
|
1202
|
+
}
|
|
1203
|
+
},
|
|
1204
|
+
});
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
1208
|
+
// META-TOOL 8: OFIERE_MEMORY_OPS — Conversations & Knowledge Fragments
|
|
1209
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
1210
|
+
|
|
1211
|
+
function registerMemoryOps(
|
|
1212
|
+
api: any,
|
|
1213
|
+
supabase: SupabaseClient,
|
|
1214
|
+
userId: string,
|
|
1215
|
+
): void {
|
|
1216
|
+
api.registerTool({
|
|
1217
|
+
name: "OFIERE_MEMORY_OPS",
|
|
1218
|
+
label: "Ofiere Memory Operations",
|
|
1219
|
+
description:
|
|
1220
|
+
`Access conversation history and knowledge memory.\n\n` +
|
|
1221
|
+
`Actions:\n` +
|
|
1222
|
+
`- "list_conversations": List recent conversations. Optional: agent_id, limit\n` +
|
|
1223
|
+
`- "get_messages": Get messages from a conversation. Required: conversation_id. Optional: limit\n` +
|
|
1224
|
+
`- "search_messages": Search all messages. Required: query. Optional: agent_id, limit\n` +
|
|
1225
|
+
`- "add_knowledge": Store a knowledge fragment. Required: agent_id, content, source. Optional: tags, importance\n` +
|
|
1226
|
+
`- "search_knowledge": Search knowledge. Required: agent_id, query. Optional: limit`,
|
|
1227
|
+
parameters: {
|
|
1228
|
+
type: "object",
|
|
1229
|
+
required: ["action"],
|
|
1230
|
+
properties: {
|
|
1231
|
+
action: { type: "string", enum: ["list_conversations", "get_messages", "search_messages", "add_knowledge", "search_knowledge"] },
|
|
1232
|
+
conversation_id: { type: "string" },
|
|
1233
|
+
agent_id: { type: "string" },
|
|
1234
|
+
query: { type: "string", description: "Search query" },
|
|
1235
|
+
content: { type: "string", description: "Knowledge content to store" },
|
|
1236
|
+
source: { type: "string", description: "Source of knowledge" },
|
|
1237
|
+
tags: { type: "array", items: { type: "string" } },
|
|
1238
|
+
importance: { type: "number", description: "1-10 importance scale" },
|
|
1239
|
+
limit: { type: "number", description: "Max results" },
|
|
1240
|
+
},
|
|
1241
|
+
},
|
|
1242
|
+
async execute(_id: string, params: Record<string, unknown>) {
|
|
1243
|
+
const action = params.action as string;
|
|
1244
|
+
switch (action) {
|
|
1245
|
+
case "list_conversations": {
|
|
1246
|
+
let q = supabase.from("conversations")
|
|
1247
|
+
.select("id, agent_id, title, created_at, updated_at")
|
|
1248
|
+
.eq("user_id", userId)
|
|
1249
|
+
.order("updated_at", { ascending: false })
|
|
1250
|
+
.limit((params.limit as number) || 20);
|
|
1251
|
+
if (params.agent_id) q = q.eq("agent_id", params.agent_id as string);
|
|
1252
|
+
const { data, error } = await q;
|
|
1253
|
+
if (error) return err(error.message);
|
|
1254
|
+
return ok({ conversations: data || [], count: (data || []).length });
|
|
1255
|
+
}
|
|
1256
|
+
case "get_messages": {
|
|
1257
|
+
if (!params.conversation_id) return err("Missing required: conversation_id");
|
|
1258
|
+
const { data, error } = await supabase.from("conversation_messages")
|
|
1259
|
+
.select("id, role, content, created_at")
|
|
1260
|
+
.eq("conversation_id", params.conversation_id as string)
|
|
1261
|
+
.order("created_at", { ascending: true })
|
|
1262
|
+
.limit((params.limit as number) || 100);
|
|
1263
|
+
if (error) return err(error.message);
|
|
1264
|
+
return ok({ messages: data || [], count: (data || []).length });
|
|
1265
|
+
}
|
|
1266
|
+
case "search_messages": {
|
|
1267
|
+
if (!params.query) return err("Missing required: query");
|
|
1268
|
+
const searchTerm = `%${params.query}%`;
|
|
1269
|
+
let q = supabase.from("conversation_messages")
|
|
1270
|
+
.select("id, conversation_id, role, content, created_at")
|
|
1271
|
+
.ilike("content", searchTerm)
|
|
1272
|
+
.order("created_at", { ascending: false })
|
|
1273
|
+
.limit((params.limit as number) || 20);
|
|
1274
|
+
const { data, error } = await q;
|
|
1275
|
+
if (error) return err(error.message);
|
|
1276
|
+
return ok({ messages: data || [], count: (data || []).length, query: params.query });
|
|
1277
|
+
}
|
|
1278
|
+
case "add_knowledge": {
|
|
1279
|
+
if (!params.agent_id || !params.content || !params.source) return err("Missing required: agent_id, content, source");
|
|
1280
|
+
const fragId = crypto.randomUUID();
|
|
1281
|
+
const { error } = await supabase.from("knowledge_fragments").insert({
|
|
1282
|
+
id: fragId,
|
|
1283
|
+
agent_id: params.agent_id,
|
|
1284
|
+
content: params.content,
|
|
1285
|
+
source: params.source,
|
|
1286
|
+
tags: (params.tags as string[]) || [],
|
|
1287
|
+
importance: (params.importance as number) || 5,
|
|
1288
|
+
});
|
|
1289
|
+
if (error) return err(error.message);
|
|
1290
|
+
return ok({ message: "Knowledge stored", id: fragId });
|
|
1291
|
+
}
|
|
1292
|
+
case "search_knowledge": {
|
|
1293
|
+
if (!params.agent_id || !params.query) return err("Missing required: agent_id, query");
|
|
1294
|
+
const searchTerm = `%${params.query}%`;
|
|
1295
|
+
const { data, error } = await supabase.from("knowledge_fragments")
|
|
1296
|
+
.select("id, content, source, tags, importance, created_at")
|
|
1297
|
+
.eq("agent_id", params.agent_id as string)
|
|
1298
|
+
.ilike("content", searchTerm)
|
|
1299
|
+
.order("importance", { ascending: false })
|
|
1300
|
+
.limit((params.limit as number) || 20);
|
|
1301
|
+
if (error) return err(error.message);
|
|
1302
|
+
return ok({ fragments: data || [], count: (data || []).length });
|
|
1303
|
+
}
|
|
1304
|
+
default:
|
|
1305
|
+
return err(`Unknown action "${action}".`);
|
|
1306
|
+
}
|
|
1307
|
+
},
|
|
1308
|
+
});
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1311
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
1312
|
+
// META-TOOL 9: OFIERE_PROMPT_OPS — System Prompt Chunk Management
|
|
1313
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
1314
|
+
|
|
1315
|
+
function registerPromptOps(
|
|
1316
|
+
api: any,
|
|
1317
|
+
supabase: SupabaseClient,
|
|
1318
|
+
userId: string,
|
|
1319
|
+
): void {
|
|
1320
|
+
api.registerTool({
|
|
1321
|
+
name: "OFIERE_PROMPT_OPS",
|
|
1322
|
+
label: "Ofiere Prompt Operations",
|
|
1323
|
+
description:
|
|
1324
|
+
`Manage system prompt instruction chunks. These are the building blocks of agent behavior.\n\n` +
|
|
1325
|
+
`Actions:\n` +
|
|
1326
|
+
`- "list": List all prompt chunks. Optional: agent_id\n` +
|
|
1327
|
+
`- "get": Get a specific chunk. Required: id\n` +
|
|
1328
|
+
`- "create": Create a new chunk. Required: label, content. Optional: agent_id, sort_order\n` +
|
|
1329
|
+
`- "update": Update a chunk. Required: id. Optional: label, content, enabled, sort_order\n` +
|
|
1330
|
+
`- "delete": Delete a chunk. Required: id`,
|
|
1331
|
+
parameters: {
|
|
1332
|
+
type: "object",
|
|
1333
|
+
required: ["action"],
|
|
1334
|
+
properties: {
|
|
1335
|
+
action: { type: "string", enum: ["list", "get", "create", "update", "delete"] },
|
|
1336
|
+
id: { type: "string", description: "Chunk ID" },
|
|
1337
|
+
label: { type: "string", description: "Chunk label/name" },
|
|
1338
|
+
content: { type: "string", description: "Prompt chunk content" },
|
|
1339
|
+
agent_id: { type: "string", description: "Associate with specific agent" },
|
|
1340
|
+
enabled: { type: "boolean", description: "Whether chunk is active" },
|
|
1341
|
+
sort_order: { type: "number", description: "Display order" },
|
|
1342
|
+
},
|
|
1343
|
+
},
|
|
1344
|
+
async execute(_id: string, params: Record<string, unknown>) {
|
|
1345
|
+
const action = params.action as string;
|
|
1346
|
+
switch (action) {
|
|
1347
|
+
case "list": {
|
|
1348
|
+
let q = supabase.from("prompt_chunks").select("*").eq("user_id", userId).order("sort_order");
|
|
1349
|
+
if (params.agent_id) q = q.eq("agent_id", params.agent_id as string);
|
|
1350
|
+
const { data, error } = await q;
|
|
1351
|
+
if (error) return err(error.message);
|
|
1352
|
+
return ok({ chunks: data || [], count: (data || []).length });
|
|
1353
|
+
}
|
|
1354
|
+
case "get": {
|
|
1355
|
+
if (!params.id) return err("Missing required: id");
|
|
1356
|
+
const { data, error } = await supabase.from("prompt_chunks").select("*").eq("id", params.id).single();
|
|
1357
|
+
if (error) return err(error.message);
|
|
1358
|
+
return ok({ chunk: data });
|
|
1359
|
+
}
|
|
1360
|
+
case "create": {
|
|
1361
|
+
if (!params.label || !params.content) return err("Missing required: label, content");
|
|
1362
|
+
const chunkId = crypto.randomUUID();
|
|
1363
|
+
const { data, error } = await supabase.from("prompt_chunks").insert({
|
|
1364
|
+
id: chunkId,
|
|
1365
|
+
user_id: userId,
|
|
1366
|
+
label: params.label,
|
|
1367
|
+
content: params.content,
|
|
1368
|
+
agent_id: (params.agent_id as string) || null,
|
|
1369
|
+
enabled: true,
|
|
1370
|
+
sort_order: (params.sort_order as number) || 0,
|
|
1371
|
+
}).select().single();
|
|
1372
|
+
if (error) return err(error.message);
|
|
1373
|
+
api.logger?.info?.(`[ofiere] Prompt chunk created: "${params.label}" by agent`);
|
|
1374
|
+
return ok({ message: `Prompt chunk "${params.label}" created`, chunk: data });
|
|
1375
|
+
}
|
|
1376
|
+
case "update": {
|
|
1377
|
+
if (!params.id) return err("Missing required: id");
|
|
1378
|
+
const upd: Record<string, any> = { updated_at: new Date().toISOString() };
|
|
1379
|
+
for (const f of ["label", "content", "enabled", "sort_order", "agent_id"]) {
|
|
1380
|
+
if ((params as any)[f] !== undefined) upd[f] = (params as any)[f];
|
|
1381
|
+
}
|
|
1382
|
+
const { error } = await supabase.from("prompt_chunks").update(upd).eq("id", params.id);
|
|
1383
|
+
if (error) return err(error.message);
|
|
1384
|
+
api.logger?.info?.(`[ofiere] Prompt chunk ${params.id} updated by agent`);
|
|
1385
|
+
return ok({ message: "Prompt chunk updated", ok: true });
|
|
1386
|
+
}
|
|
1387
|
+
case "delete": {
|
|
1388
|
+
if (!params.id) return err("Missing required: id");
|
|
1389
|
+
const { error } = await supabase.from("prompt_chunks").delete().eq("id", params.id);
|
|
1390
|
+
if (error) return err(error.message);
|
|
1391
|
+
api.logger?.info?.(`[ofiere] Prompt chunk ${params.id} deleted by agent`);
|
|
1392
|
+
return ok({ message: "Prompt chunk deleted", ok: true });
|
|
1393
|
+
}
|
|
1394
|
+
default:
|
|
1395
|
+
return err(`Unknown action "${action}".`);
|
|
1396
|
+
}
|
|
1397
|
+
},
|
|
1398
|
+
});
|
|
1399
|
+
}
|
|
1400
|
+
|
|
1401
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
1402
|
+
// Public: Register All Meta-Tools
|
|
1403
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
499
1404
|
// This is the single entry point called by index.ts.
|
|
500
1405
|
// Returns the number of tools registered for dynamic prompt generation.
|
|
501
|
-
//
|
|
502
|
-
// To expand: add new register*Ops() calls here and increment the count.
|
|
503
1406
|
|
|
504
1407
|
export function registerTools(
|
|
505
1408
|
api: any, // OpenClawPluginApi — typed as any to avoid import-path issues at install time
|
|
@@ -512,11 +1415,18 @@ export function registerTools(
|
|
|
512
1415
|
const resolveAgent = createAgentResolver(api, supabase, userId, fallbackAgentId);
|
|
513
1416
|
|
|
514
1417
|
// ── Register each domain meta-tool ──
|
|
515
|
-
registerTaskOps(api, supabase, userId, resolveAgent);
|
|
516
|
-
registerAgentOps(api, supabase, userId, fallbackAgentId);
|
|
1418
|
+
registerTaskOps(api, supabase, userId, resolveAgent); // 1
|
|
1419
|
+
registerAgentOps(api, supabase, userId, fallbackAgentId); // 2
|
|
1420
|
+
registerProjectOps(api, supabase, userId); // 3
|
|
1421
|
+
registerScheduleOps(api, supabase, userId); // 4
|
|
1422
|
+
registerKnowledgeOps(api, supabase, userId); // 5
|
|
1423
|
+
registerWorkflowOps(api, supabase, userId); // 6
|
|
1424
|
+
registerNotifyOps(api, supabase, userId); // 7
|
|
1425
|
+
registerMemoryOps(api, supabase, userId); // 8
|
|
1426
|
+
registerPromptOps(api, supabase, userId); // 9
|
|
517
1427
|
|
|
518
1428
|
// ── Count and log ──
|
|
519
|
-
const toolCount =
|
|
1429
|
+
const toolCount = 9;
|
|
520
1430
|
const callerName = getCallingAgentName(api);
|
|
521
1431
|
const agentLabel = fallbackAgentId || callerName || "auto-detect";
|
|
522
1432
|
api.logger.info(`[ofiere] ${toolCount} meta-tools registered (agent: ${agentLabel})`);
|