ofiere-openclaw-plugin 4.18.2 → 4.18.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/tools.ts +67 -17
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ofiere-openclaw-plugin",
3
- "version": "4.18.2",
3
+ "version": "4.18.4",
4
4
  "type": "module",
5
5
  "description": "OpenClaw plugin for Ofiere PM - 12 meta-tools covering tasks, agents, projects, scheduling, knowledge, workflows, notifications, memory, prompts, constellation, space file management, and execution plan builder",
6
6
  "keywords": ["openclaw", "ofiere", "project-management", "agents", "plugin"],
package/src/tools.ts CHANGED
@@ -4269,6 +4269,18 @@ async function handleUpdatePlan(supabase: SupabaseClient, userId: string, params
4269
4269
  if (params.nodes !== undefined) {
4270
4270
  const rootNodes = Array.isArray(params.nodes) ? (params.nodes as any[]).map(normalizeNode) : [];
4271
4271
  updates.plan_data = buildPlanJson(params.plan_id as string, (params.name as string) || "Plan", params.description as string | undefined, rootNodes);
4272
+ } else if (params.name !== undefined || params.description !== undefined) {
4273
+ // Sync plan_data inner name/description without replacing nodes
4274
+ const { data: existing } = await supabase.from("pm_plans").select("plan_data").eq("id", params.plan_id as string).eq("user_id", userId);
4275
+ if (existing && existing.length > 0) {
4276
+ try {
4277
+ const pd = JSON.parse(existing[0].plan_data || "{}");
4278
+ if (params.name !== undefined) pd.name = params.name;
4279
+ if (params.description !== undefined) pd.description = params.description;
4280
+ pd.updatedAt = new Date().toISOString();
4281
+ updates.plan_data = JSON.stringify(pd, null, 2);
4282
+ } catch { /* plan_data parse failure — skip inner sync */ }
4283
+ }
4272
4284
  }
4273
4285
  const { data, error } = await supabase.from("pm_plans").update(updates).eq("id", params.plan_id as string).eq("user_id", userId).select("id");
4274
4286
  if (error) return err(error.message);
@@ -4334,15 +4346,23 @@ async function handleExecutePlan(
4334
4346
  let folderId = (params.folder_id as string) || null;
4335
4347
 
4336
4348
  // Step 1: Create project folder
4349
+ let folderSkipped = false;
4350
+ let folderError: string | null = null;
4337
4351
  if (createFolder && spaceId) {
4338
4352
  const folderRow = {
4339
4353
  id: crypto.randomUUID(),
4340
- user_id: userId, space_id: spaceId, name: plan.name || "Untitled Plan",
4341
- type: "project", parent_folder_id: folderId, sort_order: 0,
4354
+ user_id: userId, space_id: spaceId, name: data.name || plan.name || "Untitled Plan",
4355
+ folder_type: "project", parent_folder_id: folderId, sort_order: 0,
4342
4356
  created_at: new Date().toISOString(), updated_at: new Date().toISOString(),
4343
4357
  };
4344
4358
  const { error: folderErr } = await supabase.from("pm_folders").insert(folderRow);
4345
- if (!folderErr) folderId = folderRow.id;
4359
+ if (!folderErr) {
4360
+ folderId = folderRow.id;
4361
+ } else {
4362
+ folderError = folderErr.message;
4363
+ }
4364
+ } else if (createFolder && !spaceId) {
4365
+ folderSkipped = true;
4346
4366
  }
4347
4367
 
4348
4368
  // Step 2: BFS — create tasks with full field mapping
@@ -4352,7 +4372,7 @@ async function handleExecutePlan(
4352
4372
 
4353
4373
  while (queue.length > 0) {
4354
4374
  const { node, parentTaskId } = queue.shift()!;
4355
- if (node.type === "task" || node.type === "milestone") {
4375
+ if (node.type === "task") {
4356
4376
  const taskId = `task-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
4357
4377
  const now = new Date().toISOString();
4358
4378
  const agentId = node.agentId ? await resolveAgent(node.agentId) : null;
@@ -4413,48 +4433,78 @@ async function handleExecutePlan(
4413
4433
  }
4414
4434
  }
4415
4435
 
4416
- // Step 3: Create dependencies (sequential chain)
4436
+ // Step 3: Create dependencies sibling chaining + parent→child
4417
4437
  const depsQueue = [...rootNodes];
4418
4438
  let depsCreated = 0;
4439
+
4440
+ // Helper: resolve a node to its nearest task ID (skip gates/milestones)
4441
+ function resolveTaskId(n: any): string | undefined {
4442
+ return idMap.get(n.id);
4443
+ }
4444
+
4419
4445
  while (depsQueue.length > 0) {
4420
4446
  const node = depsQueue.shift()!;
4421
- const nodeRealId = idMap.get(node.id);
4447
+ const nodeRealId = resolveTaskId(node);
4448
+
4449
+ // Chain siblings: sequential children of this node get chained left-to-right
4450
+ if (Array.isArray(node.children) && node.children.length > 1 && !node.parallel) {
4451
+ // Collect task-type children in order for chaining
4452
+ const taskChildren: string[] = [];
4453
+ for (const child of node.children) {
4454
+ const cid = resolveTaskId(child);
4455
+ if (cid) taskChildren.push(cid);
4456
+ }
4457
+ for (let i = 1; i < taskChildren.length; i++) {
4458
+ await supabase.from("task_dependencies").insert({ user_id: userId, predecessor_id: taskChildren[i - 1], successor_id: taskChildren[i], dependency_type: "finish_to_start", lag_days: 0 });
4459
+ depsCreated++;
4460
+ }
4461
+ }
4462
+
4463
+ // Parent → first child link (sequential only)
4422
4464
  if (nodeRealId && !node.parallel && node.children?.length > 0) {
4423
- const firstChildId = idMap.get(node.children[0].id);
4465
+ const firstChildId = resolveTaskId(node.children[0]);
4424
4466
  if (firstChildId) {
4425
4467
  await supabase.from("task_dependencies").insert({ user_id: userId, predecessor_id: nodeRealId, successor_id: firstChildId, dependency_type: "finish_to_start", lag_days: 0 });
4426
4468
  depsCreated++;
4427
4469
  }
4428
- for (let i = 1; i < node.children.length; i++) {
4429
- const prevId = idMap.get(node.children[i - 1].id);
4430
- const currId = idMap.get(node.children[i].id);
4431
- if (prevId && currId) {
4432
- await supabase.from("task_dependencies").insert({ user_id: userId, predecessor_id: prevId, successor_id: currId, dependency_type: "finish_to_start", lag_days: 0 });
4433
- depsCreated++;
4434
- }
4435
- }
4436
4470
  }
4471
+
4472
+ // Parallel fan-out: parent → each child
4437
4473
  if (nodeRealId && node.parallel && node.children?.length > 0) {
4438
4474
  for (const child of node.children) {
4439
- const childId = idMap.get(child.id);
4475
+ const childId = resolveTaskId(child);
4440
4476
  if (childId) {
4441
4477
  await supabase.from("task_dependencies").insert({ user_id: userId, predecessor_id: nodeRealId, successor_id: childId, dependency_type: "finish_to_start", lag_days: 0 });
4442
4478
  depsCreated++;
4443
4479
  }
4444
4480
  }
4445
4481
  }
4482
+
4446
4483
  if (Array.isArray(node.children)) depsQueue.push(...node.children);
4447
4484
  }
4448
4485
 
4486
+ // Chain root-level siblings (sequential root nodes)
4487
+ const rootTaskIds: string[] = [];
4488
+ for (const rn of rootNodes) {
4489
+ const rid = resolveTaskId(rn);
4490
+ if (rid) rootTaskIds.push(rid);
4491
+ }
4492
+ for (let i = 1; i < rootTaskIds.length; i++) {
4493
+ await supabase.from("task_dependencies").insert({ user_id: userId, predecessor_id: rootTaskIds[i - 1], successor_id: rootTaskIds[i], dependency_type: "finish_to_start", lag_days: 0 });
4494
+ depsCreated++;
4495
+ }
4496
+
4449
4497
  // Mark plan as deployed
4450
4498
  await supabase.from("pm_plans").update({ is_deployed: true, deployed_at: new Date().toISOString(), updated_at: new Date().toISOString() }).eq("id", params.plan_id as string).eq("user_id", userId);
4451
4499
 
4452
4500
  return ok({
4453
- message: `Plan "${plan.name}" executed successfully`,
4501
+ message: `Plan "${data.name || plan.name}" executed successfully`,
4454
4502
  tasks_created: tasksCreated,
4455
4503
  dependencies_created: depsCreated,
4456
4504
  folder_id: folderId,
4457
4505
  space_id: spaceId,
4506
+ ...(folderSkipped ? { folder_skipped_reason: "No space_id provided — assign the plan to a space to enable folder creation" } : {}),
4507
+ ...(folderError ? { folder_error: folderError } : {}),
4458
4508
  });
4459
4509
  } catch (e) { return err(e instanceof Error ? e.message : String(e)); }
4460
4510
  }