forge-openclaw-plugin 0.2.26 → 0.2.28

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 (109) hide show
  1. package/README.md +60 -3
  2. package/dist/assets/{board-ta0rUHOf.js → board-DPFvZf-D.js} +2 -2
  3. package/dist/assets/{board-ta0rUHOf.js.map → board-DPFvZf-D.js.map} +1 -1
  4. package/dist/assets/index-Auw3JrdE.css +1 -0
  5. package/dist/assets/index-D1H7myQH.js +85 -0
  6. package/dist/assets/index-D1H7myQH.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-Bvwc85ch.js} +2 -2
  10. package/dist/assets/{motion-fBKPB6yw.js.map → motion-Bvwc85ch.js.map} +1 -1
  11. package/dist/assets/{table-C-IGTQni.js → table-FJQTJvUR.js} +2 -2
  12. package/dist/assets/{table-C-IGTQni.js.map → table-FJQTJvUR.js.map} +1 -1
  13. package/dist/assets/{ui-DInOpaYF.js → ui-GXFcgvSw.js} +2 -2
  14. package/dist/assets/{ui-DInOpaYF.js.map → ui-GXFcgvSw.js.map} +1 -1
  15. package/dist/assets/vendor-Cwf49UMz.js +1247 -0
  16. package/dist/assets/vendor-Cwf49UMz.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 +1900 -91
  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 +1390 -105
  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/calendar.js +151 -0
  44. package/dist/server/server/src/repositories/habits.js +37 -1
  45. package/dist/server/server/src/repositories/model-settings.js +13 -3
  46. package/dist/server/server/src/repositories/notes.js +3 -0
  47. package/dist/server/server/src/repositories/settings.js +380 -18
  48. package/dist/server/server/src/repositories/tasks.js +170 -10
  49. package/dist/server/server/src/runtime-data-root.js +82 -0
  50. package/dist/server/server/src/screen-time.js +802 -0
  51. package/dist/server/server/src/services/data-management.js +788 -0
  52. package/dist/server/server/src/services/entity-crud.js +205 -2
  53. package/dist/server/server/src/services/knowledge-graph.js +1455 -0
  54. package/dist/server/server/src/services/life-force-model.js +217 -0
  55. package/dist/server/server/src/services/life-force.js +2506 -0
  56. package/dist/server/server/src/services/psyche-observation-calendar.js +383 -16
  57. package/dist/server/server/src/types.js +307 -14
  58. package/dist/server/server/src/web.js +228 -13
  59. package/dist/server/src/components/customization/utility-widgets.js +136 -27
  60. package/dist/server/src/components/ui/info-tooltip.js +25 -0
  61. package/dist/server/src/components/workbench-boxes/calendar/calendar-boxes.js +78 -0
  62. package/dist/server/src/components/workbench-boxes/goals/goals-boxes.js +62 -0
  63. package/dist/server/src/components/workbench-boxes/habits/habits-boxes.js +62 -0
  64. package/dist/server/src/components/workbench-boxes/health/health-boxes.js +63 -8
  65. package/dist/server/src/components/workbench-boxes/insights/insights-boxes.js +50 -0
  66. package/dist/server/src/components/workbench-boxes/kanban/kanban-boxes.js +62 -54
  67. package/dist/server/src/components/workbench-boxes/movement/movement-boxes.js +18 -8
  68. package/dist/server/src/components/workbench-boxes/notes/notes-boxes.js +56 -38
  69. package/dist/server/src/components/workbench-boxes/overview/overview-boxes.js +65 -0
  70. package/dist/server/src/components/workbench-boxes/preferences/preferences-boxes.js +78 -0
  71. package/dist/server/src/components/workbench-boxes/projects/projects-boxes.js +35 -30
  72. package/dist/server/src/components/workbench-boxes/psyche/psyche-boxes.js +88 -0
  73. package/dist/server/src/components/workbench-boxes/questionnaires/questionnaires-boxes.js +61 -0
  74. package/dist/server/src/components/workbench-boxes/review/review-boxes.js +53 -0
  75. package/dist/server/src/components/workbench-boxes/shared/define-workbench-box.js +3 -1
  76. package/dist/server/src/components/workbench-boxes/shared/generic-node-view.js +39 -3
  77. package/dist/server/src/components/workbench-boxes/strategies/strategies-boxes.js +62 -0
  78. package/dist/server/src/components/workbench-boxes/tasks/tasks-boxes.js +76 -0
  79. package/dist/server/src/components/workbench-boxes/today/today-boxes.js +47 -32
  80. package/dist/server/src/components/workbench-boxes/wiki/wiki-boxes.js +60 -0
  81. package/dist/server/src/lib/api.js +280 -21
  82. package/dist/server/src/lib/data-management-types.js +1 -0
  83. package/dist/server/src/lib/entity-visuals.js +279 -0
  84. package/dist/server/src/lib/knowledge-graph-types.js +276 -0
  85. package/dist/server/src/lib/knowledge-graph.js +470 -0
  86. package/dist/server/src/lib/schemas.js +4 -0
  87. package/dist/server/src/lib/snapshot-normalizer.js +45 -1
  88. package/dist/server/src/lib/workbench/contracts.js +229 -0
  89. package/dist/server/src/lib/workbench/nodes.js +200 -0
  90. package/dist/server/src/lib/workbench/registry.js +52 -5
  91. package/dist/server/src/lib/workbench/runtime.js +254 -38
  92. package/dist/server/src/lib/workbench/tool-catalog.js +68 -0
  93. package/openclaw.plugin.json +1 -1
  94. package/package.json +1 -1
  95. package/server/migrations/037_workbench_public_inputs_and_run_inputs.sql +5 -0
  96. package/server/migrations/038_data_management_settings.sql +11 -0
  97. package/server/migrations/039_life_force_and_action_points.sql +114 -0
  98. package/server/migrations/040_screen_time_domain.sql +89 -0
  99. package/server/migrations/041_companion_source_states.sql +21 -0
  100. package/server/migrations/042_movement_boxes.sql +47 -0
  101. package/server/migrations/043_movement_box_overlap_overrides.sql +26 -0
  102. package/skills/forge-openclaw/SKILL.md +41 -11
  103. package/skills/forge-openclaw/entity_conversation_playbooks.md +448 -34
  104. package/skills/forge-openclaw/psyche_entity_playbooks.md +170 -17
  105. package/dist/assets/index-Ro0ZF_az.css +0 -1
  106. package/dist/assets/index-ytlpSj23.js +0 -79
  107. package/dist/assets/index-ytlpSj23.js.map +0 -1
  108. package/dist/assets/vendor-lE3tZJcC.js +0 -876
  109. package/dist/assets/vendor-lE3tZJcC.js.map +0 -1
@@ -4,6 +4,7 @@ import { recordActivityEvent } from "./activity-events.js";
4
4
  import { decorateOwnedEntity, filterOwnedEntities, inferFirstOwnedUserId, setEntityOwner } from "./entity-ownership.js";
5
5
  import { getProjectById } from "./projects.js";
6
6
  import { getTaskById } from "./tasks.js";
7
+ import { buildCalendarEventActionProfile, buildTaskTimeboxActionProfile, buildWorkBlockTemplateActionProfile, readEntityActionProfile, upsertEntityActionProfile } from "../services/life-force.js";
7
8
  import { calendarConnectionSchema, calendarContextConflictSchema, calendarEventSchema, calendarEventLinkSchema, calendarEventSourceSchema, calendarOverviewPayloadSchema, calendarSchema, calendarSchedulingRulesSchema, taskTimeboxSchema, workBlockInstanceSchema, workBlockTemplateSchema } from "../types.js";
8
9
  const DEFAULT_SCHEDULING_RULES = {
9
10
  allowWorkBlockKinds: [],
@@ -149,6 +150,11 @@ function mapEvent(row) {
149
150
  categories: JSON.parse(row.categories_json || "[]"),
150
151
  sourceMappings,
151
152
  links: listEventLinksForEvent(row.id),
153
+ actionProfile: readEntityActionProfile("calendar_event", row.id, {
154
+ profileKey: `calendar_event_${row.id}`,
155
+ title: row.title,
156
+ entityType: "calendar_event"
157
+ }),
152
158
  remoteUpdatedAt: primarySource?.lastSyncedAt ?? null,
153
159
  deletedAt: row.deleted_at,
154
160
  createdAt: row.created_at,
@@ -168,11 +174,17 @@ function mapWorkBlockTemplate(row) {
168
174
  startsOn: row.starts_on,
169
175
  endsOn: row.ends_on,
170
176
  blockingState: row.blocking_state,
177
+ actionProfile: readEntityActionProfile("work_block_template", row.id, {
178
+ profileKey: `work_block_template_${row.id}`,
179
+ title: row.title,
180
+ entityType: "work_block_template"
181
+ }),
171
182
  createdAt: row.created_at,
172
183
  updatedAt: row.updated_at
173
184
  });
174
185
  }
175
186
  function mapTimebox(row) {
187
+ const task = getTaskById(row.task_id);
176
188
  return taskTimeboxSchema.parse({
177
189
  id: row.id,
178
190
  taskId: row.task_id,
@@ -187,6 +199,21 @@ function mapTimebox(row) {
187
199
  startsAt: row.starts_at,
188
200
  endsAt: row.ends_at,
189
201
  overrideReason: row.override_reason,
202
+ actionProfile: readEntityActionProfile("task_timebox", row.id, {
203
+ profileKey: `task_timebox_${row.id}`,
204
+ title: row.title,
205
+ entityType: "task_timebox"
206
+ }) ??
207
+ (task
208
+ ? buildTaskTimeboxActionProfile({
209
+ timeboxId: row.id,
210
+ title: row.title,
211
+ taskId: row.task_id,
212
+ taskPlannedDurationSeconds: task.plannedDurationSeconds,
213
+ startsAt: row.starts_at,
214
+ endsAt: row.ends_at
215
+ })
216
+ : null),
190
217
  createdAt: row.created_at,
191
218
  updatedAt: row.updated_at
192
219
  });
@@ -597,6 +624,20 @@ export function createCalendarEvent(input) {
597
624
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
598
625
  .run(id, preferredCalendar?.connectionId ?? null, preferredCalendar?.id ?? null, "forge", "native", "confirmed", input.title, input.description, input.location, place.label || input.location, place.address, place.timezone, place.latitude, place.longitude, place.source, place.externalPlaceId, input.startAt, input.endAt, normalizeTimezone(input.timezone), input.isAllDay ? 1 : 0, input.availability, input.eventType, JSON.stringify(input.categories), now, now);
599
626
  replaceEventLinks(id, input.links);
627
+ upsertEntityActionProfile({
628
+ entityType: "calendar_event",
629
+ entityId: id,
630
+ profile: buildCalendarEventActionProfile({
631
+ eventId: id,
632
+ title: input.title,
633
+ eventType: input.eventType,
634
+ availability: input.availability,
635
+ startAt: input.startAt,
636
+ endAt: input.endAt,
637
+ activityPresetKey: input.activityPresetKey ?? null,
638
+ customSustainRateApPerHour: input.customSustainRateApPerHour ?? null
639
+ })
640
+ });
600
641
  setEntityOwner("calendar_event", id, inferCalendarEventOwnerId(input));
601
642
  return getCalendarEventById(id);
602
643
  }
@@ -652,6 +693,34 @@ export function updateCalendarEvent(eventId, patch) {
652
693
  if (patch.links) {
653
694
  replaceEventLinks(eventId, patch.links);
654
695
  }
696
+ if (patch.title !== undefined ||
697
+ patch.startAt !== undefined ||
698
+ patch.endAt !== undefined ||
699
+ patch.availability !== undefined ||
700
+ patch.eventType !== undefined ||
701
+ patch.activityPresetKey !== undefined ||
702
+ patch.customSustainRateApPerHour !== undefined) {
703
+ upsertEntityActionProfile({
704
+ entityType: "calendar_event",
705
+ entityId: eventId,
706
+ profile: buildCalendarEventActionProfile({
707
+ eventId,
708
+ title: next.title,
709
+ eventType: next.eventType,
710
+ availability: next.availability,
711
+ startAt: next.startAt,
712
+ endAt: next.endAt,
713
+ activityPresetKey: patch.activityPresetKey === undefined
714
+ ? current.actionProfile?.metadata?.activityPresetKey
715
+ : patch.activityPresetKey,
716
+ customSustainRateApPerHour: patch.customSustainRateApPerHour === undefined
717
+ ? (typeof current.actionProfile?.metadata?.customSustainRateApPerHour === "number"
718
+ ? current.actionProfile.metadata.customSustainRateApPerHour
719
+ : null)
720
+ : patch.customSustainRateApPerHour
721
+ })
722
+ });
723
+ }
655
724
  if (patch.userId !== undefined || patch.links !== undefined) {
656
725
  setEntityOwner("calendar_event", eventId, patch.userId === undefined
657
726
  ? inferCalendarEventOwnerId({
@@ -699,6 +768,19 @@ export function createWorkBlockTemplate(input) {
699
768
  )
700
769
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
701
770
  .run(id, input.title, input.kind, input.color, normalizeTimezone(input.timezone), JSON.stringify(input.weekDays), input.startMinute, input.endMinute, input.startsOn ?? null, input.endsOn ?? null, input.blockingState, now, now);
771
+ upsertEntityActionProfile({
772
+ entityType: "work_block_template",
773
+ entityId: id,
774
+ profile: buildWorkBlockTemplateActionProfile({
775
+ templateId: id,
776
+ title: input.title,
777
+ kind: input.kind,
778
+ startMinute: input.startMinute,
779
+ endMinute: input.endMinute,
780
+ activityPresetKey: input.activityPresetKey ?? null,
781
+ customSustainRateApPerHour: input.customSustainRateApPerHour ?? null
782
+ })
783
+ });
702
784
  setEntityOwner("work_block_template", id, input.userId);
703
785
  return getWorkBlockTemplateById(id);
704
786
  });
@@ -744,6 +826,32 @@ export function updateWorkBlockTemplate(templateId, patch) {
744
826
  SET title = ?, kind = ?, color = ?, timezone = ?, weekdays_json = ?, start_minute = ?, end_minute = ?, starts_on = ?, ends_on = ?, blocking_state = ?, updated_at = ?
745
827
  WHERE id = ?`)
746
828
  .run(next.title, next.kind, next.color, next.timezone, JSON.stringify(next.weekDays), next.startMinute, next.endMinute, next.startsOn, next.endsOn, next.blockingState, next.updatedAt, templateId);
829
+ if (patch.title !== undefined ||
830
+ patch.kind !== undefined ||
831
+ patch.startMinute !== undefined ||
832
+ patch.endMinute !== undefined ||
833
+ patch.activityPresetKey !== undefined ||
834
+ patch.customSustainRateApPerHour !== undefined) {
835
+ upsertEntityActionProfile({
836
+ entityType: "work_block_template",
837
+ entityId: templateId,
838
+ profile: buildWorkBlockTemplateActionProfile({
839
+ templateId,
840
+ title: next.title,
841
+ kind: next.kind,
842
+ startMinute: next.startMinute,
843
+ endMinute: next.endMinute,
844
+ activityPresetKey: patch.activityPresetKey === undefined
845
+ ? current.actionProfile?.metadata?.activityPresetKey
846
+ : patch.activityPresetKey,
847
+ customSustainRateApPerHour: patch.customSustainRateApPerHour === undefined
848
+ ? (typeof current.actionProfile?.metadata?.customSustainRateApPerHour === "number"
849
+ ? current.actionProfile.metadata.customSustainRateApPerHour
850
+ : null)
851
+ : patch.customSustainRateApPerHour
852
+ })
853
+ });
854
+ }
747
855
  if (patch.userId !== undefined) {
748
856
  setEntityOwner("work_block_template", templateId, patch.userId);
749
857
  }
@@ -790,6 +898,7 @@ function deriveWorkBlockInstances(template, query) {
790
898
  color: template.color,
791
899
  blockingState: template.blockingState,
792
900
  calendarEventId: null,
901
+ actionProfile: template.actionProfile ?? null,
793
902
  createdAt: template.createdAt,
794
903
  updatedAt: template.updatedAt
795
904
  }));
@@ -836,12 +945,27 @@ export function getTaskTimeboxById(timeboxId) {
836
945
  export function createTaskTimebox(input) {
837
946
  const now = nowIso();
838
947
  const id = `timebox_${randomUUID().replaceAll("-", "").slice(0, 10)}`;
948
+ const task = getTaskById(input.taskId);
839
949
  getDatabase()
840
950
  .prepare(`INSERT INTO task_timeboxes (
841
951
  id, task_id, project_id, connection_id, calendar_id, linked_task_run_id, status, source, title, starts_at, ends_at, override_reason, created_at, updated_at
842
952
  )
843
953
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
844
954
  .run(id, input.taskId, input.projectId ?? null, input.connectionId ?? null, input.calendarId ?? null, input.linkedTaskRunId ?? null, input.status ?? "planned", input.source ?? "manual", input.title, input.startsAt, input.endsAt, input.overrideReason ?? null, now, now);
955
+ upsertEntityActionProfile({
956
+ entityType: "task_timebox",
957
+ entityId: id,
958
+ profile: buildTaskTimeboxActionProfile({
959
+ timeboxId: id,
960
+ title: input.title,
961
+ taskId: input.taskId,
962
+ taskPlannedDurationSeconds: task?.plannedDurationSeconds ?? null,
963
+ startsAt: input.startsAt,
964
+ endsAt: input.endsAt,
965
+ activityPresetKey: input.activityPresetKey ?? null,
966
+ customSustainRateApPerHour: input.customSustainRateApPerHour ?? null
967
+ })
968
+ });
845
969
  setEntityOwner("task_timebox", id, inferTaskTimeboxOwnerId(input));
846
970
  return getTaskTimeboxById(id);
847
971
  }
@@ -869,6 +993,33 @@ export function updateTaskTimebox(timeboxId, patch) {
869
993
  starts_at = ?, ends_at = ?, override_reason = ?, updated_at = ?
870
994
  WHERE id = ?`)
871
995
  .run(next.connectionId, next.calendarId, next.remoteEventId, next.linkedTaskRunId, next.status, next.source, next.title, next.startsAt, next.endsAt, next.overrideReason, next.updatedAt, timeboxId);
996
+ if (patch.title !== undefined ||
997
+ patch.startsAt !== undefined ||
998
+ patch.endsAt !== undefined ||
999
+ patch.activityPresetKey !== undefined ||
1000
+ patch.customSustainRateApPerHour !== undefined) {
1001
+ const task = getTaskById(current.taskId);
1002
+ upsertEntityActionProfile({
1003
+ entityType: "task_timebox",
1004
+ entityId: timeboxId,
1005
+ profile: buildTaskTimeboxActionProfile({
1006
+ timeboxId,
1007
+ title: next.title,
1008
+ taskId: current.taskId,
1009
+ taskPlannedDurationSeconds: task?.plannedDurationSeconds ?? null,
1010
+ startsAt: next.startsAt,
1011
+ endsAt: next.endsAt,
1012
+ activityPresetKey: patch.activityPresetKey === undefined
1013
+ ? current.actionProfile?.metadata?.activityPresetKey
1014
+ : patch.activityPresetKey,
1015
+ customSustainRateApPerHour: patch.customSustainRateApPerHour === undefined
1016
+ ? (typeof current.actionProfile?.metadata?.customSustainRateApPerHour === "number"
1017
+ ? current.actionProfile.metadata.customSustainRateApPerHour
1018
+ : null)
1019
+ : patch.customSustainRateApPerHour
1020
+ })
1021
+ });
1022
+ }
872
1023
  if (patch.userId !== undefined) {
873
1024
  setEntityOwner("task_timebox", timeboxId, patch.userId);
874
1025
  }
@@ -225,6 +225,39 @@ function getHabitRow(habitId) {
225
225
  WHERE id = ?`)
226
226
  .get(habitId);
227
227
  }
228
+ function compareDateDesc(left, right) {
229
+ return new Date(right ?? 0).getTime() - new Date(left ?? 0).getTime();
230
+ }
231
+ function compareDateAsc(left, right) {
232
+ return new Date(left ?? 0).getTime() - new Date(right ?? 0).getTime();
233
+ }
234
+ function sortHabits(habits, orderBy) {
235
+ const nextHabits = [...habits];
236
+ nextHabits.sort((left, right) => {
237
+ if (orderBy === "name") {
238
+ return (left.title.localeCompare(right.title, undefined, { sensitivity: "base" }) ||
239
+ compareDateDesc(left.createdAt, right.createdAt));
240
+ }
241
+ if (orderBy === "streak") {
242
+ return (right.streakCount - left.streakCount ||
243
+ Number(right.dueToday) - Number(left.dueToday) ||
244
+ left.title.localeCompare(right.title, undefined, { sensitivity: "base" }));
245
+ }
246
+ if (orderBy === "created_at") {
247
+ return (compareDateDesc(left.createdAt, right.createdAt) ||
248
+ left.title.localeCompare(right.title, undefined, { sensitivity: "base" }));
249
+ }
250
+ if (orderBy === "updated_at") {
251
+ return (compareDateDesc(left.updatedAt, right.updatedAt) ||
252
+ left.title.localeCompare(right.title, undefined, { sensitivity: "base" }));
253
+ }
254
+ return (Number(right.dueToday) - Number(left.dueToday) ||
255
+ compareDateAsc(left.lastCheckInAt, right.lastCheckInAt) ||
256
+ compareDateDesc(left.updatedAt, right.updatedAt) ||
257
+ left.title.localeCompare(right.title, undefined, { sensitivity: "base" }));
258
+ });
259
+ return nextHabits;
260
+ }
228
261
  export function listHabits(filters = {}) {
229
262
  const parsed = filters;
230
263
  const whereClauses = [];
@@ -257,7 +290,10 @@ export function listHabits(filters = {}) {
257
290
  ${limitSql}`)
258
291
  .all(...params);
259
292
  const habits = filterDeletedEntities("habit", rows.map((row) => mapHabit(row)));
260
- return parsed.dueToday ? habits.filter((habit) => habit.dueToday) : habits;
293
+ const filteredHabits = parsed.dueToday
294
+ ? habits.filter((habit) => habit.dueToday)
295
+ : habits;
296
+ return sortHabits(filteredHabits, parsed.orderBy);
261
297
  }
262
298
  export function getHabitById(habitId) {
263
299
  if (isEntityDeleted("habit", habitId)) {
@@ -5,6 +5,7 @@ import { deleteEncryptedSecret, readEncryptedSecret, storeEncryptedSecret } from
5
5
  import { upsertWikiLlmProfile } from "./wiki-memory.js";
6
6
  export const DEFAULT_OPENAI_BASE_URL = "https://api.openai.com/v1";
7
7
  export const DEFAULT_OPENAI_CODEX_BASE_URL = "https://chatgpt.com/backend-api";
8
+ export const DEFAULT_MOCK_LLM_BASE_URL = "mock://workbench";
8
9
  export const FORGE_MANAGED_WIKI_PROFILE_ID = "wiki_llm_forge_managed";
9
10
  export const FORGE_DEFAULT_AGENT_ID = "agt_forge_default";
10
11
  function parseMetadata(value) {
@@ -26,6 +27,9 @@ export function defaultBaseUrlForProvider(provider) {
26
27
  if (provider === "openai-compatible") {
27
28
  return "http://127.0.0.1:11434/v1";
28
29
  }
30
+ if (provider === "mock") {
31
+ return DEFAULT_MOCK_LLM_BASE_URL;
32
+ }
29
33
  return DEFAULT_OPENAI_BASE_URL;
30
34
  }
31
35
  function buildConnectionAgentId(connectionId) {
@@ -36,7 +40,9 @@ export function buildConnectionAgentIdentity(connection) {
36
40
  ? "Chat agent backed by OpenAI Codex OAuth."
37
41
  : connection.provider === "openai-compatible"
38
42
  ? "Chat agent backed by a local or OpenAI-compatible endpoint."
39
- : "Chat agent backed by the OpenAI API.";
43
+ : connection.provider === "mock"
44
+ ? "Chat agent backed by Forge's deterministic mock workflow runtime."
45
+ : "Chat agent backed by the OpenAI API.";
40
46
  return {
41
47
  id: connection.agentId,
42
48
  label: connection.agentLabel,
@@ -52,7 +58,8 @@ export function buildConnectionAgentIdentity(connection) {
52
58
  };
53
59
  }
54
60
  function mapConnection(row) {
55
- const hasStoredCredential = Boolean(row.secret_id) && Boolean(readEncryptedSecret(row.secret_id));
61
+ const hasStoredCredential = row.provider === "mock" ||
62
+ (Boolean(row.secret_id) && Boolean(readEncryptedSecret(row.secret_id)));
56
63
  return aiModelConnectionSchema.parse({
57
64
  id: row.id,
58
65
  label: row.label,
@@ -122,7 +129,10 @@ export function upsertAiModelConnection(input, secrets, options = {}) {
122
129
  defaultBaseUrlForProvider(provider);
123
130
  let secretId = existing?.secret_id ?? null;
124
131
  let accountLabel = existing?.account_label ?? null;
125
- if (parsed.apiKey?.trim()) {
132
+ if (parsed.provider === "mock") {
133
+ secretId = null;
134
+ }
135
+ else if (parsed.apiKey?.trim()) {
126
136
  secretId =
127
137
  secretId ?? `mdl_secret_${randomUUID().replaceAll("-", "").slice(0, 10)}`;
128
138
  storeEncryptedSecret(secretId, secrets.sealJson({
@@ -10,6 +10,9 @@ function normalizeAnchorKey(anchorKey) {
10
10
  return anchorKey.trim().length > 0 ? anchorKey : null;
11
11
  }
12
12
  function normalizeLinks(links) {
13
+ if (!links) {
14
+ return [];
15
+ }
13
16
  const seen = new Set();
14
17
  return links.filter((link) => {
15
18
  const key = `${link.entityType}:${link.entityId}:${link.anchorKey ?? ""}`;