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
@@ -67,9 +67,10 @@ import { createManagerRuntime } from "./managers/runtime.js";
67
67
  import { isManagerError } from "./managers/type-guards.js";
68
68
  import { buildCompanionPairingTransport, getCompanionIrohStatus, stopCompanionIroh } from "./services/companion-iroh.js";
69
69
  import { createCompanionPairingSession, createCompanionPairingSessionSchema, createSleepSession, createSleepSessionSchema, createWorkoutSession, createWorkoutSessionSchema, deleteSleepSession, deleteWorkoutSession, getCompanionPairingSessionById, getCompanionOverview, getFitnessViewData, getSleepSessionById, getSleepSessionDetailById, getSleepTimelineOverlaysForRange, getSleepViewData, getTrainingLoadViewData, getVitalsViewData, getHealthZoneProfileForUser, getMobileHealthSyncSessionStatus, getWorkoutSessionById, getWorkoutSessionDetailById, heartbeatCompanionPairing, heartbeatCompanionPairingSchema, healthZoneProfilePatchSchema, abortMobileHealthSyncSession, completeMobileHealthSyncSession, ingestMobileHealthSync, ingestMobileHealthSyncChunk, mobileHealthSyncChunkSchema, mobileHealthSyncSessionCompleteSchema, mobileHealthSyncSessionStartSchema, mobileHealthSyncSchema, patchHealthZoneProfileForUser, patchCompanionPairingSourceState, patchCompanionPairingSourceStateSchema, companionSourceKeySchema, requireValidPairing, startMobileHealthSyncSession, revokeAllCompanionPairingSessions, revokeAllCompanionPairingSessionsSchema, revokeCompanionPairingSession, updateMobileCompanionSourceState, updateMobileCompanionSourceStateSchema, verifyCompanionPairing, verifyCompanionPairingSchema, updateSleepMetadata, updateSleepMetadataSchema, updateWorkoutMetadata, updateWorkoutMetadataSchema } from "./health.js";
70
+ import { createNutritionAppearanceCheckin, createNutritionBodyCheckin, createNutritionExperiment, createNutritionFoodLog, createNutritionGutCheckin, createNutritionSubjectiveCheckin, deleteNutritionFoodLog, getWeightLossViewData, lookupNutritionBarcode, nutritionAppearanceCheckinCreateSchema, nutritionBarcodeLookupSchema, nutritionBodyCheckinCreateSchema, nutritionExperimentCreateSchema, nutritionExperimentPatchSchema, nutritionFoodLogCreateSchema, nutritionFoodLogPatchSchema, nutritionFoodSearchSchema, nutritionGutCheckinCreateSchema, nutritionParseRequestSchema, nutritionSubjectiveCheckinCreateSchema, nutritionTargetUpdateSchema, parseNutritionFoodLogWithChatGpt, patchNutritionExperiment, patchNutritionFoodLog, searchNutritionFoods, updateNutritionTarget } from "./health-weight-loss.js";
70
71
  import { analyzeMovementUserBoxPreflight, createMovementUserBox, createMovementPlace, deleteMovementUserBox, getMovementAllTimeSummary, getMovementBoxDetail, getMovementDayDetail, getMovementMobileBootstrap, getMovementTimeline, getMovementSelectionAggregate, getMovementSettings, getMovementTripDetail, getMovementMonthSummary, invalidateAutomaticMovementBox, listMovementPlaces, movementAutomaticBoxInvalidateSchema, movementMobileBootstrapSchema, movementMobilePlaceMutationSchema, movementMobileStayPatchSchema, movementMobileUserBoxCreateSchema, movementMobileUserBoxPreflightSchema, movementMobileUserBoxPatchSchema, movementMobileAutomaticBoxInvalidateSchema, movementMobileTimelineSchema, movementPlaceMutationSchema, movementPlacePatchSchema, movementSelectionAggregateSchema, movementStayPatchSchema, movementTripPatchSchema, movementUserBoxCreateSchema, movementUserBoxPreflightSchema, movementUserBoxPatchSchema, movementSettingsPatchSchema, movementTimelineQuerySchema, movementTripPointPatchSchema, deleteMovementStay, deleteMovementTrip, deleteMovementTripPoint, updateMovementPlace, updateMovementSettings, updateMovementStay, updateMovementTrip, updateMovementUserBox, updateMovementTripPoint, resolveMovementTimelineSegmentForBox } from "./movement.js";
71
72
  import { getScreenTimeAllTimeSummary, getScreenTimeDayDetail, getScreenTimeMonthSummary, getScreenTimeSettings, screenTimeSettingsPatchSchema, updateScreenTimeSettings } from "./screen-time.js";
72
- import { assertWatchReady, buildWatchBootstrap, ingestWatchCaptureBatch, mobileWatchBootstrapSchema, mobileWatchCaptureBatchSchema, mobileWatchHabitCheckInSchema } from "./watch-mobile.js";
73
+ import { assertWatchReady, buildWatchBootstrap, ingestWatchCommandBatch, ingestWatchCaptureBatch, mobileWatchBootstrapSchema, mobileWatchCommandBatchSchema, mobileWatchCaptureBatchSchema, mobileWatchHabitCheckInSchema } from "./watch-mobile.js";
73
74
  const COMPATIBILITY_SUNSET = "transitional-node";
74
75
  function markCompatibilityRoute(reply) {
75
76
  reply.header("Deprecation", "true");
@@ -3066,6 +3067,7 @@ const AGENT_ONBOARDING_CONVERSATION_RULES = [
3066
3067
  "Use a progression of concrete example or intent, working name, purpose or meaning, placement in Forge, operational details, and linked context.",
3067
3068
  "Ask one to three focused questions at a time. One is usually best when the user is uncertain or emotionally loaded.",
3068
3069
  "One focused question is the default. Only stack a second question when both serve the same clarification job and the user is steady enough for it.",
3070
+ "For operational create flows, do not bundle name, scope, and timing into one opener when the user has already supplied part of it. Ask the route-changing missing detail first, then move to the next detail only if it is still unknown.",
3069
3071
  "When the user wants review, comparison, or navigation around an existing record, ask what they are trying to understand first and look up the existing record before reopening create or update intake.",
3070
3072
  "If the user already answered the normal opening question, do not repeat it. Move to the next missing clarification.",
3071
3073
  "Do not over-therapize logistical entities. For tasks, calendar events, work blocks, timeboxes, and task runs, one brief confirming sentence plus one question is usually enough.",
@@ -3087,6 +3089,7 @@ const AGENT_ONBOARDING_CONVERSATION_RULES = [
3087
3089
  "Do not bury schema work in self-observation. If the user is describing a schema theme, preserve it through a belief_entry, behavior_pattern, mode_profile, mode_guide_session, trigger_report, or wiki_page depending on whether it appears as a rule, loop, part-state, live exploration, one episode, or durable explanation.",
3088
3090
  "Do not minimize functional analysis, trigger chains, behavior patterns, modes, beliefs, or schema themes. After at least one concrete example is clear, offer one careful interpretive hypothesis when it would help the user understand what the reaction may be protecting, predicting, relieving, or costing.",
3089
3091
  "Phrase Psyche interpretive hypotheses as collaborative and testable, not as verdicts. Ask whether the hypothesis lands or needs correction before turning it into a saved belief, pattern, mode, trigger report, or note.",
3092
+ "A useful Psyche hypothesis starts from the user's concrete example, names one possible protection, prediction, relief, or cost, and asks for correction before it becomes a saved record shape.",
3090
3093
  "Once the Movement, Life Force, or Workbench job is clear, speak in product nouns such as timeline, overlay, weekday template, published output, run detail, or node result instead of generic record language.",
3091
3094
  "If the next answer would not change the route, wording, timing, or write shape in a meaningful way, stop asking and act.",
3092
3095
  "Before saving, briefly summarize the working formulation in the user's own language when that would reduce ambiguity.",
@@ -3216,7 +3219,7 @@ const AGENT_ONBOARDING_ENTITY_CONVERSATION_PLAYBOOKS = [
3216
3219
  },
3217
3220
  {
3218
3221
  focus: "calendar_event",
3219
- openingQuestion: "What is the event, and when should it happen in your local time?",
3222
+ openingQuestion: "What time should Forge hold for this event in your local timezone?",
3220
3223
  coachingGoal: "Make the event legible as a real commitment in time, with the right timezone and links.",
3221
3224
  askSequence: [
3222
3225
  "Ask what the event is.",
@@ -3227,7 +3230,7 @@ const AGENT_ONBOARDING_ENTITY_CONVERSATION_PLAYBOOKS = [
3227
3230
  },
3228
3231
  {
3229
3232
  focus: "work_block_template",
3230
- openingQuestion: "What recurring block do you want to set up, and when should it repeat?",
3233
+ openingQuestion: "When should this recurring block repeat?",
3231
3234
  coachingGoal: "Define a reusable availability rule rather than a one-off event.",
3232
3235
  askSequence: [
3233
3236
  "Ask what kind of block it is and what it should be called.",
@@ -3238,7 +3241,7 @@ const AGENT_ONBOARDING_ENTITY_CONVERSATION_PLAYBOOKS = [
3238
3241
  },
3239
3242
  {
3240
3243
  focus: "task_timebox",
3241
- openingQuestion: "Which task are you trying to make time for, and when should the slot be?",
3244
+ openingQuestion: "When should Forge reserve focused time for this task?",
3242
3245
  coachingGoal: "Reserve real time for one task without confusing planned work with completed work.",
3243
3246
  askSequence: [
3244
3247
  "Ask which task the slot belongs to.",
@@ -3260,7 +3263,7 @@ const AGENT_ONBOARDING_ENTITY_CONVERSATION_PLAYBOOKS = [
3260
3263
  },
3261
3264
  {
3262
3265
  focus: "calendar_connection",
3263
- openingQuestion: "Which calendar provider are you trying to connect, and what do you want Forge to do with it?",
3266
+ openingQuestion: "What workflow do you want this calendar connection to unlock?",
3264
3267
  coachingGoal: "Connect the right provider deliberately without turning setup into a credential dump.",
3265
3268
  askSequence: [
3266
3269
  "Ask which provider the user wants to connect and what they want Forge to do with it.",
@@ -3493,7 +3496,7 @@ const AGENT_ONBOARDING_ENTITY_CONVERSATION_PLAYBOOKS = [
3493
3496
  },
3494
3497
  {
3495
3498
  focus: "questionnaire_run",
3496
- openingQuestion: "What do you want from this questionnaire run right now: start, continue, review, or finish it?",
3499
+ openingQuestion: "Are you trying to start, continue, review, or finish this run right now?",
3497
3500
  coachingGoal: "Clarify whether the user wants to start, continue, review, or complete one answer session without turning the run into a mechanical form fill.",
3498
3501
  askSequence: [
3499
3502
  "Ask what the user wants from the run right now: start, continue, review, or finish.",
@@ -4273,6 +4276,68 @@ const AGENT_ONBOARDING_TOOL_INPUT_CATALOG = [
4273
4276
  ],
4274
4277
  example: '{"userIds":["user_operator"]}'
4275
4278
  },
4279
+ {
4280
+ toolName: "forge_get_weight_loss_overview",
4281
+ summary: "Read the nutrition and weight-loss surface with calorie ledger, protein/fiber targets, energy balance, body trend, subjective energy, gut comfort, aesthetic check-ins, hypotheses, experiments, and data-quality flags.",
4282
+ whenToUse: "Use when the operator wants food, body composition, sport-fueling, appearance, gut-health, or subjective-energy context before coaching or logging.",
4283
+ inputShape: "{ userIds?: string[] }",
4284
+ requiredFields: [],
4285
+ notes: [
4286
+ "The API path is /api/v1/health/weight-loss and the UI route is /weight-loss.",
4287
+ "Use the dedicated nutrition logging tools for food, body, appearance, subjective, gut, and experiment mutations.",
4288
+ "Food parsing uses Forge's configured openai-codex ChatGPT subscription connection, not the metered OpenAI Platform API."
4289
+ ],
4290
+ example: '{"userIds":["user_operator"]}'
4291
+ },
4292
+ {
4293
+ toolName: "forge_parse_food_log_with_chatgpt",
4294
+ summary: "Parse natural-language food text or a photo description into a candidate Forge nutrition log through the openai-codex ChatGPT subscription connection.",
4295
+ whenToUse: "Use when the operator describes a meal imprecisely, wants AI-assisted food decomposition, or wants subjective food-effect notes captured alongside nutrition.",
4296
+ inputShape: "{ text?: string, imageDescription?: string, loggedAt?: string, mealLabel?: string, userIds?: string[] }",
4297
+ requiredFields: ["text/imageDescription"],
4298
+ notes: [
4299
+ "Requires a configured openai-codex model connection.",
4300
+ "Returns a candidate log that should be confirmed or corrected when precision matters.",
4301
+ "Do not route this through OpenAI Platform API billing."
4302
+ ],
4303
+ example: '{"text":"3 eggs, sourdough, Greek yogurt with honey, espresso; focused but bloated later","mealLabel":"breakfast"}'
4304
+ },
4305
+ {
4306
+ toolName: "forge_log_food",
4307
+ summary: "Create a food log with explicit food items, calories, macros, quality tags, hunger, satiety, cravings, and context.",
4308
+ whenToUse: "Use for confirmed manual entries, search/barcode-selected foods, or corrected ChatGPT candidates.",
4309
+ inputShape: "{ mealLabel?: string, loggedAt?: string, source?: string, confirmationState?: string, notes?: string, items: Array<{ name, quantity, unit?, caloriesKcal?, proteinG?, carbsG?, fatG?, fiberG?, tags? }>, userIds?: string[] }",
4310
+ requiredFields: ["items"],
4311
+ notes: [
4312
+ "Prefer confirmed entries when the user gave concrete amounts.",
4313
+ "Use candidate entries for rough estimates that need later correction."
4314
+ ],
4315
+ example: '{"mealLabel":"post-workout","items":[{"name":"Greek yogurt","quantity":250,"unit":"g","caloriesKcal":180,"proteinG":25}]}'
4316
+ },
4317
+ {
4318
+ toolName: "forge_log_body_checkin | forge_log_appearance_checkin | forge_log_subjective_food_effect | forge_log_gut_checkin",
4319
+ summary: "Record body composition, aesthetic look, energy/mood/focus/cravings/performance, and gut-comfort check-ins.",
4320
+ whenToUse: "Use alongside food logs to let Forge discover links between eating, sport, visual look, energy, cravings, and gut health.",
4321
+ inputShape: "{ checkedAt?: string, score fields, linkedFoodLogId?: string, notes?: string, userIds?: string[] }",
4322
+ requiredFields: ["at least one metric field"],
4323
+ notes: [
4324
+ "Scores are 0-10 unless the field name gives a physical unit such as kg or cm.",
4325
+ "Link subjective or gut check-ins to a food log when the timing is known."
4326
+ ],
4327
+ example: '{"energy":8,"focus":7,"bloating":2,"timeRelation":"2h after lunch","linkedFoodLogId":"nfl_123"}'
4328
+ },
4329
+ {
4330
+ toolName: "forge_get_nutrition_patterns | forge_start_nutrition_experiment | forge_update_nutrition_experiment",
4331
+ summary: "Review nutrition hypotheses and manage N-of-1 experiments for food, training fuel, gut comfort, cravings, and aesthetic outcomes.",
4332
+ whenToUse: "Use when the operator wants to test a causal idea such as caffeine timing, carb timing, sodium/puffiness, fiber ramp, low-FODMAP trial, or pre-training fueling.",
4333
+ inputShape: "{ title, hypothesis, metricKey, intervention, baseline dates?, experiment dates?, successCriteria?, userIds? }",
4334
+ requiredFields: ["title", "hypothesis", "metricKey", "intervention"],
4335
+ notes: [
4336
+ "Experiments should name the metric being optimized and define success criteria before results are interpreted.",
4337
+ "Forge treats hypotheses as decision aids, not medical diagnosis."
4338
+ ],
4339
+ example: '{"title":"Carbs before kickboxing","hypothesis":"60g carbs 2h before training improves performance without gut discomfort","metricKey":"workoutPerformance","intervention":"60g low-fat carbs before training"}'
4340
+ },
4276
4341
  {
4277
4342
  toolName: "forge_update_sleep_session",
4278
4343
  summary: "Patch one sleep session with reflective notes, tags, or linked Forge context.",
@@ -5208,7 +5273,8 @@ function buildAgentOnboardingPayload(request) {
5208
5273
  batchSearch: "/api/v1/entities/search",
5209
5274
  psycheSchemaCatalog: "/api/v1/psyche/schema-catalog",
5210
5275
  psycheEventTypes: "/api/v1/psyche/event-types",
5211
- psycheEmotions: "/api/v1/psyche/emotions"
5276
+ psycheEmotions: "/api/v1/psyche/emotions",
5277
+ weightLoss: "/api/v1/health/weight-loss"
5212
5278
  },
5213
5279
  recommendedPluginTools: {
5214
5280
  bootstrap: ["forge_get_operator_overview"],
@@ -5220,6 +5286,7 @@ function buildAgentOnboardingPayload(request) {
5220
5286
  "forge_get_sleep_overview",
5221
5287
  "forge_get_sports_overview",
5222
5288
  "forge_get_training_load_overview",
5289
+ "forge_get_weight_loss_overview",
5223
5290
  "forge_get_xp_metrics",
5224
5291
  "forge_get_weekly_review"
5225
5292
  ],
@@ -5251,6 +5318,19 @@ function buildAgentOnboardingPayload(request) {
5251
5318
  "forge_get_sleep_overview",
5252
5319
  "forge_get_sports_overview",
5253
5320
  "forge_get_training_load_overview",
5321
+ "forge_get_weight_loss_overview",
5322
+ "forge_search_foods",
5323
+ "forge_search_nutrition_foods",
5324
+ "forge_lookup_nutrition_barcode",
5325
+ "forge_log_food",
5326
+ "forge_parse_food_log_with_chatgpt",
5327
+ "forge_log_body_checkin",
5328
+ "forge_log_appearance_checkin",
5329
+ "forge_log_subjective_food_effect",
5330
+ "forge_log_gut_checkin",
5331
+ "forge_get_nutrition_patterns",
5332
+ "forge_start_nutrition_experiment",
5333
+ "forge_update_nutrition_experiment",
5254
5334
  "forge_update_sleep_session",
5255
5335
  "forge_update_workout_session"
5256
5336
  ],
@@ -5284,6 +5364,10 @@ function buildAgentOnboardingPayload(request) {
5284
5364
  reviewShortcutRule: "When the user is reviewing or correcting an existing record, ask what practical question they want the read or correction to answer, then narrow the saved object, timeframe, or route family first. Do not reopen the whole intake unless the user is actually redefining the record.",
5285
5365
  readModelWriteRule: "Self-observation is note-backed and should be written through observed notes with frontmatter.observedAt only when a lightweight episode observation is the right container. Do not use it as the default bucket for Psyche material: prefer trigger_report for one emotionally meaningful episode, behavior_pattern for functional analysis of a recurring loop, behavior for one repeated move, belief_entry for a core sentence, mode_guide_session or mode_profile for a central part-state, and wiki_page for durable memory such as books, articles, concepts, sources, or personal manuals. Sleep and workout sessions stay on batch CRUD by default; use the reflective review helpers only when enriching one already-known record after review.",
5286
5366
  psycheOpeningQuestionRule: "Prefer a concrete opening question tied to the entity: ask when the value mattered, what happened the last time the pattern appeared, what cue or body signal came first before the behavior, what the belief starts saying about self or outcome, what feels most at risk inside the mode, what the part is trying to get the user to do or stop doing, or where the shift began in the incident. Reflect briefly before the question, choose one follow-up lane at a time, say what is becoming clearer before the next deeper question, and if several Psyche entities are visible hold the adjacent ones lightly until the main container is clear.",
5367
+ psycheHypothesisRule: "When one concrete Psyche example is visible, a helpful hypothesis should start from evidence in the user's own example, offer one testable interpretation, name the function without blame such as protection, prediction, relief, or cost, and ask whether the danger, need, or wording fits. Do not present schema, mode, belief, or pattern language as a verdict. If the user corrects the hypothesis, revise it once and move toward the saveable record shape instead of asking for another broad story.",
5368
+ mixedIntentSequencingRule: "When one user message combines several Forge jobs, identify the primary job and the order of operations before asking a follow-up. If a read changes the truth of a later write, read first: Movement timeline or box detail before correction, Workbench run or node detail before editing or publishing, and Life Force overview before changing durable assumptions when the current energy picture is uncertain. If the user asks to understand and save Psyche material plus create a support record, formulate the primary Psyche record first, then derive the flashcard, note, link, task, or habit from the accepted wording. If the user already gave the concrete action, do not ask a broad lane question; say the product sequence briefly and ask only for the missing span, wording, flow, run, node, weekday, or link that changes the next action.",
5369
+ duplicateDisambiguationRule: "Before creating or updating a normal stored entity when duplicate risk is plausible, search the shared batch entity route by entity type, distinctive title or wording, owner scope, and linked content. If a likely existing record appears, ask whether the user wants to update that record, link to it, or save a separate new record; do not reopen the whole create flow. For Psyche records, a similar belief, pattern, mode, trigger report, value, or flashcard is a formulation choice, not a duplicate error: compare the sentence, cue/payoff/cost, protective job, episode, urge sentence, or message and let the user choose update, link, or new version. For wiki_page and calendar_connection, use dedicated search/list/read routes before creating another page or connection. For Movement, Life Force, and Workbench, use the dedicated read lanes instead of batch duplicate search.",
5370
+ destructiveActionRule: "Before deleting, archiving, invalidating, overwriting, disconnecting, or substantially replacing a Forge record or specialized object, confirm the exact target and what should remain understandable. Prefer normal soft-delete for stored entities unless the user explicitly asks for permanent removal. For Psyche records, preserve therapeutic history by asking whether the old belief, pattern, mode, trigger report, value, or flashcard should be updated, linked as history, archived, or kept distinct; do not delete it just because a cleaner formulation exists. For Movement, distinguish user-defined overlay deletion from automatic-box invalidation and stay/trip/point deletion, and read the specific span first when the target is uncertain. For calendar connections, Workbench flows, wiki pages, and questionnaire instruments, ask what downstream sync, published output, backlinks, run history, or completed runs should remain understandable before deleting or replacing the saved object.",
5287
5371
  followUpQuestionRule: "After a substantive answer, do not restart the opener or jump to the next schema field. First say what became clearer in concrete language, then choose exactly one next lane: wording, boundary, placement, timing, route scope, link, hypothesis, or write confirmation. Ask the smallest question that would change the record shape, route choice, useful wording, timing, or links. If nothing decision-relevant would change, stop asking, summarize the working record, and act with consent.",
5288
5372
  antiDriftRule: "Avoid vague reflective filler and internal route language. Replace phrases like 'that sounds important' with the specific stake you heard, and replace API nouns like surface, CRUD, payload, mutation path, or endpoint with user-facing product nouns such as belief, pattern, note, wiki page, timeline, overlay, weekday template, flow, run, node result, or published output. If a question would only decorate the intake, skip it.",
5289
5373
  duplicateCheckRoute: "/api/v1/entities/search",
@@ -5683,6 +5767,7 @@ function buildV1Context(scope = {
5683
5767
  projectIds: [],
5684
5768
  tagIds: []
5685
5769
  }) {
5770
+ const now = new Date();
5686
5771
  const users = listUsers();
5687
5772
  const { validScopedUserIds, scopedUserIdsForReads } = normalizeScopedUserIdsForReads({
5688
5773
  scope,
@@ -5702,17 +5787,26 @@ function buildV1Context(scope = {
5702
5787
  meta: {
5703
5788
  apiVersion: "v1",
5704
5789
  transport: "rest+sse",
5705
- generatedAt: new Date().toISOString(),
5790
+ generatedAt: now.toISOString(),
5706
5791
  backend: "forge-node-runtime",
5707
5792
  mode: "transitional-node"
5708
5793
  },
5709
- metrics: buildGamificationProfile(goals, tasks, habits, new Date(), {
5794
+ metrics: buildGamificationProfile(goals, tasks, habits, now, {
5710
5795
  userIds: scopedUserIdsForReads
5711
5796
  }),
5712
5797
  dashboard,
5713
- overview: getOverviewContext(new Date(), { userIds: scopedUserIdsForReads }),
5714
- today: getTodayContext(new Date(), { userIds: scopedUserIdsForReads }),
5715
- risk: getRiskContext(new Date(), { userIds: scopedUserIdsForReads }),
5798
+ overview: getOverviewContext(now, {
5799
+ userIds: scopedUserIdsForReads,
5800
+ dashboard
5801
+ }),
5802
+ today: getTodayContext(now, {
5803
+ userIds: scopedUserIdsForReads,
5804
+ dashboard
5805
+ }),
5806
+ risk: getRiskContext(now, {
5807
+ userIds: scopedUserIdsForReads,
5808
+ dashboard
5809
+ }),
5716
5810
  goals,
5717
5811
  projects,
5718
5812
  tags: listTags(),
@@ -5730,7 +5824,7 @@ function buildV1Context(scope = {
5730
5824
  ? []
5731
5825
  : listTaskRuns({ active: true, limit: 25, userIds: scopedUserIdsForReads }),
5732
5826
  activity: dashboard.recentActivity,
5733
- lifeForce: buildLifeForcePayload(new Date(), scopedUserIdsForReads)
5827
+ lifeForce: buildLifeForcePayload(now, scopedUserIdsForReads)
5734
5828
  };
5735
5829
  }
5736
5830
  function buildXpMetricsPayload(input = {}) {
@@ -6302,6 +6396,42 @@ function compactVitals(vitals) {
6302
6396
  detailRoute: "/api/v1/health/vitals"
6303
6397
  };
6304
6398
  }
6399
+ function compactWeightLoss(weightLoss) {
6400
+ return {
6401
+ generatedAt: weightLoss.generatedAt,
6402
+ userId: weightLoss.userId,
6403
+ summary: weightLoss.summary,
6404
+ todayLedger: {
6405
+ dateKey: weightLoss.todayLedger.dateKey,
6406
+ mealCount: weightLoss.todayLedger.meals.length,
6407
+ totals: weightLoss.todayLedger.totals,
6408
+ targetCalories: weightLoss.todayLedger.targetCalories,
6409
+ calorieDelta: weightLoss.todayLedger.calorieDelta,
6410
+ proteinCoverage: weightLoss.todayLedger.proteinCoverage,
6411
+ fiberCoverage: weightLoss.todayLedger.fiberCoverage,
6412
+ unconfirmedCount: weightLoss.todayLedger.unconfirmedCount
6413
+ },
6414
+ energyModel: {
6415
+ estimatedTdeeKcal: weightLoss.energyModel.estimatedTdeeKcal,
6416
+ activeBurnKcal: weightLoss.energyModel.activeBurnKcal,
6417
+ movementCaloriesKcal: weightLoss.energyModel.movementCaloriesKcal,
6418
+ averageCalorieIntake: weightLoss.energyModel.averageCalorieIntake,
6419
+ estimatedDailyEnergyBalanceKcal: weightLoss.energyModel.estimatedDailyEnergyBalanceKcal,
6420
+ wearableConfidence: weightLoss.energyModel.wearableConfidence
6421
+ },
6422
+ weightTrend: weightLoss.weightTrend,
6423
+ foodQuality: weightLoss.foodQuality,
6424
+ trainingFuel: weightLoss.trainingFuel,
6425
+ subjective: weightLoss.subjective,
6426
+ gut: weightLoss.gut,
6427
+ topHypotheses: weightLoss.hypotheses.slice(0, 4),
6428
+ activeExperiments: weightLoss.experiments
6429
+ .filter((experiment) => experiment.status !== "completed")
6430
+ .slice(0, 4),
6431
+ dataQuality: weightLoss.dataQuality,
6432
+ detailRoute: "/api/v1/health/weight-loss"
6433
+ };
6434
+ }
6305
6435
  function compactLifeForce(lifeForce) {
6306
6436
  return {
6307
6437
  userId: lifeForce.userId,
@@ -6567,6 +6697,7 @@ function buildOperatorOverview(request) {
6567
6697
  const fitness = compactFitness(getFitnessViewData(userIds));
6568
6698
  const trainingLoad = compactTrainingLoad(getTrainingLoadViewData(userIds));
6569
6699
  const vitals = compactVitals(getVitalsViewData(userIds));
6700
+ const weightLoss = compactWeightLoss(getWeightLossViewData(userIds));
6570
6701
  const lifeForce = compactLifeForce(buildLifeForcePayload(now, userIds));
6571
6702
  const psyche = canReadPsyche ? compactPsyche(getPsycheOverview(userIds)) : null;
6572
6703
  const onboarding = compactOnboardingPayload(buildAgentOnboardingPayload(request));
@@ -6649,6 +6780,7 @@ function buildOperatorOverview(request) {
6649
6780
  fitness,
6650
6781
  trainingLoad,
6651
6782
  vitals,
6783
+ weightLoss,
6652
6784
  lifeForce,
6653
6785
  domains: listDomains().map((domain) => ({
6654
6786
  id: domain.id,
@@ -7332,6 +7464,118 @@ export async function buildServer(options = {}) {
7332
7464
  app.get("/api/v1/health/vitals", async (request) => ({
7333
7465
  vitals: getVitalsViewData(resolveScopedUserIds(request.query))
7334
7466
  }));
7467
+ app.get("/api/v1/health/weight-loss", async (request) => ({
7468
+ weightLoss: getWeightLossViewData(resolveScopedUserIds(request.query))
7469
+ }));
7470
+ app.patch("/api/v1/health/weight-loss/target", async (request) => {
7471
+ requireScopedAccess(request.headers, ["write"], {
7472
+ route: "/api/v1/health/weight-loss/target"
7473
+ });
7474
+ return {
7475
+ target: updateNutritionTarget(nutritionTargetUpdateSchema.parse(request.body ?? {}))
7476
+ };
7477
+ });
7478
+ app.post("/api/v1/health/weight-loss/foods/search", async (request) => ({
7479
+ ...(await searchNutritionFoods(nutritionFoodSearchSchema.parse(request.body ?? {})))
7480
+ }));
7481
+ app.post("/api/v1/health/weight-loss/foods/barcode", async (request) => ({
7482
+ ...(await lookupNutritionBarcode(nutritionBarcodeLookupSchema.parse(request.body ?? {})))
7483
+ }));
7484
+ app.post("/api/v1/health/weight-loss/food-logs", async (request, reply) => {
7485
+ requireScopedAccess(request.headers, ["write"], {
7486
+ route: "/api/v1/health/weight-loss/food-logs"
7487
+ });
7488
+ const foodLog = createNutritionFoodLog(nutritionFoodLogCreateSchema.parse(request.body ?? {}));
7489
+ reply.code(201);
7490
+ return { log: foodLog };
7491
+ });
7492
+ app.patch("/api/v1/health/weight-loss/food-logs/:id", async (request, reply) => {
7493
+ requireScopedAccess(request.headers, ["write"], {
7494
+ route: "/api/v1/health/weight-loss/food-logs/:id"
7495
+ });
7496
+ const foodLog = patchNutritionFoodLog(request.params.id, nutritionFoodLogPatchSchema.parse(request.body ?? {}));
7497
+ if (!foodLog) {
7498
+ reply.code(404);
7499
+ return { error: "Food log not found" };
7500
+ }
7501
+ return { log: foodLog };
7502
+ });
7503
+ app.delete("/api/v1/health/weight-loss/food-logs/:id", async (request, reply) => {
7504
+ requireScopedAccess(request.headers, ["write"], {
7505
+ route: "/api/v1/health/weight-loss/food-logs/:id"
7506
+ });
7507
+ const foodLog = deleteNutritionFoodLog(request.params.id);
7508
+ if (!foodLog) {
7509
+ reply.code(404);
7510
+ return { error: "Food log not found" };
7511
+ }
7512
+ return { deleted: true };
7513
+ });
7514
+ app.post("/api/v1/health/weight-loss/parse", async (request, reply) => {
7515
+ requireScopedAccess(request.headers, ["write"], {
7516
+ route: "/api/v1/health/weight-loss/parse"
7517
+ });
7518
+ reply.code(201);
7519
+ return await parseNutritionFoodLogWithChatGpt(nutritionParseRequestSchema.parse(request.body ?? {}), managers.llm);
7520
+ });
7521
+ app.post("/api/v1/health/weight-loss/body-checkins", async (request, reply) => {
7522
+ requireScopedAccess(request.headers, ["write"], {
7523
+ route: "/api/v1/health/weight-loss/body-checkins"
7524
+ });
7525
+ const bodyCheckin = createNutritionBodyCheckin(nutritionBodyCheckinCreateSchema.parse(request.body ?? {}));
7526
+ reply.code(201);
7527
+ return { checkin: bodyCheckin };
7528
+ });
7529
+ app.post("/api/v1/health/weight-loss/appearance-checkins", async (request, reply) => {
7530
+ requireScopedAccess(request.headers, ["write"], {
7531
+ route: "/api/v1/health/weight-loss/appearance-checkins"
7532
+ });
7533
+ const appearanceCheckin = createNutritionAppearanceCheckin(nutritionAppearanceCheckinCreateSchema.parse(request.body ?? {}));
7534
+ reply.code(201);
7535
+ return { checkin: appearanceCheckin };
7536
+ });
7537
+ app.post("/api/v1/health/weight-loss/subjective-checkins", async (request, reply) => {
7538
+ requireScopedAccess(request.headers, ["write"], {
7539
+ route: "/api/v1/health/weight-loss/subjective-checkins"
7540
+ });
7541
+ const subjectiveCheckin = createNutritionSubjectiveCheckin(nutritionSubjectiveCheckinCreateSchema.parse(request.body ?? {}));
7542
+ reply.code(201);
7543
+ return { checkin: subjectiveCheckin };
7544
+ });
7545
+ app.post("/api/v1/health/weight-loss/gut-checkins", async (request, reply) => {
7546
+ requireScopedAccess(request.headers, ["write"], {
7547
+ route: "/api/v1/health/weight-loss/gut-checkins"
7548
+ });
7549
+ const gutCheckin = createNutritionGutCheckin(nutritionGutCheckinCreateSchema.parse(request.body ?? {}));
7550
+ reply.code(201);
7551
+ return { checkin: gutCheckin };
7552
+ });
7553
+ app.get("/api/v1/health/weight-loss/patterns", async (request) => {
7554
+ const weightLoss = getWeightLossViewData(resolveScopedUserIds(request.query));
7555
+ return {
7556
+ hypotheses: weightLoss.hypotheses,
7557
+ experiments: weightLoss.experiments
7558
+ };
7559
+ });
7560
+ app.post("/api/v1/health/weight-loss/experiments", async (request, reply) => {
7561
+ requireScopedAccess(request.headers, ["write"], {
7562
+ route: "/api/v1/health/weight-loss/experiments"
7563
+ });
7564
+ const experiment = createNutritionExperiment(nutritionExperimentCreateSchema.parse(request.body ?? {}));
7565
+ reply.code(201);
7566
+ return { experiment };
7567
+ });
7568
+ app.patch("/api/v1/health/weight-loss/experiments/:id", async (request, reply) => {
7569
+ requireScopedAccess(request.headers, ["write"], {
7570
+ route: "/api/v1/health/weight-loss/experiments/:id"
7571
+ });
7572
+ const experiment = patchNutritionExperiment(request.params.id, nutritionExperimentPatchSchema.parse(request.body ?? {}));
7573
+ if (!experiment) {
7574
+ reply.code(404);
7575
+ return { error: "Nutrition experiment not found" };
7576
+ }
7577
+ return { experiment };
7578
+ });
7335
7579
  app.get("/api/v1/health/zone-profile", async (request) => {
7336
7580
  const userIds = resolveScopedUserIds(request.query);
7337
7581
  return {
@@ -7878,6 +8122,15 @@ export async function buildServer(options = {}) {
7878
8122
  watch: buildWatchBootstrap(pairing)
7879
8123
  };
7880
8124
  });
8125
+ app.post("/api/v1/mobile/watch/actions:batch", async (request) => {
8126
+ const parsed = mobileWatchCommandBatchSchema.parse(request.body ?? {});
8127
+ const pairing = requireValidPairing(parsed.sessionId, parsed.pairingToken);
8128
+ assertWatchReady(pairing);
8129
+ return {
8130
+ receipt: ingestWatchCommandBatch(pairing, parsed),
8131
+ watch: buildWatchBootstrap(pairing)
8132
+ };
8133
+ });
7881
8134
  app.post("/api/v1/mobile/healthkit/sync-sessions", async (request) => ({
7882
8135
  upload: startMobileHealthSyncSession(mobileHealthSyncSessionStartSchema.parse(request.body ?? {}))
7883
8136
  }));