forge-openclaw-plugin 0.2.99 → 0.2.101

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-CpjuNSHw.js +1 -0
  3. package/dist/assets/ai-surface-workspace-DEAFZruS.js +1 -0
  4. package/dist/assets/atlas-panel-CdVNPotj.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-DNNt6lfz.js +1 -0
  8. package/dist/assets/calendar-rules-DNJFNsxi.js +1 -0
  9. package/dist/assets/calendar-ui-Cy1XRwzV.js +1 -0
  10. package/dist/assets/calendar-week-toolbar-BbPwYeN0.js +1 -0
  11. package/dist/assets/charts-P7EVhIog.js +36 -0
  12. package/dist/assets/companion-sync-lab-page-KxEDigM6.js +1 -0
  13. package/dist/assets/daily-metrics-dashboard-B3cqJgDt.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-DrhjJZ4i.js +1 -0
  17. package/dist/assets/entity-notes-surface-CkcRsKJQ.js +1 -0
  18. package/dist/assets/execution-board-D07gOocB.js +1 -0
  19. package/dist/assets/faceted-token-search-BxRRcM3q.js +1 -0
  20. package/dist/assets/flagship-signal-deck-cmy82b8_.js +1 -0
  21. package/dist/assets/floating-action-menu-Fs_ZiUMo.js +1 -0
  22. package/dist/assets/forms-BFlTgZ3W.js +1 -0
  23. package/dist/assets/goal-detail-page-CqLiNz4f.js +1 -0
  24. package/dist/assets/goals-page-BTk7mg_T.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-BJxagdzx.js +1 -0
  28. package/dist/assets/health-link-options-Cpx8w7uM.js +1 -0
  29. package/dist/assets/index-CF4J4R9L.js +19 -0
  30. package/dist/assets/index-CZbuZQjw.css +1 -0
  31. package/dist/assets/insight-flow-dialog-8f3D0GuC.js +1 -0
  32. package/dist/assets/insights-page-D6rOa7uk.js +8 -0
  33. package/dist/assets/kanban-page-XQ7Se6dH.js +1 -0
  34. package/dist/assets/knowledge-graph-page-BtAg8iv3.js +1 -0
  35. package/dist/assets/life-force-page-Dy0JTS2G.js +1 -0
  36. package/dist/assets/life-force-workspace-OfyB9HJM.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-DKpo-8xw.js +1 -0
  40. package/dist/assets/{motion-DRPJkN3a.js → motion-BeD44FeG.js} +1 -1
  41. package/dist/assets/movement-page-Bg_T_Stx.js +1 -0
  42. package/dist/assets/note-markdown-N-uxD3Xt.js +3 -0
  43. package/dist/assets/note-tags-input-Cdu7wiw6.js +1 -0
  44. package/dist/assets/notes-page-CKXnF_KU.js +1 -0
  45. package/dist/assets/open-in-graph-button-IXe9SGth.js +1 -0
  46. package/dist/assets/orbit-map-Dzi6KliQ.js +1 -0
  47. package/dist/assets/overview-page-1miYqaVS.js +1 -0
  48. package/dist/assets/page-hero-DRy5b2MU.js +1 -0
  49. package/dist/assets/pill-cluster-C9QczVJ2.js +1 -0
  50. package/dist/assets/preference-entity-handoff-button-5PzUn42S.js +1 -0
  51. package/dist/assets/preferences-page-DtNaF5Q3.js +1 -0
  52. package/dist/assets/project-collections-xPz2mlRr.js +1 -0
  53. package/dist/assets/project-detail-page-BXK5-4xW.js +1 -0
  54. package/dist/assets/project-management-hierarchy-page-DtRpMABw.js +1 -0
  55. package/dist/assets/project-management-section-nav-P3ixzPa-.js +1 -0
  56. package/dist/assets/projects-page-C5ViRuf4.js +1 -0
  57. package/dist/assets/psyche-behaviors-page-Dco46sC4.js +5 -0
  58. package/dist/assets/psyche-flashcards-page-ZcoEB8gV.js +1 -0
  59. package/dist/assets/psyche-goal-map-page-CLBAQOI0.js +1 -0
  60. package/dist/assets/psyche-graph-k4tX2tdp.js +1 -0
  61. package/dist/assets/psyche-metrics-page-CuR9oqEy.js +1 -0
  62. package/dist/assets/psyche-mode-guide-page-hIVXcCnE.js +1 -0
  63. package/dist/assets/psyche-modes-page-0lYtBlhO.js +1 -0
  64. package/dist/assets/psyche-page-VZ9k9ISp.js +1 -0
  65. package/dist/assets/psyche-patterns-page-gx5nmdGq.js +5 -0
  66. package/dist/assets/psyche-questionnaire-builder-page-Bn0TOISd.js +1 -0
  67. package/dist/assets/psyche-questionnaire-detail-page-CmVzSd_s.js +1 -0
  68. package/dist/assets/psyche-questionnaire-run-detail-page-BsMbmXCG.js +1 -0
  69. package/dist/assets/psyche-questionnaire-run-page-CgkRL2vi.js +1 -0
  70. package/dist/assets/psyche-questionnaires-page-D7V8uLXM.js +1 -0
  71. package/dist/assets/psyche-report-detail-page-OlFq57eL.js +3 -0
  72. package/dist/assets/psyche-reports-page-dVUZjna1.js +1 -0
  73. package/dist/assets/psyche-schemas-HFmg37Wj.js +1 -0
  74. package/dist/assets/psyche-schemas-beliefs-page-BCgc8FUd.js +9 -0
  75. package/dist/assets/psyche-screen-time-page-B_6BT_WN.js +1 -0
  76. package/dist/assets/psyche-self-observation-page-CEG5mluK.js +1 -0
  77. package/dist/assets/psyche-values-page-DRbRfEd6.js +5 -0
  78. package/dist/assets/query-cache-IQ8W-LNC.js +1 -0
  79. package/dist/assets/report-chain-fields-CALCV3V5.js +1 -0
  80. package/dist/assets/rewards-page-DmC4R_Ps.js +1 -0
  81. package/dist/assets/scheduling-rules-editor-D02s70hr.js +1 -0
  82. package/dist/assets/schema-badge-BZO-qNhO.js +1 -0
  83. package/dist/assets/schema-visuals-D6nxjbYC.js +1 -0
  84. package/dist/assets/select-menu-fYyreSdQ.js +1 -0
  85. package/dist/assets/settings-agents-page-C_v_hMJF.js +6 -0
  86. package/dist/assets/settings-bin-page-DY5bg81n.js +1 -0
  87. package/dist/assets/settings-calendar-page-D1CzE6cg.js +5 -0
  88. package/dist/assets/settings-data-page-CHRQFU9H.js +1 -0
  89. package/dist/assets/settings-logs-page-B04pUwEv.js +1 -0
  90. package/dist/assets/settings-mobile-page-D9kTlYDS.js +1 -0
  91. package/dist/assets/settings-models-page-D26270R2.js +1 -0
  92. package/dist/assets/settings-page-DYDTFlnv.js +1 -0
  93. package/dist/assets/settings-rewards-page-cl4vqqO_.js +1 -0
  94. package/dist/assets/settings-section-nav-DSOuht_F.js +1 -0
  95. package/dist/assets/settings-users-page-BU79JB_T.js +1 -0
  96. package/dist/assets/settings-wiki-page-DwAUlyA3.js +1 -0
  97. package/dist/assets/sleep-page-D8NbdhyS.js +1 -0
  98. package/dist/assets/sports-page-CV4Cnzwn.js +1 -0
  99. package/dist/assets/state-B-4sS1xO.js +1 -0
  100. package/dist/assets/strategies-page-C4qvXnql.js +1 -0
  101. package/dist/assets/strategy-detail-page-DJLo5rfy.js +1 -0
  102. package/dist/assets/strategy-dialog-D3AuUlVz.js +1 -0
  103. package/dist/assets/{table-DewbFlTh.js → table-WfAPUppN.js} +1 -1
  104. package/dist/assets/task-detail-page-z-9u9rF0.js +1 -0
  105. package/dist/assets/timebox-planning-dialog-DB6FLqmI.js +1 -0
  106. package/dist/assets/today-page-BKlu6gx5.js +1 -0
  107. package/dist/assets/training-load-page-CyJQqo_3.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-qre17Nw8.js +1 -0
  113. package/dist/assets/weekly-review-page-Cz4vkRcx.js +1 -0
  114. package/dist/assets/weight-loss-page-BQrnOI0y.js +1 -0
  115. package/dist/assets/wiki-article-markdown-DdiR2TJE.js +4 -0
  116. package/dist/assets/wiki-editor-page-DqwoqVFb.js +26 -0
  117. package/dist/assets/wiki-ingest-history-page--evBLbOw.js +1 -0
  118. package/dist/assets/wiki-ingest-modal--ohzFnj2.js +1 -0
  119. package/dist/assets/wiki-page-B_VJFBPA.js +1 -0
  120. package/dist/assets/workbench-flow-page-Du62mtJU.js +5 -0
  121. package/dist/assets/workbench-page-4MKr3iRm.js +1 -0
  122. package/dist/assets/workout-detail-page-DfUbYYw1.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 +576 -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
@@ -3082,6 +3082,7 @@ function summarizeChunkPayload(family, payload) {
3082
3082
  return { daySummaries: payload.vitals?.daySummaries.length ?? 0 };
3083
3083
  case "movement":
3084
3084
  return {
3085
+ knownPlaces: payload.movement?.knownPlaces.length ?? 0,
3085
3086
  stays: payload.movement?.stays.length ?? 0,
3086
3087
  trips: payload.movement?.trips.length ?? 0
3087
3088
  };
@@ -3210,6 +3211,76 @@ function applyWorkoutEvidenceChunkImmediately(session, payload) {
3210
3211
  }
3211
3212
  });
3212
3213
  }
3214
+ function markMobileHealthSyncChunkApplied(input) {
3215
+ const now = nowIso();
3216
+ getDatabase()
3217
+ .prepare(`UPDATE health_mobile_sync_chunks
3218
+ SET applied_at = ?, payload_summary_json = ?, updated_at = ?
3219
+ WHERE sync_session_id = ? AND chunk_id = ?`)
3220
+ .run(now, JSON.stringify({
3221
+ ...input.payloadSummary,
3222
+ immediateApplied: true,
3223
+ immediateAppliedAt: now,
3224
+ immediateAppliedMode: input.mode
3225
+ }), now, input.syncSessionId, input.chunkId);
3226
+ }
3227
+ function mobileHealthSyncChunkWasImmediatelyApplied(chunk) {
3228
+ const summary = safeJsonParse(chunk.payload_summary_json, {});
3229
+ return summary.immediateApplied === true;
3230
+ }
3231
+ function applyMovementChunkImmediately(session, payload) {
3232
+ if (!payload.movement) {
3233
+ return false;
3234
+ }
3235
+ const pairing = mobileSyncSessionPairing(session);
3236
+ ingestMovementSync(pairing, payload.movement);
3237
+ return true;
3238
+ }
3239
+ function applyScreenTimeChunkImmediately(session, payload) {
3240
+ if (!payload.screenTime) {
3241
+ return false;
3242
+ }
3243
+ const pairing = mobileSyncSessionPairing(session);
3244
+ const metadata = mobileSyncSessionMetadata(session);
3245
+ ingestScreenTimeSync(pairing, payload.screenTime, metadata.device?.sourceDevice ?? pairing.device_name ?? "iPhone");
3246
+ return true;
3247
+ }
3248
+ function applyVitalsChunkImmediately(session, payload) {
3249
+ if (!payload.vitals) {
3250
+ return false;
3251
+ }
3252
+ const pairing = mobileSyncSessionPairing(session);
3253
+ for (const daySummary of payload.vitals.daySummaries) {
3254
+ upsertVitalDaySummary(pairing.user_id, daySummary);
3255
+ }
3256
+ return true;
3257
+ }
3258
+ function applyMobileHealthSyncChunkImmediately(session, family, payload) {
3259
+ switch (family) {
3260
+ case "workout_summaries":
3261
+ case "workout_archive":
3262
+ applyWorkoutChunkImmediately(session, family, payload.workouts ?? []);
3263
+ return "workout_progressive_apply";
3264
+ case "workout_time_series":
3265
+ case "workout_routes":
3266
+ applyWorkoutEvidenceChunkImmediately(session, payload);
3267
+ return "workout_evidence_progressive_apply";
3268
+ case "movement":
3269
+ return applyMovementChunkImmediately(session, payload)
3270
+ ? "movement_progressive_apply"
3271
+ : null;
3272
+ case "screen_time":
3273
+ return applyScreenTimeChunkImmediately(session, payload)
3274
+ ? "screen_time_progressive_apply"
3275
+ : null;
3276
+ case "vitals":
3277
+ return applyVitalsChunkImmediately(session, payload)
3278
+ ? "vitals_progressive_apply"
3279
+ : null;
3280
+ default:
3281
+ return null;
3282
+ }
3283
+ }
3213
3284
  function updateMobileSyncSessionProgress(syncSessionId) {
3214
3285
  const progress = mobileSyncSessionProgress(syncSessionId);
3215
3286
  getDatabase()
@@ -3376,40 +3447,44 @@ export function ingestMobileHealthSyncChunk(syncSessionId, payload, rawPayloadJs
3376
3447
  mode: wirePayload.mode
3377
3448
  });
3378
3449
  }
3379
- const now = nowIso();
3380
- getDatabase()
3381
- .prepare(`INSERT INTO health_mobile_sync_chunks (
3382
- id, sync_session_id, chunk_id, sequence, family, checksum_sha256,
3383
- record_count, byte_count, payload_json, payload_summary_json,
3384
- received_at, applied_at, created_at, updated_at
3385
- )
3386
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
3387
- .run(mobileSyncChunkRecordId(), syncSessionId, parsed.chunkId, parsed.sequence, parsed.family, serverChecksum, parsed.recordCount, actualByteCount, payloadJson, JSON.stringify({
3388
- ...summarizeChunkPayload(parsed.family, wirePayload.payload),
3389
- clientByteCount: parsed.byteCount,
3390
- actualByteCount,
3391
- compressedByteCount: wirePayload.compressedByteCount ?? null,
3392
- serverChecksum,
3393
- mode: wirePayload.mode
3394
- }), now, now, now, now);
3395
- if (parsed.family === "workout_summaries" ||
3396
- parsed.family === "workout_archive") {
3397
- applyWorkoutChunkImmediately(session, parsed.family, wirePayload.payload.workouts ?? []);
3398
- }
3399
- else if (parsed.family === "workout_time_series" ||
3400
- parsed.family === "workout_routes") {
3401
- applyWorkoutEvidenceChunkImmediately(session, wirePayload.payload);
3402
- }
3403
- const progress = updateMobileSyncSessionProgress(syncSessionId);
3404
- return {
3405
- accepted: true,
3406
- duplicate: false,
3407
- receivedCount: progress.chunkCount,
3408
- receivedBytes: progress.receivedBytes,
3409
- progress
3410
- };
3450
+ return runInTransaction(() => {
3451
+ const now = nowIso();
3452
+ const payloadSummary = {
3453
+ ...summarizeChunkPayload(parsed.family, wirePayload.payload),
3454
+ clientByteCount: parsed.byteCount,
3455
+ actualByteCount,
3456
+ compressedByteCount: wirePayload.compressedByteCount ?? null,
3457
+ serverChecksum,
3458
+ mode: wirePayload.mode
3459
+ };
3460
+ getDatabase()
3461
+ .prepare(`INSERT INTO health_mobile_sync_chunks (
3462
+ id, sync_session_id, chunk_id, sequence, family, checksum_sha256,
3463
+ record_count, byte_count, payload_json, payload_summary_json,
3464
+ received_at, applied_at, created_at, updated_at
3465
+ )
3466
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?)`)
3467
+ .run(mobileSyncChunkRecordId(), syncSessionId, parsed.chunkId, parsed.sequence, parsed.family, serverChecksum, parsed.recordCount, actualByteCount, payloadJson, JSON.stringify(payloadSummary), now, now, now);
3468
+ const appliedMode = applyMobileHealthSyncChunkImmediately(session, parsed.family, wirePayload.payload);
3469
+ if (appliedMode) {
3470
+ markMobileHealthSyncChunkApplied({
3471
+ syncSessionId,
3472
+ chunkId: parsed.chunkId,
3473
+ payloadSummary,
3474
+ mode: appliedMode
3475
+ });
3476
+ }
3477
+ const progress = updateMobileSyncSessionProgress(syncSessionId);
3478
+ return {
3479
+ accepted: true,
3480
+ duplicate: false,
3481
+ receivedCount: progress.chunkCount,
3482
+ receivedBytes: progress.receivedBytes,
3483
+ progress
3484
+ };
3485
+ });
3411
3486
  }
3412
- function mergeMobileHealthSyncChunks(session, chunks) {
3487
+ function mergeMobileHealthSyncChunks(session, chunks, options = {}) {
3413
3488
  const pairing = getDatabase()
3414
3489
  .prepare(`SELECT * FROM companion_pairing_sessions WHERE id = ?`)
3415
3490
  .get(session.pairing_session_id);
@@ -3449,6 +3524,17 @@ function mergeMobileHealthSyncChunks(session, chunks) {
3449
3524
  const tombstones = [];
3450
3525
  for (const chunk of chunks.sort((left, right) => left.sequence - right.sequence)) {
3451
3526
  const payload = mobileHealthSyncChunkPayloadSchema.parse(safeJsonParse(chunk.payload_json, {}));
3527
+ const skipRecords = options.skipImmediatelyApplied === true &&
3528
+ mobileHealthSyncChunkWasImmediatelyApplied(chunk);
3529
+ if (skipRecords) {
3530
+ if (payload.movement?.settings) {
3531
+ assembled.movement.settings = payload.movement.settings;
3532
+ }
3533
+ if (payload.screenTime?.settings) {
3534
+ assembled.screenTime.settings = payload.screenTime.settings;
3535
+ }
3536
+ continue;
3537
+ }
3452
3538
  if (payload.sleepNights) {
3453
3539
  assembled.sleepNights.push(...payload.sleepNights);
3454
3540
  }
@@ -3556,6 +3642,73 @@ function validateMobileSyncExpectedCounts(syncSessionId, expectedCounts) {
3556
3642
  throw new HttpError(409, "missing_required_chunks", "The HealthKit sync session is missing required chunks.", { families: missingFamilies });
3557
3643
  }
3558
3644
  }
3645
+ function aggregateMobileSyncChunkCounts(chunks) {
3646
+ const counts = {
3647
+ sleepNights: 0,
3648
+ sleepSegments: 0,
3649
+ sleepRawRecords: 0,
3650
+ workouts: 0,
3651
+ vitalsDaySummaries: 0,
3652
+ movementKnownPlaces: 0,
3653
+ movementStays: 0,
3654
+ movementTrips: 0,
3655
+ screenTimeDaySummaries: 0,
3656
+ screenTimeHourlySegments: 0
3657
+ };
3658
+ for (const chunk of chunks) {
3659
+ const summary = safeJsonParse(chunk.payload_summary_json, {});
3660
+ const numberValue = (key) => typeof summary[key] === "number" && Number.isFinite(summary[key])
3661
+ ? summary[key]
3662
+ : 0;
3663
+ switch (chunk.family) {
3664
+ case "sleep_nights":
3665
+ counts.sleepNights += numberValue("sleepNights");
3666
+ break;
3667
+ case "sleep_segments":
3668
+ counts.sleepSegments += numberValue("sleepSegments");
3669
+ break;
3670
+ case "sleep_raw_records":
3671
+ counts.sleepRawRecords += numberValue("sleepRawRecords");
3672
+ break;
3673
+ case "workout_summaries":
3674
+ case "workout_archive":
3675
+ counts.workouts += numberValue("workouts");
3676
+ break;
3677
+ case "vitals":
3678
+ counts.vitalsDaySummaries += numberValue("daySummaries");
3679
+ break;
3680
+ case "movement":
3681
+ counts.movementKnownPlaces += numberValue("knownPlaces");
3682
+ counts.movementStays += numberValue("stays");
3683
+ counts.movementTrips += numberValue("trips");
3684
+ break;
3685
+ case "screen_time":
3686
+ counts.screenTimeDaySummaries += numberValue("daySummaries");
3687
+ counts.screenTimeHourlySegments += numberValue("hourlySegments");
3688
+ break;
3689
+ }
3690
+ }
3691
+ return counts;
3692
+ }
3693
+ function syncReceiptWithChunkCounts(sync, chunks) {
3694
+ const counts = aggregateMobileSyncChunkCounts(chunks);
3695
+ return {
3696
+ ...sync,
3697
+ imported: {
3698
+ ...sync.imported,
3699
+ sleepNights: Math.max(sync.imported.sleepNights ?? 0, counts.sleepNights),
3700
+ sleepSegments: Math.max(sync.imported.sleepSegments ?? 0, counts.sleepSegments),
3701
+ sleepRawRecords: Math.max(sync.imported.sleepRawRecords ?? 0, counts.sleepRawRecords),
3702
+ workouts: Math.max(sync.imported.workouts, counts.workouts),
3703
+ vitalsDaySummaries: Math.max(sync.imported.vitalsDaySummaries ?? 0, counts.vitalsDaySummaries),
3704
+ movementKnownPlaces: Math.max(sync.imported.movementKnownPlaces ?? 0, counts.movementKnownPlaces),
3705
+ movementStays: Math.max(sync.imported.movementStays ?? 0, counts.movementStays),
3706
+ movementTrips: Math.max(sync.imported.movementTrips ?? 0, counts.movementTrips),
3707
+ screenTimeDaySummaries: Math.max(sync.imported.screenTimeDaySummaries ?? 0, counts.screenTimeDaySummaries),
3708
+ screenTimeHourlySegments: Math.max(sync.imported.screenTimeHourlySegments ?? 0, counts.screenTimeHourlySegments)
3709
+ }
3710
+ };
3711
+ }
3559
3712
  function markMobileSyncSessionFailed(session, error) {
3560
3713
  const now = nowIso();
3561
3714
  const errorMessage = error instanceof Error ? error.message : String(error);
@@ -3589,7 +3742,7 @@ export function completeMobileHealthSyncSession(syncSessionId, payload) {
3589
3742
  try {
3590
3743
  return runInTransaction(() => {
3591
3744
  validateMobileSyncExpectedCounts(syncSessionId, parsed.expectedCounts);
3592
- const { assembled, tombstones } = mergeMobileHealthSyncChunks(session, chunks);
3745
+ const { assembled, tombstones } = mergeMobileHealthSyncChunks(session, chunks, { skipImmediatelyApplied: true });
3593
3746
  const sync = ingestMobileHealthSync(assembled);
3594
3747
  const deletedWorkoutCount = applyWorkoutTombstones(pairing, tombstones);
3595
3748
  upsertMobileSyncFamilyCursors(pairing, parsed.finalCursor);
@@ -3601,7 +3754,7 @@ export function completeMobileHealthSyncSession(syncSessionId, payload) {
3601
3754
  WHERE id = ?`)
3602
3755
  .run(JSON.stringify(parsed.expectedCounts), now, now, syncSessionId);
3603
3756
  return {
3604
- ...sync,
3757
+ ...syncReceiptWithChunkCounts(sync, chunks),
3605
3758
  upload: {
3606
3759
  syncSessionId,
3607
3760
  chunks: chunks.length,