forge-openclaw-plugin 0.2.109 → 0.2.111

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (109) hide show
  1. package/dist/assets/{activity-page-DWHZ8_sJ.js → activity-page-BdeoV3N4.js} +1 -1
  2. package/dist/assets/{ai-surface-workspace-DPygfyhc.js → ai-surface-workspace-BCbQUekv.js} +1 -1
  3. package/dist/assets/{atlas-panel-D261RUQF.js → atlas-panel-DTnRBOZx.js} +1 -1
  4. package/dist/assets/{calendar-page-BQbJkdPF.js → calendar-page-YDexXy_e.js} +1 -1
  5. package/dist/assets/{calendar-rules-BoFsuvZ8.js → calendar-rules-B_aOcMi3.js} +1 -1
  6. package/dist/assets/{calendar-week-toolbar-LetevPI7.js → calendar-week-toolbar-BTbIzq3e.js} +1 -1
  7. package/dist/assets/{companion-sync-lab-page-Bid-4B8W.js → companion-sync-lab-page-DKouMut9.js} +1 -1
  8. package/dist/assets/{daily-metrics-dashboard-CqnGJNeI.js → daily-metrics-dashboard-B3UCGG1t.js} +1 -1
  9. package/dist/assets/{entity-note-count-link-VMURwBMi.js → entity-note-count-link-DUxobQUt.js} +1 -1
  10. package/dist/assets/{entity-notes-surface-DGXcyNzo.js → entity-notes-surface-BdHBn8rx.js} +1 -1
  11. package/dist/assets/{execution-board-BGf6zZqJ.js → execution-board-R5-oyf-Z.js} +1 -1
  12. package/dist/assets/{faceted-token-search-CAsZrhZ4.js → faceted-token-search-DtO6OUwQ.js} +1 -1
  13. package/dist/assets/{flagship-signal-deck-DJsq3jn2.js → flagship-signal-deck-B9k46sWA.js} +1 -1
  14. package/dist/assets/{floating-action-menu-B3tQStUN.js → floating-action-menu-DBzfnJOx.js} +1 -1
  15. package/dist/assets/{goal-detail-page-DBs-9wpk.js → goal-detail-page-Mr_gnZ2l.js} +1 -1
  16. package/dist/assets/{goals-page-CPmhjsG-.js → goals-page-BG7FuqK1.js} +1 -1
  17. package/dist/assets/{habits-page-DkW8zT6_.js → habits-page-CGdaOqL3.js} +1 -1
  18. package/dist/assets/{index-BhkubYaC.js → index-BP9__l6C.js} +2 -2
  19. package/dist/assets/{insight-flow-dialog-BvgRjkPi.js → insight-flow-dialog-BUv06Vn-.js} +1 -1
  20. package/dist/assets/{insights-page-Dtij6pm5.js → insights-page-BgD1_1we.js} +1 -1
  21. package/dist/assets/{kanban-page-R1x5hUU-.js → kanban-page-DvZSN-Bi.js} +1 -1
  22. package/dist/assets/{knowledge-graph-page-BRBB5Vvz.js → knowledge-graph-page-BXWXtPvo.js} +1 -1
  23. package/dist/assets/{life-force-page-DGPWKM9I.js → life-force-page-CwjHvpzu.js} +1 -1
  24. package/dist/assets/{life-force-workspace-DoRIQoU_.js → life-force-workspace-Ct70SsNQ.js} +1 -1
  25. package/dist/assets/{metric-tile-NJiFcFxW.js → metric-tile-DCxLxNli.js} +1 -1
  26. package/dist/assets/{movement-page-Dk8aV737.js → movement-page-C06ebiYk.js} +1 -1
  27. package/dist/assets/{note-markdown-CZHjJDve.js → note-markdown-3wB52NOn.js} +1 -1
  28. package/dist/assets/{note-tags-input-DFpxZHPg.js → note-tags-input-Blg5Gqz_.js} +1 -1
  29. package/dist/assets/{notes-page-r3kymlqg.js → notes-page-CSkjizyK.js} +1 -1
  30. package/dist/assets/{open-in-graph-button-B_XHtHZD.js → open-in-graph-button-CDHxzS8B.js} +1 -1
  31. package/dist/assets/{orbit-map-C_xy4kYC.js → orbit-map-OV-GcUZl.js} +1 -1
  32. package/dist/assets/{overview-page-Dwg4uYe9.js → overview-page-Cw3pI34n.js} +1 -1
  33. package/dist/assets/{page-hero-DzEsy8i5.js → page-hero-DgnhI5es.js} +1 -1
  34. package/dist/assets/{pill-cluster-DcBUeEMT.js → pill-cluster-DIGkIvKN.js} +1 -1
  35. package/dist/assets/{preference-entity-handoff-button-BX2n07ob.js → preference-entity-handoff-button-LT_GBuqB.js} +1 -1
  36. package/dist/assets/{preferences-page-BSnwiWBt.js → preferences-page-Dkvz7nkU.js} +1 -1
  37. package/dist/assets/{project-collections-BR6YdlCZ.js → project-collections-BsAT8WYE.js} +1 -1
  38. package/dist/assets/{project-detail-page-DeYshcCb.js → project-detail-page-DsJUWg8k.js} +1 -1
  39. package/dist/assets/{project-management-hierarchy-page-Bhdt79Ea.js → project-management-hierarchy-page-CIy0wAp8.js} +1 -1
  40. package/dist/assets/{project-management-section-nav-DnXuWUfe.js → project-management-section-nav-ClB2S1FW.js} +1 -1
  41. package/dist/assets/{projects-page-BA9W0ZPk.js → projects-page-Da1bZLSH.js} +1 -1
  42. package/dist/assets/{psyche-behaviors-page-DpTj53GI.js → psyche-behaviors-page-D0uwf78o.js} +1 -1
  43. package/dist/assets/{psyche-flashcards-page-CV58hAJA.js → psyche-flashcards-page-e_qwwsOf.js} +1 -1
  44. package/dist/assets/{psyche-goal-map-page-C07CALFg.js → psyche-goal-map-page-BIY028Wx.js} +1 -1
  45. package/dist/assets/{psyche-graph-CP7mxfMP.js → psyche-graph-Y1UKALEm.js} +1 -1
  46. package/dist/assets/{psyche-metrics-page-CY8e9R0D.js → psyche-metrics-page-DDkGJzo_.js} +1 -1
  47. package/dist/assets/{psyche-mode-guide-page-DYt6AmHl.js → psyche-mode-guide-page-C7mD9uiv.js} +1 -1
  48. package/dist/assets/{psyche-modes-page-Dd77kTtJ.js → psyche-modes-page-RUdaOQWr.js} +1 -1
  49. package/dist/assets/{psyche-page-DNXZjIKp.js → psyche-page-D3FZy__c.js} +1 -1
  50. package/dist/assets/{psyche-patterns-page-DxGzpeT5.js → psyche-patterns-page-BSD35F41.js} +1 -1
  51. package/dist/assets/{psyche-questionnaire-builder-page-5EQctRHO.js → psyche-questionnaire-builder-page-BpgUTMgj.js} +1 -1
  52. package/dist/assets/{psyche-questionnaire-detail-page-W8nhUGAc.js → psyche-questionnaire-detail-page-DwjyjNhg.js} +1 -1
  53. package/dist/assets/{psyche-questionnaire-run-detail-page-DVwzSuqg.js → psyche-questionnaire-run-detail-page-mJ-DgeB-.js} +1 -1
  54. package/dist/assets/{psyche-questionnaire-run-page-BHResQgL.js → psyche-questionnaire-run-page-YuMBkH1G.js} +1 -1
  55. package/dist/assets/{psyche-questionnaires-page-DFTOXpyp.js → psyche-questionnaires-page-CNglToKh.js} +1 -1
  56. package/dist/assets/{psyche-report-detail-page-BqDE_tf7.js → psyche-report-detail-page-CV6tbLxz.js} +1 -1
  57. package/dist/assets/{psyche-reports-page-B75pDz0l.js → psyche-reports-page-BEOe8OPA.js} +1 -1
  58. package/dist/assets/{psyche-schemas-beliefs-page-BjSO4tYL.js → psyche-schemas-beliefs-page-KIRqmO8C.js} +1 -1
  59. package/dist/assets/{psyche-screen-time-page-B7lKf1g7.js → psyche-screen-time-page-DvXuwgU1.js} +1 -1
  60. package/dist/assets/{psyche-self-observation-page-C6RMThwc.js → psyche-self-observation-page-E6yDPBm1.js} +1 -1
  61. package/dist/assets/{psyche-values-page-1O158mIJ.js → psyche-values-page-CSLG7C6a.js} +1 -1
  62. package/dist/assets/{report-chain-fields-CyJ_VFY5.js → report-chain-fields-BYEp3tRr.js} +1 -1
  63. package/dist/assets/{rewards-page-sPlDYuVo.js → rewards-page-pAa_MIte.js} +1 -1
  64. package/dist/assets/{scheduling-rules-editor-DGgcBs1W.js → scheduling-rules-editor-BmGrjC9q.js} +1 -1
  65. package/dist/assets/{schema-badge-Do-YvNHm.js → schema-badge-IVJ4SoeC.js} +1 -1
  66. package/dist/assets/{select-menu-CAM7rMdO.js → select-menu-BE5AIdgO.js} +1 -1
  67. package/dist/assets/{settings-agents-page-BsZY71kz.js → settings-agents-page-hqI0IaNx.js} +1 -1
  68. package/dist/assets/{settings-bin-page-kB3jtHo_.js → settings-bin-page-2bPjFtZ3.js} +1 -1
  69. package/dist/assets/{settings-calendar-page-C-IGNk3_.js → settings-calendar-page-u_TfZJEh.js} +1 -1
  70. package/dist/assets/{settings-data-page-CFGSL9g3.js → settings-data-page-DSQ14F2i.js} +1 -1
  71. package/dist/assets/{settings-logs-page-C7e9FIN_.js → settings-logs-page-BU0CJhDM.js} +1 -1
  72. package/dist/assets/{settings-mobile-page-DzKeZQDM.js → settings-mobile-page-B1fQ61BB.js} +1 -1
  73. package/dist/assets/{settings-models-page-dFhaOV3E.js → settings-models-page-B0Dis_c_.js} +1 -1
  74. package/dist/assets/{settings-page-C0fSyYUx.js → settings-page-V4ibXIp9.js} +1 -1
  75. package/dist/assets/{settings-rewards-page-D-LuZHhv.js → settings-rewards-page-DeCGBNn5.js} +1 -1
  76. package/dist/assets/{settings-section-nav-kFFQSJEe.js → settings-section-nav-DV5umEkp.js} +1 -1
  77. package/dist/assets/{settings-users-page-CX797HPB.js → settings-users-page-BB8Dzd_P.js} +1 -1
  78. package/dist/assets/{settings-wiki-page-CWLAvcsf.js → settings-wiki-page-B0k0KRsU.js} +1 -1
  79. package/dist/assets/{sleep-page-QgBkKyk_.js → sleep-page-CPwUb-em.js} +1 -1
  80. package/dist/assets/{sports-page-C8tLMl7V.js → sports-page-Cl4vZVwh.js} +1 -1
  81. package/dist/assets/{strategies-page-CGgh-KXu.js → strategies-page-DhFXf-g3.js} +1 -1
  82. package/dist/assets/{strategy-detail-page-CnzqjjoD.js → strategy-detail-page-B2gotRKI.js} +1 -1
  83. package/dist/assets/{strategy-dialog-C6AThZ9L.js → strategy-dialog-DD1bbrRB.js} +1 -1
  84. package/dist/assets/{surface-CIDhCEsF.js → surface-98myBzix.js} +1 -1
  85. package/dist/assets/{task-detail-page-Dp0r_qZP.js → task-detail-page-nx4eQbMW.js} +1 -1
  86. package/dist/assets/{timebox-planning-dialog-B3uaSsWI.js → timebox-planning-dialog-DoZ9zUra.js} +1 -1
  87. package/dist/assets/{today-page-CTnMYrPi.js → today-page-7BH7Yne1.js} +1 -1
  88. package/dist/assets/{training-load-page-D0WB5_Tq.js → training-load-page-E82pNn1-.js} +1 -1
  89. package/dist/assets/{vitals-page-0vg8nqwj.js → vitals-page-D64WlwVv.js} +1 -1
  90. package/dist/assets/{weekly-review-page-CSSKlkbH.js → weekly-review-page-BkRxxm7Q.js} +1 -1
  91. package/dist/assets/weight-loss-page-DPbg7rTC.js +5 -0
  92. package/dist/assets/{wiki-article-markdown-CBFvfJx_.js → wiki-article-markdown-B0Q6082A.js} +1 -1
  93. package/dist/assets/{wiki-editor-page-CL1uxpih.js → wiki-editor-page-Tkb50wEA.js} +1 -1
  94. package/dist/assets/{wiki-ingest-history-page-XX8DeNCC.js → wiki-ingest-history-page-CcQzkZRr.js} +1 -1
  95. package/dist/assets/{wiki-ingest-modal-j-ftDmzp.js → wiki-ingest-modal-D6yft0Qb.js} +1 -1
  96. package/dist/assets/{wiki-page-B46TQYpy.js → wiki-page-DEND1jmD.js} +1 -1
  97. package/dist/assets/{workbench-flow-page-CicEuBKq.js → workbench-flow-page-CkNI03j_.js} +1 -1
  98. package/dist/assets/{workbench-page-tXezYLh_.js → workbench-page-W3zQpeWr.js} +1 -1
  99. package/dist/assets/{workout-detail-page-BTO5X0Of.js → workout-detail-page-BVSZv53z.js} +1 -1
  100. package/dist/index.html +1 -1
  101. package/dist/server/server/src/app.js +2 -0
  102. package/dist/server/server/src/health-weight-loss.js +160 -10
  103. package/dist/server/src/lib/weight-loss-energy-model.js +64 -0
  104. package/openclaw.plugin.json +1 -1
  105. package/package.json +1 -1
  106. package/skills/forge-openclaw/SKILL.md +10 -0
  107. package/skills/forge-openclaw/entity_conversation_playbooks.md +33 -0
  108. package/skills/forge-openclaw/psyche_entity_playbooks.md +31 -0
  109. package/dist/assets/weight-loss-page-Dcr4wjjy.js +0 -5
@@ -4,6 +4,7 @@ import { getDatabase, runInTransaction } from "./db.js";
4
4
  import { getSettings } from "./repositories/settings.js";
5
5
  import { getDefaultUser, resolveUserForMutation } from "./repositories/users.js";
6
6
  import { readEncryptedSecret } from "./repositories/calendar.js";
7
+ import { hallNiddkWeeklyRateKgToDailyEnergyAdjustment } from "../../src/lib/weight-loss-energy-model.js";
7
8
  const optionalNumberSchema = z
8
9
  .union([z.coerce.number().finite(), z.null()])
9
10
  .optional();
@@ -269,12 +270,31 @@ function average(values) {
269
270
  }
270
271
  return real.reduce((sum, value) => sum + value, 0) / real.length;
271
272
  }
272
- function metricTotal(metrics, key) {
273
+ function median(values) {
274
+ const sorted = values
275
+ .filter((value) => Number.isFinite(value))
276
+ .sort((left, right) => left - right);
277
+ if (sorted.length === 0) {
278
+ return null;
279
+ }
280
+ const middle = Math.floor(sorted.length / 2);
281
+ if (sorted.length % 2 === 1) {
282
+ return sorted[middle] ?? null;
283
+ }
284
+ return ((sorted[middle - 1] ?? 0) + (sorted[middle] ?? 0)) / 2;
285
+ }
286
+ function metricRecord(metrics, key) {
273
287
  const metric = metrics[key];
274
288
  if (!metric || typeof metric !== "object") {
275
289
  return null;
276
290
  }
277
- const record = metric;
291
+ return metric;
292
+ }
293
+ function metricTotal(metrics, key) {
294
+ const record = metricRecord(metrics, key);
295
+ if (!record) {
296
+ return null;
297
+ }
278
298
  for (const field of ["total", "average", "latest"]) {
279
299
  const value = record[field];
280
300
  if (typeof value === "number" && Number.isFinite(value)) {
@@ -283,6 +303,31 @@ function metricTotal(metrics, key) {
283
303
  }
284
304
  return null;
285
305
  }
306
+ function metricSampleCount(metrics, key) {
307
+ const value = metricRecord(metrics, key)?.sampleCount;
308
+ return typeof value === "number" && Number.isFinite(value) ? value : null;
309
+ }
310
+ function metricLatestSampleAt(metrics, key) {
311
+ const value = metricRecord(metrics, key)?.latestSampleAt;
312
+ return typeof value === "string" && value.trim().length > 0
313
+ ? value.trim()
314
+ : null;
315
+ }
316
+ function sampleHourOfDay(value) {
317
+ if (!value) {
318
+ return null;
319
+ }
320
+ const match = value.match(/T(\d{2}):/);
321
+ if (match?.[1]) {
322
+ const parsed = Number(match[1]);
323
+ return Number.isFinite(parsed) ? parsed : null;
324
+ }
325
+ const date = new Date(value);
326
+ if (Number.isNaN(date.getTime())) {
327
+ return null;
328
+ }
329
+ return date.getHours();
330
+ }
286
331
  function parsePlanNoteNumber(notes, key) {
287
332
  if (!notes) {
288
333
  return null;
@@ -292,6 +337,30 @@ function parsePlanNoteNumber(notes, key) {
292
337
  const parsed = Number(match?.[1]?.trim());
293
338
  return Number.isFinite(parsed) ? parsed : null;
294
339
  }
340
+ function parsePlanNoteText(notes, key) {
341
+ if (!notes) {
342
+ return null;
343
+ }
344
+ const escapedKey = key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
345
+ const match = notes.match(new RegExp(`${escapedKey}=([^;]+)`));
346
+ return match?.[1]?.trim() ?? null;
347
+ }
348
+ function mifflinStJeorRestingKcal(input) {
349
+ if ((input.sex !== "male" && input.sex !== "female") ||
350
+ input.ageYears == null ||
351
+ input.ageYears <= 0 ||
352
+ input.heightCm == null ||
353
+ input.heightCm <= 0 ||
354
+ input.weightKg == null ||
355
+ input.weightKg <= 0) {
356
+ return null;
357
+ }
358
+ const sexAdjustment = input.sex === "male" ? 5 : -161;
359
+ return (10 * input.weightKg +
360
+ 6.25 * input.heightCm -
361
+ 5 * input.ageYears +
362
+ sexAdjustment);
363
+ }
295
364
  function estimateStepActiveCaloriesKcal(input) {
296
365
  if (input.stepCount == null ||
297
366
  input.stepCount <= 0 ||
@@ -344,6 +413,8 @@ function buildStoredEnergyModel(input) {
344
413
  dateKey: row.date_key,
345
414
  activeEnergyKcal: metricTotal(metrics, "activeEnergyBurned"),
346
415
  restingEnergyKcal: metricTotal(metrics, "basalEnergyBurned"),
416
+ restingSampleCount: metricSampleCount(metrics, "basalEnergyBurned"),
417
+ restingLatestSampleAt: metricLatestSampleAt(metrics, "basalEnergyBurned"),
347
418
  exerciseMinutes: metricTotal(metrics, "appleExerciseTime"),
348
419
  stepCount: metricTotal(metrics, "stepCount")
349
420
  };
@@ -396,7 +467,59 @@ function buildStoredEnergyModel(input) {
396
467
  .get(input.userId, input.dayStartAt, input.dayEndAt)
397
468
  : null;
398
469
  const activeEnergyAverage = average(dailyHealthKit.map((day) => day.activeEnergyKcal));
399
- const restingEnergyAverage = average(dailyHealthKit.map((day) => day.restingEnergyKcal));
470
+ const restingEvidenceDays = dailyHealthKit.filter((day) => day.restingEnergyKcal != null);
471
+ const restingExclusionReasons = [];
472
+ const qualifiedRestingDays = restingEvidenceDays.filter((day) => {
473
+ const reasons = [];
474
+ if (day.dateKey >= todayKey) {
475
+ reasons.push("current_day_or_future_day");
476
+ }
477
+ if ((day.restingSampleCount ?? 0) < 24) {
478
+ reasons.push(`sample_count_${day.restingSampleCount ?? 0}_below_24`);
479
+ }
480
+ const latestSampleHour = sampleHourOfDay(day.restingLatestSampleAt);
481
+ if (latestSampleHour == null) {
482
+ reasons.push("missing_latest_basal_sample");
483
+ }
484
+ else if (latestSampleHour < 20) {
485
+ reasons.push(`latest_basal_sample_hour_${latestSampleHour}_before_20`);
486
+ }
487
+ if (input.formulaRestingKcal != null && day.restingEnergyKcal != null) {
488
+ const lowThreshold = Math.max(input.formulaRestingKcal * 0.75, 1000);
489
+ if (day.restingEnergyKcal < lowThreshold) {
490
+ reasons.push(`basal_${round(day.restingEnergyKcal, 0)}_below_${round(lowThreshold, 0)}`);
491
+ }
492
+ }
493
+ if (reasons.length > 0) {
494
+ restingExclusionReasons.push(`${day.dateKey}: ${reasons.join(",")}`);
495
+ return false;
496
+ }
497
+ return true;
498
+ });
499
+ const wearableRestingKcal = median(qualifiedRestingDays
500
+ .map((day) => day.restingEnergyKcal)
501
+ .filter((value) => value != null));
502
+ const chosenRestingKcal = input.formulaRestingKcal ?? input.savedRestingKcal ?? null;
503
+ const chosenRestingSource = input.formulaRestingKcal != null
504
+ ? "mifflin_profile_baseline"
505
+ : input.savedRestingKcal != null
506
+ ? "saved_plan_legacy"
507
+ : null;
508
+ const restingConfidence = (() => {
509
+ if (input.formulaRestingKcal == null && input.savedRestingKcal != null) {
510
+ return "low";
511
+ }
512
+ if (input.formulaRestingKcal == null) {
513
+ return "low";
514
+ }
515
+ if (wearableRestingKcal != null &&
516
+ Math.abs(wearableRestingKcal - input.formulaRestingKcal) /
517
+ input.formulaRestingKcal >
518
+ 0.15) {
519
+ return "review";
520
+ }
521
+ return qualifiedRestingDays.length > 0 ? "high" : "medium";
522
+ })();
400
523
  const workoutEnergyAverage = average([...workoutByDay.values()]);
401
524
  const movementCaloriesAverage = average([...movementByDay.values()]);
402
525
  const fallbackActiveBurn = workoutEnergyAverage != null || movementCaloriesAverage != null
@@ -477,13 +600,15 @@ function buildStoredEnergyModel(input) {
477
600
  const todayActiveCalories = input.dailyActiveOverride?.activeCaloriesKcal ??
478
601
  todayObservedActiveCalories ??
479
602
  baselineActiveCalories;
480
- const todayTargetAdjustmentKcal = todayActiveCalories - baselineActiveCalories;
481
- const estimatedTdeeKcal = activeBurnKcal != null && restingEnergyAverage != null
482
- ? round(activeBurnKcal + restingEnergyAverage, 0)
603
+ const todayActiveSurplusKcal = Math.max(0, todayActiveCalories - baselineActiveCalories);
604
+ const todayActivityBufferKcal = todayActiveSurplusKcal * input.activityEatBackFraction;
605
+ const todayTargetAdjustmentKcal = todayActivityBufferKcal;
606
+ const estimatedTdeeKcal = activeBurnKcal != null && chosenRestingKcal != null
607
+ ? round(activeBurnKcal + chosenRestingKcal, 0)
483
608
  : input.inferredTdee;
484
609
  const hasHealthKitDailyActiveEnergy = activeEnergyAverage != null;
485
610
  const hasHealthKitEnergy = hasHealthKitDailyActiveEnergy ||
486
- restingEnergyAverage != null ||
611
+ wearableRestingKcal != null ||
487
612
  workoutEnergyAverage != null;
488
613
  const hasMovementEnergy = movementCaloriesAverage != null;
489
614
  const sourceConfidence = activeEnergyAverage != null
@@ -493,7 +618,18 @@ function buildStoredEnergyModel(input) {
493
618
  : "target_inference_only";
494
619
  return {
495
620
  activeEnergyCalories: activeEnergyAverage != null ? round(activeEnergyAverage, 0) : null,
496
- restingEnergyCalories: restingEnergyAverage != null ? round(restingEnergyAverage, 0) : null,
621
+ restingEnergyCalories: chosenRestingKcal != null ? round(chosenRestingKcal, 0) : null,
622
+ formulaRestingKcal: input.formulaRestingKcal != null
623
+ ? round(input.formulaRestingKcal, 0)
624
+ : null,
625
+ wearableRestingKcal: wearableRestingKcal != null ? round(wearableRestingKcal, 0) : null,
626
+ wearableRestingSource: wearableRestingKcal != null ? "healthkit_basal_energy" : null,
627
+ wearableRestingDayCount: restingEvidenceDays.length,
628
+ wearableRestingCoverageQualifiedDayCount: qualifiedRestingDays.length,
629
+ chosenRestingKcal: chosenRestingKcal != null ? round(chosenRestingKcal, 0) : null,
630
+ chosenRestingSource,
631
+ restingConfidence,
632
+ restingExclusionReasons,
497
633
  wearableConfidence: hasHealthKitEnergy
498
634
  ? "measured_directional"
499
635
  : "directional",
@@ -507,6 +643,9 @@ function buildStoredEnergyModel(input) {
507
643
  : null,
508
644
  todayActiveCaloriesSource: todayActiveSource,
509
645
  todayTargetAdjustmentKcal: round(todayTargetAdjustmentKcal, 0),
646
+ todayActiveSurplusKcal: round(todayActiveSurplusKcal, 0),
647
+ todayActivityBufferKcal: round(todayActivityBufferKcal, 0),
648
+ activityEatBackFraction: round(input.activityEatBackFraction, 2),
510
649
  todayWorkoutEnergyKcal: todayWorkoutEnergy != null ? round(todayWorkoutEnergy, 0) : null,
511
650
  todayMovementCaloriesKcal: todayMovementCalories != null ? round(todayMovementCalories, 0) : null,
512
651
  todayHealthKitActiveCaloriesKcal: todayHealthKitActive != null ? round(todayHealthKitActive, 0) : null,
@@ -1539,9 +1678,17 @@ export function getWeightLossViewData(userIds, options = {}) {
1539
1678
  ? round(recentTotals.calories / recentTrackedDays, 0)
1540
1679
  : 0;
1541
1680
  const inferredTdee = target.calorieTarget != null
1542
- ? round(target.calorieTarget + Math.abs(n(target.weeklyRateGoalKg)) * 1100, 0)
1681
+ ? round(target.calorieTarget +
1682
+ Math.abs(hallNiddkWeeklyRateKgToDailyEnergyAdjustment(target.weeklyRateGoalKg) ?? 0), 0)
1543
1683
  : null;
1544
1684
  const defaultActiveCalories = parsePlanNoteNumber(target.notes, "activity_kcal");
1685
+ const formulaRestingKcal = mifflinStJeorRestingKcal({
1686
+ sex: parsePlanNoteText(target.notes, "sex"),
1687
+ ageYears: parsePlanNoteNumber(target.notes, "age_years"),
1688
+ heightCm: parsePlanNoteNumber(target.notes, "height_cm"),
1689
+ weightKg: weightTrend.latestWeightKg
1690
+ });
1691
+ const activityEatBackFraction = Math.min(1, Math.max(0, parsePlanNoteNumber(target.notes, "eat_back_fraction") ?? 0.5));
1545
1692
  const dailyActiveOverride = getDailyEnergyOverride(userId, todayKey);
1546
1693
  const energyModel = buildStoredEnergyModel({
1547
1694
  userId,
@@ -1554,7 +1701,10 @@ export function getWeightLossViewData(userIds, options = {}) {
1554
1701
  recentFoodLogDayCount: recentTrackedDays,
1555
1702
  defaultActiveCalories,
1556
1703
  latestWeightKg: weightTrend.latestWeightKg,
1557
- dailyActiveOverride
1704
+ dailyActiveOverride,
1705
+ formulaRestingKcal,
1706
+ savedRestingKcal: parsePlanNoteNumber(target.notes, "resting_kcal"),
1707
+ activityEatBackFraction
1558
1708
  });
1559
1709
  const todayTargetCalories = Math.max(0, round(target.calorieTarget + energyModel.todayTargetAdjustmentKcal, 0));
1560
1710
  const todayLedger = buildTodayLedger(logs, target, todayKey, todayTargetCalories, energyModel.todayTargetAdjustmentKcal, energyModel.todayActiveCaloriesSource);
@@ -0,0 +1,64 @@
1
+ export const HALL_NIDDK_LINEAR_WEIGHT_MODEL = {
2
+ id: "hall_niddk_linearized_adult_12w",
3
+ label: "Hall/NIDDK linearized adult model",
4
+ defaultPlanningHorizonDays: 84,
5
+ steadyStateKcalPerKgPerDay: 10 / 0.45359237,
6
+ timeConstantDays: 365,
7
+ staticEnergyDensityKcalPerKg: 7700
8
+ };
9
+ function positiveFinite(value) {
10
+ return typeof value === "number" && Number.isFinite(value) && value > 0
11
+ ? value
12
+ : null;
13
+ }
14
+ export function hallNiddkWeightModelParameters(options = {}) {
15
+ const planningHorizonDays = positiveFinite(options.planningHorizonDays) ??
16
+ HALL_NIDDK_LINEAR_WEIGHT_MODEL.defaultPlanningHorizonDays;
17
+ const timeConstantDays = positiveFinite(options.timeConstantDays) ??
18
+ HALL_NIDDK_LINEAR_WEIGHT_MODEL.timeConstantDays;
19
+ const steadyStateKcalPerKgPerDay = positiveFinite(options.steadyStateKcalPerKgPerDay) ??
20
+ HALL_NIDDK_LINEAR_WEIGHT_MODEL.steadyStateKcalPerKgPerDay;
21
+ const responseFraction = 1 - Math.exp(-planningHorizonDays / timeConstantDays);
22
+ return {
23
+ planningHorizonDays,
24
+ timeConstantDays,
25
+ steadyStateKcalPerKgPerDay,
26
+ responseFraction
27
+ };
28
+ }
29
+ export function hallNiddkWeeklyRateKgToDailyEnergyAdjustment(weeklyRateKg, options = {}) {
30
+ if (typeof weeklyRateKg !== "number" || !Number.isFinite(weeklyRateKg)) {
31
+ return null;
32
+ }
33
+ if (weeklyRateKg === 0) {
34
+ return 0;
35
+ }
36
+ const parameters = hallNiddkWeightModelParameters(options);
37
+ if (parameters.responseFraction <= 0) {
38
+ return null;
39
+ }
40
+ const targetChangeKg = weeklyRateKg * (parameters.planningHorizonDays / 7);
41
+ return ((targetChangeKg * parameters.steadyStateKcalPerKgPerDay) /
42
+ parameters.responseFraction);
43
+ }
44
+ export function hallNiddkDailyEnergyAdjustmentToAverageWeeklyRateKg(dailyEnergyAdjustmentKcal, options = {}) {
45
+ if (typeof dailyEnergyAdjustmentKcal !== "number" ||
46
+ !Number.isFinite(dailyEnergyAdjustmentKcal)) {
47
+ return null;
48
+ }
49
+ if (dailyEnergyAdjustmentKcal === 0) {
50
+ return 0;
51
+ }
52
+ const parameters = hallNiddkWeightModelParameters(options);
53
+ const totalChangeKg = (dailyEnergyAdjustmentKcal / parameters.steadyStateKcalPerKgPerDay) *
54
+ parameters.responseFraction;
55
+ return totalChangeKg / (parameters.planningHorizonDays / 7);
56
+ }
57
+ export function staticKgRateToDailyEnergyAdjustment(weeklyRateKg) {
58
+ if (typeof weeklyRateKg !== "number" || !Number.isFinite(weeklyRateKg)) {
59
+ return null;
60
+ }
61
+ return ((weeklyRateKg *
62
+ HALL_NIDDK_LINEAR_WEIGHT_MODEL.staticEnergyDensityKcalPerKg) /
63
+ 7);
64
+ }
@@ -2,7 +2,7 @@
2
2
  "id": "forge-openclaw-plugin",
3
3
  "name": "Forge",
4
4
  "description": "Curated OpenClaw adapter for the Forge collaboration API, UI entrypoint, and localhost auto-start runtime.",
5
- "version": "0.2.109",
5
+ "version": "0.2.111",
6
6
  "activation": {
7
7
  "onStartup": true,
8
8
  "onCapabilities": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "forge-openclaw-plugin",
3
- "version": "0.2.109",
3
+ "version": "0.2.111",
4
4
  "description": "Curated OpenClaw adapter for the Forge collaboration API, UI entrypoint, and localhost auto-start runtime.",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -238,6 +238,11 @@ Entity conversation rule:
238
238
 
239
239
  - For all entity creation or update flows, first use [`entity_conversation_playbooks.md`](./entity_conversation_playbooks.md) to decide the next best question.
240
240
  - Ask only for what is missing or unclear. Do not walk through every schema field.
241
+ - Before asking another question, run the playbook's minimum save-readiness
242
+ checkpoint: if accepted wording, meaningful body, route lane, target object or
243
+ time scope, and any ownership/placement that changes later use are already clear,
244
+ summarize once and write, read, run, or update instead of collecting optional
245
+ fields.
241
246
  - Let each question have one job. Know what you are trying to clarify before you ask it.
242
247
  - Before you ask, decide the exact missing thing you need and how that answer will help you name, place, or save the record.
243
248
  - Prefer a progression of:
@@ -299,6 +304,11 @@ Psyche interview rule:
299
304
  - In that first exploratory turn, ask only one question, do not search Forge or mention whether a matching entity exists, and avoid openings like "This sounds like" or "What you're describing is".
300
305
  - In that first exploratory turn, prefer exactly two sentences: one brief empathic reflection and one concrete question. Avoid colons because they tend to trigger list-like answers.
301
306
  - Follow the preferred opening-question patterns in [`psyche_entity_playbooks.md`](./psyche_entity_playbooks.md) when they fit the entity the user is exploring.
307
+ - After a Psyche formulation lands, use the Psyche save-readiness checkpoint from
308
+ the playbook. If the belief sentence, functional loop, behavior move, part-state,
309
+ trigger episode, value, event type, emotion definition, or flashcard cue/message is
310
+ true enough to save, ask at most one accuracy question and then use shared batch
311
+ CRUD.
302
312
  - If the user already offers a usable belief sentence, value phrase, or mode name,
303
313
  refine from their wording first instead of replacing it with a cleaner label too
304
314
  early.
@@ -258,6 +258,39 @@ Most good Forge intake flows follow this sequence:
258
258
 
259
259
  That sequence is not a script. Skip steps the user already answered.
260
260
 
261
+ ## Minimum save-readiness checkpoint
262
+
263
+ Use this before asking another polished follow-up. The question quality is worse, not
264
+ better, when the agent keeps exploring after the record or route is already clear
265
+ enough to act.
266
+
267
+ - For normal batch entities, save or update when you have the accepted working name
268
+ or distinctive wording, the meaningful body of the record, and owner scope only if
269
+ ownership changes accountability. Do not ask for tags, links, dates, priority,
270
+ assignees, or status just because those fields exist.
271
+ - For strategic and planning records, the minimum is the intended outcome plus the
272
+ hierarchy placement that would change later retrieval or execution. If placement is
273
+ not known but the user asked to capture the idea now, save the provisional record
274
+ with clear wording and leave placement for a later link/update.
275
+ - For operational records, the minimum is the target action plus the time, object, or
276
+ state that makes the action truthful: event time for a calendar event, recurrence
277
+ for a work block, task and slot for a timebox, task id for a task run, or target
278
+ record and signed minutes for a work adjustment. Generate a plain title yourself
279
+ when the title is obvious.
280
+ - For read-model and review surfaces, the minimum is the practical question plus any
281
+ scope that would change the answer. Once you have that, read the overview instead
282
+ of asking for a preferred report shape.
283
+ - For specialized Movement, Life Force, and Workbench writes, the minimum is the
284
+ selected lane plus the target span/object/weekday/flow/run/node and the intended
285
+ correction or effect. Do not ask a reflective question after the dedicated route
286
+ and write shape are already selected.
287
+ - For reflective non-Psyche records, the minimum is what future review should
288
+ remember and the container that preserves it. If a stronger Psyche container clearly
289
+ emerges, route there; otherwise do not keep deepening just to make the note more
290
+ therapeutic.
291
+ - Close the loop in one sentence before acting: "What seems clear now is..." followed
292
+ by the save, update, read, run, or handoff.
293
+
261
294
  ## Project-management hierarchy playbook
262
295
 
263
296
  When the conversation is about Forge planning or delivery, preserve this
@@ -236,6 +236,37 @@ of leaving it as warm reflective prose.
236
236
  - Save through shared batch entity routes only after the user accepts the working
237
237
  wording or explicitly asks to save.
238
238
 
239
+ ## Psyche save-readiness checkpoint
240
+
241
+ Use this before asking another deepening question. Psyche work should feel careful,
242
+ but not endless. Once the user recognizes the working formulation, protect momentum and
243
+ save the record instead of reopening the whole story.
244
+
245
+ - For `belief_entry`, the minimum is the accepted sentence or prediction plus whether
246
+ it is mainly about self, others, world/safety, or outcome when that distinction is
247
+ visible.
248
+ - For `behavior_pattern`, the minimum is a concrete cue or situation, the felt
249
+ meaning/body shift, the behavior or urge, and at least one payoff or cost. A
250
+ preferred response is useful, but do not force it before saving a provisional
251
+ functional analysis.
252
+ - For `behavior`, the minimum is the repeated move, the cue or urge that brings it
253
+ online, and whether the move functions as avoidance, committed action, repair, or
254
+ recovery when that is clear.
255
+ - For `mode_profile`, the minimum is the part's voice or posture, what it is trying
256
+ to protect, and what danger or burden it expects.
257
+ - For `mode_guide_session`, the minimum is the present trigger, a brief summary of
258
+ what the active part seems to want, and the answers gathered so far.
259
+ - For `trigger_report`, the minimum is the situation, felt stake, meaning or thought,
260
+ action/urge, and consequence that make it one emotionally meaningful episode.
261
+ - For `psyche_value`, `event_type`, and `emotion_definition`, the minimum is the
262
+ future use: what this value, recurring moment, or lived feeling label will help
263
+ future reports notice consistently.
264
+ - For `flashcard`, the minimum is the cue or urge sentence and one brief message the
265
+ user can recognize while triggered. Link to beliefs, modes, values, or patterns only
266
+ when the link is already clear or will materially improve retrieval.
267
+ - After the minimum is present, ask one accuracy question at most: "Is this true
268
+ enough to save as a first version?" If yes, save through shared batch CRUD.
269
+
239
270
  ## Psyche Hypothesis Map
240
271
 
241
272
  Use these shapes after at least one concrete example is clear. The hypothesis should