forge-openclaw-plugin 0.2.26 → 0.2.27

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 (108) hide show
  1. package/README.md +59 -3
  2. package/dist/assets/{board-ta0rUHOf.js → board-C6jCchjI.js} +2 -2
  3. package/dist/assets/{board-ta0rUHOf.js.map → board-C6jCchjI.js.map} +1 -1
  4. package/dist/assets/index-DVvS8iiU.css +1 -0
  5. package/dist/assets/index-zYB-9Dfo.js +85 -0
  6. package/dist/assets/index-zYB-9Dfo.js.map +1 -0
  7. package/dist/assets/knowledge-graph-layout.worker-DRvzPxhP.js +2 -0
  8. package/dist/assets/knowledge-graph-layout.worker-DRvzPxhP.js.map +1 -0
  9. package/dist/assets/{motion-fBKPB6yw.js → motion-DFHrH2rd.js} +2 -2
  10. package/dist/assets/{motion-fBKPB6yw.js.map → motion-DFHrH2rd.js.map} +1 -1
  11. package/dist/assets/{table-C-IGTQni.js → table-ZL7Di_u3.js} +2 -2
  12. package/dist/assets/{table-C-IGTQni.js.map → table-ZL7Di_u3.js.map} +1 -1
  13. package/dist/assets/{ui-DInOpaYF.js → ui-CKNPpz7q.js} +2 -2
  14. package/dist/assets/{ui-DInOpaYF.js.map → ui-CKNPpz7q.js.map} +1 -1
  15. package/dist/assets/vendor-DoNZuFhn.js +1247 -0
  16. package/dist/assets/vendor-DoNZuFhn.js.map +1 -0
  17. package/dist/index.html +7 -7
  18. package/dist/openclaw/local-runtime.js +16 -0
  19. package/dist/openclaw/routes.d.ts +27 -0
  20. package/dist/openclaw/routes.js +16 -12
  21. package/dist/server/server/migrations/037_workbench_public_inputs_and_run_inputs.sql +5 -0
  22. package/dist/server/server/migrations/038_data_management_settings.sql +11 -0
  23. package/dist/server/server/migrations/039_life_force_and_action_points.sql +114 -0
  24. package/dist/server/server/migrations/040_screen_time_domain.sql +89 -0
  25. package/dist/server/server/migrations/041_companion_source_states.sql +21 -0
  26. package/dist/server/server/migrations/042_movement_boxes.sql +47 -0
  27. package/dist/server/server/migrations/043_movement_box_overlap_overrides.sql +26 -0
  28. package/dist/server/server/src/app.js +1684 -117
  29. package/dist/server/server/src/connectors/box-registry.js +44 -9
  30. package/dist/server/server/src/data-management-types.js +107 -0
  31. package/dist/server/server/src/db.js +68 -4
  32. package/dist/server/server/src/demo-data.js +2 -2
  33. package/dist/server/server/src/health.js +702 -18
  34. package/dist/server/server/src/managers/platform/llm-manager.js +7 -4
  35. package/dist/server/server/src/managers/platform/mock-workbench-provider.js +149 -0
  36. package/dist/server/server/src/managers/platform/secrets-manager.js +18 -1
  37. package/dist/server/server/src/managers/runtime.js +9 -0
  38. package/dist/server/server/src/movement.js +1971 -112
  39. package/dist/server/server/src/openapi.js +489 -1
  40. package/dist/server/server/src/psyche-types.js +9 -1
  41. package/dist/server/server/src/repositories/activity-events.js +8 -0
  42. package/dist/server/server/src/repositories/ai-connectors.js +522 -74
  43. package/dist/server/server/src/repositories/habits.js +37 -1
  44. package/dist/server/server/src/repositories/model-settings.js +13 -3
  45. package/dist/server/server/src/repositories/notes.js +3 -0
  46. package/dist/server/server/src/repositories/settings.js +380 -18
  47. package/dist/server/server/src/repositories/tasks.js +170 -10
  48. package/dist/server/server/src/runtime-data-root.js +82 -0
  49. package/dist/server/server/src/screen-time.js +802 -0
  50. package/dist/server/server/src/services/data-management.js +788 -0
  51. package/dist/server/server/src/services/entity-crud.js +205 -2
  52. package/dist/server/server/src/services/knowledge-graph.js +1455 -0
  53. package/dist/server/server/src/services/life-force-model.js +197 -0
  54. package/dist/server/server/src/services/life-force.js +1270 -0
  55. package/dist/server/server/src/services/psyche-observation-calendar.js +383 -16
  56. package/dist/server/server/src/types.js +286 -13
  57. package/dist/server/server/src/web.js +228 -13
  58. package/dist/server/src/components/customization/utility-widgets.js +136 -27
  59. package/dist/server/src/components/ui/info-tooltip.js +25 -0
  60. package/dist/server/src/components/workbench-boxes/calendar/calendar-boxes.js +78 -0
  61. package/dist/server/src/components/workbench-boxes/goals/goals-boxes.js +62 -0
  62. package/dist/server/src/components/workbench-boxes/habits/habits-boxes.js +62 -0
  63. package/dist/server/src/components/workbench-boxes/health/health-boxes.js +63 -8
  64. package/dist/server/src/components/workbench-boxes/insights/insights-boxes.js +50 -0
  65. package/dist/server/src/components/workbench-boxes/kanban/kanban-boxes.js +62 -54
  66. package/dist/server/src/components/workbench-boxes/movement/movement-boxes.js +18 -8
  67. package/dist/server/src/components/workbench-boxes/notes/notes-boxes.js +56 -38
  68. package/dist/server/src/components/workbench-boxes/overview/overview-boxes.js +65 -0
  69. package/dist/server/src/components/workbench-boxes/preferences/preferences-boxes.js +78 -0
  70. package/dist/server/src/components/workbench-boxes/projects/projects-boxes.js +35 -30
  71. package/dist/server/src/components/workbench-boxes/psyche/psyche-boxes.js +88 -0
  72. package/dist/server/src/components/workbench-boxes/questionnaires/questionnaires-boxes.js +61 -0
  73. package/dist/server/src/components/workbench-boxes/review/review-boxes.js +53 -0
  74. package/dist/server/src/components/workbench-boxes/shared/define-workbench-box.js +3 -1
  75. package/dist/server/src/components/workbench-boxes/shared/generic-node-view.js +39 -3
  76. package/dist/server/src/components/workbench-boxes/strategies/strategies-boxes.js +62 -0
  77. package/dist/server/src/components/workbench-boxes/tasks/tasks-boxes.js +76 -0
  78. package/dist/server/src/components/workbench-boxes/today/today-boxes.js +47 -32
  79. package/dist/server/src/components/workbench-boxes/wiki/wiki-boxes.js +60 -0
  80. package/dist/server/src/lib/api.js +280 -21
  81. package/dist/server/src/lib/data-management-types.js +1 -0
  82. package/dist/server/src/lib/entity-visuals.js +279 -0
  83. package/dist/server/src/lib/knowledge-graph-types.js +276 -0
  84. package/dist/server/src/lib/knowledge-graph.js +470 -0
  85. package/dist/server/src/lib/schemas.js +4 -0
  86. package/dist/server/src/lib/snapshot-normalizer.js +43 -1
  87. package/dist/server/src/lib/workbench/contracts.js +229 -0
  88. package/dist/server/src/lib/workbench/nodes.js +200 -0
  89. package/dist/server/src/lib/workbench/registry.js +52 -5
  90. package/dist/server/src/lib/workbench/runtime.js +254 -38
  91. package/dist/server/src/lib/workbench/tool-catalog.js +68 -0
  92. package/openclaw.plugin.json +1 -1
  93. package/package.json +1 -1
  94. package/server/migrations/037_workbench_public_inputs_and_run_inputs.sql +5 -0
  95. package/server/migrations/038_data_management_settings.sql +11 -0
  96. package/server/migrations/039_life_force_and_action_points.sql +114 -0
  97. package/server/migrations/040_screen_time_domain.sql +89 -0
  98. package/server/migrations/041_companion_source_states.sql +21 -0
  99. package/server/migrations/042_movement_boxes.sql +47 -0
  100. package/server/migrations/043_movement_box_overlap_overrides.sql +26 -0
  101. package/skills/forge-openclaw/SKILL.md +24 -11
  102. package/skills/forge-openclaw/entity_conversation_playbooks.md +210 -34
  103. package/skills/forge-openclaw/psyche_entity_playbooks.md +113 -17
  104. package/dist/assets/index-Ro0ZF_az.css +0 -1
  105. package/dist/assets/index-ytlpSj23.js +0 -79
  106. package/dist/assets/index-ytlpSj23.js.map +0 -1
  107. package/dist/assets/vendor-lE3tZJcC.js +0 -876
  108. package/dist/assets/vendor-lE3tZJcC.js.map +0 -1
@@ -13,6 +13,8 @@ import { awardTaskCompletionReward, reverseLatestTaskCompletionReward } from "./
13
13
  import { findUserByLabel, getDefaultUser, getUserById, resolveUserForMutation } from "./users.js";
14
14
  import { assertTaskRelations } from "../services/relations.js";
15
15
  import { computeWorkTime, emptyTaskTimeSummary } from "../services/work-time.js";
16
+ import { buildTaskLifeForceFields, getTaskCompletionRequirement, upsertTaskActionProfile } from "../services/life-force.js";
17
+ import { createWorkAdjustment } from "./work-adjustments.js";
16
18
  import { calendarSchedulingRulesSchema, taskSchema } from "../types.js";
17
19
  function readTaskTagIds(taskId) {
18
20
  const rows = getDatabase()
@@ -21,7 +23,7 @@ function readTaskTagIds(taskId) {
21
23
  return filterDeletedIds("tag", rows.map((row) => row.tag_id));
22
24
  }
23
25
  function mapTask(row, time = emptyTaskTimeSummary()) {
24
- return taskSchema.parse(decorateOwnedEntity("task", {
26
+ const task = taskSchema.parse(decorateOwnedEntity("task", {
25
27
  id: row.id,
26
28
  title: row.title,
27
29
  description: row.description,
@@ -39,12 +41,20 @@ function mapTask(row, time = emptyTaskTimeSummary()) {
39
41
  ? null
40
42
  : calendarSchedulingRulesSchema.parse(JSON.parse(row.scheduling_rules_json)),
41
43
  sortOrder: row.sort_order,
44
+ resolutionKind: row.resolution_kind === "completed" || row.resolution_kind === "split"
45
+ ? row.resolution_kind
46
+ : null,
47
+ splitParentTaskId: row.split_parent_task_id,
42
48
  completedAt: row.completed_at,
43
49
  createdAt: row.created_at,
44
50
  updatedAt: row.updated_at,
45
51
  tagIds: readTaskTagIds(row.id),
46
52
  time
47
53
  }));
54
+ return {
55
+ ...task,
56
+ ...buildTaskLifeForceFields(task, task.userId ?? undefined)
57
+ };
48
58
  }
49
59
  function replaceTaskTags(taskId, tagIds) {
50
60
  const database = getDatabase();
@@ -193,12 +203,38 @@ function updateTaskRecord(current, input, activity) {
193
203
  const movedColumns = nextStatus !== current.status;
194
204
  const nextSort = input.sortOrder ??
195
205
  (movedColumns ? nextSortOrder(nextStatus) : current.sortOrder);
206
+ if (current.status !== "done" &&
207
+ nextStatus === "done" &&
208
+ input.resolutionKind !== "split") {
209
+ const completionRequirement = getTaskCompletionRequirement(current, current.userId ?? undefined);
210
+ if (input.enforceTodayWorkLog === true &&
211
+ completionRequirement.requiresWorkLog &&
212
+ input.completedTodayWorkSeconds === undefined) {
213
+ throw new HttpError(409, "task_completion_work_log_required", "Log how long you worked on this task today before closing it.", {
214
+ taskId: current.id,
215
+ todayCreditedSeconds: completionRequirement.todayCreditedSeconds
216
+ });
217
+ }
218
+ if ((input.completedTodayWorkSeconds ?? 0) > 0) {
219
+ const appliedDeltaMinutes = Math.max(1, Math.round((input.completedTodayWorkSeconds ?? 0) / 60));
220
+ createWorkAdjustment({
221
+ entityType: "task",
222
+ entityId: current.id,
223
+ deltaMinutes: appliedDeltaMinutes,
224
+ appliedDeltaMinutes,
225
+ note: "Completion log for today"
226
+ }, {
227
+ actor: activity?.actor ?? null,
228
+ source: activity?.source ?? "ui"
229
+ });
230
+ }
231
+ }
196
232
  const completedAt = normalizeCompletedAt(nextStatus, current.completedAt);
197
233
  const updatedAt = new Date().toISOString();
198
234
  getDatabase()
199
235
  .prepare(`UPDATE tasks
200
236
  SET title = ?, description = ?, status = ?, priority = ?, owner = ?, goal_id = ?, due_date = ?, effort = ?,
201
- energy = ?, points = ?, planned_duration_seconds = ?, scheduling_rules_json = ?, sort_order = ?, completed_at = ?, updated_at = ?, project_id = ?
237
+ energy = ?, points = ?, planned_duration_seconds = ?, scheduling_rules_json = ?, sort_order = ?, resolution_kind = ?, split_parent_task_id = ?, completed_at = ?, updated_at = ?, project_id = ?
202
238
  WHERE id = ?`)
203
239
  .run(input.title ?? current.title, input.description ?? current.description, nextStatus, input.priority ?? current.priority, assignment.ownerLabel, nextGoalId, input.dueDate === undefined ? current.dueDate : input.dueDate, input.effort ?? current.effort, input.energy ?? current.energy, input.points ?? current.points, input.plannedDurationSeconds === undefined
204
240
  ? current.plannedDurationSeconds
@@ -208,10 +244,23 @@ function updateTaskRecord(current, input, activity) {
208
244
  : JSON.stringify(current.schedulingRules)
209
245
  : input.schedulingRules === null
210
246
  ? null
211
- : JSON.stringify(input.schedulingRules), nextSort, completedAt, updatedAt, nextProjectId, current.id);
247
+ : JSON.stringify(input.schedulingRules), nextSort, input.resolutionKind === undefined ? current.resolutionKind : input.resolutionKind, input.splitParentTaskId === undefined
248
+ ? current.splitParentTaskId
249
+ : input.splitParentTaskId, completedAt, updatedAt, nextProjectId, current.id);
212
250
  replaceTaskTags(current.id, nextTagIds);
213
251
  setEntityOwner("task", current.id, assignment.userId);
214
252
  const updated = getTaskById(current.id);
253
+ if (updated &&
254
+ (input.actionCostBand !== undefined ||
255
+ input.plannedDurationSeconds !== undefined ||
256
+ input.title !== undefined)) {
257
+ upsertTaskActionProfile({
258
+ taskId: updated.id,
259
+ title: updated.title,
260
+ plannedDurationSeconds: updated.plannedDurationSeconds,
261
+ actionCostBand: input.actionCostBand ?? updated.actionPointSummary?.costBand ?? "standard"
262
+ });
263
+ }
215
264
  if (updated && activity) {
216
265
  const statusChanged = current.status !== updated.status;
217
266
  const ownerChanged = current.owner !== updated.owner;
@@ -262,7 +311,9 @@ function updateTaskRecord(current, input, activity) {
262
311
  pointsChanged
263
312
  }
264
313
  });
265
- if (current.status !== "done" && updated.status === "done") {
314
+ if (current.status !== "done" &&
315
+ updated.status === "done" &&
316
+ updated.resolutionKind !== "split") {
266
317
  awardTaskCompletionReward(updated, activity);
267
318
  }
268
319
  else if (current.status === "done" && updated.status !== "done") {
@@ -322,15 +373,21 @@ function insertTaskRecord(input, activity) {
322
373
  getDatabase()
323
374
  .prepare(`INSERT INTO tasks (
324
375
  id, title, description, status, priority, owner, goal_id, project_id, due_date, effort, energy, points,
325
- planned_duration_seconds, scheduling_rules_json, sort_order, completed_at, created_at, updated_at
376
+ planned_duration_seconds, scheduling_rules_json, sort_order, resolution_kind, split_parent_task_id, completed_at, created_at, updated_at
326
377
  )
327
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
378
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
328
379
  .run(id, input.title, input.description, input.status, input.priority, assignment.ownerLabel, relationState.goalId, relationState.projectId, input.dueDate, input.effort, input.energy, input.points, input.plannedDurationSeconds, input.schedulingRules === null
329
380
  ? null
330
- : JSON.stringify(input.schedulingRules), sortOrder, completedAt, now, now);
381
+ : JSON.stringify(input.schedulingRules), sortOrder, input.status === "done" ? "completed" : null, null, completedAt, now, now);
331
382
  setEntityOwner("task", id, assignment.userId);
332
383
  replaceTaskTags(id, input.tagIds);
333
384
  const task = getTaskById(id);
385
+ upsertTaskActionProfile({
386
+ taskId: task.id,
387
+ title: task.title,
388
+ plannedDurationSeconds: task.plannedDurationSeconds,
389
+ actionCostBand: input.actionCostBand
390
+ });
334
391
  if (activity) {
335
392
  recordActivityEvent({
336
393
  entityType: "task",
@@ -350,7 +407,7 @@ function insertTaskRecord(input, activity) {
350
407
  points: task.points
351
408
  }
352
409
  });
353
- if (task.status === "done") {
410
+ if (task.status === "done" && task.resolutionKind !== "split") {
354
411
  awardTaskCompletionReward(task, activity);
355
412
  }
356
413
  }
@@ -403,7 +460,7 @@ export function listTasks(filters = {}) {
403
460
  }
404
461
  const rows = getDatabase()
405
462
  .prepare(`SELECT id, title, description, status, priority, owner, goal_id, project_id, due_date, effort, energy, points,
406
- planned_duration_seconds, scheduling_rules_json, sort_order,
463
+ planned_duration_seconds, scheduling_rules_json, sort_order, resolution_kind, split_parent_task_id,
407
464
  completed_at, created_at, updated_at
408
465
  FROM tasks
409
466
  ${whereSql}
@@ -428,7 +485,7 @@ export function getTaskById(taskId) {
428
485
  }
429
486
  const row = getDatabase()
430
487
  .prepare(`SELECT id, title, description, status, priority, owner, goal_id, project_id, due_date, effort, energy, points,
431
- planned_duration_seconds, scheduling_rules_json, sort_order,
488
+ planned_duration_seconds, scheduling_rules_json, sort_order, resolution_kind, split_parent_task_id,
432
489
  completed_at, created_at, updated_at
433
490
  FROM tasks
434
491
  WHERE id = ?`)
@@ -520,3 +577,106 @@ export function deleteTask(taskId, activity) {
520
577
  return current;
521
578
  });
522
579
  }
580
+ export function splitTask(taskId, input, activity) {
581
+ const current = getTaskById(taskId);
582
+ if (!current) {
583
+ return undefined;
584
+ }
585
+ return runInTransaction(() => {
586
+ const remainingRatio = clampRatio(input.remainingRatio);
587
+ const remainingExpectedDurationSeconds = Math.max(60, current.actionPointSummary.expectedDurationSeconds -
588
+ current.time.totalCreditedSeconds);
589
+ const remainingAp = Math.max(1, current.actionPointSummary.remainingAp);
590
+ const firstChildDurationSeconds = Math.max(60, Math.round(remainingExpectedDurationSeconds * remainingRatio));
591
+ const secondChildDurationSeconds = Math.max(60, remainingExpectedDurationSeconds - firstChildDurationSeconds);
592
+ const firstChildTotalAp = Math.max(1, Math.round(remainingAp * remainingRatio));
593
+ const secondChildTotalAp = Math.max(1, remainingAp - firstChildTotalAp);
594
+ const parent = updateTaskRecord(current, {
595
+ status: "done",
596
+ resolutionKind: "split"
597
+ }, activity);
598
+ if (!parent) {
599
+ throw new HttpError(500, "task_split_failed", "Could not mark the original task as split.");
600
+ }
601
+ const totalCostPoints = current.points;
602
+ const firstChild = insertTaskRecord({
603
+ title: input.firstTitle,
604
+ description: current.description,
605
+ status: "focus",
606
+ priority: current.priority,
607
+ owner: current.owner,
608
+ userId: current.userId ?? null,
609
+ goalId: current.goalId,
610
+ projectId: current.projectId,
611
+ dueDate: current.dueDate,
612
+ effort: current.effort,
613
+ energy: current.energy,
614
+ points: Math.max(5, Math.round(totalCostPoints * remainingRatio)),
615
+ plannedDurationSeconds: firstChildDurationSeconds,
616
+ schedulingRules: current.schedulingRules,
617
+ actionCostBand: current.actionPointSummary.costBand,
618
+ tagIds: current.tagIds,
619
+ notes: []
620
+ }, activity);
621
+ const secondChild = insertTaskRecord({
622
+ title: input.secondTitle,
623
+ description: current.description,
624
+ status: "focus",
625
+ priority: current.priority,
626
+ owner: current.owner,
627
+ userId: current.userId ?? null,
628
+ goalId: current.goalId,
629
+ projectId: current.projectId,
630
+ dueDate: current.dueDate,
631
+ effort: current.effort,
632
+ energy: current.energy,
633
+ points: Math.max(5, totalCostPoints - firstChild.points),
634
+ plannedDurationSeconds: secondChildDurationSeconds,
635
+ schedulingRules: current.schedulingRules,
636
+ actionCostBand: current.actionPointSummary.costBand,
637
+ tagIds: current.tagIds,
638
+ notes: []
639
+ }, activity);
640
+ upsertTaskActionProfile({
641
+ taskId: firstChild.id,
642
+ title: firstChild.title,
643
+ plannedDurationSeconds: firstChildDurationSeconds,
644
+ actionCostBand: current.actionPointSummary.costBand,
645
+ totalCostAp: firstChildTotalAp
646
+ });
647
+ upsertTaskActionProfile({
648
+ taskId: secondChild.id,
649
+ title: secondChild.title,
650
+ plannedDurationSeconds: secondChildDurationSeconds,
651
+ actionCostBand: current.actionPointSummary.costBand,
652
+ totalCostAp: secondChildTotalAp
653
+ });
654
+ updateTaskRecord(firstChild, { splitParentTaskId: current.id }, activity);
655
+ updateTaskRecord(secondChild, { splitParentTaskId: current.id }, activity);
656
+ if (activity) {
657
+ recordActivityEvent({
658
+ entityType: "task",
659
+ entityId: current.id,
660
+ eventType: "task_split",
661
+ title: `Task split: ${current.title}`,
662
+ description: `Remaining work was split into ${input.firstTitle} and ${input.secondTitle}.`,
663
+ actor: activity.actor ?? null,
664
+ source: activity.source,
665
+ metadata: {
666
+ firstChildTaskId: firstChild.id,
667
+ secondChildTaskId: secondChild.id
668
+ }
669
+ });
670
+ }
671
+ return {
672
+ parent: getTaskById(current.id),
673
+ children: [getTaskById(firstChild.id), getTaskById(secondChild.id)]
674
+ };
675
+ });
676
+ }
677
+ function clampRatio(value) {
678
+ if (typeof value !== "number" || Number.isNaN(value)) {
679
+ return 0.5;
680
+ }
681
+ return Math.min(0.9, Math.max(0.1, value));
682
+ }
@@ -0,0 +1,82 @@
1
+ import { existsSync } from "node:fs";
2
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
3
+ import os from "node:os";
4
+ import path from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+ const projectRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "..");
7
+ const monorepoRuntimePreferencePath = path.resolve(projectRoot, "..", "..", "data", "forge-runtime.json");
8
+ export function getMonorepoRuntimePreferencePath() {
9
+ return monorepoRuntimePreferencePath;
10
+ }
11
+ export async function readMonorepoPreferredDataRoot() {
12
+ if (!existsSync(monorepoRuntimePreferencePath)) {
13
+ return null;
14
+ }
15
+ try {
16
+ const raw = await readFile(monorepoRuntimePreferencePath, "utf8");
17
+ const parsed = JSON.parse(raw);
18
+ return typeof parsed.dataRoot === "string" && parsed.dataRoot.trim().length > 0
19
+ ? path.resolve(parsed.dataRoot)
20
+ : null;
21
+ }
22
+ catch {
23
+ return null;
24
+ }
25
+ }
26
+ export async function writeMonorepoPreferredDataRoot(dataRoot) {
27
+ await mkdir(path.dirname(monorepoRuntimePreferencePath), { recursive: true });
28
+ await writeFile(monorepoRuntimePreferencePath, `${JSON.stringify({
29
+ dataRoot: path.resolve(dataRoot),
30
+ updatedAt: new Date().toISOString()
31
+ }, null, 2)}\n`, "utf8");
32
+ }
33
+ async function patchJsonFile(filePath, transform) {
34
+ let payload = {};
35
+ if (existsSync(filePath)) {
36
+ try {
37
+ const raw = await readFile(filePath, "utf8");
38
+ const parsed = JSON.parse(raw);
39
+ if (parsed && typeof parsed === "object") {
40
+ payload = parsed;
41
+ }
42
+ }
43
+ catch {
44
+ payload = {};
45
+ }
46
+ }
47
+ const next = transform(payload);
48
+ await mkdir(path.dirname(filePath), { recursive: true });
49
+ await writeFile(filePath, `${JSON.stringify(next, null, 2)}\n`, "utf8");
50
+ }
51
+ export async function syncLocalAdapterDataRoots(dataRoot) {
52
+ const resolved = path.resolve(dataRoot);
53
+ const openClawConfigPath = path.join(os.homedir(), ".openclaw", "openclaw.json");
54
+ const hermesConfigPath = path.join(os.homedir(), ".hermes", "forge", "config.json");
55
+ await patchJsonFile(openClawConfigPath, (payload) => {
56
+ const plugins = payload.plugins && typeof payload.plugins === "object"
57
+ ? { ...payload.plugins }
58
+ : {};
59
+ const entries = plugins.entries && typeof plugins.entries === "object"
60
+ ? { ...plugins.entries }
61
+ : {};
62
+ const currentEntry = entries["forge-openclaw-plugin"] &&
63
+ typeof entries["forge-openclaw-plugin"] === "object"
64
+ ? { ...entries["forge-openclaw-plugin"] }
65
+ : { enabled: true };
66
+ const currentConfig = currentEntry.config && typeof currentEntry.config === "object"
67
+ ? { ...currentEntry.config }
68
+ : {};
69
+ currentConfig.dataRoot = resolved;
70
+ currentEntry.config = currentConfig;
71
+ entries["forge-openclaw-plugin"] = currentEntry;
72
+ plugins.entries = entries;
73
+ return {
74
+ ...payload,
75
+ plugins
76
+ };
77
+ });
78
+ await patchJsonFile(hermesConfigPath, (payload) => ({
79
+ ...payload,
80
+ dataRoot: resolved
81
+ }));
82
+ }