ofiere-openclaw-plugin 1.1.1 → 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/README.md +21 -9
- package/index.ts +4 -3
- package/package.json +2 -2
- package/src/prompt.ts +80 -11
- package/src/tools.ts +1223 -281
package/src/tools.ts
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
|
-
// src/tools.ts —
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
// https://docs.openclaw.ai/plugins/building-plugins#registering-agent-tools
|
|
1
|
+
// src/tools.ts — Meta-tool registration for Ofiere PM plugin
|
|
2
|
+
// Architecture: Each meta-tool handles one domain (tasks, agents, etc.)
|
|
3
|
+
// with an "action" parameter that routes to the correct handler.
|
|
5
4
|
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
5
|
+
// To add a new domain:
|
|
6
|
+
// 1. Create a handler function (e.g. registerProjectOps)
|
|
7
|
+
// 2. Add it to the registerAllTools() call at the bottom
|
|
8
|
+
// 3. Update prompt.ts to document the new meta-tool
|
|
9
|
+
//
|
|
10
|
+
// This pattern keeps the tool count low (1 tool per domain)
|
|
11
|
+
// while supporting unlimited operations within each domain.
|
|
8
12
|
|
|
9
13
|
import type { SupabaseClient } from "@supabase/supabase-js";
|
|
10
14
|
import type { OfiereConfig } from "./types.js";
|
|
@@ -100,21 +104,19 @@ export function probeApiForAgentName(api: any, logger?: any): string {
|
|
|
100
104
|
return "";
|
|
101
105
|
}
|
|
102
106
|
|
|
103
|
-
// ───
|
|
107
|
+
// ─── Shared: Agent ID Resolution ─────────────────────────────────────────────
|
|
104
108
|
|
|
105
|
-
|
|
106
|
-
api: any,
|
|
109
|
+
function createAgentResolver(
|
|
110
|
+
api: any,
|
|
107
111
|
supabase: SupabaseClient,
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
const fallbackAgentId = config.agentId; // May be empty — that's fine
|
|
112
|
-
|
|
112
|
+
userId: string,
|
|
113
|
+
fallbackAgentId: string,
|
|
114
|
+
) {
|
|
113
115
|
/**
|
|
114
116
|
* Resolve the agent ID for the calling agent.
|
|
115
117
|
* Priority: explicit param > runtime context > registration-time detection > env var > DB fallback
|
|
116
118
|
*/
|
|
117
|
-
async function resolveAgent(explicitId?: string): Promise<string | null> {
|
|
119
|
+
return async function resolveAgent(explicitId?: string): Promise<string | null> {
|
|
118
120
|
// 1. Explicit agent_id passed by the LLM (e.g. "ivy", "daisy", or a UUID)
|
|
119
121
|
if (explicitId && explicitId.trim()) {
|
|
120
122
|
const trimmed = explicitId.trim();
|
|
@@ -167,327 +169,1267 @@ export function registerTools(
|
|
|
167
169
|
}
|
|
168
170
|
|
|
169
171
|
return null;
|
|
170
|
-
}
|
|
172
|
+
};
|
|
173
|
+
}
|
|
171
174
|
|
|
172
|
-
|
|
175
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
176
|
+
// META-TOOL 1: OFIERE_TASK_OPS — Task Management
|
|
177
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
173
178
|
|
|
179
|
+
function registerTaskOps(
|
|
180
|
+
api: any,
|
|
181
|
+
supabase: SupabaseClient,
|
|
182
|
+
userId: string,
|
|
183
|
+
resolveAgent: (id?: string) => Promise<string | null>,
|
|
184
|
+
): void {
|
|
174
185
|
api.registerTool({
|
|
175
|
-
name: "
|
|
176
|
-
label: "
|
|
186
|
+
name: "OFIERE_TASK_OPS",
|
|
187
|
+
label: "Ofiere Task Operations",
|
|
177
188
|
description:
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
"
|
|
189
|
+
`Manage tasks in the Ofiere PM dashboard. All task operations go through this tool.\n\n` +
|
|
190
|
+
`Actions:\n` +
|
|
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`,
|
|
181
199
|
parameters: {
|
|
182
200
|
type: "object",
|
|
201
|
+
required: ["action"],
|
|
183
202
|
properties: {
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
203
|
+
action: {
|
|
204
|
+
type: "string",
|
|
205
|
+
description: "The operation to perform",
|
|
206
|
+
enum: ["list", "create", "update", "delete"],
|
|
207
|
+
},
|
|
208
|
+
task_id: { type: "string", description: "Task ID (required for update, delete)" },
|
|
209
|
+
title: { type: "string", description: "Task title (required for create)" },
|
|
210
|
+
description: { type: "string", description: "Task description" },
|
|
211
|
+
instructions: { type: "string", description: "Detailed instructions for the agent executing this task" },
|
|
212
|
+
agent_id: {
|
|
213
|
+
type: "string",
|
|
214
|
+
description: "Agent name or ID. Your name to self-assign, 'none' for unassigned.",
|
|
215
|
+
},
|
|
187
216
|
status: {
|
|
188
217
|
type: "string",
|
|
189
|
-
description: "
|
|
218
|
+
description: "Task status",
|
|
190
219
|
enum: ["PENDING", "IN_PROGRESS", "DONE", "FAILED"],
|
|
191
220
|
},
|
|
192
|
-
|
|
221
|
+
priority: { type: "number", description: "Priority: 0=LOW, 1=MEDIUM, 2=HIGH, 3=CRITICAL" },
|
|
222
|
+
progress: { type: "number", description: "Progress percentage 0-100 (update only)" },
|
|
223
|
+
space_id: { type: "string", description: "PM Space ID" },
|
|
224
|
+
folder_id: { type: "string", description: "PM Folder ID" },
|
|
225
|
+
start_date: { type: "string", description: "Start date (ISO 8601)" },
|
|
226
|
+
due_date: { type: "string", description: "Due date (ISO 8601)" },
|
|
227
|
+
tags: {
|
|
228
|
+
type: "array",
|
|
229
|
+
items: { type: "string" },
|
|
230
|
+
description: "Tags for the task",
|
|
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" },
|
|
268
|
+
limit: { type: "number", description: "Max results for list (default 50)" },
|
|
193
269
|
},
|
|
194
270
|
},
|
|
195
271
|
async execute(_id: string, params: Record<string, unknown>) {
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
)
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
const { data, error } = await query;
|
|
213
|
-
if (error) return err(error.message);
|
|
214
|
-
return ok({ tasks: data || [], count: (data || []).length });
|
|
215
|
-
} catch (e) {
|
|
216
|
-
return err(e instanceof Error ? e.message : String(e));
|
|
272
|
+
const action = params.action as string;
|
|
273
|
+
|
|
274
|
+
switch (action) {
|
|
275
|
+
case "list":
|
|
276
|
+
return handleListTasks(supabase, userId, params);
|
|
277
|
+
case "create":
|
|
278
|
+
return handleCreateTask(supabase, userId, resolveAgent, params);
|
|
279
|
+
case "update":
|
|
280
|
+
return handleUpdateTask(supabase, userId, params);
|
|
281
|
+
case "delete":
|
|
282
|
+
return handleDeleteTask(supabase, userId, params);
|
|
283
|
+
default:
|
|
284
|
+
return err(
|
|
285
|
+
`Unknown action "${action}". Valid actions: list, create, update, delete`,
|
|
286
|
+
);
|
|
217
287
|
}
|
|
218
288
|
},
|
|
219
289
|
});
|
|
290
|
+
}
|
|
220
291
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
"
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
292
|
+
// ── Task action handlers ─────────────────────────────────────────────────────
|
|
293
|
+
|
|
294
|
+
async function handleListTasks(
|
|
295
|
+
supabase: SupabaseClient,
|
|
296
|
+
userId: string,
|
|
297
|
+
params: Record<string, unknown>,
|
|
298
|
+
): Promise<ToolResult> {
|
|
299
|
+
try {
|
|
300
|
+
let query = supabase
|
|
301
|
+
.from("tasks")
|
|
302
|
+
.select(
|
|
303
|
+
"id, title, description, status, priority, agent_id, space_id, folder_id, " +
|
|
304
|
+
"start_date, due_date, progress, tags, custom_fields, created_at, updated_at",
|
|
305
|
+
)
|
|
306
|
+
.eq("user_id", userId)
|
|
307
|
+
.order("updated_at", { ascending: false });
|
|
308
|
+
|
|
309
|
+
if (params.space_id) query = query.eq("space_id", params.space_id as string);
|
|
310
|
+
if (params.folder_id) query = query.eq("folder_id", params.folder_id as string);
|
|
311
|
+
if (params.agent_id) query = query.eq("agent_id", params.agent_id as string);
|
|
312
|
+
if (params.status) query = query.eq("status", params.status as string);
|
|
313
|
+
query = query.limit((params.limit as number) || 50);
|
|
314
|
+
|
|
315
|
+
const { data, error } = await query;
|
|
316
|
+
if (error) return err(error.message);
|
|
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 });
|
|
332
|
+
} catch (e) {
|
|
333
|
+
return err(e instanceof Error ? e.message : String(e));
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
async function handleCreateTask(
|
|
338
|
+
supabase: SupabaseClient,
|
|
339
|
+
userId: string,
|
|
340
|
+
resolveAgent: (id?: string) => Promise<string | null>,
|
|
341
|
+
params: Record<string, unknown>,
|
|
342
|
+
): Promise<ToolResult> {
|
|
343
|
+
try {
|
|
344
|
+
if (!params.title) return err("Missing required field: title");
|
|
345
|
+
|
|
346
|
+
const id = `task-${Date.now()}`;
|
|
347
|
+
const now = new Date().toISOString();
|
|
348
|
+
|
|
349
|
+
// Handle explicit "none"/"unassigned"
|
|
350
|
+
const rawAgentId = params.agent_id as string | undefined;
|
|
351
|
+
const isUnassigned =
|
|
352
|
+
rawAgentId &&
|
|
353
|
+
["none", "unassigned", "null", ""].includes(rawAgentId.toLowerCase().trim());
|
|
354
|
+
|
|
355
|
+
const assignee = isUnassigned ? null : await resolveAgent(rawAgentId);
|
|
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
|
+
|
|
387
|
+
const insertData: Record<string, unknown> = {
|
|
388
|
+
id,
|
|
389
|
+
user_id: userId,
|
|
390
|
+
title: params.title,
|
|
391
|
+
description: (params.description as string) || (params.instructions as string) || null,
|
|
392
|
+
agent_id: assignee,
|
|
393
|
+
assignee_type: "agent",
|
|
394
|
+
status: (params.status as string) || "PENDING",
|
|
395
|
+
priority: params.priority !== undefined ? params.priority : 1,
|
|
396
|
+
space_id: (params.space_id as string) || null,
|
|
397
|
+
folder_id: (params.folder_id as string) || null,
|
|
398
|
+
start_date: (params.start_date as string) || null,
|
|
399
|
+
due_date: (params.due_date as string) || null,
|
|
400
|
+
tags: (params.tags as string[]) || [],
|
|
401
|
+
progress: 0,
|
|
402
|
+
sort_order: 0,
|
|
403
|
+
custom_fields: Object.keys(cf).length > 0 ? cf : {},
|
|
404
|
+
created_at: now,
|
|
405
|
+
updated_at: now,
|
|
406
|
+
};
|
|
407
|
+
|
|
408
|
+
const { error } = await supabase.from("tasks").insert(insertData);
|
|
409
|
+
|
|
410
|
+
if (error) {
|
|
411
|
+
if (error.message?.includes("agent_id") || error.message?.includes("foreign key")) {
|
|
412
|
+
insertData.agent_id = null;
|
|
413
|
+
const retry = await supabase.from("tasks").insert(insertData);
|
|
414
|
+
if (retry.error) return err(retry.error.message);
|
|
415
|
+
return ok({
|
|
416
|
+
id,
|
|
417
|
+
message: `Task "${params.title}" created (agent_id "${assignee}" was invalid, assigned to none)`,
|
|
418
|
+
task: insertData,
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
return err(error.message);
|
|
422
|
+
}
|
|
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
|
+
|
|
431
|
+
return ok({
|
|
432
|
+
id,
|
|
433
|
+
message: `Task "${params.title}" created and assigned to ${assignee || "no one"}${extrasStr}`,
|
|
434
|
+
task: insertData,
|
|
435
|
+
});
|
|
436
|
+
} catch (e) {
|
|
437
|
+
return err(e instanceof Error ? e.message : String(e));
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
async function handleUpdateTask(
|
|
442
|
+
supabase: SupabaseClient,
|
|
443
|
+
userId: string,
|
|
444
|
+
params: Record<string, unknown>,
|
|
445
|
+
): Promise<ToolResult> {
|
|
446
|
+
try {
|
|
447
|
+
if (!params.task_id) return err("Missing required field: task_id");
|
|
448
|
+
|
|
449
|
+
const updates: Record<string, unknown> = { updated_at: new Date().toISOString() };
|
|
450
|
+
const fields = [
|
|
451
|
+
"title", "description", "status", "priority", "progress",
|
|
452
|
+
"agent_id", "start_date", "due_date", "tags",
|
|
453
|
+
];
|
|
454
|
+
for (const f of fields) {
|
|
455
|
+
if (params[f] !== undefined) updates[f] = params[f];
|
|
456
|
+
}
|
|
457
|
+
if (params.status === "DONE") updates.completed_at = new Date().toISOString();
|
|
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
|
+
|
|
514
|
+
const { data, error } = await supabase
|
|
515
|
+
.from("tasks")
|
|
516
|
+
.update(updates)
|
|
517
|
+
.eq("id", params.task_id as string)
|
|
518
|
+
.eq("user_id", userId)
|
|
519
|
+
.select("id, title, status, priority, agent_id")
|
|
520
|
+
.single();
|
|
521
|
+
|
|
522
|
+
if (error) return err(error.message);
|
|
523
|
+
return ok({ message: `Task "${data?.title}" updated`, task: data });
|
|
524
|
+
} catch (e) {
|
|
525
|
+
return err(e instanceof Error ? e.message : String(e));
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
async function handleDeleteTask(
|
|
530
|
+
supabase: SupabaseClient,
|
|
531
|
+
userId: string,
|
|
532
|
+
params: Record<string, unknown>,
|
|
533
|
+
): Promise<ToolResult> {
|
|
534
|
+
try {
|
|
535
|
+
if (!params.task_id) return err("Missing required field: task_id");
|
|
536
|
+
const taskId = params.task_id as string;
|
|
537
|
+
|
|
538
|
+
await supabase.from("scheduler_events").delete().eq("task_id", taskId);
|
|
539
|
+
|
|
540
|
+
const { data: subtasks } = await supabase
|
|
541
|
+
.from("tasks")
|
|
542
|
+
.select("id")
|
|
543
|
+
.eq("parent_task_id", taskId)
|
|
544
|
+
.eq("user_id", userId);
|
|
545
|
+
|
|
546
|
+
if (subtasks && subtasks.length > 0) {
|
|
547
|
+
for (const sub of subtasks) {
|
|
548
|
+
await supabase.from("scheduler_events").delete().eq("task_id", sub.id);
|
|
549
|
+
}
|
|
550
|
+
await supabase
|
|
551
|
+
.from("tasks")
|
|
552
|
+
.delete()
|
|
553
|
+
.in("id", subtasks.map((s: { id: string }) => s.id))
|
|
554
|
+
.eq("user_id", userId);
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
const { error } = await supabase
|
|
558
|
+
.from("tasks")
|
|
559
|
+
.delete()
|
|
560
|
+
.eq("id", taskId)
|
|
561
|
+
.eq("user_id", userId);
|
|
562
|
+
|
|
563
|
+
if (error) return err(error.message);
|
|
564
|
+
return ok({ message: `Task ${taskId} deleted`, deleted: true });
|
|
565
|
+
} catch (e) {
|
|
566
|
+
return err(e instanceof Error ? e.message : String(e));
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
571
|
+
// META-TOOL 2: OFIERE_AGENT_OPS — Agent Management
|
|
572
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
573
|
+
|
|
574
|
+
function registerAgentOps(
|
|
575
|
+
api: any,
|
|
576
|
+
supabase: SupabaseClient,
|
|
577
|
+
userId: string,
|
|
578
|
+
fallbackAgentId: string,
|
|
579
|
+
): void {
|
|
580
|
+
api.registerTool({
|
|
581
|
+
name: "OFIERE_AGENT_OPS",
|
|
582
|
+
label: "Ofiere Agent Operations",
|
|
583
|
+
description:
|
|
584
|
+
`Query agents in the Ofiere PM system.\n\n` +
|
|
585
|
+
`Actions:\n` +
|
|
586
|
+
`- "list": List all available agents with their IDs, names, roles, and status. Use this to find the correct agent_id for task assignment.`,
|
|
587
|
+
parameters: {
|
|
588
|
+
type: "object",
|
|
589
|
+
required: ["action"],
|
|
590
|
+
properties: {
|
|
591
|
+
action: {
|
|
592
|
+
type: "string",
|
|
593
|
+
description: "The operation to perform",
|
|
594
|
+
enum: ["list"],
|
|
264
595
|
},
|
|
265
596
|
},
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
597
|
+
},
|
|
598
|
+
async execute(_id: string, params: Record<string, unknown>) {
|
|
599
|
+
const action = params.action as string;
|
|
269
600
|
|
|
270
|
-
|
|
271
|
-
|
|
601
|
+
switch (action) {
|
|
602
|
+
case "list":
|
|
603
|
+
return handleListAgents(api, supabase, userId, fallbackAgentId);
|
|
604
|
+
default:
|
|
605
|
+
return err(`Unknown action "${action}". Valid actions: list`);
|
|
606
|
+
}
|
|
607
|
+
},
|
|
608
|
+
});
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
async function handleListAgents(
|
|
612
|
+
api: any,
|
|
613
|
+
supabase: SupabaseClient,
|
|
614
|
+
userId: string,
|
|
615
|
+
fallbackAgentId: string,
|
|
616
|
+
): Promise<ToolResult> {
|
|
617
|
+
try {
|
|
618
|
+
// Resolve calling agent's ID for the "your_agent_id" hint
|
|
619
|
+
const callerName = getCallingAgentName(api);
|
|
620
|
+
let yourAgentId = fallbackAgentId || "";
|
|
621
|
+
if (callerName && !yourAgentId) {
|
|
622
|
+
try {
|
|
623
|
+
yourAgentId = await resolveAgentId(callerName, userId, supabase);
|
|
624
|
+
} catch { /* ignore */ }
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
const { data, error } = await supabase
|
|
628
|
+
.from("agents")
|
|
629
|
+
.select("id, name, codename, role, status")
|
|
630
|
+
.eq("user_id", userId)
|
|
631
|
+
.order("name");
|
|
632
|
+
|
|
633
|
+
if (error) return err(error.message);
|
|
634
|
+
return ok({
|
|
635
|
+
agents: data || [],
|
|
636
|
+
count: (data || []).length,
|
|
637
|
+
your_agent_id: yourAgentId,
|
|
638
|
+
});
|
|
639
|
+
} catch (e) {
|
|
640
|
+
return err(e instanceof Error ? e.message : String(e));
|
|
641
|
+
}
|
|
642
|
+
}
|
|
272
643
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
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
|
+
}
|
|
278
816
|
|
|
279
|
-
|
|
817
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
818
|
+
// META-TOOL 4: OFIERE_SCHEDULE_OPS — Calendar & Scheduler Events
|
|
819
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
280
820
|
|
|
281
|
-
|
|
282
|
-
|
|
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,
|
|
283
881
|
user_id: userId,
|
|
882
|
+
task_id: (params.task_id as string) || null,
|
|
883
|
+
agent_id: (params.agent_id as string) || null,
|
|
284
884
|
title: params.title,
|
|
285
885
|
description: (params.description as string) || null,
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
progress: 0,
|
|
296
|
-
sort_order: 0,
|
|
297
|
-
custom_fields: {},
|
|
298
|
-
created_at: now,
|
|
299
|
-
updated_at: now,
|
|
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,
|
|
300
895
|
};
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
message: `Task "${params.title}" created (agent_id "${assignee}" was invalid, assigned to none)`,
|
|
312
|
-
task: insertData,
|
|
313
|
-
});
|
|
314
|
-
}
|
|
315
|
-
return err(error.message);
|
|
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];
|
|
316
906
|
}
|
|
317
|
-
|
|
318
|
-
return
|
|
319
|
-
|
|
320
|
-
message: `Task "${params.title}" created and assigned to ${assignee || "no one"}`,
|
|
321
|
-
task: insertData,
|
|
322
|
-
});
|
|
323
|
-
} catch (e) {
|
|
324
|
-
return err(e instanceof Error ? e.message : String(e));
|
|
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 });
|
|
325
910
|
}
|
|
326
|
-
|
|
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
|
+
}
|
|
327
920
|
},
|
|
328
|
-
);
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
},
|
|
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" },
|
|
362
962
|
},
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
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}`);
|
|
374
992
|
}
|
|
375
|
-
|
|
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
|
+
}
|
|
376
1039
|
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
.eq("id", params.task_id as string)
|
|
381
|
-
.eq("user_id", userId)
|
|
382
|
-
.select("id, title, status, priority, agent_id")
|
|
383
|
-
.single();
|
|
1040
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
1041
|
+
// META-TOOL 6: OFIERE_WORKFLOW_OPS — Workflow Management & Execution
|
|
1042
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
384
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;
|
|
385
1082
|
if (error) return err(error.message);
|
|
386
|
-
return ok({
|
|
387
|
-
} catch (e) {
|
|
388
|
-
return err(e instanceof Error ? e.message : String(e));
|
|
1083
|
+
return ok({ workflows: data || [], count: (data || []).length });
|
|
389
1084
|
}
|
|
390
|
-
|
|
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
|
+
}
|
|
391
1137
|
},
|
|
392
|
-
);
|
|
393
|
-
|
|
394
|
-
// ── OFIERE_DELETE_TASK — Optional (destructive side effect) ──────────
|
|
395
|
-
|
|
396
|
-
api.registerTool(
|
|
397
|
-
{
|
|
398
|
-
name: "OFIERE_DELETE_TASK",
|
|
399
|
-
label: "Delete Ofiere Task",
|
|
400
|
-
description:
|
|
401
|
-
"Delete a task from the Ofiere PM dashboard. Also removes subtasks and linked scheduler events.",
|
|
402
|
-
parameters: {
|
|
403
|
-
type: "object",
|
|
404
|
-
required: ["task_id"],
|
|
405
|
-
properties: {
|
|
406
|
-
task_id: { type: "string", description: "The task ID to delete (required)" },
|
|
407
|
-
},
|
|
408
|
-
},
|
|
409
|
-
async execute(_id: string, params: Record<string, unknown>) {
|
|
410
|
-
try {
|
|
411
|
-
if (!params.task_id) return err("Missing required field: task_id");
|
|
412
|
-
const taskId = params.task_id as string;
|
|
413
|
-
|
|
414
|
-
await supabase.from("scheduler_events").delete().eq("task_id", taskId);
|
|
415
|
-
|
|
416
|
-
const { data: subtasks } = await supabase
|
|
417
|
-
.from("tasks")
|
|
418
|
-
.select("id")
|
|
419
|
-
.eq("parent_task_id", taskId)
|
|
420
|
-
.eq("user_id", userId);
|
|
421
|
-
|
|
422
|
-
if (subtasks && subtasks.length > 0) {
|
|
423
|
-
for (const sub of subtasks) {
|
|
424
|
-
await supabase.from("scheduler_events").delete().eq("task_id", sub.id);
|
|
425
|
-
}
|
|
426
|
-
await supabase
|
|
427
|
-
.from("tasks")
|
|
428
|
-
.delete()
|
|
429
|
-
.in("id", subtasks.map((s: { id: string }) => s.id))
|
|
430
|
-
.eq("user_id", userId);
|
|
431
|
-
}
|
|
1138
|
+
});
|
|
1139
|
+
}
|
|
432
1140
|
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
.eq("id", taskId)
|
|
437
|
-
.eq("user_id", userId);
|
|
1141
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
1142
|
+
// META-TOOL 7: OFIERE_NOTIFY_OPS — Notifications
|
|
1143
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
438
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);
|
|
439
1197
|
if (error) return err(error.message);
|
|
440
|
-
return ok({ message:
|
|
441
|
-
} catch (e) {
|
|
442
|
-
return err(e instanceof Error ? e.message : String(e));
|
|
1198
|
+
return ok({ message: "Notification deleted", ok: true });
|
|
443
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" },
|
|
444
1240
|
},
|
|
445
1241
|
},
|
|
446
|
-
|
|
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
|
+
}
|
|
447
1310
|
|
|
448
|
-
|
|
1311
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
1312
|
+
// META-TOOL 9: OFIERE_PROMPT_OPS — System Prompt Chunk Management
|
|
1313
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
449
1314
|
|
|
1315
|
+
function registerPromptOps(
|
|
1316
|
+
api: any,
|
|
1317
|
+
supabase: SupabaseClient,
|
|
1318
|
+
userId: string,
|
|
1319
|
+
): void {
|
|
450
1320
|
api.registerTool({
|
|
451
|
-
name: "
|
|
452
|
-
label: "
|
|
1321
|
+
name: "OFIERE_PROMPT_OPS",
|
|
1322
|
+
label: "Ofiere Prompt Operations",
|
|
453
1323
|
description:
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
"
|
|
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`,
|
|
457
1331
|
parameters: {
|
|
458
1332
|
type: "object",
|
|
459
|
-
|
|
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
|
+
},
|
|
460
1343
|
},
|
|
461
|
-
async execute(_id: string,
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
.from("
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
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}".`);
|
|
486
1396
|
}
|
|
487
1397
|
},
|
|
488
1398
|
});
|
|
1399
|
+
}
|
|
1400
|
+
|
|
1401
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
1402
|
+
// Public: Register All Meta-Tools
|
|
1403
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
1404
|
+
// This is the single entry point called by index.ts.
|
|
1405
|
+
// Returns the number of tools registered for dynamic prompt generation.
|
|
489
1406
|
|
|
1407
|
+
export function registerTools(
|
|
1408
|
+
api: any, // OpenClawPluginApi — typed as any to avoid import-path issues at install time
|
|
1409
|
+
supabase: SupabaseClient,
|
|
1410
|
+
config: OfiereConfig,
|
|
1411
|
+
): number {
|
|
1412
|
+
const userId = config.userId;
|
|
1413
|
+
const fallbackAgentId = config.agentId; // May be empty — that's fine
|
|
1414
|
+
|
|
1415
|
+
const resolveAgent = createAgentResolver(api, supabase, userId, fallbackAgentId);
|
|
1416
|
+
|
|
1417
|
+
// ── Register each domain meta-tool ──
|
|
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
|
|
1427
|
+
|
|
1428
|
+
// ── Count and log ──
|
|
1429
|
+
const toolCount = 9;
|
|
490
1430
|
const callerName = getCallingAgentName(api);
|
|
491
1431
|
const agentLabel = fallbackAgentId || callerName || "auto-detect";
|
|
492
|
-
api.logger.info(`[ofiere]
|
|
1432
|
+
api.logger.info(`[ofiere] ${toolCount} meta-tools registered (agent: ${agentLabel})`);
|
|
1433
|
+
|
|
1434
|
+
return toolCount;
|
|
493
1435
|
}
|