forge-openclaw-plugin 0.2.98 → 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
@@ -4101,6 +4101,10 @@ export function buildOpenApiDocument() {
4101
4101
  "reviewShortcutRule",
4102
4102
  "readModelWriteRule",
4103
4103
  "psycheOpeningQuestionRule",
4104
+ "psycheHypothesisRule",
4105
+ "mixedIntentSequencingRule",
4106
+ "duplicateDisambiguationRule",
4107
+ "destructiveActionRule",
4104
4108
  "followUpQuestionRule",
4105
4109
  "antiDriftRule",
4106
4110
  "duplicateCheckRoute",
@@ -4118,6 +4122,10 @@ export function buildOpenApiDocument() {
4118
4122
  reviewShortcutRule: { type: "string" },
4119
4123
  readModelWriteRule: { type: "string" },
4120
4124
  psycheOpeningQuestionRule: { type: "string" },
4125
+ psycheHypothesisRule: { type: "string" },
4126
+ mixedIntentSequencingRule: { type: "string" },
4127
+ duplicateDisambiguationRule: { type: "string" },
4128
+ destructiveActionRule: { type: "string" },
4121
4129
  followUpQuestionRule: { type: "string" },
4122
4130
  antiDriftRule: { type: "string" },
4123
4131
  duplicateCheckRoute: { type: "string" },
@@ -5216,6 +5224,139 @@ export function buildOpenApiDocument() {
5216
5224
  targetModel: { type: "object", additionalProperties: true }
5217
5225
  }
5218
5226
  };
5227
+ const nutritionMealItemInput = {
5228
+ type: "object",
5229
+ additionalProperties: false,
5230
+ required: ["name", "quantity"],
5231
+ properties: {
5232
+ name: { type: "string" },
5233
+ brand: nullable({ type: "string" }),
5234
+ quantity: { type: "number" },
5235
+ unit: nullable({ type: "string" }),
5236
+ calories: nullable({ type: "number" }),
5237
+ proteinGrams: nullable({ type: "number" }),
5238
+ carbohydrateGrams: nullable({ type: "number" }),
5239
+ fatGrams: nullable({ type: "number" }),
5240
+ fiberGrams: nullable({ type: "number" }),
5241
+ sugarGrams: nullable({ type: "number" }),
5242
+ sodiumMg: nullable({ type: "number" }),
5243
+ potassiumMg: nullable({ type: "number" }),
5244
+ caffeineMg: nullable({ type: "number" }),
5245
+ alcoholGrams: nullable({ type: "number" }),
5246
+ tags: arrayOf({ type: "string" }),
5247
+ confidence: nullable({ type: "number" })
5248
+ }
5249
+ };
5250
+ const nutritionFoodLogInput = {
5251
+ type: "object",
5252
+ additionalProperties: false,
5253
+ required: ["items"],
5254
+ properties: {
5255
+ loggedAt: { type: "string", format: "date-time" },
5256
+ mealLabel: nullable({ type: "string" }),
5257
+ source: {
5258
+ type: "string",
5259
+ enum: ["manual", "search", "barcode", "chatgpt", "photo", "saved_meal"]
5260
+ },
5261
+ confirmationState: {
5262
+ type: "string",
5263
+ enum: ["candidate", "confirmed", "needs_review", "discarded"]
5264
+ },
5265
+ userId: { type: "string" },
5266
+ placeId: nullable({ type: "string" }),
5267
+ stayId: nullable({ type: "string" }),
5268
+ workoutId: nullable({ type: "string" }),
5269
+ sleepId: nullable({ type: "string" }),
5270
+ dayKey: nullable({ type: "string" }),
5271
+ imageRefs: arrayOf({ type: "string" }),
5272
+ parserProvenance: { type: "object", additionalProperties: true },
5273
+ satietyScore: nullable({ type: "number" }),
5274
+ hungerBefore: nullable({ type: "number" }),
5275
+ hungerAfter: nullable({ type: "number" }),
5276
+ cravingScore: nullable({ type: "number" }),
5277
+ enjoymentScore: nullable({ type: "number" }),
5278
+ socialContext: nullable({ type: "string" }),
5279
+ locationContext: nullable({ type: "string" }),
5280
+ notes: nullable({ type: "string" }),
5281
+ items: arrayOf(nutritionMealItemInput)
5282
+ }
5283
+ };
5284
+ const nutritionFoodLog = {
5285
+ type: "object",
5286
+ additionalProperties: true,
5287
+ required: ["id", "loggedAt", "items", "totals"],
5288
+ properties: {
5289
+ id: { type: "string" },
5290
+ userId: { type: "string" },
5291
+ loggedAt: { type: "string", format: "date-time" },
5292
+ mealLabel: nullable({ type: "string" }),
5293
+ source: { type: "string" },
5294
+ confirmationState: { type: "string" },
5295
+ totals: { type: "object", additionalProperties: { type: "number" } },
5296
+ items: arrayOf({ type: "object", additionalProperties: true })
5297
+ }
5298
+ };
5299
+ const nutritionFoodSearchResult = {
5300
+ type: "object",
5301
+ additionalProperties: true,
5302
+ required: ["id", "source", "name"],
5303
+ properties: {
5304
+ id: { type: "string" },
5305
+ source: { type: "string" },
5306
+ sourceId: nullable({ type: "string" }),
5307
+ name: { type: "string" },
5308
+ brand: nullable({ type: "string" }),
5309
+ barcode: nullable({ type: "string" }),
5310
+ servingLabel: nullable({ type: "string" }),
5311
+ servingGrams: nullable({ type: "number" }),
5312
+ calories: nullable({ type: "number" }),
5313
+ proteinGrams: nullable({ type: "number" }),
5314
+ carbohydrateGrams: nullable({ type: "number" }),
5315
+ fatGrams: nullable({ type: "number" }),
5316
+ fiberGrams: nullable({ type: "number" }),
5317
+ tags: arrayOf({ type: "string" })
5318
+ }
5319
+ };
5320
+ const weightLossViewData = {
5321
+ type: "object",
5322
+ additionalProperties: false,
5323
+ required: [
5324
+ "userId",
5325
+ "generatedAt",
5326
+ "target",
5327
+ "summary",
5328
+ "todayLedger",
5329
+ "recentMeals",
5330
+ "energyModel",
5331
+ "weightTrend",
5332
+ "foodQuality",
5333
+ "trainingFuel",
5334
+ "subjective",
5335
+ "gut",
5336
+ "hypotheses",
5337
+ "experiments",
5338
+ "dataQuality"
5339
+ ],
5340
+ properties: {
5341
+ userId: { type: "string" },
5342
+ generatedAt: { type: "string", format: "date-time" },
5343
+ target: { type: "object", additionalProperties: true },
5344
+ summary: { type: "object", additionalProperties: true },
5345
+ todayLedger: { type: "object", additionalProperties: true },
5346
+ recentMeals: arrayOf(nutritionFoodLog),
5347
+ bodyCheckins: arrayOf({ type: "object", additionalProperties: true }),
5348
+ appearanceCheckins: arrayOf({ type: "object", additionalProperties: true }),
5349
+ energyModel: { type: "object", additionalProperties: true },
5350
+ weightTrend: { type: "object", additionalProperties: true },
5351
+ foodQuality: { type: "object", additionalProperties: true },
5352
+ trainingFuel: { type: "object", additionalProperties: true },
5353
+ subjective: { type: "object", additionalProperties: true },
5354
+ gut: { type: "object", additionalProperties: true },
5355
+ hypotheses: arrayOf({ type: "object", additionalProperties: true }),
5356
+ experiments: arrayOf({ type: "object", additionalProperties: true }),
5357
+ dataQuality: { type: "object", additionalProperties: true }
5358
+ }
5359
+ };
5219
5360
  const document = {
5220
5361
  openapi: "3.1.0",
5221
5362
  info: {
@@ -5328,6 +5469,11 @@ export function buildOpenApiDocument() {
5328
5469
  SleepViewData: sleepViewData,
5329
5470
  FitnessViewData: fitnessViewData,
5330
5471
  TrainingLoadViewData: trainingLoadViewData,
5472
+ NutritionMealItemInput: nutritionMealItemInput,
5473
+ NutritionFoodLogInput: nutritionFoodLogInput,
5474
+ NutritionFoodLog: nutritionFoodLog,
5475
+ NutritionFoodSearchResult: nutritionFoodSearchResult,
5476
+ WeightLossViewData: weightLossViewData,
5331
5477
  PsycheMetricsViewData: psycheMetricsViewData,
5332
5478
  PsycheOverviewPayload: psycheOverviewPayload,
5333
5479
  Insight: insight,
@@ -5635,6 +5781,309 @@ export function buildOpenApiDocument() {
5635
5781
  }
5636
5782
  }
5637
5783
  },
5784
+ "/api/v1/health/weight-loss": {
5785
+ get: {
5786
+ tags: ["Health"],
5787
+ summary: "Read the Forge nutrition, weight-loss, food-effect, and body insight surface",
5788
+ responses: {
5789
+ "200": jsonResponse({
5790
+ type: "object",
5791
+ required: ["weightLoss"],
5792
+ properties: {
5793
+ weightLoss: {
5794
+ $ref: "#/components/schemas/WeightLossViewData"
5795
+ }
5796
+ }
5797
+ }, "Weight loss overview")
5798
+ }
5799
+ }
5800
+ },
5801
+ "/api/v1/health/weight-loss/target": {
5802
+ patch: {
5803
+ tags: ["Health"],
5804
+ summary: "Update nutrition and weight-loss targets",
5805
+ requestBody: {
5806
+ content: {
5807
+ "application/json": {
5808
+ schema: { type: "object", additionalProperties: true }
5809
+ }
5810
+ }
5811
+ },
5812
+ responses: {
5813
+ "200": jsonResponse({
5814
+ type: "object",
5815
+ required: ["target"],
5816
+ properties: {
5817
+ target: { type: "object", additionalProperties: true }
5818
+ }
5819
+ }, "Updated nutrition target")
5820
+ }
5821
+ }
5822
+ },
5823
+ "/api/v1/health/weight-loss/foods/search": {
5824
+ post: {
5825
+ tags: ["Health"],
5826
+ summary: "Search nutrition foods across local and public catalogs",
5827
+ requestBody: {
5828
+ content: {
5829
+ "application/json": {
5830
+ schema: {
5831
+ type: "object",
5832
+ additionalProperties: false,
5833
+ required: ["query"],
5834
+ properties: {
5835
+ query: { type: "string" },
5836
+ limit: { type: "integer", minimum: 1, maximum: 30 }
5837
+ }
5838
+ }
5839
+ }
5840
+ }
5841
+ },
5842
+ responses: {
5843
+ "200": jsonResponse({
5844
+ type: "object",
5845
+ required: ["foods"],
5846
+ properties: {
5847
+ foods: arrayOf({
5848
+ $ref: "#/components/schemas/NutritionFoodSearchResult"
5849
+ })
5850
+ }
5851
+ }, "Nutrition food search results")
5852
+ }
5853
+ }
5854
+ },
5855
+ "/api/v1/health/weight-loss/foods/barcode": {
5856
+ post: {
5857
+ tags: ["Health"],
5858
+ summary: "Lookup one nutrition food by barcode",
5859
+ requestBody: {
5860
+ content: {
5861
+ "application/json": {
5862
+ schema: {
5863
+ type: "object",
5864
+ additionalProperties: false,
5865
+ required: ["barcode"],
5866
+ properties: { barcode: { type: "string" } }
5867
+ }
5868
+ }
5869
+ }
5870
+ },
5871
+ responses: {
5872
+ "200": jsonResponse({
5873
+ type: "object",
5874
+ required: ["food"],
5875
+ properties: {
5876
+ food: nullable({
5877
+ $ref: "#/components/schemas/NutritionFoodSearchResult"
5878
+ })
5879
+ }
5880
+ }, "Nutrition barcode lookup result")
5881
+ }
5882
+ }
5883
+ },
5884
+ "/api/v1/health/weight-loss/food-logs": {
5885
+ post: {
5886
+ tags: ["Health"],
5887
+ summary: "Create a nutrition food log",
5888
+ requestBody: {
5889
+ content: {
5890
+ "application/json": {
5891
+ schema: { $ref: "#/components/schemas/NutritionFoodLogInput" }
5892
+ }
5893
+ }
5894
+ },
5895
+ responses: {
5896
+ "201": jsonResponse({
5897
+ type: "object",
5898
+ required: ["log"],
5899
+ properties: {
5900
+ log: { $ref: "#/components/schemas/NutritionFoodLog" }
5901
+ }
5902
+ }, "Created nutrition food log")
5903
+ }
5904
+ }
5905
+ },
5906
+ "/api/v1/health/weight-loss/food-logs/{id}": {
5907
+ patch: {
5908
+ tags: ["Health"],
5909
+ summary: "Patch a nutrition food log",
5910
+ responses: {
5911
+ "200": jsonResponse({
5912
+ type: "object",
5913
+ required: ["log"],
5914
+ properties: {
5915
+ log: { $ref: "#/components/schemas/NutritionFoodLog" }
5916
+ }
5917
+ }, "Updated nutrition food log")
5918
+ }
5919
+ },
5920
+ delete: {
5921
+ tags: ["Health"],
5922
+ summary: "Delete a nutrition food log",
5923
+ responses: {
5924
+ "200": jsonResponse({
5925
+ type: "object",
5926
+ required: ["deleted"],
5927
+ properties: { deleted: { type: "boolean" } }
5928
+ }, "Deleted nutrition food log")
5929
+ }
5930
+ }
5931
+ },
5932
+ "/api/v1/health/weight-loss/parse": {
5933
+ post: {
5934
+ tags: ["Health"],
5935
+ summary: "Parse a food log through Forge's openai-codex ChatGPT subscription connection",
5936
+ requestBody: {
5937
+ content: {
5938
+ "application/json": {
5939
+ schema: {
5940
+ type: "object",
5941
+ additionalProperties: false,
5942
+ properties: {
5943
+ text: { type: "string" },
5944
+ imageDescription: { type: "string" },
5945
+ loggedAt: { type: "string", format: "date-time" },
5946
+ mealLabel: { type: "string" }
5947
+ }
5948
+ }
5949
+ }
5950
+ }
5951
+ },
5952
+ responses: {
5953
+ "201": jsonResponse({
5954
+ type: "object",
5955
+ required: [
5956
+ "candidate",
5957
+ "log",
5958
+ "clarificationQuestions",
5959
+ "uncertaintyReasons"
5960
+ ],
5961
+ properties: {
5962
+ candidate: {
5963
+ $ref: "#/components/schemas/NutritionFoodLogInput"
5964
+ },
5965
+ log: nullable({
5966
+ $ref: "#/components/schemas/NutritionFoodLog"
5967
+ }),
5968
+ clarificationQuestions: arrayOf({ type: "string" }),
5969
+ uncertaintyReasons: arrayOf({ type: "string" })
5970
+ }
5971
+ }, "Parsed candidate nutrition food log")
5972
+ }
5973
+ }
5974
+ },
5975
+ "/api/v1/health/weight-loss/body-checkins": {
5976
+ post: {
5977
+ tags: ["Health"],
5978
+ summary: "Create a body-composition check-in",
5979
+ responses: {
5980
+ "201": jsonResponse({
5981
+ type: "object",
5982
+ required: ["checkin"],
5983
+ properties: {
5984
+ checkin: { type: "object", additionalProperties: true }
5985
+ }
5986
+ }, "Created body check-in")
5987
+ }
5988
+ }
5989
+ },
5990
+ "/api/v1/health/weight-loss/appearance-checkins": {
5991
+ post: {
5992
+ tags: ["Health"],
5993
+ summary: "Create an aesthetic appearance check-in",
5994
+ responses: {
5995
+ "201": jsonResponse({
5996
+ type: "object",
5997
+ required: ["checkin"],
5998
+ properties: {
5999
+ checkin: { type: "object", additionalProperties: true }
6000
+ }
6001
+ }, "Created appearance check-in")
6002
+ }
6003
+ }
6004
+ },
6005
+ "/api/v1/health/weight-loss/subjective-checkins": {
6006
+ post: {
6007
+ tags: ["Health"],
6008
+ summary: "Create a subjective food-effect check-in",
6009
+ responses: {
6010
+ "201": jsonResponse({
6011
+ type: "object",
6012
+ required: ["checkin"],
6013
+ properties: {
6014
+ checkin: { type: "object", additionalProperties: true }
6015
+ }
6016
+ }, "Created subjective check-in")
6017
+ }
6018
+ }
6019
+ },
6020
+ "/api/v1/health/weight-loss/gut-checkins": {
6021
+ post: {
6022
+ tags: ["Health"],
6023
+ summary: "Create a gut-health food-effect check-in",
6024
+ responses: {
6025
+ "201": jsonResponse({
6026
+ type: "object",
6027
+ required: ["checkin"],
6028
+ properties: {
6029
+ checkin: { type: "object", additionalProperties: true }
6030
+ }
6031
+ }, "Created gut check-in")
6032
+ }
6033
+ }
6034
+ },
6035
+ "/api/v1/health/weight-loss/patterns": {
6036
+ get: {
6037
+ tags: ["Health"],
6038
+ summary: "Read current nutrition hypotheses and experiments",
6039
+ responses: {
6040
+ "200": jsonResponse({
6041
+ type: "object",
6042
+ required: ["hypotheses", "experiments"],
6043
+ properties: {
6044
+ hypotheses: arrayOf({
6045
+ type: "object",
6046
+ additionalProperties: true
6047
+ }),
6048
+ experiments: arrayOf({
6049
+ type: "object",
6050
+ additionalProperties: true
6051
+ })
6052
+ }
6053
+ }, "Nutrition pattern candidates")
6054
+ }
6055
+ }
6056
+ },
6057
+ "/api/v1/health/weight-loss/experiments": {
6058
+ post: {
6059
+ tags: ["Health"],
6060
+ summary: "Create a nutrition N-of-1 experiment",
6061
+ responses: {
6062
+ "201": jsonResponse({
6063
+ type: "object",
6064
+ required: ["experiment"],
6065
+ properties: {
6066
+ experiment: { type: "object", additionalProperties: true }
6067
+ }
6068
+ }, "Created nutrition experiment")
6069
+ }
6070
+ }
6071
+ },
6072
+ "/api/v1/health/weight-loss/experiments/{id}": {
6073
+ patch: {
6074
+ tags: ["Health"],
6075
+ summary: "Patch a nutrition N-of-1 experiment",
6076
+ responses: {
6077
+ "200": jsonResponse({
6078
+ type: "object",
6079
+ required: ["experiment"],
6080
+ properties: {
6081
+ experiment: { type: "object", additionalProperties: true }
6082
+ }
6083
+ }, "Updated nutrition experiment")
6084
+ }
6085
+ }
6086
+ },
5638
6087
  "/api/v1/health/workouts": {
5639
6088
  post: {
5640
6089
  summary: "Create one manual workout session",
@@ -2,7 +2,6 @@ import { listActivityEvents } from "../repositories/activity-events.js";
2
2
  import { listRewardLedger } from "../repositories/rewards.js";
3
3
  import { listTags, listTagsByIds } from "../repositories/tags.js";
4
4
  import { getDashboard } from "./dashboard.js";
5
- import { buildAchievementSignals, buildGamificationProfile, buildMilestoneRewards } from "./gamification.js";
6
5
  import { overviewContextSchema, riskContextSchema, todayContextSchema } from "../types.js";
7
6
  function priorityWeight(task) {
8
7
  switch (task.priority) {
@@ -105,7 +104,7 @@ function buildDomainBalance(goals, tasks) {
105
104
  return [...domainRows.values()].sort((left, right) => right.completedPoints - left.completedPoints);
106
105
  }
107
106
  export function getOverviewContext(now = new Date(), options = {}) {
108
- const dashboard = getDashboard(options);
107
+ const dashboard = options.dashboard ?? getDashboard({ userIds: options.userIds });
109
108
  const focusTasks = dashboard.tasks.filter((task) => task.status === "focus" || task.status === "in_progress").length;
110
109
  const overdueTasks = dashboard.tasks.filter((task) => task.status !== "done" && task.dueDate !== null && task.dueDate < now.toISOString().slice(0, 10)).length;
111
110
  const dueHabits = dashboard.habits.filter((habit) => habit.dueToday).slice(0, 6);
@@ -129,17 +128,17 @@ export function getOverviewContext(now = new Date(), options = {}) {
129
128
  topTasks: sortStrategicTasks(tasks.filter((task) => task.status !== "done")).slice(0, 6),
130
129
  dueHabits,
131
130
  recentEvidence: listActivityEvents({ limit: 12, userIds: options.userIds }),
132
- achievements: buildAchievementSignals(goals, tasks, habits, now),
131
+ achievements: dashboard.achievements,
133
132
  domainBalance: buildDomainBalance(goals, tasks),
134
133
  neglectedGoals: buildNeglectedGoals(goals, tasks, now)
135
134
  });
136
135
  }
137
136
  export function getTodayContext(now = new Date(), options = {}) {
138
- const dashboard = getDashboard(options);
137
+ const dashboard = options.dashboard ?? getDashboard({ userIds: options.userIds });
139
138
  const goals = dashboard.goals;
140
139
  const tasks = dashboard.tasks;
141
140
  const habits = dashboard.habits;
142
- const gamification = buildGamificationProfile(goals, tasks, habits, now);
141
+ const gamification = dashboard.gamification;
143
142
  const inProgressTasks = sortStrategicTasks(tasks.filter((task) => task.status === "in_progress")).slice(0, 4);
144
143
  const readyTasks = sortStrategicTasks(tasks.filter((task) => task.status === "focus" || task.status === "backlog")).slice(0, 4);
145
144
  const deferredTasks = sortStrategicTasks(tasks.filter((task) => task.status === "blocked")).slice(0, 4);
@@ -193,7 +192,7 @@ export function getTodayContext(now = new Date(), options = {}) {
193
192
  }
194
193
  ],
195
194
  dueHabits,
196
- milestoneRewards: buildMilestoneRewards(goals, tasks, habits, now),
195
+ milestoneRewards: dashboard.milestoneRewards,
197
196
  recentHabitRewards: listRewardLedger({ entityType: "habit", limit: 8 }),
198
197
  momentum: {
199
198
  streakDays: gamification.streakDays,
@@ -207,7 +206,7 @@ export function getTodayContext(now = new Date(), options = {}) {
207
206
  });
208
207
  }
209
208
  export function getRiskContext(now = new Date(), options = {}) {
210
- const dashboard = getDashboard(options);
209
+ const dashboard = options.dashboard ?? getDashboard({ userIds: options.userIds });
211
210
  const tasks = dashboard.tasks;
212
211
  const goals = dashboard.goals;
213
212
  const overdueTasks = sortStrategicTasks(tasks.filter((task) => task.status !== "done" && task.dueDate !== null && task.dueDate < now.toISOString().slice(0, 10))).slice(0, 8);
@@ -464,7 +464,39 @@ function strategyJsonChecks() {
464
464
  function rewardAndGamificationChecks(settings) {
465
465
  const checks = [];
466
466
  const catalogItemIds = new Set(GAMIFICATION_CATALOG.map((item) => item.id));
467
- const equipmentItemIds = new Set(GAMIFICATION_CATALOG.filter((item) => item.kind === "unlock").map((item) => item.id));
467
+ const equipmentValueFields = [
468
+ {
469
+ column: "selected_mascot_skin",
470
+ unlockType: "mascot_skin",
471
+ payloadKey: "mascotSkin"
472
+ },
473
+ {
474
+ column: "selected_hud_treatment",
475
+ unlockType: "hud_treatment",
476
+ payloadKey: "hudTreatment"
477
+ },
478
+ {
479
+ column: "selected_streak_effect",
480
+ unlockType: "streak_effect",
481
+ payloadKey: "streakEffect"
482
+ },
483
+ {
484
+ column: "selected_trophy_shelf",
485
+ unlockType: "trophy_shelf",
486
+ payloadKey: "trophyShelf"
487
+ },
488
+ {
489
+ column: "selected_celebration_variant",
490
+ unlockType: "celebration_variant",
491
+ payloadKey: "celebrationVariant"
492
+ }
493
+ ];
494
+ const equipmentValuesByColumn = new Map(equipmentValueFields.map((field) => [
495
+ field.column,
496
+ new Set(GAMIFICATION_CATALOG.filter((item) => item.kind === "unlock" &&
497
+ item.unlockType === field.unlockType &&
498
+ typeof item.rewardPayload[field.payloadKey] === "string").map((item) => item.rewardPayload[field.payloadKey]))
499
+ ]));
468
500
  checks.push(safeCountCheck({
469
501
  id: "rewards.rules.missing",
470
502
  group: "Rewards",
@@ -532,7 +564,10 @@ function rewardAndGamificationChecks(settings) {
532
564
  : [];
533
565
  const staleEquipment = equipmentRows.reduce((count, row) => {
534
566
  return (count +
535
- Object.values(row).filter((value) => typeof value === "string" && !equipmentItemIds.has(value)).length);
567
+ Object.entries(row).filter(([column, value]) => typeof value === "string" &&
568
+ !(equipmentValuesByColumn
569
+ .get(column)
570
+ ?.has(value) ?? false)).length);
536
571
  }, 0);
537
572
  checks.push(check({
538
573
  id: "rewards.gamification.equipment",
@@ -541,8 +576,8 @@ function rewardAndGamificationChecks(settings) {
541
576
  passed: staleEquipment === 0,
542
577
  severity: "warning",
543
578
  summary: staleEquipment === 0
544
- ? "Selected gamification equipment points to current unlock catalog items."
545
- : `${staleEquipment} selected equipment reference${staleEquipment === 1 ? "" : "s"} point to removed catalog items.`,
579
+ ? "Selected gamification equipment values match current unlock reward payloads."
580
+ : `${staleEquipment} selected equipment value${staleEquipment === 1 ? "" : "s"} point to removed catalog rewards.`,
546
581
  affectedCount: staleEquipment
547
582
  }));
548
583
  return checks.concat(check({