forge-openclaw-plugin 0.2.99 → 0.2.100

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 (149) hide show
  1. package/dist/assets/activity-copy-Bj4h9OcF.js +1 -0
  2. package/dist/assets/activity-page-5oyCFOns.js +1 -0
  3. package/dist/assets/ai-surface-workspace-qgk_B57-.js +1 -0
  4. package/dist/assets/atlas-panel-rfH2qOez.js +1 -0
  5. package/dist/assets/{board-Ju0h0SeG.js → board-BkDRaMp6.js} +1 -1
  6. package/dist/assets/calendar-display-preferences-Cid-2RnL.js +1 -0
  7. package/dist/assets/calendar-page-Bo2iua-a.js +1 -0
  8. package/dist/assets/calendar-rules-DA1g3QUk.js +1 -0
  9. package/dist/assets/calendar-ui-Cy1XRwzV.js +1 -0
  10. package/dist/assets/calendar-week-toolbar-DU1Q4RYj.js +1 -0
  11. package/dist/assets/charts-P7EVhIog.js +36 -0
  12. package/dist/assets/companion-sync-lab-page-CosNknOK.js +1 -0
  13. package/dist/assets/daily-metrics-dashboard-LjuGAB3f.js +1 -0
  14. package/dist/assets/date-keys-Cj1G3TOn.js +1 -0
  15. package/dist/assets/entity-links-DwpxhW2H.js +1 -0
  16. package/dist/assets/entity-note-count-link-BmGDB572.js +1 -0
  17. package/dist/assets/entity-notes-surface-DgEgicaE.js +1 -0
  18. package/dist/assets/execution-board-CDRXQB85.js +1 -0
  19. package/dist/assets/faceted-token-search-CE1YauRd.js +1 -0
  20. package/dist/assets/flagship-signal-deck-DDds90Gl.js +1 -0
  21. package/dist/assets/floating-action-menu-CJkI2iFy.js +1 -0
  22. package/dist/assets/forms-BFlTgZ3W.js +1 -0
  23. package/dist/assets/goal-detail-page-cJvHaLMQ.js +1 -0
  24. package/dist/assets/goals-page-f_39hvUV.js +1 -0
  25. package/dist/assets/graph-BZV40eAE.css +1 -0
  26. package/dist/assets/graph-D6JLqDbD.js +318 -0
  27. package/dist/assets/habits-page-DKb96_mj.js +1 -0
  28. package/dist/assets/health-link-options-Cpx8w7uM.js +1 -0
  29. package/dist/assets/index-BHTUu_4M.js +19 -0
  30. package/dist/assets/index-CZbuZQjw.css +1 -0
  31. package/dist/assets/insight-flow-dialog-pzAzyayN.js +1 -0
  32. package/dist/assets/insights-page-Dc9oFltJ.js +8 -0
  33. package/dist/assets/kanban-page-JAxerYh6.js +1 -0
  34. package/dist/assets/knowledge-graph-page-UQ3skqEi.js +1 -0
  35. package/dist/assets/life-force-page-BGDbQuVh.js +1 -0
  36. package/dist/assets/life-force-workspace-B1fYSXRC.js +1 -0
  37. package/dist/assets/maps-B-YMMjus.css +1 -0
  38. package/dist/assets/maps-ClgJoCjz.js +803 -0
  39. package/dist/assets/metric-tile-DX6TclqM.js +1 -0
  40. package/dist/assets/{motion-DRPJkN3a.js → motion-BeD44FeG.js} +1 -1
  41. package/dist/assets/movement-page-6HP6nGJx.js +1 -0
  42. package/dist/assets/note-markdown-DiW2-5d3.js +3 -0
  43. package/dist/assets/note-tags-input-DDLXf54U.js +1 -0
  44. package/dist/assets/notes-page-BuguDjhz.js +1 -0
  45. package/dist/assets/open-in-graph-button-Cg5VrKsC.js +1 -0
  46. package/dist/assets/orbit-map-GD05-0oS.js +1 -0
  47. package/dist/assets/overview-page-DuOs2OCB.js +1 -0
  48. package/dist/assets/page-hero-CQWo1Mm_.js +1 -0
  49. package/dist/assets/pill-cluster-BJogDRDJ.js +1 -0
  50. package/dist/assets/preference-entity-handoff-button-D4WAs9pC.js +1 -0
  51. package/dist/assets/preferences-page-BaJTMU1I.js +1 -0
  52. package/dist/assets/project-collections-DvaX20q_.js +1 -0
  53. package/dist/assets/project-detail-page-drPIFZGb.js +1 -0
  54. package/dist/assets/project-management-hierarchy-page-BUbRXvny.js +1 -0
  55. package/dist/assets/project-management-section-nav-C2Ud8Zdd.js +1 -0
  56. package/dist/assets/projects-page-BGzEZUtg.js +1 -0
  57. package/dist/assets/psyche-behaviors-page-Dmm_Io9D.js +5 -0
  58. package/dist/assets/psyche-flashcards-page-BgNKJ6QJ.js +1 -0
  59. package/dist/assets/psyche-goal-map-page-DXJs98Vr.js +1 -0
  60. package/dist/assets/psyche-graph-CFgs_Bqc.js +1 -0
  61. package/dist/assets/psyche-metrics-page-zYTJDbyZ.js +1 -0
  62. package/dist/assets/psyche-mode-guide-page-XPgRfCOf.js +1 -0
  63. package/dist/assets/psyche-modes-page-B-GA8oRF.js +1 -0
  64. package/dist/assets/psyche-page--r6a3e1t.js +1 -0
  65. package/dist/assets/psyche-patterns-page-BM5-3bMm.js +5 -0
  66. package/dist/assets/psyche-questionnaire-builder-page-CJshQ-mg.js +1 -0
  67. package/dist/assets/psyche-questionnaire-detail-page-USmR5G5A.js +1 -0
  68. package/dist/assets/psyche-questionnaire-run-detail-page-D7iBCmTi.js +1 -0
  69. package/dist/assets/psyche-questionnaire-run-page-Cpil-kDh.js +1 -0
  70. package/dist/assets/psyche-questionnaires-page-C-_y3VwS.js +1 -0
  71. package/dist/assets/psyche-report-detail-page--dkSPRaj.js +3 -0
  72. package/dist/assets/psyche-reports-page-CUaOXmIN.js +1 -0
  73. package/dist/assets/psyche-schemas-HFmg37Wj.js +1 -0
  74. package/dist/assets/psyche-schemas-beliefs-page-BX6xaap3.js +9 -0
  75. package/dist/assets/psyche-screen-time-page-CAAI4mD7.js +1 -0
  76. package/dist/assets/psyche-self-observation-page-BZ6FLuwa.js +1 -0
  77. package/dist/assets/psyche-values-page-yEV6MGt8.js +5 -0
  78. package/dist/assets/query-cache-IQ8W-LNC.js +1 -0
  79. package/dist/assets/report-chain-fields-fZ8Xd4H6.js +1 -0
  80. package/dist/assets/rewards-page-C2HQjIAf.js +1 -0
  81. package/dist/assets/scheduling-rules-editor-BHOpHOrV.js +1 -0
  82. package/dist/assets/schema-badge-DyKbxb51.js +1 -0
  83. package/dist/assets/schema-visuals-D6nxjbYC.js +1 -0
  84. package/dist/assets/select-menu-BX-pZNqL.js +1 -0
  85. package/dist/assets/settings-agents-page-VuYXTiyc.js +6 -0
  86. package/dist/assets/settings-bin-page-BNzvYaOk.js +1 -0
  87. package/dist/assets/settings-calendar-page-CjSFB53S.js +5 -0
  88. package/dist/assets/settings-data-page-CGSlryuI.js +1 -0
  89. package/dist/assets/settings-logs-page-BTK5fine.js +1 -0
  90. package/dist/assets/settings-mobile-page-CRaObOGo.js +1 -0
  91. package/dist/assets/settings-models-page-DFshpYF8.js +1 -0
  92. package/dist/assets/settings-page-BP81Mb5R.js +1 -0
  93. package/dist/assets/settings-rewards-page-CDJ1PH2G.js +1 -0
  94. package/dist/assets/settings-section-nav-CCFm27r2.js +1 -0
  95. package/dist/assets/settings-users-page-TdUocFPa.js +1 -0
  96. package/dist/assets/settings-wiki-page-B2zX0QQG.js +1 -0
  97. package/dist/assets/sleep-page-cI1GMVzk.js +1 -0
  98. package/dist/assets/sports-page-06LTqp0V.js +1 -0
  99. package/dist/assets/state-B-4sS1xO.js +1 -0
  100. package/dist/assets/strategies-page-DXP9Kx8s.js +1 -0
  101. package/dist/assets/strategy-detail-page-D6mx_Mik.js +1 -0
  102. package/dist/assets/strategy-dialog-BvzomTaF.js +1 -0
  103. package/dist/assets/{table-DewbFlTh.js → table-WfAPUppN.js} +1 -1
  104. package/dist/assets/task-detail-page-BIWIggdp.js +1 -0
  105. package/dist/assets/timebox-planning-dialog-CaCnoslG.js +1 -0
  106. package/dist/assets/today-page-DO2mRPT2.js +1 -0
  107. package/dist/assets/training-load-page-CyZ0mlEr.js +1 -0
  108. package/dist/assets/{ui-C2IvSrAz.js → ui-C13Nbgas.js} +4 -4
  109. package/dist/assets/use-psyche-focus-target-C1C_XjYG.js +1 -0
  110. package/dist/assets/vendor-CRS-psbw.css +1 -0
  111. package/dist/assets/vendor-DHkYh85p.js +1052 -0
  112. package/dist/assets/vitals-page-BQvEjTc6.js +1 -0
  113. package/dist/assets/weekly-review-page-Tp6Q9CRj.js +1 -0
  114. package/dist/assets/weight-loss-page-BBzlhLVV.js +1 -0
  115. package/dist/assets/wiki-article-markdown-DQYohmW2.js +4 -0
  116. package/dist/assets/wiki-editor-page-Dem_3eZv.js +26 -0
  117. package/dist/assets/wiki-ingest-history-page-BxoOcCoJ.js +1 -0
  118. package/dist/assets/wiki-ingest-modal-DhguKk3J.js +1 -0
  119. package/dist/assets/wiki-page-BLRxVXkl.js +1 -0
  120. package/dist/assets/workbench-flow-page-DqMkCCTy.js +5 -0
  121. package/dist/assets/workbench-page-BWd02wPw.js +1 -0
  122. package/dist/assets/workout-detail-page-BD8u7GyL.js +2 -0
  123. package/dist/index.html +148 -9
  124. package/dist/openclaw/tools.js +340 -0
  125. package/dist/server/server/migrations/065_weight_loss_nutrition_insights.sql +236 -0
  126. package/dist/server/server/migrations/066_watch_action_receipts.sql +20 -0
  127. package/dist/server/server/src/app.js +266 -13
  128. package/dist/server/server/src/health-weight-loss.js +1378 -0
  129. package/dist/server/server/src/health.js +188 -35
  130. package/dist/server/server/src/openapi.js +449 -0
  131. package/dist/server/server/src/services/context.js +6 -7
  132. package/dist/server/server/src/services/doctor.js +39 -4
  133. package/dist/server/server/src/services/gamification.js +146 -34
  134. package/dist/server/server/src/watch-mobile.js +564 -4
  135. package/dist/server/server/src/web.js +18 -5
  136. package/dist/server/src/components/ui/info-tooltip.js +48 -3
  137. package/dist/server/src/lib/api.js +131 -0
  138. package/dist/server/src/lib/weight-loss-types.js +1 -0
  139. package/openclaw.plugin.json +14 -1
  140. package/package.json +1 -1
  141. package/server/migrations/065_weight_loss_nutrition_insights.sql +236 -0
  142. package/server/migrations/066_watch_action_receipts.sql +20 -0
  143. package/skills/forge-openclaw/SKILL.md +26 -5
  144. package/skills/forge-openclaw/entity_conversation_playbooks.md +134 -5
  145. package/skills/forge-openclaw/psyche_entity_playbooks.md +45 -0
  146. package/dist/assets/index-Cn5Wpwau.css +0 -1
  147. package/dist/assets/index-CwvGs8n4.js +0 -91
  148. package/dist/assets/vendor-B-Lq_OG3.css +0 -1
  149. package/dist/assets/vendor-DL2K5ayT.js +0 -2186
@@ -133,6 +133,12 @@ function resolveScopeUsers(requestedUserIds) {
133
133
  return { mode: "selected_user", users: selectedUsers };
134
134
  }
135
135
  }
136
+ if (uniqueRequestedUserIds.length > 1) {
137
+ const selectedUsers = listUsersByIds(uniqueRequestedUserIds);
138
+ if (selectedUsers.length > 0) {
139
+ return { mode: "aggregate_fallback", users: selectedUsers };
140
+ }
141
+ }
136
142
  try {
137
143
  return { mode: "operator_fallback", users: [getDefaultUser()] };
138
144
  }
@@ -154,6 +160,23 @@ export function resolveGamificationScope(requestedUserIds) {
154
160
  label
155
161
  };
156
162
  }
163
+ function persistableUserIdForScope(scope) {
164
+ if (scope.users.length === 1 &&
165
+ (scope.mode === "selected_user" || scope.mode === "operator_fallback")) {
166
+ return scope.users[0].id;
167
+ }
168
+ return null;
169
+ }
170
+ function emptyGamificationEquipment() {
171
+ return {
172
+ selectedMascotSkin: null,
173
+ selectedHudTreatment: null,
174
+ selectedStreakEffect: null,
175
+ selectedTrophyShelf: null,
176
+ selectedCelebrationVariant: null,
177
+ updatedAt: null
178
+ };
179
+ }
157
180
  function buildOwnerResolver(defaultUserId) {
158
181
  const ownerRows = getDatabase()
159
182
  .prepare(`SELECT entity_type, entity_id, user_id
@@ -210,7 +233,7 @@ function loadScopedRewardEvents(scope) {
210
233
  ORDER BY reward_ledger.created_at ASC`)
211
234
  .all();
212
235
  const scopeUserIds = new Set(scope.userIds);
213
- const defaultUserId = scope.userIds[0] ?? null;
236
+ const defaultUserId = persistableUserIdForScope(scope);
214
237
  const resolveOwner = buildOwnerResolver(defaultUserId);
215
238
  return rows
216
239
  .map((row) => {
@@ -242,6 +265,9 @@ function loadScopedRewardEvents(scope) {
242
265
  : event.ownerUserId !== null && scopeUserIds.has(event.ownerUserId));
243
266
  }
244
267
  function syncEntityCreationRewards(scope) {
268
+ if (!persistableUserIdForScope(scope)) {
269
+ return;
270
+ }
245
271
  const database = getDatabase();
246
272
  const scopeUserIds = [...new Set(scope.userIds)];
247
273
  const scopePlaceholders = scopeUserIds.map(() => "?").join(", ");
@@ -282,7 +308,7 @@ function isQualifyingStreakReward(event) {
282
308
  event.reversedByRewardId === null &&
283
309
  event.metadata.manual !== true);
284
310
  }
285
- function syncDailyActivity(userId, scopedRewards, timezone) {
311
+ function deriveDailyActivityRows(userId, scopedRewards, timezone) {
286
312
  const byDate = new Map();
287
313
  for (const event of scopedRewards.filter(isQualifyingStreakReward)) {
288
314
  const dateKeyValue = dateKeyInTimezone(event.createdAt, timezone);
@@ -300,7 +326,7 @@ function syncDailyActivity(userId, scopedRewards, timezone) {
300
326
  current.lastRewardEventId = event.id;
301
327
  byDate.set(dateKeyValue, current);
302
328
  }
303
- replaceGamificationDailyActivity(userId, [...byDate.values()].map((row) => ({
329
+ return [...byDate.values()].map((row) => ({
304
330
  userId,
305
331
  dateKey: row.dateKey,
306
332
  timezone,
@@ -308,7 +334,11 @@ function syncDailyActivity(userId, scopedRewards, timezone) {
308
334
  eventCount: row.eventCount,
309
335
  firstRewardEventId: row.firstRewardEventId,
310
336
  lastRewardEventId: row.lastRewardEventId
311
- })));
337
+ }));
338
+ }
339
+ function syncDailyActivity(userId, scopedRewards, timezone) {
340
+ const rows = deriveDailyActivityRows(userId, scopedRewards, timezone);
341
+ replaceGamificationDailyActivity(userId, rows);
312
342
  return listGamificationDailyActivity(userId);
313
343
  }
314
344
  function calculateStreakFromActivity(activeDateKeys, now, timezone) {
@@ -695,14 +725,16 @@ function evaluateCatalogItem(item, metricValues) {
695
725
  return evaluateRequirement(item.requirement, metricValues);
696
726
  }
697
727
  function syncCatalog(input) {
698
- const userId = input.scope.userIds[0] ?? "aggregate";
728
+ const userId = input.persistableUserId;
699
729
  const nowIso = input.now.toISOString();
700
730
  const catalogItemIds = new Set(GAMIFICATION_CATALOG.map((item) => item.id));
701
- const existingUnlocks = listGamificationUnlocks(userId).filter((unlock) => catalogItemIds.has(unlock.itemId));
731
+ const existingUnlocks = userId
732
+ ? listGamificationUnlocks(userId).filter((unlock) => catalogItemIds.has(unlock.itemId))
733
+ : [];
702
734
  const isInitialBackfill = existingUnlocks.length === 0;
703
735
  for (const item of GAMIFICATION_CATALOG) {
704
736
  const evaluation = evaluateCatalogItem(item, input.metricValues);
705
- if (evaluation.met) {
737
+ if (userId && evaluation.met) {
706
738
  const inserted = insertGamificationUnlock({
707
739
  userId,
708
740
  itemId: item.id,
@@ -731,7 +763,7 @@ function syncCatalog(input) {
731
763
  }
732
764
  }
733
765
  }
734
- if (input.profile.level > 1) {
766
+ if (userId && input.profile.level > 1) {
735
767
  enqueueGamificationCelebration({
736
768
  id: `gce_${userId}_level_${input.profile.level}`,
737
769
  userId,
@@ -747,7 +779,7 @@ function syncCatalog(input) {
747
779
  createdAt: nowIso
748
780
  });
749
781
  }
750
- if (input.mascot.missedDays > 0) {
782
+ if (userId && input.mascot.missedDays > 0) {
751
783
  enqueueGamificationCelebration({
752
784
  id: `gce_${userId}_comeback_pressure_${input.mascot.lastActiveDateKey ?? "none"}`,
753
785
  userId,
@@ -762,15 +794,16 @@ function syncCatalog(input) {
762
794
  createdAt: nowIso
763
795
  });
764
796
  }
765
- const unlocksByItemId = new Map(listGamificationUnlocks(userId)
797
+ const unlocksByItemId = new Map((userId ? listGamificationUnlocks(userId) : existingUnlocks)
766
798
  .filter((unlock) => catalogItemIds.has(unlock.itemId))
767
799
  .map((unlock) => [unlock.itemId, unlock]));
768
800
  const entries = GAMIFICATION_CATALOG.map((item) => {
769
801
  const evaluation = evaluateCatalogItem(item, input.metricValues);
770
802
  const unlock = unlocksByItemId.get(item.id);
803
+ const readOnlyAggregateUnlock = !userId && evaluation.met;
771
804
  return {
772
805
  ...item,
773
- unlocked: Boolean(unlock),
806
+ unlocked: Boolean(unlock) || readOnlyAggregateUnlock,
774
807
  unlockedAt: unlock?.unlockedAt ?? null,
775
808
  progressCurrent: Math.max(0, Math.min(evaluation.current, evaluation.target)),
776
809
  progressTarget: evaluation.target,
@@ -819,11 +852,13 @@ function syncCatalog(input) {
819
852
  function buildGamificationState(goals, tasks, habits, options = {}) {
820
853
  const now = options.now ?? new Date();
821
854
  const scope = resolveGamificationScope(options.userIds);
855
+ const persistableUserId = persistableUserIdForScope(scope);
822
856
  syncEntityCreationRewards(scope);
823
857
  const scopedRewards = loadScopedRewardEvents(scope);
824
858
  const timezone = resolveTimezone();
825
- const primaryUserId = scope.userIds[0] ?? "aggregate";
826
- const dailyActivity = syncDailyActivity(primaryUserId, scopedRewards, timezone);
859
+ const dailyActivity = persistableUserId
860
+ ? syncDailyActivity(persistableUserId, scopedRewards, timezone)
861
+ : deriveDailyActivityRows("aggregate", scopedRewards, timezone);
827
862
  const activeDateKeys = dailyActivity.map((row) => row.dateKey);
828
863
  const activeDateSet = new Set(activeDateKeys);
829
864
  const streakDays = calculateStreakFromActivity(activeDateSet, now, timezone);
@@ -871,7 +906,9 @@ function buildGamificationState(goals, tasks, habits, options = {}) {
871
906
  topGoalId: topGoal?.goalId ?? null,
872
907
  topGoalTitle: topGoal?.goalTitle ?? null
873
908
  });
874
- const equipment = getGamificationEquipment(primaryUserId);
909
+ const equipment = persistableUserId
910
+ ? getGamificationEquipment(persistableUserId)
911
+ : emptyGamificationEquipment();
875
912
  const metricValues = buildMetricValues({
876
913
  scope,
877
914
  profile,
@@ -888,6 +925,7 @@ function buildGamificationState(goals, tasks, habits, options = {}) {
888
925
  });
889
926
  const catalog = syncCatalog({
890
927
  scope,
928
+ persistableUserId,
891
929
  profile,
892
930
  metricValues,
893
931
  equipment,
@@ -901,7 +939,8 @@ function buildGamificationState(goals, tasks, habits, options = {}) {
901
939
  metricValues,
902
940
  equipment,
903
941
  mascot,
904
- catalog
942
+ catalog,
943
+ persistableUserId
905
944
  };
906
945
  }
907
946
  export function buildGamificationProfile(goals, tasks, habits, now = new Date(), options = {}) {
@@ -910,12 +949,8 @@ export function buildGamificationProfile(goals, tasks, habits, now = new Date(),
910
949
  now
911
950
  }).profile;
912
951
  }
913
- export function buildAchievementSignals(goals, tasks, habits, now = new Date(), options = {}) {
914
- const state = buildGamificationState(goals, tasks, habits, {
915
- userIds: options.userIds,
916
- now
917
- });
918
- const profile = state.profile;
952
+ function buildAchievementSignalsFromProfile(input) {
953
+ const { goals, tasks, habits, now, profile } = input;
919
954
  const doneTasks = tasks.filter((task) => task.status === "done");
920
955
  const alignedDoneTasks = doneTasks.filter((task) => task.goalId !== null && task.tagIds.length > 0);
921
956
  const focusTasks = tasks.filter((task) => task.status === "focus" || task.status === "in_progress");
@@ -1000,8 +1035,21 @@ export function buildAchievementSignals(goals, tasks, habits, now = new Date(),
1000
1035
  }
1001
1036
  ].map((achievement) => achievementSignalSchema.parse(achievement));
1002
1037
  }
1003
- export function buildMilestoneRewards(goals, tasks, habits, now = new Date(), options = {}) {
1004
- const profile = buildGamificationProfile(goals, tasks, habits, now, options);
1038
+ export function buildAchievementSignals(goals, tasks, habits, now = new Date(), options = {}) {
1039
+ const state = buildGamificationState(goals, tasks, habits, {
1040
+ userIds: options.userIds,
1041
+ now
1042
+ });
1043
+ return buildAchievementSignalsFromProfile({
1044
+ goals,
1045
+ tasks,
1046
+ habits,
1047
+ now,
1048
+ profile: state.profile
1049
+ });
1050
+ }
1051
+ function buildMilestoneRewardsFromProfile(input) {
1052
+ const { goals, tasks, habits, now, profile } = input;
1005
1053
  const doneTasks = tasks.filter((task) => task.status === "done");
1006
1054
  const topGoal = profile.topGoalId
1007
1055
  ? goals.find((goal) => goal.id === profile.topGoalId) ?? null
@@ -1071,10 +1119,12 @@ export function buildMilestoneRewards(goals, tasks, habits, now = new Date(), op
1071
1119
  }
1072
1120
  ].map((reward) => milestoneRewardSchema.parse(reward));
1073
1121
  }
1074
- export function buildXpMomentumPulse(goals, tasks, habits, now = new Date(), options = {}) {
1122
+ export function buildMilestoneRewards(goals, tasks, habits, now = new Date(), options = {}) {
1075
1123
  const profile = buildGamificationProfile(goals, tasks, habits, now, options);
1076
- const achievements = buildAchievementSignals(goals, tasks, habits, now, options);
1077
- const milestoneRewards = buildMilestoneRewards(goals, tasks, habits, now, options);
1124
+ return buildMilestoneRewardsFromProfile({ goals, tasks, habits, now, profile });
1125
+ }
1126
+ function buildXpMomentumPulseFromParts(input) {
1127
+ const { profile, achievements, milestoneRewards } = input;
1078
1128
  const nextMilestone = milestoneRewards.find((reward) => !reward.completed) ??
1079
1129
  milestoneRewards[0] ??
1080
1130
  null;
@@ -1106,6 +1156,31 @@ export function buildXpMomentumPulse(goals, tasks, habits, now = new Date(), opt
1106
1156
  nextMilestoneLabel: nextMilestone?.rewardLabel ?? "Keep building visible momentum"
1107
1157
  };
1108
1158
  }
1159
+ export function buildXpMomentumPulse(goals, tasks, habits, now = new Date(), options = {}) {
1160
+ const state = buildGamificationState(goals, tasks, habits, {
1161
+ userIds: options.userIds,
1162
+ now
1163
+ });
1164
+ const achievements = buildAchievementSignalsFromProfile({
1165
+ goals,
1166
+ tasks,
1167
+ habits,
1168
+ now,
1169
+ profile: state.profile
1170
+ });
1171
+ const milestoneRewards = buildMilestoneRewardsFromProfile({
1172
+ goals,
1173
+ tasks,
1174
+ habits,
1175
+ now,
1176
+ profile: state.profile
1177
+ });
1178
+ return buildXpMomentumPulseFromParts({
1179
+ profile: state.profile,
1180
+ achievements,
1181
+ milestoneRewards
1182
+ });
1183
+ }
1109
1184
  export function buildGamificationCatalogPayload(goals, tasks, habits, options = {}) {
1110
1185
  return buildGamificationState(goals, tasks, habits, options).catalog;
1111
1186
  }
@@ -1146,7 +1221,7 @@ export function updateGamificationEquipmentSelection(input) {
1146
1221
  const state = buildGamificationState(input.goals, input.tasks, input.habits, {
1147
1222
  userIds: input.userIds
1148
1223
  });
1149
- const userId = state.scope.userIds[0];
1224
+ const userId = state.persistableUserId;
1150
1225
  if (!userId) {
1151
1226
  throw new LockedGamificationCosmeticError("Equipment can only be changed for a concrete Forge user.");
1152
1227
  }
@@ -1166,10 +1241,28 @@ export function updateGamificationEquipmentSelection(input) {
1166
1241
  return upsertGamificationEquipment(userId, input.equipment);
1167
1242
  }
1168
1243
  export function buildGamificationOverview(goals, tasks, habits, now = new Date(), options = {}) {
1244
+ const state = buildGamificationState(goals, tasks, habits, {
1245
+ userIds: options.userIds,
1246
+ now
1247
+ });
1248
+ const achievements = buildAchievementSignalsFromProfile({
1249
+ goals,
1250
+ tasks,
1251
+ habits,
1252
+ now,
1253
+ profile: state.profile
1254
+ });
1255
+ const milestoneRewards = buildMilestoneRewardsFromProfile({
1256
+ goals,
1257
+ tasks,
1258
+ habits,
1259
+ now,
1260
+ profile: state.profile
1261
+ });
1169
1262
  return {
1170
- profile: buildGamificationProfile(goals, tasks, habits, now, options),
1171
- achievements: buildAchievementSignals(goals, tasks, habits, now, options),
1172
- milestoneRewards: buildMilestoneRewards(goals, tasks, habits, now, options)
1263
+ profile: state.profile,
1264
+ achievements,
1265
+ milestoneRewards
1173
1266
  };
1174
1267
  }
1175
1268
  export function buildXpMetricsPayloadModel(input) {
@@ -1182,8 +1275,25 @@ export function buildXpMetricsPayloadModel(input) {
1182
1275
  const dailyAmbientCap = rules
1183
1276
  .filter((rule) => rule.family === "ambient")
1184
1277
  .reduce((max, rule) => Math.max(max, Number(rule.config.dailyCap ?? 0)), 0) || 12;
1185
- const achievements = buildAchievementSignals(input.goals, input.tasks, input.habits, now, { userIds: input.userIds });
1186
- const milestoneRewards = buildMilestoneRewards(input.goals, input.tasks, input.habits, now, { userIds: input.userIds });
1278
+ const achievements = buildAchievementSignalsFromProfile({
1279
+ goals: input.goals,
1280
+ tasks: input.tasks,
1281
+ habits: input.habits,
1282
+ now,
1283
+ profile: state.profile
1284
+ });
1285
+ const milestoneRewards = buildMilestoneRewardsFromProfile({
1286
+ goals: input.goals,
1287
+ tasks: input.tasks,
1288
+ habits: input.habits,
1289
+ now,
1290
+ profile: state.profile
1291
+ });
1292
+ const momentumPulse = buildXpMomentumPulseFromParts({
1293
+ profile: state.profile,
1294
+ achievements,
1295
+ milestoneRewards
1296
+ });
1187
1297
  const visibleCatalog = [
1188
1298
  ...(state.catalog.newestUnlock ? [state.catalog.newestUnlock] : []),
1189
1299
  ...(state.catalog.nextUnlock ? [state.catalog.nextUnlock] : []),
@@ -1198,7 +1308,7 @@ export function buildXpMetricsPayloadModel(input) {
1198
1308
  profile: state.profile,
1199
1309
  achievements,
1200
1310
  milestoneRewards,
1201
- momentumPulse: buildXpMomentumPulse(input.goals, input.tasks, input.habits, now, { userIds: input.userIds }),
1311
+ momentumPulse,
1202
1312
  catalogPreview: uniquePreview,
1203
1313
  unlockedItemCount: state.catalog.unlockedCount,
1204
1314
  totalItemCount: state.catalog.totalCount,
@@ -1207,7 +1317,9 @@ export function buildXpMetricsPayloadModel(input) {
1207
1317
  nextTargets: state.catalog.nextTargets,
1208
1318
  equipment: state.equipment,
1209
1319
  mascot: state.mascot,
1210
- celebrations: listUnseenGamificationCelebrations(state.scope.userIds[0] ?? "aggregate", 5),
1320
+ celebrations: state.persistableUserId
1321
+ ? listUnseenGamificationCelebrations(state.persistableUserId, 5)
1322
+ : [],
1211
1323
  recentLedger: state.scopedRewards
1212
1324
  .slice(-25)
1213
1325
  .reverse()